From 1cf4db96158cab2a811fb3e2775ad5b84926df1b Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Fri, 19 Aug 2022 16:30:19 +0200 Subject: [PATCH 001/182] Generate gas report for next-v* branches --- .github/workflows/checks.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 2bb494c35..5ce2c9411 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -4,6 +4,7 @@ on: push: branches: - master + - next-v* - release-v* pull_request: {} workflow_dispatch: {} From 39a752e398f5867c27c30a96c1e27ff3882a1922 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Fri, 19 Aug 2022 16:30:19 +0200 Subject: [PATCH 002/182] Generate gas report for next-v* branches --- .github/workflows/checks.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 4e92c1043..091ba910a 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -4,6 +4,7 @@ on: push: branches: - master + - next-v* - release-v* pull_request: {} workflow_dispatch: {} From 887985413cafaf67f6a61d5674d2798759350962 Mon Sep 17 00:00:00 2001 From: JulissaDantes Date: Wed, 9 Nov 2022 12:18:27 -0400 Subject: [PATCH 003/182] Use default admin role in TimelockController (#3799) Co-authored-by: Hadrien Croubois Co-authored-by: Francisco --- CHANGELOG.md | 4 ++++ contracts/governance/README.adoc | 2 -- contracts/governance/TimelockController.sol | 10 ++-------- package-lock.json | 2 +- test/governance/TimelockController.test.js | 8 ++++---- .../extensions/GovernorTimelockControl.test.js | 6 +++--- 6 files changed, 14 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 59ae8bc72..dafdf4990 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## Unreleased (Breaking) + + * `TimelockController`: Changed the role architecture to use `DEFAULT_ADMIN_ROLE` as the admin for all roles, instead of the bespoke `TIMELOCK_ADMIN_ROLE` that was used previously. This aligns with the general recommendation for `AccessControl` and makes the addition of new roles easier. Accordingly, the `admin` parameter and timelock will now be granted `DEFAULT_ADMIN_ROLE` instead of `TIMELOCK_ADMIN_ROLE`. ([#3799](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3799)) + ## Unreleased * `ReentrancyGuard`: Add a `_reentrancyGuardEntered` function to expose the guard status. ([#3714](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3714)) diff --git a/contracts/governance/README.adoc b/contracts/governance/README.adoc index f711c63cf..cdcbdf7e2 100644 --- a/contracts/governance/README.adoc +++ b/contracts/governance/README.adoc @@ -155,8 +155,6 @@ Operations status can be queried using the functions: The admins are in charge of managing proposers and executors. For the timelock to be self-governed, this role should only be given to the timelock itself. Upon deployment, the admin role can be granted to any address (in addition to the timelock itself). After further configuration and testing, this optional admin should renounce its role such that all further maintenance operations have to go through the timelock process. -This role is identified by the *TIMELOCK_ADMIN_ROLE* value: `0x5f58e3a2316349923ce3780f8d587db2d72378aed66a8261c916544fa6846ca5` - [[timelock-proposer]] ===== Proposer diff --git a/contracts/governance/TimelockController.sol b/contracts/governance/TimelockController.sol index c171475e2..34f3f12f9 100644 --- a/contracts/governance/TimelockController.sol +++ b/contracts/governance/TimelockController.sol @@ -24,7 +24,6 @@ import "../utils/Address.sol"; * _Available since v3.3._ */ contract TimelockController is AccessControl, IERC721Receiver, IERC1155Receiver { - bytes32 public constant TIMELOCK_ADMIN_ROLE = keccak256("TIMELOCK_ADMIN_ROLE"); bytes32 public constant PROPOSER_ROLE = keccak256("PROPOSER_ROLE"); bytes32 public constant EXECUTOR_ROLE = keccak256("EXECUTOR_ROLE"); bytes32 public constant CANCELLER_ROLE = keccak256("CANCELLER_ROLE"); @@ -80,17 +79,12 @@ contract TimelockController is AccessControl, IERC721Receiver, IERC1155Receiver address[] memory executors, address admin ) { - _setRoleAdmin(TIMELOCK_ADMIN_ROLE, TIMELOCK_ADMIN_ROLE); - _setRoleAdmin(PROPOSER_ROLE, TIMELOCK_ADMIN_ROLE); - _setRoleAdmin(EXECUTOR_ROLE, TIMELOCK_ADMIN_ROLE); - _setRoleAdmin(CANCELLER_ROLE, TIMELOCK_ADMIN_ROLE); - // self administration - _setupRole(TIMELOCK_ADMIN_ROLE, address(this)); + _grantRole(DEFAULT_ADMIN_ROLE, address(this)); // optional admin if (admin != address(0)) { - _setupRole(TIMELOCK_ADMIN_ROLE, admin); + _grantRole(DEFAULT_ADMIN_ROLE, admin); } // register proposers and cancellers diff --git a/package-lock.json b/package-lock.json index 05010fa86..cbc1e4654 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28547,4 +28547,4 @@ "dev": true } } -} +} \ No newline at end of file diff --git a/test/governance/TimelockController.test.js b/test/governance/TimelockController.test.js index e0a32440e..5ea5bb5fe 100644 --- a/test/governance/TimelockController.test.js +++ b/test/governance/TimelockController.test.js @@ -54,7 +54,7 @@ function genOperationBatch (targets, values, payloads, predecessor, salt) { contract('TimelockController', function (accounts) { const [ , admin, proposer, canceller, executor, other ] = accounts; - const TIMELOCK_ADMIN_ROLE = web3.utils.soliditySha3('TIMELOCK_ADMIN_ROLE'); + const DEFAULT_ADMIN_ROLE = '0x0000000000000000000000000000000000000000000000000000000000000000'; const PROPOSER_ROLE = web3.utils.soliditySha3('PROPOSER_ROLE'); const EXECUTOR_ROLE = web3.utils.soliditySha3('EXECUTOR_ROLE'); const CANCELLER_ROLE = web3.utils.soliditySha3('CANCELLER_ROLE'); @@ -84,7 +84,7 @@ contract('TimelockController', function (accounts) { it('initial state', async function () { expect(await this.mock.getMinDelay()).to.be.bignumber.equal(MINDELAY); - expect(await this.mock.TIMELOCK_ADMIN_ROLE()).to.be.equal(TIMELOCK_ADMIN_ROLE); + expect(await this.mock.DEFAULT_ADMIN_ROLE()).to.be.equal(DEFAULT_ADMIN_ROLE); expect(await this.mock.PROPOSER_ROLE()).to.be.equal(PROPOSER_ROLE); expect(await this.mock.EXECUTOR_ROLE()).to.be.equal(EXECUTOR_ROLE); expect(await this.mock.CANCELLER_ROLE()).to.be.equal(CANCELLER_ROLE); @@ -111,8 +111,8 @@ contract('TimelockController', function (accounts) { { from: other }, ); - expect(await mock.hasRole(TIMELOCK_ADMIN_ROLE, admin)).to.be.equal(false); - expect(await mock.hasRole(TIMELOCK_ADMIN_ROLE, other)).to.be.equal(false); + expect(await mock.hasRole(DEFAULT_ADMIN_ROLE, admin)).to.be.equal(false); + expect(await mock.hasRole(DEFAULT_ADMIN_ROLE, mock.address)).to.be.equal(true); }); describe('methods', function () { diff --git a/test/governance/extensions/GovernorTimelockControl.test.js b/test/governance/extensions/GovernorTimelockControl.test.js index 56d3b225c..a2fdbb565 100644 --- a/test/governance/extensions/GovernorTimelockControl.test.js +++ b/test/governance/extensions/GovernorTimelockControl.test.js @@ -15,7 +15,7 @@ const CallReceiver = artifacts.require('CallReceiverMock'); contract('GovernorTimelockControl', function (accounts) { const [ owner, voter1, voter2, voter3, voter4, other ] = accounts; - const TIMELOCK_ADMIN_ROLE = web3.utils.soliditySha3('TIMELOCK_ADMIN_ROLE'); + const DEFAULT_ADMIN_ROLE = '0x0000000000000000000000000000000000000000000000000000000000000000'; const PROPOSER_ROLE = web3.utils.soliditySha3('PROPOSER_ROLE'); const EXECUTOR_ROLE = web3.utils.soliditySha3('EXECUTOR_ROLE'); const CANCELLER_ROLE = web3.utils.soliditySha3('CANCELLER_ROLE'); @@ -46,7 +46,7 @@ contract('GovernorTimelockControl', function (accounts) { this.helper = new GovernorHelper(this.mock); - this.TIMELOCK_ADMIN_ROLE = await this.timelock.TIMELOCK_ADMIN_ROLE(); + this.DEFAULT_ADMIN_ROLE = await this.timelock.DEFAULT_ADMIN_ROLE(); this.PROPOSER_ROLE = await this.timelock.PROPOSER_ROLE(); this.EXECUTOR_ROLE = await this.timelock.EXECUTOR_ROLE(); this.CANCELLER_ROLE = await this.timelock.CANCELLER_ROLE(); @@ -59,7 +59,7 @@ contract('GovernorTimelockControl', function (accounts) { await this.timelock.grantRole(CANCELLER_ROLE, this.mock.address); await this.timelock.grantRole(CANCELLER_ROLE, owner); await this.timelock.grantRole(EXECUTOR_ROLE, constants.ZERO_ADDRESS); - await this.timelock.revokeRole(TIMELOCK_ADMIN_ROLE, deployer); + await this.timelock.revokeRole(DEFAULT_ADMIN_ROLE, deployer); await this.token.mint(owner, tokenSupply); await this.helper.delegate({ token: this.token, to: voter1, value: web3.utils.toWei('10') }, { from: owner }); From 4a9db80cb95fab85fa59238596bddf694cf79e82 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Fri, 25 Nov 2022 16:46:47 +0100 Subject: [PATCH 004/182] Remove presets (#3637) Co-authored-by: Francisco Giordano Co-authored-by: JulissaDantes --- CHANGELOG.md | 1 + contracts/token/ERC1155/README.adoc | 8 +- .../presets/ERC1155PresetMinterPauser.sol | 128 --------------- contracts/token/ERC1155/presets/README.md | 1 - contracts/token/ERC20/ERC20.sol | 1 - contracts/token/ERC20/README.adoc | 10 +- .../ERC20/presets/ERC20PresetFixedSupply.sol | 35 ----- .../ERC20/presets/ERC20PresetMinterPauser.sol | 94 ----------- contracts/token/ERC20/presets/README.md | 1 - contracts/token/ERC721/README.adoc | 8 +- .../ERC721PresetMinterPauserAutoId.sol | 140 ----------------- contracts/token/ERC721/presets/README.md | 1 - contracts/token/ERC777/README.adoc | 6 - .../presets/ERC777PresetFixedSupply.sol | 30 ---- docs/modules/ROOT/pages/erc1155.adoc | 8 - docs/modules/ROOT/pages/erc20-supply.adoc | 34 +--- docs/modules/ROOT/pages/erc20.adoc | 8 - docs/modules/ROOT/pages/erc721.adoc | 8 - package-lock.json | 3 - package.json | 3 - test/migrate-imports.test.js | 29 ---- .../presets/ERC1155PresetMinterPauser.test.js | 146 ------------------ .../presets/ERC20PresetFixedSupply.test.js | 42 ----- .../presets/ERC20PresetMinterPauser.test.js | 113 -------------- .../ERC721PresetMinterPauserAutoId.test.js | 125 --------------- .../presets/ERC777PresetFixedSupply.test.js | 49 ------ 26 files changed, 7 insertions(+), 1025 deletions(-) delete mode 100644 contracts/token/ERC1155/presets/ERC1155PresetMinterPauser.sol delete mode 100644 contracts/token/ERC1155/presets/README.md delete mode 100644 contracts/token/ERC20/presets/ERC20PresetFixedSupply.sol delete mode 100644 contracts/token/ERC20/presets/ERC20PresetMinterPauser.sol delete mode 100644 contracts/token/ERC20/presets/README.md delete mode 100644 contracts/token/ERC721/presets/ERC721PresetMinterPauserAutoId.sol delete mode 100644 contracts/token/ERC721/presets/README.md delete mode 100644 contracts/token/ERC777/presets/ERC777PresetFixedSupply.sol delete mode 100644 test/migrate-imports.test.js delete mode 100644 test/token/ERC1155/presets/ERC1155PresetMinterPauser.test.js delete mode 100644 test/token/ERC20/presets/ERC20PresetFixedSupply.test.js delete mode 100644 test/token/ERC20/presets/ERC20PresetMinterPauser.test.js delete mode 100644 test/token/ERC721/presets/ERC721PresetMinterPauserAutoId.test.js delete mode 100644 test/token/ERC777/presets/ERC777PresetFixedSupply.test.js diff --git a/CHANGELOG.md b/CHANGELOG.md index dafdf4990..8d21be5f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased (Breaking) * `TimelockController`: Changed the role architecture to use `DEFAULT_ADMIN_ROLE` as the admin for all roles, instead of the bespoke `TIMELOCK_ADMIN_ROLE` that was used previously. This aligns with the general recommendation for `AccessControl` and makes the addition of new roles easier. Accordingly, the `admin` parameter and timelock will now be granted `DEFAULT_ADMIN_ROLE` instead of `TIMELOCK_ADMIN_ROLE`. ([#3799](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3799)) +* Removed presets in favor of [OpenZeppelin Contracts Wizard](https://wizard.openzeppelin.com/). ## Unreleased diff --git a/contracts/token/ERC1155/README.adoc b/contracts/token/ERC1155/README.adoc index 13ffbdbdd..48c7faa0a 100644 --- a/contracts/token/ERC1155/README.adoc +++ b/contracts/token/ERC1155/README.adoc @@ -14,7 +14,7 @@ Additionally there are multiple custom extensions, including: * designation of addresses that can pause token transfers for all users ({ERC1155Pausable}). * destruction of own tokens ({ERC1155Burnable}). -NOTE: This core set of contracts is designed to be unopinionated, allowing developers to access the internal functions in ERC1155 (such as <>) and expose them as external functions in the way they prefer. On the other hand, xref:ROOT:erc1155.adoc#Presets[ERC1155 Presets] (such as {ERC1155PresetMinterPauser}) are designed using opinionated patterns to provide developers with ready to use, deployable contracts. +NOTE: This core set of contracts is designed to be unopinionated, allowing developers to access the internal functions in ERC1155 (such as <>) and expose them as external functions in the way they prefer. == Core @@ -38,12 +38,6 @@ NOTE: This core set of contracts is designed to be unopinionated, allowing devel {{ERC1155URIStorage}} -== Presets - -These contracts are preconfigured combinations of the above features. They can be used through inheritance or as models to copy and paste their source code. - -{{ERC1155PresetMinterPauser}} - == Utilities {{ERC1155Holder}} diff --git a/contracts/token/ERC1155/presets/ERC1155PresetMinterPauser.sol b/contracts/token/ERC1155/presets/ERC1155PresetMinterPauser.sol deleted file mode 100644 index e57fdcc39..000000000 --- a/contracts/token/ERC1155/presets/ERC1155PresetMinterPauser.sol +++ /dev/null @@ -1,128 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC1155/presets/ERC1155PresetMinterPauser.sol) - -pragma solidity ^0.8.0; - -import "../ERC1155.sol"; -import "../extensions/ERC1155Burnable.sol"; -import "../extensions/ERC1155Pausable.sol"; -import "../../../access/AccessControlEnumerable.sol"; -import "../../../utils/Context.sol"; - -/** - * @dev {ERC1155} token, including: - * - * - ability for holders to burn (destroy) their tokens - * - a minter role that allows for token minting (creation) - * - a pauser role that allows to stop all token transfers - * - * This contract uses {AccessControl} to lock permissioned functions using the - * different roles - head to its documentation for details. - * - * The account that deploys the contract will be granted the minter and pauser - * roles, as well as the default admin role, which will let it grant both minter - * and pauser roles to other accounts. - * - * _Deprecated in favor of https://wizard.openzeppelin.com/[Contracts Wizard]._ - */ -contract ERC1155PresetMinterPauser is Context, AccessControlEnumerable, ERC1155Burnable, ERC1155Pausable { - bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); - bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE"); - - /** - * @dev Grants `DEFAULT_ADMIN_ROLE`, `MINTER_ROLE`, and `PAUSER_ROLE` to the account that - * deploys the contract. - */ - constructor(string memory uri) ERC1155(uri) { - _setupRole(DEFAULT_ADMIN_ROLE, _msgSender()); - - _setupRole(MINTER_ROLE, _msgSender()); - _setupRole(PAUSER_ROLE, _msgSender()); - } - - /** - * @dev Creates `amount` new tokens for `to`, of token type `id`. - * - * See {ERC1155-_mint}. - * - * Requirements: - * - * - the caller must have the `MINTER_ROLE`. - */ - function mint( - address to, - uint256 id, - uint256 amount, - bytes memory data - ) public virtual { - require(hasRole(MINTER_ROLE, _msgSender()), "ERC1155PresetMinterPauser: must have minter role to mint"); - - _mint(to, id, amount, data); - } - - /** - * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] variant of {mint}. - */ - function mintBatch( - address to, - uint256[] memory ids, - uint256[] memory amounts, - bytes memory data - ) public virtual { - require(hasRole(MINTER_ROLE, _msgSender()), "ERC1155PresetMinterPauser: must have minter role to mint"); - - _mintBatch(to, ids, amounts, data); - } - - /** - * @dev Pauses all token transfers. - * - * See {ERC1155Pausable} and {Pausable-_pause}. - * - * Requirements: - * - * - the caller must have the `PAUSER_ROLE`. - */ - function pause() public virtual { - require(hasRole(PAUSER_ROLE, _msgSender()), "ERC1155PresetMinterPauser: must have pauser role to pause"); - _pause(); - } - - /** - * @dev Unpauses all token transfers. - * - * See {ERC1155Pausable} and {Pausable-_unpause}. - * - * Requirements: - * - * - the caller must have the `PAUSER_ROLE`. - */ - function unpause() public virtual { - require(hasRole(PAUSER_ROLE, _msgSender()), "ERC1155PresetMinterPauser: must have pauser role to unpause"); - _unpause(); - } - - /** - * @dev See {IERC165-supportsInterface}. - */ - function supportsInterface(bytes4 interfaceId) - public - view - virtual - override(AccessControlEnumerable, ERC1155) - returns (bool) - { - return super.supportsInterface(interfaceId); - } - - function _beforeTokenTransfer( - address operator, - address from, - address to, - uint256[] memory ids, - uint256[] memory amounts, - bytes memory data - ) internal virtual override(ERC1155, ERC1155Pausable) { - super._beforeTokenTransfer(operator, from, to, ids, amounts, data); - } -} diff --git a/contracts/token/ERC1155/presets/README.md b/contracts/token/ERC1155/presets/README.md deleted file mode 100644 index 468200b72..000000000 --- a/contracts/token/ERC1155/presets/README.md +++ /dev/null @@ -1 +0,0 @@ -Contract presets are now deprecated in favor of [Contracts Wizard](https://wizard.openzeppelin.com/) as a more powerful alternative. diff --git a/contracts/token/ERC20/ERC20.sol b/contracts/token/ERC20/ERC20.sol index 492e26101..c5c52691d 100644 --- a/contracts/token/ERC20/ERC20.sol +++ b/contracts/token/ERC20/ERC20.sol @@ -12,7 +12,6 @@ import "../../utils/Context.sol"; * * This implementation is agnostic to the way tokens are created. This means * that a supply mechanism has to be added in a derived contract using {_mint}. - * For a generic mechanism see {ERC20PresetMinterPauser}. * * TIP: For a detailed writeup see our guide * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How diff --git a/contracts/token/ERC20/README.adoc b/contracts/token/ERC20/README.adoc index b3f68e543..326aae684 100644 --- a/contracts/token/ERC20/README.adoc +++ b/contracts/token/ERC20/README.adoc @@ -31,7 +31,7 @@ Finally, there are some utilities to interact with ERC20 contracts in various wa * {SafeERC20}: a wrapper around the interface that eliminates the need to handle boolean return values. * {TokenTimelock}: hold tokens for a beneficiary until a specified time. -NOTE: This core set of contracts is designed to be unopinionated, allowing developers to access the internal functions in ERC20 (such as <>) and expose them as external functions in the way they prefer. On the other hand, xref:ROOT:erc20.adoc#Presets[ERC20 Presets] (such as {ERC20PresetMinterPauser}) are designed using opinionated patterns to provide developers with ready to use, deployable contracts. +NOTE: This core set of contracts is designed to be unopinionated, allowing developers to access the internal functions in ERC20 (such as <>) and expose them as external functions in the way they prefer. == Core @@ -63,14 +63,6 @@ NOTE: This core set of contracts is designed to be unopinionated, allowing devel {{ERC4626}} -== Presets - -These contracts are preconfigured combinations of the above features. They can be used through inheritance or as models to copy and paste their source code. - -{{ERC20PresetMinterPauser}} - -{{ERC20PresetFixedSupply}} - == Utilities {{SafeERC20}} diff --git a/contracts/token/ERC20/presets/ERC20PresetFixedSupply.sol b/contracts/token/ERC20/presets/ERC20PresetFixedSupply.sol deleted file mode 100644 index 52afef3ab..000000000 --- a/contracts/token/ERC20/presets/ERC20PresetFixedSupply.sol +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC20/presets/ERC20PresetFixedSupply.sol) -pragma solidity ^0.8.0; - -import "../extensions/ERC20Burnable.sol"; - -/** - * @dev {ERC20} token, including: - * - * - Preminted initial supply - * - Ability for holders to burn (destroy) their tokens - * - No access control mechanism (for minting/pausing) and hence no governance - * - * This contract uses {ERC20Burnable} to include burn capabilities - head to - * its documentation for details. - * - * _Available since v3.4._ - * - * _Deprecated in favor of https://wizard.openzeppelin.com/[Contracts Wizard]._ - */ -contract ERC20PresetFixedSupply is ERC20Burnable { - /** - * @dev Mints `initialSupply` amount of token and transfers them to `owner`. - * - * See {ERC20-constructor}. - */ - constructor( - string memory name, - string memory symbol, - uint256 initialSupply, - address owner - ) ERC20(name, symbol) { - _mint(owner, initialSupply); - } -} diff --git a/contracts/token/ERC20/presets/ERC20PresetMinterPauser.sol b/contracts/token/ERC20/presets/ERC20PresetMinterPauser.sol deleted file mode 100644 index e711a894f..000000000 --- a/contracts/token/ERC20/presets/ERC20PresetMinterPauser.sol +++ /dev/null @@ -1,94 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC20/presets/ERC20PresetMinterPauser.sol) - -pragma solidity ^0.8.0; - -import "../ERC20.sol"; -import "../extensions/ERC20Burnable.sol"; -import "../extensions/ERC20Pausable.sol"; -import "../../../access/AccessControlEnumerable.sol"; -import "../../../utils/Context.sol"; - -/** - * @dev {ERC20} token, including: - * - * - ability for holders to burn (destroy) their tokens - * - a minter role that allows for token minting (creation) - * - a pauser role that allows to stop all token transfers - * - * This contract uses {AccessControl} to lock permissioned functions using the - * different roles - head to its documentation for details. - * - * The account that deploys the contract will be granted the minter and pauser - * roles, as well as the default admin role, which will let it grant both minter - * and pauser roles to other accounts. - * - * _Deprecated in favor of https://wizard.openzeppelin.com/[Contracts Wizard]._ - */ -contract ERC20PresetMinterPauser is Context, AccessControlEnumerable, ERC20Burnable, ERC20Pausable { - bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); - bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE"); - - /** - * @dev Grants `DEFAULT_ADMIN_ROLE`, `MINTER_ROLE` and `PAUSER_ROLE` to the - * account that deploys the contract. - * - * See {ERC20-constructor}. - */ - constructor(string memory name, string memory symbol) ERC20(name, symbol) { - _setupRole(DEFAULT_ADMIN_ROLE, _msgSender()); - - _setupRole(MINTER_ROLE, _msgSender()); - _setupRole(PAUSER_ROLE, _msgSender()); - } - - /** - * @dev Creates `amount` new tokens for `to`. - * - * See {ERC20-_mint}. - * - * Requirements: - * - * - the caller must have the `MINTER_ROLE`. - */ - function mint(address to, uint256 amount) public virtual { - require(hasRole(MINTER_ROLE, _msgSender()), "ERC20PresetMinterPauser: must have minter role to mint"); - _mint(to, amount); - } - - /** - * @dev Pauses all token transfers. - * - * See {ERC20Pausable} and {Pausable-_pause}. - * - * Requirements: - * - * - the caller must have the `PAUSER_ROLE`. - */ - function pause() public virtual { - require(hasRole(PAUSER_ROLE, _msgSender()), "ERC20PresetMinterPauser: must have pauser role to pause"); - _pause(); - } - - /** - * @dev Unpauses all token transfers. - * - * See {ERC20Pausable} and {Pausable-_unpause}. - * - * Requirements: - * - * - the caller must have the `PAUSER_ROLE`. - */ - function unpause() public virtual { - require(hasRole(PAUSER_ROLE, _msgSender()), "ERC20PresetMinterPauser: must have pauser role to unpause"); - _unpause(); - } - - function _beforeTokenTransfer( - address from, - address to, - uint256 amount - ) internal virtual override(ERC20, ERC20Pausable) { - super._beforeTokenTransfer(from, to, amount); - } -} diff --git a/contracts/token/ERC20/presets/README.md b/contracts/token/ERC20/presets/README.md deleted file mode 100644 index 468200b72..000000000 --- a/contracts/token/ERC20/presets/README.md +++ /dev/null @@ -1 +0,0 @@ -Contract presets are now deprecated in favor of [Contracts Wizard](https://wizard.openzeppelin.com/) as a more powerful alternative. diff --git a/contracts/token/ERC721/README.adoc b/contracts/token/ERC721/README.adoc index b3377afef..253bbd950 100644 --- a/contracts/token/ERC721/README.adoc +++ b/contracts/token/ERC721/README.adoc @@ -29,7 +29,7 @@ Additionally there are a few of other extensions: * {ERC721Pausable}: A primitive to pause contract operation. * {ERC721Burnable}: A way for token holders to burn their own tokens. -NOTE: This core set of contracts is designed to be unopinionated, allowing developers to access the internal functions in ERC721 (such as <>) and expose them as external functions in the way they prefer. On the other hand, xref:ROOT:erc721.adoc#Presets[ERC721 Presets] (such as {ERC721PresetMinterPauserAutoId}) are designed using opinionated patterns to provide developers with ready to use, deployable contracts. +NOTE: This core set of contracts is designed to be unopinionated, allowing developers to access the internal functions in ERC721 (such as <>) and expose them as external functions in the way they prefer. == Core @@ -59,12 +59,6 @@ NOTE: This core set of contracts is designed to be unopinionated, allowing devel {{ERC721Royalty}} -== Presets - -These contracts are preconfigured combinations of the above features. They can be used through inheritance or as models to copy and paste their source code. - -{{ERC721PresetMinterPauserAutoId}} - == Utilities {{ERC721Holder}} diff --git a/contracts/token/ERC721/presets/ERC721PresetMinterPauserAutoId.sol b/contracts/token/ERC721/presets/ERC721PresetMinterPauserAutoId.sol deleted file mode 100644 index c2ff99d7e..000000000 --- a/contracts/token/ERC721/presets/ERC721PresetMinterPauserAutoId.sol +++ /dev/null @@ -1,140 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC721/presets/ERC721PresetMinterPauserAutoId.sol) - -pragma solidity ^0.8.0; - -import "../ERC721.sol"; -import "../extensions/ERC721Enumerable.sol"; -import "../extensions/ERC721Burnable.sol"; -import "../extensions/ERC721Pausable.sol"; -import "../../../access/AccessControlEnumerable.sol"; -import "../../../utils/Context.sol"; -import "../../../utils/Counters.sol"; - -/** - * @dev {ERC721} token, including: - * - * - ability for holders to burn (destroy) their tokens - * - a minter role that allows for token minting (creation) - * - a pauser role that allows to stop all token transfers - * - token ID and URI autogeneration - * - * This contract uses {AccessControl} to lock permissioned functions using the - * different roles - head to its documentation for details. - * - * The account that deploys the contract will be granted the minter and pauser - * roles, as well as the default admin role, which will let it grant both minter - * and pauser roles to other accounts. - * - * _Deprecated in favor of https://wizard.openzeppelin.com/[Contracts Wizard]._ - */ -contract ERC721PresetMinterPauserAutoId is - Context, - AccessControlEnumerable, - ERC721Enumerable, - ERC721Burnable, - ERC721Pausable -{ - using Counters for Counters.Counter; - - bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); - bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE"); - - Counters.Counter private _tokenIdTracker; - - string private _baseTokenURI; - - /** - * @dev Grants `DEFAULT_ADMIN_ROLE`, `MINTER_ROLE` and `PAUSER_ROLE` to the - * account that deploys the contract. - * - * Token URIs will be autogenerated based on `baseURI` and their token IDs. - * See {ERC721-tokenURI}. - */ - constructor( - string memory name, - string memory symbol, - string memory baseTokenURI - ) ERC721(name, symbol) { - _baseTokenURI = baseTokenURI; - - _setupRole(DEFAULT_ADMIN_ROLE, _msgSender()); - - _setupRole(MINTER_ROLE, _msgSender()); - _setupRole(PAUSER_ROLE, _msgSender()); - } - - function _baseURI() internal view virtual override returns (string memory) { - return _baseTokenURI; - } - - /** - * @dev Creates a new token for `to`. Its token ID will be automatically - * assigned (and available on the emitted {IERC721-Transfer} event), and the token - * URI autogenerated based on the base URI passed at construction. - * - * See {ERC721-_mint}. - * - * Requirements: - * - * - the caller must have the `MINTER_ROLE`. - */ - function mint(address to) public virtual { - require(hasRole(MINTER_ROLE, _msgSender()), "ERC721PresetMinterPauserAutoId: must have minter role to mint"); - - // We cannot just use balanceOf to create the new tokenId because tokens - // can be burned (destroyed), so we need a separate counter. - _mint(to, _tokenIdTracker.current()); - _tokenIdTracker.increment(); - } - - /** - * @dev Pauses all token transfers. - * - * See {ERC721Pausable} and {Pausable-_pause}. - * - * Requirements: - * - * - the caller must have the `PAUSER_ROLE`. - */ - function pause() public virtual { - require(hasRole(PAUSER_ROLE, _msgSender()), "ERC721PresetMinterPauserAutoId: must have pauser role to pause"); - _pause(); - } - - /** - * @dev Unpauses all token transfers. - * - * See {ERC721Pausable} and {Pausable-_unpause}. - * - * Requirements: - * - * - the caller must have the `PAUSER_ROLE`. - */ - function unpause() public virtual { - require(hasRole(PAUSER_ROLE, _msgSender()), "ERC721PresetMinterPauserAutoId: must have pauser role to unpause"); - _unpause(); - } - - function _beforeTokenTransfer( - address from, - address to, - uint256 firstTokenId, - uint256 batchSize - ) internal virtual override(ERC721, ERC721Enumerable, ERC721Pausable) { - super._beforeTokenTransfer(from, to, firstTokenId, batchSize); - } - - /** - * @dev See {IERC165-supportsInterface}. - */ - function supportsInterface(bytes4 interfaceId) - public - view - virtual - override(AccessControlEnumerable, ERC721, ERC721Enumerable) - returns (bool) - { - return super.supportsInterface(interfaceId); - } -} diff --git a/contracts/token/ERC721/presets/README.md b/contracts/token/ERC721/presets/README.md deleted file mode 100644 index 468200b72..000000000 --- a/contracts/token/ERC721/presets/README.md +++ /dev/null @@ -1 +0,0 @@ -Contract presets are now deprecated in favor of [Contracts Wizard](https://wizard.openzeppelin.com/) as a more powerful alternative. diff --git a/contracts/token/ERC777/README.adoc b/contracts/token/ERC777/README.adoc index 5012a3110..09c9e58ad 100644 --- a/contracts/token/ERC777/README.adoc +++ b/contracts/token/ERC777/README.adoc @@ -22,9 +22,3 @@ Additionally there are interfaces used to develop contracts that react to token {{IERC777Sender}} {{IERC777Recipient}} - -== Presets - -These contracts are preconfigured combinations of features. They can be used through inheritance or as models to copy and paste their source code. - -{{ERC777PresetFixedSupply}} diff --git a/contracts/token/ERC777/presets/ERC777PresetFixedSupply.sol b/contracts/token/ERC777/presets/ERC777PresetFixedSupply.sol deleted file mode 100644 index 8bd4b79aa..000000000 --- a/contracts/token/ERC777/presets/ERC777PresetFixedSupply.sol +++ /dev/null @@ -1,30 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts v4.4.1 (token/ERC777/presets/ERC777PresetFixedSupply.sol) -pragma solidity ^0.8.0; - -import "../ERC777.sol"; - -/** - * @dev {ERC777} token, including: - * - * - Preminted initial supply - * - No access control mechanism (for minting/pausing) and hence no governance - * - * _Available since v3.4._ - */ -contract ERC777PresetFixedSupply is ERC777 { - /** - * @dev Mints `initialSupply` amount of token and transfers them to `owner`. - * - * See {ERC777-constructor}. - */ - constructor( - string memory name, - string memory symbol, - address[] memory defaultOperators, - uint256 initialSupply, - address owner - ) ERC777(name, symbol, defaultOperators) { - _mint(owner, initialSupply, "", ""); - } -} diff --git a/docs/modules/ROOT/pages/erc1155.adoc b/docs/modules/ROOT/pages/erc1155.adoc index 0ca933865..a01542c5a 100644 --- a/docs/modules/ROOT/pages/erc1155.adoc +++ b/docs/modules/ROOT/pages/erc1155.adoc @@ -143,11 +143,3 @@ contract MyContract is ERC1155Holder { ---- We can also implement more complex scenarios using the xref:api:token/ERC1155.adoc#IERC1155Receiver-onERC1155Received-address-address-uint256-uint256-bytes-[`onERC1155Received`] and xref:api:token/ERC1155.adoc#IERC1155Receiver-onERC1155BatchReceived-address-address-uint256---uint256---bytes-[`onERC1155BatchReceived`] functions. - -[[Presets]] -== Preset ERC1155 contract -A preset ERC1155 is available, https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v4.7/contracts/token/ERC1155/presets/ERC1155PresetMinterPauser.sol[`ERC1155PresetMinterPauser`]. It is preset to allow for token minting (create) - including batch minting, stop all token transfers (pause) and allow holders to burn (destroy) their tokens. The contract uses xref:access-control.adoc[Access Control] to control access to the minting and pausing functionality. The account that deploys the contract will be granted the minter and pauser roles, as well as the default admin role. - -This contract is ready to deploy without having to write any Solidity code. It can be used as-is for quick prototyping and testing, but is also suitable for production environments. - -NOTE: Contract presets are now deprecated in favor of https://wizard.openzeppelin.com[Contracts Wizard] as a more powerful alternative. diff --git a/docs/modules/ROOT/pages/erc20-supply.adoc b/docs/modules/ROOT/pages/erc20-supply.adoc index 31b0cd956..44cbd73dd 100644 --- a/docs/modules/ROOT/pages/erc20-supply.adoc +++ b/docs/modules/ROOT/pages/erc20-supply.adoc @@ -54,40 +54,12 @@ contract ERC20WithMinerReward is ERC20 { As we can see, `_mint` makes it super easy to do this correctly. -[[modularizing-the-mechanism]] -== Modularizing the Mechanism - -There is one supply mechanism already included in Contracts: `ERC20PresetMinterPauser`. This is a generic mechanism in which a set of accounts is assigned the `minter` role, granting them the permission to call a `mint` function, an external version of `_mint`. - -This can be used for centralized minting, where an externally owned account (i.e. someone with a pair of cryptographic keys) decides how much supply to create and for whom. There are very legitimate use cases for this mechanism, such as https://medium.com/reserve-currency/why-another-stablecoin-866f774afede#3aea[traditional asset-backed stablecoins]. - -The accounts with the minter role don't need to be externally owned, though, and can just as well be smart contracts that implement a trustless mechanism. We can in fact implement the same behavior as the previous section. - -[source,solidity] ----- -contract MinerRewardMinter { - ERC20PresetMinterPauser _token; - - constructor(ERC20PresetMinterPauser token) { - _token = token; - } - - function mintMinerReward() public { - _token.mint(block.coinbase, 1000); - } -} ----- - -This contract, when initialized with an `ERC20PresetMinterPauser` instance, and granted the `minter` role for that contract, will result in exactly the same behavior implemented in the previous section. What is interesting about using `ERC20PresetMinterPauser` is that we can easily combine multiple supply mechanisms by assigning the role to multiple contracts, and moreover that we can do this dynamically. - -TIP: To learn more about roles and permissioned systems, head to our xref:access-control.adoc[Access Control guide]. - [[automating-the-reward]] == Automating the Reward -So far our supply mechanisms were triggered manually, but `ERC20` also allows us to extend the core functionality of the token through the xref:api:token/ERC20.adoc#ERC20-_beforeTokenTransfer-address-address-uint256-[`_beforeTokenTransfer`] hook (see xref:extending-contracts.adoc#using-hooks[Using Hooks]). +So far our supply mechanism was triggered manually, but `ERC20` also allows us to extend the core functionality of the token through the xref:api:token/ERC20.adoc#ERC20-_beforeTokenTransfer-address-address-uint256-[`_beforeTokenTransfer`] hook (see xref:extending-contracts.adoc#using-hooks[Using Hooks]). -Adding to the supply mechanism from previous sections, we can use this hook to mint a miner reward for every token transfer that is included in the blockchain. +Adding to the supply mechanism from the previous section, we can use this hook to mint a miner reward for every token transfer that is included in the blockchain. [source,solidity] ---- @@ -110,4 +82,4 @@ contract ERC20WithAutoMinerReward is ERC20 { [[wrapping-up]] == Wrapping Up -We've seen two ways to implement ERC20 supply mechanisms: internally through `_mint`, and externally through `ERC20PresetMinterPauser`. Hopefully this has helped you understand how to use OpenZeppelin Contracts and some of the design principles behind it, and you can apply them to your own smart contracts. +We've seen how to implement a ERC20 supply mechanism: internally through `_mint`. Hopefully this has helped you understand how to use OpenZeppelin Contracts and some of the design principles behind it, and you can apply them to your own smart contracts. diff --git a/docs/modules/ROOT/pages/erc20.adoc b/docs/modules/ROOT/pages/erc20.adoc index bcb510c51..e72491ab6 100644 --- a/docs/modules/ROOT/pages/erc20.adoc +++ b/docs/modules/ROOT/pages/erc20.adoc @@ -75,11 +75,3 @@ So if you want to send `5` tokens using a token contract with 18 decimals, the m ```solidity transfer(recipient, 5 * (10 ** 18)); ``` - -[[Presets]] -== Preset ERC20 contract -A preset ERC20 is available, https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v4.7/contracts/token/ERC20/presets/ERC20PresetMinterPauser.sol[`ERC20PresetMinterPauser`]. It is preset to allow for token minting (create), stop all token transfers (pause) and allow holders to burn (destroy) their tokens. The contract uses xref:access-control.adoc[Access Control] to control access to the minting and pausing functionality. The account that deploys the contract will be granted the minter and pauser roles, as well as the default admin role. - -This contract is ready to deploy without having to write any Solidity code. It can be used as-is for quick prototyping and testing, but is also suitable for production environments. - -NOTE: Contract presets are now deprecated in favor of https://wizard.openzeppelin.com[Contracts Wizard] as a more powerful alternative. diff --git a/docs/modules/ROOT/pages/erc721.adoc b/docs/modules/ROOT/pages/erc721.adoc index 56a52e874..812630ab3 100644 --- a/docs/modules/ROOT/pages/erc721.adoc +++ b/docs/modules/ROOT/pages/erc721.adoc @@ -80,11 +80,3 @@ For more information about the `tokenURI` metadata JSON Schema, check out the ht NOTE: You'll notice that the item's information is included in the metadata, but that information isn't on-chain! So a game developer could change the underlying metadata, changing the rules of the game! TIP: If you'd like to put all item information on-chain, you can extend ERC721 to do so (though it will be rather costly) by providing a xref:utilities.adoc#base64[`Base64`] Data URI with the JSON schema encoded. You could also leverage IPFS to store the tokenURI information, but these techniques are out of the scope of this overview guide. - -[[Presets]] -== Preset ERC721 contract -A preset ERC721 is available, https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v4.7/contracts/token/ERC721/presets/ERC721PresetMinterPauserAutoId.sol[`ERC721PresetMinterPauserAutoId`]. It is preconfigured with token minting (creation) with token ID and URI auto generation, the ability to stop all token transfers (pause), and it allows holders to burn (destroy) their tokens. The contract uses xref:access-control.adoc[Access Control] to control access to the minting and pausing functionality. The account that deploys the contract will be granted the minter and pauser roles, as well as the default admin role. - -This contract is ready to deploy without having to write any Solidity code. It can be used as-is for quick prototyping and testing but is also suitable for production environments. - -NOTE: Contract presets are now deprecated in favor of https://wizard.openzeppelin.com[Contracts Wizard] as a more powerful alternative. diff --git a/package-lock.json b/package-lock.json index cbc1e4654..38c0a15e7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,9 +8,6 @@ "name": "openzeppelin-solidity", "version": "4.7.0", "license": "MIT", - "bin": { - "openzeppelin-contracts-migrate-imports": "scripts/migrate-imports.js" - }, "devDependencies": { "@nomicfoundation/hardhat-network-helpers": "^1.0.3", "@nomiclabs/hardhat-truffle5": "^2.0.5", diff --git a/package.json b/package.json index 09fadfc70..11f643bd0 100644 --- a/package.json +++ b/package.json @@ -7,9 +7,6 @@ "/build/contracts/*.json", "!/contracts/mocks/**/*" ], - "bin": { - "openzeppelin-contracts-migrate-imports": "scripts/migrate-imports.js" - }, "scripts": { "compile": "hardhat compile", "coverage": "env COVERAGE=true hardhat coverage", diff --git a/test/migrate-imports.test.js b/test/migrate-imports.test.js deleted file mode 100644 index 639767c25..000000000 --- a/test/migrate-imports.test.js +++ /dev/null @@ -1,29 +0,0 @@ -const path = require('path'); -const { promises: fs, constants: { F_OK } } = require('fs'); -const { expect } = require('chai'); - -const { pathUpdates, updateImportPaths, getUpgradeablePath } = require('../scripts/migrate-imports.js'); - -describe('migrate-imports.js', function () { - it('every new path exists', async function () { - for (const p of Object.values(pathUpdates)) { - try { - await fs.access(path.join('contracts', p), F_OK); - } catch (e) { - await fs.access(path.join('contracts', getUpgradeablePath(p)), F_OK); - } - } - }); - - it('replaces import paths in a file', async function () { - const source = ` -import '@openzeppelin/contracts/math/Math.sol'; -import '@openzeppelin/contracts-upgradeable/math/MathUpgradeable.sol'; - `; - const expected = ` -import '@openzeppelin/contracts/utils/math/Math.sol'; -import '@openzeppelin/contracts-upgradeable/utils/math/MathUpgradeable.sol'; - `; - expect(updateImportPaths(source)).to.equal(expected); - }); -}); diff --git a/test/token/ERC1155/presets/ERC1155PresetMinterPauser.test.js b/test/token/ERC1155/presets/ERC1155PresetMinterPauser.test.js deleted file mode 100644 index a8d83d123..000000000 --- a/test/token/ERC1155/presets/ERC1155PresetMinterPauser.test.js +++ /dev/null @@ -1,146 +0,0 @@ -const { BN, constants, expectEvent, expectRevert } = require('@openzeppelin/test-helpers'); -const { ZERO_ADDRESS } = constants; -const { shouldSupportInterfaces } = require('../../../utils/introspection/SupportsInterface.behavior'); - -const { expect } = require('chai'); - -const ERC1155PresetMinterPauser = artifacts.require('ERC1155PresetMinterPauser'); - -contract('ERC1155PresetMinterPauser', function (accounts) { - const [ deployer, other ] = accounts; - - const firstTokenId = new BN('845'); - const firstTokenIdAmount = new BN('5000'); - - const secondTokenId = new BN('48324'); - const secondTokenIdAmount = new BN('77875'); - - const DEFAULT_ADMIN_ROLE = '0x0000000000000000000000000000000000000000000000000000000000000000'; - const MINTER_ROLE = web3.utils.soliditySha3('MINTER_ROLE'); - const PAUSER_ROLE = web3.utils.soliditySha3('PAUSER_ROLE'); - - const uri = 'https://token.com'; - - beforeEach(async function () { - this.token = await ERC1155PresetMinterPauser.new(uri, { from: deployer }); - }); - - shouldSupportInterfaces(['ERC1155', 'AccessControl', 'AccessControlEnumerable']); - - it('deployer has the default admin role', async function () { - expect(await this.token.getRoleMemberCount(DEFAULT_ADMIN_ROLE)).to.be.bignumber.equal('1'); - expect(await this.token.getRoleMember(DEFAULT_ADMIN_ROLE, 0)).to.equal(deployer); - }); - - it('deployer has the minter role', async function () { - expect(await this.token.getRoleMemberCount(MINTER_ROLE)).to.be.bignumber.equal('1'); - expect(await this.token.getRoleMember(MINTER_ROLE, 0)).to.equal(deployer); - }); - - it('deployer has the pauser role', async function () { - expect(await this.token.getRoleMemberCount(PAUSER_ROLE)).to.be.bignumber.equal('1'); - expect(await this.token.getRoleMember(PAUSER_ROLE, 0)).to.equal(deployer); - }); - - it('minter and pauser role admin is the default admin', async function () { - expect(await this.token.getRoleAdmin(MINTER_ROLE)).to.equal(DEFAULT_ADMIN_ROLE); - expect(await this.token.getRoleAdmin(PAUSER_ROLE)).to.equal(DEFAULT_ADMIN_ROLE); - }); - - describe('minting', function () { - it('deployer can mint tokens', async function () { - const receipt = await this.token.mint(other, firstTokenId, firstTokenIdAmount, '0x', { from: deployer }); - expectEvent(receipt, 'TransferSingle', - { operator: deployer, from: ZERO_ADDRESS, to: other, value: firstTokenIdAmount, id: firstTokenId }, - ); - - expect(await this.token.balanceOf(other, firstTokenId)).to.be.bignumber.equal(firstTokenIdAmount); - }); - - it('other accounts cannot mint tokens', async function () { - await expectRevert( - this.token.mint(other, firstTokenId, firstTokenIdAmount, '0x', { from: other }), - 'ERC1155PresetMinterPauser: must have minter role to mint', - ); - }); - }); - - describe('batched minting', function () { - it('deployer can batch mint tokens', async function () { - const receipt = await this.token.mintBatch( - other, [firstTokenId, secondTokenId], [firstTokenIdAmount, secondTokenIdAmount], '0x', { from: deployer }, - ); - - expectEvent(receipt, 'TransferBatch', - { operator: deployer, from: ZERO_ADDRESS, to: other }, - ); - - expect(await this.token.balanceOf(other, firstTokenId)).to.be.bignumber.equal(firstTokenIdAmount); - }); - - it('other accounts cannot batch mint tokens', async function () { - await expectRevert( - this.token.mintBatch( - other, [firstTokenId, secondTokenId], [firstTokenIdAmount, secondTokenIdAmount], '0x', { from: other }, - ), - 'ERC1155PresetMinterPauser: must have minter role to mint', - ); - }); - }); - - describe('pausing', function () { - it('deployer can pause', async function () { - const receipt = await this.token.pause({ from: deployer }); - expectEvent(receipt, 'Paused', { account: deployer }); - - expect(await this.token.paused()).to.equal(true); - }); - - it('deployer can unpause', async function () { - await this.token.pause({ from: deployer }); - - const receipt = await this.token.unpause({ from: deployer }); - expectEvent(receipt, 'Unpaused', { account: deployer }); - - expect(await this.token.paused()).to.equal(false); - }); - - it('cannot mint while paused', async function () { - await this.token.pause({ from: deployer }); - - await expectRevert( - this.token.mint(other, firstTokenId, firstTokenIdAmount, '0x', { from: deployer }), - 'ERC1155Pausable: token transfer while paused', - ); - }); - - it('other accounts cannot pause', async function () { - await expectRevert( - this.token.pause({ from: other }), - 'ERC1155PresetMinterPauser: must have pauser role to pause', - ); - }); - - it('other accounts cannot unpause', async function () { - await this.token.pause({ from: deployer }); - - await expectRevert( - this.token.unpause({ from: other }), - 'ERC1155PresetMinterPauser: must have pauser role to unpause', - ); - }); - }); - - describe('burning', function () { - it('holders can burn their tokens', async function () { - await this.token.mint(other, firstTokenId, firstTokenIdAmount, '0x', { from: deployer }); - - const receipt = await this.token.burn(other, firstTokenId, firstTokenIdAmount.subn(1), { from: other }); - expectEvent(receipt, 'TransferSingle', - { operator: other, from: other, to: ZERO_ADDRESS, value: firstTokenIdAmount.subn(1), id: firstTokenId }, - ); - - expect(await this.token.balanceOf(other, firstTokenId)).to.be.bignumber.equal('1'); - }); - }); -}); diff --git a/test/token/ERC20/presets/ERC20PresetFixedSupply.test.js b/test/token/ERC20/presets/ERC20PresetFixedSupply.test.js deleted file mode 100644 index f1d0b53a0..000000000 --- a/test/token/ERC20/presets/ERC20PresetFixedSupply.test.js +++ /dev/null @@ -1,42 +0,0 @@ -const { BN, constants, expectEvent } = require('@openzeppelin/test-helpers'); -const { ZERO_ADDRESS } = constants; - -const { expect } = require('chai'); - -const ERC20PresetFixedSupply = artifacts.require('ERC20PresetFixedSupply'); - -contract('ERC20PresetFixedSupply', function (accounts) { - const [deployer, owner] = accounts; - - const name = 'PresetFixedSupply'; - const symbol = 'PFS'; - - const initialSupply = new BN('50000'); - const amount = new BN('10000'); - - before(async function () { - this.token = await ERC20PresetFixedSupply.new(name, symbol, initialSupply, owner, { from: deployer }); - }); - - it('deployer has the balance equal to initial supply', async function () { - expect(await this.token.balanceOf(owner)).to.be.bignumber.equal(initialSupply); - }); - - it('total supply is equal to initial supply', async function () { - expect(await this.token.totalSupply()).to.be.bignumber.equal(initialSupply); - }); - - describe('burning', function () { - it('holders can burn their tokens', async function () { - const remainingBalance = initialSupply.sub(amount); - const receipt = await this.token.burn(amount, { from: owner }); - expectEvent(receipt, 'Transfer', { from: owner, to: ZERO_ADDRESS, value: amount }); - expect(await this.token.balanceOf(owner)).to.be.bignumber.equal(remainingBalance); - }); - - it('decrements totalSupply', async function () { - const expectedSupply = initialSupply.sub(amount); - expect(await this.token.totalSupply()).to.be.bignumber.equal(expectedSupply); - }); - }); -}); diff --git a/test/token/ERC20/presets/ERC20PresetMinterPauser.test.js b/test/token/ERC20/presets/ERC20PresetMinterPauser.test.js deleted file mode 100644 index c143790f4..000000000 --- a/test/token/ERC20/presets/ERC20PresetMinterPauser.test.js +++ /dev/null @@ -1,113 +0,0 @@ -const { BN, constants, expectEvent, expectRevert } = require('@openzeppelin/test-helpers'); -const { ZERO_ADDRESS } = constants; - -const { expect } = require('chai'); - -const ERC20PresetMinterPauser = artifacts.require('ERC20PresetMinterPauser'); - -contract('ERC20PresetMinterPauser', function (accounts) { - const [ deployer, other ] = accounts; - - const name = 'MinterPauserToken'; - const symbol = 'DRT'; - - const amount = new BN('5000'); - - const DEFAULT_ADMIN_ROLE = '0x0000000000000000000000000000000000000000000000000000000000000000'; - const MINTER_ROLE = web3.utils.soliditySha3('MINTER_ROLE'); - const PAUSER_ROLE = web3.utils.soliditySha3('PAUSER_ROLE'); - - beforeEach(async function () { - this.token = await ERC20PresetMinterPauser.new(name, symbol, { from: deployer }); - }); - - it('deployer has the default admin role', async function () { - expect(await this.token.getRoleMemberCount(DEFAULT_ADMIN_ROLE)).to.be.bignumber.equal('1'); - expect(await this.token.getRoleMember(DEFAULT_ADMIN_ROLE, 0)).to.equal(deployer); - }); - - it('deployer has the minter role', async function () { - expect(await this.token.getRoleMemberCount(MINTER_ROLE)).to.be.bignumber.equal('1'); - expect(await this.token.getRoleMember(MINTER_ROLE, 0)).to.equal(deployer); - }); - - it('deployer has the pauser role', async function () { - expect(await this.token.getRoleMemberCount(PAUSER_ROLE)).to.be.bignumber.equal('1'); - expect(await this.token.getRoleMember(PAUSER_ROLE, 0)).to.equal(deployer); - }); - - it('minter and pauser role admin is the default admin', async function () { - expect(await this.token.getRoleAdmin(MINTER_ROLE)).to.equal(DEFAULT_ADMIN_ROLE); - expect(await this.token.getRoleAdmin(PAUSER_ROLE)).to.equal(DEFAULT_ADMIN_ROLE); - }); - - describe('minting', function () { - it('deployer can mint tokens', async function () { - const receipt = await this.token.mint(other, amount, { from: deployer }); - expectEvent(receipt, 'Transfer', { from: ZERO_ADDRESS, to: other, value: amount }); - - expect(await this.token.balanceOf(other)).to.be.bignumber.equal(amount); - }); - - it('other accounts cannot mint tokens', async function () { - await expectRevert( - this.token.mint(other, amount, { from: other }), - 'ERC20PresetMinterPauser: must have minter role to mint', - ); - }); - }); - - describe('pausing', function () { - it('deployer can pause', async function () { - const receipt = await this.token.pause({ from: deployer }); - expectEvent(receipt, 'Paused', { account: deployer }); - - expect(await this.token.paused()).to.equal(true); - }); - - it('deployer can unpause', async function () { - await this.token.pause({ from: deployer }); - - const receipt = await this.token.unpause({ from: deployer }); - expectEvent(receipt, 'Unpaused', { account: deployer }); - - expect(await this.token.paused()).to.equal(false); - }); - - it('cannot mint while paused', async function () { - await this.token.pause({ from: deployer }); - - await expectRevert( - this.token.mint(other, amount, { from: deployer }), - 'ERC20Pausable: token transfer while paused', - ); - }); - - it('other accounts cannot pause', async function () { - await expectRevert( - this.token.pause({ from: other }), - 'ERC20PresetMinterPauser: must have pauser role to pause', - ); - }); - - it('other accounts cannot unpause', async function () { - await this.token.pause({ from: deployer }); - - await expectRevert( - this.token.unpause({ from: other }), - 'ERC20PresetMinterPauser: must have pauser role to unpause', - ); - }); - }); - - describe('burning', function () { - it('holders can burn their tokens', async function () { - await this.token.mint(other, amount, { from: deployer }); - - const receipt = await this.token.burn(amount.subn(1), { from: other }); - expectEvent(receipt, 'Transfer', { from: other, to: ZERO_ADDRESS, value: amount.subn(1) }); - - expect(await this.token.balanceOf(other)).to.be.bignumber.equal('1'); - }); - }); -}); diff --git a/test/token/ERC721/presets/ERC721PresetMinterPauserAutoId.test.js b/test/token/ERC721/presets/ERC721PresetMinterPauserAutoId.test.js deleted file mode 100644 index 4ad73552a..000000000 --- a/test/token/ERC721/presets/ERC721PresetMinterPauserAutoId.test.js +++ /dev/null @@ -1,125 +0,0 @@ -const { BN, constants, expectEvent, expectRevert } = require('@openzeppelin/test-helpers'); -const { ZERO_ADDRESS } = constants; -const { shouldSupportInterfaces } = require('../../../utils/introspection/SupportsInterface.behavior'); - -const { expect } = require('chai'); - -const ERC721PresetMinterPauserAutoId = artifacts.require('ERC721PresetMinterPauserAutoId'); - -contract('ERC721PresetMinterPauserAutoId', function (accounts) { - const [ deployer, other ] = accounts; - - const name = 'MinterAutoIDToken'; - const symbol = 'MAIT'; - const baseURI = 'my.app/'; - - const DEFAULT_ADMIN_ROLE = '0x0000000000000000000000000000000000000000000000000000000000000000'; - const MINTER_ROLE = web3.utils.soliditySha3('MINTER_ROLE'); - - beforeEach(async function () { - this.token = await ERC721PresetMinterPauserAutoId.new(name, symbol, baseURI, { from: deployer }); - }); - - shouldSupportInterfaces(['ERC721', 'ERC721Enumerable', 'AccessControl', 'AccessControlEnumerable']); - - it('token has correct name', async function () { - expect(await this.token.name()).to.equal(name); - }); - - it('token has correct symbol', async function () { - expect(await this.token.symbol()).to.equal(symbol); - }); - - it('deployer has the default admin role', async function () { - expect(await this.token.getRoleMemberCount(DEFAULT_ADMIN_ROLE)).to.be.bignumber.equal('1'); - expect(await this.token.getRoleMember(DEFAULT_ADMIN_ROLE, 0)).to.equal(deployer); - }); - - it('deployer has the minter role', async function () { - expect(await this.token.getRoleMemberCount(MINTER_ROLE)).to.be.bignumber.equal('1'); - expect(await this.token.getRoleMember(MINTER_ROLE, 0)).to.equal(deployer); - }); - - it('minter role admin is the default admin', async function () { - expect(await this.token.getRoleAdmin(MINTER_ROLE)).to.equal(DEFAULT_ADMIN_ROLE); - }); - - describe('minting', function () { - it('deployer can mint tokens', async function () { - const tokenId = new BN('0'); - - const receipt = await this.token.mint(other, { from: deployer }); - expectEvent(receipt, 'Transfer', { from: ZERO_ADDRESS, to: other, tokenId }); - - expect(await this.token.balanceOf(other)).to.be.bignumber.equal('1'); - expect(await this.token.ownerOf(tokenId)).to.equal(other); - - expect(await this.token.tokenURI(tokenId)).to.equal(baseURI + tokenId); - }); - - it('other accounts cannot mint tokens', async function () { - await expectRevert( - this.token.mint(other, { from: other }), - 'ERC721PresetMinterPauserAutoId: must have minter role to mint', - ); - }); - }); - - describe('pausing', function () { - it('deployer can pause', async function () { - const receipt = await this.token.pause({ from: deployer }); - expectEvent(receipt, 'Paused', { account: deployer }); - - expect(await this.token.paused()).to.equal(true); - }); - - it('deployer can unpause', async function () { - await this.token.pause({ from: deployer }); - - const receipt = await this.token.unpause({ from: deployer }); - expectEvent(receipt, 'Unpaused', { account: deployer }); - - expect(await this.token.paused()).to.equal(false); - }); - - it('cannot mint while paused', async function () { - await this.token.pause({ from: deployer }); - - await expectRevert( - this.token.mint(other, { from: deployer }), - 'ERC721Pausable: token transfer while paused', - ); - }); - - it('other accounts cannot pause', async function () { - await expectRevert( - this.token.pause({ from: other }), - 'ERC721PresetMinterPauserAutoId: must have pauser role to pause', - ); - }); - - it('other accounts cannot unpause', async function () { - await this.token.pause({ from: deployer }); - - await expectRevert( - this.token.unpause({ from: other }), - 'ERC721PresetMinterPauserAutoId: must have pauser role to unpause', - ); - }); - }); - - describe('burning', function () { - it('holders can burn their tokens', async function () { - const tokenId = new BN('0'); - - await this.token.mint(other, { from: deployer }); - - const receipt = await this.token.burn(tokenId, { from: other }); - - expectEvent(receipt, 'Transfer', { from: other, to: ZERO_ADDRESS, tokenId }); - - expect(await this.token.balanceOf(other)).to.be.bignumber.equal('0'); - expect(await this.token.totalSupply()).to.be.bignumber.equal('0'); - }); - }); -}); diff --git a/test/token/ERC777/presets/ERC777PresetFixedSupply.test.js b/test/token/ERC777/presets/ERC777PresetFixedSupply.test.js deleted file mode 100644 index e6a842bec..000000000 --- a/test/token/ERC777/presets/ERC777PresetFixedSupply.test.js +++ /dev/null @@ -1,49 +0,0 @@ -const { BN, singletons } = require('@openzeppelin/test-helpers'); - -const { expect } = require('chai'); - -const ERC777PresetFixedSupply = artifacts.require('ERC777PresetFixedSupply'); - -contract('ERC777PresetFixedSupply', function (accounts) { - const [registryFunder, owner, defaultOperatorA, defaultOperatorB, anyone] = accounts; - - const initialSupply = new BN('10000'); - const name = 'ERC777Preset'; - const symbol = '777P'; - - const defaultOperators = [defaultOperatorA, defaultOperatorB]; - - before(async function () { - await singletons.ERC1820Registry(registryFunder); - }); - - beforeEach(async function () { - this.token = await ERC777PresetFixedSupply.new(name, symbol, defaultOperators, initialSupply, owner); - }); - - it('returns the name', async function () { - expect(await this.token.name()).to.equal(name); - }); - - it('returns the symbol', async function () { - expect(await this.token.symbol()).to.equal(symbol); - }); - - it('returns the default operators', async function () { - expect(await this.token.defaultOperators()).to.deep.equal(defaultOperators); - }); - - it('default operators are operators for all accounts', async function () { - for (const operator of defaultOperators) { - expect(await this.token.isOperatorFor(operator, anyone)).to.equal(true); - } - }); - - it('returns the total supply equal to initial supply', async function () { - expect(await this.token.totalSupply()).to.be.bignumber.equal(initialSupply); - }); - - it('returns the balance of owner equal to initial supply', async function () { - expect(await this.token.balanceOf(owner)).to.be.bignumber.equal(initialSupply); - }); -}); From bc8f442d0037758681d3f3264665f5c1438450e2 Mon Sep 17 00:00:00 2001 From: JulissaDantes Date: Fri, 25 Nov 2022 14:27:58 -0400 Subject: [PATCH 005/182] Remove admin and implementation getters from TransparentUpgradeableProxy (#3820) Co-authored-by: Francisco --- CHANGELOG.md | 4 +- contracts/proxy/ERC1967/ERC1967Proxy.sol | 4 ++ contracts/proxy/ERC1967/ERC1967Upgrade.sol | 4 ++ contracts/proxy/transparent/ProxyAdmin.sol | 30 ------------ .../TransparentUpgradeableProxy.sol | 26 ---------- test/helpers/erc1967.js | 6 +++ test/proxy/transparent/ProxyAdmin.test.js | 36 +++----------- .../TransparentUpgradeableProxy.behaviour.js | 49 +++++-------------- 8 files changed, 36 insertions(+), 123 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d21be5f3..61e6d6c23 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,9 @@ ## Unreleased (Breaking) * `TimelockController`: Changed the role architecture to use `DEFAULT_ADMIN_ROLE` as the admin for all roles, instead of the bespoke `TIMELOCK_ADMIN_ROLE` that was used previously. This aligns with the general recommendation for `AccessControl` and makes the addition of new roles easier. Accordingly, the `admin` parameter and timelock will now be granted `DEFAULT_ADMIN_ROLE` instead of `TIMELOCK_ADMIN_ROLE`. ([#3799](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3799)) -* Removed presets in favor of [OpenZeppelin Contracts Wizard](https://wizard.openzeppelin.com/). + * Removed presets in favor of [OpenZeppelin Contracts Wizard](https://wizard.openzeppelin.com/). + * `TransparentUpgradeableProxy`: Removed `admin` and `implementation` getters, which were only callable by the proxy owner and thus not very useful. ([#3820](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3820)) + * `ProxyAdmin`: Removed `getProxyAdmin` and `getProxyImplementation` getters. ([#3820](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3820)) ## Unreleased diff --git a/contracts/proxy/ERC1967/ERC1967Proxy.sol b/contracts/proxy/ERC1967/ERC1967Proxy.sol index a04d701ce..642d811cc 100644 --- a/contracts/proxy/ERC1967/ERC1967Proxy.sol +++ b/contracts/proxy/ERC1967/ERC1967Proxy.sol @@ -25,6 +25,10 @@ contract ERC1967Proxy is Proxy, ERC1967Upgrade { /** * @dev Returns the current implementation address. + * + * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the + * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call. + * `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc` */ function _implementation() internal view virtual override returns (address impl) { return ERC1967Upgrade._getImplementation(); diff --git a/contracts/proxy/ERC1967/ERC1967Upgrade.sol b/contracts/proxy/ERC1967/ERC1967Upgrade.sol index 77fbdd165..07dc04862 100644 --- a/contracts/proxy/ERC1967/ERC1967Upgrade.sol +++ b/contracts/proxy/ERC1967/ERC1967Upgrade.sol @@ -112,6 +112,10 @@ abstract contract ERC1967Upgrade { /** * @dev Returns the current admin. + * + * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the + * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call. + * `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103` */ function _getAdmin() internal view returns (address) { return StorageSlot.getAddressSlot(_ADMIN_SLOT).value; diff --git a/contracts/proxy/transparent/ProxyAdmin.sol b/contracts/proxy/transparent/ProxyAdmin.sol index 839534298..7f340f629 100644 --- a/contracts/proxy/transparent/ProxyAdmin.sol +++ b/contracts/proxy/transparent/ProxyAdmin.sol @@ -11,36 +11,6 @@ import "../../access/Ownable.sol"; * explanation of why you would want to use this see the documentation for {TransparentUpgradeableProxy}. */ contract ProxyAdmin is Ownable { - /** - * @dev Returns the current implementation of `proxy`. - * - * Requirements: - * - * - This contract must be the admin of `proxy`. - */ - function getProxyImplementation(TransparentUpgradeableProxy proxy) public view virtual returns (address) { - // We need to manually run the static call since the getter cannot be flagged as view - // bytes4(keccak256("implementation()")) == 0x5c60da1b - (bool success, bytes memory returndata) = address(proxy).staticcall(hex"5c60da1b"); - require(success); - return abi.decode(returndata, (address)); - } - - /** - * @dev Returns the current admin of `proxy`. - * - * Requirements: - * - * - This contract must be the admin of `proxy`. - */ - function getProxyAdmin(TransparentUpgradeableProxy proxy) public view virtual returns (address) { - // We need to manually run the static call since the getter cannot be flagged as view - // bytes4(keccak256("admin()")) == 0xf851a440 - (bool success, bytes memory returndata) = address(proxy).staticcall(hex"f851a440"); - require(success); - return abi.decode(returndata, (address)); - } - /** * @dev Changes the admin of `proxy` to `newAdmin`. * diff --git a/contracts/proxy/transparent/TransparentUpgradeableProxy.sol b/contracts/proxy/transparent/TransparentUpgradeableProxy.sol index 4de85075a..57417296d 100644 --- a/contracts/proxy/transparent/TransparentUpgradeableProxy.sol +++ b/contracts/proxy/transparent/TransparentUpgradeableProxy.sol @@ -50,32 +50,6 @@ contract TransparentUpgradeableProxy is ERC1967Proxy { } } - /** - * @dev Returns the current admin. - * - * NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyAdmin}. - * - * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the - * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call. - * `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103` - */ - function admin() external ifAdmin returns (address admin_) { - admin_ = _getAdmin(); - } - - /** - * @dev Returns the current implementation. - * - * NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyImplementation}. - * - * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the - * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call. - * `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc` - */ - function implementation() external ifAdmin returns (address implementation_) { - implementation_ = _implementation(); - } - /** * @dev Changes the admin of the proxy. * diff --git a/test/helpers/erc1967.js b/test/helpers/erc1967.js index aab0e2288..184f1cd0f 100644 --- a/test/helpers/erc1967.js +++ b/test/helpers/erc1967.js @@ -13,6 +13,11 @@ function getSlot (address, slot) { ); } +async function getAddressInSlot (address, slot) { + const slotValue = await getSlot(address, slot); + return web3.utils.toChecksumAddress(slotValue.substr(-40)); +} + module.exports = { ImplementationLabel, AdminLabel, @@ -21,4 +26,5 @@ module.exports = { AdminSlot: labelToSlot(AdminLabel), BeaconSlot: labelToSlot(BeaconLabel), getSlot, + getAddressInSlot, }; diff --git a/test/proxy/transparent/ProxyAdmin.test.js b/test/proxy/transparent/ProxyAdmin.test.js index 07adba6ad..b1f550029 100644 --- a/test/proxy/transparent/ProxyAdmin.test.js +++ b/test/proxy/transparent/ProxyAdmin.test.js @@ -1,7 +1,6 @@ const { expectRevert } = require('@openzeppelin/test-helpers'); - +const { getAddressInSlot, ImplementationSlot, AdminSlot } = require('../../helpers/erc1967'); const { expect } = require('chai'); - const ImplV1 = artifacts.require('DummyImplementation'); const ImplV2 = artifacts.require('DummyImplementationV2'); const ProxyAdmin = artifacts.require('ProxyAdmin'); @@ -30,17 +29,6 @@ contract('ProxyAdmin', function (accounts) { expect(await this.proxyAdmin.owner()).to.equal(proxyAdminOwner); }); - describe('#getProxyAdmin', function () { - it('returns proxyAdmin as admin of the proxy', async function () { - const admin = await this.proxyAdmin.getProxyAdmin(this.proxy.address); - expect(admin).to.be.equal(this.proxyAdmin.address); - }); - - it('call to invalid proxy', async function () { - await expectRevert.unspecified(this.proxyAdmin.getProxyAdmin(this.implementationV1.address)); - }); - }); - describe('#changeProxyAdmin', function () { it('fails to change proxy admin if its not the proxy owner', async function () { await expectRevert( @@ -51,18 +39,9 @@ contract('ProxyAdmin', function (accounts) { it('changes proxy admin', async function () { await this.proxyAdmin.changeProxyAdmin(this.proxy.address, newAdmin, { from: proxyAdminOwner }); - expect(await this.proxy.admin.call({ from: newAdmin })).to.eq(newAdmin); - }); - }); - describe('#getProxyImplementation', function () { - it('returns proxy implementation address', async function () { - const implementationAddress = await this.proxyAdmin.getProxyImplementation(this.proxy.address); - expect(implementationAddress).to.be.equal(this.implementationV1.address); - }); - - it('call to invalid proxy', async function () { - await expectRevert.unspecified(this.proxyAdmin.getProxyImplementation(this.implementationV1.address)); + const newProxyAdmin = await getAddressInSlot(this.proxy, AdminSlot); + expect(newProxyAdmin).to.be.eq(newAdmin); }); }); @@ -79,8 +58,9 @@ contract('ProxyAdmin', function (accounts) { context('with authorized account', function () { it('upgrades implementation', async function () { await this.proxyAdmin.upgrade(this.proxy.address, this.implementationV2.address, { from: proxyAdminOwner }); - const implementationAddress = await this.proxyAdmin.getProxyImplementation(this.proxy.address); - expect(implementationAddress).to.be.equal(this.implementationV2.address); + + const implementationAddress = await getAddressInSlot(this.proxy, ImplementationSlot); + expect(implementationAddress).to.be.eq(this.implementationV2.address); }); }); }); @@ -116,8 +96,8 @@ contract('ProxyAdmin', function (accounts) { await this.proxyAdmin.upgradeAndCall(this.proxy.address, this.implementationV2.address, callData, { from: proxyAdminOwner }, ); - const implementationAddress = await this.proxyAdmin.getProxyImplementation(this.proxy.address); - expect(implementationAddress).to.be.equal(this.implementationV2.address); + const implementationAddress = await getAddressInSlot(this.proxy, ImplementationSlot); + expect(implementationAddress).to.be.eq(this.implementationV2.address); }); }); }); diff --git a/test/proxy/transparent/TransparentUpgradeableProxy.behaviour.js b/test/proxy/transparent/TransparentUpgradeableProxy.behaviour.js index 33fef6f41..478bc1c29 100644 --- a/test/proxy/transparent/TransparentUpgradeableProxy.behaviour.js +++ b/test/proxy/transparent/TransparentUpgradeableProxy.behaviour.js @@ -1,6 +1,6 @@ const { BN, expectRevert, expectEvent, constants } = require('@openzeppelin/test-helpers'); const { ZERO_ADDRESS } = constants; -const { getSlot, ImplementationSlot, AdminSlot } = require('../../helpers/erc1967'); +const { getAddressInSlot, ImplementationSlot, AdminSlot } = require('../../helpers/erc1967'); const { expect } = require('chai'); @@ -34,9 +34,8 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy (createPro describe('implementation', function () { it('returns the current implementation address', async function () { - const implementation = await this.proxy.implementation.call({ from: proxyAdminAddress }); - - expect(implementation).to.be.equal(this.implementationV0); + const implementationAddress = await getAddressInSlot(this.proxy, ImplementationSlot); + expect(implementationAddress).to.be.equal(this.implementationV0); }); it('delegates to the implementation', async function () { @@ -55,8 +54,8 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy (createPro it('upgrades to the requested implementation', async function () { await this.proxy.upgradeTo(this.implementationV1, { from }); - const implementation = await this.proxy.implementation.call({ from: proxyAdminAddress }); - expect(implementation).to.be.equal(this.implementationV1); + const implementationAddress = await getAddressInSlot(this.proxy, ImplementationSlot); + expect(implementationAddress).to.be.equal(this.implementationV1); }); it('emits an event', async function () { @@ -108,8 +107,8 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy (createPro }); it('upgrades to the requested implementation', async function () { - const implementation = await this.proxy.implementation.call({ from: proxyAdminAddress }); - expect(implementation).to.be.equal(this.behavior.address); + const implementationAddress = await getAddressInSlot(this.proxy, ImplementationSlot); + expect(implementationAddress).to.be.equal(this.behavior.address); }); it('emits an event', function () { @@ -173,7 +172,7 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy (createPro }); it('upgrades to the requested version and emits an event', async function () { - const implementation = await this.proxy.implementation.call({ from: proxyAdminAddress }); + const implementation = await getAddressInSlot(this.proxy, ImplementationSlot); expect(implementation).to.be.equal(this.behaviorV1.address); expectEvent(this.receipt, 'Upgraded', { implementation: this.behaviorV1.address }); }); @@ -199,7 +198,7 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy (createPro }); it('upgrades to the requested version and emits an event', async function () { - const implementation = await this.proxy.implementation.call({ from: proxyAdminAddress }); + const implementation = await getAddressInSlot(this.proxy, ImplementationSlot); expect(implementation).to.be.equal(this.behaviorV2.address); expectEvent(this.receipt, 'Upgraded', { implementation: this.behaviorV2.address }); }); @@ -228,7 +227,7 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy (createPro }); it('upgrades to the requested version and emits an event', async function () { - const implementation = await this.proxy.implementation.call({ from: proxyAdminAddress }); + const implementation = await getAddressInSlot(this.proxy, ImplementationSlot); expect(implementation).to.be.equal(this.behaviorV3.address); expectEvent(this.receipt, 'Upgraded', { implementation: this.behaviorV3.address }); }); @@ -274,7 +273,7 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy (createPro }); it('assigns new proxy admin', async function () { - const newProxyAdmin = await this.proxy.admin.call({ from: newAdmin }); + const newProxyAdmin = await getAddressInSlot(this.proxy, AdminSlot); expect(newProxyAdmin).to.be.equal(anotherAccount); }); @@ -303,20 +302,6 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy (createPro }); }); - describe('storage', function () { - it('should store the implementation address in specified location', async function () { - const implementationSlot = await getSlot(this.proxy, ImplementationSlot); - const implementationAddress = web3.utils.toChecksumAddress(implementationSlot.substr(-40)); - expect(implementationAddress).to.be.equal(this.implementationV0); - }); - - it('should store the admin proxy in specified location', async function () { - const proxyAdminSlot = await getSlot(this.proxy, AdminSlot); - const proxyAdminAddress = web3.utils.toChecksumAddress(proxyAdminSlot.substr(-40)); - expect(proxyAdminAddress).to.be.equal(proxyAdminAddress); - }); - }); - describe('transparent proxy', function () { beforeEach('creating proxy', async function () { const initializeData = Buffer.from(''); @@ -332,18 +317,6 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy (createPro 'TransparentUpgradeableProxy: admin cannot fallback to proxy target', ); }); - - context('when function names clash', function () { - it('when sender is proxy admin should run the proxy function', async function () { - const value = await this.proxy.admin.call({ from: proxyAdminAddress }); - expect(value).to.be.equal(proxyAdminAddress); - }); - - it('when sender is other should delegate to implementation', async function () { - const value = await this.proxy.admin.call({ from: anotherAccount }); - expect(value).to.be.equal('0x0000000000000000000000000000000011111142'); - }); - }); }); describe('regression', () => { From e2d2ebc8fcb8d5582b262dd94f432a40df43b114 Mon Sep 17 00:00:00 2001 From: JulissaDantes Date: Tue, 29 Nov 2022 10:25:52 -0400 Subject: [PATCH 006/182] Make ERC20Votes independent from ERC20Permit (#3816) Co-authored-by: Francisco --- CHANGELOG.md | 2 + contracts/governance/utils/Votes.sol | 40 ++- contracts/mocks/ERC20VotesCompMock.sol | 2 +- contracts/mocks/ERC20VotesMock.sol | 2 +- contracts/mocks/ERC721VotesMock.sol | 2 +- contracts/mocks/NoncesImpl.sol | 11 + contracts/mocks/VotesMock.sol | 2 +- .../token/ERC20/extensions/ERC20Permit.sol | 23 +- .../token/ERC20/extensions/ERC20Votes.sol | 251 ++---------------- contracts/utils/Checkpoints.sol | 7 + contracts/utils/Nonces.sol | 31 +++ scripts/generate/templates/Checkpoints.js | 7 + test/governance/utils/Votes.behavior.js | 29 +- test/governance/utils/Votes.test.js | 8 +- .../token/ERC20/extensions/ERC20Votes.test.js | 46 ++-- .../ERC20/extensions/ERC20VotesComp.test.js | 35 ++- .../ERC721/extensions/ERC721Votes.test.js | 51 ++-- test/utils/Nonces.test.js | 25 ++ 18 files changed, 231 insertions(+), 343 deletions(-) create mode 100644 contracts/mocks/NoncesImpl.sol create mode 100644 contracts/utils/Nonces.sol create mode 100644 test/utils/Nonces.test.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 61e6d6c23..bf92d9b43 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ ## Unreleased (Breaking) * `TimelockController`: Changed the role architecture to use `DEFAULT_ADMIN_ROLE` as the admin for all roles, instead of the bespoke `TIMELOCK_ADMIN_ROLE` that was used previously. This aligns with the general recommendation for `AccessControl` and makes the addition of new roles easier. Accordingly, the `admin` parameter and timelock will now be granted `DEFAULT_ADMIN_ROLE` instead of `TIMELOCK_ADMIN_ROLE`. ([#3799](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3799)) + * `ERC20Votes`: Changed internal vote accounting to reusable `Votes` module previously used by `ERC721Votes`. Removed implicit `ERC20Permit` inheritance. [#3816](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3816) + * `Nonces`: Added a new contract to keep track of user nonces. Used for signatures in `ERC20Permit`, `ERC20Votes`, and `ERC721Votes`. ([#3816](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3816)) * Removed presets in favor of [OpenZeppelin Contracts Wizard](https://wizard.openzeppelin.com/). * `TransparentUpgradeableProxy`: Removed `admin` and `implementation` getters, which were only callable by the proxy owner and thus not very useful. ([#3820](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3820)) * `ProxyAdmin`: Removed `getProxyAdmin` and `getProxyImplementation` getters. ([#3820](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3820)) diff --git a/contracts/governance/utils/Votes.sol b/contracts/governance/utils/Votes.sol index f763589c6..dfcf1e610 100644 --- a/contracts/governance/utils/Votes.sol +++ b/contracts/governance/utils/Votes.sol @@ -3,10 +3,11 @@ pragma solidity ^0.8.0; import "../../utils/Context.sol"; -import "../../utils/Counters.sol"; +import "../../utils/Nonces.sol"; import "../../utils/Checkpoints.sol"; import "../../utils/cryptography/EIP712.sol"; import "./IVotes.sol"; +import "../../utils/math/SafeCast.sol"; /** * @dev This is a base abstract contract that tracks voting units, which are a measure of voting power that can be @@ -28,9 +29,8 @@ import "./IVotes.sol"; * * _Available since v4.5._ */ -abstract contract Votes is IVotes, Context, EIP712 { +abstract contract Votes is IVotes, Context, EIP712, Nonces { using Checkpoints for Checkpoints.History; - using Counters for Counters.Counter; bytes32 private constant _DELEGATION_TYPEHASH = keccak256("Delegation(address delegatee,uint256 nonce,uint256 expiry)"); @@ -39,8 +39,6 @@ abstract contract Votes is IVotes, Context, EIP712 { mapping(address => Checkpoints.History) private _delegateCheckpoints; Checkpoints.History private _totalCheckpoints; - mapping(address => Counters.Counter) private _nonces; - /** * @dev Returns the current amount of votes that `account` has. */ @@ -170,30 +168,26 @@ abstract contract Votes is IVotes, Context, EIP712 { } } - function _add(uint256 a, uint256 b) private pure returns (uint256) { - return a + b; - } - - function _subtract(uint256 a, uint256 b) private pure returns (uint256) { - return a - b; - } - /** - * @dev Consumes a nonce. - * - * Returns the current value and increments nonce. + * @dev Get number of checkpoints for `account`. */ - function _useNonce(address owner) internal virtual returns (uint256 current) { - Counters.Counter storage nonce = _nonces[owner]; - current = nonce.current(); - nonce.increment(); + function _numCheckpoints(address account) internal view virtual returns (uint32) { + return SafeCast.toUint32(_delegateCheckpoints[account].length()); } /** - * @dev Returns an address nonce. + * @dev Get the `pos`-th checkpoint for `account`. */ - function nonces(address owner) public view virtual returns (uint256) { - return _nonces[owner].current(); + function _checkpoints(address account, uint32 pos) internal view virtual returns (Checkpoints.Checkpoint memory) { + return _delegateCheckpoints[account].getAtPosition(pos); + } + + function _add(uint256 a, uint256 b) private pure returns (uint256) { + return a + b; + } + + function _subtract(uint256 a, uint256 b) private pure returns (uint256) { + return a - b; } /** diff --git a/contracts/mocks/ERC20VotesCompMock.sol b/contracts/mocks/ERC20VotesCompMock.sol index 171071fd5..5a19ae81a 100644 --- a/contracts/mocks/ERC20VotesCompMock.sol +++ b/contracts/mocks/ERC20VotesCompMock.sol @@ -5,7 +5,7 @@ pragma solidity ^0.8.0; import "../token/ERC20/extensions/ERC20VotesComp.sol"; contract ERC20VotesCompMock is ERC20VotesComp { - constructor(string memory name, string memory symbol) ERC20(name, symbol) ERC20Permit(name) {} + constructor(string memory name, string memory symbol) ERC20(name, symbol) EIP712(name, "1") {} function mint(address account, uint256 amount) public { _mint(account, amount); diff --git a/contracts/mocks/ERC20VotesMock.sol b/contracts/mocks/ERC20VotesMock.sol index 0975e8b9f..654a089ac 100644 --- a/contracts/mocks/ERC20VotesMock.sol +++ b/contracts/mocks/ERC20VotesMock.sol @@ -5,7 +5,7 @@ pragma solidity ^0.8.0; import "../token/ERC20/extensions/ERC20Votes.sol"; contract ERC20VotesMock is ERC20Votes { - constructor(string memory name, string memory symbol) ERC20(name, symbol) ERC20Permit(name) {} + constructor(string memory name, string memory symbol) ERC20(name, symbol) EIP712(name, "1") {} function mint(address account, uint256 amount) public { _mint(account, amount); diff --git a/contracts/mocks/ERC721VotesMock.sol b/contracts/mocks/ERC721VotesMock.sol index acb51ebfb..2d9447428 100644 --- a/contracts/mocks/ERC721VotesMock.sol +++ b/contracts/mocks/ERC721VotesMock.sol @@ -15,7 +15,7 @@ contract ERC721VotesMock is ERC721Votes { _mint(account, tokenId); } - function burn(uint256 tokenId) public { + function burn(address, uint256 tokenId) public { _burn(tokenId); } diff --git a/contracts/mocks/NoncesImpl.sol b/contracts/mocks/NoncesImpl.sol new file mode 100644 index 000000000..41b913c81 --- /dev/null +++ b/contracts/mocks/NoncesImpl.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import "../utils/Nonces.sol"; + +contract NoncesImpl is Nonces { + function useNonce(address owner) public { + super._useNonce(owner); + } +} diff --git a/contracts/mocks/VotesMock.sol b/contracts/mocks/VotesMock.sol index f888490da..f080bfb72 100644 --- a/contracts/mocks/VotesMock.sol +++ b/contracts/mocks/VotesMock.sol @@ -28,7 +28,7 @@ contract VotesMock is Votes { _transferVotingUnits(address(0), account, 1); } - function burn(uint256 voteId) external { + function burn(address, uint256 voteId) external { address owner = _owners[voteId]; _balances[owner] -= 1; _transferVotingUnits(owner, address(0), 1); diff --git a/contracts/token/ERC20/extensions/ERC20Permit.sol b/contracts/token/ERC20/extensions/ERC20Permit.sol index a357199b1..70b48a8d4 100644 --- a/contracts/token/ERC20/extensions/ERC20Permit.sol +++ b/contracts/token/ERC20/extensions/ERC20Permit.sol @@ -7,7 +7,7 @@ import "./IERC20Permit.sol"; import "../ERC20.sol"; import "../../../utils/cryptography/ECDSA.sol"; import "../../../utils/cryptography/EIP712.sol"; -import "../../../utils/Counters.sol"; +import "../../../utils/Nonces.sol"; /** * @dev Implementation of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in @@ -19,11 +19,7 @@ import "../../../utils/Counters.sol"; * * _Available since v3.4._ */ -abstract contract ERC20Permit is ERC20, IERC20Permit, EIP712 { - using Counters for Counters.Counter; - - mapping(address => Counters.Counter) private _nonces; - +abstract contract ERC20Permit is ERC20, IERC20Permit, EIP712, Nonces { // solhint-disable-next-line var-name-mixedcase bytes32 private constant _PERMIT_TYPEHASH = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"); @@ -70,8 +66,8 @@ abstract contract ERC20Permit is ERC20, IERC20Permit, EIP712 { /** * @dev See {IERC20Permit-nonces}. */ - function nonces(address owner) public view virtual override returns (uint256) { - return _nonces[owner].current(); + function nonces(address owner) public view virtual override(IERC20Permit, Nonces) returns (uint256) { + return super.nonces(owner); } /** @@ -81,15 +77,4 @@ abstract contract ERC20Permit is ERC20, IERC20Permit, EIP712 { function DOMAIN_SEPARATOR() external view override returns (bytes32) { return _domainSeparatorV4(); } - - /** - * @dev "Consume a nonce": return the current value and increment. - * - * _Available since v4.1._ - */ - function _useNonce(address owner) internal virtual returns (uint256 current) { - Counters.Counter storage nonce = _nonces[owner]; - current = nonce.current(); - nonce.increment(); - } } diff --git a/contracts/token/ERC20/extensions/ERC20Votes.sol b/contracts/token/ERC20/extensions/ERC20Votes.sol index bf319c26f..e2a070921 100644 --- a/contracts/token/ERC20/extensions/ERC20Votes.sol +++ b/contracts/token/ERC20/extensions/ERC20Votes.sol @@ -3,11 +3,9 @@ pragma solidity ^0.8.0; -import "./ERC20Permit.sol"; -import "../../../utils/math/Math.sol"; -import "../../../governance/utils/IVotes.sol"; +import "../ERC20.sol"; +import "../../../governance/utils/Votes.sol"; import "../../../utils/math/SafeCast.sol"; -import "../../../utils/cryptography/ECDSA.sol"; /** * @dev Extension of ERC20 to support Compound-like voting and delegation. This version is more generic than Compound's, @@ -24,148 +22,7 @@ import "../../../utils/cryptography/ECDSA.sol"; * * _Available since v4.2._ */ -abstract contract ERC20Votes is IVotes, ERC20Permit { - struct Checkpoint { - uint32 fromBlock; - uint224 votes; - } - - bytes32 private constant _DELEGATION_TYPEHASH = - keccak256("Delegation(address delegatee,uint256 nonce,uint256 expiry)"); - - mapping(address => address) private _delegates; - mapping(address => Checkpoint[]) private _checkpoints; - Checkpoint[] private _totalSupplyCheckpoints; - - /** - * @dev Get the `pos`-th checkpoint for `account`. - */ - function checkpoints(address account, uint32 pos) public view virtual returns (Checkpoint memory) { - return _checkpoints[account][pos]; - } - - /** - * @dev Get number of checkpoints for `account`. - */ - function numCheckpoints(address account) public view virtual returns (uint32) { - return SafeCast.toUint32(_checkpoints[account].length); - } - - /** - * @dev Get the address `account` is currently delegating to. - */ - function delegates(address account) public view virtual override returns (address) { - return _delegates[account]; - } - - /** - * @dev Gets the current votes balance for `account` - */ - function getVotes(address account) public view virtual override returns (uint256) { - uint256 pos = _checkpoints[account].length; - unchecked { - return pos == 0 ? 0 : _checkpoints[account][pos - 1].votes; - } - } - - /** - * @dev Retrieve the number of votes for `account` at the end of `blockNumber`. - * - * Requirements: - * - * - `blockNumber` must have been already mined - */ - function getPastVotes(address account, uint256 blockNumber) public view virtual override returns (uint256) { - require(blockNumber < block.number, "ERC20Votes: block not yet mined"); - return _checkpointsLookup(_checkpoints[account], blockNumber); - } - - /** - * @dev Retrieve the `totalSupply` at the end of `blockNumber`. Note, this value is the sum of all balances. - * It is NOT the sum of all the delegated votes! - * - * Requirements: - * - * - `blockNumber` must have been already mined - */ - function getPastTotalSupply(uint256 blockNumber) public view virtual override returns (uint256) { - require(blockNumber < block.number, "ERC20Votes: block not yet mined"); - return _checkpointsLookup(_totalSupplyCheckpoints, blockNumber); - } - - /** - * @dev Lookup a value in a list of (sorted) checkpoints. - */ - function _checkpointsLookup(Checkpoint[] storage ckpts, uint256 blockNumber) private view returns (uint256) { - // We run a binary search to look for the earliest checkpoint taken after `blockNumber`. - // - // Initially we check if the block is recent to narrow the search range. - // During the loop, the index of the wanted checkpoint remains in the range [low-1, high). - // With each iteration, either `low` or `high` is moved towards the middle of the range to maintain the invariant. - // - If the middle checkpoint is after `blockNumber`, we look in [low, mid) - // - If the middle checkpoint is before or equal to `blockNumber`, we look in [mid+1, high) - // Once we reach a single value (when low == high), we've found the right checkpoint at the index high-1, if not - // out of bounds (in which case we're looking too far in the past and the result is 0). - // Note that if the latest checkpoint available is exactly for `blockNumber`, we end up with an index that is - // past the end of the array, so we technically don't find a checkpoint after `blockNumber`, but it works out - // the same. - uint256 length = ckpts.length; - - uint256 low = 0; - uint256 high = length; - - if (length > 5) { - uint256 mid = length - Math.sqrt(length); - if (_unsafeAccess(ckpts, mid).fromBlock > blockNumber) { - high = mid; - } else { - low = mid + 1; - } - } - - while (low < high) { - uint256 mid = Math.average(low, high); - if (_unsafeAccess(ckpts, mid).fromBlock > blockNumber) { - high = mid; - } else { - low = mid + 1; - } - } - - unchecked { - return high == 0 ? 0 : _unsafeAccess(ckpts, high - 1).votes; - } - } - - /** - * @dev Delegate votes from the sender to `delegatee`. - */ - function delegate(address delegatee) public virtual override { - _delegate(_msgSender(), delegatee); - } - - /** - * @dev Delegates votes from signer to `delegatee` - */ - function delegateBySig( - address delegatee, - uint256 nonce, - uint256 expiry, - uint8 v, - bytes32 r, - bytes32 s - ) public virtual override { - require(block.timestamp <= expiry, "ERC20Votes: signature expired"); - address signer = ECDSA.recover( - _hashTypedDataV4(keccak256(abi.encode(_DELEGATION_TYPEHASH, delegatee, nonce, expiry))), - v, - r, - s - ); - require(nonce == _useNonce(signer), "ERC20Votes: invalid nonce"); - _delegate(signer, delegatee); - } - +abstract contract ERC20Votes is ERC20, Votes { /** * @dev Maximum token supply. Defaults to `type(uint224).max` (2^224^ - 1). */ @@ -173,25 +30,6 @@ abstract contract ERC20Votes is IVotes, ERC20Permit { return type(uint224).max; } - /** - * @dev Snapshots the totalSupply after it has been increased. - */ - function _mint(address account, uint256 amount) internal virtual override { - super._mint(account, amount); - require(totalSupply() <= _maxSupply(), "ERC20Votes: total supply risks overflowing votes"); - - _writeCheckpoint(_totalSupplyCheckpoints, _add, amount); - } - - /** - * @dev Snapshots the totalSupply after it has been decreased. - */ - function _burn(address account, uint256 amount) internal virtual override { - super._burn(account, amount); - - _writeCheckpoint(_totalSupplyCheckpoints, _subtract, amount); - } - /** * @dev Move voting power when tokens are transferred. * @@ -202,79 +40,36 @@ abstract contract ERC20Votes is IVotes, ERC20Permit { address to, uint256 amount ) internal virtual override { + _transferVotingUnits(from, to, amount); super._afterTokenTransfer(from, to, amount); - - _moveVotingPower(delegates(from), delegates(to), amount); } /** - * @dev Change delegation for `delegator` to `delegatee`. - * - * Emits events {IVotes-DelegateChanged} and {IVotes-DelegateVotesChanged}. + * @dev Get number of checkpoints for `account`. */ - function _delegate(address delegator, address delegatee) internal virtual { - address currentDelegate = delegates(delegator); - uint256 delegatorBalance = balanceOf(delegator); - _delegates[delegator] = delegatee; - - emit DelegateChanged(delegator, currentDelegate, delegatee); - - _moveVotingPower(currentDelegate, delegatee, delegatorBalance); - } - - function _moveVotingPower( - address src, - address dst, - uint256 amount - ) private { - if (src != dst && amount > 0) { - if (src != address(0)) { - (uint256 oldWeight, uint256 newWeight) = _writeCheckpoint(_checkpoints[src], _subtract, amount); - emit DelegateVotesChanged(src, oldWeight, newWeight); - } - - if (dst != address(0)) { - (uint256 oldWeight, uint256 newWeight) = _writeCheckpoint(_checkpoints[dst], _add, amount); - emit DelegateVotesChanged(dst, oldWeight, newWeight); - } - } - } - - function _writeCheckpoint( - Checkpoint[] storage ckpts, - function(uint256, uint256) view returns (uint256) op, - uint256 delta - ) private returns (uint256 oldWeight, uint256 newWeight) { - uint256 pos = ckpts.length; - - unchecked { - Checkpoint memory oldCkpt = pos == 0 ? Checkpoint(0, 0) : _unsafeAccess(ckpts, pos - 1); - - oldWeight = oldCkpt.votes; - newWeight = op(oldWeight, delta); - - if (pos > 0 && oldCkpt.fromBlock == block.number) { - _unsafeAccess(ckpts, pos - 1).votes = SafeCast.toUint224(newWeight); - } else { - ckpts.push( - Checkpoint({fromBlock: SafeCast.toUint32(block.number), votes: SafeCast.toUint224(newWeight)}) - ); - } - } + function numCheckpoints(address account) public view virtual returns (uint32) { + return _numCheckpoints(account); } - function _add(uint256 a, uint256 b) private pure returns (uint256) { - return a + b; + /** + * @dev Get the `pos`-th checkpoint for `account`. + */ + function checkpoints(address account, uint32 pos) public view virtual returns (Checkpoints.Checkpoint memory) { + return _checkpoints(account, pos); } - function _subtract(uint256 a, uint256 b) private pure returns (uint256) { - return a - b; + /** + * @dev Returns the balance of `account`. + */ + function _getVotingUnits(address account) internal view virtual override returns (uint256) { + return balanceOf(account); } - function _unsafeAccess(Checkpoint[] storage ckpts, uint256 pos) private pure returns (Checkpoint storage result) { - assembly { - mstore(0, ckpts.slot) - result.slot := add(keccak256(0, 0x20), pos) - } + /** + * @dev Snapshots the totalSupply after it has been increased. + */ + function _mint(address account, uint256 amount) internal virtual override { + super._mint(account, amount); + require(totalSupply() <= _maxSupply(), "ERC20Votes: total supply risks overflowing votes"); } } diff --git a/contracts/utils/Checkpoints.sol b/contracts/utils/Checkpoints.sol index 81a8b5d5a..da9523b5e 100644 --- a/contracts/utils/Checkpoints.sol +++ b/contracts/utils/Checkpoints.sol @@ -68,6 +68,13 @@ library Checkpoints { return pos == 0 ? 0 : _unsafeAccess(self._checkpoints, pos - 1)._value; } + /** + * @dev Returns checkpoint at given position. + */ + function getAtPosition(History storage self, uint32 pos) internal view returns (Checkpoint memory) { + return self._checkpoints[pos]; + } + /** * @dev Pushes a value onto a History so that it is stored as the checkpoint for the current block. * diff --git a/contracts/utils/Nonces.sol b/contracts/utils/Nonces.sol new file mode 100644 index 000000000..ce65909ff --- /dev/null +++ b/contracts/utils/Nonces.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "./Counters.sol"; + +/** + * @dev Provides tracking nonces for addresses. Nonces will only increment. + */ +abstract contract Nonces { + using Counters for Counters.Counter; + + mapping(address => Counters.Counter) private _nonces; + + /** + * @dev Returns an address nonce. + */ + function nonces(address owner) public view virtual returns (uint256) { + return _nonces[owner].current(); + } + + /** + * @dev Consumes a nonce. + * + * Returns the current value and increments nonce. + */ + function _useNonce(address owner) internal virtual returns (uint256 current) { + Counters.Counter storage nonce = _nonces[owner]; + current = nonce.current(); + nonce.increment(); + } +} diff --git a/scripts/generate/templates/Checkpoints.js b/scripts/generate/templates/Checkpoints.js index b78ed5ceb..ed8940b75 100644 --- a/scripts/generate/templates/Checkpoints.js +++ b/scripts/generate/templates/Checkpoints.js @@ -107,6 +107,13 @@ function getAtProbablyRecentBlock(${opts.historyTypeName} storage self, uint256 return pos == 0 ? 0 : _unsafeAccess(self.${opts.checkpointFieldName}, pos - 1).${opts.valueFieldName}; } +/** + * @dev Returns checkpoint at given position. + */ +function getAtPosition(History storage self, uint32 pos) internal view returns (Checkpoint memory) { + return self._checkpoints[pos]; +} + /** * @dev Pushes a value onto a History so that it is stored as the checkpoint for the current block. * diff --git a/test/governance/utils/Votes.behavior.js b/test/governance/utils/Votes.behavior.js index aee227bdb..40fa4c152 100644 --- a/test/governance/utils/Votes.behavior.js +++ b/test/governance/utils/Votes.behavior.js @@ -7,6 +7,7 @@ const ethSigUtil = require('eth-sig-util'); const Wallet = require('ethereumjs-wallet').default; const { EIP712Domain, domainSeparator } = require('../../helpers/eip712'); +const { web3 } = require('hardhat'); const Delegation = [ { name: 'delegatee', type: 'address' }, @@ -45,7 +46,7 @@ function shouldBehaveLikeVotes () { }); beforeEach(async function () { - await this.votes.mint(delegatorAddress, this.NFT0); + await this.votes.mint(delegatorAddress, this.token0); }); it('accept signed delegation', async function () { @@ -151,7 +152,7 @@ function shouldBehaveLikeVotes () { describe('set delegation', function () { describe('call', function () { it('delegation with tokens', async function () { - await this.votes.mint(this.account1, this.NFT0); + await this.votes.mint(this.account1, this.token0); expect(await this.votes.delegates(this.account1)).to.be.equal(ZERO_ADDRESS); const { receipt } = await this.votes.delegate(this.account1, { from: this.account1 }); @@ -192,7 +193,7 @@ function shouldBehaveLikeVotes () { describe('change delegation', function () { beforeEach(async function () { - await this.votes.mint(this.account1, this.NFT0); + await this.votes.mint(this.account1, this.token0); await this.votes.delegate(this.account1, { from: this.account1 }); }); @@ -245,7 +246,7 @@ function shouldBehaveLikeVotes () { }); it('returns the latest block if >= last checkpoint block', async function () { - const t1 = await this.votes.mint(this.account1, this.NFT0); + const t1 = await this.votes.mint(this.account1, this.token0); await time.advanceBlock(); await time.advanceBlock(); @@ -255,7 +256,7 @@ function shouldBehaveLikeVotes () { it('returns zero if < first checkpoint block', async function () { await time.advanceBlock(); - const t2 = await this.votes.mint(this.account1, this.NFT1); + const t2 = await this.votes.mint(this.account1, this.token1); await time.advanceBlock(); await time.advanceBlock(); @@ -264,19 +265,19 @@ function shouldBehaveLikeVotes () { }); it('generally returns the voting balance at the appropriate checkpoint', async function () { - const t1 = await this.votes.mint(this.account1, this.NFT1); + const t1 = await this.votes.mint(this.account1, this.token1); await time.advanceBlock(); await time.advanceBlock(); - const t2 = await this.votes.burn(this.NFT1); + const t2 = await this.votes.burn(this.account1, this.token1); await time.advanceBlock(); await time.advanceBlock(); - const t3 = await this.votes.mint(this.account1, this.NFT2); + const t3 = await this.votes.mint(this.account1, this.token2); await time.advanceBlock(); await time.advanceBlock(); - const t4 = await this.votes.burn(this.NFT2); + const t4 = await this.votes.burn(this.account1, this.token2); await time.advanceBlock(); await time.advanceBlock(); - const t5 = await this.votes.mint(this.account1, this.NFT3); + const t5 = await this.votes.mint(this.account1, this.token3); await time.advanceBlock(); await time.advanceBlock(); @@ -298,10 +299,10 @@ function shouldBehaveLikeVotes () { // https://github.com/compound-finance/compound-protocol/blob/master/tests/Governance/CompTest.js. describe('Compound test suite', function () { beforeEach(async function () { - await this.votes.mint(this.account1, this.NFT0); - await this.votes.mint(this.account1, this.NFT1); - await this.votes.mint(this.account1, this.NFT2); - await this.votes.mint(this.account1, this.NFT3); + await this.votes.mint(this.account1, this.token0); + await this.votes.mint(this.account1, this.token1); + await this.votes.mint(this.account1, this.token2); + await this.votes.mint(this.account1, this.token3); }); describe('getPastVotes', function () { diff --git a/test/governance/utils/Votes.test.js b/test/governance/utils/Votes.test.js index 32b7d1dca..8d5de78f7 100644 --- a/test/governance/utils/Votes.test.js +++ b/test/governance/utils/Votes.test.js @@ -50,10 +50,10 @@ contract('Votes', function (accounts) { this.account1 = account1; this.account2 = account2; this.account1Delegatee = account2; - this.NFT0 = new BN('10000000000000000000000000'); - this.NFT1 = new BN('10'); - this.NFT2 = new BN('20'); - this.NFT3 = new BN('30'); + this.token0 = new BN('10000000000000000000000000'); + this.token1 = new BN('10'); + this.token2 = new BN('20'); + this.token3 = new BN('30'); }); shouldBehaveLikeVotes(); diff --git a/test/token/ERC20/extensions/ERC20Votes.test.js b/test/token/ERC20/extensions/ERC20Votes.test.js index 9d3160bd1..0d3ab74aa 100644 --- a/test/token/ERC20/extensions/ERC20Votes.test.js +++ b/test/token/ERC20/extensions/ERC20Votes.test.js @@ -2,8 +2,9 @@ const { BN, constants, expectEvent, expectRevert, time } = require('@openzeppelin/test-helpers'); const { expect } = require('chai'); -const { MAX_UINT256, ZERO_ADDRESS, ZERO_BYTES32 } = constants; +const { MAX_UINT256, ZERO_ADDRESS } = constants; +const { shouldBehaveLikeVotes } = require('../../../governance/utils/Votes.behavior'); const { fromRpcSig } = require('ethereumjs-util'); const ethSigUtil = require('eth-sig-util'); const Wallet = require('ethereumjs-wallet').default; @@ -52,7 +53,7 @@ contract('ERC20Votes', function (accounts) { const amount = new BN('2').pow(new BN('224')); await expectRevert( this.token.mint(holder, amount), - 'ERC20Votes: total supply risks overflowing votes', + "SafeCast: value doesn't fit in 224 bits", ); }); @@ -62,7 +63,6 @@ contract('ERC20Votes', function (accounts) { await this.token.mint(holder, 1); } const block = await web3.eth.getBlockNumber(); - expect(await this.token.numCheckpoints(holder)).to.be.bignumber.equal('6'); // recent expect(await this.token.getPastVotes(holder, block - 1)).to.be.bignumber.equal('5'); // non-recent @@ -172,7 +172,7 @@ contract('ERC20Votes', function (accounts) { await expectRevert( this.token.delegateBySig(delegatorAddress, nonce, MAX_UINT256, v, r, s), - 'ERC20Votes: invalid nonce', + 'Votes: invalid nonce', ); }); @@ -204,7 +204,7 @@ contract('ERC20Votes', function (accounts) { )); await expectRevert( this.token.delegateBySig(delegatorAddress, nonce + 1, MAX_UINT256, v, r, s), - 'ERC20Votes: invalid nonce', + 'Votes: invalid nonce', ); }); @@ -221,7 +221,7 @@ contract('ERC20Votes', function (accounts) { await expectRevert( this.token.delegateBySig(delegatorAddress, nonce, expiry, v, r, s), - 'ERC20Votes: signature expired', + 'Votes: signature expired', ); }); }); @@ -341,12 +341,6 @@ contract('ERC20Votes', function (accounts) { await this.token.mint(holder, supply); }); - describe('balanceOf', function () { - it('grants to initial account', async function () { - expect(await this.token.balanceOf(holder)).to.be.bignumber.equal('10000000000000000000000000'); - }); - }); - describe('numCheckpoints', function () { it('returns the number of checkpoints for a delegate', async function () { await this.token.transfer(recipient, '100', { from: holder }); //give an account a few tokens for readability @@ -354,7 +348,7 @@ contract('ERC20Votes', function (accounts) { const t1 = await this.token.delegate(other1, { from: recipient }); expect(await this.token.numCheckpoints(other1)).to.be.bignumber.equal('1'); - + const t2 = await this.token.transfer(other2, 10, { from: recipient }); expect(await this.token.numCheckpoints(other1)).to.be.bignumber.equal('2'); @@ -396,11 +390,17 @@ contract('ERC20Votes', function (accounts) { }); }); + describe('balanceOf', function () { + it('grants to initial account', async function () { + expect(await this.token.balanceOf(holder)).to.be.bignumber.equal('10000000000000000000000000'); + }); + }); + describe('getPastVotes', function () { it('reverts if block number >= current block', async function () { await expectRevert( this.token.getPastVotes(other1, 5e10), - 'ERC20Votes: block not yet mined', + 'Checkpoints: block not yet mined', ); }); @@ -462,7 +462,7 @@ contract('ERC20Votes', function (accounts) { it('reverts if block number >= current block', async function () { await expectRevert( this.token.getPastTotalSupply(5e10), - 'ERC20Votes: block not yet mined', + 'Votes: block not yet mined', ); }); @@ -515,4 +515,20 @@ contract('ERC20Votes', function (accounts) { expect(await this.token.getPastTotalSupply(t4.receipt.blockNumber + 1)).to.be.bignumber.equal('10000000000000000000000000'); }); }); + + describe('Voting workflow', function () { + beforeEach(async function () { + this.account1 = holder; + this.account1Delegatee = holderDelegatee; + this.account2 = recipient; + this.name = 'My Token'; + this.votes = this.token + this.token0 = 1; + this.token1 = 1; + this.token2 = 1; + this.token3 = 1; + }); + + shouldBehaveLikeVotes(); + }); }); diff --git a/test/token/ERC20/extensions/ERC20VotesComp.test.js b/test/token/ERC20/extensions/ERC20VotesComp.test.js index b70c6d167..de2090b67 100644 --- a/test/token/ERC20/extensions/ERC20VotesComp.test.js +++ b/test/token/ERC20/extensions/ERC20VotesComp.test.js @@ -2,15 +2,16 @@ const { BN, constants, expectEvent, expectRevert, time } = require('@openzeppelin/test-helpers'); const { expect } = require('chai'); -const { MAX_UINT256, ZERO_ADDRESS, ZERO_BYTES32 } = constants; +const { MAX_UINT256, ZERO_ADDRESS } = constants; +const { batchInBlock } = require('../../../helpers/txpool'); +const { shouldBehaveLikeVotes } = require('../../../governance/utils/Votes.behavior'); const { fromRpcSig } = require('ethereumjs-util'); const ethSigUtil = require('eth-sig-util'); const Wallet = require('ethereumjs-wallet').default; const ERC20VotesCompMock = artifacts.require('ERC20VotesCompMock'); -const { batchInBlock } = require('../../../helpers/txpool'); const { EIP712Domain, domainSeparator } = require('../../../helpers/eip712'); const Delegation = [ @@ -52,7 +53,7 @@ contract('ERC20VotesComp', function (accounts) { const amount = new BN('2').pow(new BN('96')); await expectRevert( this.token.mint(holder, amount), - 'ERC20Votes: total supply risks overflowing votes', + "ERC20Votes: total supply risks overflowing votes", ); }); @@ -159,7 +160,7 @@ contract('ERC20VotesComp', function (accounts) { await expectRevert( this.token.delegateBySig(delegatorAddress, nonce, MAX_UINT256, v, r, s), - 'ERC20Votes: invalid nonce', + 'Votes: invalid nonce', ); }); @@ -191,7 +192,7 @@ contract('ERC20VotesComp', function (accounts) { )); await expectRevert( this.token.delegateBySig(delegatorAddress, nonce + 1, MAX_UINT256, v, r, s), - 'ERC20Votes: invalid nonce', + 'Votes: invalid nonce', ); }); @@ -208,7 +209,7 @@ contract('ERC20VotesComp', function (accounts) { await expectRevert( this.token.delegateBySig(delegatorAddress, nonce, expiry, v, r, s), - 'ERC20Votes: signature expired', + 'Votes: signature expired', ); }); }); @@ -373,12 +374,12 @@ contract('ERC20VotesComp', function (accounts) { expect(await this.token.checkpoints(other1, 1)).to.be.deep.equal([ t4.receipt.blockNumber.toString(), '100' ]); }); }); - + describe('getPriorVotes', function () { it('reverts if block number >= current block', async function () { await expectRevert( this.token.getPriorVotes(other1, 5e10), - 'ERC20Votes: block not yet mined', + 'Checkpoints: block not yet mined', ); }); @@ -440,7 +441,7 @@ contract('ERC20VotesComp', function (accounts) { it('reverts if block number >= current block', async function () { await expectRevert( this.token.getPastTotalSupply(5e10), - 'ERC20Votes: block not yet mined', + 'Votes: block not yet mined', ); }); @@ -493,4 +494,20 @@ contract('ERC20VotesComp', function (accounts) { expect(await this.token.getPastTotalSupply(t4.receipt.blockNumber + 1)).to.be.bignumber.equal('10000000000000000000000000'); }); }); + + describe('Voting workflow', function () { + beforeEach(async function () { + this.account1 = holder; + this.account1Delegatee = holderDelegatee; + this.account2 = recipient; + this.name = 'My Token'; + this.votes = this.token + this.token0 = 1; + this.token1 = 1; + this.token2 = 1; + this.token3 = 1; + }); + + shouldBehaveLikeVotes(); + }); }); diff --git a/test/token/ERC721/extensions/ERC721Votes.test.js b/test/token/ERC721/extensions/ERC721Votes.test.js index 6f001f20b..c6a9bbb32 100644 --- a/test/token/ERC721/extensions/ERC721Votes.test.js +++ b/test/token/ERC721/extensions/ERC721Votes.test.js @@ -3,9 +3,6 @@ const { BN, expectEvent, time } = require('@openzeppelin/test-helpers'); const { expect } = require('chai'); -const { promisify } = require('util'); -const queue = promisify(setImmediate); - const ERC721VotesMock = artifacts.require('ERC721VotesMock'); const { shouldBehaveLikeVotes } = require('../../../governance/utils/Votes.behavior'); @@ -23,18 +20,18 @@ contract('ERC721Votes', function (accounts) { // See https://github.com/trufflesuite/ganache-core/issues/515 this.chainId = await this.votes.getChainId(); - this.NFT0 = new BN('10000000000000000000000000'); - this.NFT1 = new BN('10'); - this.NFT2 = new BN('20'); - this.NFT3 = new BN('30'); + this.token0 = new BN('10000000000000000000000000'); + this.token1 = new BN('10'); + this.token2 = new BN('20'); + this.token3 = new BN('30'); }); describe('balanceOf', function () { beforeEach(async function () { - await this.votes.mint(account1, this.NFT0); - await this.votes.mint(account1, this.NFT1); - await this.votes.mint(account1, this.NFT2); - await this.votes.mint(account1, this.NFT3); + await this.votes.mint(account1, this.token0); + await this.votes.mint(account1, this.token1); + await this.votes.mint(account1, this.token2); + await this.votes.mint(account1, this.token3); }); it('grants to initial account', async function () { @@ -44,12 +41,12 @@ contract('ERC721Votes', function (accounts) { describe('transfers', function () { beforeEach(async function () { - await this.votes.mint(account1, this.NFT0); + await this.votes.mint(account1, this.token0); }); it('no delegation', async function () { - const { receipt } = await this.votes.transferFrom(account1, account2, this.NFT0, { from: account1 }); - expectEvent(receipt, 'Transfer', { from: account1, to: account2, tokenId: this.NFT0 }); + const { receipt } = await this.votes.transferFrom(account1, account2, this.token0, { from: account1 }); + expectEvent(receipt, 'Transfer', { from: account1, to: account2, tokenId: this.token0 }); expectEvent.notEmitted(receipt, 'DelegateVotesChanged'); this.account1Votes = '0'; @@ -59,8 +56,8 @@ contract('ERC721Votes', function (accounts) { it('sender delegation', async function () { await this.votes.delegate(account1, { from: account1 }); - const { receipt } = await this.votes.transferFrom(account1, account2, this.NFT0, { from: account1 }); - expectEvent(receipt, 'Transfer', { from: account1, to: account2, tokenId: this.NFT0 }); + const { receipt } = await this.votes.transferFrom(account1, account2, this.token0, { from: account1 }); + expectEvent(receipt, 'Transfer', { from: account1, to: account2, tokenId: this.token0 }); expectEvent(receipt, 'DelegateVotesChanged', { delegate: account1, previousBalance: '1', newBalance: '0' }); const { logIndex: transferLogIndex } = receipt.logs.find(({ event }) => event == 'Transfer'); @@ -73,8 +70,8 @@ contract('ERC721Votes', function (accounts) { it('receiver delegation', async function () { await this.votes.delegate(account2, { from: account2 }); - const { receipt } = await this.votes.transferFrom(account1, account2, this.NFT0, { from: account1 }); - expectEvent(receipt, 'Transfer', { from: account1, to: account2, tokenId: this.NFT0 }); + const { receipt } = await this.votes.transferFrom(account1, account2, this.token0, { from: account1 }); + expectEvent(receipt, 'Transfer', { from: account1, to: account2, tokenId: this.token0 }); expectEvent(receipt, 'DelegateVotesChanged', { delegate: account2, previousBalance: '0', newBalance: '1' }); const { logIndex: transferLogIndex } = receipt.logs.find(({ event }) => event == 'Transfer'); @@ -88,8 +85,8 @@ contract('ERC721Votes', function (accounts) { await this.votes.delegate(account1, { from: account1 }); await this.votes.delegate(account2, { from: account2 }); - const { receipt } = await this.votes.transferFrom(account1, account2, this.NFT0, { from: account1 }); - expectEvent(receipt, 'Transfer', { from: account1, to: account2, tokenId: this.NFT0 }); + const { receipt } = await this.votes.transferFrom(account1, account2, this.token0, { from: account1 }); + expectEvent(receipt, 'Transfer', { from: account1, to: account2, tokenId: this.token0 }); expectEvent(receipt, 'DelegateVotesChanged', { delegate: account1, previousBalance: '1', newBalance: '0'}); expectEvent(receipt, 'DelegateVotesChanged', { delegate: account2, previousBalance: '0', newBalance: '1' }); @@ -103,7 +100,7 @@ contract('ERC721Votes', function (accounts) { it('returns the same total supply on transfers', async function () { await this.votes.delegate(account1, { from: account1 }); - const { receipt } = await this.votes.transferFrom(account1, account2, this.NFT0, { from: account1 }); + const { receipt } = await this.votes.transferFrom(account1, account2, this.token0, { from: account1 }); await time.advanceBlock(); await time.advanceBlock(); @@ -116,22 +113,22 @@ contract('ERC721Votes', function (accounts) { }); it('generally returns the voting balance at the appropriate checkpoint', async function () { - await this.votes.mint(account1, this.NFT1); - await this.votes.mint(account1, this.NFT2); - await this.votes.mint(account1, this.NFT3); + await this.votes.mint(account1, this.token1); + await this.votes.mint(account1, this.token2); + await this.votes.mint(account1, this.token3); const total = await this.votes.balanceOf(account1); const t1 = await this.votes.delegate(other1, { from: account1 }); await time.advanceBlock(); await time.advanceBlock(); - const t2 = await this.votes.transferFrom(account1, other2, this.NFT0, { from: account1 }); + const t2 = await this.votes.transferFrom(account1, other2, this.token0, { from: account1 }); await time.advanceBlock(); await time.advanceBlock(); - const t3 = await this.votes.transferFrom(account1, other2, this.NFT2, { from: account1 }); + const t3 = await this.votes.transferFrom(account1, other2, this.token2, { from: account1 }); await time.advanceBlock(); await time.advanceBlock(); - const t4 = await this.votes.transferFrom(other2, account1, this.NFT2, { from: other2 }); + const t4 = await this.votes.transferFrom(other2, account1, this.token2, { from: other2 }); await time.advanceBlock(); await time.advanceBlock(); diff --git a/test/utils/Nonces.test.js b/test/utils/Nonces.test.js new file mode 100644 index 000000000..890bdb4b7 --- /dev/null +++ b/test/utils/Nonces.test.js @@ -0,0 +1,25 @@ +require('@openzeppelin/test-helpers'); + +const NoncesImpl = artifacts.require('NoncesImpl'); + +contract('Nonces', function (accounts) { + const [ sender, other ] = accounts; + + beforeEach(async function () { + this.nonces = await NoncesImpl.new(); + }); + + it('gets a nonce', async function () { + expect(await this.nonces.nonces(sender)).to.be.bignumber.equal('0'); + }); + + it('increment a nonce', async function () { + await this.nonces.useNonce(sender); + expect(await this.nonces.nonces(sender)).to.be.bignumber.equal('1'); + }); + + it('nonce is specific to address argument', async function () { + await this.nonces.useNonce(sender); + expect(await this.nonces.nonces(other)).to.be.bignumber.equal('0'); + }); +}); From 3c80a4286688cd6100f127aaa1e91daab4c34163 Mon Sep 17 00:00:00 2001 From: JulissaDantes Date: Wed, 14 Dec 2022 14:43:45 -0500 Subject: [PATCH 007/182] Remove hooks from ERC20 (#3838) Co-authored-by: Francisco --- CHANGELOG.md | 24 +++ contracts/token/ERC20/ERC20.sol | 144 ++++++------------ .../token/ERC20/extensions/ERC20Capped.sol | 15 +- .../token/ERC20/extensions/ERC20Pausable.sol | 7 +- .../token/ERC20/extensions/ERC20Snapshot.sol | 9 +- .../token/ERC20/extensions/ERC20Votes.sol | 15 +- test/token/ERC20/ERC20.behavior.js | 13 +- test/token/ERC20/ERC20.test.js | 16 +- .../ERC20/extensions/ERC20FlashMint.test.js | 4 +- .../token/ERC20/extensions/ERC20Votes.test.js | 2 +- 10 files changed, 110 insertions(+), 139 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bf92d9b43..9450563fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,30 @@ * Removed presets in favor of [OpenZeppelin Contracts Wizard](https://wizard.openzeppelin.com/). * `TransparentUpgradeableProxy`: Removed `admin` and `implementation` getters, which were only callable by the proxy owner and thus not very useful. ([#3820](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3820)) * `ProxyAdmin`: Removed `getProxyAdmin` and `getProxyImplementation` getters. ([#3820](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3820)) + * `ERC20`: Deleted `_beforeTokenTransfer` and `_afterTokenTransfer` hooks, added a new internal `_update` function for customizations, and refactored all extensions using those hooks to use `_update` instead. ([#3838](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3838)) + +### How to upgrade from 4.x + +#### ERC20, ERC721, and ERC1155 + +These breaking changes will require modifications to ERC20, ERC721, and ERC1155 contracts, since the `_afterTokenTransfer` and `_beforeTokenTransfer` functions were removed. Any customization made through those hooks should now be done overriding the new `_update` function instead. + +Minting and burning are implemented by `_update` and customizations should be done by overriding this function as well. `_mint` and `_burn` are no longer virtual (meaning they are not overridable) to guard against possible inconsistencies. + +For example, a contract using `ERC20`'s `_beforeTokenTransfer` hook would have to be changed in the following way. + +```diff +- function _beforeTokenTransfer( ++ function _update( + address from, + address to, + uint256 amount + ) internal virtual override { +- super._beforeTokenTransfer(from, to, amount); + require(!condition(), "ERC20: wrong condition"); ++ super._update(from, to, amount); + } +``` ## Unreleased diff --git a/contracts/token/ERC20/ERC20.sol b/contracts/token/ERC20/ERC20.sol index c5c52691d..c10f659d0 100644 --- a/contracts/token/ERC20/ERC20.sol +++ b/contracts/token/ERC20/ERC20.sol @@ -216,87 +216,81 @@ contract ERC20 is Context, IERC20, IERC20Metadata { * * Emits a {Transfer} event. * - * Requirements: - * - * - `from` cannot be the zero address. - * - `to` cannot be the zero address. - * - `from` must have a balance of at least `amount`. + * NOTE: This function is not virtual, {_update} should be overridden instead. */ function _transfer( address from, address to, uint256 amount - ) internal virtual { + ) internal { require(from != address(0), "ERC20: transfer from the zero address"); require(to != address(0), "ERC20: transfer to the zero address"); + _update(from, to, amount); + } - _beforeTokenTransfer(from, to, amount); - - uint256 fromBalance = _balances[from]; - require(fromBalance >= amount, "ERC20: transfer amount exceeds balance"); - unchecked { - _balances[from] = fromBalance - amount; - // Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by - // decrementing then incrementing. - _balances[to] += amount; + /** + * @dev Transfers `amount` of tokens from `from` to `to`, or alternatively mints (or burns) if `from` (or `to`) is + * the zero address. All customizations to transfers, mints, and burns should be done by overriding this function. + * + * Emits a {Transfer} event. + */ + function _update( + address from, + address to, + uint256 amount + ) internal virtual { + if (from == address(0)) { + _totalSupply += amount; + unchecked { + // Overflow not possible: balance + amount is at most totalSupply + amount, which is checked above. + _balances[to] += amount; + } + } else if (to == address(0)) { + uint256 fromBalance = _balances[from]; + require(fromBalance >= amount, "ERC20: burn amount exceeds balance"); + _totalSupply -= amount; + unchecked { + // Overflow not possible: amount <= fromBalance <= totalSupply. + _balances[from] = fromBalance - amount; + } + } else { + uint256 fromBalance = _balances[from]; + require(fromBalance >= amount, "ERC20: transfer amount exceeds balance"); + unchecked { + _balances[from] = fromBalance - amount; + // Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by + // decrementing then incrementing. + _balances[to] += amount; + } } emit Transfer(from, to, amount); - - _afterTokenTransfer(from, to, amount); } - /** @dev Creates `amount` tokens and assigns them to `account`, increasing - * the total supply. + /** + * @dev Creates `amount` tokens and assigns them to `account`, by transferring it from address(0). + * Relies on the `_update` mechanism * * Emits a {Transfer} event with `from` set to the zero address. * - * Requirements: - * - * - `account` cannot be the zero address. + * NOTE: This function is not virtual, {_update} should be overridden instead. */ - function _mint(address account, uint256 amount) internal virtual { + function _mint(address account, uint256 amount) internal { require(account != address(0), "ERC20: mint to the zero address"); - - _beforeTokenTransfer(address(0), account, amount); - - _totalSupply += amount; - unchecked { - // Overflow not possible: balance + amount is at most totalSupply + amount, which is checked above. - _balances[account] += amount; - } - emit Transfer(address(0), account, amount); - - _afterTokenTransfer(address(0), account, amount); + _update(address(0), account, amount); } /** - * @dev Destroys `amount` tokens from `account`, reducing the - * total supply. + * @dev Destroys `amount` tokens from `account`, by transferring it to address(0). + * Relies on the `_update` mechanism. * * Emits a {Transfer} event with `to` set to the zero address. * - * Requirements: - * - * - `account` cannot be the zero address. - * - `account` must have at least `amount` tokens. + * NOTE: This function is not virtual, {_update} should be overridden instead */ - function _burn(address account, uint256 amount) internal virtual { + function _burn(address account, uint256 amount) internal { require(account != address(0), "ERC20: burn from the zero address"); - - _beforeTokenTransfer(account, address(0), amount); - - uint256 accountBalance = _balances[account]; - require(accountBalance >= amount, "ERC20: burn amount exceeds balance"); - unchecked { - _balances[account] = accountBalance - amount; - // Overflow not possible: amount <= accountBalance <= totalSupply. - _totalSupply -= amount; - } - - emit Transfer(account, address(0), amount); - - _afterTokenTransfer(account, address(0), amount); + _update(account, address(0), amount); } /** @@ -345,44 +339,4 @@ contract ERC20 is Context, IERC20, IERC20Metadata { } } } - - /** - * @dev Hook that is called before any transfer of tokens. This includes - * minting and burning. - * - * Calling conditions: - * - * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens - * will be transferred to `to`. - * - when `from` is zero, `amount` tokens will be minted for `to`. - * - when `to` is zero, `amount` of ``from``'s tokens will be burned. - * - `from` and `to` are never both zero. - * - * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - */ - function _beforeTokenTransfer( - address from, - address to, - uint256 amount - ) internal virtual {} - - /** - * @dev Hook that is called after any transfer of tokens. This includes - * minting and burning. - * - * Calling conditions: - * - * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens - * has been transferred to `to`. - * - when `from` is zero, `amount` tokens have been minted for `to`. - * - when `to` is zero, `amount` of ``from``'s tokens have been burned. - * - `from` and `to` are never both zero. - * - * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - */ - function _afterTokenTransfer( - address from, - address to, - uint256 amount - ) internal virtual {} } diff --git a/contracts/token/ERC20/extensions/ERC20Capped.sol b/contracts/token/ERC20/extensions/ERC20Capped.sol index 16f830d18..3be9ff805 100644 --- a/contracts/token/ERC20/extensions/ERC20Capped.sol +++ b/contracts/token/ERC20/extensions/ERC20Capped.sol @@ -28,10 +28,17 @@ abstract contract ERC20Capped is ERC20 { } /** - * @dev See {ERC20-_mint}. + * @dev See {ERC20-_transfer}. */ - function _mint(address account, uint256 amount) internal virtual override { - require(ERC20.totalSupply() + amount <= cap(), "ERC20Capped: cap exceeded"); - super._mint(account, amount); + function _update( + address from, + address to, + uint256 amount + ) internal virtual override { + if (from == address(0)) { + require(ERC20.totalSupply() + amount <= cap(), "ERC20Capped: cap exceeded"); + } + + super._update(from, to, amount); } } diff --git a/contracts/token/ERC20/extensions/ERC20Pausable.sol b/contracts/token/ERC20/extensions/ERC20Pausable.sol index e448e96a6..92d764932 100644 --- a/contracts/token/ERC20/extensions/ERC20Pausable.sol +++ b/contracts/token/ERC20/extensions/ERC20Pausable.sol @@ -15,19 +15,18 @@ import "../../../security/Pausable.sol"; */ abstract contract ERC20Pausable is ERC20, Pausable { /** - * @dev See {ERC20-_beforeTokenTransfer}. + * @dev See {ERC20-_update}. * * Requirements: * * - the contract must not be paused. */ - function _beforeTokenTransfer( + function _update( address from, address to, uint256 amount ) internal virtual override { - super._beforeTokenTransfer(from, to, amount); - require(!paused(), "ERC20Pausable: token transfer while paused"); + super._update(from, to, amount); } } diff --git a/contracts/token/ERC20/extensions/ERC20Snapshot.sol b/contracts/token/ERC20/extensions/ERC20Snapshot.sol index 0b46fc660..2c34e2268 100644 --- a/contracts/token/ERC20/extensions/ERC20Snapshot.sol +++ b/contracts/token/ERC20/extensions/ERC20Snapshot.sol @@ -118,15 +118,13 @@ abstract contract ERC20Snapshot is ERC20 { return snapshotted ? value : totalSupply(); } - // Update balance and/or total supply snapshots before the values are modified. This is implemented - // in the _beforeTokenTransfer hook, which is executed for _mint, _burn, and _transfer operations. - function _beforeTokenTransfer( + // Update balance and/or total supply snapshots before the values are modified. This is executed + // for _mint, _burn, and _transfer operations. + function _update( address from, address to, uint256 amount ) internal virtual override { - super._beforeTokenTransfer(from, to, amount); - if (from == address(0)) { // mint _updateAccountSnapshot(to); @@ -140,6 +138,7 @@ abstract contract ERC20Snapshot is ERC20 { _updateAccountSnapshot(from); _updateAccountSnapshot(to); } + super._update(from, to, amount); } function _valueAt(uint256 snapshotId, Snapshots storage snapshots) private view returns (bool, uint256) { diff --git a/contracts/token/ERC20/extensions/ERC20Votes.sol b/contracts/token/ERC20/extensions/ERC20Votes.sol index e2a070921..7f9f36b76 100644 --- a/contracts/token/ERC20/extensions/ERC20Votes.sol +++ b/contracts/token/ERC20/extensions/ERC20Votes.sol @@ -35,13 +35,16 @@ abstract contract ERC20Votes is ERC20, Votes { * * Emits a {IVotes-DelegateVotesChanged} event. */ - function _afterTokenTransfer( + function _update( address from, address to, uint256 amount ) internal virtual override { + super._update(from, to, amount); + if (from == address(0)) { + require(totalSupply() <= _maxSupply(), "ERC20Votes: total supply risks overflowing votes"); + } _transferVotingUnits(from, to, amount); - super._afterTokenTransfer(from, to, amount); } /** @@ -64,12 +67,4 @@ abstract contract ERC20Votes is ERC20, Votes { function _getVotingUnits(address account) internal view virtual override returns (uint256) { return balanceOf(account); } - - /** - * @dev Snapshots the totalSupply after it has been increased. - */ - function _mint(address account, uint256 amount) internal virtual override { - super._mint(account, amount); - require(totalSupply() <= _maxSupply(), "ERC20Votes: total supply risks overflowing votes"); - } } diff --git a/test/token/ERC20/ERC20.behavior.js b/test/token/ERC20/ERC20.behavior.js index 8bc54762a..701ec2e6d 100644 --- a/test/token/ERC20/ERC20.behavior.js +++ b/test/token/ERC20/ERC20.behavior.js @@ -158,8 +158,9 @@ function shouldBehaveLikeERC20 (errorPrefix, initialSupply, initialHolder, recip }); it('reverts', async function () { - await expectRevert(this.token.transferFrom( - tokenOwner, to, amount, { from: spender }), `${errorPrefix}: transfer to the zero address`, + await expectRevert( + this.token.transferFrom(tokenOwner, to, amount, { from: spender }), + `${errorPrefix}: transfer to the zero address`, ); }); }); @@ -240,14 +241,6 @@ function shouldBehaveLikeERC20Transfer (errorPrefix, from, to, balance, transfer }); }); }); - - describe('when the recipient is the zero address', function () { - it('reverts', async function () { - await expectRevert(transfer.call(this, from, ZERO_ADDRESS, balance), - `${errorPrefix}: transfer to the zero address`, - ); - }); - }); } function shouldBehaveLikeERC20Approve (errorPrefix, owner, spender, supply, approve) { diff --git a/test/token/ERC20/ERC20.test.js b/test/token/ERC20/ERC20.test.js index 992edf93b..46ee5ab63 100644 --- a/test/token/ERC20/ERC20.test.js +++ b/test/token/ERC20/ERC20.test.js @@ -207,6 +207,14 @@ contract('ERC20', function (accounts) { ); }); + it('rejects overflow', async function () { + const maxUint256 = new BN('2').pow(new BN(256)).subn(1); + await expectRevert( + this.token.mint(recipient, maxUint256), + 'reverted with panic code 0x11 (Arithmetic operation underflowed or overflowed outside of an unchecked block)', + ); + }); + describe('for a non zero account', function () { beforeEach('minting', async function () { this.receipt = await this.token.mint(recipient, amount); @@ -283,14 +291,6 @@ contract('ERC20', function (accounts) { shouldBehaveLikeERC20Transfer('ERC20', initialHolder, recipient, initialSupply, function (from, to, amount) { return this.token.transferInternal(from, to, amount); }); - - describe('when the sender is the zero address', function () { - it('reverts', async function () { - await expectRevert(this.token.transferInternal(ZERO_ADDRESS, recipient, initialSupply), - 'ERC20: transfer from the zero address', - ); - }); - }); }); describe('_approve', function () { diff --git a/test/token/ERC20/extensions/ERC20FlashMint.test.js b/test/token/ERC20/extensions/ERC20FlashMint.test.js index d08464dec..4354dd90c 100644 --- a/test/token/ERC20/extensions/ERC20FlashMint.test.js +++ b/test/token/ERC20/extensions/ERC20FlashMint.test.js @@ -1,8 +1,8 @@ /* eslint-disable */ -const { BN, constants, expectEvent, expectRevert, time } = require('@openzeppelin/test-helpers'); +const { BN, constants, expectEvent, expectRevert } = require('@openzeppelin/test-helpers'); const { expect } = require('chai'); -const { MAX_UINT256, ZERO_ADDRESS, ZERO_BYTES32 } = constants; +const { MAX_UINT256, ZERO_ADDRESS } = constants; const ERC20FlashMintMock = artifacts.require('ERC20FlashMintMock'); const ERC3156FlashBorrowerMock = artifacts.require('ERC3156FlashBorrowerMock'); diff --git a/test/token/ERC20/extensions/ERC20Votes.test.js b/test/token/ERC20/extensions/ERC20Votes.test.js index 0d3ab74aa..d89a7b9fb 100644 --- a/test/token/ERC20/extensions/ERC20Votes.test.js +++ b/test/token/ERC20/extensions/ERC20Votes.test.js @@ -53,7 +53,7 @@ contract('ERC20Votes', function (accounts) { const amount = new BN('2').pow(new BN('224')); await expectRevert( this.token.mint(holder, amount), - "SafeCast: value doesn't fit in 224 bits", + "ERC20Votes: total supply risks overflowing votes", ); }); From 81dbe643a06d4d40d2026825791688368c05f998 Mon Sep 17 00:00:00 2001 From: JulissaDantes Date: Mon, 19 Dec 2022 15:56:36 -0500 Subject: [PATCH 008/182] Remove ERC165Storage (#3880) Co-authored-by: Francisco --- CHANGELOG.md | 11 +++++ contracts/mocks/ERC165StorageMock.sol | 11 ----- contracts/utils/README.adoc | 2 - contracts/utils/introspection/ERC165.sol | 2 - .../utils/introspection/ERC165Storage.sol | 42 ------------------- .../utils/introspection/ERC165Storage.test.js | 25 ----------- 6 files changed, 11 insertions(+), 82 deletions(-) delete mode 100644 contracts/mocks/ERC165StorageMock.sol delete mode 100644 contracts/utils/introspection/ERC165Storage.sol delete mode 100644 test/utils/introspection/ERC165Storage.test.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 0430d8736..30b1da173 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ * `TransparentUpgradeableProxy`: Removed `admin` and `implementation` getters, which were only callable by the proxy owner and thus not very useful. ([#3820](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3820)) * `ProxyAdmin`: Removed `getProxyAdmin` and `getProxyImplementation` getters. ([#3820](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3820)) * `ERC20`: Deleted `_beforeTokenTransfer` and `_afterTokenTransfer` hooks, added a new internal `_update` function for customizations, and refactored all extensions using those hooks to use `_update` instead. ([#3838](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3838)) + * `ERC165Storage`: Removed this contract in favor of inheritance based approach. ([#3880](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3880)) ### How to upgrade from 4.x @@ -33,6 +34,16 @@ For example, a contract using `ERC20`'s `_beforeTokenTransfer` hook would have t } ``` +#### ERC165Storage + +Users that were registering EIP-165 interfaces with `_registerInterface` from `ERC165Storage` should instead do so so by overriding the `supportsInterface` function as seen below: + +```solidity +function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { + return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId); +} +``` + ## Unreleased * `ReentrancyGuard`: Add a `_reentrancyGuardEntered` function to expose the guard status. ([#3714](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3714)) diff --git a/contracts/mocks/ERC165StorageMock.sol b/contracts/mocks/ERC165StorageMock.sol deleted file mode 100644 index 4b0bae908..000000000 --- a/contracts/mocks/ERC165StorageMock.sol +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -import "../utils/introspection/ERC165Storage.sol"; - -contract ERC165StorageMock is ERC165Storage { - function registerInterface(bytes4 interfaceId) public { - _registerInterface(interfaceId); - } -} diff --git a/contracts/utils/README.adoc b/contracts/utils/README.adoc index 7fef825a6..babaef522 100644 --- a/contracts/utils/README.adoc +++ b/contracts/utils/README.adoc @@ -70,8 +70,6 @@ Note that, in all cases, accounts simply _declare_ their interfaces, but they ar {{ERC165}} -{{ERC165Storage}} - {{ERC165Checker}} {{IERC1820Registry}} diff --git a/contracts/utils/introspection/ERC165.sol b/contracts/utils/introspection/ERC165.sol index 3bf5613a6..49395f8a1 100644 --- a/contracts/utils/introspection/ERC165.sol +++ b/contracts/utils/introspection/ERC165.sol @@ -16,8 +16,6 @@ import "./IERC165.sol"; * return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId); * } * ``` - * - * Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation. */ abstract contract ERC165 is IERC165 { /** diff --git a/contracts/utils/introspection/ERC165Storage.sol b/contracts/utils/introspection/ERC165Storage.sol deleted file mode 100644 index c99d9f3fb..000000000 --- a/contracts/utils/introspection/ERC165Storage.sol +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165Storage.sol) - -pragma solidity ^0.8.0; - -import "./ERC165.sol"; - -/** - * @dev Storage based implementation of the {IERC165} interface. - * - * Contracts may inherit from this and call {_registerInterface} to declare - * their support of an interface. - */ -abstract contract ERC165Storage is ERC165 { - /** - * @dev Mapping of interface ids to whether or not it's supported. - */ - mapping(bytes4 => bool) private _supportedInterfaces; - - /** - * @dev See {IERC165-supportsInterface}. - */ - function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { - return super.supportsInterface(interfaceId) || _supportedInterfaces[interfaceId]; - } - - /** - * @dev Registers the contract as an implementer of the interface defined by - * `interfaceId`. Support of the actual ERC165 interface is automatic and - * registering its interface id is not required. - * - * See {IERC165-supportsInterface}. - * - * Requirements: - * - * - `interfaceId` cannot be the ERC165 invalid interface (`0xffffffff`). - */ - function _registerInterface(bytes4 interfaceId) internal virtual { - require(interfaceId != 0xffffffff, "ERC165: invalid interface id"); - _supportedInterfaces[interfaceId] = true; - } -} diff --git a/test/utils/introspection/ERC165Storage.test.js b/test/utils/introspection/ERC165Storage.test.js deleted file mode 100644 index 568d64576..000000000 --- a/test/utils/introspection/ERC165Storage.test.js +++ /dev/null @@ -1,25 +0,0 @@ -const { expectRevert } = require('@openzeppelin/test-helpers'); - -const { shouldSupportInterfaces } = require('./SupportsInterface.behavior'); - -const ERC165Mock = artifacts.require('ERC165StorageMock'); - -contract('ERC165Storage', function (accounts) { - beforeEach(async function () { - this.mock = await ERC165Mock.new(); - }); - - it('register interface', async function () { - expect(await this.mock.supportsInterface('0x00000001')).to.be.equal(false); - await this.mock.registerInterface('0x00000001'); - expect(await this.mock.supportsInterface('0x00000001')).to.be.equal(true); - }); - - it('does not allow 0xffffffff', async function () { - await expectRevert(this.mock.registerInterface('0xffffffff'), 'ERC165: invalid interface id'); - }); - - shouldSupportInterfaces([ - 'ERC165', - ]); -}); From 4147005b0c156d495b77d821e8537899ef39cc62 Mon Sep 17 00:00:00 2001 From: Francisco Giordano Date: Mon, 26 Dec 2022 23:28:51 -0300 Subject: [PATCH 009/182] Update Prettier Solidity (#3898) --- .prettierrc | 3 +- .../polygon/CrossChainEnabledPolygonChild.sol | 2 +- contracts/finance/VestingWallet.sol | 6 +- contracts/governance/Governor.sol | 27 +- contracts/governance/TimelockController.sol | 20 +- .../GovernorCompatibilityBravo.sol | 17 +- .../IGovernorCompatibilityBravo.sol | 8 +- .../extensions/GovernorCountingSimple.sol | 13 +- .../extensions/GovernorSettings.sol | 6 +- .../extensions/GovernorTimelockControl.sol | 2 +- contracts/governance/utils/IVotes.sol | 9 +- contracts/governance/utils/Votes.sol | 12 +- contracts/interfaces/IERC1363.sol | 25 +- contracts/interfaces/IERC1363Spender.sol | 6 +- contracts/interfaces/IERC2981.sol | 8 +- contracts/interfaces/IERC4626.sol | 12 +- contracts/metatx/MinimalForwarder.sol | 9 +- contracts/mocks/AddressImpl.sol | 6 +- contracts/mocks/CheckpointsMock.sol | 30 +- contracts/mocks/ClonesMock.sol | 6 +- contracts/mocks/ContextMock.sol | 6 +- contracts/mocks/Create2Impl.sol | 6 +- contracts/mocks/DummyImplementation.sol | 6 +- contracts/mocks/ECDSAMock.sol | 13 +- contracts/mocks/EIP712External.sol | 7 +- contracts/mocks/ERC1155BurnableMock.sol | 7 +- contracts/mocks/ERC1155Mock.sol | 26 +- contracts/mocks/ERC1155ReceiverMock.sol | 7 +- contracts/mocks/ERC20CappedMock.sol | 6 +- contracts/mocks/ERC20DecimalsMock.sol | 6 +- contracts/mocks/ERC20Mock.sol | 12 +- contracts/mocks/ERC3156FlashBorrowerMock.sol | 2 +- contracts/mocks/ERC4626Mock.sol | 32 +- contracts/mocks/ERC721BurnableMock.sol | 6 +- .../mocks/ERC721ConsecutiveEnumerableMock.sol | 10 +- contracts/mocks/ERC721EnumerableMock.sol | 6 +- contracts/mocks/ERC721Mock.sol | 6 +- contracts/mocks/ERC721PausableMock.sol | 6 +- contracts/mocks/ERC721RoyaltyMock.sol | 6 +- contracts/mocks/ERC721URIStorageMock.sol | 6 +- contracts/mocks/ERC777Mock.sol | 20 +- contracts/mocks/ERC777SenderRecipientMock.sol | 13 +- .../mocks/GovernorCompatibilityBravoMock.sol | 27 +- .../mocks/GovernorPreventLateQuorumMock.sol | 9 +- .../mocks/GovernorTimelockCompoundMock.sol | 27 +- .../mocks/GovernorTimelockControlMock.sol | 18 +- contracts/mocks/MathMock.sol | 7 +- contracts/mocks/MerkleProofWrapper.sol | 12 +- .../MultipleInheritanceInitializableMocks.sol | 7 +- contracts/mocks/SafeERC20Helper.sol | 18 +- contracts/mocks/SafeMathMock.sol | 18 +- contracts/mocks/SignatureCheckerMock.sol | 6 +- contracts/mocks/UUPS/UUPSLegacy.sol | 6 +- contracts/mocks/crosschain/bridges.sol | 12 +- contracts/mocks/wizard/MyGovernor1.sol | 28 +- contracts/mocks/wizard/MyGovernor2.sol | 28 +- contracts/mocks/wizard/MyGovernor3.sol | 37 +- contracts/proxy/Clones.sol | 9 +- contracts/proxy/ERC1967/ERC1967Upgrade.sol | 18 +- .../TransparentUpgradeableProxy.sol | 6 +- contracts/token/ERC1155/ERC1155.sol | 36 +- contracts/token/ERC1155/IERC1155.sol | 16 +- .../ERC1155/extensions/ERC1155Burnable.sol | 12 +- contracts/token/ERC20/ERC20.sol | 30 +- contracts/token/ERC20/IERC20.sol | 6 +- .../token/ERC20/extensions/ERC20Capped.sol | 6 +- .../token/ERC20/extensions/ERC20Pausable.sol | 6 +- .../token/ERC20/extensions/ERC20Snapshot.sol | 6 +- .../token/ERC20/extensions/ERC20Votes.sol | 6 +- contracts/token/ERC20/extensions/ERC4626.sol | 19 +- contracts/token/ERC20/utils/SafeERC20.sol | 31 +- contracts/token/ERC20/utils/TokenTimelock.sol | 6 +- contracts/token/ERC721/ERC721.sol | 53 +- contracts/token/ERC721/IERC721.sol | 19 +- contracts/token/ERC721/utils/ERC721Holder.sol | 7 +- contracts/token/ERC777/ERC777.sol | 51 +- contracts/token/ERC777/IERC777.sol | 13 +- contracts/token/common/ERC2981.sol | 6 +- contracts/utils/Address.sol | 6 +- contracts/utils/Checkpoints.sol | 80 +- contracts/utils/Create2.sol | 12 +- contracts/utils/cryptography/ECDSA.sol | 26 +- contracts/utils/cryptography/MerkleProof.sol | 12 +- .../utils/cryptography/SignatureChecker.sol | 6 +- .../utils/introspection/ERC165Checker.sol | 9 +- .../introspection/ERC1820Implementer.sol | 11 +- .../utils/introspection/IERC1820Registry.sol | 6 +- contracts/utils/math/Math.sol | 41 +- contracts/utils/math/SafeMath.sol | 18 +- contracts/utils/structs/BitMaps.sol | 6 +- contracts/utils/structs/EnumerableMap.sol | 36 +- contracts/vendor/amb/IAMB.sol | 14 +- contracts/vendor/arbitrum/IArbSys.sol | 9 +- contracts/vendor/arbitrum/IBridge.sol | 9 +- contracts/vendor/arbitrum/IOutbox.sol | 6 +- .../vendor/optimism/ICrossDomainMessenger.sol | 6 +- .../vendor/polygon/IFxMessageProcessor.sol | 6 +- package-lock.json | 18032 ++++++++++++++-- package.json | 2 +- test/utils/math/Math.t.sol | 42 +- 100 files changed, 16306 insertions(+), 3153 deletions(-) diff --git a/.prettierrc b/.prettierrc index f91ad7ee6..923a710a6 100644 --- a/.prettierrc +++ b/.prettierrc @@ -6,8 +6,7 @@ "files": "*.sol", "options": { "singleQuote": false, - "printWidth": 120, - "explicitTypes": "always" + "printWidth": 120 } } ] diff --git a/contracts/crosschain/polygon/CrossChainEnabledPolygonChild.sol b/contracts/crosschain/polygon/CrossChainEnabledPolygonChild.sol index 3918bfe25..fa0994834 100644 --- a/contracts/crosschain/polygon/CrossChainEnabledPolygonChild.sol +++ b/contracts/crosschain/polygon/CrossChainEnabledPolygonChild.sol @@ -59,7 +59,7 @@ abstract contract CrossChainEnabledPolygonChild is IFxMessageProcessor, CrossCha * then security could be compromised. */ function processMessageFromRoot( - uint256, /* stateId */ + uint256 /* stateId */, address rootMessageSender, bytes calldata data ) external override nonReentrant { diff --git a/contracts/finance/VestingWallet.sol b/contracts/finance/VestingWallet.sol index 0feac4ac6..fe67eb54f 100644 --- a/contracts/finance/VestingWallet.sol +++ b/contracts/finance/VestingWallet.sol @@ -29,11 +29,7 @@ contract VestingWallet is Context { /** * @dev Set the beneficiary, start timestamp and vesting duration of the vesting wallet. */ - constructor( - address beneficiaryAddress, - uint64 startTimestamp, - uint64 durationSeconds - ) payable { + constructor(address beneficiaryAddress, uint64 startTimestamp, uint64 durationSeconds) payable { require(beneficiaryAddress != address(0), "VestingWallet: beneficiary is zero address"); _beneficiary = beneficiaryAddress; _start = startTimestamp; diff --git a/contracts/governance/Governor.sol b/contracts/governance/Governor.sol index 84b3128ff..8235fb5ec 100644 --- a/contracts/governance/Governor.sol +++ b/contracts/governance/Governor.sol @@ -314,7 +314,7 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor, IERC721Receive * @dev Internal execution mechanism. Can be overridden to implement different execution mechanism */ function _execute( - uint256, /* proposalId */ + uint256 /* proposalId */, address[] memory targets, uint256[] memory values, bytes[] memory calldatas, @@ -331,9 +331,9 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor, IERC721Receive * @dev Hook before execution is triggered. */ function _beforeExecute( - uint256, /* proposalId */ + uint256 /* proposalId */, address[] memory targets, - uint256[] memory, /* values */ + uint256[] memory /* values */, bytes[] memory calldatas, bytes32 /*descriptionHash*/ ) internal virtual { @@ -350,10 +350,10 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor, IERC721Receive * @dev Hook after execution is triggered. */ function _afterExecute( - uint256, /* proposalId */ - address[] memory, /* targets */ - uint256[] memory, /* values */ - bytes[] memory, /* calldatas */ + uint256 /* proposalId */, + address[] memory /* targets */, + uint256[] memory /* values */, + bytes[] memory /* calldatas */, bytes32 /*descriptionHash*/ ) internal virtual { if (_executor() != address(this)) { @@ -540,11 +540,7 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor, IERC721Receive * in a governance proposal to recover tokens or Ether that was sent to the governor contract by mistake. * Note that if the executor is simply the governor itself, use of `relay` is redundant. */ - function relay( - address target, - uint256 value, - bytes calldata data - ) external payable virtual onlyGovernance { + function relay(address target, uint256 value, bytes calldata data) external payable virtual onlyGovernance { (bool success, bytes memory returndata) = target.call{value: value}(data); Address.verifyCallResult(success, returndata, "Governor: relay reverted without message"); } @@ -560,12 +556,7 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor, IERC721Receive /** * @dev See {IERC721Receiver-onERC721Received}. */ - function onERC721Received( - address, - address, - uint256, - bytes memory - ) public virtual override returns (bytes4) { + function onERC721Received(address, address, uint256, bytes memory) public virtual override returns (bytes4) { return this.onERC721Received.selector; } diff --git a/contracts/governance/TimelockController.sol b/contracts/governance/TimelockController.sol index 43eedd234..67e6d206c 100644 --- a/contracts/governance/TimelockController.sol +++ b/contracts/governance/TimelockController.sol @@ -73,12 +73,7 @@ contract TimelockController is AccessControl, IERC721Receiver, IERC1155Receiver * administration through timelocked proposals. Previous versions of this contract would assign * this admin to the deployer automatically and should be renounced as well. */ - constructor( - uint256 minDelay, - address[] memory proposers, - address[] memory executors, - address admin - ) { + constructor(uint256 minDelay, address[] memory proposers, address[] memory executors, address admin) { // self administration _grantRole(DEFAULT_ADMIN_ROLE, address(this)); @@ -336,11 +331,7 @@ contract TimelockController is AccessControl, IERC721Receiver, IERC1155Receiver /** * @dev Execute an operation's call. */ - function _execute( - address target, - uint256 value, - bytes calldata data - ) internal virtual { + function _execute(address target, uint256 value, bytes calldata data) internal virtual { (bool success, ) = target.call{value: value}(data); require(success, "TimelockController: underlying transaction reverted"); } @@ -380,12 +371,7 @@ contract TimelockController is AccessControl, IERC721Receiver, IERC1155Receiver /** * @dev See {IERC721Receiver-onERC721Received}. */ - function onERC721Received( - address, - address, - uint256, - bytes memory - ) public virtual override returns (bytes4) { + function onERC721Received(address, address, uint256, bytes memory) public virtual override returns (bytes4) { return this.onERC721Received.selector; } diff --git a/contracts/governance/compatibility/GovernorCompatibilityBravo.sol b/contracts/governance/compatibility/GovernorCompatibilityBravo.sol index 8d96be7d6..8d74742c5 100644 --- a/contracts/governance/compatibility/GovernorCompatibilityBravo.sol +++ b/contracts/governance/compatibility/GovernorCompatibilityBravo.sol @@ -118,11 +118,10 @@ abstract contract GovernorCompatibilityBravo is IGovernorTimelock, IGovernorComp /** * @dev Encodes calldatas with optional function signature. */ - function _encodeCalldata(string[] memory signatures, bytes[] memory calldatas) - private - pure - returns (bytes[] memory) - { + function _encodeCalldata( + string[] memory signatures, + bytes[] memory calldatas + ) private pure returns (bytes[] memory) { bytes[] memory fullcalldatas = new bytes[](calldatas.length); for (uint256 i = 0; i < signatures.length; ++i) { @@ -163,7 +162,9 @@ abstract contract GovernorCompatibilityBravo is IGovernorTimelock, IGovernorComp /** * @dev See {IGovernorCompatibilityBravo-proposals}. */ - function proposals(uint256 proposalId) + function proposals( + uint256 proposalId + ) public view virtual @@ -200,7 +201,9 @@ abstract contract GovernorCompatibilityBravo is IGovernorTimelock, IGovernorComp /** * @dev See {IGovernorCompatibilityBravo-getActions}. */ - function getActions(uint256 proposalId) + function getActions( + uint256 proposalId + ) public view virtual diff --git a/contracts/governance/compatibility/IGovernorCompatibilityBravo.sol b/contracts/governance/compatibility/IGovernorCompatibilityBravo.sol index 83e4e1ae9..d1ec0337d 100644 --- a/contracts/governance/compatibility/IGovernorCompatibilityBravo.sol +++ b/contracts/governance/compatibility/IGovernorCompatibilityBravo.sol @@ -50,7 +50,9 @@ abstract contract IGovernorCompatibilityBravo is IGovernor { /** * @dev Part of the Governor Bravo's interface: _"The official record of all proposals ever proposed"_. */ - function proposals(uint256) + function proposals( + uint256 + ) public view virtual @@ -96,7 +98,9 @@ abstract contract IGovernorCompatibilityBravo is IGovernor { /** * @dev Part of the Governor Bravo's interface: _"Gets actions of a proposal"_. */ - function getActions(uint256 proposalId) + function getActions( + uint256 proposalId + ) public view virtual diff --git a/contracts/governance/extensions/GovernorCountingSimple.sol b/contracts/governance/extensions/GovernorCountingSimple.sol index 5611fc669..f3eea9d7f 100644 --- a/contracts/governance/extensions/GovernorCountingSimple.sol +++ b/contracts/governance/extensions/GovernorCountingSimple.sol @@ -47,16 +47,9 @@ abstract contract GovernorCountingSimple is Governor { /** * @dev Accessor to the internal vote counts. */ - function proposalVotes(uint256 proposalId) - public - view - virtual - returns ( - uint256 againstVotes, - uint256 forVotes, - uint256 abstainVotes - ) - { + function proposalVotes( + uint256 proposalId + ) public view virtual returns (uint256 againstVotes, uint256 forVotes, uint256 abstainVotes) { ProposalVote storage proposalVote = _proposalVotes[proposalId]; return (proposalVote.againstVotes, proposalVote.forVotes, proposalVote.abstainVotes); } diff --git a/contracts/governance/extensions/GovernorSettings.sol b/contracts/governance/extensions/GovernorSettings.sol index a3187c6e1..527f41cd8 100644 --- a/contracts/governance/extensions/GovernorSettings.sol +++ b/contracts/governance/extensions/GovernorSettings.sol @@ -22,11 +22,7 @@ abstract contract GovernorSettings is Governor { /** * @dev Initialize the governance parameters. */ - constructor( - uint256 initialVotingDelay, - uint256 initialVotingPeriod, - uint256 initialProposalThreshold - ) { + constructor(uint256 initialVotingDelay, uint256 initialVotingPeriod, uint256 initialProposalThreshold) { _setVotingDelay(initialVotingDelay); _setVotingPeriod(initialVotingPeriod); _setProposalThreshold(initialProposalThreshold); diff --git a/contracts/governance/extensions/GovernorTimelockControl.sol b/contracts/governance/extensions/GovernorTimelockControl.sol index aaeaf9409..6aa2556ab 100644 --- a/contracts/governance/extensions/GovernorTimelockControl.sol +++ b/contracts/governance/extensions/GovernorTimelockControl.sol @@ -110,7 +110,7 @@ abstract contract GovernorTimelockControl is IGovernorTimelock, Governor { * @dev Overridden execute function that run the already queued proposal through the timelock. */ function _execute( - uint256, /* proposalId */ + uint256 /* proposalId */, address[] memory targets, uint256[] memory values, bytes[] memory calldatas, diff --git a/contracts/governance/utils/IVotes.sol b/contracts/governance/utils/IVotes.sol index 6317d7752..0bef3f920 100644 --- a/contracts/governance/utils/IVotes.sol +++ b/contracts/governance/utils/IVotes.sol @@ -50,12 +50,5 @@ interface IVotes { /** * @dev Delegates votes from signer to `delegatee`. */ - function delegateBySig( - address delegatee, - uint256 nonce, - uint256 expiry, - uint8 v, - bytes32 r, - bytes32 s - ) external; + function delegateBySig(address delegatee, uint256 nonce, uint256 expiry, uint8 v, bytes32 r, bytes32 s) external; } diff --git a/contracts/governance/utils/Votes.sol b/contracts/governance/utils/Votes.sol index 801be3bcd..492c1afb1 100644 --- a/contracts/governance/utils/Votes.sol +++ b/contracts/governance/utils/Votes.sol @@ -134,11 +134,7 @@ abstract contract Votes is IVotes, Context, EIP712, Nonces { * @dev Transfers, mints, or burns voting units. To register a mint, `from` should be zero. To register a burn, `to` * should be zero. Total supply of voting units will be adjusted with mints and burns. */ - function _transferVotingUnits( - address from, - address to, - uint256 amount - ) internal virtual { + function _transferVotingUnits(address from, address to, uint256 amount) internal virtual { if (from == address(0)) { _totalCheckpoints.push(_add, amount); } @@ -151,11 +147,7 @@ abstract contract Votes is IVotes, Context, EIP712, Nonces { /** * @dev Moves delegated votes from one delegate to another. */ - function _moveDelegateVotes( - address from, - address to, - uint256 amount - ) private { + function _moveDelegateVotes(address from, address to, uint256 amount) private { if (from != to && amount > 0) { if (from != address(0)) { (uint256 oldValue, uint256 newValue) = _delegateCheckpoints[from].push(_subtract, amount); diff --git a/contracts/interfaces/IERC1363.sol b/contracts/interfaces/IERC1363.sol index 5fad104c2..1a8dc79f4 100644 --- a/contracts/interfaces/IERC1363.sol +++ b/contracts/interfaces/IERC1363.sol @@ -38,11 +38,7 @@ interface IERC1363 is IERC165, IERC20 { * @param data bytes Additional data with no specified format, sent in call to `to` * @return true unless throwing */ - function transferAndCall( - address to, - uint256 value, - bytes memory data - ) external returns (bool); + function transferAndCall(address to, uint256 value, bytes memory data) external returns (bool); /** * @dev Transfer tokens from one address to another and then call `onTransferReceived` on receiver @@ -51,11 +47,7 @@ interface IERC1363 is IERC165, IERC20 { * @param value uint256 The amount of tokens to be transferred * @return true unless throwing */ - function transferFromAndCall( - address from, - address to, - uint256 value - ) external returns (bool); + function transferFromAndCall(address from, address to, uint256 value) external returns (bool); /** * @dev Transfer tokens from one address to another and then call `onTransferReceived` on receiver @@ -65,12 +57,7 @@ interface IERC1363 is IERC165, IERC20 { * @param data bytes Additional data with no specified format, sent in call to `to` * @return true unless throwing */ - function transferFromAndCall( - address from, - address to, - uint256 value, - bytes memory data - ) external returns (bool); + function transferFromAndCall(address from, address to, uint256 value, bytes memory data) external returns (bool); /** * @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender @@ -87,9 +74,5 @@ interface IERC1363 is IERC165, IERC20 { * @param value uint256 The amount of tokens to be spent * @param data bytes Additional data with no specified format, sent in call to `spender` */ - function approveAndCall( - address spender, - uint256 value, - bytes memory data - ) external returns (bool); + function approveAndCall(address spender, uint256 value, bytes memory data) external returns (bool); } diff --git a/contracts/interfaces/IERC1363Spender.sol b/contracts/interfaces/IERC1363Spender.sol index 48f6fd56d..28775e140 100644 --- a/contracts/interfaces/IERC1363Spender.sol +++ b/contracts/interfaces/IERC1363Spender.sol @@ -22,9 +22,5 @@ interface IERC1363Spender { * @return `bytes4(keccak256("onApprovalReceived(address,uint256,bytes)"))` * unless throwing */ - function onApprovalReceived( - address owner, - uint256 value, - bytes memory data - ) external returns (bytes4); + function onApprovalReceived(address owner, uint256 value, bytes memory data) external returns (bytes4); } diff --git a/contracts/interfaces/IERC2981.sol b/contracts/interfaces/IERC2981.sol index 6b0558169..1c9448a91 100644 --- a/contracts/interfaces/IERC2981.sol +++ b/contracts/interfaces/IERC2981.sol @@ -18,8 +18,8 @@ interface IERC2981 is IERC165 { * @dev Returns how much royalty is owed and to whom, based on a sale price that may be denominated in any unit of * exchange. The royalty amount is denominated and should be paid in that same unit of exchange. */ - function royaltyInfo(uint256 tokenId, uint256 salePrice) - external - view - returns (address receiver, uint256 royaltyAmount); + function royaltyInfo( + uint256 tokenId, + uint256 salePrice + ) external view returns (address receiver, uint256 royaltyAmount); } diff --git a/contracts/interfaces/IERC4626.sol b/contracts/interfaces/IERC4626.sol index f7c5397a0..08e5de717 100644 --- a/contracts/interfaces/IERC4626.sol +++ b/contracts/interfaces/IERC4626.sol @@ -187,11 +187,7 @@ interface IERC4626 is IERC20, IERC20Metadata { * Note that some implementations will require pre-requesting to the Vault before a withdrawal may be performed. * Those methods should be performed separately. */ - function withdraw( - uint256 assets, - address receiver, - address owner - ) external returns (uint256 shares); + function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares); /** * @dev Returns the maximum amount of Vault shares that can be redeemed from the owner balance in the Vault, @@ -232,9 +228,5 @@ interface IERC4626 is IERC20, IERC20Metadata { * NOTE: some implementations will require pre-requesting to the Vault before a withdrawal may be performed. * Those methods should be performed separately. */ - function redeem( - uint256 shares, - address receiver, - address owner - ) external returns (uint256 assets); + function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets); } diff --git a/contracts/metatx/MinimalForwarder.sol b/contracts/metatx/MinimalForwarder.sol index bb49c794d..9298ae675 100644 --- a/contracts/metatx/MinimalForwarder.sol +++ b/contracts/metatx/MinimalForwarder.sol @@ -44,11 +44,10 @@ contract MinimalForwarder is EIP712 { return _nonces[req.from] == req.nonce && signer == req.from; } - function execute(ForwardRequest calldata req, bytes calldata signature) - public - payable - returns (bool, bytes memory) - { + function execute( + ForwardRequest calldata req, + bytes calldata signature + ) public payable returns (bool, bytes memory) { require(verify(req, signature), "MinimalForwarder: signature does not match request"); _nonces[req.from] = req.nonce + 1; diff --git a/contracts/mocks/AddressImpl.sol b/contracts/mocks/AddressImpl.sol index 702093c73..b06bec372 100644 --- a/contracts/mocks/AddressImpl.sol +++ b/contracts/mocks/AddressImpl.sol @@ -22,11 +22,7 @@ contract AddressImpl { emit CallReturnValue(abi.decode(returnData, (string))); } - function functionCallWithValue( - address target, - bytes calldata data, - uint256 value - ) external payable { + function functionCallWithValue(address target, bytes calldata data, uint256 value) external payable { bytes memory returnData = Address.functionCallWithValue(target, data, value); emit CallReturnValue(abi.decode(returnData, (string))); } diff --git a/contracts/mocks/CheckpointsMock.sol b/contracts/mocks/CheckpointsMock.sol index f1dadabaf..874a1d1f2 100644 --- a/contracts/mocks/CheckpointsMock.sol +++ b/contracts/mocks/CheckpointsMock.sol @@ -14,15 +14,7 @@ contract CheckpointsMock { return _totalCheckpoints.latest(); } - function latestCheckpoint() - public - view - returns ( - bool, - uint256, - uint256 - ) - { + function latestCheckpoint() public view returns (bool, uint256, uint256) { return _totalCheckpoints.latestCheckpoint(); } @@ -52,15 +44,7 @@ contract Checkpoints224Mock { return _totalCheckpoints.latest(); } - function latestCheckpoint() - public - view - returns ( - bool, - uint32, - uint224 - ) - { + function latestCheckpoint() public view returns (bool, uint32, uint224) { return _totalCheckpoints.latestCheckpoint(); } @@ -90,15 +74,7 @@ contract Checkpoints160Mock { return _totalCheckpoints.latest(); } - function latestCheckpoint() - public - view - returns ( - bool, - uint96, - uint160 - ) - { + function latestCheckpoint() public view returns (bool, uint96, uint160) { return _totalCheckpoints.latestCheckpoint(); } diff --git a/contracts/mocks/ClonesMock.sol b/contracts/mocks/ClonesMock.sol index 3719b0a78..c65d30cc3 100644 --- a/contracts/mocks/ClonesMock.sol +++ b/contracts/mocks/ClonesMock.sol @@ -15,11 +15,7 @@ contract ClonesMock { _initAndEmit(implementation.clone(), initdata); } - function cloneDeterministic( - address implementation, - bytes32 salt, - bytes calldata initdata - ) public payable { + function cloneDeterministic(address implementation, bytes32 salt, bytes calldata initdata) public payable { _initAndEmit(implementation.cloneDeterministic(salt), initdata); } diff --git a/contracts/mocks/ContextMock.sol b/contracts/mocks/ContextMock.sol index f17af38a4..7759f3506 100644 --- a/contracts/mocks/ContextMock.sol +++ b/contracts/mocks/ContextMock.sol @@ -23,11 +23,7 @@ contract ContextMockCaller { context.msgSender(); } - function callData( - ContextMock context, - uint256 integerValue, - string memory stringValue - ) public { + function callData(ContextMock context, uint256 integerValue, string memory stringValue) public { context.msgData(integerValue, stringValue); } } diff --git a/contracts/mocks/Create2Impl.sol b/contracts/mocks/Create2Impl.sol index 070ad3671..6b2f4b712 100644 --- a/contracts/mocks/Create2Impl.sol +++ b/contracts/mocks/Create2Impl.sol @@ -6,11 +6,7 @@ import "../utils/Create2.sol"; import "../utils/introspection/ERC1820Implementer.sol"; contract Create2Impl { - function deploy( - uint256 value, - bytes32 salt, - bytes memory code - ) public { + function deploy(uint256 value, bytes32 salt, bytes memory code) public { Create2.deploy(value, salt, code); } diff --git a/contracts/mocks/DummyImplementation.sol b/contracts/mocks/DummyImplementation.sol index d8651340d..ddcca6604 100644 --- a/contracts/mocks/DummyImplementation.sol +++ b/contracts/mocks/DummyImplementation.sol @@ -27,11 +27,7 @@ contract DummyImplementation { value = _value; } - function initialize( - uint256 _value, - string memory _text, - uint256[] memory _values - ) public { + function initialize(uint256 _value, string memory _text, uint256[] memory _values) public { value = _value; text = _text; values = _values; diff --git a/contracts/mocks/ECDSAMock.sol b/contracts/mocks/ECDSAMock.sol index 97bd46669..cfc37d26e 100644 --- a/contracts/mocks/ECDSAMock.sol +++ b/contracts/mocks/ECDSAMock.sol @@ -13,21 +13,12 @@ contract ECDSAMock { } // solhint-disable-next-line func-name-mixedcase - function recover_v_r_s( - bytes32 hash, - uint8 v, - bytes32 r, - bytes32 s - ) public pure returns (address) { + function recover_v_r_s(bytes32 hash, uint8 v, bytes32 r, bytes32 s) public pure returns (address) { return hash.recover(v, r, s); } // solhint-disable-next-line func-name-mixedcase - function recover_r_vs( - bytes32 hash, - bytes32 r, - bytes32 vs - ) public pure returns (address) { + function recover_r_vs(bytes32 hash, bytes32 r, bytes32 vs) public pure returns (address) { return hash.recover(r, vs); } diff --git a/contracts/mocks/EIP712External.sol b/contracts/mocks/EIP712External.sol index 93f77d549..bc5b1269f 100644 --- a/contracts/mocks/EIP712External.sol +++ b/contracts/mocks/EIP712External.sol @@ -12,12 +12,7 @@ contract EIP712External is EIP712 { return _domainSeparatorV4(); } - function verify( - bytes memory signature, - address signer, - address mailTo, - string memory mailContents - ) external view { + function verify(bytes memory signature, address signer, address mailTo, string memory mailContents) external view { bytes32 digest = _hashTypedDataV4( keccak256(abi.encode(keccak256("Mail(address to,string contents)"), mailTo, keccak256(bytes(mailContents)))) ); diff --git a/contracts/mocks/ERC1155BurnableMock.sol b/contracts/mocks/ERC1155BurnableMock.sol index 62138f28d..3334523cf 100644 --- a/contracts/mocks/ERC1155BurnableMock.sol +++ b/contracts/mocks/ERC1155BurnableMock.sol @@ -7,12 +7,7 @@ import "../token/ERC1155/extensions/ERC1155Burnable.sol"; contract ERC1155BurnableMock is ERC1155Burnable { constructor(string memory uri) ERC1155(uri) {} - function mint( - address to, - uint256 id, - uint256 value, - bytes memory data - ) public { + function mint(address to, uint256 id, uint256 value, bytes memory data) public { _mint(to, id, value, data); } } diff --git a/contracts/mocks/ERC1155Mock.sol b/contracts/mocks/ERC1155Mock.sol index 0518ac26c..6bfc86cea 100644 --- a/contracts/mocks/ERC1155Mock.sol +++ b/contracts/mocks/ERC1155Mock.sol @@ -15,37 +15,19 @@ contract ERC1155Mock is ERC1155 { _setURI(newuri); } - function mint( - address to, - uint256 id, - uint256 value, - bytes memory data - ) public { + function mint(address to, uint256 id, uint256 value, bytes memory data) public { _mint(to, id, value, data); } - function mintBatch( - address to, - uint256[] memory ids, - uint256[] memory values, - bytes memory data - ) public { + function mintBatch(address to, uint256[] memory ids, uint256[] memory values, bytes memory data) public { _mintBatch(to, ids, values, data); } - function burn( - address owner, - uint256 id, - uint256 value - ) public { + function burn(address owner, uint256 id, uint256 value) public { _burn(owner, id, value); } - function burnBatch( - address owner, - uint256[] memory ids, - uint256[] memory values - ) public { + function burnBatch(address owner, uint256[] memory ids, uint256[] memory values) public { _burnBatch(owner, ids, values); } } diff --git a/contracts/mocks/ERC1155ReceiverMock.sol b/contracts/mocks/ERC1155ReceiverMock.sol index 6443a56c7..b2d505c0a 100644 --- a/contracts/mocks/ERC1155ReceiverMock.sol +++ b/contracts/mocks/ERC1155ReceiverMock.sol @@ -14,12 +14,7 @@ contract ERC1155ReceiverMock is ERC165, IERC1155Receiver { event Received(address operator, address from, uint256 id, uint256 value, bytes data, uint256 gas); event BatchReceived(address operator, address from, uint256[] ids, uint256[] values, bytes data, uint256 gas); - constructor( - bytes4 recRetval, - bool recReverts, - bytes4 batRetval, - bool batReverts - ) { + constructor(bytes4 recRetval, bool recReverts, bytes4 batRetval, bool batReverts) { _recRetval = recRetval; _recReverts = recReverts; _batRetval = batRetval; diff --git a/contracts/mocks/ERC20CappedMock.sol b/contracts/mocks/ERC20CappedMock.sol index edb36f205..e69aadfdf 100644 --- a/contracts/mocks/ERC20CappedMock.sol +++ b/contracts/mocks/ERC20CappedMock.sol @@ -5,11 +5,7 @@ pragma solidity ^0.8.0; import "../token/ERC20/extensions/ERC20Capped.sol"; contract ERC20CappedMock is ERC20Capped { - constructor( - string memory name, - string memory symbol, - uint256 cap - ) ERC20(name, symbol) ERC20Capped(cap) {} + constructor(string memory name, string memory symbol, uint256 cap) ERC20(name, symbol) ERC20Capped(cap) {} function mint(address to, uint256 tokenId) public { _mint(to, tokenId); diff --git a/contracts/mocks/ERC20DecimalsMock.sol b/contracts/mocks/ERC20DecimalsMock.sol index 3721f6c39..7677e9dd1 100644 --- a/contracts/mocks/ERC20DecimalsMock.sol +++ b/contracts/mocks/ERC20DecimalsMock.sol @@ -7,11 +7,7 @@ import "../token/ERC20/ERC20.sol"; contract ERC20DecimalsMock is ERC20 { uint8 private immutable _decimals; - constructor( - string memory name_, - string memory symbol_, - uint8 decimals_ - ) ERC20(name_, symbol_) { + constructor(string memory name_, string memory symbol_, uint8 decimals_) ERC20(name_, symbol_) { _decimals = decimals_; } diff --git a/contracts/mocks/ERC20Mock.sol b/contracts/mocks/ERC20Mock.sol index fd7f991ba..16ffad19f 100644 --- a/contracts/mocks/ERC20Mock.sol +++ b/contracts/mocks/ERC20Mock.sol @@ -23,19 +23,11 @@ contract ERC20Mock is ERC20 { _burn(account, amount); } - function transferInternal( - address from, - address to, - uint256 value - ) public { + function transferInternal(address from, address to, uint256 value) public { _transfer(from, to, value); } - function approveInternal( - address owner, - address spender, - uint256 value - ) public { + function approveInternal(address owner, address spender, uint256 value) public { _approve(owner, spender, value); } } diff --git a/contracts/mocks/ERC3156FlashBorrowerMock.sol b/contracts/mocks/ERC3156FlashBorrowerMock.sol index 288a278fb..6a4410fcb 100644 --- a/contracts/mocks/ERC3156FlashBorrowerMock.sol +++ b/contracts/mocks/ERC3156FlashBorrowerMock.sol @@ -28,7 +28,7 @@ contract ERC3156FlashBorrowerMock is IERC3156FlashBorrower { } function onFlashLoan( - address, /*initiator*/ + address /*initiator*/, address token, uint256 amount, uint256 fee, diff --git a/contracts/mocks/ERC4626Mock.sol b/contracts/mocks/ERC4626Mock.sol index 4f80b4bd7..9c13346f0 100644 --- a/contracts/mocks/ERC4626Mock.sol +++ b/contracts/mocks/ERC4626Mock.sol @@ -5,11 +5,7 @@ pragma solidity ^0.8.0; import "../token/ERC20/extensions/ERC4626.sol"; contract ERC4626Mock is ERC4626 { - constructor( - IERC20Metadata asset, - string memory name, - string memory symbol - ) ERC20(name, symbol) ERC4626(asset) {} + constructor(IERC20Metadata asset, string memory name, string memory symbol) ERC20(name, symbol) ERC4626(asset) {} function mockMint(address account, uint256 amount) public { _mint(account, amount); @@ -38,23 +34,17 @@ contract ERC4626DecimalMock is ERC4626Mock { return _decimals; } - function _initialConvertToShares(uint256 assets, Math.Rounding rounding) - internal - view - virtual - override - returns (uint256 shares) - { - return assets.mulDiv(10**decimals(), 10**super.decimals(), rounding); + function _initialConvertToShares( + uint256 assets, + Math.Rounding rounding + ) internal view virtual override returns (uint256 shares) { + return assets.mulDiv(10 ** decimals(), 10 ** super.decimals(), rounding); } - function _initialConvertToAssets(uint256 shares, Math.Rounding rounding) - internal - view - virtual - override - returns (uint256 assets) - { - return shares.mulDiv(10**super.decimals(), 10**decimals(), rounding); + function _initialConvertToAssets( + uint256 shares, + Math.Rounding rounding + ) internal view virtual override returns (uint256 assets) { + return shares.mulDiv(10 ** super.decimals(), 10 ** decimals(), rounding); } } diff --git a/contracts/mocks/ERC721BurnableMock.sol b/contracts/mocks/ERC721BurnableMock.sol index b30dbf53d..ecf427681 100644 --- a/contracts/mocks/ERC721BurnableMock.sol +++ b/contracts/mocks/ERC721BurnableMock.sol @@ -19,11 +19,7 @@ contract ERC721BurnableMock is ERC721Burnable { _safeMint(to, tokenId); } - function safeMint( - address to, - uint256 tokenId, - bytes memory _data - ) public { + function safeMint(address to, uint256 tokenId, bytes memory _data) public { _safeMint(to, tokenId, _data); } } diff --git a/contracts/mocks/ERC721ConsecutiveEnumerableMock.sol b/contracts/mocks/ERC721ConsecutiveEnumerableMock.sol index cde3bd86c..f4f5c5832 100644 --- a/contracts/mocks/ERC721ConsecutiveEnumerableMock.sol +++ b/contracts/mocks/ERC721ConsecutiveEnumerableMock.sol @@ -17,13 +17,9 @@ contract ERC721ConsecutiveEnumerableMock is ERC721Consecutive, ERC721Enumerable } } - function supportsInterface(bytes4 interfaceId) - public - view - virtual - override(ERC721, ERC721Enumerable) - returns (bool) - { + function supportsInterface( + bytes4 interfaceId + ) public view virtual override(ERC721, ERC721Enumerable) returns (bool) { return super.supportsInterface(interfaceId); } diff --git a/contracts/mocks/ERC721EnumerableMock.sol b/contracts/mocks/ERC721EnumerableMock.sol index a747925e6..b7ea94ee3 100644 --- a/contracts/mocks/ERC721EnumerableMock.sol +++ b/contracts/mocks/ERC721EnumerableMock.sol @@ -37,11 +37,7 @@ contract ERC721EnumerableMock is ERC721Enumerable { _safeMint(to, tokenId); } - function safeMint( - address to, - uint256 tokenId, - bytes memory _data - ) public { + function safeMint(address to, uint256 tokenId, bytes memory _data) public { _safeMint(to, tokenId, _data); } diff --git a/contracts/mocks/ERC721Mock.sol b/contracts/mocks/ERC721Mock.sol index 74a092334..a3bc839ae 100644 --- a/contracts/mocks/ERC721Mock.sol +++ b/contracts/mocks/ERC721Mock.sol @@ -27,11 +27,7 @@ contract ERC721Mock is ERC721 { _safeMint(to, tokenId); } - function safeMint( - address to, - uint256 tokenId, - bytes memory _data - ) public { + function safeMint(address to, uint256 tokenId, bytes memory _data) public { _safeMint(to, tokenId, _data); } diff --git a/contracts/mocks/ERC721PausableMock.sol b/contracts/mocks/ERC721PausableMock.sol index 8d8e818fb..753842e90 100644 --- a/contracts/mocks/ERC721PausableMock.sol +++ b/contracts/mocks/ERC721PausableMock.sol @@ -31,11 +31,7 @@ contract ERC721PausableMock is ERC721Pausable { _safeMint(to, tokenId); } - function safeMint( - address to, - uint256 tokenId, - bytes memory _data - ) public { + function safeMint(address to, uint256 tokenId, bytes memory _data) public { _safeMint(to, tokenId, _data); } diff --git a/contracts/mocks/ERC721RoyaltyMock.sol b/contracts/mocks/ERC721RoyaltyMock.sol index 83a9074e2..6f19d5248 100644 --- a/contracts/mocks/ERC721RoyaltyMock.sol +++ b/contracts/mocks/ERC721RoyaltyMock.sol @@ -7,11 +7,7 @@ import "../token/ERC721/extensions/ERC721Royalty.sol"; contract ERC721RoyaltyMock is ERC721Royalty { constructor(string memory name, string memory symbol) ERC721(name, symbol) {} - function setTokenRoyalty( - uint256 tokenId, - address recipient, - uint96 fraction - ) public { + function setTokenRoyalty(uint256 tokenId, address recipient, uint96 fraction) public { _setTokenRoyalty(tokenId, recipient, fraction); } diff --git a/contracts/mocks/ERC721URIStorageMock.sol b/contracts/mocks/ERC721URIStorageMock.sol index 60f9f7b7c..4bb26b1ad 100644 --- a/contracts/mocks/ERC721URIStorageMock.sol +++ b/contracts/mocks/ERC721URIStorageMock.sol @@ -41,11 +41,7 @@ contract ERC721URIStorageMock is ERC721URIStorage { _safeMint(to, tokenId); } - function safeMint( - address to, - uint256 tokenId, - bytes memory _data - ) public { + function safeMint(address to, uint256 tokenId, bytes memory _data) public { _safeMint(to, tokenId, _data); } diff --git a/contracts/mocks/ERC777Mock.sol b/contracts/mocks/ERC777Mock.sol index f8a3b6784..59c00b307 100644 --- a/contracts/mocks/ERC777Mock.sol +++ b/contracts/mocks/ERC777Mock.sol @@ -18,12 +18,7 @@ contract ERC777Mock is Context, ERC777 { _mint(initialHolder, initialBalance, "", ""); } - function mintInternal( - address to, - uint256 amount, - bytes memory userData, - bytes memory operatorData - ) public { + function mintInternal(address to, uint256 amount, bytes memory userData, bytes memory operatorData) public { _mint(to, amount, userData, operatorData); } @@ -37,20 +32,11 @@ contract ERC777Mock is Context, ERC777 { _mint(to, amount, userData, operatorData, requireReceptionAck); } - function approveInternal( - address holder, - address spender, - uint256 value - ) public { + function approveInternal(address holder, address spender, uint256 value) public { _approve(holder, spender, value); } - function _beforeTokenTransfer( - address, - address, - address, - uint256 - ) internal override { + function _beforeTokenTransfer(address, address, address, uint256) internal override { emit BeforeTokenTransfer(); } } diff --git a/contracts/mocks/ERC777SenderRecipientMock.sol b/contracts/mocks/ERC777SenderRecipientMock.sol index 169912f69..8e8c749ce 100644 --- a/contracts/mocks/ERC777SenderRecipientMock.sol +++ b/contracts/mocks/ERC777SenderRecipientMock.sol @@ -141,21 +141,12 @@ contract ERC777SenderRecipientMock is Context, IERC777Sender, IERC777Recipient, _shouldRevertReceive = shouldRevert; } - function send( - IERC777 token, - address to, - uint256 amount, - bytes memory data - ) public { + function send(IERC777 token, address to, uint256 amount, bytes memory data) public { // This is 777's send function, not the Solidity send function token.send(to, amount, data); // solhint-disable-line check-send-result } - function burn( - IERC777 token, - uint256 amount, - bytes memory data - ) public { + function burn(IERC777 token, uint256 amount, bytes memory data) public { token.burn(amount, data); } } diff --git a/contracts/mocks/GovernorCompatibilityBravoMock.sol b/contracts/mocks/GovernorCompatibilityBravoMock.sol index 1ef0f94b9..d3b4f707d 100644 --- a/contracts/mocks/GovernorCompatibilityBravoMock.sol +++ b/contracts/mocks/GovernorCompatibilityBravoMock.sol @@ -27,12 +27,9 @@ contract GovernorCompatibilityBravoMock is GovernorVotesComp(token_) {} - function supportsInterface(bytes4 interfaceId) - public - view - override(IERC165, Governor, GovernorTimelockCompound) - returns (bool) - { + function supportsInterface( + bytes4 interfaceId + ) public view override(IERC165, Governor, GovernorTimelockCompound) returns (bool) { return super.supportsInterface(interfaceId); } @@ -40,21 +37,15 @@ contract GovernorCompatibilityBravoMock is return 0; } - function state(uint256 proposalId) - public - view - override(IGovernor, Governor, GovernorTimelockCompound) - returns (ProposalState) - { + function state( + uint256 proposalId + ) public view override(IGovernor, Governor, GovernorTimelockCompound) returns (ProposalState) { return super.state(proposalId); } - function proposalEta(uint256 proposalId) - public - view - override(IGovernorTimelock, GovernorTimelockCompound) - returns (uint256) - { + function proposalEta( + uint256 proposalId + ) public view override(IGovernorTimelock, GovernorTimelockCompound) returns (uint256) { return super.proposalEta(proposalId); } diff --git a/contracts/mocks/GovernorPreventLateQuorumMock.sol b/contracts/mocks/GovernorPreventLateQuorumMock.sol index d868ab22b..b6b5e7619 100644 --- a/contracts/mocks/GovernorPreventLateQuorumMock.sol +++ b/contracts/mocks/GovernorPreventLateQuorumMock.sol @@ -35,12 +35,9 @@ contract GovernorPreventLateQuorumMock is return _quorum; } - function proposalDeadline(uint256 proposalId) - public - view - override(Governor, GovernorPreventLateQuorum) - returns (uint256) - { + function proposalDeadline( + uint256 proposalId + ) public view override(Governor, GovernorPreventLateQuorum) returns (uint256) { return super.proposalDeadline(proposalId); } diff --git a/contracts/mocks/GovernorTimelockCompoundMock.sol b/contracts/mocks/GovernorTimelockCompoundMock.sol index becc72a06..75a2f3c14 100644 --- a/contracts/mocks/GovernorTimelockCompoundMock.sol +++ b/contracts/mocks/GovernorTimelockCompoundMock.sol @@ -28,21 +28,15 @@ contract GovernorTimelockCompoundMock is GovernorVotesQuorumFraction(quorumNumerator_) {} - function supportsInterface(bytes4 interfaceId) - public - view - override(Governor, GovernorTimelockCompound) - returns (bool) - { + function supportsInterface( + bytes4 interfaceId + ) public view override(Governor, GovernorTimelockCompound) returns (bool) { return super.supportsInterface(interfaceId); } - function quorum(uint256 blockNumber) - public - view - override(IGovernor, GovernorVotesQuorumFraction) - returns (uint256) - { + function quorum( + uint256 blockNumber + ) public view override(IGovernor, GovernorVotesQuorumFraction) returns (uint256) { return super.quorum(blockNumber); } @@ -58,12 +52,9 @@ contract GovernorTimelockCompoundMock is /** * Overriding nightmare */ - function state(uint256 proposalId) - public - view - override(Governor, GovernorTimelockCompound) - returns (ProposalState) - { + function state( + uint256 proposalId + ) public view override(Governor, GovernorTimelockCompound) returns (ProposalState) { return super.state(proposalId); } diff --git a/contracts/mocks/GovernorTimelockControlMock.sol b/contracts/mocks/GovernorTimelockControlMock.sol index ca412d1ca..671a1e0ea 100644 --- a/contracts/mocks/GovernorTimelockControlMock.sol +++ b/contracts/mocks/GovernorTimelockControlMock.sol @@ -28,21 +28,15 @@ contract GovernorTimelockControlMock is GovernorVotesQuorumFraction(quorumNumerator_) {} - function supportsInterface(bytes4 interfaceId) - public - view - override(Governor, GovernorTimelockControl) - returns (bool) - { + function supportsInterface( + bytes4 interfaceId + ) public view override(Governor, GovernorTimelockControl) returns (bool) { return super.supportsInterface(interfaceId); } - function quorum(uint256 blockNumber) - public - view - override(IGovernor, GovernorVotesQuorumFraction) - returns (uint256) - { + function quorum( + uint256 blockNumber + ) public view override(IGovernor, GovernorVotesQuorumFraction) returns (uint256) { return super.quorum(blockNumber); } diff --git a/contracts/mocks/MathMock.sol b/contracts/mocks/MathMock.sol index c6b1e8723..be935f91d 100644 --- a/contracts/mocks/MathMock.sol +++ b/contracts/mocks/MathMock.sol @@ -21,12 +21,7 @@ contract MathMock { return Math.ceilDiv(a, b); } - function mulDiv( - uint256 a, - uint256 b, - uint256 denominator, - Math.Rounding direction - ) public pure returns (uint256) { + function mulDiv(uint256 a, uint256 b, uint256 denominator, Math.Rounding direction) public pure returns (uint256) { return Math.mulDiv(a, b, denominator, direction); } diff --git a/contracts/mocks/MerkleProofWrapper.sol b/contracts/mocks/MerkleProofWrapper.sol index b74459dc8..60741e41c 100644 --- a/contracts/mocks/MerkleProofWrapper.sol +++ b/contracts/mocks/MerkleProofWrapper.sol @@ -5,19 +5,11 @@ pragma solidity ^0.8.0; import "../utils/cryptography/MerkleProof.sol"; contract MerkleProofWrapper { - function verify( - bytes32[] memory proof, - bytes32 root, - bytes32 leaf - ) public pure returns (bool) { + function verify(bytes32[] memory proof, bytes32 root, bytes32 leaf) public pure returns (bool) { return MerkleProof.verify(proof, root, leaf); } - function verifyCalldata( - bytes32[] calldata proof, - bytes32 root, - bytes32 leaf - ) public pure returns (bool) { + function verifyCalldata(bytes32[] calldata proof, bytes32 root, bytes32 leaf) public pure returns (bool) { return MerkleProof.verifyCalldata(proof, root, leaf); } diff --git a/contracts/mocks/MultipleInheritanceInitializableMocks.sol b/contracts/mocks/MultipleInheritanceInitializableMocks.sol index f6d644065..b8cf9d9d5 100644 --- a/contracts/mocks/MultipleInheritanceInitializableMocks.sol +++ b/contracts/mocks/MultipleInheritanceInitializableMocks.sol @@ -108,12 +108,7 @@ contract SampleFather is Initializable, SampleGramps { contract SampleChild is Initializable, SampleMother, SampleFather { uint256 public child; - function initialize( - uint256 _mother, - string memory _gramps, - uint256 _father, - uint256 _child - ) public initializer { + function initialize(uint256 _mother, string memory _gramps, uint256 _father, uint256 _child) public initializer { __SampleChild_init(_mother, _gramps, _father, _child); } diff --git a/contracts/mocks/SafeERC20Helper.sol b/contracts/mocks/SafeERC20Helper.sol index 98fdc6444..237e32041 100644 --- a/contracts/mocks/SafeERC20Helper.sol +++ b/contracts/mocks/SafeERC20Helper.sol @@ -19,11 +19,7 @@ contract ERC20ReturnFalseMock is Context { return false; } - function transferFrom( - address, - address, - uint256 - ) public returns (bool) { + function transferFrom(address, address, uint256) public returns (bool) { _dummy = 0; return false; } @@ -51,11 +47,7 @@ contract ERC20ReturnTrueMock is Context { return true; } - function transferFrom( - address, - address, - uint256 - ) public returns (bool) { + function transferFrom(address, address, uint256) public returns (bool) { _dummy = 0; return true; } @@ -85,11 +77,7 @@ contract ERC20NoReturnMock is Context { _dummy = 0; } - function transferFrom( - address, - address, - uint256 - ) public { + function transferFrom(address, address, uint256) public { _dummy = 0; } diff --git a/contracts/mocks/SafeMathMock.sol b/contracts/mocks/SafeMathMock.sol index 3d1f4727e..ef504e3ab 100644 --- a/contracts/mocks/SafeMathMock.sol +++ b/contracts/mocks/SafeMathMock.sol @@ -47,27 +47,15 @@ contract SafeMathMock { return SafeMath.mod(a, b); } - function subWithMessage( - uint256 a, - uint256 b, - string memory errorMessage - ) public pure returns (uint256) { + function subWithMessage(uint256 a, uint256 b, string memory errorMessage) public pure returns (uint256) { return SafeMath.sub(a, b, errorMessage); } - function divWithMessage( - uint256 a, - uint256 b, - string memory errorMessage - ) public pure returns (uint256) { + function divWithMessage(uint256 a, uint256 b, string memory errorMessage) public pure returns (uint256) { return SafeMath.div(a, b, errorMessage); } - function modWithMessage( - uint256 a, - uint256 b, - string memory errorMessage - ) public pure returns (uint256) { + function modWithMessage(uint256 a, uint256 b, string memory errorMessage) public pure returns (uint256) { return SafeMath.mod(a, b, errorMessage); } diff --git a/contracts/mocks/SignatureCheckerMock.sol b/contracts/mocks/SignatureCheckerMock.sol index 3b399c1ae..5671540ec 100644 --- a/contracts/mocks/SignatureCheckerMock.sol +++ b/contracts/mocks/SignatureCheckerMock.sol @@ -7,11 +7,7 @@ import "../utils/cryptography/SignatureChecker.sol"; contract SignatureCheckerMock { using SignatureChecker for address; - function isValidSignatureNow( - address signer, - bytes32 hash, - bytes memory signature - ) public view returns (bool) { + function isValidSignatureNow(address signer, bytes32 hash, bytes memory signature) public view returns (bool) { return signer.isValidSignatureNow(hash, signature); } } diff --git a/contracts/mocks/UUPS/UUPSLegacy.sol b/contracts/mocks/UUPS/UUPSLegacy.sol index e03fa862d..7a3002889 100644 --- a/contracts/mocks/UUPS/UUPSLegacy.sol +++ b/contracts/mocks/UUPS/UUPSLegacy.sol @@ -17,11 +17,7 @@ contract UUPSUpgradeableLegacyMock is UUPSUpgradeableMock { StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation; } - function _upgradeToAndCallSecureLegacyV1( - address newImplementation, - bytes memory data, - bool forceCall - ) internal { + function _upgradeToAndCallSecureLegacyV1(address newImplementation, bytes memory data, bool forceCall) internal { address oldImplementation = _getImplementation(); // Initial upgrade and setup call diff --git a/contracts/mocks/crosschain/bridges.sol b/contracts/mocks/crosschain/bridges.sol index 35c7f4c06..41baffed8 100644 --- a/contracts/mocks/crosschain/bridges.sol +++ b/contracts/mocks/crosschain/bridges.sol @@ -12,11 +12,7 @@ abstract contract BaseRelayMock { address internal _currentSender; - function relayAs( - address target, - bytes calldata data, - address sender - ) external virtual { + function relayAs(address target, bytes calldata data, address sender) external virtual { address previousSender = _currentSender; _currentSender = sender; @@ -92,11 +88,7 @@ contract BridgeOptimismMock is BaseRelayMock { * Polygon */ contract BridgePolygonChildMock is BaseRelayMock { - function relayAs( - address target, - bytes calldata data, - address sender - ) external override { + function relayAs(address target, bytes calldata data, address sender) external override { IFxMessageProcessor(target).processMessageFromRoot(0, sender, data); } } diff --git a/contracts/mocks/wizard/MyGovernor1.sol b/contracts/mocks/wizard/MyGovernor1.sol index a80d8400c..37ecfd57d 100644 --- a/contracts/mocks/wizard/MyGovernor1.sol +++ b/contracts/mocks/wizard/MyGovernor1.sol @@ -14,12 +14,10 @@ contract MyGovernor1 is GovernorVotesQuorumFraction, GovernorCountingSimple { - constructor(IVotes _token, TimelockController _timelock) - Governor("MyGovernor") - GovernorVotes(_token) - GovernorVotesQuorumFraction(4) - GovernorTimelockControl(_timelock) - {} + constructor( + IVotes _token, + TimelockController _timelock + ) Governor("MyGovernor") GovernorVotes(_token) GovernorVotesQuorumFraction(4) GovernorTimelockControl(_timelock) {} function votingDelay() public pure override returns (uint256) { return 1; // 1 block @@ -31,12 +29,9 @@ contract MyGovernor1 is // The following functions are overrides required by Solidity. - function quorum(uint256 blockNumber) - public - view - override(IGovernor, GovernorVotesQuorumFraction) - returns (uint256) - { + function quorum( + uint256 blockNumber + ) public view override(IGovernor, GovernorVotesQuorumFraction) returns (uint256) { return super.quorum(blockNumber); } @@ -76,12 +71,9 @@ contract MyGovernor1 is return super._executor(); } - function supportsInterface(bytes4 interfaceId) - public - view - override(Governor, GovernorTimelockControl) - returns (bool) - { + function supportsInterface( + bytes4 interfaceId + ) public view override(Governor, GovernorTimelockControl) returns (bool) { return super.supportsInterface(interfaceId); } } diff --git a/contracts/mocks/wizard/MyGovernor2.sol b/contracts/mocks/wizard/MyGovernor2.sol index 34c608c5c..1472b67d5 100644 --- a/contracts/mocks/wizard/MyGovernor2.sol +++ b/contracts/mocks/wizard/MyGovernor2.sol @@ -16,12 +16,10 @@ contract MyGovernor2 is GovernorVotesQuorumFraction, GovernorCountingSimple { - constructor(IVotes _token, TimelockController _timelock) - Governor("MyGovernor") - GovernorVotes(_token) - GovernorVotesQuorumFraction(4) - GovernorTimelockControl(_timelock) - {} + constructor( + IVotes _token, + TimelockController _timelock + ) Governor("MyGovernor") GovernorVotes(_token) GovernorVotesQuorumFraction(4) GovernorTimelockControl(_timelock) {} function votingDelay() public pure override returns (uint256) { return 1; // 1 block @@ -37,12 +35,9 @@ contract MyGovernor2 is // The following functions are overrides required by Solidity. - function quorum(uint256 blockNumber) - public - view - override(IGovernor, GovernorVotesQuorumFraction) - returns (uint256) - { + function quorum( + uint256 blockNumber + ) public view override(IGovernor, GovernorVotesQuorumFraction) returns (uint256) { return super.quorum(blockNumber); } @@ -82,12 +77,9 @@ contract MyGovernor2 is return super._executor(); } - function supportsInterface(bytes4 interfaceId) - public - view - override(Governor, GovernorTimelockControl) - returns (bool) - { + function supportsInterface( + bytes4 interfaceId + ) public view override(Governor, GovernorTimelockControl) returns (bool) { return super.supportsInterface(interfaceId); } } diff --git a/contracts/mocks/wizard/MyGovernor3.sol b/contracts/mocks/wizard/MyGovernor3.sol index 70e4e87f0..320342290 100644 --- a/contracts/mocks/wizard/MyGovernor3.sol +++ b/contracts/mocks/wizard/MyGovernor3.sol @@ -14,12 +14,10 @@ contract MyGovernor is GovernorVotes, GovernorVotesQuorumFraction { - constructor(IVotes _token, TimelockController _timelock) - Governor("MyGovernor") - GovernorVotes(_token) - GovernorVotesQuorumFraction(4) - GovernorTimelockControl(_timelock) - {} + constructor( + IVotes _token, + TimelockController _timelock + ) Governor("MyGovernor") GovernorVotes(_token) GovernorVotesQuorumFraction(4) GovernorTimelockControl(_timelock) {} function votingDelay() public pure override returns (uint256) { return 1; // 1 block @@ -35,21 +33,15 @@ contract MyGovernor is // The following functions are overrides required by Solidity. - function quorum(uint256 blockNumber) - public - view - override(IGovernor, GovernorVotesQuorumFraction) - returns (uint256) - { + function quorum( + uint256 blockNumber + ) public view override(IGovernor, GovernorVotesQuorumFraction) returns (uint256) { return super.quorum(blockNumber); } - function state(uint256 proposalId) - public - view - override(Governor, IGovernor, GovernorTimelockControl) - returns (ProposalState) - { + function state( + uint256 proposalId + ) public view override(Governor, IGovernor, GovernorTimelockControl) returns (ProposalState) { return super.state(proposalId); } @@ -85,12 +77,9 @@ contract MyGovernor is return super._executor(); } - function supportsInterface(bytes4 interfaceId) - public - view - override(Governor, IERC165, GovernorTimelockControl) - returns (bool) - { + function supportsInterface( + bytes4 interfaceId + ) public view override(Governor, IERC165, GovernorTimelockControl) returns (bool) { return super.supportsInterface(interfaceId); } } diff --git a/contracts/proxy/Clones.sol b/contracts/proxy/Clones.sol index 93ea0cec7..712519892 100644 --- a/contracts/proxy/Clones.sol +++ b/contracts/proxy/Clones.sol @@ -79,11 +79,10 @@ library Clones { /** * @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}. */ - function predictDeterministicAddress(address implementation, bytes32 salt) - internal - view - returns (address predicted) - { + function predictDeterministicAddress( + address implementation, + bytes32 salt + ) internal view returns (address predicted) { return predictDeterministicAddress(implementation, salt, address(this)); } } diff --git a/contracts/proxy/ERC1967/ERC1967Upgrade.sol b/contracts/proxy/ERC1967/ERC1967Upgrade.sol index 07dc04862..38ea7ea1b 100644 --- a/contracts/proxy/ERC1967/ERC1967Upgrade.sol +++ b/contracts/proxy/ERC1967/ERC1967Upgrade.sol @@ -62,11 +62,7 @@ abstract contract ERC1967Upgrade { * * Emits an {Upgraded} event. */ - function _upgradeToAndCall( - address newImplementation, - bytes memory data, - bool forceCall - ) internal { + function _upgradeToAndCall(address newImplementation, bytes memory data, bool forceCall) internal { _upgradeTo(newImplementation); if (data.length > 0 || forceCall) { Address.functionDelegateCall(newImplementation, data); @@ -78,11 +74,7 @@ abstract contract ERC1967Upgrade { * * Emits an {Upgraded} event. */ - function _upgradeToAndCallUUPS( - address newImplementation, - bytes memory data, - bool forceCall - ) internal { + function _upgradeToAndCallUUPS(address newImplementation, bytes memory data, bool forceCall) internal { // Upgrades from old implementations will perform a rollback test. This test requires the new // implementation to upgrade back to the old, non-ERC1822 compliant, implementation. Removing // this special case will break upgrade paths from old UUPS implementation to new ones. @@ -175,11 +167,7 @@ abstract contract ERC1967Upgrade { * * Emits a {BeaconUpgraded} event. */ - function _upgradeBeaconToAndCall( - address newBeacon, - bytes memory data, - bool forceCall - ) internal { + function _upgradeBeaconToAndCall(address newBeacon, bytes memory data, bool forceCall) internal { _setBeacon(newBeacon); emit BeaconUpgraded(newBeacon); if (data.length > 0 || forceCall) { diff --git a/contracts/proxy/transparent/TransparentUpgradeableProxy.sol b/contracts/proxy/transparent/TransparentUpgradeableProxy.sol index 57417296d..ffc97ff48 100644 --- a/contracts/proxy/transparent/TransparentUpgradeableProxy.sol +++ b/contracts/proxy/transparent/TransparentUpgradeableProxy.sol @@ -31,11 +31,7 @@ contract TransparentUpgradeableProxy is ERC1967Proxy { * @dev Initializes an upgradeable proxy managed by `_admin`, backed by the implementation at `_logic`, and * optionally initialized with `_data` as explained in {ERC1967Proxy-constructor}. */ - constructor( - address _logic, - address admin_, - bytes memory _data - ) payable ERC1967Proxy(_logic, _data) { + constructor(address _logic, address admin_, bytes memory _data) payable ERC1967Proxy(_logic, _data) { _changeAdmin(admin_); } diff --git a/contracts/token/ERC1155/ERC1155.sol b/contracts/token/ERC1155/ERC1155.sol index e33faf80c..b20b711d5 100644 --- a/contracts/token/ERC1155/ERC1155.sol +++ b/contracts/token/ERC1155/ERC1155.sol @@ -79,13 +79,10 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { * * - `accounts` and `ids` must have the same length. */ - function balanceOfBatch(address[] memory accounts, uint256[] memory ids) - public - view - virtual - override - returns (uint256[] memory) - { + function balanceOfBatch( + address[] memory accounts, + uint256[] memory ids + ) public view virtual override returns (uint256[] memory) { require(accounts.length == ids.length, "ERC1155: accounts and ids length mismatch"); uint256[] memory batchBalances = new uint256[](accounts.length); @@ -263,12 +260,7 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the * acceptance magic value. */ - function _mint( - address to, - uint256 id, - uint256 amount, - bytes memory data - ) internal virtual { + function _mint(address to, uint256 id, uint256 amount, bytes memory data) internal virtual { require(to != address(0), "ERC1155: mint to the zero address"); address operator = _msgSender(); @@ -330,11 +322,7 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { * - `from` cannot be the zero address. * - `from` must have at least `amount` tokens of token type `id`. */ - function _burn( - address from, - uint256 id, - uint256 amount - ) internal virtual { + function _burn(address from, uint256 id, uint256 amount) internal virtual { require(from != address(0), "ERC1155: burn from the zero address"); address operator = _msgSender(); @@ -363,11 +351,7 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { * * - `ids` and `amounts` must have the same length. */ - function _burnBatch( - address from, - uint256[] memory ids, - uint256[] memory amounts - ) internal virtual { + function _burnBatch(address from, uint256[] memory ids, uint256[] memory amounts) internal virtual { require(from != address(0), "ERC1155: burn from the zero address"); require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch"); @@ -396,11 +380,7 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { * * Emits an {ApprovalForAll} event. */ - function _setApprovalForAll( - address owner, - address operator, - bool approved - ) internal virtual { + function _setApprovalForAll(address owner, address operator, bool approved) internal virtual { require(owner != operator, "ERC1155: setting approval status for self"); _operatorApprovals[owner][operator] = approved; emit ApprovalForAll(owner, operator, approved); diff --git a/contracts/token/ERC1155/IERC1155.sol b/contracts/token/ERC1155/IERC1155.sol index 05f74dc4f..eae0b7029 100644 --- a/contracts/token/ERC1155/IERC1155.sol +++ b/contracts/token/ERC1155/IERC1155.sol @@ -60,10 +60,10 @@ interface IERC1155 is IERC165 { * * - `accounts` and `ids` must have the same length. */ - function balanceOfBatch(address[] calldata accounts, uint256[] calldata ids) - external - view - returns (uint256[] memory); + function balanceOfBatch( + address[] calldata accounts, + uint256[] calldata ids + ) external view returns (uint256[] memory); /** * @dev Grants or revokes permission to `operator` to transfer the caller's tokens, according to `approved`, @@ -96,13 +96,7 @@ interface IERC1155 is IERC165 { * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the * acceptance magic value. */ - function safeTransferFrom( - address from, - address to, - uint256 id, - uint256 amount, - bytes calldata data - ) external; + function safeTransferFrom(address from, address to, uint256 id, uint256 amount, bytes calldata data) external; /** * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {safeTransferFrom}. diff --git a/contracts/token/ERC1155/extensions/ERC1155Burnable.sol b/contracts/token/ERC1155/extensions/ERC1155Burnable.sol index cfaa2359d..cc81957a7 100644 --- a/contracts/token/ERC1155/extensions/ERC1155Burnable.sol +++ b/contracts/token/ERC1155/extensions/ERC1155Burnable.sol @@ -12,11 +12,7 @@ import "../ERC1155.sol"; * _Available since v3.1._ */ abstract contract ERC1155Burnable is ERC1155 { - function burn( - address account, - uint256 id, - uint256 value - ) public virtual { + function burn(address account, uint256 id, uint256 value) public virtual { require( account == _msgSender() || isApprovedForAll(account, _msgSender()), "ERC1155: caller is not token owner or approved" @@ -25,11 +21,7 @@ abstract contract ERC1155Burnable is ERC1155 { _burn(account, id, value); } - function burnBatch( - address account, - uint256[] memory ids, - uint256[] memory values - ) public virtual { + function burnBatch(address account, uint256[] memory ids, uint256[] memory values) public virtual { require( account == _msgSender() || isApprovedForAll(account, _msgSender()), "ERC1155: caller is not token owner or approved" diff --git a/contracts/token/ERC20/ERC20.sol b/contracts/token/ERC20/ERC20.sol index 261a46f22..f9be8b689 100644 --- a/contracts/token/ERC20/ERC20.sol +++ b/contracts/token/ERC20/ERC20.sol @@ -154,11 +154,7 @@ contract ERC20 is Context, IERC20, IERC20Metadata { * - the caller must have allowance for ``from``'s tokens of at least * `amount`. */ - function transferFrom( - address from, - address to, - uint256 amount - ) public virtual override returns (bool) { + function transferFrom(address from, address to, uint256 amount) public virtual override returns (bool) { address spender = _msgSender(); _spendAllowance(from, spender, amount); _transfer(from, to, amount); @@ -218,11 +214,7 @@ contract ERC20 is Context, IERC20, IERC20Metadata { * * NOTE: This function is not virtual, {_update} should be overridden instead. */ - function _transfer( - address from, - address to, - uint256 amount - ) internal { + function _transfer(address from, address to, uint256 amount) internal { require(from != address(0), "ERC20: transfer from the zero address"); require(to != address(0), "ERC20: transfer to the zero address"); _update(from, to, amount); @@ -234,11 +226,7 @@ contract ERC20 is Context, IERC20, IERC20Metadata { * * Emits a {Transfer} event. */ - function _update( - address from, - address to, - uint256 amount - ) internal virtual { + function _update(address from, address to, uint256 amount) internal virtual { if (from == address(0)) { _totalSupply += amount; unchecked { @@ -306,11 +294,7 @@ contract ERC20 is Context, IERC20, IERC20Metadata { * - `owner` cannot be the zero address. * - `spender` cannot be the zero address. */ - function _approve( - address owner, - address spender, - uint256 amount - ) internal virtual { + function _approve(address owner, address spender, uint256 amount) internal virtual { require(owner != address(0), "ERC20: approve from the zero address"); require(spender != address(0), "ERC20: approve to the zero address"); @@ -326,11 +310,7 @@ contract ERC20 is Context, IERC20, IERC20Metadata { * * Might emit an {Approval} event. */ - function _spendAllowance( - address owner, - address spender, - uint256 amount - ) internal virtual { + function _spendAllowance(address owner, address spender, uint256 amount) internal virtual { uint256 currentAllowance = allowance(owner, spender); if (currentAllowance != type(uint256).max) { require(currentAllowance >= amount, "ERC20: insufficient allowance"); diff --git a/contracts/token/ERC20/IERC20.sol b/contracts/token/ERC20/IERC20.sol index b816bfed0..66c4e4d88 100644 --- a/contracts/token/ERC20/IERC20.sol +++ b/contracts/token/ERC20/IERC20.sol @@ -74,9 +74,5 @@ interface IERC20 { * * Emits a {Transfer} event. */ - function transferFrom( - address from, - address to, - uint256 amount - ) external returns (bool); + function transferFrom(address from, address to, uint256 amount) external returns (bool); } diff --git a/contracts/token/ERC20/extensions/ERC20Capped.sol b/contracts/token/ERC20/extensions/ERC20Capped.sol index 3be9ff805..d80fb2a4c 100644 --- a/contracts/token/ERC20/extensions/ERC20Capped.sol +++ b/contracts/token/ERC20/extensions/ERC20Capped.sol @@ -30,11 +30,7 @@ abstract contract ERC20Capped is ERC20 { /** * @dev See {ERC20-_transfer}. */ - function _update( - address from, - address to, - uint256 amount - ) internal virtual override { + function _update(address from, address to, uint256 amount) internal virtual override { if (from == address(0)) { require(ERC20.totalSupply() + amount <= cap(), "ERC20Capped: cap exceeded"); } diff --git a/contracts/token/ERC20/extensions/ERC20Pausable.sol b/contracts/token/ERC20/extensions/ERC20Pausable.sol index 92d764932..85dd164d3 100644 --- a/contracts/token/ERC20/extensions/ERC20Pausable.sol +++ b/contracts/token/ERC20/extensions/ERC20Pausable.sol @@ -21,11 +21,7 @@ abstract contract ERC20Pausable is ERC20, Pausable { * * - the contract must not be paused. */ - function _update( - address from, - address to, - uint256 amount - ) internal virtual override { + function _update(address from, address to, uint256 amount) internal virtual override { require(!paused(), "ERC20Pausable: token transfer while paused"); super._update(from, to, amount); } diff --git a/contracts/token/ERC20/extensions/ERC20Snapshot.sol b/contracts/token/ERC20/extensions/ERC20Snapshot.sol index 2c34e2268..3bc1a74ee 100644 --- a/contracts/token/ERC20/extensions/ERC20Snapshot.sol +++ b/contracts/token/ERC20/extensions/ERC20Snapshot.sol @@ -120,11 +120,7 @@ abstract contract ERC20Snapshot is ERC20 { // Update balance and/or total supply snapshots before the values are modified. This is executed // for _mint, _burn, and _transfer operations. - function _update( - address from, - address to, - uint256 amount - ) internal virtual override { + function _update(address from, address to, uint256 amount) internal virtual override { if (from == address(0)) { // mint _updateAccountSnapshot(to); diff --git a/contracts/token/ERC20/extensions/ERC20Votes.sol b/contracts/token/ERC20/extensions/ERC20Votes.sol index 7f9f36b76..225d08c44 100644 --- a/contracts/token/ERC20/extensions/ERC20Votes.sol +++ b/contracts/token/ERC20/extensions/ERC20Votes.sol @@ -35,11 +35,7 @@ abstract contract ERC20Votes is ERC20, Votes { * * Emits a {IVotes-DelegateVotesChanged} event. */ - function _update( - address from, - address to, - uint256 amount - ) internal virtual override { + function _update(address from, address to, uint256 amount) internal virtual override { super._update(from, to, amount); if (from == address(0)) { require(totalSupply() <= _maxSupply(), "ERC20Votes: total supply risks overflowing votes"); diff --git a/contracts/token/ERC20/extensions/ERC4626.sol b/contracts/token/ERC20/extensions/ERC4626.sol index 3ffcd7bfd..9b26aefeb 100644 --- a/contracts/token/ERC20/extensions/ERC4626.sol +++ b/contracts/token/ERC20/extensions/ERC4626.sol @@ -153,11 +153,7 @@ abstract contract ERC4626 is ERC20, IERC4626 { } /** @dev See {IERC4626-withdraw}. */ - function withdraw( - uint256 assets, - address receiver, - address owner - ) public virtual override returns (uint256) { + function withdraw(uint256 assets, address receiver, address owner) public virtual override returns (uint256) { require(assets <= maxWithdraw(owner), "ERC4626: withdraw more than max"); uint256 shares = previewWithdraw(assets); @@ -167,11 +163,7 @@ abstract contract ERC4626 is ERC20, IERC4626 { } /** @dev See {IERC4626-redeem}. */ - function redeem( - uint256 shares, - address receiver, - address owner - ) public virtual override returns (uint256) { + function redeem(uint256 shares, address receiver, address owner) public virtual override returns (uint256) { require(shares <= maxRedeem(owner), "ERC4626: redeem more than max"); uint256 assets = previewRedeem(shares); @@ -230,12 +222,7 @@ abstract contract ERC4626 is ERC20, IERC4626 { /** * @dev Deposit/mint common workflow. */ - function _deposit( - address caller, - address receiver, - uint256 assets, - uint256 shares - ) internal virtual { + function _deposit(address caller, address receiver, uint256 assets, uint256 shares) internal virtual { // If _asset is ERC777, `transferFrom` can trigger a reenterancy BEFORE the transfer happens through the // `tokensToSend` hook. On the other hand, the `tokenReceived` hook, that is triggered after the transfer, // calls the vault, which is assumed not malicious. diff --git a/contracts/token/ERC20/utils/SafeERC20.sol b/contracts/token/ERC20/utils/SafeERC20.sol index bfc381f9b..028711ddf 100644 --- a/contracts/token/ERC20/utils/SafeERC20.sol +++ b/contracts/token/ERC20/utils/SafeERC20.sol @@ -19,20 +19,11 @@ import "../../../utils/Address.sol"; library SafeERC20 { using Address for address; - function safeTransfer( - IERC20 token, - address to, - uint256 value - ) internal { + function safeTransfer(IERC20 token, address to, uint256 value) internal { _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); } - function safeTransferFrom( - IERC20 token, - address from, - address to, - uint256 value - ) internal { + function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal { _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value)); } @@ -43,11 +34,7 @@ library SafeERC20 { * Whenever possible, use {safeIncreaseAllowance} and * {safeDecreaseAllowance} instead. */ - function safeApprove( - IERC20 token, - address spender, - uint256 value - ) internal { + function safeApprove(IERC20 token, address spender, uint256 value) internal { // safeApprove should only be called when setting an initial allowance, // or when resetting it to zero. To increase and decrease it, use // 'safeIncreaseAllowance' and 'safeDecreaseAllowance' @@ -58,20 +45,12 @@ library SafeERC20 { _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value)); } - function safeIncreaseAllowance( - IERC20 token, - address spender, - uint256 value - ) internal { + function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal { uint256 newAllowance = token.allowance(address(this), spender) + value; _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); } - function safeDecreaseAllowance( - IERC20 token, - address spender, - uint256 value - ) internal { + function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal { unchecked { uint256 oldAllowance = token.allowance(address(this), spender); require(oldAllowance >= value, "SafeERC20: decreased allowance below zero"); diff --git a/contracts/token/ERC20/utils/TokenTimelock.sol b/contracts/token/ERC20/utils/TokenTimelock.sol index d879a7e7d..ed855b7bc 100644 --- a/contracts/token/ERC20/utils/TokenTimelock.sol +++ b/contracts/token/ERC20/utils/TokenTimelock.sol @@ -29,11 +29,7 @@ contract TokenTimelock { * `beneficiary_` when {release} is invoked after `releaseTime_`. The release time is specified as a Unix timestamp * (in seconds). */ - constructor( - IERC20 token_, - address beneficiary_, - uint256 releaseTime_ - ) { + constructor(IERC20 token_, address beneficiary_, uint256 releaseTime_) { require(releaseTime_ > block.timestamp, "TokenTimelock: release time is before current time"); _token = token_; _beneficiary = beneficiary_; diff --git a/contracts/token/ERC721/ERC721.sol b/contracts/token/ERC721/ERC721.sol index 80fc22de0..6bf620b4f 100644 --- a/contracts/token/ERC721/ERC721.sol +++ b/contracts/token/ERC721/ERC721.sol @@ -147,11 +147,7 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata { /** * @dev See {IERC721-transferFrom}. */ - function transferFrom( - address from, - address to, - uint256 tokenId - ) public virtual override { + function transferFrom(address from, address to, uint256 tokenId) public virtual override { //solhint-disable-next-line max-line-length require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner or approved"); @@ -161,23 +157,14 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata { /** * @dev See {IERC721-safeTransferFrom}. */ - function safeTransferFrom( - address from, - address to, - uint256 tokenId - ) public virtual override { + function safeTransferFrom(address from, address to, uint256 tokenId) public virtual override { safeTransferFrom(from, to, tokenId, ""); } /** * @dev See {IERC721-safeTransferFrom}. */ - function safeTransferFrom( - address from, - address to, - uint256 tokenId, - bytes memory data - ) public virtual override { + function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) public virtual override { require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner or approved"); _safeTransfer(from, to, tokenId, data); } @@ -200,12 +187,7 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata { * * Emits a {Transfer} event. */ - function _safeTransfer( - address from, - address to, - uint256 tokenId, - bytes memory data - ) internal virtual { + function _safeTransfer(address from, address to, uint256 tokenId, bytes memory data) internal virtual { _transfer(from, to, tokenId); require(_checkOnERC721Received(from, to, tokenId, data), "ERC721: transfer to non ERC721Receiver implementer"); } @@ -259,11 +241,7 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata { * @dev Same as {xref-ERC721-_safeMint-address-uint256-}[`_safeMint`], with an additional `data` parameter which is * forwarded in {IERC721Receiver-onERC721Received} to contract recipients. */ - function _safeMint( - address to, - uint256 tokenId, - bytes memory data - ) internal virtual { + function _safeMint(address to, uint256 tokenId, bytes memory data) internal virtual { _mint(to, tokenId); require( _checkOnERC721Received(address(0), to, tokenId, data), @@ -352,11 +330,7 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata { * * Emits a {Transfer} event. */ - function _transfer( - address from, - address to, - uint256 tokenId - ) internal virtual { + function _transfer(address from, address to, uint256 tokenId) internal virtual { require(ERC721.ownerOf(tokenId) == from, "ERC721: transfer from incorrect owner"); require(to != address(0), "ERC721: transfer to the zero address"); @@ -399,11 +373,7 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata { * * Emits an {ApprovalForAll} event. */ - function _setApprovalForAll( - address owner, - address operator, - bool approved - ) internal virtual { + function _setApprovalForAll(address owner, address operator, bool approved) internal virtual { require(owner != operator, "ERC721: approve to caller"); _operatorApprovals[owner][operator] = approved; emit ApprovalForAll(owner, operator, approved); @@ -467,7 +437,7 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata { function _beforeTokenTransfer( address from, address to, - uint256, /* firstTokenId */ + uint256 /* firstTokenId */, uint256 batchSize ) internal virtual { if (batchSize > 1) { @@ -494,10 +464,5 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata { * * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. */ - function _afterTokenTransfer( - address from, - address to, - uint256 firstTokenId, - uint256 batchSize - ) internal virtual {} + function _afterTokenTransfer(address from, address to, uint256 firstTokenId, uint256 batchSize) internal virtual {} } diff --git a/contracts/token/ERC721/IERC721.sol b/contracts/token/ERC721/IERC721.sol index 22020bab0..646530aa5 100644 --- a/contracts/token/ERC721/IERC721.sol +++ b/contracts/token/ERC721/IERC721.sol @@ -51,12 +51,7 @@ interface IERC721 is IERC165 { * * Emits a {Transfer} event. */ - function safeTransferFrom( - address from, - address to, - uint256 tokenId, - bytes calldata data - ) external; + function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external; /** * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients @@ -72,11 +67,7 @@ interface IERC721 is IERC165 { * * Emits a {Transfer} event. */ - function safeTransferFrom( - address from, - address to, - uint256 tokenId - ) external; + function safeTransferFrom(address from, address to, uint256 tokenId) external; /** * @dev Transfers `tokenId` token from `from` to `to`. @@ -94,11 +85,7 @@ interface IERC721 is IERC165 { * * Emits a {Transfer} event. */ - function transferFrom( - address from, - address to, - uint256 tokenId - ) external; + function transferFrom(address from, address to, uint256 tokenId) external; /** * @dev Gives permission to `to` to transfer `tokenId` token to another account. diff --git a/contracts/token/ERC721/utils/ERC721Holder.sol b/contracts/token/ERC721/utils/ERC721Holder.sol index 394926d51..cfa533a47 100644 --- a/contracts/token/ERC721/utils/ERC721Holder.sol +++ b/contracts/token/ERC721/utils/ERC721Holder.sol @@ -17,12 +17,7 @@ contract ERC721Holder is IERC721Receiver { * * Always returns `IERC721Receiver.onERC721Received.selector`. */ - function onERC721Received( - address, - address, - uint256, - bytes memory - ) public virtual override returns (bytes4) { + function onERC721Received(address, address, uint256, bytes memory) public virtual override returns (bytes4) { return this.onERC721Received.selector; } } diff --git a/contracts/token/ERC777/ERC777.sol b/contracts/token/ERC777/ERC777.sol index 7c35bf5a8..c1503c4df 100644 --- a/contracts/token/ERC777/ERC777.sol +++ b/contracts/token/ERC777/ERC777.sol @@ -57,11 +57,7 @@ contract ERC777 is Context, IERC777, IERC20 { /** * @dev `defaultOperators` may be an empty array. */ - constructor( - string memory name_, - string memory symbol_, - address[] memory defaultOperators_ - ) { + constructor(string memory name_, string memory symbol_, address[] memory defaultOperators_) { _name = name_; _symbol = symbol_; @@ -127,11 +123,7 @@ contract ERC777 is Context, IERC777, IERC20 { * * Also emits a {IERC20-Transfer} event for ERC20 compatibility. */ - function send( - address recipient, - uint256 amount, - bytes memory data - ) public virtual override { + function send(address recipient, uint256 amount, bytes memory data) public virtual override { _send(_msgSender(), recipient, amount, data, "", true); } @@ -272,11 +264,7 @@ contract ERC777 is Context, IERC777, IERC20 { * * Emits {Sent}, {IERC20-Transfer} and {IERC20-Approval} events. */ - function transferFrom( - address holder, - address recipient, - uint256 amount - ) public virtual override returns (bool) { + function transferFrom(address holder, address recipient, uint256 amount) public virtual override returns (bool) { address spender = _msgSender(); _spendAllowance(holder, spender, amount); _send(holder, recipient, amount, "", "", false); @@ -301,12 +289,7 @@ contract ERC777 is Context, IERC777, IERC20 { * - if `account` is a contract, it must implement the {IERC777Recipient} * interface. */ - function _mint( - address account, - uint256 amount, - bytes memory userData, - bytes memory operatorData - ) internal virtual { + function _mint(address account, uint256 amount, bytes memory userData, bytes memory operatorData) internal virtual { _mint(account, amount, userData, operatorData, true); } @@ -387,12 +370,7 @@ contract ERC777 is Context, IERC777, IERC20 { * @param data bytes extra information provided by the token holder * @param operatorData bytes extra information provided by the operator (if any) */ - function _burn( - address from, - uint256 amount, - bytes memory data, - bytes memory operatorData - ) internal virtual { + function _burn(address from, uint256 amount, bytes memory data, bytes memory operatorData) internal virtual { require(from != address(0), "ERC777: burn from the zero address"); address operator = _msgSender(); @@ -439,11 +417,7 @@ contract ERC777 is Context, IERC777, IERC20 { * * Note that accounts cannot have allowance issued by their operators. */ - function _approve( - address holder, - address spender, - uint256 value - ) internal virtual { + function _approve(address holder, address spender, uint256 value) internal virtual { require(holder != address(0), "ERC777: approve from the zero address"); require(spender != address(0), "ERC777: approve to the zero address"); @@ -510,11 +484,7 @@ contract ERC777 is Context, IERC777, IERC20 { * * Might emit an {IERC20-Approval} event. */ - function _spendAllowance( - address owner, - address spender, - uint256 amount - ) internal virtual { + function _spendAllowance(address owner, address spender, uint256 amount) internal virtual { uint256 currentAllowance = allowance(owner, spender); if (currentAllowance != type(uint256).max) { require(currentAllowance >= amount, "ERC777: insufficient allowance"); @@ -538,10 +508,5 @@ contract ERC777 is Context, IERC777, IERC20 { * * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. */ - function _beforeTokenTransfer( - address operator, - address from, - address to, - uint256 amount - ) internal virtual {} + function _beforeTokenTransfer(address operator, address from, address to, uint256 amount) internal virtual {} } diff --git a/contracts/token/ERC777/IERC777.sol b/contracts/token/ERC777/IERC777.sol index 2af7771b0..d3bede626 100644 --- a/contracts/token/ERC777/IERC777.sol +++ b/contracts/token/ERC777/IERC777.sol @@ -83,11 +83,7 @@ interface IERC777 { * - if `recipient` is a contract, it must implement the {IERC777Recipient} * interface. */ - function send( - address recipient, - uint256 amount, - bytes calldata data - ) external; + function send(address recipient, uint256 amount, bytes calldata data) external; /** * @dev Destroys `amount` tokens from the caller's account, reducing the @@ -191,12 +187,7 @@ interface IERC777 { * - `account` must have at least `amount` tokens. * - the caller must be an operator for `account`. */ - function operatorBurn( - address account, - uint256 amount, - bytes calldata data, - bytes calldata operatorData - ) external; + function operatorBurn(address account, uint256 amount, bytes calldata data, bytes calldata operatorData) external; event Sent( address indexed operator, diff --git a/contracts/token/common/ERC2981.sol b/contracts/token/common/ERC2981.sol index 604dba304..84cb6b8de 100644 --- a/contracts/token/common/ERC2981.sol +++ b/contracts/token/common/ERC2981.sol @@ -91,11 +91,7 @@ abstract contract ERC2981 is IERC2981, ERC165 { * - `receiver` cannot be the zero address. * - `feeNumerator` cannot be greater than the fee denominator. */ - function _setTokenRoyalty( - uint256 tokenId, - address receiver, - uint96 feeNumerator - ) internal virtual { + function _setTokenRoyalty(uint256 tokenId, address receiver, uint96 feeNumerator) internal virtual { require(feeNumerator <= _feeDenominator(), "ERC2981: royalty fee will exceed salePrice"); require(receiver != address(0), "ERC2981: Invalid parameters"); diff --git a/contracts/utils/Address.sol b/contracts/utils/Address.sol index e89512342..70d03e3d2 100644 --- a/contracts/utils/Address.sol +++ b/contracts/utils/Address.sol @@ -111,11 +111,7 @@ library Address { * * _Available since v3.1._ */ - function functionCallWithValue( - address target, - bytes memory data, - uint256 value - ) internal returns (bytes memory) { + function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) { return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); } diff --git a/contracts/utils/Checkpoints.sol b/contracts/utils/Checkpoints.sol index f20601c6a..76203edd2 100644 --- a/contracts/utils/Checkpoints.sol +++ b/contracts/utils/Checkpoints.sol @@ -111,15 +111,9 @@ library Checkpoints { * @dev Returns whether there is a checkpoint in the structure (i.e. it is not empty), and if so the key and value * in the most recent checkpoint. */ - function latestCheckpoint(History storage self) - internal - view - returns ( - bool exists, - uint32 _blockNumber, - uint224 _value - ) - { + function latestCheckpoint( + History storage self + ) internal view returns (bool exists, uint32 _blockNumber, uint224 _value) { uint256 pos = self._checkpoints.length; if (pos == 0) { return (false, 0, 0); @@ -140,11 +134,7 @@ library Checkpoints { * @dev Pushes a (`key`, `value`) pair into an ordered list of checkpoints, either by inserting a new checkpoint, * or by updating the last one. */ - function _insert( - Checkpoint[] storage self, - uint32 key, - uint224 value - ) private returns (uint224, uint224) { + function _insert(Checkpoint[] storage self, uint32 key, uint224 value) private returns (uint224, uint224) { uint256 pos = self.length; if (pos > 0) { @@ -237,11 +227,7 @@ library Checkpoints { * * Returns previous value and new value. */ - function push( - Trace224 storage self, - uint32 key, - uint224 value - ) internal returns (uint224, uint224) { + function push(Trace224 storage self, uint32 key, uint224 value) internal returns (uint224, uint224) { return _insert(self._checkpoints, key, value); } @@ -275,15 +261,7 @@ library Checkpoints { * @dev Returns whether there is a checkpoint in the structure (i.e. it is not empty), and if so the key and value * in the most recent checkpoint. */ - function latestCheckpoint(Trace224 storage self) - internal - view - returns ( - bool exists, - uint32 _key, - uint224 _value - ) - { + function latestCheckpoint(Trace224 storage self) internal view returns (bool exists, uint32 _key, uint224 _value) { uint256 pos = self._checkpoints.length; if (pos == 0) { return (false, 0, 0); @@ -304,11 +282,7 @@ library Checkpoints { * @dev Pushes a (`key`, `value`) pair into an ordered list of checkpoints, either by inserting a new checkpoint, * or by updating the last one. */ - function _insert( - Checkpoint224[] storage self, - uint32 key, - uint224 value - ) private returns (uint224, uint224) { + function _insert(Checkpoint224[] storage self, uint32 key, uint224 value) private returns (uint224, uint224) { uint256 pos = self.length; if (pos > 0) { @@ -380,11 +354,10 @@ library Checkpoints { /** * @dev Access an element of the array without performing bounds check. The position is assumed to be within bounds. */ - function _unsafeAccess(Checkpoint224[] storage self, uint256 pos) - private - pure - returns (Checkpoint224 storage result) - { + function _unsafeAccess( + Checkpoint224[] storage self, + uint256 pos + ) private pure returns (Checkpoint224 storage result) { assembly { mstore(0, self.slot) result.slot := add(keccak256(0, 0x20), pos) @@ -405,11 +378,7 @@ library Checkpoints { * * Returns previous value and new value. */ - function push( - Trace160 storage self, - uint96 key, - uint160 value - ) internal returns (uint160, uint160) { + function push(Trace160 storage self, uint96 key, uint160 value) internal returns (uint160, uint160) { return _insert(self._checkpoints, key, value); } @@ -443,15 +412,7 @@ library Checkpoints { * @dev Returns whether there is a checkpoint in the structure (i.e. it is not empty), and if so the key and value * in the most recent checkpoint. */ - function latestCheckpoint(Trace160 storage self) - internal - view - returns ( - bool exists, - uint96 _key, - uint160 _value - ) - { + function latestCheckpoint(Trace160 storage self) internal view returns (bool exists, uint96 _key, uint160 _value) { uint256 pos = self._checkpoints.length; if (pos == 0) { return (false, 0, 0); @@ -472,11 +433,7 @@ library Checkpoints { * @dev Pushes a (`key`, `value`) pair into an ordered list of checkpoints, either by inserting a new checkpoint, * or by updating the last one. */ - function _insert( - Checkpoint160[] storage self, - uint96 key, - uint160 value - ) private returns (uint160, uint160) { + function _insert(Checkpoint160[] storage self, uint96 key, uint160 value) private returns (uint160, uint160) { uint256 pos = self.length; if (pos > 0) { @@ -548,11 +505,10 @@ library Checkpoints { /** * @dev Access an element of the array without performing bounds check. The position is assumed to be within bounds. */ - function _unsafeAccess(Checkpoint160[] storage self, uint256 pos) - private - pure - returns (Checkpoint160 storage result) - { + function _unsafeAccess( + Checkpoint160[] storage self, + uint256 pos + ) private pure returns (Checkpoint160 storage result) { assembly { mstore(0, self.slot) result.slot := add(keccak256(0, 0x20), pos) diff --git a/contracts/utils/Create2.sol b/contracts/utils/Create2.sol index 8df86d669..2255a4df8 100644 --- a/contracts/utils/Create2.sol +++ b/contracts/utils/Create2.sol @@ -27,11 +27,7 @@ library Create2 { * - the factory must have a balance of at least `amount`. * - if `amount` is non-zero, `bytecode` must have a `payable` constructor. */ - function deploy( - uint256 amount, - bytes32 salt, - bytes memory bytecode - ) internal returns (address addr) { + function deploy(uint256 amount, bytes32 salt, bytes memory bytecode) internal returns (address addr) { require(address(this).balance >= amount, "Create2: insufficient balance"); require(bytecode.length != 0, "Create2: bytecode length is zero"); /// @solidity memory-safe-assembly @@ -53,11 +49,7 @@ library Create2 { * @dev Returns the address where a contract will be stored if deployed via {deploy} from a contract located at * `deployer`. If `deployer` is this contract's address, returns the same value as {computeAddress}. */ - function computeAddress( - bytes32 salt, - bytes32 bytecodeHash, - address deployer - ) internal pure returns (address addr) { + function computeAddress(bytes32 salt, bytes32 bytecodeHash, address deployer) internal pure returns (address addr) { /// @solidity memory-safe-assembly assembly { let ptr := mload(0x40) // Get free memory pointer diff --git a/contracts/utils/cryptography/ECDSA.sol b/contracts/utils/cryptography/ECDSA.sol index 4b1d66b09..3f996520e 100644 --- a/contracts/utils/cryptography/ECDSA.sol +++ b/contracts/utils/cryptography/ECDSA.sol @@ -98,11 +98,7 @@ library ECDSA { * * _Available since v4.3._ */ - function tryRecover( - bytes32 hash, - bytes32 r, - bytes32 vs - ) internal pure returns (address, RecoverError) { + function tryRecover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address, RecoverError) { bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff); uint8 v = uint8((uint256(vs) >> 255) + 27); return tryRecover(hash, v, r, s); @@ -113,11 +109,7 @@ library ECDSA { * * _Available since v4.2._ */ - function recover( - bytes32 hash, - bytes32 r, - bytes32 vs - ) internal pure returns (address) { + function recover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address) { (address recovered, RecoverError error) = tryRecover(hash, r, vs); _throwError(error); return recovered; @@ -129,12 +121,7 @@ library ECDSA { * * _Available since v4.3._ */ - function tryRecover( - bytes32 hash, - uint8 v, - bytes32 r, - bytes32 s - ) internal pure returns (address, RecoverError) { + function tryRecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address, RecoverError) { // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most @@ -161,12 +148,7 @@ library ECDSA { * @dev Overload of {ECDSA-recover} that receives the `v`, * `r` and `s` signature fields separately. */ - function recover( - bytes32 hash, - uint8 v, - bytes32 r, - bytes32 s - ) internal pure returns (address) { + function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) { (address recovered, RecoverError error) = tryRecover(hash, v, r, s); _throwError(error); return recovered; diff --git a/contracts/utils/cryptography/MerkleProof.sol b/contracts/utils/cryptography/MerkleProof.sol index 47c60eb01..0ce87faf2 100644 --- a/contracts/utils/cryptography/MerkleProof.sol +++ b/contracts/utils/cryptography/MerkleProof.sol @@ -24,11 +24,7 @@ library MerkleProof { * sibling hashes on the branch from the leaf to the root of the tree. Each * pair of leaves and each pair of pre-images are assumed to be sorted. */ - function verify( - bytes32[] memory proof, - bytes32 root, - bytes32 leaf - ) internal pure returns (bool) { + function verify(bytes32[] memory proof, bytes32 root, bytes32 leaf) internal pure returns (bool) { return processProof(proof, leaf) == root; } @@ -37,11 +33,7 @@ library MerkleProof { * * _Available since v4.7._ */ - function verifyCalldata( - bytes32[] calldata proof, - bytes32 root, - bytes32 leaf - ) internal pure returns (bool) { + function verifyCalldata(bytes32[] calldata proof, bytes32 root, bytes32 leaf) internal pure returns (bool) { return processProofCalldata(proof, leaf) == root; } diff --git a/contracts/utils/cryptography/SignatureChecker.sol b/contracts/utils/cryptography/SignatureChecker.sol index 77fe98282..e06778deb 100644 --- a/contracts/utils/cryptography/SignatureChecker.sol +++ b/contracts/utils/cryptography/SignatureChecker.sol @@ -21,11 +21,7 @@ library SignatureChecker { * NOTE: Unlike ECDSA signatures, contract signatures are revocable, and the outcome of this function can thus * change through time. It could return true at block N and false at block N+1 (or the opposite). */ - function isValidSignatureNow( - address signer, - bytes32 hash, - bytes memory signature - ) internal view returns (bool) { + function isValidSignatureNow(address signer, bytes32 hash, bytes memory signature) internal view returns (bool) { (address recovered, ECDSA.RecoverError error) = ECDSA.tryRecover(hash, signature); if (error == ECDSA.RecoverError.NoError && recovered == signer) { return true; diff --git a/contracts/utils/introspection/ERC165Checker.sol b/contracts/utils/introspection/ERC165Checker.sol index 4c5fe2092..40ffd68f7 100644 --- a/contracts/utils/introspection/ERC165Checker.sol +++ b/contracts/utils/introspection/ERC165Checker.sol @@ -48,11 +48,10 @@ library ERC165Checker { * * _Available since v3.4._ */ - function getSupportedInterfaces(address account, bytes4[] memory interfaceIds) - internal - view - returns (bool[] memory) - { + function getSupportedInterfaces( + address account, + bytes4[] memory interfaceIds + ) internal view returns (bool[] memory) { // an array of booleans corresponding to interfaceIds and whether they're supported or not bool[] memory interfaceIdsSupported = new bool[](interfaceIds.length); diff --git a/contracts/utils/introspection/ERC1820Implementer.sol b/contracts/utils/introspection/ERC1820Implementer.sol index 1b5139658..ac5a884c0 100644 --- a/contracts/utils/introspection/ERC1820Implementer.sol +++ b/contracts/utils/introspection/ERC1820Implementer.sol @@ -21,13 +21,10 @@ contract ERC1820Implementer is IERC1820Implementer { /** * @dev See {IERC1820Implementer-canImplementInterfaceForAddress}. */ - function canImplementInterfaceForAddress(bytes32 interfaceHash, address account) - public - view - virtual - override - returns (bytes32) - { + function canImplementInterfaceForAddress( + bytes32 interfaceHash, + address account + ) public view virtual override returns (bytes32) { return _supportedInterfaces[interfaceHash][account] ? _ERC1820_ACCEPT_MAGIC : bytes32(0x00); } diff --git a/contracts/utils/introspection/IERC1820Registry.sol b/contracts/utils/introspection/IERC1820Registry.sol index 42cf46a8a..a146bc2a6 100644 --- a/contracts/utils/introspection/IERC1820Registry.sol +++ b/contracts/utils/introspection/IERC1820Registry.sol @@ -64,11 +64,7 @@ interface IERC1820Registry { * queried for support, unless `implementer` is the caller. See * {IERC1820Implementer-canImplementInterfaceForAddress}. */ - function setInterfaceImplementer( - address account, - bytes32 _interfaceHash, - address implementer - ) external; + function setInterfaceImplementer(address account, bytes32 _interfaceHash, address implementer) external; /** * @dev Returns the implementer of `interfaceHash` for `account`. If no such diff --git a/contracts/utils/math/Math.sol b/contracts/utils/math/Math.sol index 0ed5460e0..f3a83b0ff 100644 --- a/contracts/utils/math/Math.sol +++ b/contracts/utils/math/Math.sol @@ -52,11 +52,7 @@ library Math { * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) * with further edits by Uniswap Labs also under MIT license. */ - function mulDiv( - uint256 x, - uint256 y, - uint256 denominator - ) internal pure returns (uint256 result) { + function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) { unchecked { // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256 @@ -137,12 +133,7 @@ library Math { /** * @notice Calculates x * y / denominator with full precision, following the selected rounding direction. */ - function mulDiv( - uint256 x, - uint256 y, - uint256 denominator, - Rounding rounding - ) internal pure returns (uint256) { + function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) { uint256 result = mulDiv(x, y, denominator); if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) { result += 1; @@ -258,31 +249,31 @@ library Math { function log10(uint256 value) internal pure returns (uint256) { uint256 result = 0; unchecked { - if (value >= 10**64) { - value /= 10**64; + if (value >= 10 ** 64) { + value /= 10 ** 64; result += 64; } - if (value >= 10**32) { - value /= 10**32; + if (value >= 10 ** 32) { + value /= 10 ** 32; result += 32; } - if (value >= 10**16) { - value /= 10**16; + if (value >= 10 ** 16) { + value /= 10 ** 16; result += 16; } - if (value >= 10**8) { - value /= 10**8; + if (value >= 10 ** 8) { + value /= 10 ** 8; result += 8; } - if (value >= 10**4) { - value /= 10**4; + if (value >= 10 ** 4) { + value /= 10 ** 4; result += 4; } - if (value >= 10**2) { - value /= 10**2; + if (value >= 10 ** 2) { + value /= 10 ** 2; result += 2; } - if (value >= 10**1) { + if (value >= 10 ** 1) { result += 1; } } @@ -296,7 +287,7 @@ library Math { function log10(uint256 value, Rounding rounding) internal pure returns (uint256) { unchecked { uint256 result = log10(value); - return result + (rounding == Rounding.Up && 10**result < value ? 1 : 0); + return result + (rounding == Rounding.Up && 10 ** result < value ? 1 : 0); } } diff --git a/contracts/utils/math/SafeMath.sol b/contracts/utils/math/SafeMath.sol index 550f0e779..2f48fb736 100644 --- a/contracts/utils/math/SafeMath.sol +++ b/contracts/utils/math/SafeMath.sol @@ -165,11 +165,7 @@ library SafeMath { * * - Subtraction cannot overflow. */ - function sub( - uint256 a, - uint256 b, - string memory errorMessage - ) internal pure returns (uint256) { + function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { unchecked { require(b <= a, errorMessage); return a - b; @@ -188,11 +184,7 @@ library SafeMath { * * - The divisor cannot be zero. */ - function div( - uint256 a, - uint256 b, - string memory errorMessage - ) internal pure returns (uint256) { + function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { unchecked { require(b > 0, errorMessage); return a / b; @@ -214,11 +206,7 @@ library SafeMath { * * - The divisor cannot be zero. */ - function mod( - uint256 a, - uint256 b, - string memory errorMessage - ) internal pure returns (uint256) { + function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { unchecked { require(b > 0, errorMessage); return a % b; diff --git a/contracts/utils/structs/BitMaps.sol b/contracts/utils/structs/BitMaps.sol index a2ddc4709..eb67bfab0 100644 --- a/contracts/utils/structs/BitMaps.sol +++ b/contracts/utils/structs/BitMaps.sol @@ -23,11 +23,7 @@ library BitMaps { /** * @dev Sets the bit at `index` to the boolean `value`. */ - function setTo( - BitMap storage bitmap, - uint256 index, - bool value - ) internal { + function setTo(BitMap storage bitmap, uint256 index, bool value) internal { if (value) { set(bitmap, index); } else { diff --git a/contracts/utils/structs/EnumerableMap.sol b/contracts/utils/structs/EnumerableMap.sol index a3fda61d8..d359671f4 100644 --- a/contracts/utils/structs/EnumerableMap.sol +++ b/contracts/utils/structs/EnumerableMap.sol @@ -70,11 +70,7 @@ library EnumerableMap { * Returns true if the key was added to the map, that is if it was not * already present. */ - function set( - Bytes32ToBytes32Map storage map, - bytes32 key, - bytes32 value - ) internal returns (bool) { + function set(Bytes32ToBytes32Map storage map, bytes32 key, bytes32 value) internal returns (bool) { map._values[key] = value; return map._keys.add(key); } @@ -173,11 +169,7 @@ library EnumerableMap { * Returns true if the key was added to the map, that is if it was not * already present. */ - function set( - UintToUintMap storage map, - uint256 key, - uint256 value - ) internal returns (bool) { + function set(UintToUintMap storage map, uint256 key, uint256 value) internal returns (bool) { return set(map._inner, bytes32(key), bytes32(value)); } @@ -244,11 +236,7 @@ library EnumerableMap { * CAUTION: This function is deprecated because it requires allocating memory for the error * message unnecessarily. For custom revert reasons use {tryGet}. */ - function get( - UintToUintMap storage map, - uint256 key, - string memory errorMessage - ) internal view returns (uint256) { + function get(UintToUintMap storage map, uint256 key, string memory errorMessage) internal view returns (uint256) { return uint256(get(map._inner, bytes32(key), errorMessage)); } @@ -265,11 +253,7 @@ library EnumerableMap { * Returns true if the key was added to the map, that is if it was not * already present. */ - function set( - UintToAddressMap storage map, - uint256 key, - address value - ) internal returns (bool) { + function set(UintToAddressMap storage map, uint256 key, address value) internal returns (bool) { return set(map._inner, bytes32(key), bytes32(uint256(uint160(value)))); } @@ -357,11 +341,7 @@ library EnumerableMap { * Returns true if the key was added to the map, that is if it was not * already present. */ - function set( - AddressToUintMap storage map, - address key, - uint256 value - ) internal returns (bool) { + function set(AddressToUintMap storage map, address key, uint256 value) internal returns (bool) { return set(map._inner, bytes32(uint256(uint160(key))), bytes32(value)); } @@ -449,11 +429,7 @@ library EnumerableMap { * Returns true if the key was added to the map, that is if it was not * already present. */ - function set( - Bytes32ToUintMap storage map, - bytes32 key, - uint256 value - ) internal returns (bool) { + function set(Bytes32ToUintMap storage map, bytes32 key, uint256 value) internal returns (bool) { return set(map._inner, key, bytes32(value)); } diff --git a/contracts/vendor/amb/IAMB.sol b/contracts/vendor/amb/IAMB.sol index 1a5d7080f..73a2bd24b 100644 --- a/contracts/vendor/amb/IAMB.sol +++ b/contracts/vendor/amb/IAMB.sol @@ -31,17 +31,9 @@ interface IAMB { function failedMessageSender(bytes32 _messageId) external view returns (address); - function requireToPassMessage( - address _contract, - bytes calldata _data, - uint256 _gas - ) external returns (bytes32); - - function requireToConfirmMessage( - address _contract, - bytes calldata _data, - uint256 _gas - ) external returns (bytes32); + function requireToPassMessage(address _contract, bytes calldata _data, uint256 _gas) external returns (bytes32); + + function requireToConfirmMessage(address _contract, bytes calldata _data, uint256 _gas) external returns (bytes32); function sourceChainId() external view returns (uint256); diff --git a/contracts/vendor/arbitrum/IArbSys.sol b/contracts/vendor/arbitrum/IArbSys.sol index aac5dd53a..9b79d5c16 100644 --- a/contracts/vendor/arbitrum/IArbSys.sol +++ b/contracts/vendor/arbitrum/IArbSys.sol @@ -92,14 +92,7 @@ interface IArbSys { * @return root root hash of the send history * @return partials hashes of partial subtrees in the send history tree */ - function sendMerkleTreeState() - external - view - returns ( - uint256 size, - bytes32 root, - bytes32[] memory partials - ); + function sendMerkleTreeState() external view returns (uint256 size, bytes32 root, bytes32[] memory partials); /** * @notice creates a send txn from L2 to L1 diff --git a/contracts/vendor/arbitrum/IBridge.sol b/contracts/vendor/arbitrum/IBridge.sol index 7518f5d13..e71bedce0 100644 --- a/contracts/vendor/arbitrum/IBridge.sol +++ b/contracts/vendor/arbitrum/IBridge.sol @@ -77,14 +77,7 @@ interface IBridge { uint256 afterDelayedMessagesRead, uint256 prevMessageCount, uint256 newMessageCount - ) - external - returns ( - uint256 seqMessageIndex, - bytes32 beforeAcc, - bytes32 delayedAcc, - bytes32 acc - ); + ) external returns (uint256 seqMessageIndex, bytes32 beforeAcc, bytes32 delayedAcc, bytes32 acc); /** * @dev Allows the sequencer inbox to submit a delayed message of the batchPostingReport type diff --git a/contracts/vendor/arbitrum/IOutbox.sol b/contracts/vendor/arbitrum/IOutbox.sol index 4f809dbf4..22fa58f40 100644 --- a/contracts/vendor/arbitrum/IOutbox.sol +++ b/contracts/vendor/arbitrum/IOutbox.sol @@ -113,9 +113,5 @@ interface IOutbox { bytes calldata data ) external pure returns (bytes32); - function calculateMerkleRoot( - bytes32[] memory proof, - uint256 path, - bytes32 item - ) external pure returns (bytes32); + function calculateMerkleRoot(bytes32[] memory proof, uint256 path, bytes32 item) external pure returns (bytes32); } diff --git a/contracts/vendor/optimism/ICrossDomainMessenger.sol b/contracts/vendor/optimism/ICrossDomainMessenger.sol index 9cc797701..cc01a48ab 100644 --- a/contracts/vendor/optimism/ICrossDomainMessenger.sol +++ b/contracts/vendor/optimism/ICrossDomainMessenger.sol @@ -30,9 +30,5 @@ interface ICrossDomainMessenger { * @param _message Message to send to the target. * @param _gasLimit Gas limit for the provided message. */ - function sendMessage( - address _target, - bytes calldata _message, - uint32 _gasLimit - ) external; + function sendMessage(address _target, bytes calldata _message, uint32 _gasLimit) external; } diff --git a/contracts/vendor/polygon/IFxMessageProcessor.sol b/contracts/vendor/polygon/IFxMessageProcessor.sol index 9f42eb647..be73e6f53 100644 --- a/contracts/vendor/polygon/IFxMessageProcessor.sol +++ b/contracts/vendor/polygon/IFxMessageProcessor.sol @@ -3,9 +3,5 @@ pragma solidity ^0.8.0; interface IFxMessageProcessor { - function processMessageFromRoot( - uint256 stateId, - address rootMessageSender, - bytes calldata data - ) external; + function processMessageFromRoot(uint256 stateId, address rootMessageSender, bytes calldata data) external; } diff --git a/package-lock.json b/package-lock.json index 32eae1dfc..8d8a2ca16 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,108 +1,175 @@ { "name": "openzeppelin-solidity", "version": "4.8.0", - "lockfileVersion": 1, + "lockfileVersion": 2, "requires": true, - "dependencies": { - "@babel/code-frame": { + "packages": { + "": { + "name": "openzeppelin-solidity", + "version": "4.8.0", + "license": "MIT", + "bin": { + "openzeppelin-contracts-migrate-imports": "scripts/migrate-imports.js" + }, + "devDependencies": { + "@nomicfoundation/hardhat-network-helpers": "^1.0.3", + "@nomiclabs/hardhat-truffle5": "^2.0.5", + "@nomiclabs/hardhat-web3": "^2.0.0", + "@openzeppelin/docs-utils": "^0.1.3", + "@openzeppelin/test-helpers": "^0.5.13", + "chai": "^4.2.0", + "eslint": "^7.32.0", + "eslint-config-standard": "^16.0.3", + "eslint-plugin-import": "^2.25.4", + "eslint-plugin-mocha": "^10.0.3", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-promise": "^5.2.0", + "eth-sig-util": "^3.0.0", + "ethereumjs-util": "^7.0.7", + "ethereumjs-wallet": "^1.0.1", + "glob": "^8.0.3", + "graphlib": "^2.1.8", + "hardhat": "^2.9.1", + "hardhat-gas-reporter": "^1.0.4", + "hardhat-ignore-warnings": "^0.2.0", + "keccak256": "^1.0.2", + "lodash.startcase": "^4.4.0", + "lodash.zip": "^4.2.0", + "merkletreejs": "^0.2.13", + "micromatch": "^4.0.2", + "prettier": "^2.3.0", + "prettier-plugin-solidity": "^1.1.0", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "solhint": "^3.3.6", + "solidity-ast": "^0.4.25", + "solidity-coverage": "^0.8.0", + "solidity-docgen": "^0.6.0-beta.29", + "web3": "^1.3.0", + "yargs": "^17.0.0" + } + }, + "node_modules/@babel/code-frame": { "version": "7.12.11", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", "dev": true, - "requires": { + "dependencies": { "@babel/highlight": "^7.10.4" } }, - "@babel/helper-validator-identifier": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz", - "integrity": "sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==", - "dev": true + "node_modules/@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } }, - "@babel/highlight": { + "node_modules/@babel/highlight": { "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", "dev": true, - "requires": { + "dependencies": { "@babel/helper-validator-identifier": "^7.18.6", "chalk": "^2.0.0", "js-tokens": "^4.0.0" }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" } }, - "@babel/runtime": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.19.0.tgz", - "integrity": "sha512-eR8Lo9hnDS7tqkO7NsV+mKvCmv5boaXFSZ70DnfhcgiEne8hv9oCEd36Klw74EtizEqLsy4YnW8UWwpBVolHZA==", + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, - "requires": { - "regenerator-runtime": "^0.13.4" + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" } }, - "@ensdomains/address-encoder": { + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/runtime": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.7.tgz", + "integrity": "sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==", + "dev": true, + "dependencies": { + "regenerator-runtime": "^0.13.11" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@ensdomains/address-encoder": { "version": "0.1.9", "resolved": "https://registry.npmjs.org/@ensdomains/address-encoder/-/address-encoder-0.1.9.tgz", "integrity": "sha512-E2d2gP4uxJQnDu2Kfg1tHNspefzbLT8Tyjrm5sEuim32UkU2sm5xL4VXtgc2X33fmPEw9+jUMpGs4veMbf+PYg==", "dev": true, - "requires": { + "dependencies": { "bech32": "^1.1.3", "blakejs": "^1.1.0", "bn.js": "^4.11.8", @@ -112,12 +179,13 @@ "ripemd160": "^2.0.2" } }, - "@ensdomains/ens": { + "node_modules/@ensdomains/ens": { "version": "0.4.5", "resolved": "https://registry.npmjs.org/@ensdomains/ens/-/ens-0.4.5.tgz", "integrity": "sha512-JSvpj1iNMFjK6K+uVl4unqMoa9rf5jopb8cya5UGBWz23Nw8hSNT7efgUx4BTlAPAgpNlEioUfeTyQ6J9ZvTVw==", + "deprecated": "Please use @ensdomains/ens-contracts", "dev": true, - "requires": { + "dependencies": { "bluebird": "^3.5.2", "eth-ens-namehash": "^2.0.8", "solc": "^0.4.20", @@ -125,12 +193,12 @@ "web3-utils": "^1.0.0-beta.31" } }, - "@ensdomains/ensjs": { + "node_modules/@ensdomains/ensjs": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@ensdomains/ensjs/-/ensjs-2.1.0.tgz", "integrity": "sha512-GRbGPT8Z/OJMDuxs75U/jUNEC0tbL0aj7/L/QQznGYKm/tiasp+ndLOaoULy9kKJFC0TBByqfFliEHDgoLhyog==", "dev": true, - "requires": { + "dependencies": { "@babel/runtime": "^7.4.4", "@ensdomains/address-encoder": "^0.1.7", "@ensdomains/ens": "0.4.5", @@ -139,60 +207,69 @@ "eth-ens-namehash": "^2.0.8", "ethers": "^5.0.13", "js-sha3": "^0.8.0" - }, - "dependencies": { - "ethers": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/ethers/-/ethers-5.7.0.tgz", - "integrity": "sha512-5Xhzp2ZQRi0Em+0OkOcRHxPzCfoBfgtOQA+RUylSkuHbhTEaQklnYi2hsWbRgs3ztJsXVXd9VKBcO1ScWL8YfA==", - "dev": true, - "requires": { - "@ethersproject/abi": "5.7.0", - "@ethersproject/abstract-provider": "5.7.0", - "@ethersproject/abstract-signer": "5.7.0", - "@ethersproject/address": "5.7.0", - "@ethersproject/base64": "5.7.0", - "@ethersproject/basex": "5.7.0", - "@ethersproject/bignumber": "5.7.0", - "@ethersproject/bytes": "5.7.0", - "@ethersproject/constants": "5.7.0", - "@ethersproject/contracts": "5.7.0", - "@ethersproject/hash": "5.7.0", - "@ethersproject/hdnode": "5.7.0", - "@ethersproject/json-wallets": "5.7.0", - "@ethersproject/keccak256": "5.7.0", - "@ethersproject/logger": "5.7.0", - "@ethersproject/networks": "5.7.0", - "@ethersproject/pbkdf2": "5.7.0", - "@ethersproject/properties": "5.7.0", - "@ethersproject/providers": "5.7.0", - "@ethersproject/random": "5.7.0", - "@ethersproject/rlp": "5.7.0", - "@ethersproject/sha2": "5.7.0", - "@ethersproject/signing-key": "5.7.0", - "@ethersproject/solidity": "5.7.0", - "@ethersproject/strings": "5.7.0", - "@ethersproject/transactions": "5.7.0", - "@ethersproject/units": "5.7.0", - "@ethersproject/wallet": "5.7.0", - "@ethersproject/web": "5.7.0", - "@ethersproject/wordlists": "5.7.0" - } - } } }, - "@ensdomains/resolver": { + "node_modules/@ensdomains/ensjs/node_modules/ethers": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-5.7.2.tgz", + "integrity": "sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/abi": "5.7.0", + "@ethersproject/abstract-provider": "5.7.0", + "@ethersproject/abstract-signer": "5.7.0", + "@ethersproject/address": "5.7.0", + "@ethersproject/base64": "5.7.0", + "@ethersproject/basex": "5.7.0", + "@ethersproject/bignumber": "5.7.0", + "@ethersproject/bytes": "5.7.0", + "@ethersproject/constants": "5.7.0", + "@ethersproject/contracts": "5.7.0", + "@ethersproject/hash": "5.7.0", + "@ethersproject/hdnode": "5.7.0", + "@ethersproject/json-wallets": "5.7.0", + "@ethersproject/keccak256": "5.7.0", + "@ethersproject/logger": "5.7.0", + "@ethersproject/networks": "5.7.1", + "@ethersproject/pbkdf2": "5.7.0", + "@ethersproject/properties": "5.7.0", + "@ethersproject/providers": "5.7.2", + "@ethersproject/random": "5.7.0", + "@ethersproject/rlp": "5.7.0", + "@ethersproject/sha2": "5.7.0", + "@ethersproject/signing-key": "5.7.0", + "@ethersproject/solidity": "5.7.0", + "@ethersproject/strings": "5.7.0", + "@ethersproject/transactions": "5.7.0", + "@ethersproject/units": "5.7.0", + "@ethersproject/wallet": "5.7.0", + "@ethersproject/web": "5.7.1", + "@ethersproject/wordlists": "5.7.0" + } + }, + "node_modules/@ensdomains/resolver": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/@ensdomains/resolver/-/resolver-0.2.4.tgz", "integrity": "sha512-bvaTH34PMCbv6anRa9I/0zjLJgY4EuznbEMgbV77JBCQ9KNC46rzi0avuxpOfu+xDjPEtSFGqVEOr5GlUSGudA==", + "deprecated": "Please use @ensdomains/ens-contracts", "dev": true }, - "@eslint/eslintrc": { + "node_modules/@eslint/eslintrc": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", "dev": true, - "requires": { + "dependencies": { "ajv": "^6.12.4", "debug": "^4.1.1", "espree": "^7.3.0", @@ -202,34 +279,47 @@ "js-yaml": "^3.13.1", "minimatch": "^3.0.4", "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" } }, - "@ethereumjs/common": { - "version": "2.6.5", - "resolved": "https://registry.npmjs.org/@ethereumjs/common/-/common-2.6.5.tgz", - "integrity": "sha512-lRyVQOeCDaIVtgfbowla32pzeDv2Obr8oR8Put5RdUBNRGr1VGPGQNGP6elWIpgK3YdpzqTOh4GyUGOureVeeA==", + "node_modules/@ethereumjs/common": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@ethereumjs/common/-/common-2.5.0.tgz", + "integrity": "sha512-DEHjW6e38o+JmB/NO3GZBpW4lpaiBpkFgXF6jLcJ6gETBYpEyaA5nTimsWBUJR3Vmtm/didUEbNjajskugZORg==", "dev": true, - "requires": { + "dependencies": { "crc-32": "^1.2.0", - "ethereumjs-util": "^7.1.5" + "ethereumjs-util": "^7.1.1" } }, - "@ethereumjs/tx": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@ethereumjs/tx/-/tx-3.5.2.tgz", - "integrity": "sha512-gQDNJWKrSDGu2w7w0PzVXVBNMzb7wwdDOmOqczmhNjqFxFuIbhVJDwiGEnxFNC2/b8ifcZzY7MLcluizohRzNw==", + "node_modules/@ethereumjs/tx": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/@ethereumjs/tx/-/tx-3.3.2.tgz", + "integrity": "sha512-6AaJhwg4ucmwTvw/1qLaZUX5miWrwZ4nLOUsKyb/HtzS3BMw/CasKhdi1ims9mBKeK9sOJCH4qGKOBGyJCeeog==", "dev": true, - "requires": { - "@ethereumjs/common": "^2.6.4", - "ethereumjs-util": "^7.1.5" + "dependencies": { + "@ethereumjs/common": "^2.5.0", + "ethereumjs-util": "^7.1.2" } }, - "@ethersproject/abi": { + "node_modules/@ethersproject/abi": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.7.0.tgz", "integrity": "sha512-351ktp42TiRcYB3H1OP8yajPeAQstMW/yCFokj/AthP9bLHzQFPlOrxOcwYEDkUAICmOHljvN4K39OMTMUa9RA==", "dev": true, - "requires": { + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { "@ethersproject/address": "^5.7.0", "@ethersproject/bignumber": "^5.7.0", "@ethersproject/bytes": "^5.7.0", @@ -241,12 +331,22 @@ "@ethersproject/strings": "^5.7.0" } }, - "@ethersproject/abstract-provider": { + "node_modules/@ethersproject/abstract-provider": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.7.0.tgz", "integrity": "sha512-R41c9UkchKCpAqStMYUpdunjo3pkEvZC3FAwZn5S5MGbXoMQOHIdHItezTETxAO5bevtMApSyEhn9+CHcDsWBw==", "dev": true, - "requires": { + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { "@ethersproject/bignumber": "^5.7.0", "@ethersproject/bytes": "^5.7.0", "@ethersproject/logger": "^5.7.0", @@ -256,12 +356,22 @@ "@ethersproject/web": "^5.7.0" } }, - "@ethersproject/abstract-signer": { + "node_modules/@ethersproject/abstract-signer": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/abstract-signer/-/abstract-signer-5.7.0.tgz", "integrity": "sha512-a16V8bq1/Cz+TGCkE2OPMTOUDLS3grCpdjoJCYNnVBbdYEMSgKrU0+B90s8b6H+ByYTBZN7a3g76jdIJi7UfKQ==", "dev": true, - "requires": { + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { "@ethersproject/abstract-provider": "^5.7.0", "@ethersproject/bignumber": "^5.7.0", "@ethersproject/bytes": "^5.7.0", @@ -269,12 +379,22 @@ "@ethersproject/properties": "^5.7.0" } }, - "@ethersproject/address": { + "node_modules/@ethersproject/address": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/address/-/address-5.7.0.tgz", "integrity": "sha512-9wYhYt7aghVGo758POM5nqcOMaE168Q6aRLJZwUmiqSrAungkG74gSSeKEIR7ukixesdRZGPgVqme6vmxs1fkA==", "dev": true, - "requires": { + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { "@ethersproject/bignumber": "^5.7.0", "@ethersproject/bytes": "^5.7.0", "@ethersproject/keccak256": "^5.7.0", @@ -282,68 +402,126 @@ "@ethersproject/rlp": "^5.7.0" } }, - "@ethersproject/base64": { + "node_modules/@ethersproject/base64": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/base64/-/base64-5.7.0.tgz", "integrity": "sha512-Dr8tcHt2mEbsZr/mwTPIQAf3Ai0Bks/7gTw9dSqk1mQvhW3XvRlmDJr/4n+wg1JmCl16NZue17CDh8xb/vZ0sQ==", "dev": true, - "requires": { + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { "@ethersproject/bytes": "^5.7.0" } }, - "@ethersproject/basex": { + "node_modules/@ethersproject/basex": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/basex/-/basex-5.7.0.tgz", "integrity": "sha512-ywlh43GwZLv2Voc2gQVTKBoVQ1mti3d8HK5aMxsfu/nRDnMmNqaSJ3r3n85HBByT8OpoY96SXM1FogC533T4zw==", "dev": true, - "requires": { + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { "@ethersproject/bytes": "^5.7.0", "@ethersproject/properties": "^5.7.0" } }, - "@ethersproject/bignumber": { + "node_modules/@ethersproject/bignumber": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.7.0.tgz", "integrity": "sha512-n1CAdIHRWjSucQO3MC1zPSVgV/6dy/fjL9pMrPP9peL+QxEg9wOsVqwD4+818B6LUEtaXzVHQiuivzRoxPxUGw==", "dev": true, - "requires": { + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { "@ethersproject/bytes": "^5.7.0", "@ethersproject/logger": "^5.7.0", "bn.js": "^5.2.1" - }, - "dependencies": { - "bn.js": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", - "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", - "dev": true - } } }, - "@ethersproject/bytes": { + "node_modules/@ethersproject/bignumber/node_modules/bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", + "dev": true + }, + "node_modules/@ethersproject/bytes": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.7.0.tgz", "integrity": "sha512-nsbxwgFXWh9NyYWo+U8atvmMsSdKJprTcICAkvbBffT75qDocbuggBU0SJiVK2MuTrp0q+xvLkTnGMPK1+uA9A==", "dev": true, - "requires": { + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { "@ethersproject/logger": "^5.7.0" } }, - "@ethersproject/constants": { + "node_modules/@ethersproject/constants": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.7.0.tgz", "integrity": "sha512-DHI+y5dBNvkpYUMiRQyxRBYBefZkJfo70VUkUAsRjcPs47muV9evftfZ0PJVCXYbAiCgght0DtcF9srFQmIgWA==", "dev": true, - "requires": { + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { "@ethersproject/bignumber": "^5.7.0" } }, - "@ethersproject/contracts": { + "node_modules/@ethersproject/contracts": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/contracts/-/contracts-5.7.0.tgz", "integrity": "sha512-5GJbzEU3X+d33CdfPhcyS+z8MzsTrBGk/sc+G+59+tPa9yFkl6HQ9D6L0QMgNTA9q8dT0XKxxkyp883XsQvbbg==", "dev": true, - "requires": { + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { "@ethersproject/abi": "^5.7.0", "@ethersproject/abstract-provider": "^5.7.0", "@ethersproject/abstract-signer": "^5.7.0", @@ -356,12 +534,22 @@ "@ethersproject/transactions": "^5.7.0" } }, - "@ethersproject/hash": { + "node_modules/@ethersproject/hash": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.7.0.tgz", "integrity": "sha512-qX5WrQfnah1EFnO5zJv1v46a8HW0+E5xuBBDTwMFZLuVTx0tbU2kkx15NqdjxecrLGatQN9FGQKpb1FKdHCt+g==", "dev": true, - "requires": { + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { "@ethersproject/abstract-signer": "^5.7.0", "@ethersproject/address": "^5.7.0", "@ethersproject/base64": "^5.7.0", @@ -373,12 +561,22 @@ "@ethersproject/strings": "^5.7.0" } }, - "@ethersproject/hdnode": { + "node_modules/@ethersproject/hdnode": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/hdnode/-/hdnode-5.7.0.tgz", "integrity": "sha512-OmyYo9EENBPPf4ERhR7oj6uAtUAhYGqOnIS+jE5pTXvdKBS99ikzq1E7Iv0ZQZ5V36Lqx1qZLeak0Ra16qpeOg==", "dev": true, - "requires": { + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { "@ethersproject/abstract-signer": "^5.7.0", "@ethersproject/basex": "^5.7.0", "@ethersproject/bignumber": "^5.7.0", @@ -393,12 +591,22 @@ "@ethersproject/wordlists": "^5.7.0" } }, - "@ethersproject/json-wallets": { + "node_modules/@ethersproject/json-wallets": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/json-wallets/-/json-wallets-5.7.0.tgz", "integrity": "sha512-8oee5Xgu6+RKgJTkvEMl2wDgSPSAQ9MB/3JYjFV9jlKvcYHUXZC+cQp0njgmxdHkYWn8s6/IqIZYm0YWCjO/0g==", "dev": true, - "requires": { + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { "@ethersproject/abstract-signer": "^5.7.0", "@ethersproject/address": "^5.7.0", "@ethersproject/bytes": "^5.7.0", @@ -412,66 +620,124 @@ "@ethersproject/transactions": "^5.7.0", "aes-js": "3.0.0", "scrypt-js": "3.0.1" - }, - "dependencies": { - "aes-js": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.0.0.tgz", - "integrity": "sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw==", - "dev": true - } } }, - "@ethersproject/keccak256": { + "node_modules/@ethersproject/json-wallets/node_modules/aes-js": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.0.0.tgz", + "integrity": "sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw==", + "dev": true + }, + "node_modules/@ethersproject/keccak256": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.7.0.tgz", "integrity": "sha512-2UcPboeL/iW+pSg6vZ6ydF8tCnv3Iu/8tUmLLzWWGzxWKFFqOBQFLo6uLUv6BDrLgCDfN28RJ/wtByx+jZ4KBg==", "dev": true, - "requires": { + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { "@ethersproject/bytes": "^5.7.0", "js-sha3": "0.8.0" } }, - "@ethersproject/logger": { + "node_modules/@ethersproject/logger": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.7.0.tgz", "integrity": "sha512-0odtFdXu/XHtjQXJYA3u9G0G8btm0ND5Cu8M7i5vhEcE8/HmF4Lbdqanwyv4uQTr2tx6b7fQRmgLrsnpQlmnig==", - "dev": true + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ] }, - "@ethersproject/networks": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.7.0.tgz", - "integrity": "sha512-MG6oHSQHd4ebvJrleEQQ4HhVu8Ichr0RDYEfHzsVAVjHNM+w36x9wp9r+hf1JstMXtseXDtkiVoARAG6M959AA==", + "node_modules/@ethersproject/networks": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.7.1.tgz", + "integrity": "sha512-n/MufjFYv3yFcUyfhnXotyDlNdFb7onmkSy8aQERi2PjNcnWQ66xXxa3XlS8nCcA8aJKJjIIMNJTC7tu80GwpQ==", "dev": true, - "requires": { + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { "@ethersproject/logger": "^5.7.0" } }, - "@ethersproject/pbkdf2": { + "node_modules/@ethersproject/pbkdf2": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/pbkdf2/-/pbkdf2-5.7.0.tgz", "integrity": "sha512-oR/dBRZR6GTyaofd86DehG72hY6NpAjhabkhxgr3X2FpJtJuodEl2auADWBZfhDHgVCbu3/H/Ocq2uC6dpNjjw==", "dev": true, - "requires": { + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { "@ethersproject/bytes": "^5.7.0", "@ethersproject/sha2": "^5.7.0" } }, - "@ethersproject/properties": { + "node_modules/@ethersproject/properties": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.7.0.tgz", "integrity": "sha512-J87jy8suntrAkIZtecpxEPxY//szqr1mlBaYlQ0r4RCaiD2hjheqF9s1LVE8vVuJCXisjIP+JgtK/Do54ej4Sw==", "dev": true, - "requires": { + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { "@ethersproject/logger": "^5.7.0" } }, - "@ethersproject/providers": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/providers/-/providers-5.7.0.tgz", - "integrity": "sha512-+TTrrINMzZ0aXtlwO/95uhAggKm4USLm1PbeCBR/3XZ7+Oey+3pMyddzZEyRhizHpy1HXV0FRWRMI1O3EGYibA==", + "node_modules/@ethersproject/providers": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/@ethersproject/providers/-/providers-5.7.2.tgz", + "integrity": "sha512-g34EWZ1WWAVgr4aptGlVBF8mhl3VWjv+8hoAnzStu8Ah22VHBsuGzP17eb6xDVRzw895G4W7vvx60lFFur/1Rg==", "dev": true, - "requires": { + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { "@ethersproject/abstract-provider": "^5.7.0", "@ethersproject/abstract-signer": "^5.7.0", "@ethersproject/address": "^5.7.0", @@ -492,75 +758,136 @@ "@ethersproject/web": "^5.7.0", "bech32": "1.1.4", "ws": "7.4.6" + } + }, + "node_modules/@ethersproject/providers/node_modules/ws": { + "version": "7.4.6", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", + "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", + "dev": true, + "engines": { + "node": ">=8.3.0" }, - "dependencies": { - "ws": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", - "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", - "dev": true + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true } } }, - "@ethersproject/random": { + "node_modules/@ethersproject/random": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/random/-/random-5.7.0.tgz", "integrity": "sha512-19WjScqRA8IIeWclFme75VMXSBvi4e6InrUNuaR4s5pTF2qNhcGdCUwdxUVGtDDqC00sDLCO93jPQoDUH4HVmQ==", "dev": true, - "requires": { + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { "@ethersproject/bytes": "^5.7.0", "@ethersproject/logger": "^5.7.0" } }, - "@ethersproject/rlp": { + "node_modules/@ethersproject/rlp": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/rlp/-/rlp-5.7.0.tgz", "integrity": "sha512-rBxzX2vK8mVF7b0Tol44t5Tb8gomOHkj5guL+HhzQ1yBh/ydjGnpw6at+X6Iw0Kp3OzzzkcKp8N9r0W4kYSs9w==", "dev": true, - "requires": { + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { "@ethersproject/bytes": "^5.7.0", "@ethersproject/logger": "^5.7.0" } }, - "@ethersproject/sha2": { + "node_modules/@ethersproject/sha2": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/sha2/-/sha2-5.7.0.tgz", "integrity": "sha512-gKlH42riwb3KYp0reLsFTokByAKoJdgFCwI+CCiX/k+Jm2mbNs6oOaCjYQSlI1+XBVejwH2KrmCbMAT/GnRDQw==", "dev": true, - "requires": { + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { "@ethersproject/bytes": "^5.7.0", "@ethersproject/logger": "^5.7.0", "hash.js": "1.1.7" } }, - "@ethersproject/signing-key": { + "node_modules/@ethersproject/signing-key": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.7.0.tgz", "integrity": "sha512-MZdy2nL3wO0u7gkB4nA/pEf8lu1TlFswPNmy8AiYkfKTdO6eXBJyUdmHO/ehm/htHw9K/qF8ujnTyUAD+Ry54Q==", "dev": true, - "requires": { + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { "@ethersproject/bytes": "^5.7.0", "@ethersproject/logger": "^5.7.0", "@ethersproject/properties": "^5.7.0", "bn.js": "^5.2.1", "elliptic": "6.5.4", "hash.js": "1.1.7" - }, - "dependencies": { - "bn.js": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", - "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", - "dev": true - } } }, - "@ethersproject/solidity": { + "node_modules/@ethersproject/signing-key/node_modules/bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", + "dev": true + }, + "node_modules/@ethersproject/solidity": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/solidity/-/solidity-5.7.0.tgz", "integrity": "sha512-HmabMd2Dt/raavyaGukF4XxizWKhKQ24DoLtdNbBmNKUOPqwjsKQSdV9GQtj9CBEea9DlzETlVER1gYeXXBGaA==", "dev": true, - "requires": { + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { "@ethersproject/bignumber": "^5.7.0", "@ethersproject/bytes": "^5.7.0", "@ethersproject/keccak256": "^5.7.0", @@ -569,23 +896,43 @@ "@ethersproject/strings": "^5.7.0" } }, - "@ethersproject/strings": { + "node_modules/@ethersproject/strings": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.7.0.tgz", "integrity": "sha512-/9nu+lj0YswRNSH0NXYqrh8775XNyEdUQAuf3f+SmOrnVewcJ5SBNAjF7lpgehKi4abvNNXyf+HX86czCdJ8Mg==", "dev": true, - "requires": { + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { "@ethersproject/bytes": "^5.7.0", "@ethersproject/constants": "^5.7.0", "@ethersproject/logger": "^5.7.0" } }, - "@ethersproject/transactions": { + "node_modules/@ethersproject/transactions": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/transactions/-/transactions-5.7.0.tgz", "integrity": "sha512-kmcNicCp1lp8qanMTC3RIikGgoJ80ztTyvtsFvCYpSCfkjhD0jZ2LOrnbcuxuToLIUYYf+4XwD1rP+B/erDIhQ==", "dev": true, - "requires": { + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { "@ethersproject/address": "^5.7.0", "@ethersproject/bignumber": "^5.7.0", "@ethersproject/bytes": "^5.7.0", @@ -597,23 +944,43 @@ "@ethersproject/signing-key": "^5.7.0" } }, - "@ethersproject/units": { + "node_modules/@ethersproject/units": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/units/-/units-5.7.0.tgz", "integrity": "sha512-pD3xLMy3SJu9kG5xDGI7+xhTEmGXlEqXU4OfNapmfnxLVY4EMSSRp7j1k7eezutBPH7RBN/7QPnwR7hzNlEFeg==", "dev": true, - "requires": { + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { "@ethersproject/bignumber": "^5.7.0", "@ethersproject/constants": "^5.7.0", "@ethersproject/logger": "^5.7.0" } }, - "@ethersproject/wallet": { + "node_modules/@ethersproject/wallet": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/wallet/-/wallet-5.7.0.tgz", "integrity": "sha512-MhmXlJXEJFBFVKrDLB4ZdDzxcBxQ3rLyCkhNqVu3CDYvR97E+8r01UgrI+TI99Le+aYm/in/0vp86guJuM7FCA==", "dev": true, - "requires": { + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { "@ethersproject/abstract-provider": "^5.7.0", "@ethersproject/abstract-signer": "^5.7.0", "@ethersproject/address": "^5.7.0", @@ -631,12 +998,22 @@ "@ethersproject/wordlists": "^5.7.0" } }, - "@ethersproject/web": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/web/-/web-5.7.0.tgz", - "integrity": "sha512-ApHcbbj+muRASVDSCl/tgxaH2LBkRMEYfLOLVa0COipx0+nlu0QKet7U2lEg0vdkh8XRSLf2nd1f1Uk9SrVSGA==", + "node_modules/@ethersproject/web": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/@ethersproject/web/-/web-5.7.1.tgz", + "integrity": "sha512-Gueu8lSvyjBWL4cYsWsjh6MtMwM0+H4HvqFPZfB6dV8ctbP9zFAO73VG1cMWae0FLPCtz0peKPpZY8/ugJJX2w==", "dev": true, - "requires": { + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { "@ethersproject/base64": "^5.7.0", "@ethersproject/bytes": "^5.7.0", "@ethersproject/logger": "^5.7.0", @@ -644,12 +1021,22 @@ "@ethersproject/strings": "^5.7.0" } }, - "@ethersproject/wordlists": { + "node_modules/@ethersproject/wordlists": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/wordlists/-/wordlists-5.7.0.tgz", "integrity": "sha512-S2TFNJNfHWVHNE6cNDjbVlZ6MgE17MIxMbMg2zv3wn+3XSJGosL1m9ZVv3GXCf/2ymSsQ+hRI5IzoMJTG6aoVA==", "dev": true, - "requires": { + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { "@ethersproject/bytes": "^5.7.0", "@ethersproject/hash": "^5.7.0", "@ethersproject/logger": "^5.7.0", @@ -657,126 +1044,158 @@ "@ethersproject/strings": "^5.7.0" } }, - "@frangio/servbot": { + "node_modules/@frangio/servbot": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/@frangio/servbot/-/servbot-0.2.5.tgz", "integrity": "sha512-ogja4iAPZ1VwM5MU3C1ZhB88358F0PGbmSTGOkIZwOyLaDoMHIqOVCnavHjR7DV5h+oAI4Z4KDqlam3myQUrmg==", - "dev": true + "dev": true, + "engines": { + "node": ">=12.x", + "pnpm": "7.5.1" + } }, - "@humanwhocodes/config-array": { + "node_modules/@humanwhocodes/config-array": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", "dev": true, - "requires": { + "dependencies": { "@humanwhocodes/object-schema": "^1.2.0", "debug": "^4.1.1", "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=10.10.0" } }, - "@humanwhocodes/object-schema": { + "node_modules/@humanwhocodes/object-schema": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, - "@metamask/eth-sig-util": { + "node_modules/@metamask/eth-sig-util": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@metamask/eth-sig-util/-/eth-sig-util-4.0.1.tgz", "integrity": "sha512-tghyZKLHZjcdlDqCA3gNZmLeR0XvOE9U1qoQO9ohyAZT6Pya+H9vkBPcsyXytmYLNgVoin7CKCmweo/R43V+tQ==", "dev": true, - "requires": { + "dependencies": { "ethereumjs-abi": "^0.6.8", "ethereumjs-util": "^6.2.1", "ethjs-util": "^0.1.6", "tweetnacl": "^1.0.3", "tweetnacl-util": "^0.15.1" }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@metamask/eth-sig-util/node_modules/@types/bn.js": { + "version": "4.11.6", + "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-4.11.6.tgz", + "integrity": "sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg==", + "dev": true, "dependencies": { - "@types/bn.js": { - "version": "4.11.6", - "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-4.11.6.tgz", - "integrity": "sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "ethereumjs-util": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-6.2.1.tgz", - "integrity": "sha512-W2Ktez4L01Vexijrm5EB6w7dg4n/TgpoYU4avuT5T3Vmnw/eCRtiBrJfQYS/DCSvDIOLn2k57GcHdeBcgVxAqw==", - "dev": true, - "requires": { - "@types/bn.js": "^4.11.3", - "bn.js": "^4.11.0", - "create-hash": "^1.1.2", - "elliptic": "^6.5.2", - "ethereum-cryptography": "^0.1.3", - "ethjs-util": "0.1.6", - "rlp": "^2.2.3" - } - } + "@types/node": "*" } }, - "@noble/hashes": { + "node_modules/@metamask/eth-sig-util/node_modules/ethereumjs-util": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-6.2.1.tgz", + "integrity": "sha512-W2Ktez4L01Vexijrm5EB6w7dg4n/TgpoYU4avuT5T3Vmnw/eCRtiBrJfQYS/DCSvDIOLn2k57GcHdeBcgVxAqw==", + "dev": true, + "dependencies": { + "@types/bn.js": "^4.11.3", + "bn.js": "^4.11.0", + "create-hash": "^1.1.2", + "elliptic": "^6.5.2", + "ethereum-cryptography": "^0.1.3", + "ethjs-util": "0.1.6", + "rlp": "^2.2.3" + } + }, + "node_modules/@noble/hashes": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.1.2.tgz", "integrity": "sha512-KYRCASVTv6aeUi1tsF8/vpyR7zpfs3FUzy2Jqm+MU+LmUKhQ0y2FpfwqkCcxSg2ua4GALJd8k2R76WxwZGbQpA==", - "dev": true + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ] }, - "@noble/secp256k1": { + "node_modules/@noble/secp256k1": { "version": "1.6.3", "resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-1.6.3.tgz", "integrity": "sha512-T04e4iTurVy7I8Sw4+c5OSN9/RkPlo1uKxAomtxQNLq8j1uPAqnsqG1bqvY3Jv7c13gyr6dui0zmh/I3+f/JaQ==", - "dev": true + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ] }, - "@nodelib/fs.scandir": { + "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "dev": true, - "requires": { + "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" } }, - "@nodelib/fs.stat": { + "node_modules/@nodelib/fs.stat": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true + "dev": true, + "engines": { + "node": ">= 8" + } }, - "@nodelib/fs.walk": { + "node_modules/@nodelib/fs.walk": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "dev": true, - "requires": { + "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" } }, - "@nomicfoundation/ethereumjs-block": { + "node_modules/@nomicfoundation/ethereumjs-block": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-block/-/ethereumjs-block-4.0.0.tgz", "integrity": "sha512-bk8uP8VuexLgyIZAHExH1QEovqx0Lzhc9Ntm63nCRKLHXIZkobaFaeCVwTESV7YkPKUk7NiK11s8ryed4CS9yA==", "dev": true, - "requires": { + "dependencies": { "@nomicfoundation/ethereumjs-common": "^3.0.0", "@nomicfoundation/ethereumjs-rlp": "^4.0.0", "@nomicfoundation/ethereumjs-trie": "^5.0.0", "@nomicfoundation/ethereumjs-tx": "^4.0.0", "@nomicfoundation/ethereumjs-util": "^8.0.0", "ethereum-cryptography": "0.1.3" + }, + "engines": { + "node": ">=14" } }, - "@nomicfoundation/ethereumjs-blockchain": { + "node_modules/@nomicfoundation/ethereumjs-blockchain": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-blockchain/-/ethereumjs-blockchain-6.0.0.tgz", "integrity": "sha512-pLFEoea6MWd81QQYSReLlLfH7N9v7lH66JC/NMPN848ySPPQA5renWnE7wPByfQFzNrPBuDDRFFULMDmj1C0xw==", "dev": true, - "requires": { + "dependencies": { "@nomicfoundation/ethereumjs-block": "^4.0.0", "@nomicfoundation/ethereumjs-common": "^3.0.0", "@nomicfoundation/ethereumjs-ethash": "^2.0.0", @@ -789,38 +1208,44 @@ "level": "^8.0.0", "lru-cache": "^5.1.1", "memory-level": "^1.0.0" + }, + "engines": { + "node": ">=14" } }, - "@nomicfoundation/ethereumjs-common": { + "node_modules/@nomicfoundation/ethereumjs-common": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-common/-/ethereumjs-common-3.0.0.tgz", "integrity": "sha512-WS7qSshQfxoZOpHG/XqlHEGRG1zmyjYrvmATvc4c62+gZXgre1ymYP8ZNgx/3FyZY0TWe9OjFlKOfLqmgOeYwA==", "dev": true, - "requires": { + "dependencies": { "@nomicfoundation/ethereumjs-util": "^8.0.0", "crc-32": "^1.2.0" } }, - "@nomicfoundation/ethereumjs-ethash": { + "node_modules/@nomicfoundation/ethereumjs-ethash": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-ethash/-/ethereumjs-ethash-2.0.0.tgz", "integrity": "sha512-WpDvnRncfDUuXdsAXlI4lXbqUDOA+adYRQaEezIkxqDkc+LDyYDbd/xairmY98GnQzo1zIqsIL6GB5MoMSJDew==", "dev": true, - "requires": { + "dependencies": { "@nomicfoundation/ethereumjs-block": "^4.0.0", "@nomicfoundation/ethereumjs-rlp": "^4.0.0", "@nomicfoundation/ethereumjs-util": "^8.0.0", "abstract-level": "^1.0.3", "bigint-crypto-utils": "^3.0.23", "ethereum-cryptography": "0.1.3" + }, + "engines": { + "node": ">=14" } }, - "@nomicfoundation/ethereumjs-evm": { + "node_modules/@nomicfoundation/ethereumjs-evm": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-evm/-/ethereumjs-evm-1.0.0.tgz", "integrity": "sha512-hVS6qRo3V1PLKCO210UfcEQHvlG7GqR8iFzp0yyjTg2TmJQizcChKgWo8KFsdMw6AyoLgLhHGHw4HdlP8a4i+Q==", "dev": true, - "requires": { + "dependencies": { "@nomicfoundation/ethereumjs-common": "^3.0.0", "@nomicfoundation/ethereumjs-util": "^8.0.0", "@types/async-eventemitter": "^0.2.1", @@ -829,20 +1254,29 @@ "ethereum-cryptography": "0.1.3", "mcl-wasm": "^0.7.1", "rustbn.js": "~0.2.0" + }, + "engines": { + "node": ">=14" } }, - "@nomicfoundation/ethereumjs-rlp": { + "node_modules/@nomicfoundation/ethereumjs-rlp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-rlp/-/ethereumjs-rlp-4.0.0.tgz", "integrity": "sha512-GaSOGk5QbUk4eBP5qFbpXoZoZUj/NrW7MRa0tKY4Ew4c2HAS0GXArEMAamtFrkazp0BO4K5p2ZCG3b2FmbShmw==", - "dev": true + "dev": true, + "bin": { + "rlp": "bin/rlp" + }, + "engines": { + "node": ">=14" + } }, - "@nomicfoundation/ethereumjs-statemanager": { + "node_modules/@nomicfoundation/ethereumjs-statemanager": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-statemanager/-/ethereumjs-statemanager-1.0.0.tgz", "integrity": "sha512-jCtqFjcd2QejtuAMjQzbil/4NHf5aAWxUc+CvS0JclQpl+7M0bxMofR2AJdtz+P3u0ke2euhYREDiE7iSO31vQ==", "dev": true, - "requires": { + "dependencies": { "@nomicfoundation/ethereumjs-common": "^3.0.0", "@nomicfoundation/ethereumjs-rlp": "^4.0.0", "@nomicfoundation/ethereumjs-trie": "^5.0.0", @@ -852,46 +1286,55 @@ "functional-red-black-tree": "^1.0.1" } }, - "@nomicfoundation/ethereumjs-trie": { + "node_modules/@nomicfoundation/ethereumjs-trie": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-trie/-/ethereumjs-trie-5.0.0.tgz", "integrity": "sha512-LIj5XdE+s+t6WSuq/ttegJzZ1vliwg6wlb+Y9f4RlBpuK35B9K02bO7xU+E6Rgg9RGptkWd6TVLdedTI4eNc2A==", "dev": true, - "requires": { + "dependencies": { "@nomicfoundation/ethereumjs-rlp": "^4.0.0", "@nomicfoundation/ethereumjs-util": "^8.0.0", "ethereum-cryptography": "0.1.3", "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=14" } }, - "@nomicfoundation/ethereumjs-tx": { + "node_modules/@nomicfoundation/ethereumjs-tx": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-tx/-/ethereumjs-tx-4.0.0.tgz", "integrity": "sha512-Gg3Lir2lNUck43Kp/3x6TfBNwcWC9Z1wYue9Nz3v4xjdcv6oDW9QSMJxqsKw9QEGoBBZ+gqwpW7+F05/rs/g1w==", "dev": true, - "requires": { + "dependencies": { "@nomicfoundation/ethereumjs-common": "^3.0.0", "@nomicfoundation/ethereumjs-rlp": "^4.0.0", "@nomicfoundation/ethereumjs-util": "^8.0.0", "ethereum-cryptography": "0.1.3" + }, + "engines": { + "node": ">=14" } }, - "@nomicfoundation/ethereumjs-util": { + "node_modules/@nomicfoundation/ethereumjs-util": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-util/-/ethereumjs-util-8.0.0.tgz", "integrity": "sha512-2emi0NJ/HmTG+CGY58fa+DQuAoroFeSH9gKu9O6JnwTtlzJtgfTixuoOqLEgyyzZVvwfIpRueuePb8TonL1y+A==", "dev": true, - "requires": { + "dependencies": { "@nomicfoundation/ethereumjs-rlp": "^4.0.0-beta.2", "ethereum-cryptography": "0.1.3" + }, + "engines": { + "node": ">=14" } }, - "@nomicfoundation/ethereumjs-vm": { + "node_modules/@nomicfoundation/ethereumjs-vm": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-vm/-/ethereumjs-vm-6.0.0.tgz", "integrity": "sha512-JMPxvPQ3fzD063Sg3Tp+UdwUkVxMoo1uML6KSzFhMH3hoQi/LMuXBoEHAoW83/vyNS9BxEe6jm6LmT5xdeEJ6w==", "dev": true, - "requires": { + "dependencies": { "@nomicfoundation/ethereumjs-block": "^4.0.0", "@nomicfoundation/ethereumjs-blockchain": "^6.0.0", "@nomicfoundation/ethereumjs-common": "^3.0.0", @@ -908,133 +1351,241 @@ "functional-red-black-tree": "^1.0.1", "mcl-wasm": "^0.7.1", "rustbn.js": "~0.2.0" + }, + "engines": { + "node": ">=14" } }, - "@nomicfoundation/hardhat-network-helpers": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-network-helpers/-/hardhat-network-helpers-1.0.6.tgz", - "integrity": "sha512-a35iVD4ycF6AoTfllAnKm96IPIzzHpgKX/ep4oKc2bsUKFfMlacWdyntgC/7d5blyCTXfFssgNAvXDZfzNWVGQ==", + "node_modules/@nomicfoundation/hardhat-network-helpers": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-network-helpers/-/hardhat-network-helpers-1.0.7.tgz", + "integrity": "sha512-X+3mNvn8B7BY5hpIaLO+TrfzWq12bpux+ajGGdmdcfC78NXmYmOZkAtiz1QZx1YIZGMS1LaXzPXyBExxKFpCaw==", "dev": true, - "requires": { + "dependencies": { "ethereumjs-util": "^7.1.4" + }, + "peerDependencies": { + "hardhat": "^2.9.5" } }, - "@nomicfoundation/solidity-analyzer": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer/-/solidity-analyzer-0.0.3.tgz", - "integrity": "sha512-VFMiOQvsw7nx5bFmrmVp2Q9rhIjw2AFST4DYvWVVO9PMHPE23BY2+kyfrQ4J3xCMFC8fcBbGLt7l4q7m1SlTqg==", + "node_modules/@nomicfoundation/solidity-analyzer": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer/-/solidity-analyzer-0.1.0.tgz", + "integrity": "sha512-xGWAiVCGOycvGiP/qrlf9f9eOn7fpNbyJygcB0P21a1MDuVPlKt0Srp7rvtBEutYQ48ouYnRXm33zlRnlTOPHg==", "dev": true, - "requires": { - "@nomicfoundation/solidity-analyzer-darwin-arm64": "0.0.3", - "@nomicfoundation/solidity-analyzer-darwin-x64": "0.0.3", - "@nomicfoundation/solidity-analyzer-freebsd-x64": "0.0.3", - "@nomicfoundation/solidity-analyzer-linux-arm64-gnu": "0.0.3", - "@nomicfoundation/solidity-analyzer-linux-arm64-musl": "0.0.3", - "@nomicfoundation/solidity-analyzer-linux-x64-gnu": "0.0.3", - "@nomicfoundation/solidity-analyzer-linux-x64-musl": "0.0.3", - "@nomicfoundation/solidity-analyzer-win32-arm64-msvc": "0.0.3", - "@nomicfoundation/solidity-analyzer-win32-ia32-msvc": "0.0.3", - "@nomicfoundation/solidity-analyzer-win32-x64-msvc": "0.0.3" + "engines": { + "node": ">= 12" + }, + "optionalDependencies": { + "@nomicfoundation/solidity-analyzer-darwin-arm64": "0.1.0", + "@nomicfoundation/solidity-analyzer-darwin-x64": "0.1.0", + "@nomicfoundation/solidity-analyzer-freebsd-x64": "0.1.0", + "@nomicfoundation/solidity-analyzer-linux-arm64-gnu": "0.1.0", + "@nomicfoundation/solidity-analyzer-linux-arm64-musl": "0.1.0", + "@nomicfoundation/solidity-analyzer-linux-x64-gnu": "0.1.0", + "@nomicfoundation/solidity-analyzer-linux-x64-musl": "0.1.0", + "@nomicfoundation/solidity-analyzer-win32-arm64-msvc": "0.1.0", + "@nomicfoundation/solidity-analyzer-win32-ia32-msvc": "0.1.0", + "@nomicfoundation/solidity-analyzer-win32-x64-msvc": "0.1.0" + } + }, + "node_modules/@nomicfoundation/solidity-analyzer-darwin-arm64": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-darwin-arm64/-/solidity-analyzer-darwin-arm64-0.1.0.tgz", + "integrity": "sha512-vEF3yKuuzfMHsZecHQcnkUrqm8mnTWfJeEVFHpg+cO+le96xQA4lAJYdUan8pXZohQxv1fSReQsn4QGNuBNuCw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" } }, - "@nomicfoundation/solidity-analyzer-darwin-arm64": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-darwin-arm64/-/solidity-analyzer-darwin-arm64-0.0.3.tgz", - "integrity": "sha512-W+bIiNiZmiy+MTYFZn3nwjyPUO6wfWJ0lnXx2zZrM8xExKObMrhCh50yy8pQING24mHfpPFCn89wEB/iG7vZDw==", + "node_modules/@nomicfoundation/solidity-analyzer-darwin-x64": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-darwin-x64/-/solidity-analyzer-darwin-x64-0.1.0.tgz", + "integrity": "sha512-dlHeIg0pTL4dB1l9JDwbi/JG6dHQaU1xpDK+ugYO8eJ1kxx9Dh2isEUtA4d02cQAl22cjOHTvifAk96A+ItEHA==", + "cpu": [ + "x64" + ], "dev": true, - "optional": true + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } }, - "@nomicfoundation/solidity-analyzer-darwin-x64": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-darwin-x64/-/solidity-analyzer-darwin-x64-0.0.3.tgz", - "integrity": "sha512-HuJd1K+2MgmFIYEpx46uzwEFjvzKAI765mmoMxy4K+Aqq1p+q7hHRlsFU2kx3NB8InwotkkIq3A5FLU1sI1WDw==", + "node_modules/@nomicfoundation/solidity-analyzer-freebsd-x64": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-freebsd-x64/-/solidity-analyzer-freebsd-x64-0.1.0.tgz", + "integrity": "sha512-WFCZYMv86WowDA4GiJKnebMQRt3kCcFqHeIomW6NMyqiKqhK1kIZCxSLDYsxqlx396kKLPN1713Q1S8tu68GKg==", + "cpu": [ + "x64" + ], "dev": true, - "optional": true + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } }, - "@nomicfoundation/solidity-analyzer-freebsd-x64": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-freebsd-x64/-/solidity-analyzer-freebsd-x64-0.0.3.tgz", - "integrity": "sha512-2cR8JNy23jZaO/vZrsAnWCsO73asU7ylrHIe0fEsXbZYqBP9sMr+/+xP3CELDHJxUbzBY8zqGvQt1ULpyrG+Kw==", + "node_modules/@nomicfoundation/solidity-analyzer-linux-arm64-gnu": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-arm64-gnu/-/solidity-analyzer-linux-arm64-gnu-0.1.0.tgz", + "integrity": "sha512-DTw6MNQWWlCgc71Pq7CEhEqkb7fZnS7oly13pujs4cMH1sR0JzNk90Mp1zpSCsCs4oKan2ClhMlLKtNat/XRKQ==", + "cpu": [ + "arm64" + ], "dev": true, - "optional": true + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } }, - "@nomicfoundation/solidity-analyzer-linux-arm64-gnu": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-arm64-gnu/-/solidity-analyzer-linux-arm64-gnu-0.0.3.tgz", - "integrity": "sha512-Eyv50EfYbFthoOb0I1568p+eqHGLwEUhYGOxcRNywtlTE9nj+c+MT1LA53HnxD9GsboH4YtOOmJOulrjG7KtbA==", + "node_modules/@nomicfoundation/solidity-analyzer-linux-arm64-musl": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-arm64-musl/-/solidity-analyzer-linux-arm64-musl-0.1.0.tgz", + "integrity": "sha512-wUpUnR/3GV5Da88MhrxXh/lhb9kxh9V3Jya2NpBEhKDIRCDmtXMSqPMXHZmOR9DfCwCvG6vLFPr/+YrPCnUN0w==", + "cpu": [ + "arm64" + ], "dev": true, - "optional": true - }, - "@nomicfoundation/solidity-analyzer-linux-arm64-musl": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-arm64-musl/-/solidity-analyzer-linux-arm64-musl-0.0.3.tgz", - "integrity": "sha512-V8grDqI+ivNrgwEt2HFdlwqV2/EQbYAdj3hbOvjrA8Qv+nq4h9jhQUxFpegYMDtpU8URJmNNlXgtfucSrAQwtQ==", - "dev": true, - "optional": true + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } }, - "@nomicfoundation/solidity-analyzer-linux-x64-gnu": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-x64-gnu/-/solidity-analyzer-linux-x64-gnu-0.0.3.tgz", - "integrity": "sha512-uRfVDlxtwT1vIy7MAExWAkRD4r9M79zMG7S09mCrWUn58DbLs7UFl+dZXBX0/8FTGYWHhOT/1Etw1ZpAf5DTrg==", + "node_modules/@nomicfoundation/solidity-analyzer-linux-x64-gnu": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-x64-gnu/-/solidity-analyzer-linux-x64-gnu-0.1.0.tgz", + "integrity": "sha512-lR0AxK1x/MeKQ/3Pt923kPvwigmGX3OxeU5qNtQ9pj9iucgk4PzhbS3ruUeSpYhUxG50jN4RkIGwUMoev5lguw==", + "cpu": [ + "x64" + ], "dev": true, - "optional": true + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } }, - "@nomicfoundation/solidity-analyzer-linux-x64-musl": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-x64-musl/-/solidity-analyzer-linux-x64-musl-0.0.3.tgz", - "integrity": "sha512-8HPwYdLbhcPpSwsE0yiU/aZkXV43vlXT2ycH+XlOjWOnLfH8C41z0njK8DHRtEFnp4OVN6E7E5lHBBKDZXCliA==", + "node_modules/@nomicfoundation/solidity-analyzer-linux-x64-musl": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-x64-musl/-/solidity-analyzer-linux-x64-musl-0.1.0.tgz", + "integrity": "sha512-A1he/8gy/JeBD3FKvmI6WUJrGrI5uWJNr5Xb9WdV+DK0F8msuOqpEByLlnTdLkXMwW7nSl3awvLezOs9xBHJEg==", + "cpu": [ + "x64" + ], "dev": true, - "optional": true + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } }, - "@nomicfoundation/solidity-analyzer-win32-arm64-msvc": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-win32-arm64-msvc/-/solidity-analyzer-win32-arm64-msvc-0.0.3.tgz", - "integrity": "sha512-5WWcT6ZNvfCuxjlpZOY7tdvOqT1kIQYlDF9Q42wMpZ5aTm4PvjdCmFDDmmTvyXEBJ4WTVmY5dWNWaxy8h/E28g==", + "node_modules/@nomicfoundation/solidity-analyzer-win32-arm64-msvc": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-win32-arm64-msvc/-/solidity-analyzer-win32-arm64-msvc-0.1.0.tgz", + "integrity": "sha512-7x5SXZ9R9H4SluJZZP8XPN+ju7Mx+XeUMWZw7ZAqkdhP5mK19I4vz3x0zIWygmfE8RT7uQ5xMap0/9NPsO+ykw==", + "cpu": [ + "arm64" + ], "dev": true, - "optional": true + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } }, - "@nomicfoundation/solidity-analyzer-win32-ia32-msvc": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-win32-ia32-msvc/-/solidity-analyzer-win32-ia32-msvc-0.0.3.tgz", - "integrity": "sha512-P/LWGZwWkyjSwkzq6skvS2wRc3gabzAbk6Akqs1/Iiuggql2CqdLBkcYWL5Xfv3haynhL+2jlNkak+v2BTZI4A==", + "node_modules/@nomicfoundation/solidity-analyzer-win32-ia32-msvc": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-win32-ia32-msvc/-/solidity-analyzer-win32-ia32-msvc-0.1.0.tgz", + "integrity": "sha512-m7w3xf+hnE774YRXu+2mGV7RiF3QJtUoiYU61FascCkQhX3QMQavh7saH/vzb2jN5D24nT/jwvaHYX/MAM9zUw==", + "cpu": [ + "ia32" + ], "dev": true, - "optional": true + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } }, - "@nomicfoundation/solidity-analyzer-win32-x64-msvc": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-win32-x64-msvc/-/solidity-analyzer-win32-x64-msvc-0.0.3.tgz", - "integrity": "sha512-4AcTtLZG1s/S5mYAIr/sdzywdNwJpOcdStGF3QMBzEt+cGn3MchMaS9b1gyhb2KKM2c39SmPF5fUuWq1oBSQZQ==", + "node_modules/@nomicfoundation/solidity-analyzer-win32-x64-msvc": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-win32-x64-msvc/-/solidity-analyzer-win32-x64-msvc-0.1.0.tgz", + "integrity": "sha512-xCuybjY0sLJQnJhupiFAXaek2EqF0AP0eBjgzaalPXSNvCEN6ZYHvUzdA50ENDVeSYFXcUsYf3+FsD3XKaeptA==", + "cpu": [ + "x64" + ], "dev": true, - "optional": true + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } }, - "@nomiclabs/hardhat-truffle5": { + "node_modules/@nomiclabs/hardhat-truffle5": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/@nomiclabs/hardhat-truffle5/-/hardhat-truffle5-2.0.7.tgz", "integrity": "sha512-Pw8451IUZp1bTp0QqCHCYfCHs66sCnyxPcaorapu9mfOV9xnZsVaFdtutnhNEiXdiZwbed7LFKpRsde4BjFwig==", "dev": true, - "requires": { + "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" + }, + "peerDependencies": { + "@nomiclabs/hardhat-web3": "^2.0.0", + "hardhat": "^2.6.4", + "web3": "^1.0.0-beta.36" } }, - "@nomiclabs/hardhat-web3": { + "node_modules/@nomiclabs/hardhat-web3": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@nomiclabs/hardhat-web3/-/hardhat-web3-2.0.0.tgz", "integrity": "sha512-zt4xN+D+fKl3wW2YlTX3k9APR3XZgPkxJYf36AcliJn3oujnKEVRZaHu0PhgLjO+gR+F/kiYayo9fgd2L8970Q==", "dev": true, - "requires": { + "dependencies": { "@types/bignumber.js": "^5.0.0" + }, + "peerDependencies": { + "hardhat": "^2.0.0", + "web3": "^1.0.0-beta.36" } }, - "@nomiclabs/truffle-contract": { + "node_modules/@nomiclabs/truffle-contract": { "version": "4.5.10", "resolved": "https://registry.npmjs.org/@nomiclabs/truffle-contract/-/truffle-contract-4.5.10.tgz", "integrity": "sha512-nF/6InFV+0hUvutyFgsdOMCoYlr//2fJbRER4itxYtQtc4/O1biTwZIKRu+5l2J5Sq6LU2WX7vZHtDgQdhWxIQ==", "dev": true, - "requires": { + "dependencies": { "@ensdomains/ensjs": "^2.0.1", "@truffle/blockchain-utils": "^0.1.3", "@truffle/contract-schema": "^3.4.7", @@ -1045,37 +1596,45 @@ "ethereum-ens": "^0.8.0", "ethers": "^4.0.0-beta.1", "source-map-support": "^0.5.19" + }, + "peerDependencies": { + "web3": "^1.2.1", + "web3-core-helpers": "^1.2.1", + "web3-core-promievent": "^1.2.1", + "web3-eth-abi": "^1.2.1", + "web3-utils": "^1.2.1" } }, - "@openzeppelin/contract-loader": { + "node_modules/@openzeppelin/contract-loader": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/@openzeppelin/contract-loader/-/contract-loader-0.6.3.tgz", "integrity": "sha512-cOFIjBjwbGgZhDZsitNgJl0Ye1rd5yu/Yx5LMgeq3u0ZYzldm4uObzHDFq4gjDdoypvyORjjJa3BlFA7eAnVIg==", "dev": true, - "requires": { + "dependencies": { "find-up": "^4.1.0", "fs-extra": "^8.1.0" - }, + } + }, + "node_modules/@openzeppelin/contract-loader/node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, "dependencies": { - "fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "dev": true, - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - } - } + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" } }, - "@openzeppelin/docs-utils": { + "node_modules/@openzeppelin/docs-utils": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@openzeppelin/docs-utils/-/docs-utils-0.1.3.tgz", "integrity": "sha512-O/iJ4jEi5ryNc/T74G9gbnFwQ8QaQ2bpAVoYXLPknZJyK52GEAvxC12UMP33KodTNV3rMzeeQrSBIdI8skjDJg==", "dev": true, - "requires": { + "dependencies": { "@frangio/servbot": "^0.2.5", "chalk": "^3.0.0", "chokidar": "^3.5.3", @@ -1085,14 +1644,17 @@ "js-yaml": "^3.13.1", "lodash.startcase": "^4.4.0", "minimist": "^1.2.0" + }, + "bin": { + "oz-docs": "oz-docs.js" } }, - "@openzeppelin/test-helpers": { + "node_modules/@openzeppelin/test-helpers": { "version": "0.5.16", "resolved": "https://registry.npmjs.org/@openzeppelin/test-helpers/-/test-helpers-0.5.16.tgz", "integrity": "sha512-T1EvspSfH1qQO/sgGlskLfYVBbqzJR23SZzYl/6B2JnT4EhThcI85UpvDk0BkLWKaDScQTabGHt4GzHW+3SfZg==", "dev": true, - "requires": { + "dependencies": { "@openzeppelin/contract-loader": "^0.6.2", "@truffle/contract": "^4.0.35", "ansi-colors": "^3.2.3", @@ -1103,84 +1665,112 @@ "semver": "^5.6.0", "web3": "^1.2.5", "web3-utils": "^1.2.5" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } } }, - "@scure/base": { + "node_modules/@openzeppelin/test-helpers/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/@scure/base": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.1.tgz", "integrity": "sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==", - "dev": true + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ] }, - "@scure/bip32": { + "node_modules/@scure/bip32": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.1.0.tgz", "integrity": "sha512-ftTW3kKX54YXLCxH6BB7oEEoJfoE2pIgw7MINKAs5PsS6nqKPuKk1haTF/EuHmYqG330t5GSrdmtRuHaY1a62Q==", "dev": true, - "requires": { + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { "@noble/hashes": "~1.1.1", "@noble/secp256k1": "~1.6.0", "@scure/base": "~1.1.0" } }, - "@scure/bip39": { + "node_modules/@scure/bip39": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.1.0.tgz", "integrity": "sha512-pwrPOS16VeTKg98dYXQyIjJEcWfz7/1YJIwxUEPFfQPtc86Ym/1sVgQ2RLoD43AazMk2l/unK4ITySSpW2+82w==", "dev": true, - "requires": { + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { "@noble/hashes": "~1.1.1", "@scure/base": "~1.1.0" } }, - "@sentry/core": { + "node_modules/@sentry/core": { "version": "5.30.0", "resolved": "https://registry.npmjs.org/@sentry/core/-/core-5.30.0.tgz", "integrity": "sha512-TmfrII8w1PQZSZgPpUESqjB+jC6MvZJZdLtE/0hZ+SrnKhW3x5WlYLvTXZpcWePYBku7rl2wn1RZu6uT0qCTeg==", "dev": true, - "requires": { + "dependencies": { "@sentry/hub": "5.30.0", "@sentry/minimal": "5.30.0", "@sentry/types": "5.30.0", "@sentry/utils": "5.30.0", "tslib": "^1.9.3" + }, + "engines": { + "node": ">=6" } }, - "@sentry/hub": { + "node_modules/@sentry/hub": { "version": "5.30.0", "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-5.30.0.tgz", "integrity": "sha512-2tYrGnzb1gKz2EkMDQcfLrDTvmGcQPuWxLnJKXJvYTQDGLlEvi2tWz1VIHjunmOvJrB5aIQLhm+dcMRwFZDCqQ==", "dev": true, - "requires": { + "dependencies": { "@sentry/types": "5.30.0", "@sentry/utils": "5.30.0", "tslib": "^1.9.3" + }, + "engines": { + "node": ">=6" } }, - "@sentry/minimal": { + "node_modules/@sentry/minimal": { "version": "5.30.0", "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-5.30.0.tgz", "integrity": "sha512-BwWb/owZKtkDX+Sc4zCSTNcvZUq7YcH3uAVlmh/gtR9rmUvbzAA3ewLuB3myi4wWRAMEtny6+J/FN/x+2wn9Xw==", "dev": true, - "requires": { + "dependencies": { "@sentry/hub": "5.30.0", "@sentry/types": "5.30.0", "tslib": "^1.9.3" + }, + "engines": { + "node": ">=6" } }, - "@sentry/node": { + "node_modules/@sentry/node": { "version": "5.30.0", "resolved": "https://registry.npmjs.org/@sentry/node/-/node-5.30.0.tgz", "integrity": "sha512-Br5oyVBF0fZo6ZS9bxbJZG4ApAjRqAnqFFurMVJJdunNb80brh7a5Qva2kjhm+U6r9NJAB5OmDyPkA1Qnt+QVg==", "dev": true, - "requires": { + "dependencies": { "@sentry/core": "5.30.0", "@sentry/hub": "5.30.0", "@sentry/tracing": "5.30.0", @@ -1190,109 +1780,107 @@ "https-proxy-agent": "^5.0.0", "lru_map": "^0.3.3", "tslib": "^1.9.3" + }, + "engines": { + "node": ">=6" } }, - "@sentry/tracing": { + "node_modules/@sentry/tracing": { "version": "5.30.0", "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-5.30.0.tgz", "integrity": "sha512-dUFowCr0AIMwiLD7Fs314Mdzcug+gBVo/+NCMyDw8tFxJkwWAKl7Qa2OZxLQ0ZHjakcj1hNKfCQJ9rhyfOl4Aw==", "dev": true, - "requires": { + "dependencies": { "@sentry/hub": "5.30.0", "@sentry/minimal": "5.30.0", "@sentry/types": "5.30.0", "@sentry/utils": "5.30.0", "tslib": "^1.9.3" + }, + "engines": { + "node": ">=6" } }, - "@sentry/types": { + "node_modules/@sentry/types": { "version": "5.30.0", "resolved": "https://registry.npmjs.org/@sentry/types/-/types-5.30.0.tgz", "integrity": "sha512-R8xOqlSTZ+htqrfteCWU5Nk0CDN5ApUTvrlvBuiH1DyP6czDZ4ktbZB0hAgBlVcK0U+qpD3ag3Tqqpa5Q67rPw==", - "dev": true + "dev": true, + "engines": { + "node": ">=6" + } }, - "@sentry/utils": { + "node_modules/@sentry/utils": { "version": "5.30.0", "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-5.30.0.tgz", "integrity": "sha512-zaYmoH0NWWtvnJjC9/CBseXMtKHm/tm40sz3YfJRxeQjyzRqNQPgivpd9R/oDJCYj999mzdW382p/qi2ypjLww==", "dev": true, - "requires": { + "dependencies": { "@sentry/types": "5.30.0", "tslib": "^1.9.3" + }, + "engines": { + "node": ">=6" } }, - "@sindresorhus/is": { + "node_modules/@sindresorhus/is": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", - "dev": true + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } }, - "@solidity-parser/parser": { - "version": "0.14.3", - "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.14.3.tgz", - "integrity": "sha512-29g2SZ29HtsqA58pLCtopI1P/cPy5/UAzlcAXO6T/CNJimG6yA8kx4NaseMyJULiC+TEs02Y9/yeHzClqoA0hw==", + "node_modules/@solidity-parser/parser": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.14.5.tgz", + "integrity": "sha512-6dKnHZn7fg/iQATVEzqyUOyEidbn05q7YA2mQ9hC0MMXhhV3/JrsxmFSYZAcr7j1yUP700LLhTruvJ3MiQmjJg==", "dev": true, - "requires": { + "dependencies": { "antlr4ts": "^0.5.0-alpha.4" } }, - "@szmarczak/http-timer": { + "node_modules/@szmarczak/http-timer": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", "dev": true, - "requires": { + "dependencies": { "defer-to-connect": "^2.0.1" + }, + "engines": { + "node": ">=14.16" } }, - "@truffle/abi-utils": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@truffle/abi-utils/-/abi-utils-0.3.1.tgz", - "integrity": "sha512-tieaDgwDm2IH1wJuVF/waREVFvzXHSF6AkQfd71DQwpwnrl/9I1iKu+1WpQyFqxu+6WMfCYhzMEbssQBt4Zniw==", + "node_modules/@truffle/abi-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@truffle/abi-utils/-/abi-utils-0.3.6.tgz", + "integrity": "sha512-61aTH2QmwVA1INaPMufRHTsS6jsEhS+GCkuCDdvBDmwctSnCKGDOr185BGt65QrpMRxYmIoH6WFBSNMYxW9GRw==", "dev": true, - "requires": { + "dependencies": { "change-case": "3.0.2", "fast-check": "3.1.1", - "web3-utils": "1.7.4" - }, - "dependencies": { - "bn.js": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", - "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", - "dev": true - }, - "web3-utils": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.7.4.tgz", - "integrity": "sha512-acBdm6Evd0TEZRnChM/MCvGsMwYKmSh7OaUfNf5OKG0CIeGWD/6gqLOWIwmwSnre/2WrA1nKGId5uW2e5EfluA==", - "dev": true, - "requires": { - "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.8.1" } }, - "@truffle/blockchain-utils": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/@truffle/blockchain-utils/-/blockchain-utils-0.1.4.tgz", - "integrity": "sha512-HegAo5A8UX9vE8dtceBRgCY207gOb9wj54c8mNOOWHcFpkyJz7kZYGo44As6Imh10/0hD2j7vHQ56Jf+uszJ3A==", + "node_modules/@truffle/blockchain-utils": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@truffle/blockchain-utils/-/blockchain-utils-0.1.6.tgz", + "integrity": "sha512-SldoNRIFSm3+HMBnSc2jFsu5TWDkCN4X6vL3wrd0t6DIeF7nD6EoPPjxwbFSoqCnkkRxMuZeL6sUx7UMJS/wSA==", "dev": true }, - "@truffle/codec": { - "version": "0.14.4", - "resolved": "https://registry.npmjs.org/@truffle/codec/-/codec-0.14.4.tgz", - "integrity": "sha512-il9dFzALUbd1JMPOVcxnIjTQ1fiJEPHfBYbqVQfWZfzAN0Kw+x1eaKunIU+NvrNRycvkXk4itWUTui5sMlXBBA==", + "node_modules/@truffle/codec": { + "version": "0.14.11", + "resolved": "https://registry.npmjs.org/@truffle/codec/-/codec-0.14.11.tgz", + "integrity": "sha512-NgfMNYemgMXqoEcJA5ZsEhxChCwq33rSxtNxlececEH/1Nf0r+ryfrfmLlyPmv8f3jorVf1GWa0zI0AedGCGYQ==", "dev": true, - "requires": { - "@truffle/abi-utils": "^0.3.1", - "@truffle/compile-common": "^0.8.0", + "dependencies": { + "@truffle/abi-utils": "^0.3.6", + "@truffle/compile-common": "^0.9.1", "big.js": "^6.0.3", "bn.js": "^5.1.3", "cbor": "^5.2.0", @@ -1300,1067 +1888,14472 @@ "lodash": "^4.17.21", "semver": "7.3.7", "utf8": "^3.0.0", - "web3-utils": "1.7.4" + "web3-utils": "1.8.1" + } + }, + "node_modules/@truffle/codec/node_modules/bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", + "dev": true + }, + "node_modules/@truffle/codec/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@truffle/codec/node_modules/semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dev": true, "dependencies": { - "bn.js": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", - "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", - "dev": true - }, - "web3-utils": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.7.4.tgz", - "integrity": "sha512-acBdm6Evd0TEZRnChM/MCvGsMwYKmSh7OaUfNf5OKG0CIeGWD/6gqLOWIwmwSnre/2WrA1nKGId5uW2e5EfluA==", - "dev": true, - "requires": { - "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" - } - } + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, - "@truffle/compile-common": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@truffle/compile-common/-/compile-common-0.8.0.tgz", - "integrity": "sha512-3mtEC73dQODTI3/ZwonunVHyPS2BGexXSBIv4pOgMrWwnZPcHlo2+IW2+m2At/DnZehL78bkF993Vti2pJfx6Q==", + "node_modules/@truffle/codec/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/@truffle/compile-common": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@truffle/compile-common/-/compile-common-0.9.1.tgz", + "integrity": "sha512-mhdkX6ExZImHSBO3jGm6aAn8NpVtMTdjq50jRXY/O59/ZNC0J9WpRapxrAKUVNc+XydMdBlfeEpXoqTJg7cbXw==", "dev": true, - "requires": { + "dependencies": { "@truffle/error": "^0.1.1", "colors": "1.4.0" } }, - "@truffle/contract": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@truffle/contract/-/contract-4.6.0.tgz", - "integrity": "sha512-FxSR7WtV1q+1AKHhJmsbd360qFFjtkGPQeJfaDcn7wlOPG+axW9iLqLSUTlRpFkPKJnUILg2FujNM965rIQJtg==", + "node_modules/@truffle/contract": { + "version": "4.6.10", + "resolved": "https://registry.npmjs.org/@truffle/contract/-/contract-4.6.10.tgz", + "integrity": "sha512-69IZSXeQKRP3EutILqe+vLY5A5gUpeXUiZhm/Fy/qHHkP238vMjtOkTZGkY6bonYqmgk+vDY7KSYSYKzDNPdCA==", "dev": true, - "requires": { + "dependencies": { "@ensdomains/ensjs": "^2.1.0", - "@truffle/blockchain-utils": "^0.1.4", - "@truffle/contract-schema": "^3.4.9", - "@truffle/debug-utils": "^6.0.34", + "@truffle/blockchain-utils": "^0.1.6", + "@truffle/contract-schema": "^3.4.11", + "@truffle/debug-utils": "^6.0.42", "@truffle/error": "^0.1.1", - "@truffle/interface-adapter": "^0.5.21", + "@truffle/interface-adapter": "^0.5.26", "bignumber.js": "^7.2.1", "debug": "^4.3.1", "ethers": "^4.0.32", - "web3": "1.7.4", - "web3-core-helpers": "1.7.4", - "web3-core-promievent": "1.7.4", - "web3-eth-abi": "1.7.4", - "web3-utils": "1.7.4" + "web3": "1.8.1", + "web3-core-helpers": "1.8.1", + "web3-core-promievent": "1.8.1", + "web3-eth-abi": "1.8.1", + "web3-utils": "1.8.1" + } + }, + "node_modules/@truffle/contract-schema": { + "version": "3.4.11", + "resolved": "https://registry.npmjs.org/@truffle/contract-schema/-/contract-schema-3.4.11.tgz", + "integrity": "sha512-wReyVZUPyU9Zy5PSCugBLG1nnruBmRAJ/gmoirQiJ9N2n+s1iGBTY49tkDqFMz3XUUE0kplfdb9YKZJlLkTWzQ==", + "dev": true, + "dependencies": { + "ajv": "^6.10.0", + "debug": "^4.3.1" + } + }, + "node_modules/@truffle/debug-utils": { + "version": "6.0.42", + "resolved": "https://registry.npmjs.org/@truffle/debug-utils/-/debug-utils-6.0.42.tgz", + "integrity": "sha512-9v70tj+My0Z2UZJ9OsuUlfo4Dt2AJqAQa/YWtGe28H8zsi+o9Dca0RsKWecuprdllgzrEs7ad8QUtSINhwjIlg==", + "dev": true, + "dependencies": { + "@truffle/codec": "^0.14.11", + "@trufflesuite/chromafi": "^3.0.0", + "bn.js": "^5.1.3", + "chalk": "^2.4.2", + "debug": "^4.3.1", + "highlightjs-solidity": "^2.0.5" + } + }, + "node_modules/@truffle/debug-utils/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@truffle/debug-utils/node_modules/bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", + "dev": true + }, + "node_modules/@truffle/debug-utils/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, "dependencies": { - "@sindresorhus/is": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", - "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==", - "dev": true - }, - "@szmarczak/http-timer": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", - "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", - "dev": true, - "requires": { - "defer-to-connect": "^1.0.1" - } - }, - "@types/node": { - "version": "12.20.55", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz", - "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==", - "dev": true - }, - "cacheable-request": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", - "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", - "dev": true, - "requires": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^3.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^4.1.0", - "responselike": "^1.0.2" - }, - "dependencies": { - "get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", - "dev": true - } - } - }, - "decompress-response": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", - "integrity": "sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA==", - "dev": true, - "requires": { - "mimic-response": "^1.0.0" - } - }, - "defer-to-connect": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", - "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==", - "dev": true - }, - "eth-lib": { - "version": "0.2.8", - "resolved": "https://registry.npmjs.org/eth-lib/-/eth-lib-0.2.8.tgz", - "integrity": "sha512-ArJ7x1WcWOlSpzdoTBX8vkwlkSQ85CjjifSZtV4co64vWxSV8geWfPI9x4SVYu3DSxnX4yWFVTtGL+j9DUFLNw==", - "dev": true, - "requires": { - "bn.js": "^4.11.6", - "elliptic": "^6.4.0", - "xhr-request-promise": "^0.1.2" - } - }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "got": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", - "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", - "dev": true, - "requires": { - "@sindresorhus/is": "^0.14.0", - "@szmarczak/http-timer": "^1.1.2", - "cacheable-request": "^6.0.0", - "decompress-response": "^3.3.0", - "duplexer3": "^0.1.4", - "get-stream": "^4.1.0", - "lowercase-keys": "^1.0.1", - "mimic-response": "^1.0.1", - "p-cancelable": "^1.0.0", - "to-readable-stream": "^1.0.0", - "url-parse-lax": "^3.0.0" - } - }, - "json-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", - "integrity": "sha512-CuUqjv0FUZIdXkHPI8MezCnFCdaTAacej1TZYulLoAg1h/PhwkdXFN4V/gzY4g+fMBCOV2xF+rp7t2XD2ns/NQ==", - "dev": true - }, - "keyv": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", - "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", - "dev": true, - "requires": { - "json-buffer": "3.0.0" - } - }, - "lowercase-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", - "dev": true - }, - "normalize-url": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz", - "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==", - "dev": true - }, - "p-cancelable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", - "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==", - "dev": true - }, - "responselike": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", - "integrity": "sha512-/Fpe5guzJk1gPqdJLJR5u7eG/gNY4nImjbRDaVWVMRhne55TCmj2i9Q+54PBRfatRC8v/rIiv9BN0pMd9OV5EQ==", - "dev": true, - "requires": { - "lowercase-keys": "^1.0.0" - } - }, - "uuid": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", - "dev": true - }, - "web3": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3/-/web3-1.7.4.tgz", - "integrity": "sha512-iFGK5jO32vnXM/ASaJBaI0+gVR6uHozvYdxkdhaeOCD6HIQ4iIXadbO2atVpE9oc/H8l2MovJ4LtPhG7lIBN8A==", - "dev": true, - "requires": { - "web3-bzz": "1.7.4", - "web3-core": "1.7.4", - "web3-eth": "1.7.4", - "web3-eth-personal": "1.7.4", - "web3-net": "1.7.4", - "web3-shh": "1.7.4", - "web3-utils": "1.7.4" - } - }, - "web3-bzz": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-bzz/-/web3-bzz-1.7.4.tgz", - "integrity": "sha512-w9zRhyEqTK/yi0LGRHjZMcPCfP24LBjYXI/9YxFw9VqsIZ9/G0CRCnUt12lUx0A56LRAMpF7iQ8eA73aBcO29Q==", - "dev": true, - "requires": { - "@types/node": "^12.12.6", - "got": "9.6.0", - "swarm-js": "^0.1.40" - } - }, - "web3-core": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-core/-/web3-core-1.7.4.tgz", - "integrity": "sha512-L0DCPlIh9bgIED37tYbe7bsWrddoXYc897ANGvTJ6MFkSNGiMwDkTLWSgYd9Mf8qu8b4iuPqXZHMwIo4atoh7Q==", - "dev": true, - "requires": { - "@types/bn.js": "^5.1.0", - "@types/node": "^12.12.6", - "bignumber.js": "^9.0.0", - "web3-core-helpers": "1.7.4", - "web3-core-method": "1.7.4", - "web3-core-requestmanager": "1.7.4", - "web3-utils": "1.7.4" - }, - "dependencies": { - "bignumber.js": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.0.tgz", - "integrity": "sha512-4LwHK4nfDOraBCtst+wOWIHbu1vhvAPJK8g8nROd4iuc3PSEjWif/qwbkh8jwCJz6yDBvtU4KPynETgrfh7y3A==", - "dev": true - } - } - }, - "web3-core-helpers": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-core-helpers/-/web3-core-helpers-1.7.4.tgz", - "integrity": "sha512-F8PH11qIkE/LpK4/h1fF/lGYgt4B6doeMi8rukeV/s4ivseZHHslv1L6aaijLX/g/j4PsFmR42byynBI/MIzFg==", - "dev": true, - "requires": { - "web3-eth-iban": "1.7.4", - "web3-utils": "1.7.4" - } - }, - "web3-core-method": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-core-method/-/web3-core-method-1.7.4.tgz", - "integrity": "sha512-56K7pq+8lZRkxJyzf5MHQPI9/VL3IJLoy4L/+q8HRdZJ3CkB1DkXYaXGU2PeylG1GosGiSzgIfu1ljqS7CP9xQ==", - "dev": true, - "requires": { - "@ethersproject/transactions": "^5.6.2", - "web3-core-helpers": "1.7.4", - "web3-core-promievent": "1.7.4", - "web3-core-subscriptions": "1.7.4", - "web3-utils": "1.7.4" - } - }, - "web3-core-promievent": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-core-promievent/-/web3-core-promievent-1.7.4.tgz", - "integrity": "sha512-o4uxwXKDldN7ER7VUvDfWsqTx9nQSP1aDssi1XYXeYC2xJbVo0n+z6ryKtmcoWoRdRj7uSpVzal3nEmlr480mA==", - "dev": true, - "requires": { - "eventemitter3": "4.0.4" - } - }, - "web3-core-requestmanager": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-core-requestmanager/-/web3-core-requestmanager-1.7.4.tgz", - "integrity": "sha512-IuXdAm65BQtPL4aI6LZJJOrKAs0SM5IK2Cqo2/lMNvVMT9Kssq6qOk68Uf7EBDH0rPuINi+ReLP+uH+0g3AnPA==", - "dev": true, - "requires": { - "util": "^0.12.0", - "web3-core-helpers": "1.7.4", - "web3-providers-http": "1.7.4", - "web3-providers-ipc": "1.7.4", - "web3-providers-ws": "1.7.4" - } - }, - "web3-core-subscriptions": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-core-subscriptions/-/web3-core-subscriptions-1.7.4.tgz", - "integrity": "sha512-VJvKWaXRyxk2nFWumOR94ut9xvjzMrRtS38c4qj8WBIRSsugrZr5lqUwgndtj0qx4F+50JhnU++QEqUEAtKm3g==", - "dev": true, - "requires": { - "eventemitter3": "4.0.4", - "web3-core-helpers": "1.7.4" - } - }, - "web3-eth": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-eth/-/web3-eth-1.7.4.tgz", - "integrity": "sha512-JG0tTMv0Ijj039emXNHi07jLb0OiWSA9O24MRSk5vToTQyDNXihdF2oyq85LfHuF690lXZaAXrjhtLNlYqb7Ug==", - "dev": true, - "requires": { - "web3-core": "1.7.4", - "web3-core-helpers": "1.7.4", - "web3-core-method": "1.7.4", - "web3-core-subscriptions": "1.7.4", - "web3-eth-abi": "1.7.4", - "web3-eth-accounts": "1.7.4", - "web3-eth-contract": "1.7.4", - "web3-eth-ens": "1.7.4", - "web3-eth-iban": "1.7.4", - "web3-eth-personal": "1.7.4", - "web3-net": "1.7.4", - "web3-utils": "1.7.4" - } - }, - "web3-eth-abi": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-eth-abi/-/web3-eth-abi-1.7.4.tgz", - "integrity": "sha512-eMZr8zgTbqyL9MCTCAvb67RbVyN5ZX7DvA0jbLOqRWCiw+KlJKTGnymKO6jPE8n5yjk4w01e165Qb11hTDwHgg==", - "dev": true, - "requires": { - "@ethersproject/abi": "^5.6.3", - "web3-utils": "1.7.4" - } - }, - "web3-eth-accounts": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-eth-accounts/-/web3-eth-accounts-1.7.4.tgz", - "integrity": "sha512-Y9vYLRKP7VU7Cgq6wG1jFaG2k3/eIuiTKAG8RAuQnb6Cd9k5BRqTm5uPIiSo0AP/u11jDomZ8j7+WEgkU9+Btw==", - "dev": true, - "requires": { - "@ethereumjs/common": "^2.5.0", - "@ethereumjs/tx": "^3.3.2", - "crypto-browserify": "3.12.0", - "eth-lib": "0.2.8", - "ethereumjs-util": "^7.0.10", - "scrypt-js": "^3.0.1", - "uuid": "3.3.2", - "web3-core": "1.7.4", - "web3-core-helpers": "1.7.4", - "web3-core-method": "1.7.4", - "web3-utils": "1.7.4" - } - }, - "web3-eth-contract": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-eth-contract/-/web3-eth-contract-1.7.4.tgz", - "integrity": "sha512-ZgSZMDVI1pE9uMQpK0T0HDT2oewHcfTCv0osEqf5qyn5KrcQDg1GT96/+S0dfqZ4HKj4lzS5O0rFyQiLPQ8LzQ==", - "dev": true, - "requires": { - "@types/bn.js": "^5.1.0", - "web3-core": "1.7.4", - "web3-core-helpers": "1.7.4", - "web3-core-method": "1.7.4", - "web3-core-promievent": "1.7.4", - "web3-core-subscriptions": "1.7.4", - "web3-eth-abi": "1.7.4", - "web3-utils": "1.7.4" - } - }, - "web3-eth-ens": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-eth-ens/-/web3-eth-ens-1.7.4.tgz", - "integrity": "sha512-Gw5CVU1+bFXP5RVXTCqJOmHn71X2ghNk9VcEH+9PchLr0PrKbHTA3hySpsPco1WJAyK4t8SNQVlNr3+bJ6/WZA==", - "dev": true, - "requires": { - "content-hash": "^2.5.2", - "eth-ens-namehash": "2.0.8", - "web3-core": "1.7.4", - "web3-core-helpers": "1.7.4", - "web3-core-promievent": "1.7.4", - "web3-eth-abi": "1.7.4", - "web3-eth-contract": "1.7.4", - "web3-utils": "1.7.4" - } - }, - "web3-eth-iban": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-eth-iban/-/web3-eth-iban-1.7.4.tgz", - "integrity": "sha512-XyrsgWlZQMv5gRcjXMsNvAoCRvV5wN7YCfFV5+tHUCqN8g9T/o4XUS20vDWD0k4HNiAcWGFqT1nrls02MGZ08w==", - "dev": true, - "requires": { - "bn.js": "^5.2.1", - "web3-utils": "1.7.4" - }, - "dependencies": { - "bn.js": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", - "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", - "dev": true - } - } - }, - "web3-eth-personal": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-eth-personal/-/web3-eth-personal-1.7.4.tgz", - "integrity": "sha512-O10C1Hln5wvLQsDhlhmV58RhXo+GPZ5+W76frSsyIrkJWLtYQTCr5WxHtRC9sMD1idXLqODKKgI2DL+7xeZ0/g==", - "dev": true, - "requires": { - "@types/node": "^12.12.6", - "web3-core": "1.7.4", - "web3-core-helpers": "1.7.4", - "web3-core-method": "1.7.4", - "web3-net": "1.7.4", - "web3-utils": "1.7.4" - } - }, - "web3-net": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-net/-/web3-net-1.7.4.tgz", - "integrity": "sha512-d2Gj+DIARHvwIdmxFQ4PwAAXZVxYCR2lET0cxz4KXbE5Og3DNjJi+MoPkX+WqoUXqimu/EOd4Cd+7gefqVAFDg==", - "dev": true, - "requires": { - "web3-core": "1.7.4", - "web3-core-method": "1.7.4", - "web3-utils": "1.7.4" - } - }, - "web3-providers-http": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-providers-http/-/web3-providers-http-1.7.4.tgz", - "integrity": "sha512-AU+/S+49rcogUER99TlhW+UBMk0N2DxvN54CJ2pK7alc2TQ7+cprNPLHJu4KREe8ndV0fT6JtWUfOMyTvl+FRA==", - "dev": true, - "requires": { - "web3-core-helpers": "1.7.4", - "xhr2-cookies": "1.1.0" - } - }, - "web3-providers-ipc": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-providers-ipc/-/web3-providers-ipc-1.7.4.tgz", - "integrity": "sha512-jhArOZ235dZy8fS8090t60nTxbd1ap92ibQw5xIrAQ9m7LcZKNfmLAQUVsD+3dTFvadRMi6z1vCO7zRi84gWHw==", - "dev": true, - "requires": { - "oboe": "2.1.5", - "web3-core-helpers": "1.7.4" - } - }, - "web3-providers-ws": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-providers-ws/-/web3-providers-ws-1.7.4.tgz", - "integrity": "sha512-g72X77nrcHMFU8hRzQJzfgi/072n8dHwRCoTw+WQrGp+XCQ71fsk2qIu3Tp+nlp5BPn8bRudQbPblVm2uT4myQ==", - "dev": true, - "requires": { - "eventemitter3": "4.0.4", - "web3-core-helpers": "1.7.4", - "websocket": "^1.0.32" - } - }, - "web3-shh": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-shh/-/web3-shh-1.7.4.tgz", - "integrity": "sha512-mlSZxSYcMkuMCxqhTYnZkUdahZ11h+bBv/8TlkXp/IHpEe4/Gg+KAbmfudakq3EzG/04z70XQmPgWcUPrsEJ+A==", - "dev": true, - "requires": { - "web3-core": "1.7.4", - "web3-core-method": "1.7.4", - "web3-core-subscriptions": "1.7.4", - "web3-net": "1.7.4" - } - }, - "web3-utils": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.7.4.tgz", - "integrity": "sha512-acBdm6Evd0TEZRnChM/MCvGsMwYKmSh7OaUfNf5OKG0CIeGWD/6gqLOWIwmwSnre/2WrA1nKGId5uW2e5EfluA==", - "dev": true, - "requires": { - "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" - }, - "dependencies": { - "bn.js": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", - "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", - "dev": true - } - } - } + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" } }, - "@truffle/contract-schema": { - "version": "3.4.9", - "resolved": "https://registry.npmjs.org/@truffle/contract-schema/-/contract-schema-3.4.9.tgz", - "integrity": "sha512-nhYMXWbUs6dMYHL1f8DTkRk/uo1sADK0yeSYXo/p/7nqnjlHzqrr75BBsKbB7OFIVT05des+GFNQJqBaRZVdxQ==", + "node_modules/@truffle/debug-utils/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "dev": true, - "requires": { - "ajv": "^6.10.0", - "debug": "^4.3.1" + "dependencies": { + "color-name": "1.1.3" } }, - "@truffle/debug-utils": { - "version": "6.0.34", - "resolved": "https://registry.npmjs.org/@truffle/debug-utils/-/debug-utils-6.0.34.tgz", - "integrity": "sha512-GbGnC9ESJXYHjzQKOV6yeFzvXDnW1yIvpfHXyc4PMDnnFoqX2OxP8mGmMzFKW2Uhqg89wl4GMPLuxycMkodWrw==", + "node_modules/@truffle/debug-utils/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@truffle/debug-utils/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@truffle/debug-utils/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@truffle/debug-utils/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, - "requires": { - "@truffle/codec": "^0.14.4", - "@trufflesuite/chromafi": "^3.0.0", - "bn.js": "^5.1.3", - "chalk": "^2.4.2", - "debug": "^4.3.1", - "highlightjs-solidity": "^2.0.5" - }, "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "bn.js": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", - "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", - "dev": true - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" } }, - "@truffle/error": { + "node_modules/@truffle/error": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/@truffle/error/-/error-0.1.1.tgz", "integrity": "sha512-sE7c9IHIGdbK4YayH4BC8i8qMjoAOeg6nUXUDZZp8wlU21/EMpaG+CLx+KqcIPyR+GSWIW3Dm0PXkr2nlggFDA==", "dev": true }, - "@truffle/interface-adapter": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/@truffle/interface-adapter/-/interface-adapter-0.5.21.tgz", - "integrity": "sha512-2ltbu3upsWS0TAQu1kLQc048XlXNmDkCzH6iebX4dg3VBB+l7oG/pu5+/kl8t+LRfzGoEMLKwOQt7vk0Vm3PNA==", + "node_modules/@truffle/interface-adapter": { + "version": "0.5.26", + "resolved": "https://registry.npmjs.org/@truffle/interface-adapter/-/interface-adapter-0.5.26.tgz", + "integrity": "sha512-fBhoqtT+CT4XKXcOijvw0RIMgyUi3FJg+n5i5PyGBsoRzqbLZd9cZq+oMNjOZPdf3GH68hsOFOaQO5tZH7oZow==", "dev": true, - "requires": { + "dependencies": { "bn.js": "^5.1.3", "ethers": "^4.0.32", - "web3": "1.7.4" - }, - "dependencies": { - "@sindresorhus/is": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", - "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==", - "dev": true - }, - "@szmarczak/http-timer": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", - "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", - "dev": true, - "requires": { - "defer-to-connect": "^1.0.1" - } - }, - "@types/node": { - "version": "12.20.55", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz", - "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==", - "dev": true - }, - "bignumber.js": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.0.tgz", - "integrity": "sha512-4LwHK4nfDOraBCtst+wOWIHbu1vhvAPJK8g8nROd4iuc3PSEjWif/qwbkh8jwCJz6yDBvtU4KPynETgrfh7y3A==", - "dev": true - }, - "bn.js": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", - "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", - "dev": true - }, - "cacheable-request": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", - "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", - "dev": true, - "requires": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^3.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^4.1.0", - "responselike": "^1.0.2" - }, - "dependencies": { - "get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", - "dev": true - } - } - }, - "decompress-response": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", - "integrity": "sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA==", - "dev": true, - "requires": { - "mimic-response": "^1.0.0" - } + "web3": "1.8.1" + } + }, + "node_modules/@truffle/interface-adapter/node_modules/bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", + "dev": true + }, + "node_modules/@trufflesuite/chromafi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@trufflesuite/chromafi/-/chromafi-3.0.0.tgz", + "integrity": "sha512-oqWcOqn8nT1bwlPPfidfzS55vqcIDdpfzo3HbU9EnUmcSTX+I8z0UyUFI3tZQjByVJulbzxHxUGS3ZJPwK/GPQ==", + "dev": true, + "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" + } + }, + "node_modules/@trufflesuite/chromafi/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@trufflesuite/chromafi/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@trufflesuite/chromafi/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@trufflesuite/chromafi/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@trufflesuite/chromafi/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@trufflesuite/chromafi/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@trufflesuite/chromafi/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@types/async-eventemitter": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@types/async-eventemitter/-/async-eventemitter-0.2.1.tgz", + "integrity": "sha512-M2P4Ng26QbAeITiH7w1d7OxtldgfAe0wobpyJzVK/XOb0cUGKU2R4pfAhqcJBXAe2ife5ZOhSv4wk7p+ffURtg==", + "dev": true + }, + "node_modules/@types/bignumber.js": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/bignumber.js/-/bignumber.js-5.0.0.tgz", + "integrity": "sha512-0DH7aPGCClywOFaxxjE6UwpN2kQYe9LwuDQMv+zYA97j5GkOMo8e66LYT+a8JYU7jfmUFRZLa9KycxHDsKXJCA==", + "deprecated": "This is a stub types definition for bignumber.js (https://github.com/MikeMcl/bignumber.js/). bignumber.js provides its own type definitions, so you don't need @types/bignumber.js installed!", + "dev": true, + "dependencies": { + "bignumber.js": "*" + } + }, + "node_modules/@types/bn.js": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.1.tgz", + "integrity": "sha512-qNrYbZqMx0uJAfKnKclPh+dTwK33KfLHYqtyODwd5HnXOjnkhc4qgn3BrK6RWyGZm5+sIFE7Q7Vz6QQtJB7w7g==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/cacheable-request": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", + "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", + "dev": true, + "dependencies": { + "@types/http-cache-semantics": "*", + "@types/keyv": "^3.1.4", + "@types/node": "*", + "@types/responselike": "^1.0.0" + } + }, + "node_modules/@types/chai": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.4.tgz", + "integrity": "sha512-KnRanxnpfpjUTqTCXslZSEdLfXExwgNxYPdiO2WGUj8+HDjFi8R3k5RVKPeSCzLjCcshCAtVO2QBbVuAV4kTnw==", + "dev": true + }, + "node_modules/@types/concat-stream": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@types/concat-stream/-/concat-stream-1.6.1.tgz", + "integrity": "sha512-eHE4cQPoj6ngxBZMvVf6Hw7Mh4jMW4U9lpGmS5GBPB9RYxlFg+CHaVN7ErNY4W9XfLIEn20b4VDYaIrbq0q4uA==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/form-data": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/@types/form-data/-/form-data-0.0.33.tgz", + "integrity": "sha512-8BSvG1kGm83cyJITQMZSulnl6QV8jqAGreJsc5tPu1Jq0vTSOiY/k24Wx82JRpWwZSqrala6sd5rWi6aNXvqcw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==", + "dev": true, + "dependencies": { + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "node_modules/@types/http-cache-semantics": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", + "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==", + "dev": true + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true + }, + "node_modules/@types/keyv": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", + "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@types/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-ssE3Vlrys7sdIzs5LOxCzTVMsU7i9oa/IaW92wF32JFb3CVczqOkru2xspuKczHEbG3nvmPY7IFqVmGGHdNbYw==", + "dev": true + }, + "node_modules/@types/minimatch": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", + "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", + "dev": true + }, + "node_modules/@types/node": { + "version": "18.11.17", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.17.tgz", + "integrity": "sha512-HJSUJmni4BeDHhfzn6nF0sVmd1SMezP7/4F0Lq+aXzmp2xm9O7WXrUtHW/CHlYVtZUbByEvWidHqRtcJXGF2Ng==", + "dev": true + }, + "node_modules/@types/pbkdf2": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/pbkdf2/-/pbkdf2-3.1.0.tgz", + "integrity": "sha512-Cf63Rv7jCQ0LaL8tNXmEyqTHuIJxRdlS5vMh1mj5voN4+QFhVZnlZruezqpWYDiJ8UTzhP0VmeLXCmBk66YrMQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", + "dev": true + }, + "node_modules/@types/responselike": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", + "integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/secp256k1": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/secp256k1/-/secp256k1-4.0.3.tgz", + "integrity": "sha512-Da66lEIFeIz9ltsdMZcpQvmrmmoqrfju8pm1BH8WbYjZSwUgCwXLb9C+9XYogwBITnbsSaMdVPb2ekf7TV+03w==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/abbrev": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", + "integrity": "sha512-LEyx4aLEC3x6T0UguF6YILf+ntvmOaWsVfENmIW0E9H09vKlLDGelMjjSm0jkDHALj8A8quZ/HapKNigzwge+Q==", + "dev": true + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dev": true, + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/abortcontroller-polyfill": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/abortcontroller-polyfill/-/abortcontroller-polyfill-1.7.5.tgz", + "integrity": "sha512-JMJ5soJWP18htbbxJjG7bG6yuI6pRhgJ0scHHTfkUjf6wjP912xZWvM+A4sJK3gqd9E8fcPbDnOefbA9Th/FIQ==", + "dev": true + }, + "node_modules/abstract-level": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/abstract-level/-/abstract-level-1.0.3.tgz", + "integrity": "sha512-t6jv+xHy+VYwc4xqZMn2Pa9DjcdzvzZmQGRjTFc8spIbRGHgBrEKbPq+rYXc7CCo0lxgYvSgKVg9qZAhpVQSjA==", + "dev": true, + "dependencies": { + "buffer": "^6.0.3", + "catering": "^2.1.0", + "is-buffer": "^2.0.5", + "level-supports": "^4.0.0", + "level-transcoder": "^1.0.1", + "module-error": "^1.0.1", + "queue-microtask": "^1.2.3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/abstract-level/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" }, - "defer-to-connect": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", - "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==", - "dev": true + { + "type": "patreon", + "url": "https://www.patreon.com/feross" }, - "eth-lib": { - "version": "0.2.8", - "resolved": "https://registry.npmjs.org/eth-lib/-/eth-lib-0.2.8.tgz", - "integrity": "sha512-ArJ7x1WcWOlSpzdoTBX8vkwlkSQ85CjjifSZtV4co64vWxSV8geWfPI9x4SVYu3DSxnX4yWFVTtGL+j9DUFLNw==", - "dev": true, - "requires": { - "bn.js": "^4.11.6", - "elliptic": "^6.4.0", - "xhr-request-promise": "^0.1.2" - }, - "dependencies": { - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - } + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/address": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/address/-/address-1.2.2.tgz", + "integrity": "sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==", + "dev": true, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/adm-zip": { + "version": "0.4.16", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.16.tgz", + "integrity": "sha512-TFi4HBKSGfIKsK5YCkKaaFG2m4PEDyViZmEwof3MTIgzimHLto6muaHVpbrljdIvIrFZzEq/p4nafOeLcYegrg==", + "dev": true, + "engines": { + "node": ">=0.3.0" + } + }, + "node_modules/aes-js": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.1.2.tgz", + "integrity": "sha512-e5pEa2kBnBOgR4Y/p20pskXI74UEz7de8ZGVo58asOtvSVG5YAbJeELPZxOmt+Bnz3rX753YKhfIn4X4l1PPRQ==", + "dev": true + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "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" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/amdefine": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", + "integrity": "sha512-S2Hw0TtNkMJhIabBwIojKL9YHO5T0n5eNqWJ7Lrlel/zDbftQpxpapi8tZs3X1HWa+u+QeydGmzzNU0m09+Rcg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.4.2" + } + }, + "node_modules/ansi-colors": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz", + "integrity": "sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", + "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/antlr4": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/antlr4/-/antlr4-4.7.1.tgz", + "integrity": "sha512-haHyTW7Y9joE5MVs37P2lNYfU2RWBLfcRDD8OWldcdZm5TiCE91B5Xl1oWSwiDUSd4rlExpt2pu1fksYQjRBYQ==", + "dev": true + }, + "node_modules/antlr4ts": { + "version": "0.5.0-alpha.4", + "resolved": "https://registry.npmjs.org/antlr4ts/-/antlr4ts-0.5.0-alpha.4.tgz", + "integrity": "sha512-WPQDt1B74OfPv/IMS2ekXAKkTZIHl88uMetg6q3OTqgFxZ/dxDXI0EWLyZid/1Pe6hTftyg5N7gel5wNAGxXyQ==", + "dev": true + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "dev": true + }, + "node_modules/array-includes": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz", + "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "get-intrinsic": "^1.1.3", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz", + "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.reduce": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/array.prototype.reduce/-/array.prototype.reduce-1.0.5.tgz", + "integrity": "sha512-kDdugMl7id9COE8R7MHF5jWk7Dqt/fs4Pv+JXoICnYwqpjjjbUurz6w5fT5IG6brLdJhv6/VoHB0H7oyIBXd+Q==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-array-method-boxes-properly": "^1.0.0", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "dev": true + }, + "node_modules/asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "dev": true, + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, + "node_modules/asn1.js": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", + "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", + "dev": true, + "dependencies": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "safer-buffer": "^2.1.0" + } + }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/ast-parents": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/ast-parents/-/ast-parents-0.0.1.tgz", + "integrity": "sha512-XHusKxKz3zoYk1ic8Un640joHbFMhbqneyoZfoKnEGtf2ey9Uh/IdpcQplODdO/kENaMIWsD0nJm4+wX3UNLHA==", + "dev": true + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "dev": true, + "dependencies": { + "lodash": "^4.17.14" + } + }, + "node_modules/async-eventemitter": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/async-eventemitter/-/async-eventemitter-0.2.4.tgz", + "integrity": "sha512-pd20BwL7Yt1zwDFy+8MX8F1+WCT8aQeKj0kQnTrH9WaeRETlRamVhD0JtRPmrV4GfOJ2F9CvdQkZeZhnh2TuHw==", + "dev": true, + "dependencies": { + "async": "^2.4.0" + } + }, + "node_modules/async-limiter": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", + "dev": true + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true + }, + "node_modules/available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/aws4": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", + "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", + "dev": true + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/base-x": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz", + "integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "dev": true, + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, + "node_modules/bcrypt-pbkdf/node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", + "dev": true + }, + "node_modules/bech32": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz", + "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==", + "dev": true + }, + "node_modules/big-integer": { + "version": "1.6.36", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.36.tgz", + "integrity": "sha512-t70bfa7HYEA1D9idDbmuv7YbsbVkQ+Hp+8KFSul4aE5e/i1bjCNIRYJZlA8Q8p0r9T8cF/RVvwUgRA//FydEyg==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/big.js": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-6.2.1.tgz", + "integrity": "sha512-bCtHMwL9LeDIozFn+oNhhFoq+yQ3BNdnsLSASUxLciOb1vgvpHsIO1dsENiGMgbb4SkP5TrzWzRiLddn8ahVOQ==", + "dev": true, + "engines": { + "node": "*" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/bigjs" + } + }, + "node_modules/bigint-crypto-utils": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/bigint-crypto-utils/-/bigint-crypto-utils-3.1.8.tgz", + "integrity": "sha512-+VMV9Laq8pXLBKKKK49nOoq9bfR3j7NNQAtbA617a4nw9bVLo8rsqkKMBgM2AJWlNX9fEIyYaYX+d0laqYV4tw==", + "dev": true, + "dependencies": { + "bigint-mod-arith": "^3.1.0" + }, + "engines": { + "node": ">=10.4.0" + } + }, + "node_modules/bigint-mod-arith": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bigint-mod-arith/-/bigint-mod-arith-3.1.2.tgz", + "integrity": "sha512-nx8J8bBeiRR+NlsROFH9jHswW5HO8mgfOSqW0AmjicMMvaONDa8AO+5ViKDUUNytBPWiwfvZP4/Bj4Y3lUfvgQ==", + "dev": true, + "engines": { + "node": ">=10.4.0" + } + }, + "node_modules/bignumber.js": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-7.2.1.tgz", + "integrity": "sha512-S4XzBk5sMB+Rcb/LNcpzXr57VRTxgAvaAEDAl1AwRx27j00hT84O6OkteE7u8UB3NuaaygCRrEpqox4uDOrbdQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/blakejs": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/blakejs/-/blakejs-1.2.1.tgz", + "integrity": "sha512-QXUSXI3QVc/gJME0dBpXrag1kbzOqCjCX8/b54ntNyW6sjtoqxqRk3LTmXzaJoh71zMsDCjM+47jS7XiwN/+fQ==", + "dev": true + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true + }, + "node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + }, + "node_modules/body-parser": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "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.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", + "dev": true + }, + "node_modules/browser-level": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browser-level/-/browser-level-1.0.1.tgz", + "integrity": "sha512-XECYKJ+Dbzw0lbydyQuJzwNXtOpbMSq737qxJN11sIRTErOMShvDpbzTlgju7orJKvx4epULolZAuJGLzCmWRQ==", + "dev": true, + "dependencies": { + "abstract-level": "^1.0.2", + "catering": "^2.1.1", + "module-error": "^1.0.2", + "run-parallel-limit": "^1.1.0" + } + }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "node_modules/browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "dev": true, + "dependencies": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/browserify-cipher": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "dev": true, + "dependencies": { + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" + } + }, + "node_modules/browserify-des": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", + "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "dev": true, + "dependencies": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/browserify-rsa": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.0.tgz", + "integrity": "sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==", + "dev": true, + "dependencies": { + "bn.js": "^5.0.0", + "randombytes": "^2.0.1" + } + }, + "node_modules/browserify-rsa/node_modules/bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", + "dev": true + }, + "node_modules/browserify-sign": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.1.tgz", + "integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==", + "dev": true, + "dependencies": { + "bn.js": "^5.1.1", + "browserify-rsa": "^4.0.1", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "elliptic": "^6.5.3", + "inherits": "^2.0.4", + "parse-asn1": "^5.1.5", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + } + }, + "node_modules/browserify-sign/node_modules/bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", + "dev": true + }, + "node_modules/bs58": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", + "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", + "dev": true, + "dependencies": { + "base-x": "^3.0.2" + } + }, + "node_modules/bs58check": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz", + "integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==", + "dev": true, + "dependencies": { + "bs58": "^4.0.0", + "create-hash": "^1.1.0", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/buffer-reverse": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-reverse/-/buffer-reverse-1.0.1.tgz", + "integrity": "sha512-M87YIUBsZ6N924W57vDwT/aOu8hw7ZgdByz6ijksLjmHJELBASmYTTlNHRgjE+pTsT9oJXGaDSgqqwfdHotDUg==", + "dev": true + }, + "node_modules/buffer-to-arraybuffer": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/buffer-to-arraybuffer/-/buffer-to-arraybuffer-0.0.5.tgz", + "integrity": "sha512-3dthu5CYiVB1DEJp61FtApNnNndTckcqe4pFcLdvHtrpG+kcyekCJKg4MRiDcFW7A6AODnXB9U4dwQiCW5kzJQ==", + "dev": true + }, + "node_modules/buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==", + "dev": true + }, + "node_modules/bufferutil": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.7.tgz", + "integrity": "sha512-kukuqc39WOHtdxtw4UScxF/WVnMFVSQVKhtx3AjZJzhd0RGZZldcrfSEbVsWWe6KNH253574cq5F+wpv0G9pJw==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dev": true, + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cacheable-lookup": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-6.1.0.tgz", + "integrity": "sha512-KJ/Dmo1lDDhmW2XDPMo+9oiy/CeqosPguPCrgcVzKyZrL6pM1gU2GmPY/xo6OQPTUaA/c0kwHuywB4E6nmT9ww==", + "dev": true, + "engines": { + "node": ">=10.6.0" + } + }, + "node_modules/cacheable-request": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.2.tgz", + "integrity": "sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew==", + "dev": true, + "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" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cacheable-request/node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cacheable-request/node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/caller-callsite": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", + "integrity": "sha512-JuG3qI4QOftFsZyOn1qq87fq5grLIyk1JYd5lJmdA+fG7aQ9pA/i3JIJGcO3q0MrRcHlOt1U+ZeHW8Dq9axALQ==", + "dev": true, + "dependencies": { + "callsites": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/caller-callsite/node_modules/callsites": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", + "integrity": "sha512-ksWePWBloaWPxJYQ8TL0JHvtci6G5QTKwQ95RcWAa/lzoAKuAOflGdAK92hpHXjkwb8zLxoLNUoNYZgVsaJzvQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/caller-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", + "integrity": "sha512-MCL3sf6nCSXOwCTzvPKhN18TU7AHTvdtam8DAogxcrJ8Rjfbbg7Lgng64H9Iy+vUV6VGFClN/TyxBkAebLRR4A==", + "dev": true, + "dependencies": { + "caller-callsite": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camel-case": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz", + "integrity": "sha512-+MbKztAYHXPr1jNTSKQF52VpcFjwY5RkR7fxksV8Doo4KAYc5Fl4UJRgthBbTmEx8C54DqahhbLJkDwjI3PI/w==", + "dev": true, + "dependencies": { + "no-case": "^2.2.0", + "upper-case": "^1.1.1" + } + }, + "node_modules/camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha512-FxAv7HpHrXbh3aPo4o2qxHay2lkLY3x5Mw3KeE4KQE8ysVfziWeRZDwcjauvwBSGEC/nXUPzZy8zeh4HokqOnw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", + "dev": true + }, + "node_modules/catering": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/catering/-/catering-2.1.1.tgz", + "integrity": "sha512-K7Qy8O9p76sL3/3m7/zLKbRkyOlSZAgzEaLhyj2mXS8PsCud2Eo4hAb8aLtZqHh0QGqLcb9dlJSu6lHRVENm1w==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/cbor": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/cbor/-/cbor-5.2.0.tgz", + "integrity": "sha512-5IMhi9e1QU76ppa5/ajP1BmMWZ2FHkhAhjeVKQ/EFCgYSEaeVaoGtL7cxJskf9oCCk+XjzaIdc3IuU/dbA/o2A==", + "dev": true, + "dependencies": { + "bignumber.js": "^9.0.1", + "nofilter": "^1.0.4" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/cbor/node_modules/bignumber.js": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.1.tgz", + "integrity": "sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/chai": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.7.tgz", + "integrity": "sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==", + "dev": true, + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^4.1.2", + "get-func-name": "^2.0.0", + "loupe": "^2.3.1", + "pathval": "^1.1.1", + "type-detect": "^4.0.5" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chai-bn": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/chai-bn/-/chai-bn-0.2.2.tgz", + "integrity": "sha512-MzjelH0p8vWn65QKmEq/DLBG1Hle4WeyqT79ANhXZhn/UxRWO0OogkAxi5oGGtfzwU9bZR8mvbvYdoqNVWQwFg==", + "dev": true, + "peerDependencies": { + "bn.js": "^4.11.0", + "chai": "^4.0.0" + } + }, + "node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/change-case": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/change-case/-/change-case-3.0.2.tgz", + "integrity": "sha512-Mww+SLF6MZ0U6kdg11algyKd5BARbyM4TbFBepwowYSR5ClfQGCGtxNXgykpN0uF/bstWeaGDT4JWaDh8zWAHA==", + "dev": true, + "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" + } + }, + "node_modules/chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, + "node_modules/charenc": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", + "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/cheerio": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz", + "integrity": "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==", + "dev": true, + "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" + }, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + } + }, + "node_modules/cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "dev": true, + "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" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true + }, + "node_modules/ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true + }, + "node_modules/cids": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/cids/-/cids-0.7.5.tgz", + "integrity": "sha512-zT7mPeghoWAu+ppn8+BS1tQ5qGmbMfB4AregnQjA/qHY3GC1m1ptI9GkWNlgeu38r7CuRdXB47uY2XgAYt6QVA==", + "deprecated": "This module has been superseded by the multiformats module", + "dev": true, + "dependencies": { + "buffer": "^5.5.0", + "class-is": "^1.1.0", + "multibase": "~0.6.0", + "multicodec": "^1.0.0", + "multihashes": "~0.4.15" + }, + "engines": { + "node": ">=4.0.0", + "npm": ">=3.0.0" + } + }, + "node_modules/cids/node_modules/multicodec": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/multicodec/-/multicodec-1.0.4.tgz", + "integrity": "sha512-NDd7FeS3QamVtbgfvu5h7fd1IlbaC4EQ0/pgU4zqE2vdHCmBGsUa0TiM8/TdSeG6BMPC92OOCf8F1ocE/Wkrrg==", + "deprecated": "This module has been superseded by the multiformats module", + "dev": true, + "dependencies": { + "buffer": "^5.6.0", + "varint": "^5.0.0" + } + }, + "node_modules/cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/class-is": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/class-is/-/class-is-1.1.0.tgz", + "integrity": "sha512-rhjH9AG1fvabIDoGRVH587413LPjTZgmDF9fOFCbFJQV4yuocX1mHxxvXI4g3cGwbVY9wAYIoKlg1N79frJKQw==", + "dev": true + }, + "node_modules/classic-level": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/classic-level/-/classic-level-1.2.0.tgz", + "integrity": "sha512-qw5B31ANxSluWz9xBzklRWTUAJ1SXIdaVKTVS7HcTGKOAmExx65Wo5BUICW+YGORe2FOUaDghoI9ZDxj82QcFg==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "abstract-level": "^1.0.2", + "catering": "^2.1.0", + "module-error": "^1.0.1", + "napi-macros": "~2.0.0", + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw==", + "dev": true, + "dependencies": { + "restore-cursor": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cli-table3": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.5.1.tgz", + "integrity": "sha512-7Qg2Jrep1S/+Q3EceiZtQcDPWxhAvBw+ERf1162v4sikJrvojMHFqXt8QIVha8UlH9rgU0BeWPytZ9/TzYqlUw==", + "dev": true, + "dependencies": { + "object-assign": "^4.1.0", + "string-width": "^2.1.1" + }, + "engines": { + "node": ">=6" + }, + "optionalDependencies": { + "colors": "^1.1.2" + } + }, + "node_modules/cli-width": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz", + "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==", + "dev": true + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/cliui/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/clone-response": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", + "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", + "dev": true, + "dependencies": { + "mimic-response": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "dev": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/command-exists": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz", + "integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==", + "dev": true + }, + "node_modules/commander": { + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.18.0.tgz", + "integrity": "sha512-6CYPa+JP2ftfRU2qkDK+UTVeQYosOg/2GbcjIcKPHfinyOLPVGXu/ovN86RP49Re5ndJK1N0kuiidFFuepc4ZQ==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "engines": [ + "node >= 0.8" + ], + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/concat-stream/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/concat-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/concat-stream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/constant-case": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/constant-case/-/constant-case-2.0.0.tgz", + "integrity": "sha512-eS0N9WwmjTqrOmR3o83F5vW8Z+9R1HnVz3xmzT2PMFug9ly+Au/fxRWlEBSb6LcZwspSsEn9Xs1uw9YgzAg1EQ==", + "dev": true, + "dependencies": { + "snake-case": "^2.1.0", + "upper-case": "^1.1.1" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dev": true, + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-hash": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/content-hash/-/content-hash-2.5.2.tgz", + "integrity": "sha512-FvIQKy0S1JaWV10sMsA7TRx8bpU+pqPkhbsfvOJAdjRXvYxEckAwQWGwtRjiaJfh+E0DvcWUGqcdjwMGFjsSdw==", + "dev": true, + "dependencies": { + "cids": "^0.7.1", + "multicodec": "^0.5.5", + "multihashes": "^0.4.15" + } + }, + "node_modules/content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "dev": true + }, + "node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", + "dev": true + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dev": true, + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/cosmiconfig": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", + "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", + "dev": true, + "dependencies": { + "import-fresh": "^2.0.0", + "is-directory": "^0.3.1", + "js-yaml": "^3.13.1", + "parse-json": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cosmiconfig/node_modules/import-fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", + "integrity": "sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg==", + "dev": true, + "dependencies": { + "caller-path": "^2.0.0", + "resolve-from": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cosmiconfig/node_modules/resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "dev": true, + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/create-ecdh": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", + "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", + "dev": true, + "dependencies": { + "bn.js": "^4.1.0", + "elliptic": "^6.5.3" + } + }, + "node_modules/create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "dev": true, + "dependencies": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "node_modules/create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "dev": true, + "dependencies": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "node_modules/cross-fetch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", + "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", + "dev": true, + "dependencies": { + "node-fetch": "2.6.7" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/crypt": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", + "integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/crypto-addr-codec": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/crypto-addr-codec/-/crypto-addr-codec-0.1.7.tgz", + "integrity": "sha512-X4hzfBzNhy4mAc3UpiXEC/L0jo5E8wAa9unsnA8nNXYzXjCcGk83hfC5avJWCSGT8V91xMnAS9AKMHmjw5+XCg==", + "dev": true, + "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" + } + }, + "node_modules/crypto-browserify": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", + "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", + "dev": true, + "dependencies": { + "browserify-cipher": "^1.0.0", + "browserify-sign": "^4.0.0", + "create-ecdh": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.0", + "diffie-hellman": "^5.0.0", + "inherits": "^2.0.1", + "pbkdf2": "^3.0.3", + "public-encrypt": "^4.0.0", + "randombytes": "^2.0.0", + "randomfill": "^1.0.3" + }, + "engines": { + "node": "*" + } + }, + "node_modules/crypto-js": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-3.3.0.tgz", + "integrity": "sha512-DIT51nX0dCfKltpRiXV+/TVZq+Qq2NgF4644+K7Ttnla7zEzqc+kjJyiB96BHNyUTBxyjzRcZYpUdZa+QAqi6Q==", + "dev": true + }, + "node_modules/css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "dev": true, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/d": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", + "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "dev": true, + "dependencies": { + "es5-ext": "^0.10.50", + "type": "^1.0.1" + } + }, + "node_modules/dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", + "dev": true, + "dependencies": { + "assert-plus": "^1.0.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/death": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/death/-/death-1.1.0.tgz", + "integrity": "sha512-vsV6S4KVHvTGxbEcij7hkWRv0It+sGGWVOM67dQde/o5Xjnr+KmLjxWJii2uEObIrt1CcM9w0Yaovx+iOlIL+w==", + "dev": true + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decode-uri-component": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", + "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", + "dev": true, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-eql": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", + "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", + "dev": true, + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/define-properties": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", + "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", + "dev": true, + "dependencies": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/des.js": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz", + "integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-indent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-5.0.0.tgz", + "integrity": "sha512-rlpvsxUtM0PQvy9iZe640/IWwWYyBsTApREbA1pHOpmOUIl9MkP/U4z7vTtg4Oaojvqhxt7sdufnT0EzGaR31g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/detect-port": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/detect-port/-/detect-port-1.5.1.tgz", + "integrity": "sha512-aBzdj76lueB6uUst5iAs7+0H/oOjqI5D16XUWxlWMIMROhcM0rfsNVk93zTngq1dDNpoXRr++Sus7ETAExppAQ==", + "dev": true, + "dependencies": { + "address": "^1.0.1", + "debug": "4" + }, + "bin": { + "detect": "bin/detect-port.js", + "detect-port": "bin/detect-port.js" + } + }, + "node_modules/diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/diffie-hellman": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "dev": true, + "dependencies": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" + } + }, + "node_modules/difflib": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/difflib/-/difflib-0.2.4.tgz", + "integrity": "sha512-9YVwmMb0wQHQNr5J9m6BSj6fk4pfGITGQOOs+D9Fl+INODWFOfvhIU1hNv6GgR1RBoC/9NJcwu77zShxV0kT7w==", + "dev": true, + "dependencies": { + "heap": ">= 0.2.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/dom-walk": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", + "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==", + "dev": true + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.0.1.tgz", + "integrity": "sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==", + "dev": true, + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.1" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dot-case": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-2.1.1.tgz", + "integrity": "sha512-HnM6ZlFqcajLsyudHq7LeeLDr2rFAVYtDv/hV5qchQEidSck8j9OPUsXY9KwJv/lHMtYlX4DjRQqwFYa+0r8Ug==", + "dev": true, + "dependencies": { + "no-case": "^2.2.0" + } + }, + "node_modules/ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", + "dev": true, + "dependencies": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true + }, + "node_modules/elliptic": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", + "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", + "dev": true, + "dependencies": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/emoji-regex": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.2.1.tgz", + "integrity": "sha512-97g6QgOk8zlDRdgq1WxwgTMgEWGVAQvB5Fdpgc1MkNy56la5SKP9GsMXKDOdqwn90/41a8yPwIGk1Y6WVbeMQA==", + "dev": true + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "dev": true, + "dependencies": { + "ansi-colors": "^4.1.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/enquirer/node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/entities": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz", + "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-abstract": { + "version": "1.20.5", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.5.tgz", + "integrity": "sha512-7h8MM2EQhsCA7pU/Nv78qOXFpD8Rhqd12gYiSJVkrH9+e8VuA8JlPJK/hQjjlLv6pJvx/z1iRFKzYb0XT/RuAQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "function.prototype.name": "^1.1.5", + "get-intrinsic": "^1.1.3", + "get-symbol-description": "^1.0.0", + "gopd": "^1.0.1", + "has": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.3", + "is-callable": "^1.2.7", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.2", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.4.3", + "safe-regex-test": "^1.0.0", + "string.prototype.trimend": "^1.0.6", + "string.prototype.trimstart": "^1.0.6", + "unbox-primitive": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-array-method-boxes-properly": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz", + "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==", + "dev": true + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", + "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es5-ext": { + "version": "0.10.62", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz", + "integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "next-tick": "^1.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", + "dev": true, + "dependencies": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", + "dev": true + }, + "node_modules/es6-symbol": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", + "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", + "dev": true, + "dependencies": { + "d": "^1.0.1", + "ext": "^1.1.2" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/escodegen": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.1.tgz", + "integrity": "sha512-yhi5S+mNTOuRvyW4gWlg5W1byMaQGWWSYHXsuFZ7GBo7tpyOwi2EdzMP/QWxh9hwkD2m+wDVHJsxhRIj+v/b/A==", + "dev": true, + "dependencies": { + "esprima": "^2.7.1", + "estraverse": "^1.9.1", + "esutils": "^2.0.2", + "optionator": "^0.8.1" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=0.12.0" + }, + "optionalDependencies": { + "source-map": "~0.2.0" + } + }, + "node_modules/escodegen/node_modules/esprima": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", + "integrity": "sha512-OarPfz0lFCiW4/AV2Oy1Rp9qu0iusTKqykwTspGCZtPxmF81JR4MmIebvF1F9+UOKth2ZubLQ4XGGaU+hSn99A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/escodegen/node_modules/estraverse": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz", + "integrity": "sha512-25w1fMXQrGdoquWnScXZGckOv+Wes+JDnuN/+7ex3SauFRS72r2lFDec0EKPt2YD1wUJ/IrfEex+9yp4hfSOJA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/escodegen/node_modules/levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", + "dev": true, + "dependencies": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/escodegen/node_modules/optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "dependencies": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/escodegen/node_modules/prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/escodegen/node_modules/type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "dev": true, + "dependencies": { + "prelude-ls": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/eslint": { + "version": "7.32.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", + "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "7.12.11", + "@eslint/eslintrc": "^0.4.3", + "@humanwhocodes/config-array": "^0.5.0", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "enquirer": "^2.3.5", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^2.1.0", + "eslint-visitor-keys": "^2.0.0", + "espree": "^7.3.1", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.1.2", + "globals": "^13.6.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.0.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "progress": "^2.0.0", + "regexpp": "^3.1.0", + "semver": "^7.2.1", + "strip-ansi": "^6.0.0", + "strip-json-comments": "^3.1.0", + "table": "^6.0.9", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-standard": { + "version": "16.0.3", + "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-16.0.3.tgz", + "integrity": "sha512-x4fmJL5hGqNJKGHSjnLdgA6U6h1YW/G2dW9fA+cyVur4SK6lyue8+UgNKWlZtUDTXvgKDD/Oa3GQjmB5kjtVvg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "peerDependencies": { + "eslint": "^7.12.1", + "eslint-plugin-import": "^2.22.1", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-promise": "^4.2.1 || ^5.0.0" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz", + "integrity": "sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==", + "dev": true, + "dependencies": { + "debug": "^3.2.7", + "resolve": "^1.20.0" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.4.tgz", + "integrity": "sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==", + "dev": true, + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-es": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz", + "integrity": "sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==", + "dev": true, + "dependencies": { + "eslint-utils": "^2.0.0", + "regexpp": "^3.0.0" + }, + "engines": { + "node": ">=8.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=4.19.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.26.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz", + "integrity": "sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.4", + "array.prototype.flat": "^1.2.5", + "debug": "^2.6.9", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.6", + "eslint-module-utils": "^2.7.3", + "has": "^1.0.3", + "is-core-module": "^2.8.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.values": "^1.1.5", + "resolve": "^1.22.0", + "tsconfig-paths": "^3.14.1" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/eslint-plugin-mocha": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-mocha/-/eslint-plugin-mocha-10.1.0.tgz", + "integrity": "sha512-xLqqWUF17llsogVOC+8C6/jvQ+4IoOREbN7ZCHuOHuD6cT5cDD4h7f2LgsZuzMAiwswWE21tO7ExaknHVDrSkw==", + "dev": true, + "dependencies": { + "eslint-utils": "^3.0.0", + "rambda": "^7.1.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-mocha/node_modules/eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5" + } + }, + "node_modules/eslint-plugin-node": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz", + "integrity": "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==", + "dev": true, + "dependencies": { + "eslint-plugin-es": "^3.0.0", + "eslint-utils": "^2.0.0", + "ignore": "^5.1.1", + "minimatch": "^3.0.4", + "resolve": "^1.10.1", + "semver": "^6.1.0" + }, + "engines": { + "node": ">=8.10.0" + }, + "peerDependencies": { + "eslint": ">=5.16.0" + } + }, + "node_modules/eslint-plugin-node/node_modules/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/eslint-plugin-node/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-promise": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-5.2.0.tgz", + "integrity": "sha512-SftLb1pUG01QYq2A/hGAWfDRXqYD82zE7j7TopDOyNdU+7SvvoXREls/+PRTY17vUXzXnZA/zfnyKgRH6x4JJw==", + "dev": true, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "peerDependencies": { + "eslint": "^7.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^1.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/espree": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", + "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", + "dev": true, + "dependencies": { + "acorn": "^7.4.0", + "acorn-jsx": "^5.3.1", + "eslint-visitor-keys": "^1.3.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esquery/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eth-ens-namehash": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/eth-ens-namehash/-/eth-ens-namehash-2.0.8.tgz", + "integrity": "sha512-VWEI1+KJfz4Km//dadyvBBoBeSQ0MHTXPvr8UIXiLW6IanxvAV+DmlZAijZwAyggqGUfwQBeHf7tc9wzc1piSw==", + "dev": true, + "dependencies": { + "idna-uts46-hx": "^2.3.1", + "js-sha3": "^0.5.7" + } + }, + "node_modules/eth-ens-namehash/node_modules/js-sha3": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.5.7.tgz", + "integrity": "sha512-GII20kjaPX0zJ8wzkTbNDYMY7msuZcTWk8S5UOh6806Jq/wz1J8/bnr8uGU0DAUmYDjj2Mr4X1cW8v/GLYnR+g==", + "dev": true + }, + "node_modules/eth-gas-reporter": { + "version": "0.2.25", + "resolved": "https://registry.npmjs.org/eth-gas-reporter/-/eth-gas-reporter-0.2.25.tgz", + "integrity": "sha512-1fRgyE4xUB8SoqLgN3eDfpDfwEfRxh2Sz1b7wzFbyQA+9TekMmvSjjoRu9SKcSVyK+vLkLIsVbJDsTWjw195OQ==", + "dev": true, + "dependencies": { + "@ethersproject/abi": "^5.0.0-beta.146", + "@solidity-parser/parser": "^0.14.0", + "cli-table3": "^0.5.0", + "colors": "1.4.0", + "ethereum-cryptography": "^1.0.3", + "ethers": "^4.0.40", + "fs-readdir-recursive": "^1.1.0", + "lodash": "^4.17.14", + "markdown-table": "^1.1.3", + "mocha": "^7.1.1", + "req-cwd": "^2.0.0", + "request": "^2.88.0", + "request-promise-native": "^1.0.5", + "sha1": "^1.1.1", + "sync-request": "^6.0.0" + }, + "peerDependencies": { + "@codechecks/client": "^0.1.0" + }, + "peerDependenciesMeta": { + "@codechecks/client": { + "optional": true + } + } + }, + "node_modules/eth-gas-reporter/node_modules/ansi-colors": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", + "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/eth-gas-reporter/node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/eth-gas-reporter/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/eth-gas-reporter/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/eth-gas-reporter/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/eth-gas-reporter/node_modules/chalk/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/eth-gas-reporter/node_modules/chokidar": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.0.tgz", + "integrity": "sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.2.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.1.1" + } + }, + "node_modules/eth-gas-reporter/node_modules/cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "dependencies": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + } + }, + "node_modules/eth-gas-reporter/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/eth-gas-reporter/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/eth-gas-reporter/node_modules/debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eth-gas-reporter/node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eth-gas-reporter/node_modules/diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/eth-gas-reporter/node_modules/emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "node_modules/eth-gas-reporter/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/eth-gas-reporter/node_modules/ethereum-cryptography": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-1.1.2.tgz", + "integrity": "sha512-XDSJlg4BD+hq9N2FjvotwUET9Tfxpxc3kWGE2AqUG5vcbeunnbImVk3cj6e/xT3phdW21mE8R5IugU4fspQDcQ==", + "dev": true, + "dependencies": { + "@noble/hashes": "1.1.2", + "@noble/secp256k1": "1.6.3", + "@scure/bip32": "1.1.0", + "@scure/bip39": "1.1.0" + } + }, + "node_modules/eth-gas-reporter/node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/eth-gas-reporter/node_modules/flat": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.1.tgz", + "integrity": "sha512-FmTtBsHskrU6FJ2VxCnsDb84wu9zhmO3cUX2kGFb5tuwhfXxGciiT0oRY+cck35QmG+NmGh5eLz6lLCpWTqwpA==", + "dev": true, + "dependencies": { + "is-buffer": "~2.0.3" + }, + "bin": { + "flat": "cli.js" + } + }, + "node_modules/eth-gas-reporter/node_modules/fsevents": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", + "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "deprecated": "\"Please update to latest v2.3 or v2.2\"", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/eth-gas-reporter/node_modules/glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eth-gas-reporter/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/eth-gas-reporter/node_modules/js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/eth-gas-reporter/node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/eth-gas-reporter/node_modules/log-symbols": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", + "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==", + "dev": true, + "dependencies": { + "chalk": "^2.4.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/eth-gas-reporter/node_modules/minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eth-gas-reporter/node_modules/mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "dependencies": { + "minimist": "^1.2.5" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/eth-gas-reporter/node_modules/mocha": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-7.2.0.tgz", + "integrity": "sha512-O9CIypScywTVpNaRrCAgoUnJgozpIofjKUYmJhiCIJMiuYnLI6otcb1/kpW9/n/tJODHGZ7i8aLQoDVsMtOKQQ==", + "dev": true, + "dependencies": { + "ansi-colors": "3.2.3", + "browser-stdout": "1.3.1", + "chokidar": "3.3.0", + "debug": "3.2.6", + "diff": "3.5.0", + "escape-string-regexp": "1.0.5", + "find-up": "3.0.0", + "glob": "7.1.3", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "3.13.1", + "log-symbols": "3.0.0", + "minimatch": "3.0.4", + "mkdirp": "0.5.5", + "ms": "2.1.1", + "node-environment-flags": "1.0.6", + "object.assign": "4.1.0", + "strip-json-comments": "2.0.1", + "supports-color": "6.0.0", + "which": "1.3.1", + "wide-align": "1.1.3", + "yargs": "13.3.2", + "yargs-parser": "13.1.2", + "yargs-unparser": "1.6.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mochajs" + } + }, + "node_modules/eth-gas-reporter/node_modules/ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "node_modules/eth-gas-reporter/node_modules/object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eth-gas-reporter/node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/eth-gas-reporter/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/eth-gas-reporter/node_modules/readdirp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.2.0.tgz", + "integrity": "sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ==", + "dev": true, + "dependencies": { + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/eth-gas-reporter/node_modules/string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "dependencies": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/eth-gas-reporter/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/eth-gas-reporter/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eth-gas-reporter/node_modules/supports-color": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", + "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/eth-gas-reporter/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/eth-gas-reporter/node_modules/wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/eth-gas-reporter/node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true + }, + "node_modules/eth-gas-reporter/node_modules/yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "dev": true, + "dependencies": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" + } + }, + "node_modules/eth-gas-reporter/node_modules/yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "dev": true, + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, + "node_modules/eth-gas-reporter/node_modules/yargs-unparser": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.0.tgz", + "integrity": "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==", + "dev": true, + "dependencies": { + "flat": "^4.1.0", + "lodash": "^4.17.15", + "yargs": "^13.3.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/eth-lib": { + "version": "0.1.29", + "resolved": "https://registry.npmjs.org/eth-lib/-/eth-lib-0.1.29.tgz", + "integrity": "sha512-bfttrr3/7gG4E02HoWTDUcDDslN003OlOoBxk9virpAZQ1ja/jDgwkWB8QfJF7ojuEowrqy+lzp9VcJG7/k5bQ==", + "dev": true, + "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" + } + }, + "node_modules/eth-lib/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/eth-lib/node_modules/ws": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", + "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", + "dev": true, + "dependencies": { + "async-limiter": "~1.0.0", + "safe-buffer": "~5.1.0", + "ultron": "~1.1.0" + } + }, + "node_modules/eth-sig-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/eth-sig-util/-/eth-sig-util-3.0.1.tgz", + "integrity": "sha512-0Us50HiGGvZgjtWTyAI/+qTzYPMLy5Q451D0Xy68bxq1QMWdoOddDwGvsqcFT27uohKgalM9z/yxplyt+mY2iQ==", + "deprecated": "Deprecated in favor of '@metamask/eth-sig-util'", + "dev": true, + "dependencies": { + "ethereumjs-abi": "^0.6.8", + "ethereumjs-util": "^5.1.1", + "tweetnacl": "^1.0.3", + "tweetnacl-util": "^0.15.0" + } + }, + "node_modules/eth-sig-util/node_modules/ethereumjs-util": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.1.tgz", + "integrity": "sha512-v3kT+7zdyCm1HIqWlLNrHGqHGLpGYIhjeHxQjnDXjLT2FyGJDsd3LWMYUo7pAFRrk86CR3nUJfhC81CCoJNNGQ==", + "dev": true, + "dependencies": { + "bn.js": "^4.11.0", + "create-hash": "^1.1.2", + "elliptic": "^6.5.2", + "ethereum-cryptography": "^0.1.3", + "ethjs-util": "^0.1.3", + "rlp": "^2.0.0", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/ethereum-bloom-filters": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/ethereum-bloom-filters/-/ethereum-bloom-filters-1.0.10.tgz", + "integrity": "sha512-rxJ5OFN3RwjQxDcFP2Z5+Q9ho4eIdEmSc2ht0fCu8Se9nbXjZ7/031uXoUYJ87KHCOdVeiUuwSnoS7hmYAGVHA==", + "dev": true, + "dependencies": { + "js-sha3": "^0.8.0" + } + }, + "node_modules/ethereum-cryptography": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", + "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", + "dev": true, + "dependencies": { + "@types/pbkdf2": "^3.0.0", + "@types/secp256k1": "^4.0.1", + "blakejs": "^1.1.0", + "browserify-aes": "^1.2.0", + "bs58check": "^2.1.2", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "hash.js": "^1.1.7", + "keccak": "^3.0.0", + "pbkdf2": "^3.0.17", + "randombytes": "^2.1.0", + "safe-buffer": "^5.1.2", + "scrypt-js": "^3.0.0", + "secp256k1": "^4.0.1", + "setimmediate": "^1.0.5" + } + }, + "node_modules/ethereum-ens": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/ethereum-ens/-/ethereum-ens-0.8.0.tgz", + "integrity": "sha512-a8cBTF4AWw1Q1Y37V1LSCS9pRY4Mh3f8vCg5cbXCCEJ3eno1hbI/+Ccv9SZLISYpqQhaglP3Bxb/34lS4Qf7Bg==", + "dev": true, + "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" + } + }, + "node_modules/ethereum-ens/node_modules/js-sha3": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.5.7.tgz", + "integrity": "sha512-GII20kjaPX0zJ8wzkTbNDYMY7msuZcTWk8S5UOh6806Jq/wz1J8/bnr8uGU0DAUmYDjj2Mr4X1cW8v/GLYnR+g==", + "dev": true + }, + "node_modules/ethereumjs-abi": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/ethereumjs-abi/-/ethereumjs-abi-0.6.8.tgz", + "integrity": "sha512-Tx0r/iXI6r+lRsdvkFDlut0N08jWMnKRZ6Gkq+Nmw75lZe4e6o3EkSnkaBP5NF6+m5PTGAr9JP43N3LyeoglsA==", + "dev": true, + "dependencies": { + "bn.js": "^4.11.8", + "ethereumjs-util": "^6.0.0" + } + }, + "node_modules/ethereumjs-abi/node_modules/@types/bn.js": { + "version": "4.11.6", + "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-4.11.6.tgz", + "integrity": "sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/ethereumjs-abi/node_modules/ethereumjs-util": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-6.2.1.tgz", + "integrity": "sha512-W2Ktez4L01Vexijrm5EB6w7dg4n/TgpoYU4avuT5T3Vmnw/eCRtiBrJfQYS/DCSvDIOLn2k57GcHdeBcgVxAqw==", + "dev": true, + "dependencies": { + "@types/bn.js": "^4.11.3", + "bn.js": "^4.11.0", + "create-hash": "^1.1.2", + "elliptic": "^6.5.2", + "ethereum-cryptography": "^0.1.3", + "ethjs-util": "0.1.6", + "rlp": "^2.2.3" + } + }, + "node_modules/ethereumjs-util": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz", + "integrity": "sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg==", + "dev": true, + "dependencies": { + "@types/bn.js": "^5.1.0", + "bn.js": "^5.1.2", + "create-hash": "^1.1.2", + "ethereum-cryptography": "^0.1.3", + "rlp": "^2.2.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/ethereumjs-util/node_modules/bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", + "dev": true + }, + "node_modules/ethereumjs-wallet": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/ethereumjs-wallet/-/ethereumjs-wallet-1.0.2.tgz", + "integrity": "sha512-CCWV4RESJgRdHIvFciVQFnCHfqyhXWchTPlkfp28Qc53ufs+doi5I/cV2+xeK9+qEo25XCWfP9MiL+WEPAZfdA==", + "dev": true, + "dependencies": { + "aes-js": "^3.1.2", + "bs58check": "^2.1.2", + "ethereum-cryptography": "^0.1.3", + "ethereumjs-util": "^7.1.2", + "randombytes": "^2.1.0", + "scrypt-js": "^3.0.1", + "utf8": "^3.0.0", + "uuid": "^8.3.2" + } + }, + "node_modules/ethers": { + "version": "4.0.49", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-4.0.49.tgz", + "integrity": "sha512-kPltTvWiyu+OktYy1IStSO16i2e7cS9D9OxZ81q2UUaiNPVrm/RTcbxamCXF9VUSKzJIdJV68EAIhTEVBalRWg==", + "dev": true, + "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" + } + }, + "node_modules/ethers/node_modules/aes-js": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.0.0.tgz", + "integrity": "sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw==", + "dev": true + }, + "node_modules/ethers/node_modules/hash.js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.3.tgz", + "integrity": "sha512-/UETyP0W22QILqS+6HowevwhEFJ3MBJnwTf75Qob9Wz9t0DPuisL8kW8YZMK62dHAKE1c1p+gY1TtOLY+USEHA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/ethers/node_modules/js-sha3": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.5.7.tgz", + "integrity": "sha512-GII20kjaPX0zJ8wzkTbNDYMY7msuZcTWk8S5UOh6806Jq/wz1J8/bnr8uGU0DAUmYDjj2Mr4X1cW8v/GLYnR+g==", + "dev": true + }, + "node_modules/ethers/node_modules/scrypt-js": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-2.0.4.tgz", + "integrity": "sha512-4KsaGcPnuhtCZQCxFxN3GVYIhKFPTdLd8PLC552XwbMndtD0cjRFAhDuuydXQ0h08ZfPgzqe6EKHozpuH74iDw==", + "dev": true + }, + "node_modules/ethers/node_modules/setimmediate": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.4.tgz", + "integrity": "sha512-/TjEmXQVEzdod/FFskf3o7oOAsGhHf2j1dZqRFbDzq4F3mvvxflIIi4Hd3bLQE9y/CpwqfSQam5JakI/mi3Pog==", + "dev": true + }, + "node_modules/ethers/node_modules/uuid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.1.tgz", + "integrity": "sha512-nWg9+Oa3qD2CQzHIP4qKUqwNfzKn8P0LtFhotaCTFchsV7ZfDhAybeip/HZVeMIpZi9JgY1E3nUlwaCmZT1sEg==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "dev": true + }, + "node_modules/ethjs-abi": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/ethjs-abi/-/ethjs-abi-0.2.1.tgz", + "integrity": "sha512-g2AULSDYI6nEJyJaEVEXtTimRY2aPC2fi7ddSy0W+LXvEVL8Fe1y76o43ecbgdUKwZD+xsmEgX1yJr1Ia3r1IA==", + "dev": true, + "dependencies": { + "bn.js": "4.11.6", + "js-sha3": "0.5.5", + "number-to-bn": "1.7.0" + }, + "engines": { + "node": ">=6.5.0", + "npm": ">=3" + } + }, + "node_modules/ethjs-abi/node_modules/bn.js": { + "version": "4.11.6", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.6.tgz", + "integrity": "sha512-XWwnNNFCuuSQ0m3r3C4LE3EiORltHd9M05pq6FOlVeiophzRbMo50Sbz1ehl8K3Z+jw9+vmgnXefY1hz8X+2wA==", + "dev": true + }, + "node_modules/ethjs-abi/node_modules/js-sha3": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.5.5.tgz", + "integrity": "sha512-yLLwn44IVeunwjpDVTDZmQeVbB0h+dZpY2eO68B/Zik8hu6dH+rKeLxwua79GGIvW6xr8NBAcrtiUbYrTjEFTA==", + "dev": true + }, + "node_modules/ethjs-unit": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/ethjs-unit/-/ethjs-unit-0.1.6.tgz", + "integrity": "sha512-/Sn9Y0oKl0uqQuvgFk/zQgR7aw1g36qX/jzSQ5lSwlO0GigPymk4eGQfeNTD03w1dPOqfz8V77Cy43jH56pagw==", + "dev": true, + "dependencies": { + "bn.js": "4.11.6", + "number-to-bn": "1.7.0" + }, + "engines": { + "node": ">=6.5.0", + "npm": ">=3" + } + }, + "node_modules/ethjs-unit/node_modules/bn.js": { + "version": "4.11.6", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.6.tgz", + "integrity": "sha512-XWwnNNFCuuSQ0m3r3C4LE3EiORltHd9M05pq6FOlVeiophzRbMo50Sbz1ehl8K3Z+jw9+vmgnXefY1hz8X+2wA==", + "dev": true + }, + "node_modules/ethjs-util": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/ethjs-util/-/ethjs-util-0.1.6.tgz", + "integrity": "sha512-CUnVOQq7gSpDHZVVrQW8ExxUETWrnrvXYvYz55wOU8Uj4VCgw56XC2B/fVqQN+f7gmrnRHSLVnFAwsCuNwji8w==", + "dev": true, + "dependencies": { + "is-hex-prefixed": "1.0.0", + "strip-hex-prefix": "1.0.0" + }, + "engines": { + "node": ">=6.5.0", + "npm": ">=3" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.4.tgz", + "integrity": "sha512-rlaVLnVxtxvoyLsQQFBx53YmXHDxRIzzTLbdfxqi4yocpSjAxXwkU0cScM5JgSKMqEhrZpnvQ2D9gjylR0AimQ==", + "dev": true + }, + "node_modules/evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "dev": true, + "dependencies": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/express": { + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", + "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "dev": true, + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.1", + "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" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/express/node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/ext": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", + "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", + "dev": true, + "dependencies": { + "type": "^2.7.2" + } + }, + "node_modules/ext/node_modules/type": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", + "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==", + "dev": true + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "node_modules/external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dev": true, + "dependencies": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", + "dev": true, + "engines": [ + "node >=0.6.0" + ] + }, + "node_modules/fast-check": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.1.1.tgz", + "integrity": "sha512-3vtXinVyuUKCKFKYcwXhGE6NtGWkqF8Yh3rvMZNzmwz8EPrgoc/v4pDdLHyLnCyCI5MZpZZkDEwFyXyEONOxpA==", + "dev": true, + "dependencies": { + "pure-rand": "^5.0.1" + }, + "engines": { + "node": ">=8.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-diff": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", + "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.14.0.tgz", + "integrity": "sha512-eR2D+V9/ExcbF9ls441yIuN6TI2ED1Y2ZcA5BmMtJsOkWOFRJQ0Jt0g1UwqXJJVAb+V+umH5Dfr8oh4EVP7VVg==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/figures/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dev": true, + "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" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "bin": { + "flat": "cli.js" + } + }, + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "dev": true + }, + "node_modules/follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/form-data-encoder": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.1.tgz", + "integrity": "sha512-EFRDrsMm/kyqbTQocNvRXMLjc7Es2Vk+IQFx/YW7hkUH1eBl4J1fqiP34l74Yt0pFLCNpc06fkbVk00008mzjg==", + "dev": true + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fp-ts": { + "version": "1.19.3", + "resolved": "https://registry.npmjs.org/fp-ts/-/fp-ts-1.19.3.tgz", + "integrity": "sha512-H5KQDspykdHuztLTg+ajGN0Z2qUjcEf3Ybxc6hLt0k7/zPkn29XnKnxlBPyW2XIddWrGaJBzBl4VLYOtk39yZg==", + "dev": true + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/fs-minipass": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", + "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", + "dev": true, + "dependencies": { + "minipass": "^2.6.0" + } + }, + "node_modules/fs-readdir-recursive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", + "integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==", + "dev": true + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", + "dev": true + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", + "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-port": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-3.2.0.tgz", + "integrity": "sha512-x5UJKlgeUiNT8nyo/AcnwLnZuZNcSjSw0kogRB+Whd1fjjFq4B1hySFxSFWWSn4mIBzg3sRNUDFYc4g5gjPoLg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", + "dev": true, + "dependencies": { + "assert-plus": "^1.0.0" + } + }, + "node_modules/ghost-testrpc": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/ghost-testrpc/-/ghost-testrpc-0.0.2.tgz", + "integrity": "sha512-i08dAEgJ2g8z5buJIrCTduwPIhih3DP+hOCTyyryikfV8T0bNvHnGXO67i0DD1H4GBDETTclPy9njZbfluQYrQ==", + "dev": true, + "dependencies": { + "chalk": "^2.4.2", + "node-emoji": "^1.10.0" + }, + "bin": { + "testrpc-sc": "index.js" + } + }, + "node_modules/ghost-testrpc/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/ghost-testrpc/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/ghost-testrpc/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/ghost-testrpc/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/ghost-testrpc/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/ghost-testrpc/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/ghost-testrpc/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/glob": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", + "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.2.tgz", + "integrity": "sha512-bNH9mmM9qsJ2X4r2Nat1B//1dJVcn3+iBLa3IgqJ7EbGaDNepL9QSHOxN4ng33s52VMMhhIfgCYDk3C4ZmlDAg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/global": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz", + "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==", + "dev": true, + "dependencies": { + "min-document": "^2.19.0", + "process": "^0.11.10" + } + }, + "node_modules/global-modules": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", + "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", + "dev": true, + "dependencies": { + "global-prefix": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/global-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", + "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", + "dev": true, + "dependencies": { + "ini": "^1.3.5", + "kind-of": "^6.0.2", + "which": "^1.3.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/global-prefix/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/globals": { + "version": "13.19.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.19.0.tgz", + "integrity": "sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-10.0.2.tgz", + "integrity": "sha512-7dUi7RvCoT/xast/o/dLN53oqND4yk0nsHkhRgn9w65C4PofCLOoJ39iSOg+qVDdWQPIEj+eszMHQ+aLVwwQSg==", + "dev": true, + "dependencies": { + "@types/glob": "^7.1.1", + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.0.3", + "glob": "^7.1.3", + "ignore": "^5.1.1", + "merge2": "^1.2.3", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/globby/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/globby/node_modules/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/got": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/got/-/got-12.1.0.tgz", + "integrity": "sha512-hBv2ty9QN2RdbJJMK3hesmSkFTjVIHyIDDbssCKnSmq62edGgImJWD10Eb1k77TiV1bxloxqcFAVK8+9pkhOig==", + "dev": true, + "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" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true + }, + "node_modules/graphlib": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/graphlib/-/graphlib-2.1.8.tgz", + "integrity": "sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==", + "dev": true, + "dependencies": { + "lodash": "^4.17.15" + } + }, + "node_modules/growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true, + "engines": { + "node": ">=4.x" + } + }, + "node_modules/handlebars": { + "version": "4.7.7", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", + "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.0", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/handlebars/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "deprecated": "this library is no longer supported", + "dev": true, + "dependencies": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/hardhat": { + "version": "2.12.4", + "resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.12.4.tgz", + "integrity": "sha512-rc9S2U/4M+77LxW1Kg7oqMMmjl81tzn5rNFARhbXKUA1am/nhfMJEujOjuKvt+ZGMiZ11PYSe8gyIpB/aRNDgw==", + "dev": true, + "dependencies": { + "@ethersproject/abi": "^5.1.2", + "@metamask/eth-sig-util": "^4.0.0", + "@nomicfoundation/ethereumjs-block": "^4.0.0", + "@nomicfoundation/ethereumjs-blockchain": "^6.0.0", + "@nomicfoundation/ethereumjs-common": "^3.0.0", + "@nomicfoundation/ethereumjs-evm": "^1.0.0", + "@nomicfoundation/ethereumjs-rlp": "^4.0.0", + "@nomicfoundation/ethereumjs-statemanager": "^1.0.0", + "@nomicfoundation/ethereumjs-trie": "^5.0.0", + "@nomicfoundation/ethereumjs-tx": "^4.0.0", + "@nomicfoundation/ethereumjs-util": "^8.0.0", + "@nomicfoundation/ethereumjs-vm": "^6.0.0", + "@nomicfoundation/solidity-analyzer": "^0.1.0", + "@sentry/node": "^5.18.1", + "@types/bn.js": "^5.1.0", + "@types/lru-cache": "^5.1.0", + "abort-controller": "^3.0.0", + "adm-zip": "^0.4.16", + "aggregate-error": "^3.0.0", + "ansi-escapes": "^4.3.0", + "chalk": "^2.4.2", + "chokidar": "^3.4.0", + "ci-info": "^2.0.0", + "debug": "^4.1.1", + "enquirer": "^2.3.0", + "env-paths": "^2.2.0", + "ethereum-cryptography": "^1.0.3", + "ethereumjs-abi": "^0.6.8", + "find-up": "^2.1.0", + "fp-ts": "1.19.3", + "fs-extra": "^7.0.1", + "glob": "7.2.0", + "immutable": "^4.0.0-rc.12", + "io-ts": "1.10.4", + "keccak": "^3.0.2", + "lodash": "^4.17.11", + "mnemonist": "^0.38.0", + "mocha": "^10.0.0", + "p-map": "^4.0.0", + "qs": "^6.7.0", + "raw-body": "^2.4.1", + "resolve": "1.17.0", + "semver": "^6.3.0", + "solc": "0.7.3", + "source-map-support": "^0.5.13", + "stacktrace-parser": "^0.1.10", + "tsort": "0.0.1", + "undici": "^5.4.0", + "uuid": "^8.3.2", + "ws": "^7.4.6" + }, + "bin": { + "hardhat": "internal/cli/cli.js" + }, + "engines": { + "node": "^14.0.0 || ^16.0.0 || ^18.0.0" + }, + "peerDependencies": { + "ts-node": "*", + "typescript": "*" + }, + "peerDependenciesMeta": { + "ts-node": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/hardhat-gas-reporter": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/hardhat-gas-reporter/-/hardhat-gas-reporter-1.0.9.tgz", + "integrity": "sha512-INN26G3EW43adGKBNzYWOlI3+rlLnasXTwW79YNnUhXPDa+yHESgt639dJEs37gCjhkbNKcRRJnomXEuMFBXJg==", + "dev": true, + "dependencies": { + "array-uniq": "1.0.3", + "eth-gas-reporter": "^0.2.25", + "sha1": "^1.1.1" + }, + "peerDependencies": { + "hardhat": "^2.0.2" + } + }, + "node_modules/hardhat-ignore-warnings": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/hardhat-ignore-warnings/-/hardhat-ignore-warnings-0.2.6.tgz", + "integrity": "sha512-GQgvjprONI8VF8b85+QJ8H9v3L9TCCtQvUx+9QaRL+sCPw1cOZHfhlEz9V6Lq7GNCQMqBORVzNzUzoP/tKAEQQ==", + "dev": true, + "dependencies": { + "minimatch": "^5.1.0", + "node-interval-tree": "^2.0.1", + "solidity-comments": "^0.0.2" + } + }, + "node_modules/hardhat-ignore-warnings/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/hardhat-ignore-warnings/node_modules/minimatch": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.2.tgz", + "integrity": "sha512-bNH9mmM9qsJ2X4r2Nat1B//1dJVcn3+iBLa3IgqJ7EbGaDNepL9QSHOxN4ng33s52VMMhhIfgCYDk3C4ZmlDAg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/hardhat/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/hardhat/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/hardhat/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/hardhat/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/hardhat/node_modules/commander": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/commander/-/commander-3.0.2.tgz", + "integrity": "sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==", + "dev": true + }, + "node_modules/hardhat/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/hardhat/node_modules/ethereum-cryptography": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-1.1.2.tgz", + "integrity": "sha512-XDSJlg4BD+hq9N2FjvotwUET9Tfxpxc3kWGE2AqUG5vcbeunnbImVk3cj6e/xT3phdW21mE8R5IugU4fspQDcQ==", + "dev": true, + "dependencies": { + "@noble/hashes": "1.1.2", + "@noble/secp256k1": "1.6.3", + "@scure/bip32": "1.1.0", + "@scure/bip39": "1.1.0" + } + }, + "node_modules/hardhat/node_modules/find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==", + "dev": true, + "dependencies": { + "locate-path": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/hardhat/node_modules/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/hardhat/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/hardhat/node_modules/jsonfile": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", + "integrity": "sha512-PKllAqbgLgxHaj8TElYymKCAgrASebJrWpTnEkOaTowt23VKXXN0sUeriJ+eh7y6ufb/CC5ap11pz71/cM0hUw==", + "dev": true, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/hardhat/node_modules/locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==", + "dev": true, + "dependencies": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/hardhat/node_modules/p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "dependencies": { + "p-try": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/hardhat/node_modules/p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==", + "dev": true, + "dependencies": { + "p-limit": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/hardhat/node_modules/p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/hardhat/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/hardhat/node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/hardhat/node_modules/resolve": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", + "dev": true, + "dependencies": { + "path-parse": "^1.0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hardhat/node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/hardhat/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/hardhat/node_modules/solc": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/solc/-/solc-0.7.3.tgz", + "integrity": "sha512-GAsWNAjGzIDg7VxzP6mPjdurby3IkGCjQcM8GFYZT6RyaoUZKmMU6Y7YwG+tFGhv7dwZ8rmR4iwFDrrD99JwqA==", + "dev": true, + "dependencies": { + "command-exists": "^1.2.8", + "commander": "3.0.2", + "follow-redirects": "^1.12.1", + "fs-extra": "^0.30.0", + "js-sha3": "0.8.0", + "memorystream": "^0.3.1", + "require-from-string": "^2.0.0", + "semver": "^5.5.0", + "tmp": "0.0.33" + }, + "bin": { + "solcjs": "solcjs" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/hardhat/node_modules/solc/node_modules/fs-extra": { + "version": "0.30.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.30.0.tgz", + "integrity": "sha512-UvSPKyhMn6LEd/WpUaV9C9t3zATuqoqfWc3QdPhPLb58prN9tqYPlPWi8Krxi44loBoUzlobqZ3+8tGpxxSzwA==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "jsonfile": "^2.1.0", + "klaw": "^1.0.0", + "path-is-absolute": "^1.0.0", + "rimraf": "^2.2.8" + } + }, + "node_modules/hardhat/node_modules/solc/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/hardhat/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hash-base": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", + "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/header-case": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/header-case/-/header-case-1.0.1.tgz", + "integrity": "sha512-i0q9mkOeSuhXw6bGgiQCCBgY/jlZuV/7dZXyZ9c6LcBrqwvT8eT719E9uxE5LiZftdl+z81Ugbg/VvXV4OJOeQ==", + "dev": true, + "dependencies": { + "no-case": "^2.2.0", + "upper-case": "^1.1.3" + } + }, + "node_modules/heap": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/heap/-/heap-0.2.7.tgz", + "integrity": "sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg==", + "dev": true + }, + "node_modules/highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/highlightjs-solidity": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/highlightjs-solidity/-/highlightjs-solidity-2.0.5.tgz", + "integrity": "sha512-ReXxQSGQkODMUgHcWzVSnfDCDrL2HshOYgw3OlIYmfHeRzUPkfJTUIp95pK4CmbiNG2eMTOmNLpfCz9Zq7Cwmg==", + "dev": true + }, + "node_modules/hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", + "dev": true, + "dependencies": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "node_modules/htmlparser2": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.1.tgz", + "integrity": "sha512-4lVbmc1diZC7GUJQtRQ5yBAeUCL1exyMwmForWkRLnwyzWBFxN633SALPMGYaWZvKe9j1pRZJpauvmxENSp/EA==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "entities": "^4.3.0" + } + }, + "node_modules/http-basic": { + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/http-basic/-/http-basic-8.1.3.tgz", + "integrity": "sha512-/EcDMwJZh3mABI2NhGfHOGOeOZITqfkEO4p/xK+l3NpyncIHUQBoMvCSF/b5GqvKtySC2srL/GGG3+EtlqlmCw==", + "dev": true, + "dependencies": { + "caseless": "^0.12.0", + "concat-stream": "^1.6.2", + "http-response-object": "^3.0.1", + "parse-cache-control": "^1.0.1" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", + "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", + "dev": true + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-https": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/http-https/-/http-https-1.0.0.tgz", + "integrity": "sha512-o0PWwVCSp3O0wS6FvNr6xfBCHgt0m1tvPLFOCc2iFDKTRAXhB7m8klDf7ErowFH8POa6dVdGatKU5I1YYwzUyg==", + "dev": true + }, + "node_modules/http-response-object": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/http-response-object/-/http-response-object-3.0.2.tgz", + "integrity": "sha512-bqX0XTF6fnXSQcEJ2Iuyr75yVakyjIDCqroJQ/aHfSdlM743Cwqoi2nDYMzLGWUcuTWGWy8AAvOKXTfiv6q9RA==", + "dev": true, + "dependencies": { + "@types/node": "^10.0.3" + } + }, + "node_modules/http-response-object/node_modules/@types/node": { + "version": "10.17.60", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz", + "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==", + "dev": true + }, + "node_modules/http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", + "dev": true, + "dependencies": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + }, + "engines": { + "node": ">=0.8", + "npm": ">=1.3.7" + } + }, + "node_modules/http2-wrapper": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.0.tgz", + "integrity": "sha512-kZB0wxMo0sh1PehyjJUWRFEd99KC5TLjZ2cULC4f9iqJBAmKQQXEICjxl5iPJRwP40dpeHFqqhm7tYCvODpqpQ==", + "dev": true, + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.2.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/idna-uts46-hx": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/idna-uts46-hx/-/idna-uts46-hx-2.3.1.tgz", + "integrity": "sha512-PWoF9Keq6laYdIRwwCdhTPl60xRqAloYNMQLiyUnG42VjT53oW07BXIRM+NK7eQjzXjAk2gUvX9caRxlnF9TAA==", + "dev": true, + "dependencies": { + "punycode": "2.1.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/immutable": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.2.1.tgz", + "integrity": "sha512-7WYV7Q5BTs0nlQm7tl92rDYYoyELLKHoDMBKhrxEoiV4mrfVdRz8hzPiYOzH7yWjzoVEamxRuAqhxL2PLRwZYQ==", + "dev": true + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, + "node_modules/inquirer": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.2.tgz", + "integrity": "sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ==", + "dev": true, + "dependencies": { + "ansi-escapes": "^3.2.0", + "chalk": "^2.4.2", + "cli-cursor": "^2.1.0", + "cli-width": "^2.0.0", + "external-editor": "^3.0.3", + "figures": "^2.0.0", + "lodash": "^4.17.12", + "mute-stream": "0.0.7", + "run-async": "^2.2.0", + "rxjs": "^6.4.0", + "string-width": "^2.1.0", + "strip-ansi": "^5.1.0", + "through": "^2.3.6" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/inquirer/node_modules/ansi-escapes": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", + "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/inquirer/node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/inquirer/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/inquirer/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/inquirer/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/inquirer/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/inquirer/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/inquirer/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/inquirer/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/inquirer/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/internal-slot": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.4.tgz", + "integrity": "sha512-tA8URYccNzMo94s5MQZgH8NB/XTa6HsOo0MLfXTKKEnHVVdegzaQoFZ7Jp44bdvLvY2waT5dc+j5ICEswhi7UQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3", + "has": "^1.0.3", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/invert-kv": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", + "integrity": "sha512-xgs2NH9AE66ucSq4cNG1nhSFghr5l6tdL15Pk+jl46bmmBapgoaY/AacXyaDznAqmGL99TiLSQgO/XazFSKYeQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/io-ts": { + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/io-ts/-/io-ts-1.10.4.tgz", + "integrity": "sha512-b23PteSnYXSONJ6JQXRAlvJhuw8KOtkqa87W4wDtvMrud/DTJd5X+NpOOI+O/zZwVq6v0VLAaJ+1EDViKEuN9g==", + "dev": true, + "dependencies": { + "fp-ts": "^1.0.0" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-buffer": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "engines": { + "node": ">=4" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", + "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-directory": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", + "integrity": "sha512-yVChGzahRFvbkscn2MlwGismPO12i9+znNruC5gVEntG3qu0xQMzsGg/JFbrsqDOHtHFPci+V5aP5T9I+yeKqw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/is-function": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz", + "integrity": "sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==", + "dev": true + }, + "node_modules/is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-hex-prefixed": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-hex-prefixed/-/is-hex-prefixed-1.0.0.tgz", + "integrity": "sha512-WvtOiug1VFrE9v1Cydwm+FnXd3+w9GaeVUss5W4v/SLy3UW00vP+6iNF2SdnfiBoLy4bTqVdkftNGTUeOFVsbA==", + "dev": true, + "engines": { + "node": ">=6.5.0", + "npm": ">=3" + } + }, + "node_modules/is-lower-case": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/is-lower-case/-/is-lower-case-1.1.3.tgz", + "integrity": "sha512-+5A1e/WJpLLXZEDlgz4G//WYSHyQBD32qa4Jd3Lw06qQlv3fJHnp3YIHjTQSGzHMgzmVKz2ZP3rBxTHkPw/lxA==", + "dev": true, + "dependencies": { + "lower-case": "^1.1.0" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-port-reachable": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-port-reachable/-/is-port-reachable-3.1.0.tgz", + "integrity": "sha512-vjc0SSRNZ32s9SbZBzGaiP6YVB+xglLShhgZD/FHMZUXBvQWaV9CtzgeVhjccFJrI6RAMV+LX7NYxueW/A8W5A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", + "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "dev": true + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-upper-case": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-upper-case/-/is-upper-case-1.1.2.tgz", + "integrity": "sha512-GQYSJMgfeAmVwh9ixyk888l7OIhNAGKtY6QA+IrWlu9MDTCaXmeozOZ2S9Knj7bQwBO/H6J2kb+pbyTUiMNbsw==", + "dev": true, + "dependencies": { + "upper-case": "^1.1.0" + } + }, + "node_modules/is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==", + "dev": true + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", + "dev": true + }, + "node_modules/js-sha3": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", + "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==", + "dev": true + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", + "dev": true + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "dev": true + }, + "node_modules/json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonschema": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsonschema/-/jsonschema-1.4.1.tgz", + "integrity": "sha512-S6cATIPVv1z0IlxdN+zUk5EPjkGCdnhN4wVSBlvoUO1tOLJootbo9CquNJmbIh4yikWHiUedhRYrNPn1arpEmQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/jsprim": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", + "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", + "dev": true, + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/keccak": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.2.tgz", + "integrity": "sha512-PyKKjkH53wDMLGrvmRGSNWgmSxZOUqbnXwKL9tmgbFYA1iAYqW21kfR7mZXV0MlESiefxQQE9X9fTa3X+2MPDQ==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "node-addon-api": "^2.0.0", + "node-gyp-build": "^4.2.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/keccak256": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/keccak256/-/keccak256-1.0.6.tgz", + "integrity": "sha512-8GLiM01PkdJVGUhR1e6M/AvWnSqYS0HaERI+K/QtStGDGlSTx2B1zTqZk4Zlqu5TxHJNTxWAdP9Y+WI50OApUw==", + "dev": true, + "dependencies": { + "bn.js": "^5.2.0", + "buffer": "^6.0.3", + "keccak": "^3.0.2" + } + }, + "node_modules/keccak256/node_modules/bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", + "dev": true + }, + "node_modules/keccak256/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/keyv": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.2.tgz", + "integrity": "sha512-5MHbFaKn8cNSmVW7BYnijeAVlE4cYA/SVkifVgrh7yotnfhKmjuXpDKjrABLnT0SfHWV21P8ow07OGfRrNDg8g==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/klaw": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz", + "integrity": "sha512-TED5xi9gGQjGpNnvRWknrwAB1eL5GciPfVFOt3Vk1OJCVDQbzuSfrF3hkUQKlsgKrG1F+0t5W0m+Fje1jIt8rw==", + "dev": true, + "optionalDependencies": { + "graceful-fs": "^4.1.9" + } + }, + "node_modules/lcid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", + "integrity": "sha512-YiGkH6EnGrDGqLMITnGjXtGmNtjoXw9SVUzcaos8RBi7Ps0VBylkq+vOcY9QE5poLasPCR849ucFUkl0UzUyOw==", + "dev": true, + "dependencies": { + "invert-kv": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/level": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/level/-/level-8.0.0.tgz", + "integrity": "sha512-ypf0jjAk2BWI33yzEaaotpq7fkOPALKAgDBxggO6Q9HGX2MRXn0wbP1Jn/tJv1gtL867+YOjOB49WaUF3UoJNQ==", + "dev": true, + "dependencies": { + "browser-level": "^1.0.1", + "classic-level": "^1.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/level" + } + }, + "node_modules/level-supports": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/level-supports/-/level-supports-4.0.1.tgz", + "integrity": "sha512-PbXpve8rKeNcZ9C1mUicC9auIYFyGpkV9/i6g76tLgANwWhtG2v7I4xNBUlkn3lE2/dZF3Pi0ygYGtLc4RXXdA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/level-transcoder": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/level-transcoder/-/level-transcoder-1.0.1.tgz", + "integrity": "sha512-t7bFwFtsQeD8cl8NIoQ2iwxA0CL/9IFw7/9gAjOonH0PWTTiRfY7Hq+Ejbsxh86tXobDQ6IOiddjNYIfOBs06w==", + "dev": true, + "dependencies": { + "buffer": "^6.0.3", + "module-error": "^1.0.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/level-transcoder/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/load-json-file": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha512-cy7ZdNRXdablkXYNI049pthVeXFurRyb9+hA/dZzerZ0pGTx42z+y+ssxBaVV2l70t1muq5IdKhn4UtcoGUY9A==", + "dev": true, + "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" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/load-json-file/node_modules/parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha512-QR/GGaKCkhwk1ePQNYDRKYZ3mwU9ypsKhB0XyFnLQdomyEqk3e8wpW3V5Jp88zbxK4n5ST1nqo+g9juTpownhQ==", + "dev": true, + "dependencies": { + "error-ex": "^1.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/load-json-file/node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/load-json-file/node_modules/strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha512-kwrX1y7czp1E69n2ajbG65mIo9dqvJ+8aBQXOGVxqwvNbsXdFM6Lq37dLAY3mknUwru8CfcCbfOLL/gMo+fi3g==", + "dev": true, + "dependencies": { + "is-utf8": "^0.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/lodash.assign": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz", + "integrity": "sha512-hFuH8TY+Yji7Eja3mGiuAxBqLagejScbG8GbG0j6o9vzn0YL14My+ktnqtZgFTosKymC9/44wP6s7xyuLfnClw==", + "dev": true + }, + "node_modules/lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==", + "dev": true + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/lodash.startcase": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.startcase/-/lodash.startcase-4.4.0.tgz", + "integrity": "sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==", + "dev": true + }, + "node_modules/lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", + "dev": true + }, + "node_modules/lodash.zip": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.zip/-/lodash.zip-4.2.0.tgz", + "integrity": "sha512-C7IOaBBK/0gMORRBd8OETNx3kmOkgIWIPvyDpZSCTwUrpYmgZwJkjZeOD8ww4xbOUOs4/attY+pciKvadNfFbg==", + "dev": true + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/loupe": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz", + "integrity": "sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.0" + } + }, + "node_modules/lower-case": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", + "integrity": "sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA==", + "dev": true + }, + "node_modules/lower-case-first": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/lower-case-first/-/lower-case-first-1.0.2.tgz", + "integrity": "sha512-UuxaYakO7XeONbKrZf5FEgkantPf5DUqDayzP5VXZrtRPdH86s4kN47I8B3TW10S4QKiE3ziHNf3kRN//okHjA==", + "dev": true, + "dependencies": { + "lower-case": "^1.1.2" + } + }, + "node_modules/lowercase-keys": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", + "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lru_map": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.3.3.tgz", + "integrity": "sha512-Pn9cox5CsMYngeDbmChANltQl+5pi6XmTrraMSzhPmMBbmgcxmqWry0U3PGapCU1yB4/LqCcom7qhHZiF/jGfQ==", + "dev": true + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/markdown-table": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-1.1.3.tgz", + "integrity": "sha512-1RUZVgQlpJSPWYbFSpmudq5nHY1doEIv89gBtF0s4gW1GF2XorxcA/70M5vq7rLv0a6mhOUccRsqkwhwLCIQ2Q==", + "dev": true + }, + "node_modules/mcl-wasm": { + "version": "0.7.9", + "resolved": "https://registry.npmjs.org/mcl-wasm/-/mcl-wasm-0.7.9.tgz", + "integrity": "sha512-iJIUcQWA88IJB/5L15GnJVnSQJmf/YaxxV6zRavv83HILHaJQb6y0iFyDMdDO0gN8X37tdxmAOrH/P8B6RB8sQ==", + "dev": true, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "dev": true, + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memory-level": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/memory-level/-/memory-level-1.0.0.tgz", + "integrity": "sha512-UXzwewuWeHBz5krr7EvehKcmLFNoXxGcvuYhC41tRnkrTbJohtS7kVn9akmgirtRygg+f7Yjsfi8Uu5SGSQ4Og==", + "dev": true, + "dependencies": { + "abstract-level": "^1.0.0", + "functional-red-black-tree": "^1.0.1", + "module-error": "^1.0.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/memorystream": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", + "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==", + "dev": true, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", + "dev": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/merkletreejs": { + "version": "0.2.32", + "resolved": "https://registry.npmjs.org/merkletreejs/-/merkletreejs-0.2.32.tgz", + "integrity": "sha512-TostQBiwYRIwSE5++jGmacu3ODcKAgqb0Y/pnIohXS7sWxh1gCkSptbmF1a43faehRDpcHf7J/kv0Ml2D/zblQ==", + "dev": true, + "dependencies": { + "bignumber.js": "^9.0.1", + "buffer-reverse": "^1.0.1", + "crypto-js": "^3.1.9-1", + "treeify": "^1.1.0", + "web3-utils": "^1.3.4" + }, + "engines": { + "node": ">= 7.6.0" + } + }, + "node_modules/merkletreejs/node_modules/bignumber.js": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.1.tgz", + "integrity": "sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "dev": true, + "dependencies": { + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + }, + "bin": { + "miller-rabin": "bin/miller-rabin" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/min-document": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", + "integrity": "sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ==", + "dev": true, + "dependencies": { + "dom-walk": "^0.1.0" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true + }, + "node_modules/minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", + "dev": true + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", + "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", + "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "node_modules/minizlib": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", + "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", + "dev": true, + "dependencies": { + "minipass": "^2.9.0" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/mkdirp-promise": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mkdirp-promise/-/mkdirp-promise-5.0.1.tgz", + "integrity": "sha512-Hepn5kb1lJPtVW84RFT40YG1OddBNTOVUZR2bzQUHc+Z03en8/3uX0+060JDhcEzyO08HmipsN9DcnFMxhIL9w==", + "deprecated": "This package is broken and no longer maintained. 'mkdirp' itself supports promises now, please switch to that.", + "dev": true, + "dependencies": { + "mkdirp": "*" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mnemonist": { + "version": "0.38.5", + "resolved": "https://registry.npmjs.org/mnemonist/-/mnemonist-0.38.5.tgz", + "integrity": "sha512-bZTFT5rrPKtPJxj8KSV0WkPyNxl72vQepqqVUAW2ARUpUSF2qXMB6jZj7hW5/k7C1rtpzqbD/IIbJwLXUjCHeg==", + "dev": true, + "dependencies": { + "obliterator": "^2.0.0" + } + }, + "node_modules/mocha": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", + "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==", + "dev": true, + "dependencies": { + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.3", + "debug": "4.3.4", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.2.0", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "5.0.1", + "ms": "2.1.3", + "nanoid": "3.3.3", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "workerpool": "6.2.1", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" + }, + "engines": { + "node": ">= 14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mochajs" + } + }, + "node_modules/mocha/node_modules/ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/mocha/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/mocha/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/mocha/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/mocha/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/mocha/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/node_modules/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mocha/node_modules/glob/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mocha/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/mocha/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/mocha/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/node_modules/minimatch": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", + "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha/node_modules/minimatch/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/mocha/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/mocha/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mocha/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mocha/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/mocha/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mock-fs": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/mock-fs/-/mock-fs-4.14.0.tgz", + "integrity": "sha512-qYvlv/exQ4+svI3UOvPUpLDF0OMX5euvUH0Ny4N5QyRyhNdgAgUrVH3iUINSzEPLvx0kbo/Bp28GJKIqvE7URw==", + "dev": true + }, + "node_modules/module-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/module-error/-/module-error-1.0.2.tgz", + "integrity": "sha512-0yuvsqSCv8LbaOKhnsQ/T5JhyFlCYLPXK3U2sgV10zoKQwzs/MyfuQUOZQ1V/6OCOJsK/TRgNVrPuPDqtdMFtA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/multibase": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/multibase/-/multibase-0.6.1.tgz", + "integrity": "sha512-pFfAwyTjbbQgNc3G7D48JkJxWtoJoBMaR4xQUOuB8RnCgRqaYmWNFeJTTvrJ2w51bjLq2zTby6Rqj9TQ9elSUw==", + "deprecated": "This module has been superseded by the multiformats module", + "dev": true, + "dependencies": { + "base-x": "^3.0.8", + "buffer": "^5.5.0" + } + }, + "node_modules/multicodec": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/multicodec/-/multicodec-0.5.7.tgz", + "integrity": "sha512-PscoRxm3f+88fAtELwUnZxGDkduE2HD9Q6GHUOywQLjOGT/HAdhjLDYNZ1e7VR0s0TP0EwZ16LNUTFpoBGivOA==", + "deprecated": "This module has been superseded by the multiformats module", + "dev": true, + "dependencies": { + "varint": "^5.0.0" + } + }, + "node_modules/multihashes": { + "version": "0.4.21", + "resolved": "https://registry.npmjs.org/multihashes/-/multihashes-0.4.21.tgz", + "integrity": "sha512-uVSvmeCWf36pU2nB4/1kzYZjsXD9vofZKpgudqkceYY5g2aZZXJ5r9lxuzoRLl1OAp28XljXsEJ/X/85ZsKmKw==", + "dev": true, + "dependencies": { + "buffer": "^5.5.0", + "multibase": "^0.7.0", + "varint": "^5.0.0" + } + }, + "node_modules/multihashes/node_modules/multibase": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/multibase/-/multibase-0.7.0.tgz", + "integrity": "sha512-TW8q03O0f6PNFTQDvh3xxH03c8CjGaaYrjkl9UQPG6rz53TQzzxJVCIWVjzcbN/Q5Y53Zd0IBQBMVktVgNx4Fg==", + "deprecated": "This module has been superseded by the multiformats module", + "dev": true, + "dependencies": { + "base-x": "^3.0.8", + "buffer": "^5.5.0" + } + }, + "node_modules/mute-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "integrity": "sha512-r65nCZhrbXXb6dXOACihYApHw2Q6pV0M3V0PSxd74N0+D8nzAdEAITq2oAjA1jVnKI+tGvEBUpqiMh0+rW6zDQ==", + "dev": true + }, + "node_modules/nano-base32": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/nano-base32/-/nano-base32-1.0.1.tgz", + "integrity": "sha512-sxEtoTqAPdjWVGv71Q17koMFGsOMSiHsIFEvzOM7cNp8BXB4AnEwmDabm5dorusJf/v1z7QxaZYxUorU9RKaAw==", + "dev": true + }, + "node_modules/nano-json-stream-parser": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/nano-json-stream-parser/-/nano-json-stream-parser-0.1.2.tgz", + "integrity": "sha512-9MqxMH/BSJC7dnLsEMPyfN5Dvoo49IsPFYMcHw3Bcfc2kN0lpHRBSzlMSVx4HGyJ7s9B31CyBTVehWJoQ8Ctew==", + "dev": true + }, + "node_modules/nanoid": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", + "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", + "dev": true, + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/napi-macros": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-macros/-/napi-macros-2.0.0.tgz", + "integrity": "sha512-A0xLykHtARfueITVDernsAWdtIMbOJgKgcluwENp3AlsKN/PloyO10HtmoqnFAQAcxPkgZN7wdfPfEd0zNGxbg==", + "dev": true + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "node_modules/next-tick": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", + "dev": true + }, + "node_modules/nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "node_modules/no-case": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz", + "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==", + "dev": true, + "dependencies": { + "lower-case": "^1.1.1" + } + }, + "node_modules/node-addon-api": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz", + "integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==", + "dev": true + }, + "node_modules/node-emoji": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz", + "integrity": "sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==", + "dev": true, + "dependencies": { + "lodash": "^4.17.21" + } + }, + "node_modules/node-environment-flags": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.6.tgz", + "integrity": "sha512-5Evy2epuL+6TM0lCQGpFIj6KwiEsGh1SrHUhTbNX+sLbBtjidPZFAnVK9y5yU1+h//RitLbRHTIMyxQPtxMdHw==", + "dev": true, + "dependencies": { + "object.getownpropertydescriptors": "^2.0.3", + "semver": "^5.7.0" + } + }, + "node_modules/node-environment-flags/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dev": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-gyp-build": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.5.0.tgz", + "integrity": "sha512-2iGbaQBV+ITgCz76ZEjmhUKAKVf7xfY1sRl4UiKQspfZMH2h06SyhNsnSVy50cwkFQDGLyif6m/6uFXHkOZ6rg==", + "dev": true, + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/node-interval-tree": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/node-interval-tree/-/node-interval-tree-2.1.2.tgz", + "integrity": "sha512-bJ9zMDuNGzVQg1xv0bCPzyEDxHgbrx7/xGj6CDokvizZZmastPsOh0JJLuY8wA5q2SfX1TLNMk7XNV8WxbGxzA==", + "dev": true, + "dependencies": { + "shallowequal": "^1.1.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/nofilter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/nofilter/-/nofilter-1.0.4.tgz", + "integrity": "sha512-N8lidFp+fCz+TD51+haYdbDGrcBWwuHX40F5+z0qkUjMJ5Tp+rdSuAkMJ9N9eoolDlEVTf6u5icM+cNKkKW2mA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/nopt": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", + "integrity": "sha512-4GUt3kSEYmk4ITxzB/b9vaIDfUVWN/Ml1Fwl11IlnIG2iaJ9O6WXZ9SrYM9NLI8OCBieN2Y8SWC2oJV0RQ7qYg==", + "dev": true, + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + } + }, + "node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/normalize-package-data/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/number-to-bn": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/number-to-bn/-/number-to-bn-1.7.0.tgz", + "integrity": "sha512-wsJ9gfSz1/s4ZsJN01lyonwuxA1tml6X1yBDnfpMglypcBRFZZkus26EdPSlqS5GJfYddVZa22p3VNb3z5m5Ig==", + "dev": true, + "dependencies": { + "bn.js": "4.11.6", + "strip-hex-prefix": "1.0.0" + }, + "engines": { + "node": ">=6.5.0", + "npm": ">=3" + } + }, + "node_modules/number-to-bn/node_modules/bn.js": { + "version": "4.11.6", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.6.tgz", + "integrity": "sha512-XWwnNNFCuuSQ0m3r3C4LE3EiORltHd9M05pq6FOlVeiophzRbMo50Sbz1ehl8K3Z+jw9+vmgnXefY1hz8X+2wA==", + "dev": true + }, + "node_modules/oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", + "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.getownpropertydescriptors": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.5.tgz", + "integrity": "sha512-yDNzckpM6ntyQiGTik1fKV1DcVDRS+w8bvpWNCBanvH5LfRX9O8WTHqQzG4RZwRAM4I0oU7TV11Lj5v0g20ibw==", + "dev": true, + "dependencies": { + "array.prototype.reduce": "^1.0.5", + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.values": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz", + "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/obliterator": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/obliterator/-/obliterator-2.0.4.tgz", + "integrity": "sha512-lgHwxlxV1qIg1Eap7LgIeoBWIMFibOjbrYPIPJZcI1mmGAI2m3lNYpK12Y+GBdPQ0U1hRwSord7GIaawz962qQ==", + "dev": true + }, + "node_modules/oboe": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/oboe/-/oboe-2.1.5.tgz", + "integrity": "sha512-zRFWiF+FoicxEs3jNI/WYUrVEgA7DeET/InK0XQuudGHRg8iIob3cNPrJTKaz4004uaA9Pbe+Dwa8iluhjLZWA==", + "dev": true, + "dependencies": { + "http-https": "^1.0.0" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ==", + "dev": true, + "dependencies": { + "mimic-fn": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/os-locale": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "integrity": "sha512-PRT7ZORmwu2MEFt4/fv3Q+mEfN4zetKxufQrkShY2oGvUms9r8otu5HfdyIFHkYXjO7laNsoVGmM2MANfuTA8g==", + "dev": true, + "dependencies": { + "lcid": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/p-cancelable": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", + "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==", + "dev": true, + "engines": { + "node": ">=12.20" + } + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true + }, + "node_modules/param-case": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz", + "integrity": "sha512-eQE845L6ot89sk2N8liD8HAuH4ca6Vvr7VWAWwt7+kvvG5aBcPmmphQ68JsEG2qa9n1TykS2DLeMt363AAH8/w==", + "dev": true, + "dependencies": { + "no-case": "^2.2.0" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-asn1": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz", + "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==", + "dev": true, + "dependencies": { + "asn1.js": "^5.2.0", + "browserify-aes": "^1.0.0", + "evp_bytestokey": "^1.0.0", + "pbkdf2": "^3.0.3", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/parse-cache-control": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-cache-control/-/parse-cache-control-1.0.1.tgz", + "integrity": "sha512-60zvsJReQPX5/QP0Kzfd/VrpjScIQ7SHBW6bFCYfEP+fp0Eppr1SHhIO5nd1PjZtvclzSzES9D/p5nFJurwfWg==", + "dev": true + }, + "node_modules/parse-headers": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.5.tgz", + "integrity": "sha512-ft3iAoLOB/MlwbNXgzy43SWGP6sQki2jQvAyBg/zDFAgr9bfNWZIUj42Kw2eJIl8kEi4PbgE6U1Zau/HwI75HA==", + "dev": true + }, + "node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "dev": true, + "dependencies": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dev": true, + "dependencies": { + "entities": "^4.4.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz", + "integrity": "sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==", + "dev": true, + "dependencies": { + "domhandler": "^5.0.2", + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/pascal-case": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-2.0.1.tgz", + "integrity": "sha512-qjS4s8rBOJa2Xm0jmxXiyh1+OFf6ekCWOvUaRgAQSktzlTbMotS0nmG9gyYAybCWBcuP4fsBeRCKNwGBnMe2OQ==", + "dev": true, + "dependencies": { + "camel-case": "^3.0.0", + "upper-case-first": "^1.1.0" + } + }, + "node_modules/path-case": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/path-case/-/path-case-2.1.1.tgz", + "integrity": "sha512-Ou0N05MioItesaLr9q8TtHVWmJ6fxWdqKB2RohFmNWVyJ+2zeKIeDNWAN6B/Pe7wpzWChhZX6nONYmOnMeJQ/Q==", + "dev": true, + "dependencies": { + "no-case": "^2.2.0" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==", + "dev": true + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", + "dev": true + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/pbkdf2": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", + "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", + "dev": true, + "dependencies": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", + "dev": true, + "dependencies": { + "pinkie": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.1.tgz", + "integrity": "sha512-lqGoSJBQNJidqCHE80vqZJHWHRFoNYsSpP9AjFhlhi9ODCJA541svILes/+/1GM3VaL/abZi7cpFzOpdR9UPKg==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-plugin-solidity": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/prettier-plugin-solidity/-/prettier-plugin-solidity-1.1.0.tgz", + "integrity": "sha512-5gq0T49ifvXH/6x1STuKyWjTUgi6ICoV65yNtKlg/vZEvocFtSpByJOJICBfqPwNsnv4vhhWIqkLGSUJmWum2w==", + "dev": true, + "dependencies": { + "@solidity-parser/parser": "^0.14.5", + "emoji-regex": "^10.2.1", + "escape-string-regexp": "^4.0.0", + "semver": "^7.3.8", + "solidity-comments-extractor": "^0.0.7", + "string-width": "^4.2.3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "prettier": "^2.3.0" + } + }, + "node_modules/prettier-plugin-solidity/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/prettier-plugin-solidity/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/prettier-plugin-solidity/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/prettier-plugin-solidity/node_modules/string-width/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/prettier-plugin-solidity/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "dev": true, + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/promise": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/promise/-/promise-8.3.0.tgz", + "integrity": "sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==", + "dev": true, + "dependencies": { + "asap": "~2.0.6" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dev": true, + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", + "dev": true + }, + "node_modules/public-encrypt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", + "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", + "dev": true, + "dependencies": { + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.0.tgz", + "integrity": "sha512-Yxz2kRwT90aPiWEMHVYnEf4+rhwF1tBmmZ4KepCP+Wkium9JxtWnUm1nqGwpiAHr/tnTSeHqr3wb++jgSkXjhA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/pure-rand": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-5.0.5.tgz", + "integrity": "sha512-BwQpbqxSCBJVpamI6ydzcKqyFmnd5msMWUGvzXLm1aXvusbbgkbOto/EUPM00hjveJEaJtdbhUjKSzWRhQVkaw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ] + }, + "node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dev": true, + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/query-string": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz", + "integrity": "sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==", + "dev": true, + "dependencies": { + "decode-uri-component": "^0.2.0", + "object-assign": "^4.1.0", + "strict-uri-encode": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/rambda": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/rambda/-/rambda-7.4.0.tgz", + "integrity": "sha512-A9hihu7dUTLOUCM+I8E61V4kRXnN4DwYeK0DwCBydC1MqNI1PidyAtbtpsJlBBzK4icSctEcCQ1bGcLpBuETUQ==", + "dev": true + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/randomfill": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "dev": true, + "dependencies": { + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha512-7BGwRHqt4s/uVbuyoeejRn4YmFnYZiFl4AuaeXHlgZf3sONF0SOGlxs2Pw8g6hCKupo08RafIO5YXFNOKTfwsQ==", + "dev": true, + "dependencies": { + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-pkg-up": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha512-WD9MTlNtI55IwYUS27iHh9tK3YoIVhxis8yKhLpTqWtml739uXc9NWTpxoHkfZf3+DkCCsXox94/VWZniuZm6A==", + "dev": true, + "dependencies": { + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-pkg-up/node_modules/find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha512-jvElSjyuo4EMQGoTwo1uJU5pQMwTW5lS1x05zzfJuTIyLR3zwO27LYrxNg+dlvKpGOuGy/MzBdXh80g0ve5+HA==", + "dev": true, + "dependencies": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-pkg-up/node_modules/path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha512-yTltuKuhtNeFJKa1PiRzfLAU5182q1y4Eb4XCJ3PBqyzEDkAZRzBrKKBct682ls9reBVHf9udYLN5Nd+K1B9BQ==", + "dev": true, + "dependencies": { + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-pkg/node_modules/path-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha512-S4eENJz1pkiQn9Znv33Q+deTOKmbl+jj1Fl+qiP/vYezj+S8x+J3Uo0ISrx/QoEvIlOaDWJhPaRd1flJ9HXZqg==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-pkg/node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", + "dev": true, + "dependencies": { + "resolve": "^1.1.6" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/recursive-readdir": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.3.tgz", + "integrity": "sha512-8HrF5ZsXk5FAH9dgsx3BlUer73nIhuj+9OrQwEbLTPOBzGkL1lsFCR01am+v+0m2Cmbs1nP12hLDl5FA7EszKA==", + "dev": true, + "dependencies": { + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "dev": true + }, + "node_modules/regexp.prototype.flags": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", + "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "functions-have-names": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/req-cwd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/req-cwd/-/req-cwd-2.0.0.tgz", + "integrity": "sha512-ueoIoLo1OfB6b05COxAA9UpeoscNpYyM+BqYlA7H6LVF4hKGPXQQSSaD2YmvDVJMkk4UDpAHIeU1zG53IqjvlQ==", + "dev": true, + "dependencies": { + "req-from": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/req-from": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/req-from/-/req-from-2.0.0.tgz", + "integrity": "sha512-LzTfEVDVQHBRfjOUMgNBA+V6DWsSnoeKzf42J7l0xa/B4jyPOuuF5MlNSmomLNGemWTnV2TIdjSSLnEn95fOQA==", + "dev": true, + "dependencies": { + "resolve-from": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/req-from/node_modules/resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", + "dev": true, + "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" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/request-promise-core": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz", + "integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==", + "dev": true, + "dependencies": { + "lodash": "^4.17.19" + }, + "engines": { + "node": ">=0.10.0" + }, + "peerDependencies": { + "request": "^2.34" + } + }, + "node_modules/request-promise-native": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.9.tgz", + "integrity": "sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g==", + "deprecated": "request-promise-native has been deprecated because it extends the now deprecated request package, see https://github.com/request/request/issues/3142", + "dev": true, + "dependencies": { + "request-promise-core": "1.1.4", + "stealthy-require": "^1.1.1", + "tough-cookie": "^2.3.3" + }, + "engines": { + "node": ">=0.12.0" + }, + "peerDependencies": { + "request": "^2.34" + } + }, + "node_modules/request/node_modules/qs": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", + "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/request/node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "dev": true, + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-1.2.1.tgz", + "integrity": "sha512-H7AkJWMobeskkttHyhTVtS0fxpFLjxhbfMa6Bk3wimP7sdPRGL3EyCg3sAQenFfAe+xQ+oAc85Nmtvq0ROM83Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "node_modules/resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "dev": true + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/responselike": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", + "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", + "dev": true, + "dependencies": { + "lowercase-keys": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/responselike/node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q==", + "dev": true, + "dependencies": { + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "dev": true, + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, + "node_modules/ripemd160-min": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/ripemd160-min/-/ripemd160-min-0.0.6.tgz", + "integrity": "sha512-+GcJgQivhs6S9qvLogusiTcS9kQUfgR75whKuy5jIhuiOfQuJ8fjqxV6EGD5duH1Y/FawFUMtMhyeq3Fbnib8A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/rlp": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/rlp/-/rlp-2.2.7.tgz", + "integrity": "sha512-d5gdPmgQ0Z+AklL2NVXr/IoSjNZFfTVvQWzL/AM2AOcSzYP2xjlb0AC8YyCLc41MSNf6P6QVtjgPdmVtzb+4lQ==", + "dev": true, + "dependencies": { + "bn.js": "^5.2.0" + }, + "bin": { + "rlp": "bin/rlp" + } + }, + "node_modules/rlp/node_modules/bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", + "dev": true + }, + "node_modules/run-async": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/run-parallel-limit": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/run-parallel-limit/-/run-parallel-limit-1.1.0.tgz", + "integrity": "sha512-jJA7irRNM91jaKc3Hcl1npHsFLOXOoTkPCUL1JEa1R82O2miplXXRaGdjW/KM/98YQWDhJLiSs793CnXfblJUw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rustbn.js": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/rustbn.js/-/rustbn.js-0.2.0.tgz", + "integrity": "sha512-4VlvkRUuCJvr2J6Y0ImW7NvTCriMi7ErOAqWk1y69vAdoNIzCF3yPmgeNzx+RQTLEDFq5sHfscn1MwHxP9hNfA==", + "dev": true + }, + "node_modules/rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "dependencies": { + "tslib": "^1.9.0" + }, + "engines": { + "npm": ">=2.0.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safe-regex-test": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", + "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-regex": "^1.1.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "node_modules/sc-istanbul": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/sc-istanbul/-/sc-istanbul-0.4.6.tgz", + "integrity": "sha512-qJFF/8tW/zJsbyfh/iT/ZM5QNHE3CXxtLJbZsL+CzdJLBsPD7SedJZoUA4d8iAcN2IoMp/Dx80shOOd2x96X/g==", + "dev": true, + "dependencies": { + "abbrev": "1.0.x", + "async": "1.x", + "escodegen": "1.8.x", + "esprima": "2.7.x", + "glob": "^5.0.15", + "handlebars": "^4.0.1", + "js-yaml": "3.x", + "mkdirp": "0.5.x", + "nopt": "3.x", + "once": "1.x", + "resolve": "1.1.x", + "supports-color": "^3.1.0", + "which": "^1.1.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "istanbul": "lib/cli.js" + } + }, + "node_modules/sc-istanbul/node_modules/async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha512-nSVgobk4rv61R9PUSDtYt7mPVB2olxNR5RWJcAsH676/ef11bUZwvu7+RGYrYauVdDPcO519v68wRhXQtxsV9w==", + "dev": true + }, + "node_modules/sc-istanbul/node_modules/esprima": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", + "integrity": "sha512-OarPfz0lFCiW4/AV2Oy1Rp9qu0iusTKqykwTspGCZtPxmF81JR4MmIebvF1F9+UOKth2ZubLQ4XGGaU+hSn99A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sc-istanbul/node_modules/glob": { + "version": "5.0.15", + "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", + "integrity": "sha512-c9IPMazfRITpmAAKi22dK1VKxGDX9ehhqfABDriL/lzO92xcUKEJPQHrVA/2YHSNFB4iFlykVmWvwo48nr3OxA==", + "dev": true, + "dependencies": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/sc-istanbul/node_modules/has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha512-DyYHfIYwAJmjAjSSPKANxI8bFY9YtFrgkAfinBojQ8YJTOuOuav64tMUJv584SES4xl74PmuaevIyaLESHdTAA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sc-istanbul/node_modules/resolve": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha512-9znBF0vBcaSN3W2j7wKvdERPwqTxSpCq+if5C0WoTCyV9n24rua28jeuQ2pL/HOf+yUe/Mef+H/5p60K0Id3bg==", + "dev": true + }, + "node_modules/sc-istanbul/node_modules/supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha512-Jds2VIYDrlp5ui7t8abHN2bjAu4LV/q4N2KivFPpGH0lrka0BMq/33AmECUXlKPcHigkNaqfXRENFju+rlcy+A==", + "dev": true, + "dependencies": { + "has-flag": "^1.0.0" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/sc-istanbul/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/scrypt-js": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz", + "integrity": "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==", + "dev": true + }, + "node_modules/secp256k1": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-4.0.3.tgz", + "integrity": "sha512-NLZVf+ROMxwtEj3Xa562qgv2BK5e2WNmXPiOdVIPLgs6lyTzMvBq0aWTYMI5XCP9jZMVKOcqZLw/Wc4vDkuxhA==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "elliptic": "^6.5.4", + "node-addon-api": "^2.0.0", + "node-gyp-build": "^4.2.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dev": true, + "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" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/sentence-case": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/sentence-case/-/sentence-case-2.1.1.tgz", + "integrity": "sha512-ENl7cYHaK/Ktwk5OTD+aDbQ3uC8IByu/6Bkg+HDv8Mm+XnBnppVNalcfJTNsp1ibstKh030/JKQQWglDvtKwEQ==", + "dev": true, + "dependencies": { + "no-case": "^2.2.0", + "upper-case-first": "^1.1.2" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dev": true, + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/servify": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/servify/-/servify-0.1.12.tgz", + "integrity": "sha512-/xE6GvsKKqyo1BAY+KxOWXcLpPsUUyji7Qg3bVD7hh1eRze5bR1uYiuDA/k3Gof1s9BTzQZEJK8sNcNGFIzeWw==", + "dev": true, + "dependencies": { + "body-parser": "^1.16.0", + "cors": "^2.8.1", + "express": "^4.14.0", + "request": "^2.79.0", + "xhr": "^2.3.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "dev": true + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "dev": true + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true + }, + "node_modules/sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "bin": { + "sha.js": "bin.js" + } + }, + "node_modules/sha1": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/sha1/-/sha1-1.1.1.tgz", + "integrity": "sha512-dZBS6OrMjtgVkopB1Gmo4RQCDKiZsqcpAQpkV/aaj+FCrCg8r4I4qMkDPQjBgLIxlmu9k4nUbWq6ohXahOneYA==", + "dev": true, + "dependencies": { + "charenc": ">= 0.0.1", + "crypt": ">= 0.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/sha3": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/sha3/-/sha3-2.1.4.tgz", + "integrity": "sha512-S8cNxbyb0UGUM2VhRD4Poe5N58gJnJsLJ5vC7FYWGUmGhcsj4++WaIOBFVDxlG0W3To6xBuiRh+i0Qp2oNCOtg==", + "dev": true, + "dependencies": { + "buffer": "6.0.3" + } + }, + "node_modules/sha3/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==", + "dev": true + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/shelljs": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", + "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", + "dev": true, + "dependencies": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + }, + "bin": { + "shjs": "bin/shjs" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/shelljs/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/simple-get": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-2.8.2.tgz", + "integrity": "sha512-Ijd/rV5o+mSBBs4F/x9oDPtTx9Zb6X9brmnXvMW4J7IR15ngi9q5xxqWBKU744jTZiaXtxaPL7uHG6vtN8kUkw==", + "dev": true, + "dependencies": { + "decompress-response": "^3.3.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/simple-get/node_modules/decompress-response": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", + "integrity": "sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA==", + "dev": true, + "dependencies": { + "mimic-response": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/snake-case": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-2.1.0.tgz", + "integrity": "sha512-FMR5YoPFwOLuh4rRz92dywJjyKYZNLpMn1R5ujVpIYkbA9p01fq8RMg0FkO4M+Yobt4MjHeLTJVm5xFFBHSV2Q==", + "dev": true, + "dependencies": { + "no-case": "^2.2.0" + } + }, + "node_modules/solc": { + "version": "0.4.26", + "resolved": "https://registry.npmjs.org/solc/-/solc-0.4.26.tgz", + "integrity": "sha512-o+c6FpkiHd+HPjmjEVpQgH7fqZ14tJpXhho+/bQXlXbliLIS/xjXb42Vxh+qQY1WCSTMQ0+a5vR9vi0MfhU6mA==", + "dev": true, + "dependencies": { + "fs-extra": "^0.30.0", + "memorystream": "^0.3.1", + "require-from-string": "^1.1.0", + "semver": "^5.3.0", + "yargs": "^4.7.1" + }, + "bin": { + "solcjs": "solcjs" + } + }, + "node_modules/solc/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/solc/node_modules/camelcase": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", + "integrity": "sha512-4nhGqUkc4BqbBBB4Q6zLuD7lzzrHYrjKGeYaEji/3tFR5VdJu9v+LilhGIVe8wxEJPPOeWo7eg8dwY13TZ1BNg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/solc/node_modules/cliui": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", + "integrity": "sha512-0yayqDxWQbqk3ojkYqUKqaAQ6AfNKeKWRNA8kR0WXzAsdHpP4BIaOmMAG87JGuO6qcobyW4GjxHd9PmhEd+T9w==", + "dev": true, + "dependencies": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wrap-ansi": "^2.0.0" + } + }, + "node_modules/solc/node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/solc/node_modules/fs-extra": { + "version": "0.30.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.30.0.tgz", + "integrity": "sha512-UvSPKyhMn6LEd/WpUaV9C9t3zATuqoqfWc3QdPhPLb58prN9tqYPlPWi8Krxi44loBoUzlobqZ3+8tGpxxSzwA==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "jsonfile": "^2.1.0", + "klaw": "^1.0.0", + "path-is-absolute": "^1.0.0", + "rimraf": "^2.2.8" + } + }, + "node_modules/solc/node_modules/get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", + "dev": true + }, + "node_modules/solc/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/solc/node_modules/is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==", + "dev": true, + "dependencies": { + "number-is-nan": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/solc/node_modules/jsonfile": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", + "integrity": "sha512-PKllAqbgLgxHaj8TElYymKCAgrASebJrWpTnEkOaTowt23VKXXN0sUeriJ+eh7y6ufb/CC5ap11pz71/cM0hUw==", + "dev": true, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/solc/node_modules/require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha512-IqSUtOVP4ksd1C/ej5zeEh/BIP2ajqpn8c5x+q99gvcIG/Qf0cud5raVnE/Dwd0ua9TXYDoDc0RE5hBSdz22Ug==", + "dev": true + }, + "node_modules/solc/node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/solc/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/solc/node_modules/string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==", + "dev": true, + "dependencies": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/solc/node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "dev": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/solc/node_modules/which-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", + "integrity": "sha512-F6+WgncZi/mJDrammbTuHe1q0R5hOXv/mBaiNA2TCNT/LTHusX0V+CJnj9XT8ki5ln2UZyyddDgHfCzyrOH7MQ==", + "dev": true + }, + "node_modules/solc/node_modules/wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha512-vAaEaDM946gbNpH5pLVNR+vX2ht6n0Bt3GXwVB1AuAqZosOvHNF3P7wDnh8KLkSqgUh0uh77le7Owgoz+Z9XBw==", + "dev": true, + "dependencies": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/solc/node_modules/y18n": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.2.tgz", + "integrity": "sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ==", + "dev": true + }, + "node_modules/solc/node_modules/yargs": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-4.8.1.tgz", + "integrity": "sha512-LqodLrnIDM3IFT+Hf/5sxBnEGECrfdC1uIbgZeJmESCSo4HoCAaKEus8MylXHAkdacGc0ye+Qa+dpkuom8uVYA==", + "dev": true, + "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" + } + }, + "node_modules/solc/node_modules/yargs-parser": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-2.4.1.tgz", + "integrity": "sha512-9pIKIJhnI5tonzG6OnCFlz/yln8xHYcGl+pn3xR0Vzff0vzN1PbNRaelgfgRUwZ3s4i3jvxT9WhmUGL4whnasA==", + "dev": true, + "dependencies": { + "camelcase": "^3.0.0", + "lodash.assign": "^4.0.6" + } + }, + "node_modules/solhint": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/solhint/-/solhint-3.3.7.tgz", + "integrity": "sha512-NjjjVmXI3ehKkb3aNtRJWw55SUVJ8HMKKodwe0HnejA+k0d2kmhw7jvpa+MCTbcEgt8IWSwx0Hu6aCo/iYOZzQ==", + "dev": true, + "dependencies": { + "@solidity-parser/parser": "^0.14.1", + "ajv": "^6.6.1", + "antlr4": "4.7.1", + "ast-parents": "0.0.1", + "chalk": "^2.4.2", + "commander": "2.18.0", + "cosmiconfig": "^5.0.7", + "eslint": "^5.6.0", + "fast-diff": "^1.1.2", + "glob": "^7.1.3", + "ignore": "^4.0.6", + "js-yaml": "^3.12.0", + "lodash": "^4.17.11", + "semver": "^6.3.0" + }, + "bin": { + "solhint": "solhint.js" + }, + "optionalDependencies": { + "prettier": "^1.14.3" + } + }, + "node_modules/solhint/node_modules/acorn": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", + "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/solhint/node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/solhint/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/solhint/node_modules/astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/solhint/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/solhint/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/solhint/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/solhint/node_modules/cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "dependencies": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "engines": { + "node": ">=4.8" + } + }, + "node_modules/solhint/node_modules/cross-spawn/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/solhint/node_modules/emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "node_modules/solhint/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/solhint/node_modules/eslint": { + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.16.0.tgz", + "integrity": "sha512-S3Rz11i7c8AA5JPv7xAH+dOyq/Cu/VXHiHXBPOU1k/JAM5dXqQPt3qcrhpHSorXmrpu2g0gkIBVXAqCpzfoZIg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "ajv": "^6.9.1", + "chalk": "^2.1.0", + "cross-spawn": "^6.0.5", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "eslint-scope": "^4.0.3", + "eslint-utils": "^1.3.1", + "eslint-visitor-keys": "^1.0.0", + "espree": "^5.0.1", + "esquery": "^1.0.1", + "esutils": "^2.0.2", + "file-entry-cache": "^5.0.1", + "functional-red-black-tree": "^1.0.1", + "glob": "^7.1.2", + "globals": "^11.7.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "inquirer": "^6.2.2", + "js-yaml": "^3.13.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.3.0", + "lodash": "^4.17.11", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "optionator": "^0.8.2", + "path-is-inside": "^1.0.2", + "progress": "^2.0.0", + "regexpp": "^2.0.1", + "semver": "^5.5.1", + "strip-ansi": "^4.0.0", + "strip-json-comments": "^2.0.1", + "table": "^5.2.3", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^6.14.0 || ^8.10.0 || >=9.10.0" + } + }, + "node_modules/solhint/node_modules/eslint-scope": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", + "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/solhint/node_modules/eslint-utils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", + "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^1.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/solhint/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/solhint/node_modules/eslint/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/solhint/node_modules/espree": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-5.0.1.tgz", + "integrity": "sha512-qWAZcWh4XE/RwzLJejfcofscgMc9CamR6Tn1+XRXNzrvUSSbiAjGOI/fggztjIi7y9VLPqnICMIPiGyr8JaZ0A==", + "dev": true, + "dependencies": { + "acorn": "^6.0.7", + "acorn-jsx": "^5.0.0", + "eslint-visitor-keys": "^1.0.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/solhint/node_modules/file-entry-cache": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", + "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", + "dev": true, + "dependencies": { + "flat-cache": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/solhint/node_modules/flat-cache": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", + "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", + "dev": true, + "dependencies": { + "flatted": "^2.0.0", + "rimraf": "2.6.3", + "write": "1.0.3" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/solhint/node_modules/flatted": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", + "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", + "dev": true + }, + "node_modules/solhint/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/solhint/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/solhint/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/solhint/node_modules/levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", + "dev": true, + "dependencies": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/solhint/node_modules/optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "dependencies": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/solhint/node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/solhint/node_modules/prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/solhint/node_modules/prettier": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz", + "integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==", + "dev": true, + "optional": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/solhint/node_modules/regexpp": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", + "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", + "dev": true, + "engines": { + "node": ">=6.5.0" + } + }, + "node_modules/solhint/node_modules/rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/solhint/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/solhint/node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "dev": true, + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/solhint/node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/solhint/node_modules/slice-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", + "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", + "is-fullwidth-code-point": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/solhint/node_modules/string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "dependencies": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/solhint/node_modules/string-width/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/solhint/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/solhint/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/solhint/node_modules/table": { + "version": "5.4.6", + "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", + "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", + "dev": true, + "dependencies": { + "ajv": "^6.10.2", + "lodash": "^4.17.14", + "slice-ansi": "^2.1.0", + "string-width": "^3.0.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/solhint/node_modules/type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "dev": true, + "dependencies": { + "prelude-ls": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/solhint/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/solidity-ast": { + "version": "0.4.39", + "resolved": "https://registry.npmjs.org/solidity-ast/-/solidity-ast-0.4.39.tgz", + "integrity": "sha512-91d4HMzV9x3ZG1fXRtAFFq2UjJrQXkyWdrmzXqBlueOSGB+v+0+iiLfZIPnTE0apndG2zm23qkZQJf8IbRrf7w==", + "dev": true + }, + "node_modules/solidity-comments": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/solidity-comments/-/solidity-comments-0.0.2.tgz", + "integrity": "sha512-G+aK6qtyUfkn1guS8uzqUeua1dURwPlcOjoTYW/TwmXAcE7z/1+oGCfZUdMSe4ZMKklNbVZNiG5ibnF8gkkFfw==", + "dev": true, + "engines": { + "node": ">= 12" + }, + "optionalDependencies": { + "solidity-comments-darwin-arm64": "0.0.2", + "solidity-comments-darwin-x64": "0.0.2", + "solidity-comments-freebsd-x64": "0.0.2", + "solidity-comments-linux-arm64-gnu": "0.0.2", + "solidity-comments-linux-arm64-musl": "0.0.2", + "solidity-comments-linux-x64-gnu": "0.0.2", + "solidity-comments-linux-x64-musl": "0.0.2", + "solidity-comments-win32-arm64-msvc": "0.0.2", + "solidity-comments-win32-ia32-msvc": "0.0.2", + "solidity-comments-win32-x64-msvc": "0.0.2" + } + }, + "node_modules/solidity-comments-darwin-arm64": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/solidity-comments-darwin-arm64/-/solidity-comments-darwin-arm64-0.0.2.tgz", + "integrity": "sha512-HidWkVLSh7v+Vu0CA7oI21GWP/ZY7ro8g8OmIxE8oTqyMwgMbE8F1yc58Sj682Hj199HCZsjmtn1BE4PCbLiGA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/solidity-comments-darwin-x64": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/solidity-comments-darwin-x64/-/solidity-comments-darwin-x64-0.0.2.tgz", + "integrity": "sha512-Zjs0Ruz6faBTPT6fBecUt6qh4CdloT8Bwoc0+qxRoTn9UhYscmbPQkUgQEbS0FQPysYqVzzxJB4h1Ofbf4wwtA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/solidity-comments-extractor": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/solidity-comments-extractor/-/solidity-comments-extractor-0.0.7.tgz", + "integrity": "sha512-wciNMLg/Irp8OKGrh3S2tfvZiZ0NEyILfcRCXCD4mp7SgK/i9gzLfhY2hY7VMCQJ3kH9UB9BzNdibIVMchzyYw==", + "dev": true + }, + "node_modules/solidity-comments-freebsd-x64": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/solidity-comments-freebsd-x64/-/solidity-comments-freebsd-x64-0.0.2.tgz", + "integrity": "sha512-8Qe4mpjuAxFSwZJVk7B8gAoLCdbtS412bQzBwk63L8dmlHogvE39iT70aAk3RHUddAppT5RMBunlPUCFYJ3ZTw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/solidity-comments-linux-arm64-gnu": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/solidity-comments-linux-arm64-gnu/-/solidity-comments-linux-arm64-gnu-0.0.2.tgz", + "integrity": "sha512-spkb0MZZnmrP+Wtq4UxP+nyPAVRe82idOjqndolcNR0S9Xvu4ebwq+LvF4HiUgjTDmeiqYiFZQ8T9KGdLSIoIg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/solidity-comments-linux-arm64-musl": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/solidity-comments-linux-arm64-musl/-/solidity-comments-linux-arm64-musl-0.0.2.tgz", + "integrity": "sha512-guCDbHArcjE+JDXYkxx5RZzY1YF6OnAKCo+sTC5fstyW/KGKaQJNPyBNWuwYsQiaEHpvhW1ha537IvlGek8GqA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/solidity-comments-linux-x64-gnu": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/solidity-comments-linux-x64-gnu/-/solidity-comments-linux-x64-gnu-0.0.2.tgz", + "integrity": "sha512-zIqLehBK/g7tvrFmQljrfZXfkEeLt2v6wbe+uFu6kH/qAHZa7ybt8Vc0wYcmjo2U0PeBm15d79ee3AkwbIjFdQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/solidity-comments-linux-x64-musl": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/solidity-comments-linux-x64-musl/-/solidity-comments-linux-x64-musl-0.0.2.tgz", + "integrity": "sha512-R9FeDloVlFGTaVkOlELDVC7+1Tjx5WBPI5L8r0AGOPHK3+jOcRh6sKYpI+VskSPDc3vOO46INkpDgUXrKydlIw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/solidity-comments-win32-arm64-msvc": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/solidity-comments-win32-arm64-msvc/-/solidity-comments-win32-arm64-msvc-0.0.2.tgz", + "integrity": "sha512-QnWJoCQcJj+rnutULOihN9bixOtYWDdF5Rfz9fpHejL1BtNjdLW1om55XNVHGAHPqBxV4aeQQ6OirKnp9zKsug==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/solidity-comments-win32-ia32-msvc": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/solidity-comments-win32-ia32-msvc/-/solidity-comments-win32-ia32-msvc-0.0.2.tgz", + "integrity": "sha512-vUg4nADtm/NcOtlIymG23NWJUSuMsvX15nU7ynhGBsdKtt8xhdP3C/zA6vjDk8Jg+FXGQL6IHVQ++g/7rSQi0w==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/solidity-comments-win32-x64-msvc": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/solidity-comments-win32-x64-msvc/-/solidity-comments-win32-x64-msvc-0.0.2.tgz", + "integrity": "sha512-36j+KUF4V/y0t3qatHm/LF5sCUCBx2UndxE1kq5bOzh/s+nQgatuyB+Pd5BfuPQHdWu2KaExYe20FlAa6NL7+Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/solidity-coverage": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/solidity-coverage/-/solidity-coverage-0.8.2.tgz", + "integrity": "sha512-cv2bWb7lOXPE9/SSleDO6czkFiMHgP4NXPj+iW9W7iEKLBk7Cj0AGBiNmGX3V1totl9wjPrT0gHmABZKZt65rQ==", + "dev": true, + "dependencies": { + "@ethersproject/abi": "^5.0.9", + "@solidity-parser/parser": "^0.14.1", + "chalk": "^2.4.2", + "death": "^1.1.0", + "detect-port": "^1.3.0", + "difflib": "^0.2.4", + "fs-extra": "^8.1.0", + "ghost-testrpc": "^0.0.2", + "global-modules": "^2.0.0", + "globby": "^10.0.1", + "jsonschema": "^1.2.4", + "lodash": "^4.17.15", + "mocha": "7.1.2", + "node-emoji": "^1.10.0", + "pify": "^4.0.1", + "recursive-readdir": "^2.2.2", + "sc-istanbul": "^0.4.5", + "semver": "^7.3.4", + "shelljs": "^0.8.3", + "web3-utils": "^1.3.6" + }, + "bin": { + "solidity-coverage": "plugins/bin.js" + }, + "peerDependencies": { + "hardhat": "^2.11.0" + } + }, + "node_modules/solidity-coverage/node_modules/ansi-colors": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", + "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/solidity-coverage/node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/solidity-coverage/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/solidity-coverage/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/solidity-coverage/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/solidity-coverage/node_modules/chokidar": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.0.tgz", + "integrity": "sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.2.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.1.1" + } + }, + "node_modules/solidity-coverage/node_modules/cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "dependencies": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + } + }, + "node_modules/solidity-coverage/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/solidity-coverage/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/solidity-coverage/node_modules/debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/solidity-coverage/node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/solidity-coverage/node_modules/diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/solidity-coverage/node_modules/emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "node_modules/solidity-coverage/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/solidity-coverage/node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/solidity-coverage/node_modules/flat": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.1.tgz", + "integrity": "sha512-FmTtBsHskrU6FJ2VxCnsDb84wu9zhmO3cUX2kGFb5tuwhfXxGciiT0oRY+cck35QmG+NmGh5eLz6lLCpWTqwpA==", + "dev": true, + "dependencies": { + "is-buffer": "~2.0.3" + }, + "bin": { + "flat": "cli.js" + } + }, + "node_modules/solidity-coverage/node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/solidity-coverage/node_modules/fsevents": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", + "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "deprecated": "\"Please update to latest v2.3 or v2.2\"", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/solidity-coverage/node_modules/glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/solidity-coverage/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/solidity-coverage/node_modules/js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/solidity-coverage/node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/solidity-coverage/node_modules/log-symbols": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", + "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==", + "dev": true, + "dependencies": { + "chalk": "^2.4.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/solidity-coverage/node_modules/minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/solidity-coverage/node_modules/mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "dependencies": { + "minimist": "^1.2.5" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/solidity-coverage/node_modules/mocha": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-7.1.2.tgz", + "integrity": "sha512-o96kdRKMKI3E8U0bjnfqW4QMk12MwZ4mhdBTf+B5a1q9+aq2HRnj+3ZdJu0B/ZhJeK78MgYuv6L8d/rA5AeBJA==", + "dev": true, + "dependencies": { + "ansi-colors": "3.2.3", + "browser-stdout": "1.3.1", + "chokidar": "3.3.0", + "debug": "3.2.6", + "diff": "3.5.0", + "escape-string-regexp": "1.0.5", + "find-up": "3.0.0", + "glob": "7.1.3", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "3.13.1", + "log-symbols": "3.0.0", + "minimatch": "3.0.4", + "mkdirp": "0.5.5", + "ms": "2.1.1", + "node-environment-flags": "1.0.6", + "object.assign": "4.1.0", + "strip-json-comments": "2.0.1", + "supports-color": "6.0.0", + "which": "1.3.1", + "wide-align": "1.1.3", + "yargs": "13.3.2", + "yargs-parser": "13.1.2", + "yargs-unparser": "1.6.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mochajs" + } + }, + "node_modules/solidity-coverage/node_modules/mocha/node_modules/supports-color": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", + "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/solidity-coverage/node_modules/ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "node_modules/solidity-coverage/node_modules/object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/solidity-coverage/node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/solidity-coverage/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/solidity-coverage/node_modules/readdirp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.2.0.tgz", + "integrity": "sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ==", + "dev": true, + "dependencies": { + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/solidity-coverage/node_modules/string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "dependencies": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/solidity-coverage/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/solidity-coverage/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/solidity-coverage/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/solidity-coverage/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/solidity-coverage/node_modules/wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/solidity-coverage/node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true + }, + "node_modules/solidity-coverage/node_modules/yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "dev": true, + "dependencies": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" + } + }, + "node_modules/solidity-coverage/node_modules/yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "dev": true, + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, + "node_modules/solidity-coverage/node_modules/yargs-unparser": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.0.tgz", + "integrity": "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==", + "dev": true, + "dependencies": { + "flat": "^4.1.0", + "lodash": "^4.17.15", + "yargs": "^13.3.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/solidity-docgen": { + "version": "0.6.0-beta.32", + "resolved": "https://registry.npmjs.org/solidity-docgen/-/solidity-docgen-0.6.0-beta.32.tgz", + "integrity": "sha512-LGcosbgqxrqTo9winVq3+Xz1shS9k4p+RKwqPVPI0HtjfsKF9Mc5GzBzOUhce9uwzy0yJfhf832whfY1UKt4Aw==", + "dev": true, + "dependencies": { + "handlebars": "^4.7.7", + "solidity-ast": "^0.4.38" + }, + "peerDependencies": { + "hardhat": "^2.8.0" + } + }, + "node_modules/source-map": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", + "integrity": "sha512-CBdZ2oa/BHhS4xj5DlhjWNHcan57/5YuvfdLf17iVmIpd9KRm+DFLmC6nBNj+6Ua7Kt3TmOjDpQT1aTYOQtoUA==", + "dev": true, + "optional": true, + "dependencies": { + "amdefine": ">=0.0.4" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/spdx-correct": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", + "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", + "dev": true, + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.12.tgz", + "integrity": "sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==", + "dev": true + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "node_modules/sshpk": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", + "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==", + "dev": true, + "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" + }, + "bin": { + "sshpk-conv": "bin/sshpk-conv", + "sshpk-sign": "bin/sshpk-sign", + "sshpk-verify": "bin/sshpk-verify" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sshpk/node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", + "dev": true + }, + "node_modules/stacktrace-parser": { + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.10.tgz", + "integrity": "sha512-KJP1OCML99+8fhOHxwwzyWrlUuVX5GQ0ZpJTd1DFXhdkrvg1szxfHhawXUZ3g9TkXORQd4/WG68jMlQZ2p8wlg==", + "dev": true, + "dependencies": { + "type-fest": "^0.7.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/stacktrace-parser/node_modules/type-fest": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.7.1.tgz", + "integrity": "sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/stealthy-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", + "integrity": "sha512-ZnWpYnYugiOVEY5GkcuJK1io5V8QmNYChG62gSit9pQVGErXtrKuPC55ITaVSukmMta5qpMU7vqLt2Lnni4f/g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/strict-uri-encode": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", + "integrity": "sha512-R3f198pcvnB+5IpnBlRkphuE9n46WyVl8I39W/ZUTZLz4nqSP/oLYUrcnJrw462Ds8he4YKMov2efsTIw1BDGQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "dependencies": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", + "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", + "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", + "dev": true, + "dependencies": { + "ansi-regex": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-hex-prefix": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-hex-prefix/-/strip-hex-prefix-1.0.0.tgz", + "integrity": "sha512-q8d4ue7JGEiVcypji1bALTos+0pWtyGlivAWyPuTkHzuTCJqrK9sWxYQZUq6Nq3cuyv3bm734IhHvHtGGURU6A==", + "dev": true, + "dependencies": { + "is-hex-prefixed": "1.0.0" + }, + "engines": { + "node": ">=6.5.0", + "npm": ">=3" + } + }, + "node_modules/strip-indent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-2.0.0.tgz", + "integrity": "sha512-RsSNPLpq6YUL7QYy44RnPVTn/lcVZtb48Uof3X5JLbF4zD/Gs7ZFDv2HWol+leoQN2mT86LAzSshGfkTlSOpsA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/swap-case": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/swap-case/-/swap-case-1.1.2.tgz", + "integrity": "sha512-BAmWG6/bx8syfc6qXPprof3Mn5vQgf5dwdUNJhsNqU9WdPt5P+ES/wQ5bxfijy8zwZgZZHslC3iAsxsuQMCzJQ==", + "dev": true, + "dependencies": { + "lower-case": "^1.1.1", + "upper-case": "^1.1.1" + } + }, + "node_modules/swarm-js": { + "version": "0.1.42", + "resolved": "https://registry.npmjs.org/swarm-js/-/swarm-js-0.1.42.tgz", + "integrity": "sha512-BV7c/dVlA3R6ya1lMlSSNPLYrntt0LUq4YMgy3iwpCIc6rZnS5W2wUoctarZ5pXlpKtxDDf9hNziEkcfrxdhqQ==", + "dev": true, + "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" + } + }, + "node_modules/swarm-js/node_modules/@szmarczak/http-timer": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "dev": true, + "dependencies": { + "defer-to-connect": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/swarm-js/node_modules/cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", + "dev": true, + "engines": { + "node": ">=10.6.0" + } + }, + "node_modules/swarm-js/node_modules/fs-extra": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz", + "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "node_modules/swarm-js/node_modules/got": { + "version": "11.8.6", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", + "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", + "dev": true, + "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" + }, + "engines": { + "node": ">=10.19.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/swarm-js/node_modules/http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "dev": true, + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/swarm-js/node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/swarm-js/node_modules/p-cancelable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/sync-request": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/sync-request/-/sync-request-6.1.0.tgz", + "integrity": "sha512-8fjNkrNlNCrVc/av+Jn+xxqfCjYaBoHqCsDz6mt030UMxJGr+GSfCV1dQt2gRtlL63+VPidwDVLr7V2OcTSdRw==", + "dev": true, + "dependencies": { + "http-response-object": "^3.0.1", + "sync-rpc": "^1.2.1", + "then-request": "^6.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/sync-rpc": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/sync-rpc/-/sync-rpc-1.3.6.tgz", + "integrity": "sha512-J8jTXuZzRlvU7HemDgHi3pGnh/rkoqR/OZSjhTyyZrEkkYQbk7Z33AXp37mkPfPpfdOuj7Ex3H/TJM1z48uPQw==", + "dev": true, + "dependencies": { + "get-port": "^3.1.0" + } + }, + "node_modules/table": { + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/table/-/table-6.8.1.tgz", + "integrity": "sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA==", + "dev": true, + "dependencies": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/table/node_modules/ajv": { + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.2.tgz", + "integrity": "sha512-E4bfmKAhGiSTvMfL1Myyycaub+cUEU2/IvpylXkUu7CHBkBj1f/ikdzbD7YQ6FKUbixDxeYvB/xY4fvyroDlQg==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/table/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/table/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/table/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/table/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/table/node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/table/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/table/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar": { + "version": "4.4.19", + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.19.tgz", + "integrity": "sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA==", + "dev": true, + "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" + }, + "engines": { + "node": ">=4.5" + } + }, + "node_modules/testrpc": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/testrpc/-/testrpc-0.0.1.tgz", + "integrity": "sha512-afH1hO+SQ/VPlmaLUFj2636QMeDvPCeQMc/9RBMW0IfjNe9gFD9Ra3ShqYkB7py0do1ZcCna/9acHyzTJ+GcNA==", + "deprecated": "testrpc has been renamed to ganache-cli, please use this package from now on.", + "dev": true + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/then-request": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/then-request/-/then-request-6.0.2.tgz", + "integrity": "sha512-3ZBiG7JvP3wbDzA9iNY5zJQcHL4jn/0BWtXIkagfz7QgOL/LqjCEOBQuJNZfu0XYnv5JhKh+cDxCPM4ILrqruA==", + "dev": true, + "dependencies": { + "@types/concat-stream": "^1.6.0", + "@types/form-data": "0.0.33", + "@types/node": "^8.0.0", + "@types/qs": "^6.2.31", + "caseless": "~0.12.0", + "concat-stream": "^1.6.0", + "form-data": "^2.2.0", + "http-basic": "^8.1.1", + "http-response-object": "^3.0.1", + "promise": "^8.0.0", + "qs": "^6.4.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/then-request/node_modules/@types/node": { + "version": "8.10.66", + "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.66.tgz", + "integrity": "sha512-tktOkFUA4kXx2hhhrB8bIFb5TbwzS4uOhKEmwiD+NoiL0qtP2OQ9mFldbgD4dV1djrlBYP6eBuQZiWjuHUpqFw==", + "dev": true + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true + }, + "node_modules/timed-out": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", + "integrity": "sha512-G7r3AhovYtr5YKOWQkta8RKAPb+J9IsO4uVmzjl8AZwfhs8UcUwTiD6gcJYSgOtzyjvQKrKYn41syHbUWMkafA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/title-case": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/title-case/-/title-case-2.1.1.tgz", + "integrity": "sha512-EkJoZ2O3zdCz3zJsYCsxyq2OC5hrxR9mfdd5I+w8h/tmFfeOxJ+vvkxsKxdmN0WtS9zLdHEgfgVOiMVgv+Po4Q==", + "dev": true, + "dependencies": { + "no-case": "^2.2.0", + "upper-case": "^1.0.3" + } + }, + "node_modules/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "dependencies": { + "os-tmpdir": "~1.0.2" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dev": true, + "dependencies": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tough-cookie/node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true + }, + "node_modules/treeify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/treeify/-/treeify-1.1.0.tgz", + "integrity": "sha512-1m4RA7xVAJrSGrrXGs0L3YTwyvBs2S8PbRHaLZAkFw7JR8oIFwYtysxlBZhYIa7xSyiYJKZ3iGrrk55cGA3i9A==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tsconfig-paths": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", + "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==", + "dev": true, + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/tsort": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/tsort/-/tsort-0.0.1.tgz", + "integrity": "sha512-Tyrf5mxF8Ofs1tNoxA13lFeZ2Zrbd6cKbuH3V+MQ5sb6DtBj5FjrXVsRWT8YvNAQTqNoz66dz1WsbigI22aEnw==", + "dev": true + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/tweetnacl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", + "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", + "dev": true + }, + "node_modules/tweetnacl-util": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/tweetnacl-util/-/tweetnacl-util-0.15.1.tgz", + "integrity": "sha512-RKJBIj8lySrShN4w6i/BonWp2Z/uxwC3h4y7xsRrpP59ZboCd0GpEVsOnMDYLMmKBpYhb5TgHzZXy7wTfYFBRw==", + "dev": true + }, + "node_modules/type": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", + "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", + "dev": true + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "dev": true + }, + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dev": true, + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, + "node_modules/uglify-js": { + "version": "3.17.4", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", + "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", + "dev": true, + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/ultron": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", + "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==", + "dev": true + }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/underscore": { + "version": "1.13.6", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", + "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==", + "dev": true + }, + "node_modules/undici": { + "version": "5.14.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.14.0.tgz", + "integrity": "sha512-yJlHYw6yXPPsuOH0x2Ib1Km61vu4hLiRRQoafs+WUgX1vO64vgnxiCEN9dpIrhZyHFsai3F0AEj4P9zy19enEQ==", + "dev": true, + "dependencies": { + "busboy": "^1.6.0" + }, + "engines": { + "node": ">=12.18" + } + }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/upper-case": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz", + "integrity": "sha512-WRbjgmYzgXkCV7zNVpy5YgrHgbBv126rMALQQMrmzOVC4GM2waQ9x7xtm8VU+1yF2kWyPzI9zbZ48n4vSxwfSA==", + "dev": true + }, + "node_modules/upper-case-first": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/upper-case-first/-/upper-case-first-1.1.2.tgz", + "integrity": "sha512-wINKYvI3Db8dtjikdAqoBbZoP6Q+PZUyfMR7pmwHzjC2quzSkUq5DmPrTtPEqHaz8AGtmsB4TqwapMTM1QAQOQ==", + "dev": true, + "dependencies": { + "upper-case": "^1.1.1" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/url-set-query": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/url-set-query/-/url-set-query-1.0.0.tgz", + "integrity": "sha512-3AChu4NiXquPfeckE5R5cGdiHCMWJx1dwCWOmWIL4KHAziJNOFIYJlpGFeKDvwLPHovZRCxK3cYlwzqI9Vp+Gg==", + "dev": true + }, + "node_modules/utf-8-validate": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz", + "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, + "node_modules/utf8": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/utf8/-/utf8-3.0.0.tgz", + "integrity": "sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==", + "dev": true + }, + "node_modules/util": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/v8-compile-cache": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", + "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", + "dev": true + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/varint": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/varint/-/varint-5.0.2.tgz", + "integrity": "sha512-lKxKYG6H03yCZUpAGOPOsMcGxd1RHCu1iKvEHYDPmTyq2HueGhD73ssNBqqQWfvYs04G9iUFRvmAVLW20Jw6ow==", + "dev": true + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "node_modules/web3": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3/-/web3-1.8.1.tgz", + "integrity": "sha512-tAqFsQhGv340C9OgRJIuoScN7f7wa1tUvsnnDUMt9YE6J4gcm7TV2Uwv+KERnzvV+xgdeuULYpsioRRNKrUvoQ==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "web3-bzz": "1.8.1", + "web3-core": "1.8.1", + "web3-eth": "1.8.1", + "web3-eth-personal": "1.8.1", + "web3-net": "1.8.1", + "web3-shh": "1.8.1", + "web3-utils": "1.8.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/web3-bzz": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-bzz/-/web3-bzz-1.8.1.tgz", + "integrity": "sha512-dJJHS84nvpoxv6ijTMkdUSlRr5beCXNtx4UZcrFLHBva8dT63QEtKdLyDt2AyMJJdVzTCk78uir/6XtVWrdS6w==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "@types/node": "^12.12.6", + "got": "12.1.0", + "swarm-js": "^0.1.40" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/web3-bzz/node_modules/@types/node": { + "version": "12.20.55", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz", + "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==", + "dev": true + }, + "node_modules/web3-core": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-core/-/web3-core-1.8.1.tgz", + "integrity": "sha512-LbRZlJH2N6nS3n3Eo9Y++25IvzMY7WvYnp4NM/Ajhh97dAdglYs6rToQ2DbL2RLvTYmTew4O/y9WmOk4nq9COw==", + "dev": true, + "dependencies": { + "@types/bn.js": "^5.1.0", + "@types/node": "^12.12.6", + "bignumber.js": "^9.0.0", + "web3-core-helpers": "1.8.1", + "web3-core-method": "1.8.1", + "web3-core-requestmanager": "1.8.1", + "web3-utils": "1.8.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/web3-core-helpers": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-core-helpers/-/web3-core-helpers-1.8.1.tgz", + "integrity": "sha512-ClzNO6T1S1gifC+BThw0+GTfcsjLEY8T1qUp6Ly2+w4PntAdNtKahxWKApWJ0l9idqot/fFIDXwO3Euu7I0Xqw==", + "dev": true, + "dependencies": { + "web3-eth-iban": "1.8.1", + "web3-utils": "1.8.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/web3-core-method": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-core-method/-/web3-core-method-1.8.1.tgz", + "integrity": "sha512-oYGRodktfs86NrnFwaWTbv2S38JnpPslFwSSARwFv4W9cjbGUW3LDeA5MKD/dRY+ssZ5OaekeMsUCLoGhX68yA==", + "dev": true, + "dependencies": { + "@ethersproject/transactions": "^5.6.2", + "web3-core-helpers": "1.8.1", + "web3-core-promievent": "1.8.1", + "web3-core-subscriptions": "1.8.1", + "web3-utils": "1.8.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/web3-core-promievent": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-core-promievent/-/web3-core-promievent-1.8.1.tgz", + "integrity": "sha512-9mxqHlgB0MrZI4oUIRFkuoJMNj3E7btjrMv3sMer/Z9rYR1PfoSc1aAokw4rxKIcAh+ylVtd/acaB2HKB7aRPg==", + "dev": true, + "dependencies": { + "eventemitter3": "4.0.4" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/web3-core-requestmanager": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-core-requestmanager/-/web3-core-requestmanager-1.8.1.tgz", + "integrity": "sha512-x+VC2YPPwZ1khvqA6TA69LvfFCOZXsoUVOxmTx/vIN22PrY9KzKhxcE7pBSiGhmab1jtmRYXUbcQSVpAXqL8cw==", + "dev": true, + "dependencies": { + "util": "^0.12.0", + "web3-core-helpers": "1.8.1", + "web3-providers-http": "1.8.1", + "web3-providers-ipc": "1.8.1", + "web3-providers-ws": "1.8.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/web3-core-subscriptions": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-core-subscriptions/-/web3-core-subscriptions-1.8.1.tgz", + "integrity": "sha512-bmCMq5OeA3E2vZUh8Js1HcJbhwtsE+yeMqGC4oIZB3XsL5SLqyKLB/pU+qUYqQ9o4GdcrFTDPhPg1bgvf7p1Pw==", + "dev": true, + "dependencies": { + "eventemitter3": "4.0.4", + "web3-core-helpers": "1.8.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/web3-core/node_modules/@types/node": { + "version": "12.20.55", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz", + "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==", + "dev": true + }, + "node_modules/web3-core/node_modules/bignumber.js": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.1.tgz", + "integrity": "sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/web3-eth": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-eth/-/web3-eth-1.8.1.tgz", + "integrity": "sha512-LgyzbhFqiFRd8M8sBXoFN4ztzOnkeckl3H/9lH5ek7AdoRMhBg7tYpYRP3E5qkhd/q+yiZmcUgy1AF6NHrC1wg==", + "dev": true, + "dependencies": { + "web3-core": "1.8.1", + "web3-core-helpers": "1.8.1", + "web3-core-method": "1.8.1", + "web3-core-subscriptions": "1.8.1", + "web3-eth-abi": "1.8.1", + "web3-eth-accounts": "1.8.1", + "web3-eth-contract": "1.8.1", + "web3-eth-ens": "1.8.1", + "web3-eth-iban": "1.8.1", + "web3-eth-personal": "1.8.1", + "web3-net": "1.8.1", + "web3-utils": "1.8.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/web3-eth-abi": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-eth-abi/-/web3-eth-abi-1.8.1.tgz", + "integrity": "sha512-0mZvCRTIG0UhDhJwNQJgJxu4b4DyIpuMA0GTfqxqeuqzX4Q/ZvmoNurw0ExTfXaGPP82UUmmdkRi6FdZOx+C6w==", + "dev": true, + "dependencies": { + "@ethersproject/abi": "^5.6.3", + "web3-utils": "1.8.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/web3-eth-accounts": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-eth-accounts/-/web3-eth-accounts-1.8.1.tgz", + "integrity": "sha512-mgzxSYgN54/NsOFBO1Fq1KkXp1S5KlBvI/DlgvajU72rupoFMq6Cu6Yp9GUaZ/w2ij9PzEJuFJk174XwtfMCmg==", + "dev": true, + "dependencies": { + "@ethereumjs/common": "2.5.0", + "@ethereumjs/tx": "3.3.2", + "crypto-browserify": "3.12.0", + "eth-lib": "0.2.8", + "ethereumjs-util": "^7.0.10", + "scrypt-js": "^3.0.1", + "uuid": "^9.0.0", + "web3-core": "1.8.1", + "web3-core-helpers": "1.8.1", + "web3-core-method": "1.8.1", + "web3-utils": "1.8.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/web3-eth-accounts/node_modules/eth-lib": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/eth-lib/-/eth-lib-0.2.8.tgz", + "integrity": "sha512-ArJ7x1WcWOlSpzdoTBX8vkwlkSQ85CjjifSZtV4co64vWxSV8geWfPI9x4SVYu3DSxnX4yWFVTtGL+j9DUFLNw==", + "dev": true, + "dependencies": { + "bn.js": "^4.11.6", + "elliptic": "^6.4.0", + "xhr-request-promise": "^0.1.2" + } + }, + "node_modules/web3-eth-accounts/node_modules/uuid": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/web3-eth-contract": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-eth-contract/-/web3-eth-contract-1.8.1.tgz", + "integrity": "sha512-1wphnl+/xwCE2io44JKnN+ti3oa47BKRiVzvWd42icwRbcpFfRxH9QH+aQX3u8VZIISNH7dAkTWpGIIJgGFTmg==", + "dev": true, + "dependencies": { + "@types/bn.js": "^5.1.0", + "web3-core": "1.8.1", + "web3-core-helpers": "1.8.1", + "web3-core-method": "1.8.1", + "web3-core-promievent": "1.8.1", + "web3-core-subscriptions": "1.8.1", + "web3-eth-abi": "1.8.1", + "web3-utils": "1.8.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/web3-eth-ens": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-eth-ens/-/web3-eth-ens-1.8.1.tgz", + "integrity": "sha512-FT8xTI9uN8RxeBQa/W8pLa2aoFh4+EE34w7W2271LICKzla1dtLyb6XSdn48vsUcPmhWsTVk9mO9RTU0l4LGQQ==", + "dev": true, + "dependencies": { + "content-hash": "^2.5.2", + "eth-ens-namehash": "2.0.8", + "web3-core": "1.8.1", + "web3-core-helpers": "1.8.1", + "web3-core-promievent": "1.8.1", + "web3-eth-abi": "1.8.1", + "web3-eth-contract": "1.8.1", + "web3-utils": "1.8.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/web3-eth-iban": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-eth-iban/-/web3-eth-iban-1.8.1.tgz", + "integrity": "sha512-DomoQBfvIdtM08RyMGkMVBOH0vpOIxSSQ+jukWk/EkMLGMWJtXw/K2c2uHAeq3L/VPWNB7zXV2DUEGV/lNE2Dg==", + "dev": true, + "dependencies": { + "bn.js": "^5.2.1", + "web3-utils": "1.8.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/web3-eth-iban/node_modules/bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", + "dev": true + }, + "node_modules/web3-eth-personal": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-eth-personal/-/web3-eth-personal-1.8.1.tgz", + "integrity": "sha512-myIYMvj7SDIoV9vE5BkVdon3pya1WinaXItugoii2VoTcQNPOtBxmYVH+XS5ErzCJlnxzphpQrkywyY64bbbCA==", + "dev": true, + "dependencies": { + "@types/node": "^12.12.6", + "web3-core": "1.8.1", + "web3-core-helpers": "1.8.1", + "web3-core-method": "1.8.1", + "web3-net": "1.8.1", + "web3-utils": "1.8.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/web3-eth-personal/node_modules/@types/node": { + "version": "12.20.55", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz", + "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==", + "dev": true + }, + "node_modules/web3-net": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-net/-/web3-net-1.8.1.tgz", + "integrity": "sha512-LyEJAwogdFo0UAXZqoSJGFjopdt+kLw0P00FSZn2yszbgcoI7EwC+nXiOsEe12xz4LqpYLOtbR7+gxgiTVjjHQ==", + "dev": true, + "dependencies": { + "web3-core": "1.8.1", + "web3-core-method": "1.8.1", + "web3-utils": "1.8.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/web3-providers-http": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-providers-http/-/web3-providers-http-1.8.1.tgz", + "integrity": "sha512-1Zyts4O9W/UNEPkp+jyL19Jc3D15S4yp8xuLTjVhcUEAlHo24NDWEKxtZGUuHk4HrKL2gp8OlsDbJ7MM+ESDgg==", + "dev": true, + "dependencies": { + "abortcontroller-polyfill": "^1.7.3", + "cross-fetch": "^3.1.4", + "es6-promise": "^4.2.8", + "web3-core-helpers": "1.8.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/web3-providers-ipc": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-providers-ipc/-/web3-providers-ipc-1.8.1.tgz", + "integrity": "sha512-nw/W5nclvi+P2z2dYkLWReKLnocStflWqFl+qjtv0xn3MrUTyXMzSF0+61i77+16xFsTgzo4wS/NWIOVkR0EFA==", + "dev": true, + "dependencies": { + "oboe": "2.1.5", + "web3-core-helpers": "1.8.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/web3-providers-ws": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-providers-ws/-/web3-providers-ws-1.8.1.tgz", + "integrity": "sha512-TNefIDAMpdx57+YdWpYZ/xdofS0P+FfKaDYXhn24ie/tH9G+AB+UBSOKnjN0KSadcRSCMBwGPRiEmNHPavZdsA==", + "dev": true, + "dependencies": { + "eventemitter3": "4.0.4", + "web3-core-helpers": "1.8.1", + "websocket": "^1.0.32" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/web3-shh": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-shh/-/web3-shh-1.8.1.tgz", + "integrity": "sha512-sqHgarnfcY2Qt3PYS4R6YveHrDy7hmL09yeLLHHCI+RKirmjLVqV0rc5LJWUtlbYI+kDoa5gbgde489M9ZAC0g==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "web3-core": "1.8.1", + "web3-core-method": "1.8.1", + "web3-core-subscriptions": "1.8.1", + "web3-net": "1.8.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/web3-utils": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.8.1.tgz", + "integrity": "sha512-LgnM9p6V7rHHUGfpMZod+NST8cRfGzJ1BTXAyNo7A9cJX9LczBfSRxJp+U/GInYe9mby40t3v22AJdlELibnsQ==", + "dev": true, + "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" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/web3-utils/node_modules/bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", + "dev": true + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true + }, + "node_modules/websocket": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.34.tgz", + "integrity": "sha512-PRDso2sGwF6kM75QykIesBijKSVceR6jL2G8NGYyq2XrItNC2P5/qL5XeR056GhA+Ly7JMFvJb9I312mJfmqnQ==", + "dev": true, + "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" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/websocket/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/websocket/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==", + "dev": true + }, + "node_modules/which-typed-array": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", + "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dev": true, + "dependencies": { + "string-width": "^1.0.2 || 2" + } + }, + "node_modules/window-size": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.2.0.tgz", + "integrity": "sha512-UD7d8HFA2+PZsbKyaOCEy8gMh1oDtHgJh1LfgjQ4zVXmYjAT/kvz3PueITKuqDiIXQe7yzpPnxX3lNc+AhQMyw==", + "dev": true, + "bin": { + "window-size": "cli.js" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true + }, + "node_modules/workerpool": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", + "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", + "dev": true + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/write": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", + "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", + "dev": true, + "dependencies": { + "mkdirp": "^0.5.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/ws": { + "version": "7.5.9", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", + "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "dev": true, + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xhr": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/xhr/-/xhr-2.6.0.tgz", + "integrity": "sha512-/eCGLb5rxjx5e3mF1A7s+pLlR6CGyqWN91fv1JgER5mVWg1MZmlhBvy9kjcsOdRk8RrIujotWyJamfyrp+WIcA==", + "dev": true, + "dependencies": { + "global": "~4.4.0", + "is-function": "^1.0.1", + "parse-headers": "^2.0.0", + "xtend": "^4.0.0" + } + }, + "node_modules/xhr-request": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/xhr-request/-/xhr-request-1.1.0.tgz", + "integrity": "sha512-Y7qzEaR3FDtL3fP30k9wO/e+FBnBByZeybKOhASsGP30NIkRAAkKD/sCnLvgEfAIEC1rcmK7YG8f4oEnIrrWzA==", + "dev": true, + "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" + } + }, + "node_modules/xhr-request-promise": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/xhr-request-promise/-/xhr-request-promise-0.1.3.tgz", + "integrity": "sha512-YUBytBsuwgitWtdRzXDDkWAXzhdGB8bYm0sSzMPZT7Z2MBjMSTHFsyCT1yCRATY+XC69DUrQraRAEgcoCRaIPg==", + "dev": true, + "dependencies": { + "xhr-request": "^1.1.0" + } + }, + "node_modules/xmlhttprequest": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz", + "integrity": "sha512-58Im/U0mlVBLM38NdZjHyhuMtCqa61469k2YP/AaPbvCoV9aQGUpbJBj1QRm2ytRiVQBD/fsw7L2bJGDVQswBA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yaeti": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz", + "integrity": "sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug==", + "dev": true, + "engines": { + "node": ">=0.10.32" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/yargs": { + "version": "17.6.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.2.tgz", + "integrity": "sha512-1/9UrdHjDZc0eOU0HxOHoS78C69UD3JRMvzlJ7S79S2nTaWRA/whGCTV8o9e/N/1Va9YIV7Q4sOxD8VV4pCWOw==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/yargs/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", + "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "dev": true + }, + "@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.18.6", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" } }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "@babel/runtime": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.7.tgz", + "integrity": "sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==", + "dev": true, + "requires": { + "regenerator-runtime": "^0.13.11" + } + }, + "@ensdomains/address-encoder": { + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/@ensdomains/address-encoder/-/address-encoder-0.1.9.tgz", + "integrity": "sha512-E2d2gP4uxJQnDu2Kfg1tHNspefzbLT8Tyjrm5sEuim32UkU2sm5xL4VXtgc2X33fmPEw9+jUMpGs4veMbf+PYg==", + "dev": true, + "requires": { + "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/ens": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/@ensdomains/ens/-/ens-0.4.5.tgz", + "integrity": "sha512-JSvpj1iNMFjK6K+uVl4unqMoa9rf5jopb8cya5UGBWz23Nw8hSNT7efgUx4BTlAPAgpNlEioUfeTyQ6J9ZvTVw==", + "dev": true, + "requires": { + "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": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@ensdomains/ensjs/-/ensjs-2.1.0.tgz", + "integrity": "sha512-GRbGPT8Z/OJMDuxs75U/jUNEC0tbL0aj7/L/QQznGYKm/tiasp+ndLOaoULy9kKJFC0TBByqfFliEHDgoLhyog==", + "dev": true, + "requires": { + "@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" + }, + "dependencies": { + "ethers": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-5.7.2.tgz", + "integrity": "sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg==", + "dev": true, + "requires": { + "@ethersproject/abi": "5.7.0", + "@ethersproject/abstract-provider": "5.7.0", + "@ethersproject/abstract-signer": "5.7.0", + "@ethersproject/address": "5.7.0", + "@ethersproject/base64": "5.7.0", + "@ethersproject/basex": "5.7.0", + "@ethersproject/bignumber": "5.7.0", + "@ethersproject/bytes": "5.7.0", + "@ethersproject/constants": "5.7.0", + "@ethersproject/contracts": "5.7.0", + "@ethersproject/hash": "5.7.0", + "@ethersproject/hdnode": "5.7.0", + "@ethersproject/json-wallets": "5.7.0", + "@ethersproject/keccak256": "5.7.0", + "@ethersproject/logger": "5.7.0", + "@ethersproject/networks": "5.7.1", + "@ethersproject/pbkdf2": "5.7.0", + "@ethersproject/properties": "5.7.0", + "@ethersproject/providers": "5.7.2", + "@ethersproject/random": "5.7.0", + "@ethersproject/rlp": "5.7.0", + "@ethersproject/sha2": "5.7.0", + "@ethersproject/signing-key": "5.7.0", + "@ethersproject/solidity": "5.7.0", + "@ethersproject/strings": "5.7.0", + "@ethersproject/transactions": "5.7.0", + "@ethersproject/units": "5.7.0", + "@ethersproject/wallet": "5.7.0", + "@ethersproject/web": "5.7.1", + "@ethersproject/wordlists": "5.7.0" + } + } + } + }, + "@ensdomains/resolver": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@ensdomains/resolver/-/resolver-0.2.4.tgz", + "integrity": "sha512-bvaTH34PMCbv6anRa9I/0zjLJgY4EuznbEMgbV77JBCQ9KNC46rzi0avuxpOfu+xDjPEtSFGqVEOr5GlUSGudA==", + "dev": true + }, + "@eslint/eslintrc": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", + "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.1.1", + "espree": "^7.3.0", + "globals": "^13.9.0", + "ignore": "^4.0.6", + "import-fresh": "^3.2.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "strip-json-comments": "^3.1.1" + } + }, + "@ethereumjs/common": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@ethereumjs/common/-/common-2.5.0.tgz", + "integrity": "sha512-DEHjW6e38o+JmB/NO3GZBpW4lpaiBpkFgXF6jLcJ6gETBYpEyaA5nTimsWBUJR3Vmtm/didUEbNjajskugZORg==", + "dev": true, + "requires": { + "crc-32": "^1.2.0", + "ethereumjs-util": "^7.1.1" + } + }, + "@ethereumjs/tx": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/@ethereumjs/tx/-/tx-3.3.2.tgz", + "integrity": "sha512-6AaJhwg4ucmwTvw/1qLaZUX5miWrwZ4nLOUsKyb/HtzS3BMw/CasKhdi1ims9mBKeK9sOJCH4qGKOBGyJCeeog==", + "dev": true, + "requires": { + "@ethereumjs/common": "^2.5.0", + "ethereumjs-util": "^7.1.2" + } + }, + "@ethersproject/abi": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.7.0.tgz", + "integrity": "sha512-351ktp42TiRcYB3H1OP8yajPeAQstMW/yCFokj/AthP9bLHzQFPlOrxOcwYEDkUAICmOHljvN4K39OMTMUa9RA==", + "dev": true, + "requires": { + "@ethersproject/address": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/hash": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/strings": "^5.7.0" + } + }, + "@ethersproject/abstract-provider": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.7.0.tgz", + "integrity": "sha512-R41c9UkchKCpAqStMYUpdunjo3pkEvZC3FAwZn5S5MGbXoMQOHIdHItezTETxAO5bevtMApSyEhn9+CHcDsWBw==", + "dev": true, + "requires": { + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/networks": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/transactions": "^5.7.0", + "@ethersproject/web": "^5.7.0" + } + }, + "@ethersproject/abstract-signer": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/abstract-signer/-/abstract-signer-5.7.0.tgz", + "integrity": "sha512-a16V8bq1/Cz+TGCkE2OPMTOUDLS3grCpdjoJCYNnVBbdYEMSgKrU0+B90s8b6H+ByYTBZN7a3g76jdIJi7UfKQ==", + "dev": true, + "requires": { + "@ethersproject/abstract-provider": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0" + } + }, + "@ethersproject/address": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/address/-/address-5.7.0.tgz", + "integrity": "sha512-9wYhYt7aghVGo758POM5nqcOMaE168Q6aRLJZwUmiqSrAungkG74gSSeKEIR7ukixesdRZGPgVqme6vmxs1fkA==", + "dev": true, + "requires": { + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/rlp": "^5.7.0" + } + }, + "@ethersproject/base64": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/base64/-/base64-5.7.0.tgz", + "integrity": "sha512-Dr8tcHt2mEbsZr/mwTPIQAf3Ai0Bks/7gTw9dSqk1mQvhW3XvRlmDJr/4n+wg1JmCl16NZue17CDh8xb/vZ0sQ==", + "dev": true, + "requires": { + "@ethersproject/bytes": "^5.7.0" + } + }, + "@ethersproject/basex": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/basex/-/basex-5.7.0.tgz", + "integrity": "sha512-ywlh43GwZLv2Voc2gQVTKBoVQ1mti3d8HK5aMxsfu/nRDnMmNqaSJ3r3n85HBByT8OpoY96SXM1FogC533T4zw==", + "dev": true, + "requires": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/properties": "^5.7.0" + } + }, + "@ethersproject/bignumber": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.7.0.tgz", + "integrity": "sha512-n1CAdIHRWjSucQO3MC1zPSVgV/6dy/fjL9pMrPP9peL+QxEg9wOsVqwD4+818B6LUEtaXzVHQiuivzRoxPxUGw==", + "dev": true, + "requires": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "bn.js": "^5.2.1" + }, + "dependencies": { + "bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", + "dev": true + } + } + }, + "@ethersproject/bytes": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.7.0.tgz", + "integrity": "sha512-nsbxwgFXWh9NyYWo+U8atvmMsSdKJprTcICAkvbBffT75qDocbuggBU0SJiVK2MuTrp0q+xvLkTnGMPK1+uA9A==", + "dev": true, + "requires": { + "@ethersproject/logger": "^5.7.0" + } + }, + "@ethersproject/constants": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.7.0.tgz", + "integrity": "sha512-DHI+y5dBNvkpYUMiRQyxRBYBefZkJfo70VUkUAsRjcPs47muV9evftfZ0PJVCXYbAiCgght0DtcF9srFQmIgWA==", + "dev": true, + "requires": { + "@ethersproject/bignumber": "^5.7.0" + } + }, + "@ethersproject/contracts": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/contracts/-/contracts-5.7.0.tgz", + "integrity": "sha512-5GJbzEU3X+d33CdfPhcyS+z8MzsTrBGk/sc+G+59+tPa9yFkl6HQ9D6L0QMgNTA9q8dT0XKxxkyp883XsQvbbg==", + "dev": true, + "requires": { + "@ethersproject/abi": "^5.7.0", + "@ethersproject/abstract-provider": "^5.7.0", + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/address": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/transactions": "^5.7.0" + } + }, + "@ethersproject/hash": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.7.0.tgz", + "integrity": "sha512-qX5WrQfnah1EFnO5zJv1v46a8HW0+E5xuBBDTwMFZLuVTx0tbU2kkx15NqdjxecrLGatQN9FGQKpb1FKdHCt+g==", + "dev": true, + "requires": { + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/address": "^5.7.0", + "@ethersproject/base64": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/strings": "^5.7.0" + } + }, + "@ethersproject/hdnode": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/hdnode/-/hdnode-5.7.0.tgz", + "integrity": "sha512-OmyYo9EENBPPf4ERhR7oj6uAtUAhYGqOnIS+jE5pTXvdKBS99ikzq1E7Iv0ZQZ5V36Lqx1qZLeak0Ra16qpeOg==", + "dev": true, + "requires": { + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/basex": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/pbkdf2": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/sha2": "^5.7.0", + "@ethersproject/signing-key": "^5.7.0", + "@ethersproject/strings": "^5.7.0", + "@ethersproject/transactions": "^5.7.0", + "@ethersproject/wordlists": "^5.7.0" + } + }, + "@ethersproject/json-wallets": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/json-wallets/-/json-wallets-5.7.0.tgz", + "integrity": "sha512-8oee5Xgu6+RKgJTkvEMl2wDgSPSAQ9MB/3JYjFV9jlKvcYHUXZC+cQp0njgmxdHkYWn8s6/IqIZYm0YWCjO/0g==", + "dev": true, + "requires": { + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/address": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/hdnode": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/pbkdf2": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/random": "^5.7.0", + "@ethersproject/strings": "^5.7.0", + "@ethersproject/transactions": "^5.7.0", + "aes-js": "3.0.0", + "scrypt-js": "3.0.1" + }, + "dependencies": { + "aes-js": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.0.0.tgz", + "integrity": "sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw==", + "dev": true + } + } + }, + "@ethersproject/keccak256": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.7.0.tgz", + "integrity": "sha512-2UcPboeL/iW+pSg6vZ6ydF8tCnv3Iu/8tUmLLzWWGzxWKFFqOBQFLo6uLUv6BDrLgCDfN28RJ/wtByx+jZ4KBg==", + "dev": true, + "requires": { + "@ethersproject/bytes": "^5.7.0", + "js-sha3": "0.8.0" + } + }, + "@ethersproject/logger": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.7.0.tgz", + "integrity": "sha512-0odtFdXu/XHtjQXJYA3u9G0G8btm0ND5Cu8M7i5vhEcE8/HmF4Lbdqanwyv4uQTr2tx6b7fQRmgLrsnpQlmnig==", + "dev": true + }, + "@ethersproject/networks": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.7.1.tgz", + "integrity": "sha512-n/MufjFYv3yFcUyfhnXotyDlNdFb7onmkSy8aQERi2PjNcnWQ66xXxa3XlS8nCcA8aJKJjIIMNJTC7tu80GwpQ==", + "dev": true, + "requires": { + "@ethersproject/logger": "^5.7.0" + } + }, + "@ethersproject/pbkdf2": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/pbkdf2/-/pbkdf2-5.7.0.tgz", + "integrity": "sha512-oR/dBRZR6GTyaofd86DehG72hY6NpAjhabkhxgr3X2FpJtJuodEl2auADWBZfhDHgVCbu3/H/Ocq2uC6dpNjjw==", + "dev": true, + "requires": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/sha2": "^5.7.0" + } + }, + "@ethersproject/properties": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.7.0.tgz", + "integrity": "sha512-J87jy8suntrAkIZtecpxEPxY//szqr1mlBaYlQ0r4RCaiD2hjheqF9s1LVE8vVuJCXisjIP+JgtK/Do54ej4Sw==", + "dev": true, + "requires": { + "@ethersproject/logger": "^5.7.0" + } + }, + "@ethersproject/providers": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/@ethersproject/providers/-/providers-5.7.2.tgz", + "integrity": "sha512-g34EWZ1WWAVgr4aptGlVBF8mhl3VWjv+8hoAnzStu8Ah22VHBsuGzP17eb6xDVRzw895G4W7vvx60lFFur/1Rg==", + "dev": true, + "requires": { + "@ethersproject/abstract-provider": "^5.7.0", + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/address": "^5.7.0", + "@ethersproject/base64": "^5.7.0", + "@ethersproject/basex": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/hash": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/networks": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/random": "^5.7.0", + "@ethersproject/rlp": "^5.7.0", + "@ethersproject/sha2": "^5.7.0", + "@ethersproject/strings": "^5.7.0", + "@ethersproject/transactions": "^5.7.0", + "@ethersproject/web": "^5.7.0", + "bech32": "1.1.4", + "ws": "7.4.6" + }, + "dependencies": { + "ws": { + "version": "7.4.6", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", + "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "got": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", - "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", + "requires": {} + } + } + }, + "@ethersproject/random": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/random/-/random-5.7.0.tgz", + "integrity": "sha512-19WjScqRA8IIeWclFme75VMXSBvi4e6InrUNuaR4s5pTF2qNhcGdCUwdxUVGtDDqC00sDLCO93jPQoDUH4HVmQ==", + "dev": true, + "requires": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0" + } + }, + "@ethersproject/rlp": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/rlp/-/rlp-5.7.0.tgz", + "integrity": "sha512-rBxzX2vK8mVF7b0Tol44t5Tb8gomOHkj5guL+HhzQ1yBh/ydjGnpw6at+X6Iw0Kp3OzzzkcKp8N9r0W4kYSs9w==", + "dev": true, + "requires": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0" + } + }, + "@ethersproject/sha2": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/sha2/-/sha2-5.7.0.tgz", + "integrity": "sha512-gKlH42riwb3KYp0reLsFTokByAKoJdgFCwI+CCiX/k+Jm2mbNs6oOaCjYQSlI1+XBVejwH2KrmCbMAT/GnRDQw==", + "dev": true, + "requires": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "hash.js": "1.1.7" + } + }, + "@ethersproject/signing-key": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.7.0.tgz", + "integrity": "sha512-MZdy2nL3wO0u7gkB4nA/pEf8lu1TlFswPNmy8AiYkfKTdO6eXBJyUdmHO/ehm/htHw9K/qF8ujnTyUAD+Ry54Q==", + "dev": true, + "requires": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "bn.js": "^5.2.1", + "elliptic": "6.5.4", + "hash.js": "1.1.7" + }, + "dependencies": { + "bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", + "dev": true + } + } + }, + "@ethersproject/solidity": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/solidity/-/solidity-5.7.0.tgz", + "integrity": "sha512-HmabMd2Dt/raavyaGukF4XxizWKhKQ24DoLtdNbBmNKUOPqwjsKQSdV9GQtj9CBEea9DlzETlVER1gYeXXBGaA==", + "dev": true, + "requires": { + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/sha2": "^5.7.0", + "@ethersproject/strings": "^5.7.0" + } + }, + "@ethersproject/strings": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.7.0.tgz", + "integrity": "sha512-/9nu+lj0YswRNSH0NXYqrh8775XNyEdUQAuf3f+SmOrnVewcJ5SBNAjF7lpgehKi4abvNNXyf+HX86czCdJ8Mg==", + "dev": true, + "requires": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/logger": "^5.7.0" + } + }, + "@ethersproject/transactions": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/transactions/-/transactions-5.7.0.tgz", + "integrity": "sha512-kmcNicCp1lp8qanMTC3RIikGgoJ80ztTyvtsFvCYpSCfkjhD0jZ2LOrnbcuxuToLIUYYf+4XwD1rP+B/erDIhQ==", + "dev": true, + "requires": { + "@ethersproject/address": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/rlp": "^5.7.0", + "@ethersproject/signing-key": "^5.7.0" + } + }, + "@ethersproject/units": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/units/-/units-5.7.0.tgz", + "integrity": "sha512-pD3xLMy3SJu9kG5xDGI7+xhTEmGXlEqXU4OfNapmfnxLVY4EMSSRp7j1k7eezutBPH7RBN/7QPnwR7hzNlEFeg==", + "dev": true, + "requires": { + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/logger": "^5.7.0" + } + }, + "@ethersproject/wallet": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/wallet/-/wallet-5.7.0.tgz", + "integrity": "sha512-MhmXlJXEJFBFVKrDLB4ZdDzxcBxQ3rLyCkhNqVu3CDYvR97E+8r01UgrI+TI99Le+aYm/in/0vp86guJuM7FCA==", + "dev": true, + "requires": { + "@ethersproject/abstract-provider": "^5.7.0", + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/address": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/hash": "^5.7.0", + "@ethersproject/hdnode": "^5.7.0", + "@ethersproject/json-wallets": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/random": "^5.7.0", + "@ethersproject/signing-key": "^5.7.0", + "@ethersproject/transactions": "^5.7.0", + "@ethersproject/wordlists": "^5.7.0" + } + }, + "@ethersproject/web": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/@ethersproject/web/-/web-5.7.1.tgz", + "integrity": "sha512-Gueu8lSvyjBWL4cYsWsjh6MtMwM0+H4HvqFPZfB6dV8ctbP9zFAO73VG1cMWae0FLPCtz0peKPpZY8/ugJJX2w==", + "dev": true, + "requires": { + "@ethersproject/base64": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/strings": "^5.7.0" + } + }, + "@ethersproject/wordlists": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/wordlists/-/wordlists-5.7.0.tgz", + "integrity": "sha512-S2TFNJNfHWVHNE6cNDjbVlZ6MgE17MIxMbMg2zv3wn+3XSJGosL1m9ZVv3GXCf/2ymSsQ+hRI5IzoMJTG6aoVA==", + "dev": true, + "requires": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/hash": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/strings": "^5.7.0" + } + }, + "@frangio/servbot": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@frangio/servbot/-/servbot-0.2.5.tgz", + "integrity": "sha512-ogja4iAPZ1VwM5MU3C1ZhB88358F0PGbmSTGOkIZwOyLaDoMHIqOVCnavHjR7DV5h+oAI4Z4KDqlam3myQUrmg==", + "dev": true + }, + "@humanwhocodes/config-array": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", + "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", + "dev": true, + "requires": { + "@humanwhocodes/object-schema": "^1.2.0", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + } + }, + "@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "@metamask/eth-sig-util": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@metamask/eth-sig-util/-/eth-sig-util-4.0.1.tgz", + "integrity": "sha512-tghyZKLHZjcdlDqCA3gNZmLeR0XvOE9U1qoQO9ohyAZT6Pya+H9vkBPcsyXytmYLNgVoin7CKCmweo/R43V+tQ==", + "dev": true, + "requires": { + "ethereumjs-abi": "^0.6.8", + "ethereumjs-util": "^6.2.1", + "ethjs-util": "^0.1.6", + "tweetnacl": "^1.0.3", + "tweetnacl-util": "^0.15.1" + }, + "dependencies": { + "@types/bn.js": { + "version": "4.11.6", + "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-4.11.6.tgz", + "integrity": "sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg==", "dev": true, "requires": { - "@sindresorhus/is": "^0.14.0", - "@szmarczak/http-timer": "^1.1.2", - "cacheable-request": "^6.0.0", - "decompress-response": "^3.3.0", - "duplexer3": "^0.1.4", - "get-stream": "^4.1.0", - "lowercase-keys": "^1.0.1", - "mimic-response": "^1.0.1", - "p-cancelable": "^1.0.0", - "to-readable-stream": "^1.0.0", - "url-parse-lax": "^3.0.0" + "@types/node": "*" } }, - "json-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", - "integrity": "sha512-CuUqjv0FUZIdXkHPI8MezCnFCdaTAacej1TZYulLoAg1h/PhwkdXFN4V/gzY4g+fMBCOV2xF+rp7t2XD2ns/NQ==", - "dev": true - }, - "keyv": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", - "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", + "ethereumjs-util": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-6.2.1.tgz", + "integrity": "sha512-W2Ktez4L01Vexijrm5EB6w7dg4n/TgpoYU4avuT5T3Vmnw/eCRtiBrJfQYS/DCSvDIOLn2k57GcHdeBcgVxAqw==", "dev": true, "requires": { - "json-buffer": "3.0.0" + "@types/bn.js": "^4.11.3", + "bn.js": "^4.11.0", + "create-hash": "^1.1.2", + "elliptic": "^6.5.2", + "ethereum-cryptography": "^0.1.3", + "ethjs-util": "0.1.6", + "rlp": "^2.2.3" } - }, - "lowercase-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", - "dev": true - }, - "normalize-url": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz", - "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==", - "dev": true - }, - "p-cancelable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", - "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==", - "dev": true - }, - "responselike": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", - "integrity": "sha512-/Fpe5guzJk1gPqdJLJR5u7eG/gNY4nImjbRDaVWVMRhne55TCmj2i9Q+54PBRfatRC8v/rIiv9BN0pMd9OV5EQ==", + } + } + }, + "@noble/hashes": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.1.2.tgz", + "integrity": "sha512-KYRCASVTv6aeUi1tsF8/vpyR7zpfs3FUzy2Jqm+MU+LmUKhQ0y2FpfwqkCcxSg2ua4GALJd8k2R76WxwZGbQpA==", + "dev": true + }, + "@noble/secp256k1": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-1.6.3.tgz", + "integrity": "sha512-T04e4iTurVy7I8Sw4+c5OSN9/RkPlo1uKxAomtxQNLq8j1uPAqnsqG1bqvY3Jv7c13gyr6dui0zmh/I3+f/JaQ==", + "dev": true + }, + "@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } + }, + "@nomicfoundation/ethereumjs-block": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-block/-/ethereumjs-block-4.0.0.tgz", + "integrity": "sha512-bk8uP8VuexLgyIZAHExH1QEovqx0Lzhc9Ntm63nCRKLHXIZkobaFaeCVwTESV7YkPKUk7NiK11s8ryed4CS9yA==", + "dev": true, + "requires": { + "@nomicfoundation/ethereumjs-common": "^3.0.0", + "@nomicfoundation/ethereumjs-rlp": "^4.0.0", + "@nomicfoundation/ethereumjs-trie": "^5.0.0", + "@nomicfoundation/ethereumjs-tx": "^4.0.0", + "@nomicfoundation/ethereumjs-util": "^8.0.0", + "ethereum-cryptography": "0.1.3" + } + }, + "@nomicfoundation/ethereumjs-blockchain": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-blockchain/-/ethereumjs-blockchain-6.0.0.tgz", + "integrity": "sha512-pLFEoea6MWd81QQYSReLlLfH7N9v7lH66JC/NMPN848ySPPQA5renWnE7wPByfQFzNrPBuDDRFFULMDmj1C0xw==", + "dev": true, + "requires": { + "@nomicfoundation/ethereumjs-block": "^4.0.0", + "@nomicfoundation/ethereumjs-common": "^3.0.0", + "@nomicfoundation/ethereumjs-ethash": "^2.0.0", + "@nomicfoundation/ethereumjs-rlp": "^4.0.0", + "@nomicfoundation/ethereumjs-trie": "^5.0.0", + "@nomicfoundation/ethereumjs-util": "^8.0.0", + "abstract-level": "^1.0.3", + "debug": "^4.3.3", + "ethereum-cryptography": "0.1.3", + "level": "^8.0.0", + "lru-cache": "^5.1.1", + "memory-level": "^1.0.0" + } + }, + "@nomicfoundation/ethereumjs-common": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-common/-/ethereumjs-common-3.0.0.tgz", + "integrity": "sha512-WS7qSshQfxoZOpHG/XqlHEGRG1zmyjYrvmATvc4c62+gZXgre1ymYP8ZNgx/3FyZY0TWe9OjFlKOfLqmgOeYwA==", + "dev": true, + "requires": { + "@nomicfoundation/ethereumjs-util": "^8.0.0", + "crc-32": "^1.2.0" + } + }, + "@nomicfoundation/ethereumjs-ethash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-ethash/-/ethereumjs-ethash-2.0.0.tgz", + "integrity": "sha512-WpDvnRncfDUuXdsAXlI4lXbqUDOA+adYRQaEezIkxqDkc+LDyYDbd/xairmY98GnQzo1zIqsIL6GB5MoMSJDew==", + "dev": true, + "requires": { + "@nomicfoundation/ethereumjs-block": "^4.0.0", + "@nomicfoundation/ethereumjs-rlp": "^4.0.0", + "@nomicfoundation/ethereumjs-util": "^8.0.0", + "abstract-level": "^1.0.3", + "bigint-crypto-utils": "^3.0.23", + "ethereum-cryptography": "0.1.3" + } + }, + "@nomicfoundation/ethereumjs-evm": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-evm/-/ethereumjs-evm-1.0.0.tgz", + "integrity": "sha512-hVS6qRo3V1PLKCO210UfcEQHvlG7GqR8iFzp0yyjTg2TmJQizcChKgWo8KFsdMw6AyoLgLhHGHw4HdlP8a4i+Q==", + "dev": true, + "requires": { + "@nomicfoundation/ethereumjs-common": "^3.0.0", + "@nomicfoundation/ethereumjs-util": "^8.0.0", + "@types/async-eventemitter": "^0.2.1", + "async-eventemitter": "^0.2.4", + "debug": "^4.3.3", + "ethereum-cryptography": "0.1.3", + "mcl-wasm": "^0.7.1", + "rustbn.js": "~0.2.0" + } + }, + "@nomicfoundation/ethereumjs-rlp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-rlp/-/ethereumjs-rlp-4.0.0.tgz", + "integrity": "sha512-GaSOGk5QbUk4eBP5qFbpXoZoZUj/NrW7MRa0tKY4Ew4c2HAS0GXArEMAamtFrkazp0BO4K5p2ZCG3b2FmbShmw==", + "dev": true + }, + "@nomicfoundation/ethereumjs-statemanager": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-statemanager/-/ethereumjs-statemanager-1.0.0.tgz", + "integrity": "sha512-jCtqFjcd2QejtuAMjQzbil/4NHf5aAWxUc+CvS0JclQpl+7M0bxMofR2AJdtz+P3u0ke2euhYREDiE7iSO31vQ==", + "dev": true, + "requires": { + "@nomicfoundation/ethereumjs-common": "^3.0.0", + "@nomicfoundation/ethereumjs-rlp": "^4.0.0", + "@nomicfoundation/ethereumjs-trie": "^5.0.0", + "@nomicfoundation/ethereumjs-util": "^8.0.0", + "debug": "^4.3.3", + "ethereum-cryptography": "0.1.3", + "functional-red-black-tree": "^1.0.1" + } + }, + "@nomicfoundation/ethereumjs-trie": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-trie/-/ethereumjs-trie-5.0.0.tgz", + "integrity": "sha512-LIj5XdE+s+t6WSuq/ttegJzZ1vliwg6wlb+Y9f4RlBpuK35B9K02bO7xU+E6Rgg9RGptkWd6TVLdedTI4eNc2A==", + "dev": true, + "requires": { + "@nomicfoundation/ethereumjs-rlp": "^4.0.0", + "@nomicfoundation/ethereumjs-util": "^8.0.0", + "ethereum-cryptography": "0.1.3", + "readable-stream": "^3.6.0" + } + }, + "@nomicfoundation/ethereumjs-tx": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-tx/-/ethereumjs-tx-4.0.0.tgz", + "integrity": "sha512-Gg3Lir2lNUck43Kp/3x6TfBNwcWC9Z1wYue9Nz3v4xjdcv6oDW9QSMJxqsKw9QEGoBBZ+gqwpW7+F05/rs/g1w==", + "dev": true, + "requires": { + "@nomicfoundation/ethereumjs-common": "^3.0.0", + "@nomicfoundation/ethereumjs-rlp": "^4.0.0", + "@nomicfoundation/ethereumjs-util": "^8.0.0", + "ethereum-cryptography": "0.1.3" + } + }, + "@nomicfoundation/ethereumjs-util": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-util/-/ethereumjs-util-8.0.0.tgz", + "integrity": "sha512-2emi0NJ/HmTG+CGY58fa+DQuAoroFeSH9gKu9O6JnwTtlzJtgfTixuoOqLEgyyzZVvwfIpRueuePb8TonL1y+A==", + "dev": true, + "requires": { + "@nomicfoundation/ethereumjs-rlp": "^4.0.0-beta.2", + "ethereum-cryptography": "0.1.3" + } + }, + "@nomicfoundation/ethereumjs-vm": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-vm/-/ethereumjs-vm-6.0.0.tgz", + "integrity": "sha512-JMPxvPQ3fzD063Sg3Tp+UdwUkVxMoo1uML6KSzFhMH3hoQi/LMuXBoEHAoW83/vyNS9BxEe6jm6LmT5xdeEJ6w==", + "dev": true, + "requires": { + "@nomicfoundation/ethereumjs-block": "^4.0.0", + "@nomicfoundation/ethereumjs-blockchain": "^6.0.0", + "@nomicfoundation/ethereumjs-common": "^3.0.0", + "@nomicfoundation/ethereumjs-evm": "^1.0.0", + "@nomicfoundation/ethereumjs-rlp": "^4.0.0", + "@nomicfoundation/ethereumjs-statemanager": "^1.0.0", + "@nomicfoundation/ethereumjs-trie": "^5.0.0", + "@nomicfoundation/ethereumjs-tx": "^4.0.0", + "@nomicfoundation/ethereumjs-util": "^8.0.0", + "@types/async-eventemitter": "^0.2.1", + "async-eventemitter": "^0.2.4", + "debug": "^4.3.3", + "ethereum-cryptography": "0.1.3", + "functional-red-black-tree": "^1.0.1", + "mcl-wasm": "^0.7.1", + "rustbn.js": "~0.2.0" + } + }, + "@nomicfoundation/hardhat-network-helpers": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-network-helpers/-/hardhat-network-helpers-1.0.7.tgz", + "integrity": "sha512-X+3mNvn8B7BY5hpIaLO+TrfzWq12bpux+ajGGdmdcfC78NXmYmOZkAtiz1QZx1YIZGMS1LaXzPXyBExxKFpCaw==", + "dev": true, + "requires": { + "ethereumjs-util": "^7.1.4" + } + }, + "@nomicfoundation/solidity-analyzer": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer/-/solidity-analyzer-0.1.0.tgz", + "integrity": "sha512-xGWAiVCGOycvGiP/qrlf9f9eOn7fpNbyJygcB0P21a1MDuVPlKt0Srp7rvtBEutYQ48ouYnRXm33zlRnlTOPHg==", + "dev": true, + "requires": { + "@nomicfoundation/solidity-analyzer-darwin-arm64": "0.1.0", + "@nomicfoundation/solidity-analyzer-darwin-x64": "0.1.0", + "@nomicfoundation/solidity-analyzer-freebsd-x64": "0.1.0", + "@nomicfoundation/solidity-analyzer-linux-arm64-gnu": "0.1.0", + "@nomicfoundation/solidity-analyzer-linux-arm64-musl": "0.1.0", + "@nomicfoundation/solidity-analyzer-linux-x64-gnu": "0.1.0", + "@nomicfoundation/solidity-analyzer-linux-x64-musl": "0.1.0", + "@nomicfoundation/solidity-analyzer-win32-arm64-msvc": "0.1.0", + "@nomicfoundation/solidity-analyzer-win32-ia32-msvc": "0.1.0", + "@nomicfoundation/solidity-analyzer-win32-x64-msvc": "0.1.0" + } + }, + "@nomicfoundation/solidity-analyzer-darwin-arm64": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-darwin-arm64/-/solidity-analyzer-darwin-arm64-0.1.0.tgz", + "integrity": "sha512-vEF3yKuuzfMHsZecHQcnkUrqm8mnTWfJeEVFHpg+cO+le96xQA4lAJYdUan8pXZohQxv1fSReQsn4QGNuBNuCw==", + "dev": true, + "optional": true + }, + "@nomicfoundation/solidity-analyzer-darwin-x64": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-darwin-x64/-/solidity-analyzer-darwin-x64-0.1.0.tgz", + "integrity": "sha512-dlHeIg0pTL4dB1l9JDwbi/JG6dHQaU1xpDK+ugYO8eJ1kxx9Dh2isEUtA4d02cQAl22cjOHTvifAk96A+ItEHA==", + "dev": true, + "optional": true + }, + "@nomicfoundation/solidity-analyzer-freebsd-x64": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-freebsd-x64/-/solidity-analyzer-freebsd-x64-0.1.0.tgz", + "integrity": "sha512-WFCZYMv86WowDA4GiJKnebMQRt3kCcFqHeIomW6NMyqiKqhK1kIZCxSLDYsxqlx396kKLPN1713Q1S8tu68GKg==", + "dev": true, + "optional": true + }, + "@nomicfoundation/solidity-analyzer-linux-arm64-gnu": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-arm64-gnu/-/solidity-analyzer-linux-arm64-gnu-0.1.0.tgz", + "integrity": "sha512-DTw6MNQWWlCgc71Pq7CEhEqkb7fZnS7oly13pujs4cMH1sR0JzNk90Mp1zpSCsCs4oKan2ClhMlLKtNat/XRKQ==", + "dev": true, + "optional": true + }, + "@nomicfoundation/solidity-analyzer-linux-arm64-musl": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-arm64-musl/-/solidity-analyzer-linux-arm64-musl-0.1.0.tgz", + "integrity": "sha512-wUpUnR/3GV5Da88MhrxXh/lhb9kxh9V3Jya2NpBEhKDIRCDmtXMSqPMXHZmOR9DfCwCvG6vLFPr/+YrPCnUN0w==", + "dev": true, + "optional": true + }, + "@nomicfoundation/solidity-analyzer-linux-x64-gnu": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-x64-gnu/-/solidity-analyzer-linux-x64-gnu-0.1.0.tgz", + "integrity": "sha512-lR0AxK1x/MeKQ/3Pt923kPvwigmGX3OxeU5qNtQ9pj9iucgk4PzhbS3ruUeSpYhUxG50jN4RkIGwUMoev5lguw==", + "dev": true, + "optional": true + }, + "@nomicfoundation/solidity-analyzer-linux-x64-musl": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-x64-musl/-/solidity-analyzer-linux-x64-musl-0.1.0.tgz", + "integrity": "sha512-A1he/8gy/JeBD3FKvmI6WUJrGrI5uWJNr5Xb9WdV+DK0F8msuOqpEByLlnTdLkXMwW7nSl3awvLezOs9xBHJEg==", + "dev": true, + "optional": true + }, + "@nomicfoundation/solidity-analyzer-win32-arm64-msvc": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-win32-arm64-msvc/-/solidity-analyzer-win32-arm64-msvc-0.1.0.tgz", + "integrity": "sha512-7x5SXZ9R9H4SluJZZP8XPN+ju7Mx+XeUMWZw7ZAqkdhP5mK19I4vz3x0zIWygmfE8RT7uQ5xMap0/9NPsO+ykw==", + "dev": true, + "optional": true + }, + "@nomicfoundation/solidity-analyzer-win32-ia32-msvc": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-win32-ia32-msvc/-/solidity-analyzer-win32-ia32-msvc-0.1.0.tgz", + "integrity": "sha512-m7w3xf+hnE774YRXu+2mGV7RiF3QJtUoiYU61FascCkQhX3QMQavh7saH/vzb2jN5D24nT/jwvaHYX/MAM9zUw==", + "dev": true, + "optional": true + }, + "@nomicfoundation/solidity-analyzer-win32-x64-msvc": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-win32-x64-msvc/-/solidity-analyzer-win32-x64-msvc-0.1.0.tgz", + "integrity": "sha512-xCuybjY0sLJQnJhupiFAXaek2EqF0AP0eBjgzaalPXSNvCEN6ZYHvUzdA50ENDVeSYFXcUsYf3+FsD3XKaeptA==", + "dev": true, + "optional": true + }, + "@nomiclabs/hardhat-truffle5": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@nomiclabs/hardhat-truffle5/-/hardhat-truffle5-2.0.7.tgz", + "integrity": "sha512-Pw8451IUZp1bTp0QqCHCYfCHs66sCnyxPcaorapu9mfOV9xnZsVaFdtutnhNEiXdiZwbed7LFKpRsde4BjFwig==", + "dev": true, + "requires": { + "@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/hardhat-web3": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@nomiclabs/hardhat-web3/-/hardhat-web3-2.0.0.tgz", + "integrity": "sha512-zt4xN+D+fKl3wW2YlTX3k9APR3XZgPkxJYf36AcliJn3oujnKEVRZaHu0PhgLjO+gR+F/kiYayo9fgd2L8970Q==", + "dev": true, + "requires": { + "@types/bignumber.js": "^5.0.0" + } + }, + "@nomiclabs/truffle-contract": { + "version": "4.5.10", + "resolved": "https://registry.npmjs.org/@nomiclabs/truffle-contract/-/truffle-contract-4.5.10.tgz", + "integrity": "sha512-nF/6InFV+0hUvutyFgsdOMCoYlr//2fJbRER4itxYtQtc4/O1biTwZIKRu+5l2J5Sq6LU2WX7vZHtDgQdhWxIQ==", + "dev": true, + "requires": { + "@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/contract-loader": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@openzeppelin/contract-loader/-/contract-loader-0.6.3.tgz", + "integrity": "sha512-cOFIjBjwbGgZhDZsitNgJl0Ye1rd5yu/Yx5LMgeq3u0ZYzldm4uObzHDFq4gjDdoypvyORjjJa3BlFA7eAnVIg==", + "dev": true, + "requires": { + "find-up": "^4.1.0", + "fs-extra": "^8.1.0" + }, + "dependencies": { + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", "dev": true, "requires": { - "lowercase-keys": "^1.0.0" + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" } - }, - "uuid": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", + } + } + }, + "@openzeppelin/docs-utils": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@openzeppelin/docs-utils/-/docs-utils-0.1.3.tgz", + "integrity": "sha512-O/iJ4jEi5ryNc/T74G9gbnFwQ8QaQ2bpAVoYXLPknZJyK52GEAvxC12UMP33KodTNV3rMzeeQrSBIdI8skjDJg==", + "dev": true, + "requires": { + "@frangio/servbot": "^0.2.5", + "chalk": "^3.0.0", + "chokidar": "^3.5.3", + "env-paths": "^2.2.0", + "find-up": "^4.1.0", + "is-port-reachable": "^3.0.0", + "js-yaml": "^3.13.1", + "lodash.startcase": "^4.4.0", + "minimist": "^1.2.0" + } + }, + "@openzeppelin/test-helpers": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/@openzeppelin/test-helpers/-/test-helpers-0.5.16.tgz", + "integrity": "sha512-T1EvspSfH1qQO/sgGlskLfYVBbqzJR23SZzYl/6B2JnT4EhThcI85UpvDk0BkLWKaDScQTabGHt4GzHW+3SfZg==", + "dev": true, + "requires": { + "@openzeppelin/contract-loader": "^0.6.2", + "@truffle/contract": "^4.0.35", + "ansi-colors": "^3.2.3", + "chai": "^4.2.0", + "chai-bn": "^0.2.1", + "ethjs-abi": "^0.2.1", + "lodash.flatten": "^4.4.0", + "semver": "^5.6.0", + "web3": "^1.2.5", + "web3-utils": "^1.2.5" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "@scure/base": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.1.tgz", + "integrity": "sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==", + "dev": true + }, + "@scure/bip32": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.1.0.tgz", + "integrity": "sha512-ftTW3kKX54YXLCxH6BB7oEEoJfoE2pIgw7MINKAs5PsS6nqKPuKk1haTF/EuHmYqG330t5GSrdmtRuHaY1a62Q==", + "dev": true, + "requires": { + "@noble/hashes": "~1.1.1", + "@noble/secp256k1": "~1.6.0", + "@scure/base": "~1.1.0" + } + }, + "@scure/bip39": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.1.0.tgz", + "integrity": "sha512-pwrPOS16VeTKg98dYXQyIjJEcWfz7/1YJIwxUEPFfQPtc86Ym/1sVgQ2RLoD43AazMk2l/unK4ITySSpW2+82w==", + "dev": true, + "requires": { + "@noble/hashes": "~1.1.1", + "@scure/base": "~1.1.0" + } + }, + "@sentry/core": { + "version": "5.30.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-5.30.0.tgz", + "integrity": "sha512-TmfrII8w1PQZSZgPpUESqjB+jC6MvZJZdLtE/0hZ+SrnKhW3x5WlYLvTXZpcWePYBku7rl2wn1RZu6uT0qCTeg==", + "dev": true, + "requires": { + "@sentry/hub": "5.30.0", + "@sentry/minimal": "5.30.0", + "@sentry/types": "5.30.0", + "@sentry/utils": "5.30.0", + "tslib": "^1.9.3" + } + }, + "@sentry/hub": { + "version": "5.30.0", + "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-5.30.0.tgz", + "integrity": "sha512-2tYrGnzb1gKz2EkMDQcfLrDTvmGcQPuWxLnJKXJvYTQDGLlEvi2tWz1VIHjunmOvJrB5aIQLhm+dcMRwFZDCqQ==", + "dev": true, + "requires": { + "@sentry/types": "5.30.0", + "@sentry/utils": "5.30.0", + "tslib": "^1.9.3" + } + }, + "@sentry/minimal": { + "version": "5.30.0", + "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-5.30.0.tgz", + "integrity": "sha512-BwWb/owZKtkDX+Sc4zCSTNcvZUq7YcH3uAVlmh/gtR9rmUvbzAA3ewLuB3myi4wWRAMEtny6+J/FN/x+2wn9Xw==", + "dev": true, + "requires": { + "@sentry/hub": "5.30.0", + "@sentry/types": "5.30.0", + "tslib": "^1.9.3" + } + }, + "@sentry/node": { + "version": "5.30.0", + "resolved": "https://registry.npmjs.org/@sentry/node/-/node-5.30.0.tgz", + "integrity": "sha512-Br5oyVBF0fZo6ZS9bxbJZG4ApAjRqAnqFFurMVJJdunNb80brh7a5Qva2kjhm+U6r9NJAB5OmDyPkA1Qnt+QVg==", + "dev": true, + "requires": { + "@sentry/core": "5.30.0", + "@sentry/hub": "5.30.0", + "@sentry/tracing": "5.30.0", + "@sentry/types": "5.30.0", + "@sentry/utils": "5.30.0", + "cookie": "^0.4.1", + "https-proxy-agent": "^5.0.0", + "lru_map": "^0.3.3", + "tslib": "^1.9.3" + } + }, + "@sentry/tracing": { + "version": "5.30.0", + "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-5.30.0.tgz", + "integrity": "sha512-dUFowCr0AIMwiLD7Fs314Mdzcug+gBVo/+NCMyDw8tFxJkwWAKl7Qa2OZxLQ0ZHjakcj1hNKfCQJ9rhyfOl4Aw==", + "dev": true, + "requires": { + "@sentry/hub": "5.30.0", + "@sentry/minimal": "5.30.0", + "@sentry/types": "5.30.0", + "@sentry/utils": "5.30.0", + "tslib": "^1.9.3" + } + }, + "@sentry/types": { + "version": "5.30.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-5.30.0.tgz", + "integrity": "sha512-R8xOqlSTZ+htqrfteCWU5Nk0CDN5ApUTvrlvBuiH1DyP6czDZ4ktbZB0hAgBlVcK0U+qpD3ag3Tqqpa5Q67rPw==", + "dev": true + }, + "@sentry/utils": { + "version": "5.30.0", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-5.30.0.tgz", + "integrity": "sha512-zaYmoH0NWWtvnJjC9/CBseXMtKHm/tm40sz3YfJRxeQjyzRqNQPgivpd9R/oDJCYj999mzdW382p/qi2ypjLww==", + "dev": true, + "requires": { + "@sentry/types": "5.30.0", + "tslib": "^1.9.3" + } + }, + "@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "dev": true + }, + "@solidity-parser/parser": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.14.5.tgz", + "integrity": "sha512-6dKnHZn7fg/iQATVEzqyUOyEidbn05q7YA2mQ9hC0MMXhhV3/JrsxmFSYZAcr7j1yUP700LLhTruvJ3MiQmjJg==", + "dev": true, + "requires": { + "antlr4ts": "^0.5.0-alpha.4" + } + }, + "@szmarczak/http-timer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", + "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", + "dev": true, + "requires": { + "defer-to-connect": "^2.0.1" + } + }, + "@truffle/abi-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@truffle/abi-utils/-/abi-utils-0.3.6.tgz", + "integrity": "sha512-61aTH2QmwVA1INaPMufRHTsS6jsEhS+GCkuCDdvBDmwctSnCKGDOr185BGt65QrpMRxYmIoH6WFBSNMYxW9GRw==", + "dev": true, + "requires": { + "change-case": "3.0.2", + "fast-check": "3.1.1", + "web3-utils": "1.8.1" + } + }, + "@truffle/blockchain-utils": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@truffle/blockchain-utils/-/blockchain-utils-0.1.6.tgz", + "integrity": "sha512-SldoNRIFSm3+HMBnSc2jFsu5TWDkCN4X6vL3wrd0t6DIeF7nD6EoPPjxwbFSoqCnkkRxMuZeL6sUx7UMJS/wSA==", + "dev": true + }, + "@truffle/codec": { + "version": "0.14.11", + "resolved": "https://registry.npmjs.org/@truffle/codec/-/codec-0.14.11.tgz", + "integrity": "sha512-NgfMNYemgMXqoEcJA5ZsEhxChCwq33rSxtNxlececEH/1Nf0r+ryfrfmLlyPmv8f3jorVf1GWa0zI0AedGCGYQ==", + "dev": true, + "requires": { + "@truffle/abi-utils": "^0.3.6", + "@truffle/compile-common": "^0.9.1", + "big.js": "^6.0.3", + "bn.js": "^5.1.3", + "cbor": "^5.2.0", + "debug": "^4.3.1", + "lodash": "^4.17.21", + "semver": "7.3.7", + "utf8": "^3.0.0", + "web3-utils": "1.8.1" + }, + "dependencies": { + "bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", "dev": true }, - "web3": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3/-/web3-1.7.4.tgz", - "integrity": "sha512-iFGK5jO32vnXM/ASaJBaI0+gVR6uHozvYdxkdhaeOCD6HIQ4iIXadbO2atVpE9oc/H8l2MovJ4LtPhG7lIBN8A==", - "dev": true, - "requires": { - "web3-bzz": "1.7.4", - "web3-core": "1.7.4", - "web3-eth": "1.7.4", - "web3-eth-personal": "1.7.4", - "web3-net": "1.7.4", - "web3-shh": "1.7.4", - "web3-utils": "1.7.4" - } - }, - "web3-bzz": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-bzz/-/web3-bzz-1.7.4.tgz", - "integrity": "sha512-w9zRhyEqTK/yi0LGRHjZMcPCfP24LBjYXI/9YxFw9VqsIZ9/G0CRCnUt12lUx0A56LRAMpF7iQ8eA73aBcO29Q==", - "dev": true, - "requires": { - "@types/node": "^12.12.6", - "got": "9.6.0", - "swarm-js": "^0.1.40" - } - }, - "web3-core": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-core/-/web3-core-1.7.4.tgz", - "integrity": "sha512-L0DCPlIh9bgIED37tYbe7bsWrddoXYc897ANGvTJ6MFkSNGiMwDkTLWSgYd9Mf8qu8b4iuPqXZHMwIo4atoh7Q==", - "dev": true, - "requires": { - "@types/bn.js": "^5.1.0", - "@types/node": "^12.12.6", - "bignumber.js": "^9.0.0", - "web3-core-helpers": "1.7.4", - "web3-core-method": "1.7.4", - "web3-core-requestmanager": "1.7.4", - "web3-utils": "1.7.4" - } - }, - "web3-core-helpers": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-core-helpers/-/web3-core-helpers-1.7.4.tgz", - "integrity": "sha512-F8PH11qIkE/LpK4/h1fF/lGYgt4B6doeMi8rukeV/s4ivseZHHslv1L6aaijLX/g/j4PsFmR42byynBI/MIzFg==", - "dev": true, - "requires": { - "web3-eth-iban": "1.7.4", - "web3-utils": "1.7.4" - } - }, - "web3-core-method": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-core-method/-/web3-core-method-1.7.4.tgz", - "integrity": "sha512-56K7pq+8lZRkxJyzf5MHQPI9/VL3IJLoy4L/+q8HRdZJ3CkB1DkXYaXGU2PeylG1GosGiSzgIfu1ljqS7CP9xQ==", - "dev": true, - "requires": { - "@ethersproject/transactions": "^5.6.2", - "web3-core-helpers": "1.7.4", - "web3-core-promievent": "1.7.4", - "web3-core-subscriptions": "1.7.4", - "web3-utils": "1.7.4" - } - }, - "web3-core-promievent": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-core-promievent/-/web3-core-promievent-1.7.4.tgz", - "integrity": "sha512-o4uxwXKDldN7ER7VUvDfWsqTx9nQSP1aDssi1XYXeYC2xJbVo0n+z6ryKtmcoWoRdRj7uSpVzal3nEmlr480mA==", - "dev": true, - "requires": { - "eventemitter3": "4.0.4" - } - }, - "web3-core-requestmanager": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-core-requestmanager/-/web3-core-requestmanager-1.7.4.tgz", - "integrity": "sha512-IuXdAm65BQtPL4aI6LZJJOrKAs0SM5IK2Cqo2/lMNvVMT9Kssq6qOk68Uf7EBDH0rPuINi+ReLP+uH+0g3AnPA==", - "dev": true, - "requires": { - "util": "^0.12.0", - "web3-core-helpers": "1.7.4", - "web3-providers-http": "1.7.4", - "web3-providers-ipc": "1.7.4", - "web3-providers-ws": "1.7.4" - } - }, - "web3-core-subscriptions": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-core-subscriptions/-/web3-core-subscriptions-1.7.4.tgz", - "integrity": "sha512-VJvKWaXRyxk2nFWumOR94ut9xvjzMrRtS38c4qj8WBIRSsugrZr5lqUwgndtj0qx4F+50JhnU++QEqUEAtKm3g==", - "dev": true, - "requires": { - "eventemitter3": "4.0.4", - "web3-core-helpers": "1.7.4" - } - }, - "web3-eth": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-eth/-/web3-eth-1.7.4.tgz", - "integrity": "sha512-JG0tTMv0Ijj039emXNHi07jLb0OiWSA9O24MRSk5vToTQyDNXihdF2oyq85LfHuF690lXZaAXrjhtLNlYqb7Ug==", - "dev": true, - "requires": { - "web3-core": "1.7.4", - "web3-core-helpers": "1.7.4", - "web3-core-method": "1.7.4", - "web3-core-subscriptions": "1.7.4", - "web3-eth-abi": "1.7.4", - "web3-eth-accounts": "1.7.4", - "web3-eth-contract": "1.7.4", - "web3-eth-ens": "1.7.4", - "web3-eth-iban": "1.7.4", - "web3-eth-personal": "1.7.4", - "web3-net": "1.7.4", - "web3-utils": "1.7.4" - } - }, - "web3-eth-abi": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-eth-abi/-/web3-eth-abi-1.7.4.tgz", - "integrity": "sha512-eMZr8zgTbqyL9MCTCAvb67RbVyN5ZX7DvA0jbLOqRWCiw+KlJKTGnymKO6jPE8n5yjk4w01e165Qb11hTDwHgg==", - "dev": true, - "requires": { - "@ethersproject/abi": "^5.6.3", - "web3-utils": "1.7.4" - } - }, - "web3-eth-accounts": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-eth-accounts/-/web3-eth-accounts-1.7.4.tgz", - "integrity": "sha512-Y9vYLRKP7VU7Cgq6wG1jFaG2k3/eIuiTKAG8RAuQnb6Cd9k5BRqTm5uPIiSo0AP/u11jDomZ8j7+WEgkU9+Btw==", - "dev": true, - "requires": { - "@ethereumjs/common": "^2.5.0", - "@ethereumjs/tx": "^3.3.2", - "crypto-browserify": "3.12.0", - "eth-lib": "0.2.8", - "ethereumjs-util": "^7.0.10", - "scrypt-js": "^3.0.1", - "uuid": "3.3.2", - "web3-core": "1.7.4", - "web3-core-helpers": "1.7.4", - "web3-core-method": "1.7.4", - "web3-utils": "1.7.4" - } - }, - "web3-eth-contract": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-eth-contract/-/web3-eth-contract-1.7.4.tgz", - "integrity": "sha512-ZgSZMDVI1pE9uMQpK0T0HDT2oewHcfTCv0osEqf5qyn5KrcQDg1GT96/+S0dfqZ4HKj4lzS5O0rFyQiLPQ8LzQ==", + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "dev": true, "requires": { - "@types/bn.js": "^5.1.0", - "web3-core": "1.7.4", - "web3-core-helpers": "1.7.4", - "web3-core-method": "1.7.4", - "web3-core-promievent": "1.7.4", - "web3-core-subscriptions": "1.7.4", - "web3-eth-abi": "1.7.4", - "web3-utils": "1.7.4" + "yallist": "^4.0.0" } }, - "web3-eth-ens": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-eth-ens/-/web3-eth-ens-1.7.4.tgz", - "integrity": "sha512-Gw5CVU1+bFXP5RVXTCqJOmHn71X2ghNk9VcEH+9PchLr0PrKbHTA3hySpsPco1WJAyK4t8SNQVlNr3+bJ6/WZA==", + "semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", "dev": true, "requires": { - "content-hash": "^2.5.2", - "eth-ens-namehash": "2.0.8", - "web3-core": "1.7.4", - "web3-core-helpers": "1.7.4", - "web3-core-promievent": "1.7.4", - "web3-eth-abi": "1.7.4", - "web3-eth-contract": "1.7.4", - "web3-utils": "1.7.4" + "lru-cache": "^6.0.0" } }, - "web3-eth-iban": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-eth-iban/-/web3-eth-iban-1.7.4.tgz", - "integrity": "sha512-XyrsgWlZQMv5gRcjXMsNvAoCRvV5wN7YCfFV5+tHUCqN8g9T/o4XUS20vDWD0k4HNiAcWGFqT1nrls02MGZ08w==", + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "@truffle/compile-common": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@truffle/compile-common/-/compile-common-0.9.1.tgz", + "integrity": "sha512-mhdkX6ExZImHSBO3jGm6aAn8NpVtMTdjq50jRXY/O59/ZNC0J9WpRapxrAKUVNc+XydMdBlfeEpXoqTJg7cbXw==", + "dev": true, + "requires": { + "@truffle/error": "^0.1.1", + "colors": "1.4.0" + } + }, + "@truffle/contract": { + "version": "4.6.10", + "resolved": "https://registry.npmjs.org/@truffle/contract/-/contract-4.6.10.tgz", + "integrity": "sha512-69IZSXeQKRP3EutILqe+vLY5A5gUpeXUiZhm/Fy/qHHkP238vMjtOkTZGkY6bonYqmgk+vDY7KSYSYKzDNPdCA==", + "dev": true, + "requires": { + "@ensdomains/ensjs": "^2.1.0", + "@truffle/blockchain-utils": "^0.1.6", + "@truffle/contract-schema": "^3.4.11", + "@truffle/debug-utils": "^6.0.42", + "@truffle/error": "^0.1.1", + "@truffle/interface-adapter": "^0.5.26", + "bignumber.js": "^7.2.1", + "debug": "^4.3.1", + "ethers": "^4.0.32", + "web3": "1.8.1", + "web3-core-helpers": "1.8.1", + "web3-core-promievent": "1.8.1", + "web3-eth-abi": "1.8.1", + "web3-utils": "1.8.1" + } + }, + "@truffle/contract-schema": { + "version": "3.4.11", + "resolved": "https://registry.npmjs.org/@truffle/contract-schema/-/contract-schema-3.4.11.tgz", + "integrity": "sha512-wReyVZUPyU9Zy5PSCugBLG1nnruBmRAJ/gmoirQiJ9N2n+s1iGBTY49tkDqFMz3XUUE0kplfdb9YKZJlLkTWzQ==", + "dev": true, + "requires": { + "ajv": "^6.10.0", + "debug": "^4.3.1" + } + }, + "@truffle/debug-utils": { + "version": "6.0.42", + "resolved": "https://registry.npmjs.org/@truffle/debug-utils/-/debug-utils-6.0.42.tgz", + "integrity": "sha512-9v70tj+My0Z2UZJ9OsuUlfo4Dt2AJqAQa/YWtGe28H8zsi+o9Dca0RsKWecuprdllgzrEs7ad8QUtSINhwjIlg==", + "dev": true, + "requires": { + "@truffle/codec": "^0.14.11", + "@trufflesuite/chromafi": "^3.0.0", + "bn.js": "^5.1.3", + "chalk": "^2.4.2", + "debug": "^4.3.1", + "highlightjs-solidity": "^2.0.5" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "bn.js": "^5.2.1", - "web3-utils": "1.7.4" + "color-convert": "^1.9.0" } }, - "web3-eth-personal": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-eth-personal/-/web3-eth-personal-1.7.4.tgz", - "integrity": "sha512-O10C1Hln5wvLQsDhlhmV58RhXo+GPZ5+W76frSsyIrkJWLtYQTCr5WxHtRC9sMD1idXLqODKKgI2DL+7xeZ0/g==", - "dev": true, - "requires": { - "@types/node": "^12.12.6", - "web3-core": "1.7.4", - "web3-core-helpers": "1.7.4", - "web3-core-method": "1.7.4", - "web3-net": "1.7.4", - "web3-utils": "1.7.4" - } + "bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", + "dev": true }, - "web3-net": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-net/-/web3-net-1.7.4.tgz", - "integrity": "sha512-d2Gj+DIARHvwIdmxFQ4PwAAXZVxYCR2lET0cxz4KXbE5Og3DNjJi+MoPkX+WqoUXqimu/EOd4Cd+7gefqVAFDg==", + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, "requires": { - "web3-core": "1.7.4", - "web3-core-method": "1.7.4", - "web3-utils": "1.7.4" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" } }, - "web3-providers-http": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-providers-http/-/web3-providers-http-1.7.4.tgz", - "integrity": "sha512-AU+/S+49rcogUER99TlhW+UBMk0N2DxvN54CJ2pK7alc2TQ7+cprNPLHJu4KREe8ndV0fT6JtWUfOMyTvl+FRA==", + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "dev": true, "requires": { - "web3-core-helpers": "1.7.4", - "xhr2-cookies": "1.1.0" + "color-name": "1.1.3" } }, - "web3-providers-ipc": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-providers-ipc/-/web3-providers-ipc-1.7.4.tgz", - "integrity": "sha512-jhArOZ235dZy8fS8090t60nTxbd1ap92ibQw5xIrAQ9m7LcZKNfmLAQUVsD+3dTFvadRMi6z1vCO7zRi84gWHw==", - "dev": true, - "requires": { - "oboe": "2.1.5", - "web3-core-helpers": "1.7.4" - } + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true }, - "web3-providers-ws": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-providers-ws/-/web3-providers-ws-1.7.4.tgz", - "integrity": "sha512-g72X77nrcHMFU8hRzQJzfgi/072n8dHwRCoTw+WQrGp+XCQ71fsk2qIu3Tp+nlp5BPn8bRudQbPblVm2uT4myQ==", - "dev": true, - "requires": { - "eventemitter3": "4.0.4", - "web3-core-helpers": "1.7.4", - "websocket": "^1.0.32" - } + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true }, - "web3-shh": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-shh/-/web3-shh-1.7.4.tgz", - "integrity": "sha512-mlSZxSYcMkuMCxqhTYnZkUdahZ11h+bBv/8TlkXp/IHpEe4/Gg+KAbmfudakq3EzG/04z70XQmPgWcUPrsEJ+A==", - "dev": true, - "requires": { - "web3-core": "1.7.4", - "web3-core-method": "1.7.4", - "web3-core-subscriptions": "1.7.4", - "web3-net": "1.7.4" - } + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true }, - "web3-utils": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.7.4.tgz", - "integrity": "sha512-acBdm6Evd0TEZRnChM/MCvGsMwYKmSh7OaUfNf5OKG0CIeGWD/6gqLOWIwmwSnre/2WrA1nKGId5uW2e5EfluA==", + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { - "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" + "has-flag": "^3.0.0" } } } }, + "@truffle/error": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@truffle/error/-/error-0.1.1.tgz", + "integrity": "sha512-sE7c9IHIGdbK4YayH4BC8i8qMjoAOeg6nUXUDZZp8wlU21/EMpaG+CLx+KqcIPyR+GSWIW3Dm0PXkr2nlggFDA==", + "dev": true + }, + "@truffle/interface-adapter": { + "version": "0.5.26", + "resolved": "https://registry.npmjs.org/@truffle/interface-adapter/-/interface-adapter-0.5.26.tgz", + "integrity": "sha512-fBhoqtT+CT4XKXcOijvw0RIMgyUi3FJg+n5i5PyGBsoRzqbLZd9cZq+oMNjOZPdf3GH68hsOFOaQO5tZH7oZow==", + "dev": true, + "requires": { + "bn.js": "^5.1.3", + "ethers": "^4.0.32", + "web3": "1.8.1" + }, + "dependencies": { + "bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", + "dev": true + } + } + }, "@trufflesuite/chromafi": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@trufflesuite/chromafi/-/chromafi-3.0.0.tgz", @@ -2460,21 +16453,21 @@ } }, "@types/cacheable-request": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.2.tgz", - "integrity": "sha512-B3xVo+dlKM6nnKTcmm5ZtY/OL8bOAOd2Olee9M1zft65ox50OzjEHW91sDiU9j6cvW8Ejg1/Qkf4xd2kugApUA==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", + "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", "dev": true, "requires": { "@types/http-cache-semantics": "*", - "@types/keyv": "*", + "@types/keyv": "^3.1.4", "@types/node": "*", - "@types/responselike": "*" + "@types/responselike": "^1.0.0" } }, "@types/chai": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.3.tgz", - "integrity": "sha512-hC7OMnszpxhZPduX+m+nrx+uFoLkWOMiR4oa/AZF3MuSETYTZmFfJAHqZEM8MVlvfG7BEUcgvtwoCTxBp6hm3g==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.4.tgz", + "integrity": "sha512-KnRanxnpfpjUTqTCXslZSEdLfXExwgNxYPdiO2WGUj8+HDjFi8R3k5RVKPeSCzLjCcshCAtVO2QBbVuAV4kTnw==", "dev": true }, "@types/concat-stream": { @@ -2539,9 +16532,9 @@ "dev": true }, "@types/node": { - "version": "18.7.17", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.17.tgz", - "integrity": "sha512-0UyfUnt02zIuqp7yC8RYtDkp/vo8bFaQ13KkSEvUAohPOAlnVNbj5Fi3fgPSuwzakS+EvvnnZ4x9y7i6ASaSPQ==", + "version": "18.11.17", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.17.tgz", + "integrity": "sha512-HJSUJmni4BeDHhfzn6nF0sVmd1SMezP7/4F0Lq+aXzmp2xm9O7WXrUtHW/CHlYVtZUbByEvWidHqRtcJXGF2Ng==", "dev": true }, "@types/pbkdf2": { @@ -2577,12 +16570,6 @@ "@types/node": "*" } }, - "@ungap/promise-all-settled": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", - "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", - "dev": true - }, "abbrev": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", @@ -2599,9 +16586,9 @@ } }, "abortcontroller-polyfill": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/abortcontroller-polyfill/-/abortcontroller-polyfill-1.7.3.tgz", - "integrity": "sha512-zetDJxd89y3X99Kvo4qFx8GKlt6GsvN3UcRZHwU6iFA/0KiOmhkTVhe8oRoTBiTVPZu09x3vCra47+w8Yz1+2Q==", + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/abortcontroller-polyfill/-/abortcontroller-polyfill-1.7.5.tgz", + "integrity": "sha512-JMJ5soJWP18htbbxJjG7bG6yuI6pRhgJ0scHHTfkUjf6wjP912xZWvM+A4sJK3gqd9E8fcPbDnOefbA9Th/FIQ==", "dev": true }, "abstract-level": { @@ -2651,12 +16638,13 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true + "dev": true, + "requires": {} }, "address": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/address/-/address-1.2.1.tgz", - "integrity": "sha512-B+6bi5D34+fDYENiH5qOlA0cV2rAGKuWZ9LeyUUehbXy8e0VS9e498yO0Jeeh+iM+6KbfudHTFjXw2MmJD4QRA==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/address/-/address-1.2.2.tgz", + "integrity": "sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==", "dev": true }, "adm-zip": { @@ -2760,9 +16748,9 @@ "dev": true }, "anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "dev": true, "requires": { "normalize-path": "^3.0.0", @@ -2785,15 +16773,15 @@ "dev": true }, "array-includes": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.5.tgz", - "integrity": "sha512-iSDYZMMyTPkiFasVqfuAQnWAYcvO/SeBSCGKePoEthjp4LEMTe4uLc7b025o4jAZpHhihh8xPo99TNWUWWkGDQ==", + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz", + "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==", "dev": true, "requires": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", - "es-abstract": "^1.19.5", - "get-intrinsic": "^1.1.1", + "es-abstract": "^1.20.4", + "get-intrinsic": "^1.1.3", "is-string": "^1.0.7" } }, @@ -2810,26 +16798,26 @@ "dev": true }, "array.prototype.flat": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.0.tgz", - "integrity": "sha512-12IUEkHsAhA4DY5s0FPgNXIdc8VRSqD9Zp78a5au9abH/SOBrsp082JOWFNTjkMozh8mqcdiKuaLGhPeYztxSw==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz", + "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==", "dev": true, "requires": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", "es-shim-unscopables": "^1.0.0" } }, "array.prototype.reduce": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/array.prototype.reduce/-/array.prototype.reduce-1.0.4.tgz", - "integrity": "sha512-WnM+AjG/DvLRLo4DDl+r+SvCzYtD2Jd9oeBYMcEaI7t3fFrHY9M53/wdLcTvmZNQ70IU6Htj0emFkZ5TS+lrdw==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/array.prototype.reduce/-/array.prototype.reduce-1.0.5.tgz", + "integrity": "sha512-kDdugMl7id9COE8R7MHF5jWk7Dqt/fs4Pv+JXoICnYwqpjjjbUurz6w5fT5IG6brLdJhv6/VoHB0H7oyIBXd+Q==", "dev": true, "requires": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", "es-array-method-boxes-properly": "^1.0.0", "is-string": "^1.0.7" } @@ -2990,18 +16978,18 @@ "dev": true }, "bigint-crypto-utils": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/bigint-crypto-utils/-/bigint-crypto-utils-3.1.6.tgz", - "integrity": "sha512-k5ljSLHx94jQTW3+18KEfxLJR8/XFBHqhfhEGF48qT8p/jL6EdiG7oNOiiIRGMFh2wEP8kaCXZbVd+5dYkngUg==", + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/bigint-crypto-utils/-/bigint-crypto-utils-3.1.8.tgz", + "integrity": "sha512-+VMV9Laq8pXLBKKKK49nOoq9bfR3j7NNQAtbA617a4nw9bVLo8rsqkKMBgM2AJWlNX9fEIyYaYX+d0laqYV4tw==", "dev": true, "requires": { "bigint-mod-arith": "^3.1.0" } }, "bigint-mod-arith": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/bigint-mod-arith/-/bigint-mod-arith-3.1.1.tgz", - "integrity": "sha512-SzFqdncZKXq5uh3oLFZXmzaZEMDsA7ml9l53xKaVGO6/+y26xNwAaTQEg2R+D+d07YduLbKi0dni3YPsR51UDQ==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bigint-mod-arith/-/bigint-mod-arith-3.1.2.tgz", + "integrity": "sha512-nx8J8bBeiRR+NlsROFH9jHswW5HO8mgfOSqW0AmjicMMvaONDa8AO+5ViKDUUNytBPWiwfvZP4/Bj4Y3lUfvgQ==", "dev": true }, "bignumber.js": { @@ -3035,9 +17023,9 @@ "dev": true }, "body-parser": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", - "integrity": "sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==", + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", "dev": true, "requires": { "bytes": "3.1.2", @@ -3048,7 +17036,7 @@ "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", - "qs": "6.10.3", + "qs": "6.11.0", "raw-body": "2.5.1", "type-is": "~1.6.18", "unpipe": "1.0.0" @@ -3068,24 +17056,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true - }, - "on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dev": true, - "requires": { - "ee-first": "1.1.1" - } - }, - "qs": { - "version": "6.10.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", - "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", - "dev": true, - "requires": { - "side-channel": "^1.0.4" - } } } }, @@ -3273,14 +17243,23 @@ "dev": true }, "bufferutil": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.6.tgz", - "integrity": "sha512-jduaYOYtnio4aIAyc6UbvPCVcgq7nYpVnucyxr6eCYg/Woad9Hf/oxxBRDnGGjPfjUm6j5O/uBWhIu4iLebFaw==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.7.tgz", + "integrity": "sha512-kukuqc39WOHtdxtw4UScxF/WVnMFVSQVKhtx3AjZJzhd0RGZZldcrfSEbVsWWe6KNH253574cq5F+wpv0G9pJw==", "dev": true, "requires": { "node-gyp-build": "^4.3.0" } }, + "busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dev": true, + "requires": { + "streamsearch": "^1.1.0" + } + }, "bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -3406,22 +17385,22 @@ }, "dependencies": { "bignumber.js": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.0.tgz", - "integrity": "sha512-4LwHK4nfDOraBCtst+wOWIHbu1vhvAPJK8g8nROd4iuc3PSEjWif/qwbkh8jwCJz6yDBvtU4KPynETgrfh7y3A==", + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.1.tgz", + "integrity": "sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig==", "dev": true } } }, "chai": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.6.tgz", - "integrity": "sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q==", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.7.tgz", + "integrity": "sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==", "dev": true, "requires": { "assertion-error": "^1.1.0", "check-error": "^1.0.2", - "deep-eql": "^3.0.1", + "deep-eql": "^4.1.2", "get-func-name": "^2.0.0", "loupe": "^2.3.1", "pathval": "^1.1.1", @@ -3432,7 +17411,8 @@ "version": "0.2.2", "resolved": "https://registry.npmjs.org/chai-bn/-/chai-bn-0.2.2.tgz", "integrity": "sha512-MzjelH0p8vWn65QKmEq/DLBG1Hle4WeyqT79ANhXZhn/UxRWO0OogkAxi5oGGtfzwU9bZR8mvbvYdoqNVWQwFg==", - "dev": true + "dev": true, + "requires": {} }, "chalk": { "version": "3.0.0", @@ -3632,13 +17612,13 @@ "dev": true }, "cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "dev": true, "requires": { "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", + "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" }, "dependencies": { @@ -3837,12 +17817,6 @@ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", "dev": true }, - "cookiejar": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.3.tgz", - "integrity": "sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ==", - "dev": true - }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", @@ -4058,9 +18032,9 @@ "dev": true }, "decode-uri-component": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.1.tgz", - "integrity": "sha512-XZHyaFJ6QMWhYmlz+UcmtaLeecNiXwkTGzCqG5WByt+1P1HnU6Siwf0TeP3OsZmlnGqQRSEMIxue0LLCaGY3dw==", + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", + "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", "dev": true }, "decompress-response": { @@ -4081,9 +18055,9 @@ } }, "deep-eql": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", - "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", + "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", "dev": true, "requires": { "type-detect": "^4.0.0" @@ -4146,30 +18120,13 @@ "dev": true }, "detect-port": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/detect-port/-/detect-port-1.3.0.tgz", - "integrity": "sha512-E+B1gzkl2gqxt1IhUzwjrxBKRqx1UzC3WLONHinn8S3T6lwV/agVCyitiFOsGJ/eYuEUBvD71MZHy3Pv1G9doQ==", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/detect-port/-/detect-port-1.5.1.tgz", + "integrity": "sha512-aBzdj76lueB6uUst5iAs7+0H/oOjqI5D16XUWxlWMIMROhcM0rfsNVk93zTngq1dDNpoXRr++Sus7ETAExppAQ==", "dev": true, "requires": { "address": "^1.0.1", - "debug": "^2.6.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - } + "debug": "4" } }, "diff": { @@ -4268,12 +18225,6 @@ "no-case": "^2.2.0" } }, - "duplexer3": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.5.tgz", - "integrity": "sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA==", - "dev": true - }, "ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", @@ -4306,9 +18257,9 @@ } }, "emoji-regex": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.1.0.tgz", - "integrity": "sha512-xAEnNCT3w2Tg6MA7ly6QqYJvEoY1tm9iIjJ3yMKK9JPlWuRHAMoe5iETwQnx3M9TVbFMfsrBgWKR+IsmswwNjg==", + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.2.1.tgz", + "integrity": "sha512-97g6QgOk8zlDRdgq1WxwgTMgEWGVAQvB5Fdpgc1MkNy56la5SKP9GsMXKDOdqwn90/41a8yPwIGk1Y6WVbeMQA==", "dev": true }, "encodeurl": { @@ -4365,22 +18316,23 @@ } }, "es-abstract": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.2.tgz", - "integrity": "sha512-XxXQuVNrySBNlEkTYJoDNFe5+s2yIOpzq80sUHEdPdQr0S5nTLz4ZPPPswNIpKseDDUS5yghX1gfLIHQZ1iNuQ==", + "version": "1.20.5", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.5.tgz", + "integrity": "sha512-7h8MM2EQhsCA7pU/Nv78qOXFpD8Rhqd12gYiSJVkrH9+e8VuA8JlPJK/hQjjlLv6pJvx/z1iRFKzYb0XT/RuAQ==", "dev": true, "requires": { "call-bind": "^1.0.2", "es-to-primitive": "^1.2.1", "function-bind": "^1.1.1", "function.prototype.name": "^1.1.5", - "get-intrinsic": "^1.1.2", + "get-intrinsic": "^1.1.3", "get-symbol-description": "^1.0.0", + "gopd": "^1.0.1", "has": "^1.0.3", "has-property-descriptors": "^1.0.0", "has-symbols": "^1.0.3", "internal-slot": "^1.0.3", - "is-callable": "^1.2.4", + "is-callable": "^1.2.7", "is-negative-zero": "^2.0.2", "is-regex": "^1.1.4", "is-shared-array-buffer": "^1.0.2", @@ -4390,8 +18342,9 @@ "object-keys": "^1.1.1", "object.assign": "^4.1.4", "regexp.prototype.flags": "^1.4.3", - "string.prototype.trimend": "^1.0.5", - "string.prototype.trimstart": "^1.0.5", + "safe-regex-test": "^1.0.0", + "string.prototype.trimend": "^1.0.6", + "string.prototype.trimstart": "^1.0.6", "unbox-primitive": "^1.0.2" } }, @@ -4622,7 +18575,8 @@ "version": "16.0.3", "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-16.0.3.tgz", "integrity": "sha512-x4fmJL5hGqNJKGHSjnLdgA6U6h1YW/G2dW9fA+cyVur4SK6lyue8+UgNKWlZtUDTXvgKDD/Oa3GQjmB5kjtVvg==", - "dev": true + "dev": true, + "requires": {} }, "eslint-import-resolver-node": { "version": "0.3.6", @@ -4758,9 +18712,9 @@ }, "dependencies": { "ignore": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", - "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", "dev": true }, "semver": { @@ -4775,7 +18729,8 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-5.2.0.tgz", "integrity": "sha512-SftLb1pUG01QYq2A/hGAWfDRXqYD82zE7j7TopDOyNdU+7SvvoXREls/+PRTY17vUXzXnZA/zfnyKgRH6x4JJw==", - "dev": true + "dev": true, + "requires": {} }, "eslint-scope": { "version": "5.1.1", @@ -5656,14 +19611,14 @@ } }, "express": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.1.tgz", - "integrity": "sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q==", + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", + "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", "dev": true, "requires": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.0", + "body-parser": "1.20.1", "content-disposition": "0.5.4", "content-type": "~1.0.4", "cookie": "0.5.0", @@ -5682,7 +19637,7 @@ "parseurl": "~1.3.3", "path-to-regexp": "0.1.7", "proxy-addr": "~2.0.7", - "qs": "6.10.3", + "qs": "6.11.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", "send": "0.18.0", @@ -5702,56 +19657,17 @@ }, "debug": { "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", - "dev": true, - "requires": { - "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" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dev": true, - "requires": { - "ee-first": "1.1.1" - } - }, - "qs": { - "version": "6.10.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", - "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "requires": { - "side-channel": "^1.0.4" + "ms": "2.0.0" } }, - "statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true } } @@ -5843,9 +19759,9 @@ "dev": true }, "fastq": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", - "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.14.0.tgz", + "integrity": "sha512-eR2D+V9/ExcbF9ls441yIuN6TI2ED1Y2ZcA5BmMtJsOkWOFRJQ0Jt0g1UwqXJJVAb+V+umH5Dfr8oh4EVP7VVg==", "dev": true, "requires": { "reusify": "^1.0.4" @@ -5886,6 +19802,38 @@ "to-regex-range": "^5.0.1" } }, + "finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dev": true, + "requires": { + "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" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + } + } + }, "find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", @@ -5919,9 +19867,9 @@ "dev": true }, "follow-redirects": { - "version": "1.15.1", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz", - "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==", + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", "dev": true }, "for-each": { @@ -6188,9 +20136,9 @@ } }, "minimatch": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", - "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.2.tgz", + "integrity": "sha512-bNH9mmM9qsJ2X4r2Nat1B//1dJVcn3+iBLa3IgqJ7EbGaDNepL9QSHOxN4ng33s52VMMhhIfgCYDk3C4ZmlDAg==", "dev": true, "requires": { "brace-expansion": "^2.0.1" @@ -6249,9 +20197,9 @@ } }, "globals": { - "version": "13.17.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", - "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==", + "version": "13.19.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.19.0.tgz", + "integrity": "sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==", "dev": true, "requires": { "type-fest": "^0.20.2" @@ -6288,13 +20236,22 @@ } }, "ignore": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", - "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", "dev": true } } }, + "gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.3" + } + }, "got": { "version": "12.1.0", "resolved": "https://registry.npmjs.org/got/-/got-12.1.0.tgz", @@ -6375,24 +20332,24 @@ } }, "hardhat": { - "version": "2.11.1", - "resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.11.1.tgz", - "integrity": "sha512-7FoyfKjBs97GHNpQejHecJBBcRPOEhAE3VkjSWXB3GeeiXefWbw+zhRVOjI4eCsUUt7PyNFAdWje/lhnBT9fig==", + "version": "2.12.4", + "resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.12.4.tgz", + "integrity": "sha512-rc9S2U/4M+77LxW1Kg7oqMMmjl81tzn5rNFARhbXKUA1am/nhfMJEujOjuKvt+ZGMiZ11PYSe8gyIpB/aRNDgw==", "dev": true, "requires": { "@ethersproject/abi": "^5.1.2", "@metamask/eth-sig-util": "^4.0.0", - "@nomicfoundation/ethereumjs-block": "^4.0.0-rc.3", - "@nomicfoundation/ethereumjs-blockchain": "^6.0.0-rc.3", - "@nomicfoundation/ethereumjs-common": "^3.0.0-rc.3", - "@nomicfoundation/ethereumjs-evm": "^1.0.0-rc.3", - "@nomicfoundation/ethereumjs-rlp": "^4.0.0-rc.3", - "@nomicfoundation/ethereumjs-statemanager": "^1.0.0-rc.3", - "@nomicfoundation/ethereumjs-trie": "^5.0.0-rc.3", - "@nomicfoundation/ethereumjs-tx": "^4.0.0-rc.3", - "@nomicfoundation/ethereumjs-util": "^8.0.0-rc.3", - "@nomicfoundation/ethereumjs-vm": "^6.0.0-rc.3", - "@nomicfoundation/solidity-analyzer": "^0.0.3", + "@nomicfoundation/ethereumjs-block": "^4.0.0", + "@nomicfoundation/ethereumjs-blockchain": "^6.0.0", + "@nomicfoundation/ethereumjs-common": "^3.0.0", + "@nomicfoundation/ethereumjs-evm": "^1.0.0", + "@nomicfoundation/ethereumjs-rlp": "^4.0.0", + "@nomicfoundation/ethereumjs-statemanager": "^1.0.0", + "@nomicfoundation/ethereumjs-trie": "^5.0.0", + "@nomicfoundation/ethereumjs-tx": "^4.0.0", + "@nomicfoundation/ethereumjs-util": "^8.0.0", + "@nomicfoundation/ethereumjs-vm": "^6.0.0", + "@nomicfoundation/solidity-analyzer": "^0.1.0", "@sentry/node": "^5.18.1", "@types/bn.js": "^5.1.0", "@types/lru-cache": "^5.1.0", @@ -6660,9 +20617,9 @@ } }, "hardhat-ignore-warnings": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/hardhat-ignore-warnings/-/hardhat-ignore-warnings-0.2.0.tgz", - "integrity": "sha512-fetYwdpjAg6pl7oxOAL0yZQTKt/87KgDV5P7sEoIORXaoqCBvRGcGAQLJZ8hCiWNZ+vZKYw/9oVVZVlFcOxZTw==", + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/hardhat-ignore-warnings/-/hardhat-ignore-warnings-0.2.6.tgz", + "integrity": "sha512-GQgvjprONI8VF8b85+QJ8H9v3L9TCCtQvUx+9QaRL+sCPw1cOZHfhlEz9V6Lq7GNCQMqBORVzNzUzoP/tKAEQQ==", "dev": true, "requires": { "minimatch": "^5.1.0", @@ -6680,9 +20637,9 @@ } }, "minimatch": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", - "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.2.tgz", + "integrity": "sha512-bNH9mmM9qsJ2X4r2Nat1B//1dJVcn3+iBLa3IgqJ7EbGaDNepL9QSHOxN4ng33s52VMMhhIfgCYDk3C4ZmlDAg==", "dev": true, "requires": { "brace-expansion": "^2.0.1" @@ -6848,14 +20805,6 @@ "setprototypeof": "1.2.0", "statuses": "2.0.1", "toidentifier": "1.0.1" - }, - "dependencies": { - "statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "dev": true - } } }, "http-https": { @@ -6893,9 +20842,9 @@ } }, "http2-wrapper": { - "version": "2.1.11", - "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.1.11.tgz", - "integrity": "sha512-aNAk5JzLturWEUiuhAN73Jcbq96R7rTitAoXV54FYMatvihnpD2+6PUgU4ce3D/m5VDbw+F5CsyKSF176ptitQ==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.0.tgz", + "integrity": "sha512-kZB0wxMo0sh1PehyjJUWRFEd99KC5TLjZ2cULC4f9iqJBAmKQQXEICjxl5iPJRwP40dpeHFqqhm7tYCvODpqpQ==", "dev": true, "requires": { "quick-lru": "^5.1.1", @@ -6943,9 +20892,9 @@ "dev": true }, "immutable": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.1.0.tgz", - "integrity": "sha512-oNkuqVTA8jqG1Q6c+UglTOD1xhC1BtjKI7XkCXRkZHrN5m18/XsnUp8Q89GkQO/z+0WjonSvl0FLhDYftp46nQ==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.2.1.tgz", + "integrity": "sha512-7WYV7Q5BTs0nlQm7tl92rDYYoyELLKHoDMBKhrxEoiV4mrfVdRz8hzPiYOzH7yWjzoVEamxRuAqhxL2PLRwZYQ==", "dev": true }, "import-fresh": { @@ -7093,12 +21042,12 @@ } }, "internal-slot": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", - "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.4.tgz", + "integrity": "sha512-tA8URYccNzMo94s5MQZgH8NB/XTa6HsOo0MLfXTKKEnHVVdegzaQoFZ7Jp44bdvLvY2waT5dc+j5ICEswhi7UQ==", "dev": true, "requires": { - "get-intrinsic": "^1.1.0", + "get-intrinsic": "^1.1.3", "has": "^1.0.3", "side-channel": "^1.0.4" } @@ -7181,15 +21130,15 @@ "dev": true }, "is-callable": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.5.tgz", - "integrity": "sha512-ZIWRujF6MvYGkEuHMYtFRkL2wAtFw89EHfKlXrkPkjQZZRWeh9L1q3SV13NIfHnqxugjLvAOkEHx9mb1zcMnEw==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", "dev": true }, "is-core-module": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz", - "integrity": "sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==", + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", + "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", "dev": true, "requires": { "has": "^1.0.3" @@ -7332,15 +21281,15 @@ } }, "is-typed-array": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.9.tgz", - "integrity": "sha512-kfrlnTTn8pZkfpJMUgYD7YZ3qzeJgWUn8XfVYBARc4wnmNOmLbmuuaAs3q5fvB0UJOn6yHAKaGTPM7d6ezoD/A==", + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", + "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", "dev": true, "requires": { "available-typed-arrays": "^1.0.5", "call-bind": "^1.0.2", - "es-abstract": "^1.20.0", "for-each": "^0.3.3", + "gopd": "^1.0.1", "has-tostringtag": "^1.0.0" } }, @@ -7462,6 +21411,15 @@ "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", "dev": true }, + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, "jsonfile": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", @@ -7530,9 +21488,9 @@ } }, "keyv": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.0.tgz", - "integrity": "sha512-2YvuMsA+jnFGtBareKqgANOEKe1mk3HKiXu2fRmAfyxG0MJAywNhi5ttWA3PMjl4NmpyjZNbFifR2vNjW1znfA==", + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.2.tgz", + "integrity": "sha512-5MHbFaKn8cNSmVW7BYnijeAVlE4cYA/SVkifVgrh7yotnfhKmjuXpDKjrABLnT0SfHWV21P8ow07OGfRrNDg8g==", "dev": true, "requires": { "json-buffer": "3.0.1" @@ -7723,9 +21681,9 @@ } }, "loupe": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.4.tgz", - "integrity": "sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ==", + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz", + "integrity": "sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==", "dev": true, "requires": { "get-func-name": "^2.0.0" @@ -7752,6 +21710,12 @@ "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==", "dev": true }, + "lru_map": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.3.3.tgz", + "integrity": "sha512-Pn9cox5CsMYngeDbmChANltQl+5pi6XmTrraMSzhPmMBbmgcxmqWry0U3PGapCU1yB4/LqCcom7qhHZiF/jGfQ==", + "dev": true + }, "lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -7761,12 +21725,6 @@ "yallist": "^3.0.2" } }, - "lru_map": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.3.3.tgz", - "integrity": "sha512-Pn9cox5CsMYngeDbmChANltQl+5pi6XmTrraMSzhPmMBbmgcxmqWry0U3PGapCU1yB4/LqCcom7qhHZiF/jGfQ==", - "dev": true - }, "markdown-table": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-1.1.3.tgz", @@ -7839,9 +21797,9 @@ }, "dependencies": { "bignumber.js": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.0.tgz", - "integrity": "sha512-4LwHK4nfDOraBCtst+wOWIHbu1vhvAPJK8g8nROd4iuc3PSEjWif/qwbkh8jwCJz6yDBvtU4KPynETgrfh7y3A==", + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.1.tgz", + "integrity": "sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig==", "dev": true } } @@ -7936,9 +21894,9 @@ } }, "minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", + "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", "dev": true }, "minipass": { @@ -7988,12 +21946,11 @@ } }, "mocha": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.0.0.tgz", - "integrity": "sha512-0Wl+elVUD43Y0BqPZBzZt8Tnkw9CMUdNYnUsTfOM1vuhJVZL+kiesFYsqwBkEEuEixaiPe5ZQdqDgX2jddhmoA==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", + "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==", "dev": true, "requires": { - "@ungap/promise-all-settled": "1.1.2", "ansi-colors": "4.1.1", "browser-stdout": "1.3.1", "chokidar": "3.5.3", @@ -8035,13 +21992,15 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", "dev": true, "requires": { - "balanced-match": "^1.0.0" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" } }, "emoji-regex": { @@ -8074,16 +22033,6 @@ "path-is-absolute": "^1.0.0" }, "dependencies": { - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, "minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -8126,6 +22075,17 @@ "dev": true, "requires": { "brace-expansion": "^2.0.1" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + } } }, "ms": { @@ -8376,9 +22336,9 @@ "dev": true }, "node-interval-tree": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/node-interval-tree/-/node-interval-tree-2.0.1.tgz", - "integrity": "sha512-KodzC8le4U8LOmCvn1wSyIY8eplzRSjsLMzs0EjLteCXWDjRpCTzrjtQ4t8jh3w3r6OIglha1zChzjRYMVwuLA==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/node-interval-tree/-/node-interval-tree-2.1.2.tgz", + "integrity": "sha512-bJ9zMDuNGzVQg1xv0bCPzyEDxHgbrx7/xGj6CDokvizZZmastPsOh0JJLuY8wA5q2SfX1TLNMk7XNV8WxbGxzA==", "dev": true, "requires": { "shallowequal": "^1.1.0" @@ -8501,26 +22461,26 @@ } }, "object.getownpropertydescriptors": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.4.tgz", - "integrity": "sha512-sccv3L/pMModT6dJAYF3fzGMVcb38ysQ0tEE6ixv2yXJDtEIPph268OlAdJj5/qZMZDq2g/jqvwppt36uS/uQQ==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.5.tgz", + "integrity": "sha512-yDNzckpM6ntyQiGTik1fKV1DcVDRS+w8bvpWNCBanvH5LfRX9O8WTHqQzG4RZwRAM4I0oU7TV11Lj5v0g20ibw==", "dev": true, "requires": { - "array.prototype.reduce": "^1.0.4", + "array.prototype.reduce": "^1.0.5", "call-bind": "^1.0.2", "define-properties": "^1.1.4", - "es-abstract": "^1.20.1" + "es-abstract": "^1.20.4" } }, "object.values": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.5.tgz", - "integrity": "sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz", + "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==", "dev": true, "requires": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" } }, "obliterator": { @@ -8538,6 +22498,15 @@ "http-https": "^1.0.0" } }, + "on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "requires": { + "ee-first": "1.1.1" + } + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -8684,9 +22653,9 @@ } }, "parse5": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.1.tgz", - "integrity": "sha512-kwpuwzB+px5WUg9pyK0IcK/shltJN5/OVhQagxhCQNtT9Y9QRZqNY2e1cmbu/paRh5LMnz/oVTVLBpjFmMZhSg==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", "dev": true, "requires": { "entities": "^4.4.0" @@ -8827,28 +22796,22 @@ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true }, - "prepend-http": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", - "integrity": "sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA==", - "dev": true - }, "prettier": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz", - "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.1.tgz", + "integrity": "sha512-lqGoSJBQNJidqCHE80vqZJHWHRFoNYsSpP9AjFhlhi9ODCJA541svILes/+/1GM3VaL/abZi7cpFzOpdR9UPKg==", "dev": true }, "prettier-plugin-solidity": { - "version": "1.0.0-beta.24", - "resolved": "https://registry.npmjs.org/prettier-plugin-solidity/-/prettier-plugin-solidity-1.0.0-beta.24.tgz", - "integrity": "sha512-6JlV5BBTWzmDSq4kZ9PTXc3eLOX7DF5HpbqmmaF+kloyUwOZbJ12hIYsUaZh2fVgZdV2t0vWcvY6qhILhlzgqg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/prettier-plugin-solidity/-/prettier-plugin-solidity-1.1.0.tgz", + "integrity": "sha512-5gq0T49ifvXH/6x1STuKyWjTUgi6ICoV65yNtKlg/vZEvocFtSpByJOJICBfqPwNsnv4vhhWIqkLGSUJmWum2w==", "dev": true, "requires": { - "@solidity-parser/parser": "^0.14.3", - "emoji-regex": "^10.1.0", + "@solidity-parser/parser": "^0.14.5", + "emoji-regex": "^10.2.1", "escape-string-regexp": "^4.0.0", - "semver": "^7.3.7", + "semver": "^7.3.8", "solidity-comments-extractor": "^0.0.7", "string-width": "^4.2.3" }, @@ -8914,9 +22877,9 @@ "dev": true }, "promise": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/promise/-/promise-8.2.0.tgz", - "integrity": "sha512-+CMAlLHqwRYwBMXKCP+o8ns7DN+xHDUiI+0nArsiJ9y+kJVPLFxEaSw6Ha9s9H0tftxg2Yzl25wqj9G7m5wLZg==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/promise/-/promise-8.3.0.tgz", + "integrity": "sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==", "dev": true, "requires": { "asap": "~2.0.6" @@ -8969,9 +22932,9 @@ "dev": true }, "pure-rand": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-5.0.3.tgz", - "integrity": "sha512-9N8x1h8dptBQpHyC7aZMS+iNOAm97WMGY0AFrguU1cpfW3I5jINkWe5BIY5md0ofy+1TCIELsVcm/GJXZSaPbw==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-5.0.5.tgz", + "integrity": "sha512-BwQpbqxSCBJVpamI6ydzcKqyFmnd5msMWUGvzXLm1aXvusbbgkbOto/EUPM00hjveJEaJtdbhUjKSzWRhQVkaw==", "dev": true }, "qs": { @@ -9007,9 +22970,9 @@ "dev": true }, "rambda": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/rambda/-/rambda-7.2.1.tgz", - "integrity": "sha512-Wswj8ZvzdI3VhaGPkZAxaCTwuMmGtgWt7Zxsgyo4P+iTmVnkojvyWaOep5q3ZjMIecW0wtQa66GWxaKkZ24RAA==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/rambda/-/rambda-7.4.0.tgz", + "integrity": "sha512-A9hihu7dUTLOUCM+I8E61V4kRXnN4DwYeK0DwCBydC1MqNI1PidyAtbtpsJlBBzK4icSctEcCQ1bGcLpBuETUQ==", "dev": true }, "randombytes": { @@ -9140,29 +23103,18 @@ } }, "recursive-readdir": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.2.tgz", - "integrity": "sha512-nRCcW9Sj7NuZwa2XvH9co8NPeXUBhZP7CRKJtU+cS6PW9FpCIFoI5ib0NT1ZrbNuPoRy0ylyCaUL8Gih4LSyFg==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.3.tgz", + "integrity": "sha512-8HrF5ZsXk5FAH9dgsx3BlUer73nIhuj+9OrQwEbLTPOBzGkL1lsFCR01am+v+0m2Cmbs1nP12hLDl5FA7EszKA==", "dev": true, "requires": { - "minimatch": "3.0.4" - }, - "dependencies": { - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - } + "minimatch": "^3.0.5" } }, "regenerator-runtime": { - "version": "0.13.9", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", - "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==", + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", "dev": true }, "regexp.prototype.flags": { @@ -9447,6 +23399,17 @@ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "dev": true }, + "safe-regex-test": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", + "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-regex": "^1.1.4" + } + }, "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -9550,9 +23513,9 @@ } }, "semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -9618,21 +23581,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true - }, - "on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dev": true, - "requires": { - "ee-first": "1.1.1" - } - }, - "statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "dev": true } } }, @@ -10465,9 +24413,9 @@ } }, "solidity-ast": { - "version": "0.4.35", - "resolved": "https://registry.npmjs.org/solidity-ast/-/solidity-ast-0.4.35.tgz", - "integrity": "sha512-F5bTDLh3rmDxRmLSrs3qt3nvxJprWSEkS7h2KmuXDx7XTfJ6ZKVTV1rtPIYCqJAuPsU/qa8YUeFn7jdOAZcTPA==", + "version": "0.4.39", + "resolved": "https://registry.npmjs.org/solidity-ast/-/solidity-ast-0.4.39.tgz", + "integrity": "sha512-91d4HMzV9x3ZG1fXRtAFFq2UjJrQXkyWdrmzXqBlueOSGB+v+0+iiLfZIPnTE0apndG2zm23qkZQJf8IbRrf7w==", "dev": true }, "solidity-comments": { @@ -10996,13 +24944,13 @@ } }, "solidity-docgen": { - "version": "0.6.0-beta.29", - "resolved": "https://registry.npmjs.org/solidity-docgen/-/solidity-docgen-0.6.0-beta.29.tgz", - "integrity": "sha512-63p3w6wj1WFhhC8pXTI3bz5qUTFuGmLNHFnwwpjZ6Qv8dF2WGDt0pg1rbA6c3bL/A4d0ATN66Mte1saGKVWdHg==", + "version": "0.6.0-beta.32", + "resolved": "https://registry.npmjs.org/solidity-docgen/-/solidity-docgen-0.6.0-beta.32.tgz", + "integrity": "sha512-LGcosbgqxrqTo9winVq3+Xz1shS9k4p+RKwqPVPI0HtjfsKF9Mc5GzBzOUhce9uwzy0yJfhf832whfY1UKt4Aw==", "dev": true, "requires": { "handlebars": "^4.7.7", - "solidity-ast": "^0.4.31" + "solidity-ast": "^0.4.38" } }, "source-map": { @@ -11113,18 +25061,39 @@ } } }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true + }, "stealthy-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", "integrity": "sha512-ZnWpYnYugiOVEY5GkcuJK1io5V8QmNYChG62gSit9pQVGErXtrKuPC55ITaVSukmMta5qpMU7vqLt2Lnni4f/g==", "dev": true }, + "streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "dev": true + }, "strict-uri-encode": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", "integrity": "sha512-R3f198pcvnB+5IpnBlRkphuE9n46WyVl8I39W/ZUTZLz4nqSP/oLYUrcnJrw462Ds8he4YKMov2efsTIw1BDGQ==", "dev": true }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "requires": { + "safe-buffer": "~5.2.0" + } + }, "string-width": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", @@ -11136,34 +25105,25 @@ } }, "string.prototype.trimend": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz", - "integrity": "sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", + "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", "dev": true, "requires": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", - "es-abstract": "^1.19.5" + "es-abstract": "^1.20.4" } }, "string.prototype.trimstart": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz", - "integrity": "sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", + "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", "dev": true, "requires": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", - "es-abstract": "^1.19.5" - } - }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, - "requires": { - "safe-buffer": "~5.2.0" + "es-abstract": "^1.20.4" } }, "strip-ansi": { @@ -11273,9 +25233,9 @@ } }, "got": { - "version": "11.8.5", - "resolved": "https://registry.npmjs.org/got/-/got-11.8.5.tgz", - "integrity": "sha512-o0Je4NvQObAuZPHLFoRSkdG2lTgtcynqymzg2Vupdx6PorhaT5MCbIyXG6d4D94kk8ZG57QeosgdiqfJWhEhlQ==", + "version": "11.8.6", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", + "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", "dev": true, "requires": { "@sindresorhus/is": "^4.0.0", @@ -11336,9 +25296,9 @@ } }, "table": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/table/-/table-6.8.0.tgz", - "integrity": "sha512-s/fitrbVeEyHKFa7mFdkuQMWlH1Wgw/yEXMt5xACT4ZpzWFluehAxRtUUQKPuWhaLAWhFcVx6w3oC8VKaUfPGA==", + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/table/-/table-6.8.1.tgz", + "integrity": "sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA==", "dev": true, "requires": { "ajv": "^8.0.1", @@ -11349,9 +25309,9 @@ }, "dependencies": { "ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.2.tgz", + "integrity": "sha512-E4bfmKAhGiSTvMfL1Myyycaub+cUEU2/IvpylXkUu7CHBkBj1f/ikdzbD7YQ6FKUbixDxeYvB/xY4fvyroDlQg==", "dev": true, "requires": { "fast-deep-equal": "^3.1.1", @@ -11497,12 +25457,6 @@ "os-tmpdir": "~1.0.2" } }, - "to-readable-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", - "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==", - "dev": true - }, "to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -11558,17 +25512,6 @@ "json5": "^1.0.1", "minimist": "^1.2.6", "strip-bom": "^3.0.0" - }, - "dependencies": { - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } - } } }, "tslib": { @@ -11657,9 +25600,9 @@ } }, "uglify-js": { - "version": "3.17.0", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.0.tgz", - "integrity": "sha512-aTeNPVmgIMPpm1cxXr2Q/nEbvkmV8yq66F3om7X3P/cvOXQ0TMQ64Wk63iyT1gPlmdmGzjGpyLh1f3y8MZWXGg==", + "version": "3.17.4", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", + "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", "dev": true, "optional": true }, @@ -11682,16 +25625,19 @@ } }, "underscore": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.4.tgz", - "integrity": "sha512-BQFnUDuAQ4Yf/cYY5LNrK9NCJFKriaRbD9uR1fTeXnBeoa97W0i41qkZfGO9pSo8I5KzjAcSY2XYtdf0oKd7KQ==", + "version": "1.13.6", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", + "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==", "dev": true }, "undici": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.10.0.tgz", - "integrity": "sha512-c8HsD3IbwmjjbLvoZuRI26TZic+TSEe8FPMLLOkN1AfYRhdjnKBU6yL+IwcSCbdZiX4e5t0lfMDLDCqj4Sq70g==", - "dev": true + "version": "5.14.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.14.0.tgz", + "integrity": "sha512-yJlHYw6yXPPsuOH0x2Ib1Km61vu4hLiRRQoafs+WUgX1vO64vgnxiCEN9dpIrhZyHFsai3F0AEj4P9zy19enEQ==", + "dev": true, + "requires": { + "busboy": "^1.6.0" + } }, "universalify": { "version": "0.1.2", @@ -11729,15 +25675,6 @@ "punycode": "^2.1.0" } }, - "url-parse-lax": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", - "integrity": "sha512-NjFKA0DidqPa5ciFcSrXnAltTtzz84ogy+NebPvfEgAck0+TNg4UJ4IN+fB7zRZfbgUf0syOo9MDxFkDSMuFaQ==", - "dev": true, - "requires": { - "prepend-http": "^2.0.0" - } - }, "url-set-query": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/url-set-query/-/url-set-query-1.0.0.tgz", @@ -11745,9 +25682,9 @@ "dev": true }, "utf-8-validate": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.9.tgz", - "integrity": "sha512-Yek7dAy0v3Kl0orwMlvi7TPtiCNrdfHNd7Gcc/pLq4BLXqfAmd0J7OWMizUQnTTJsyjKn02mU7anqwfmUP4J8Q==", + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz", + "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==", "dev": true, "requires": { "node-gyp-build": "^4.3.0" @@ -11760,16 +25697,15 @@ "dev": true }, "util": { - "version": "0.12.4", - "resolved": "https://registry.npmjs.org/util/-/util-0.12.4.tgz", - "integrity": "sha512-bxZ9qtSlGUWSOy9Qa9Xgk11kSslpuZwaxCg4sNIDj6FLucDab2JxnHwyNTCpHMtK1MjoQiWQ6DiUMZYbSrO+Sw==", + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", "dev": true, "requires": { "inherits": "^2.0.3", "is-arguments": "^1.0.4", "is-generator-function": "^1.0.7", "is-typed-array": "^1.1.3", - "safe-buffer": "^5.1.2", "which-typed-array": "^1.1.2" } }, @@ -11831,24 +25767,24 @@ } }, "web3": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/web3/-/web3-1.7.5.tgz", - "integrity": "sha512-3jHZTWyXt975AOXgnZKayiSWDLpoSKk9fZtLk1hURQtt7AdSbXPT8AK9ooBCm0Dt3GYaOeNcHGaiHC3gtyqhLg==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3/-/web3-1.8.1.tgz", + "integrity": "sha512-tAqFsQhGv340C9OgRJIuoScN7f7wa1tUvsnnDUMt9YE6J4gcm7TV2Uwv+KERnzvV+xgdeuULYpsioRRNKrUvoQ==", "dev": true, "requires": { - "web3-bzz": "1.7.5", - "web3-core": "1.7.5", - "web3-eth": "1.7.5", - "web3-eth-personal": "1.7.5", - "web3-net": "1.7.5", - "web3-shh": "1.7.5", - "web3-utils": "1.7.5" + "web3-bzz": "1.8.1", + "web3-core": "1.8.1", + "web3-eth": "1.8.1", + "web3-eth-personal": "1.8.1", + "web3-net": "1.8.1", + "web3-shh": "1.8.1", + "web3-utils": "1.8.1" } }, "web3-bzz": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/web3-bzz/-/web3-bzz-1.7.5.tgz", - "integrity": "sha512-Z53sY0YK/losqjJncmL4vP0zZI9r6tiXg6o7R6e1JD2Iy7FH3serQvU+qXmPjqEBzsnhf8wTG+YcBPB3RHpr0Q==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-bzz/-/web3-bzz-1.8.1.tgz", + "integrity": "sha512-dJJHS84nvpoxv6ijTMkdUSlRr5beCXNtx4UZcrFLHBva8dT63QEtKdLyDt2AyMJJdVzTCk78uir/6XtVWrdS6w==", "dev": true, "requires": { "@types/node": "^12.12.6", @@ -11865,18 +25801,18 @@ } }, "web3-core": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/web3-core/-/web3-core-1.7.5.tgz", - "integrity": "sha512-UgOWXZr1fR/3cUQJKWbfMwRxj1/N7o6RSd/dHqdXBlOD+62EjNZItFmLRg5veq5kp9YfXzrNw9bnDkXfsL+nKQ==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-core/-/web3-core-1.8.1.tgz", + "integrity": "sha512-LbRZlJH2N6nS3n3Eo9Y++25IvzMY7WvYnp4NM/Ajhh97dAdglYs6rToQ2DbL2RLvTYmTew4O/y9WmOk4nq9COw==", "dev": true, "requires": { "@types/bn.js": "^5.1.0", "@types/node": "^12.12.6", "bignumber.js": "^9.0.0", - "web3-core-helpers": "1.7.5", - "web3-core-method": "1.7.5", - "web3-core-requestmanager": "1.7.5", - "web3-utils": "1.7.5" + "web3-core-helpers": "1.8.1", + "web3-core-method": "1.8.1", + "web3-core-requestmanager": "1.8.1", + "web3-utils": "1.8.1" }, "dependencies": { "@types/node": { @@ -11886,115 +25822,115 @@ "dev": true }, "bignumber.js": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.0.tgz", - "integrity": "sha512-4LwHK4nfDOraBCtst+wOWIHbu1vhvAPJK8g8nROd4iuc3PSEjWif/qwbkh8jwCJz6yDBvtU4KPynETgrfh7y3A==", + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.1.tgz", + "integrity": "sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig==", "dev": true } } }, "web3-core-helpers": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/web3-core-helpers/-/web3-core-helpers-1.7.5.tgz", - "integrity": "sha512-lDDjTks6Q6aNUO87RYrY2xub3UWTKr/RIWxpHJODEqkLxZS1dWdyliJ6aIx3031VQwsNT5HE7NvABe/t0p3iDQ==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-core-helpers/-/web3-core-helpers-1.8.1.tgz", + "integrity": "sha512-ClzNO6T1S1gifC+BThw0+GTfcsjLEY8T1qUp6Ly2+w4PntAdNtKahxWKApWJ0l9idqot/fFIDXwO3Euu7I0Xqw==", "dev": true, "requires": { - "web3-eth-iban": "1.7.5", - "web3-utils": "1.7.5" + "web3-eth-iban": "1.8.1", + "web3-utils": "1.8.1" } }, "web3-core-method": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/web3-core-method/-/web3-core-method-1.7.5.tgz", - "integrity": "sha512-ApTvq1Llzlbxmy0n4L7QaE6NodIsR80VJqk8qN4kLg30SGznt/pNJFebryLI2kpyDmxSgj1TjEWzmHJBp6FhYg==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-core-method/-/web3-core-method-1.8.1.tgz", + "integrity": "sha512-oYGRodktfs86NrnFwaWTbv2S38JnpPslFwSSARwFv4W9cjbGUW3LDeA5MKD/dRY+ssZ5OaekeMsUCLoGhX68yA==", "dev": true, "requires": { "@ethersproject/transactions": "^5.6.2", - "web3-core-helpers": "1.7.5", - "web3-core-promievent": "1.7.5", - "web3-core-subscriptions": "1.7.5", - "web3-utils": "1.7.5" + "web3-core-helpers": "1.8.1", + "web3-core-promievent": "1.8.1", + "web3-core-subscriptions": "1.8.1", + "web3-utils": "1.8.1" } }, "web3-core-promievent": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/web3-core-promievent/-/web3-core-promievent-1.7.5.tgz", - "integrity": "sha512-uZ1VRErVuhiLtHlyt3oEH/JSvAf6bWPndChHR9PG7i1Zfqm6ZVCeM91ICTPmiL8ddsGQOxASpnJk4vhApcTIww==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-core-promievent/-/web3-core-promievent-1.8.1.tgz", + "integrity": "sha512-9mxqHlgB0MrZI4oUIRFkuoJMNj3E7btjrMv3sMer/Z9rYR1PfoSc1aAokw4rxKIcAh+ylVtd/acaB2HKB7aRPg==", "dev": true, "requires": { "eventemitter3": "4.0.4" } }, "web3-core-requestmanager": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/web3-core-requestmanager/-/web3-core-requestmanager-1.7.5.tgz", - "integrity": "sha512-3KpfxW/wVH4mgwWEsSJGHKrtRVoijWlDxtUrm17xgtqRNZ2mFolifKnHAUKa0fY48C9CrxmcCiMIi3W4G6WYRw==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-core-requestmanager/-/web3-core-requestmanager-1.8.1.tgz", + "integrity": "sha512-x+VC2YPPwZ1khvqA6TA69LvfFCOZXsoUVOxmTx/vIN22PrY9KzKhxcE7pBSiGhmab1jtmRYXUbcQSVpAXqL8cw==", "dev": true, "requires": { "util": "^0.12.0", - "web3-core-helpers": "1.7.5", - "web3-providers-http": "1.7.5", - "web3-providers-ipc": "1.7.5", - "web3-providers-ws": "1.7.5" + "web3-core-helpers": "1.8.1", + "web3-providers-http": "1.8.1", + "web3-providers-ipc": "1.8.1", + "web3-providers-ws": "1.8.1" } }, "web3-core-subscriptions": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/web3-core-subscriptions/-/web3-core-subscriptions-1.7.5.tgz", - "integrity": "sha512-YK6utQ7Wwjbe4XZOIA8quWGBPi1lFDS1A+jQYwxKKrCvm6BloBNc3FhvrcSYlDhLe/kOy8+2Je8i9amndgT4ww==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-core-subscriptions/-/web3-core-subscriptions-1.8.1.tgz", + "integrity": "sha512-bmCMq5OeA3E2vZUh8Js1HcJbhwtsE+yeMqGC4oIZB3XsL5SLqyKLB/pU+qUYqQ9o4GdcrFTDPhPg1bgvf7p1Pw==", "dev": true, "requires": { "eventemitter3": "4.0.4", - "web3-core-helpers": "1.7.5" + "web3-core-helpers": "1.8.1" } }, "web3-eth": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/web3-eth/-/web3-eth-1.7.5.tgz", - "integrity": "sha512-BucjvqZyDWYkGlsFX+OnOBub0YutlC1KZiNGibdmvtNX0NQK+8iw1uzAoL9yTTwCSszL7lnkFe8N+HCOl9B4Dw==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-eth/-/web3-eth-1.8.1.tgz", + "integrity": "sha512-LgyzbhFqiFRd8M8sBXoFN4ztzOnkeckl3H/9lH5ek7AdoRMhBg7tYpYRP3E5qkhd/q+yiZmcUgy1AF6NHrC1wg==", "dev": true, "requires": { - "web3-core": "1.7.5", - "web3-core-helpers": "1.7.5", - "web3-core-method": "1.7.5", - "web3-core-subscriptions": "1.7.5", - "web3-eth-abi": "1.7.5", - "web3-eth-accounts": "1.7.5", - "web3-eth-contract": "1.7.5", - "web3-eth-ens": "1.7.5", - "web3-eth-iban": "1.7.5", - "web3-eth-personal": "1.7.5", - "web3-net": "1.7.5", - "web3-utils": "1.7.5" + "web3-core": "1.8.1", + "web3-core-helpers": "1.8.1", + "web3-core-method": "1.8.1", + "web3-core-subscriptions": "1.8.1", + "web3-eth-abi": "1.8.1", + "web3-eth-accounts": "1.8.1", + "web3-eth-contract": "1.8.1", + "web3-eth-ens": "1.8.1", + "web3-eth-iban": "1.8.1", + "web3-eth-personal": "1.8.1", + "web3-net": "1.8.1", + "web3-utils": "1.8.1" } }, "web3-eth-abi": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/web3-eth-abi/-/web3-eth-abi-1.7.5.tgz", - "integrity": "sha512-qWHvF7sayxql9BD1yqK9sZRLBQ66eJzGeaU53Y1PRq2iFPrhY6NUWxQ3c3ps0rg+dyObvRbloviWpKXcS4RE/A==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-eth-abi/-/web3-eth-abi-1.8.1.tgz", + "integrity": "sha512-0mZvCRTIG0UhDhJwNQJgJxu4b4DyIpuMA0GTfqxqeuqzX4Q/ZvmoNurw0ExTfXaGPP82UUmmdkRi6FdZOx+C6w==", "dev": true, "requires": { "@ethersproject/abi": "^5.6.3", - "web3-utils": "1.7.5" + "web3-utils": "1.8.1" } }, "web3-eth-accounts": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/web3-eth-accounts/-/web3-eth-accounts-1.7.5.tgz", - "integrity": "sha512-AzMLoTj3RGwKpyp3x3TtHrEeU4VpR99iMOD6NKrWSDumS6QEi0lCo+y7QZhdTlINw3iIA3SFIdvbAOO4NCHSDg==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-eth-accounts/-/web3-eth-accounts-1.8.1.tgz", + "integrity": "sha512-mgzxSYgN54/NsOFBO1Fq1KkXp1S5KlBvI/DlgvajU72rupoFMq6Cu6Yp9GUaZ/w2ij9PzEJuFJk174XwtfMCmg==", "dev": true, "requires": { - "@ethereumjs/common": "^2.5.0", - "@ethereumjs/tx": "^3.3.2", + "@ethereumjs/common": "2.5.0", + "@ethereumjs/tx": "3.3.2", "crypto-browserify": "3.12.0", "eth-lib": "0.2.8", "ethereumjs-util": "^7.0.10", "scrypt-js": "^3.0.1", - "uuid": "3.3.2", - "web3-core": "1.7.5", - "web3-core-helpers": "1.7.5", - "web3-core-method": "1.7.5", - "web3-utils": "1.7.5" + "uuid": "^9.0.0", + "web3-core": "1.8.1", + "web3-core-helpers": "1.8.1", + "web3-core-method": "1.8.1", + "web3-utils": "1.8.1" }, "dependencies": { "eth-lib": { @@ -12009,53 +25945,53 @@ } }, "uuid": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", "dev": true } } }, "web3-eth-contract": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/web3-eth-contract/-/web3-eth-contract-1.7.5.tgz", - "integrity": "sha512-qab7NPJRKRlTs58ozsqK8YIEwWpxIm3vD/okSIKBGkFx5gIHWW+vGmMh5PDSfefLJM9rCd+T+Lc0LYvtME7uqg==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-eth-contract/-/web3-eth-contract-1.8.1.tgz", + "integrity": "sha512-1wphnl+/xwCE2io44JKnN+ti3oa47BKRiVzvWd42icwRbcpFfRxH9QH+aQX3u8VZIISNH7dAkTWpGIIJgGFTmg==", "dev": true, "requires": { "@types/bn.js": "^5.1.0", - "web3-core": "1.7.5", - "web3-core-helpers": "1.7.5", - "web3-core-method": "1.7.5", - "web3-core-promievent": "1.7.5", - "web3-core-subscriptions": "1.7.5", - "web3-eth-abi": "1.7.5", - "web3-utils": "1.7.5" + "web3-core": "1.8.1", + "web3-core-helpers": "1.8.1", + "web3-core-method": "1.8.1", + "web3-core-promievent": "1.8.1", + "web3-core-subscriptions": "1.8.1", + "web3-eth-abi": "1.8.1", + "web3-utils": "1.8.1" } }, "web3-eth-ens": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/web3-eth-ens/-/web3-eth-ens-1.7.5.tgz", - "integrity": "sha512-k1Q0msdRv/wac2egpZBIwG3n/sa/KdrVmVJvFm471gLTL4xfUizV5qJjkDVf+ikf9JyDvWJTs5eWNUUbOFIw/A==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-eth-ens/-/web3-eth-ens-1.8.1.tgz", + "integrity": "sha512-FT8xTI9uN8RxeBQa/W8pLa2aoFh4+EE34w7W2271LICKzla1dtLyb6XSdn48vsUcPmhWsTVk9mO9RTU0l4LGQQ==", "dev": true, "requires": { "content-hash": "^2.5.2", "eth-ens-namehash": "2.0.8", - "web3-core": "1.7.5", - "web3-core-helpers": "1.7.5", - "web3-core-promievent": "1.7.5", - "web3-eth-abi": "1.7.5", - "web3-eth-contract": "1.7.5", - "web3-utils": "1.7.5" + "web3-core": "1.8.1", + "web3-core-helpers": "1.8.1", + "web3-core-promievent": "1.8.1", + "web3-eth-abi": "1.8.1", + "web3-eth-contract": "1.8.1", + "web3-utils": "1.8.1" } }, "web3-eth-iban": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/web3-eth-iban/-/web3-eth-iban-1.7.5.tgz", - "integrity": "sha512-mn2W5t/1IpL8OZvzAabLKT4kvwRnZSJ9K0tctndl9sDNWkfITYQibEEhUaNNA50Q5fJKgVudHI/m0gwIVTyG8Q==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-eth-iban/-/web3-eth-iban-1.8.1.tgz", + "integrity": "sha512-DomoQBfvIdtM08RyMGkMVBOH0vpOIxSSQ+jukWk/EkMLGMWJtXw/K2c2uHAeq3L/VPWNB7zXV2DUEGV/lNE2Dg==", "dev": true, "requires": { "bn.js": "^5.2.1", - "web3-utils": "1.7.5" + "web3-utils": "1.8.1" }, "dependencies": { "bn.js": { @@ -12067,17 +26003,17 @@ } }, "web3-eth-personal": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/web3-eth-personal/-/web3-eth-personal-1.7.5.tgz", - "integrity": "sha512-txh2P/eN8I4AOUKFi9++KKddoD0tWfCuu9Y1Kc41jSRbk6smO88Fum0KWNmYFYhSCX2qiknS1DfqsONl3igoKQ==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-eth-personal/-/web3-eth-personal-1.8.1.tgz", + "integrity": "sha512-myIYMvj7SDIoV9vE5BkVdon3pya1WinaXItugoii2VoTcQNPOtBxmYVH+XS5ErzCJlnxzphpQrkywyY64bbbCA==", "dev": true, "requires": { "@types/node": "^12.12.6", - "web3-core": "1.7.5", - "web3-core-helpers": "1.7.5", - "web3-core-method": "1.7.5", - "web3-net": "1.7.5", - "web3-utils": "1.7.5" + "web3-core": "1.8.1", + "web3-core-helpers": "1.8.1", + "web3-core-method": "1.8.1", + "web3-net": "1.8.1", + "web3-utils": "1.8.1" }, "dependencies": { "@types/node": { @@ -12089,65 +26025,65 @@ } }, "web3-net": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/web3-net/-/web3-net-1.7.5.tgz", - "integrity": "sha512-xwuCb2YWw49PmW81AJQ/G+Xi2ikRsYyZXSgyPt4LmZuKjiqg/6kSdK8lZvUi3Pi3wM+QDBXbpr73M/WEkW0KvA==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-net/-/web3-net-1.8.1.tgz", + "integrity": "sha512-LyEJAwogdFo0UAXZqoSJGFjopdt+kLw0P00FSZn2yszbgcoI7EwC+nXiOsEe12xz4LqpYLOtbR7+gxgiTVjjHQ==", "dev": true, "requires": { - "web3-core": "1.7.5", - "web3-core-method": "1.7.5", - "web3-utils": "1.7.5" + "web3-core": "1.8.1", + "web3-core-method": "1.8.1", + "web3-utils": "1.8.1" } }, "web3-providers-http": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/web3-providers-http/-/web3-providers-http-1.7.5.tgz", - "integrity": "sha512-vPgr4Kzy0M3CHtoP/Bh7qwK/D9h2fhjpoqctdMWVJseOfeTgfOphCKN0uwV8w2VpZgDPXA8aeTdBx5OjmDdStA==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-providers-http/-/web3-providers-http-1.8.1.tgz", + "integrity": "sha512-1Zyts4O9W/UNEPkp+jyL19Jc3D15S4yp8xuLTjVhcUEAlHo24NDWEKxtZGUuHk4HrKL2gp8OlsDbJ7MM+ESDgg==", "dev": true, "requires": { "abortcontroller-polyfill": "^1.7.3", "cross-fetch": "^3.1.4", "es6-promise": "^4.2.8", - "web3-core-helpers": "1.7.5" + "web3-core-helpers": "1.8.1" } }, "web3-providers-ipc": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/web3-providers-ipc/-/web3-providers-ipc-1.7.5.tgz", - "integrity": "sha512-aNHx+RAROzO+apDEzy8Zncj78iqWBadIXtpmFDg7uiTn8i+oO+IcP1Yni7jyzkltsysVJHgHWG4kPx50ANCK3Q==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-providers-ipc/-/web3-providers-ipc-1.8.1.tgz", + "integrity": "sha512-nw/W5nclvi+P2z2dYkLWReKLnocStflWqFl+qjtv0xn3MrUTyXMzSF0+61i77+16xFsTgzo4wS/NWIOVkR0EFA==", "dev": true, "requires": { "oboe": "2.1.5", - "web3-core-helpers": "1.7.5" + "web3-core-helpers": "1.8.1" } }, "web3-providers-ws": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/web3-providers-ws/-/web3-providers-ws-1.7.5.tgz", - "integrity": "sha512-9uJNVVkIGC8PmM9kNbgPth56HDMSSsxZh3ZEENdwO3LNWemaADiQYUDCsD/dMVkn0xsGLHP5dgAy4Q5msqySLg==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-providers-ws/-/web3-providers-ws-1.8.1.tgz", + "integrity": "sha512-TNefIDAMpdx57+YdWpYZ/xdofS0P+FfKaDYXhn24ie/tH9G+AB+UBSOKnjN0KSadcRSCMBwGPRiEmNHPavZdsA==", "dev": true, "requires": { "eventemitter3": "4.0.4", - "web3-core-helpers": "1.7.5", + "web3-core-helpers": "1.8.1", "websocket": "^1.0.32" } }, "web3-shh": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/web3-shh/-/web3-shh-1.7.5.tgz", - "integrity": "sha512-aCIWJyLMH5H76OybU4ZpUCJ93yNOPATGhJ+KboRPU8QZDzS2CcVhtEzyl27bbvw+rSnVroMLqBgTXBB4mmKI7A==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-shh/-/web3-shh-1.8.1.tgz", + "integrity": "sha512-sqHgarnfcY2Qt3PYS4R6YveHrDy7hmL09yeLLHHCI+RKirmjLVqV0rc5LJWUtlbYI+kDoa5gbgde489M9ZAC0g==", "dev": true, "requires": { - "web3-core": "1.7.5", - "web3-core-method": "1.7.5", - "web3-core-subscriptions": "1.7.5", - "web3-net": "1.7.5" + "web3-core": "1.8.1", + "web3-core-method": "1.8.1", + "web3-core-subscriptions": "1.8.1", + "web3-net": "1.8.1" } }, "web3-utils": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.7.5.tgz", - "integrity": "sha512-9AqNOziQky4wNQadEwEfHiBdOZqopIHzQQVzmvvv6fJwDSMhP+khqmAZC7YTiGjs0MboyZ8tWNivqSO1699XQw==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.8.1.tgz", + "integrity": "sha512-LgnM9p6V7rHHUGfpMZod+NST8cRfGzJ1BTXAyNo7A9cJX9LczBfSRxJp+U/GInYe9mby40t3v22AJdlELibnsQ==", "dev": true, "requires": { "bn.js": "^5.2.1", @@ -12243,17 +26179,17 @@ "dev": true }, "which-typed-array": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.8.tgz", - "integrity": "sha512-Jn4e5PItbcAHyLoRDwvPj1ypu27DJbtdYXUa5zsinrUx77Uvfb0cXwwnGMTn7cjUfhhqgVQnVJCwF+7cgU7tpw==", + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", + "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", "dev": true, "requires": { "available-typed-arrays": "^1.0.5", "call-bind": "^1.0.2", - "es-abstract": "^1.20.0", "for-each": "^0.3.3", + "gopd": "^1.0.1", "has-tostringtag": "^1.0.0", - "is-typed-array": "^1.1.9" + "is-typed-array": "^1.1.10" } }, "wide-align": { @@ -12359,7 +26295,8 @@ "version": "7.5.9", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", - "dev": true + "dev": true, + "requires": {} }, "xhr": { "version": "2.6.0", @@ -12397,15 +26334,6 @@ "xhr-request": "^1.1.0" } }, - "xhr2-cookies": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/xhr2-cookies/-/xhr2-cookies-1.1.0.tgz", - "integrity": "sha512-hjXUA6q+jl/bd8ADHcVfFsSPIf+tyLIjuO9TwJC9WI6JP2zKcS7C+p56I9kCLLsaCiNT035iYvEUUzdEFj/8+g==", - "dev": true, - "requires": { - "cookiejar": "^2.1.1" - } - }, "xmlhttprequest": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz", @@ -12437,18 +26365,18 @@ "dev": true }, "yargs": { - "version": "17.5.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", - "integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==", + "version": "17.6.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.2.tgz", + "integrity": "sha512-1/9UrdHjDZc0eOU0HxOHoS78C69UD3JRMvzlJ7S79S2nTaWRA/whGCTV8o9e/N/1Va9YIV7Q4sOxD8VV4pCWOw==", "dev": true, "requires": { - "cliui": "^7.0.2", + "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", - "yargs-parser": "^21.0.0" + "yargs-parser": "^21.1.1" }, "dependencies": { "ansi-regex": { diff --git a/package.json b/package.json index 443a52fa6..a51037840 100644 --- a/package.json +++ b/package.json @@ -76,7 +76,7 @@ "merkletreejs": "^0.2.13", "micromatch": "^4.0.2", "prettier": "^2.3.0", - "prettier-plugin-solidity": "^1.0.0-beta.16", + "prettier-plugin-solidity": "^1.1.0", "rimraf": "^3.0.2", "semver": "^7.3.5", "solhint": "^3.3.6", diff --git a/test/utils/math/Math.t.sol b/test/utils/math/Math.t.sol index c1c6f447d..5542baf9d 100644 --- a/test/utils/math/Math.t.sol +++ b/test/utils/math/Math.t.sol @@ -70,16 +70,16 @@ contract MathTest is Test { assertFalse(rounding == Math.Rounding.Up); assertTrue(_powerOf2Bigger(result + 1, input)); } else { - assertEq(2**result, input); + assertEq(2 ** result, input); } } function _powerOf2Bigger(uint256 value, uint256 ref) private pure returns (bool) { - return value >= 256 || 2**value > ref; // 2**256 overflows uint256 + return value >= 256 || 2 ** value > ref; // 2**256 overflows uint256 } function _powerOf2Smaller(uint256 value, uint256 ref) private pure returns (bool) { - return 2**value < ref; + return 2 ** value < ref; } // LOG10 @@ -97,16 +97,16 @@ contract MathTest is Test { assertFalse(rounding == Math.Rounding.Up); assertTrue(_powerOf10Bigger(result + 1, input)); } else { - assertEq(10**result, input); + assertEq(10 ** result, input); } } function _powerOf10Bigger(uint256 value, uint256 ref) private pure returns (bool) { - return value >= 78 || 10**value > ref; // 10**78 overflows uint256 + return value >= 78 || 10 ** value > ref; // 10**78 overflows uint256 } function _powerOf10Smaller(uint256 value, uint256 ref) private pure returns (bool) { - return 10**value < ref; + return 10 ** value < ref; } // LOG256 @@ -124,24 +124,20 @@ contract MathTest is Test { assertFalse(rounding == Math.Rounding.Up); assertTrue(_powerOf256Bigger(result + 1, input)); } else { - assertEq(256**result, input); + assertEq(256 ** result, input); } } function _powerOf256Bigger(uint256 value, uint256 ref) private pure returns (bool) { - return value >= 32 || 256**value > ref; // 256**32 overflows uint256 + return value >= 32 || 256 ** value > ref; // 256**32 overflows uint256 } function _powerOf256Smaller(uint256 value, uint256 ref) private pure returns (bool) { - return 256**value < ref; + return 256 ** value < ref; } // MULDIV - function testMulDiv( - uint256 x, - uint256 y, - uint256 d - ) public { + function testMulDiv(uint256 x, uint256 y, uint256 d) public { // Full precision for x * y (uint256 xyHi, uint256 xyLo) = _mulHighLow(x, y); @@ -163,11 +159,7 @@ contract MathTest is Test { assertEq(xyLo, qdRemLo); } - function testMulDivDomain( - uint256 x, - uint256 y, - uint256 d - ) public { + function testMulDivDomain(uint256 x, uint256 y, uint256 d) public { (uint256 xyHi, ) = _mulHighLow(x, y); // Violate {testMulDiv} assumption (covers d is 0 and result overflow) @@ -180,11 +172,7 @@ contract MathTest is Test { } // External call - function muldiv( - uint256 x, - uint256 y, - uint256 d - ) external pure returns (uint256) { + function muldiv(uint256 x, uint256 y, uint256 d) external pure returns (uint256) { return Math.mulDiv(x, y, d); } @@ -194,11 +182,7 @@ contract MathTest is Test { return Math.Rounding(r); } - function _mulmod( - uint256 x, - uint256 y, - uint256 z - ) private pure returns (uint256 r) { + function _mulmod(uint256 x, uint256 y, uint256 z) private pure returns (uint256 r) { assembly { r := mulmod(x, y, z) } From 97bba5adaaf7388693f1c89529868d072a5ff9da Mon Sep 17 00:00:00 2001 From: JulissaDantes Date: Tue, 10 Jan 2023 05:13:05 -0500 Subject: [PATCH 010/182] Remove ERC1155 hooks (#3876) Co-authored-by: Hadrien Croubois Co-authored-by: Francisco --- CHANGELOG.md | 2 +- contracts/mocks/ERC1155PausableMock.sol | 5 +- contracts/mocks/ERC1155SupplyMock.sol | 5 +- contracts/token/ERC1155/ERC1155.sol | 238 +++++------------- .../ERC1155/extensions/ERC1155Pausable.sol | 8 +- .../ERC1155/extensions/ERC1155Supply.sol | 8 +- test/token/ERC1155/ERC1155.test.js | 6 +- 7 files changed, 79 insertions(+), 193 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 288cda55b..a31fb22c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ * Removed presets in favor of [OpenZeppelin Contracts Wizard](https://wizard.openzeppelin.com/). * `TransparentUpgradeableProxy`: Removed `admin` and `implementation` getters, which were only callable by the proxy owner and thus not very useful. ([#3820](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3820)) * `ProxyAdmin`: Removed `getProxyAdmin` and `getProxyImplementation` getters. ([#3820](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3820)) - * `ERC20`: Deleted `_beforeTokenTransfer` and `_afterTokenTransfer` hooks, added a new internal `_update` function for customizations, and refactored all extensions using those hooks to use `_update` instead. ([#3838](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3838)) + * `ERC20`, `ERC1155`: Deleted `_beforeTokenTransfer` and `_afterTokenTransfer` hooks, added a new internal `_update` function for customizations, and refactored all extensions using those hooks to use `_update` instead. ([#3838](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3838), [#3876](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3876)) * `ERC165Storage`: Removed this contract in favor of inheritance based approach. ([#3880](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3880)) ### How to upgrade from 4.x diff --git a/contracts/mocks/ERC1155PausableMock.sol b/contracts/mocks/ERC1155PausableMock.sol index cd068234f..501ed3d1f 100644 --- a/contracts/mocks/ERC1155PausableMock.sol +++ b/contracts/mocks/ERC1155PausableMock.sol @@ -16,14 +16,13 @@ contract ERC1155PausableMock is ERC1155Mock, ERC1155Pausable { _unpause(); } - function _beforeTokenTransfer( - address operator, + function _update( address from, address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data ) internal override(ERC1155, ERC1155Pausable) { - super._beforeTokenTransfer(operator, from, to, ids, amounts, data); + super._update(from, to, ids, amounts, data); } } diff --git a/contracts/mocks/ERC1155SupplyMock.sol b/contracts/mocks/ERC1155SupplyMock.sol index 9c0cd7b63..a7421f045 100644 --- a/contracts/mocks/ERC1155SupplyMock.sol +++ b/contracts/mocks/ERC1155SupplyMock.sol @@ -8,14 +8,13 @@ import "../token/ERC1155/extensions/ERC1155Supply.sol"; contract ERC1155SupplyMock is ERC1155Mock, ERC1155Supply { constructor(string memory uri) ERC1155Mock(uri) {} - function _beforeTokenTransfer( - address operator, + function _update( address from, address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data ) internal override(ERC1155, ERC1155Supply) { - super._beforeTokenTransfer(operator, from, to, ids, amounts, data); + super._update(from, to, ids, amounts, data); } } diff --git a/contracts/token/ERC1155/ERC1155.sol b/contracts/token/ERC1155/ERC1155.sol index b20b711d5..e491a2310 100644 --- a/contracts/token/ERC1155/ERC1155.sol +++ b/contracts/token/ERC1155/ERC1155.sol @@ -143,44 +143,76 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { } /** - * @dev Transfers `amount` tokens of token type `id` from `from` to `to`. + * @dev Transfers `amount` tokens of token type `id` from `from` to `to`. Will mint (or burn) if `from` (or `to`) is the zero address. * - * Emits a {TransferSingle} event. + * Emits a {TransferSingle} event if the arrays contain one element, and {TransferBatch} otherwise. * * Requirements: * - * - `to` cannot be the zero address. - * - `from` must have a balance of tokens of type `id` of at least `amount`. - * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the - * acceptance magic value. + * - If `to` refers to a smart contract, it must implement either {IERC1155Receiver-onERC1155Received} + * or {IERC1155Receiver-onERC1155BatchReceived} and return the acceptance magic value. */ - function _safeTransferFrom( + function _update( address from, address to, - uint256 id, - uint256 amount, + uint256[] memory ids, + uint256[] memory amounts, bytes memory data ) internal virtual { - require(to != address(0), "ERC1155: transfer to the zero address"); + require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch"); address operator = _msgSender(); - uint256[] memory ids = _asSingletonArray(id); - uint256[] memory amounts = _asSingletonArray(amount); - _beforeTokenTransfer(operator, from, to, ids, amounts, data); + for (uint256 i = 0; i < ids.length; ++i) { + uint256 id = ids[i]; + uint256 amount = amounts[i]; - uint256 fromBalance = _balances[id][from]; - require(fromBalance >= amount, "ERC1155: insufficient balance for transfer"); - unchecked { - _balances[id][from] = fromBalance - amount; - } - _balances[id][to] += amount; + if (from != address(0)) { + uint256 fromBalance = _balances[id][from]; + require(fromBalance >= amount, "ERC1155: insufficient balance for transfer"); + unchecked { + _balances[id][from] = fromBalance - amount; + } + } - emit TransferSingle(operator, from, to, id, amount); + if (to != address(0)) { + _balances[id][to] += amount; + } + } - _afterTokenTransfer(operator, from, to, ids, amounts, data); + if (ids.length == 1) { + uint256 id = ids[0]; + uint256 amount = amounts[0]; + emit TransferSingle(operator, from, to, id, amount); + if (to != address(0)) { + _doSafeTransferAcceptanceCheck(operator, from, to, id, amount, data); + } + } else { + emit TransferBatch(operator, from, to, ids, amounts); + if (to != address(0)) { + _doSafeBatchTransferAcceptanceCheck(operator, from, to, ids, amounts, data); + } + } + } - _doSafeTransferAcceptanceCheck(operator, from, to, id, amount, data); + /** + * @dev Transfers `amount` tokens of token type `id` from `from` to `to`. + * + * Emits a {TransferSingle} event. + * + * Requirements: + * + * - `to` cannot be the zero address. + * - `from` must have a balance of tokens of type `id` of at least `amount`. + * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the + * acceptance magic value. + */ + function _safeTransferFrom(address from, address to, uint256 id, uint256 amount, bytes memory data) internal { + require(to != address(0), "ERC1155: transfer to the zero address"); + require(from != address(0), "ERC1155: transfer from the zero address"); + uint256[] memory ids = _asSingletonArray(id); + uint256[] memory amounts = _asSingletonArray(amount); + _update(from, to, ids, amounts, data); } /** @@ -199,31 +231,10 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { uint256[] memory ids, uint256[] memory amounts, bytes memory data - ) internal virtual { - require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch"); + ) internal { require(to != address(0), "ERC1155: transfer to the zero address"); - - address operator = _msgSender(); - - _beforeTokenTransfer(operator, from, to, ids, amounts, data); - - for (uint256 i = 0; i < ids.length; ++i) { - uint256 id = ids[i]; - uint256 amount = amounts[i]; - - uint256 fromBalance = _balances[id][from]; - require(fromBalance >= amount, "ERC1155: insufficient balance for transfer"); - unchecked { - _balances[id][from] = fromBalance - amount; - } - _balances[id][to] += amount; - } - - emit TransferBatch(operator, from, to, ids, amounts); - - _afterTokenTransfer(operator, from, to, ids, amounts, data); - - _doSafeBatchTransferAcceptanceCheck(operator, from, to, ids, amounts, data); + require(from != address(0), "ERC1155: transfer from the zero address"); + _update(from, to, ids, amounts, data); } /** @@ -260,21 +271,11 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the * acceptance magic value. */ - function _mint(address to, uint256 id, uint256 amount, bytes memory data) internal virtual { + function _mint(address to, uint256 id, uint256 amount, bytes memory data) internal { require(to != address(0), "ERC1155: mint to the zero address"); - - address operator = _msgSender(); uint256[] memory ids = _asSingletonArray(id); uint256[] memory amounts = _asSingletonArray(amount); - - _beforeTokenTransfer(operator, address(0), to, ids, amounts, data); - - _balances[id][to] += amount; - emit TransferSingle(operator, address(0), to, id, amount); - - _afterTokenTransfer(operator, address(0), to, ids, amounts, data); - - _doSafeTransferAcceptanceCheck(operator, address(0), to, id, amount, data); + _update(address(0), to, ids, amounts, data); } /** @@ -288,28 +289,9 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the * acceptance magic value. */ - function _mintBatch( - address to, - uint256[] memory ids, - uint256[] memory amounts, - bytes memory data - ) internal virtual { + function _mintBatch(address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data) internal { require(to != address(0), "ERC1155: mint to the zero address"); - require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch"); - - address operator = _msgSender(); - - _beforeTokenTransfer(operator, address(0), to, ids, amounts, data); - - for (uint256 i = 0; i < ids.length; i++) { - _balances[ids[i]][to] += amounts[i]; - } - - emit TransferBatch(operator, address(0), to, ids, amounts); - - _afterTokenTransfer(operator, address(0), to, ids, amounts, data); - - _doSafeBatchTransferAcceptanceCheck(operator, address(0), to, ids, amounts, data); + _update(address(0), to, ids, amounts, data); } /** @@ -322,24 +304,11 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { * - `from` cannot be the zero address. * - `from` must have at least `amount` tokens of token type `id`. */ - function _burn(address from, uint256 id, uint256 amount) internal virtual { + function _burn(address from, uint256 id, uint256 amount) internal { require(from != address(0), "ERC1155: burn from the zero address"); - - address operator = _msgSender(); uint256[] memory ids = _asSingletonArray(id); uint256[] memory amounts = _asSingletonArray(amount); - - _beforeTokenTransfer(operator, from, address(0), ids, amounts, ""); - - uint256 fromBalance = _balances[id][from]; - require(fromBalance >= amount, "ERC1155: burn amount exceeds balance"); - unchecked { - _balances[id][from] = fromBalance - amount; - } - - emit TransferSingle(operator, from, address(0), id, amount); - - _afterTokenTransfer(operator, from, address(0), ids, amounts, ""); + _update(from, address(0), ids, amounts, ""); } /** @@ -351,28 +320,9 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { * * - `ids` and `amounts` must have the same length. */ - function _burnBatch(address from, uint256[] memory ids, uint256[] memory amounts) internal virtual { + function _burnBatch(address from, uint256[] memory ids, uint256[] memory amounts) internal { require(from != address(0), "ERC1155: burn from the zero address"); - require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch"); - - address operator = _msgSender(); - - _beforeTokenTransfer(operator, from, address(0), ids, amounts, ""); - - for (uint256 i = 0; i < ids.length; i++) { - uint256 id = ids[i]; - uint256 amount = amounts[i]; - - uint256 fromBalance = _balances[id][from]; - require(fromBalance >= amount, "ERC1155: burn amount exceeds balance"); - unchecked { - _balances[id][from] = fromBalance - amount; - } - } - - emit TransferBatch(operator, from, address(0), ids, amounts); - - _afterTokenTransfer(operator, from, address(0), ids, amounts, ""); + _update(from, address(0), ids, amounts, ""); } /** @@ -386,64 +336,6 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { emit ApprovalForAll(owner, operator, approved); } - /** - * @dev Hook that is called before any token transfer. This includes minting - * and burning, as well as batched variants. - * - * The same hook is called on both single and batched variants. For single - * transfers, the length of the `ids` and `amounts` arrays will be 1. - * - * Calling conditions (for each `id` and `amount` pair): - * - * - When `from` and `to` are both non-zero, `amount` of ``from``'s tokens - * of token type `id` will be transferred to `to`. - * - When `from` is zero, `amount` tokens of token type `id` will be minted - * for `to`. - * - when `to` is zero, `amount` of ``from``'s tokens of token type `id` - * will be burned. - * - `from` and `to` are never both zero. - * - `ids` and `amounts` have the same, non-zero length. - * - * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - */ - function _beforeTokenTransfer( - address operator, - address from, - address to, - uint256[] memory ids, - uint256[] memory amounts, - bytes memory data - ) internal virtual {} - - /** - * @dev Hook that is called after any token transfer. This includes minting - * and burning, as well as batched variants. - * - * The same hook is called on both single and batched variants. For single - * transfers, the length of the `id` and `amount` arrays will be 1. - * - * Calling conditions (for each `id` and `amount` pair): - * - * - When `from` and `to` are both non-zero, `amount` of ``from``'s tokens - * of token type `id` will be transferred to `to`. - * - When `from` is zero, `amount` tokens of token type `id` will be minted - * for `to`. - * - when `to` is zero, `amount` of ``from``'s tokens of token type `id` - * will be burned. - * - `from` and `to` are never both zero. - * - `ids` and `amounts` have the same, non-zero length. - * - * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - */ - function _afterTokenTransfer( - address operator, - address from, - address to, - uint256[] memory ids, - uint256[] memory amounts, - bytes memory data - ) internal virtual {} - function _doSafeTransferAcceptanceCheck( address operator, address from, diff --git a/contracts/token/ERC1155/extensions/ERC1155Pausable.sol b/contracts/token/ERC1155/extensions/ERC1155Pausable.sol index 64790e2aa..f37d90e25 100644 --- a/contracts/token/ERC1155/extensions/ERC1155Pausable.sol +++ b/contracts/token/ERC1155/extensions/ERC1155Pausable.sol @@ -17,22 +17,20 @@ import "../../../security/Pausable.sol"; */ abstract contract ERC1155Pausable is ERC1155, Pausable { /** - * @dev See {ERC1155-_beforeTokenTransfer}. + * @dev See {ERC1155-_update}. * * Requirements: * * - the contract must not be paused. */ - function _beforeTokenTransfer( - address operator, + function _update( address from, address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data ) internal virtual override { - super._beforeTokenTransfer(operator, from, to, ids, amounts, data); - require(!paused(), "ERC1155Pausable: token transfer while paused"); + super._update(from, to, ids, amounts, data); } } diff --git a/contracts/token/ERC1155/extensions/ERC1155Supply.sol b/contracts/token/ERC1155/extensions/ERC1155Supply.sol index ec24389a1..77690b59d 100644 --- a/contracts/token/ERC1155/extensions/ERC1155Supply.sol +++ b/contracts/token/ERC1155/extensions/ERC1155Supply.sol @@ -31,18 +31,15 @@ abstract contract ERC1155Supply is ERC1155 { } /** - * @dev See {ERC1155-_beforeTokenTransfer}. + * @dev See {ERC1155-_update}. */ - function _beforeTokenTransfer( - address operator, + function _update( address from, address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data ) internal virtual override { - super._beforeTokenTransfer(operator, from, to, ids, amounts, data); - if (from == address(0)) { for (uint256 i = 0; i < ids.length; ++i) { _totalSupply[ids[i]] += amounts[i]; @@ -60,5 +57,6 @@ abstract contract ERC1155Supply is ERC1155 { } } } + super._update(from, to, ids, amounts, data); } } diff --git a/test/token/ERC1155/ERC1155.test.js b/test/token/ERC1155/ERC1155.test.js index a0a8cf3ff..2f145de5d 100644 --- a/test/token/ERC1155/ERC1155.test.js +++ b/test/token/ERC1155/ERC1155.test.js @@ -120,7 +120,7 @@ contract('ERC1155', function (accounts) { it('reverts when burning a non-existent token id', async function () { await expectRevert( this.token.burn(tokenHolder, tokenId, mintAmount), - 'ERC1155: burn amount exceeds balance', + 'ERC1155: insufficient balance for transfer', ); }); @@ -135,7 +135,7 @@ contract('ERC1155', function (accounts) { await expectRevert( this.token.burn(tokenHolder, tokenId, mintAmount.addn(1)), - 'ERC1155: burn amount exceeds balance', + 'ERC1155: insufficient balance for transfer', ); }); @@ -192,7 +192,7 @@ contract('ERC1155', function (accounts) { it('reverts when burning a non-existent token id', async function () { await expectRevert( this.token.burnBatch(tokenBatchHolder, tokenBatchIds, burnAmounts), - 'ERC1155: burn amount exceeds balance', + 'ERC1155: insufficient balance for transfer', ); }); From a290e13099ea2314de19ee7d68da66c1c544ed3c Mon Sep 17 00:00:00 2001 From: Francisco Date: Tue, 10 Jan 2023 07:15:22 -0300 Subject: [PATCH 011/182] Add warning in next-v5.0 branch readme (#3941) --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index a30fd6c3f..7cf25747d 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ +> **Warning** +> Version 5.0 is under active development. The code in this branch is not recommended for use. + # OpenZeppelin [![Docs](https://img.shields.io/badge/docs-%F0%9F%93%84-blue)](https://docs.openzeppelin.com/contracts) From d210847e28e7f81265155e465b139cdda50d0c21 Mon Sep 17 00:00:00 2001 From: JulissaDantes Date: Tue, 10 Jan 2023 15:21:35 -0500 Subject: [PATCH 012/182] Fix ERC20._update (#3921) Co-authored-by: Francisco Co-authored-by: Hadrien Croubois --- contracts/mocks/ERC20Mock.sol | 5 +++ contracts/token/ERC20/ERC20.sol | 22 +++++---- .../token/ERC20/extensions/ERC20Capped.sol | 2 +- .../token/ERC20/extensions/ERC20Snapshot.sol | 11 +++-- test/token/ERC20/ERC20.test.js | 45 ++++++++++++++++++- .../extensions/ERC20Burnable.behavior.js | 4 +- .../ERC20/extensions/ERC20FlashMint.test.js | 2 +- .../ERC20/extensions/ERC20Wrapper.test.js | 2 +- 8 files changed, 69 insertions(+), 24 deletions(-) diff --git a/contracts/mocks/ERC20Mock.sol b/contracts/mocks/ERC20Mock.sol index 16ffad19f..2530eafc2 100644 --- a/contracts/mocks/ERC20Mock.sol +++ b/contracts/mocks/ERC20Mock.sol @@ -30,4 +30,9 @@ contract ERC20Mock is ERC20 { function approveInternal(address owner, address spender, uint256 value) public { _approve(owner, spender, value); } + + // Exposed for testing purposes + function update(address from, address to, uint256 amount) public { + _update(from, to, amount); + } } diff --git a/contracts/token/ERC20/ERC20.sol b/contracts/token/ERC20/ERC20.sol index f9be8b689..ee310e9f2 100644 --- a/contracts/token/ERC20/ERC20.sol +++ b/contracts/token/ERC20/ERC20.sol @@ -229,25 +229,23 @@ contract ERC20 is Context, IERC20, IERC20Metadata { function _update(address from, address to, uint256 amount) internal virtual { if (from == address(0)) { _totalSupply += amount; - unchecked { - // Overflow not possible: balance + amount is at most totalSupply + amount, which is checked above. - _balances[to] += amount; - } - } else if (to == address(0)) { + } else { uint256 fromBalance = _balances[from]; - require(fromBalance >= amount, "ERC20: burn amount exceeds balance"); - _totalSupply -= amount; + require(fromBalance >= amount, "ERC20: transfer amount exceeds balance"); unchecked { // Overflow not possible: amount <= fromBalance <= totalSupply. _balances[from] = fromBalance - amount; } + } + + if (to == address(0)) { + unchecked { + // Overflow not possible: amount <= totalSupply or amount <= fromBalance <= totalSupply. + _totalSupply -= amount; + } } else { - uint256 fromBalance = _balances[from]; - require(fromBalance >= amount, "ERC20: transfer amount exceeds balance"); unchecked { - _balances[from] = fromBalance - amount; - // Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by - // decrementing then incrementing. + // Overflow not possible: balance + amount is at most totalSupply, which we know fits into a uint256. _balances[to] += amount; } } diff --git a/contracts/token/ERC20/extensions/ERC20Capped.sol b/contracts/token/ERC20/extensions/ERC20Capped.sol index d80fb2a4c..b7ddd788c 100644 --- a/contracts/token/ERC20/extensions/ERC20Capped.sol +++ b/contracts/token/ERC20/extensions/ERC20Capped.sol @@ -32,7 +32,7 @@ abstract contract ERC20Capped is ERC20 { */ function _update(address from, address to, uint256 amount) internal virtual override { if (from == address(0)) { - require(ERC20.totalSupply() + amount <= cap(), "ERC20Capped: cap exceeded"); + require(totalSupply() + amount <= cap(), "ERC20Capped: cap exceeded"); } super._update(from, to, amount); diff --git a/contracts/token/ERC20/extensions/ERC20Snapshot.sol b/contracts/token/ERC20/extensions/ERC20Snapshot.sol index 3bc1a74ee..4eae931bc 100644 --- a/contracts/token/ERC20/extensions/ERC20Snapshot.sol +++ b/contracts/token/ERC20/extensions/ERC20Snapshot.sol @@ -122,18 +122,17 @@ abstract contract ERC20Snapshot is ERC20 { // for _mint, _burn, and _transfer operations. function _update(address from, address to, uint256 amount) internal virtual override { if (from == address(0)) { - // mint - _updateAccountSnapshot(to); _updateTotalSupplySnapshot(); - } else if (to == address(0)) { - // burn + } else { _updateAccountSnapshot(from); + } + + if (to == address(0)) { _updateTotalSupplySnapshot(); } else { - // transfer - _updateAccountSnapshot(from); _updateAccountSnapshot(to); } + super._update(from, to, amount); } diff --git a/test/token/ERC20/ERC20.test.js b/test/token/ERC20/ERC20.test.js index 46ee5ab63..cfe803d74 100644 --- a/test/token/ERC20/ERC20.test.js +++ b/test/token/ERC20/ERC20.test.js @@ -250,7 +250,7 @@ contract('ERC20', function (accounts) { describe('for a non zero account', function () { it('rejects burning more than balance', async function () { await expectRevert(this.token.burn( - initialHolder, initialSupply.addn(1)), 'ERC20: burn amount exceeds balance', + initialHolder, initialSupply.addn(1)), 'ERC20: transfer amount exceeds balance', ); }); @@ -287,6 +287,49 @@ contract('ERC20', function (accounts) { }); }); + describe('_update', function () { + const amount = new BN(1); + + it('from is the zero address', async function () { + const balanceBefore = await this.token.balanceOf(initialHolder); + const totalSupply = await this.token.totalSupply(); + + expectEvent( + await this.token.update(ZERO_ADDRESS, initialHolder, amount), + 'Transfer', + { from: ZERO_ADDRESS, to: initialHolder, value: amount }, + ); + expect(await this.token.totalSupply()).to.be.bignumber.equal(totalSupply.add(amount)); + expect(await this.token.balanceOf(initialHolder)).to.be.bignumber.equal(balanceBefore.add(amount)); + }); + + it('to is the zero address', async function () { + const balanceBefore = await this.token.balanceOf(initialHolder); + const totalSupply = await this.token.totalSupply(); + + expectEvent( + await this.token.update(initialHolder, ZERO_ADDRESS, amount), + 'Transfer', + { from: initialHolder, to: ZERO_ADDRESS, value: amount }, + ); + expect(await this.token.totalSupply()).to.be.bignumber.equal(totalSupply.sub(amount)); + expect(await this.token.balanceOf(initialHolder)).to.be.bignumber.equal(balanceBefore.sub(amount)); + }); + + it('from and to are the zero address', async function () { + const totalSupply = await this.token.totalSupply(); + + await this.token.update(ZERO_ADDRESS, ZERO_ADDRESS, amount); + + expect(await this.token.totalSupply()).to.be.bignumber.equal(totalSupply); + expectEvent( + await this.token.update(ZERO_ADDRESS, ZERO_ADDRESS, amount), + 'Transfer', + { from: ZERO_ADDRESS, to: ZERO_ADDRESS, value: amount }, + ); + }); + }); + describe('_transfer', function () { shouldBehaveLikeERC20Transfer('ERC20', initialHolder, recipient, initialSupply, function (from, to, amount) { return this.token.transferInternal(from, to, amount); diff --git a/test/token/ERC20/extensions/ERC20Burnable.behavior.js b/test/token/ERC20/extensions/ERC20Burnable.behavior.js index a931bf60d..80e71d1a0 100644 --- a/test/token/ERC20/extensions/ERC20Burnable.behavior.js +++ b/test/token/ERC20/extensions/ERC20Burnable.behavior.js @@ -38,7 +38,7 @@ function shouldBehaveLikeERC20Burnable (owner, initialBalance, [burner]) { it('reverts', async function () { await expectRevert(this.token.burn(amount, { from: owner }), - 'ERC20: burn amount exceeds balance', + 'ERC20: transfer amount exceeds balance', ); }); }); @@ -86,7 +86,7 @@ function shouldBehaveLikeERC20Burnable (owner, initialBalance, [burner]) { it('reverts', async function () { await this.token.approve(burner, amount, { from: owner }); await expectRevert(this.token.burnFrom(owner, amount, { from: burner }), - 'ERC20: burn amount exceeds balance', + 'ERC20: transfer amount exceeds balance', ); }); }); diff --git a/test/token/ERC20/extensions/ERC20FlashMint.test.js b/test/token/ERC20/extensions/ERC20FlashMint.test.js index 4354dd90c..ac242a6bf 100644 --- a/test/token/ERC20/extensions/ERC20FlashMint.test.js +++ b/test/token/ERC20/extensions/ERC20FlashMint.test.js @@ -82,7 +82,7 @@ contract('ERC20FlashMint', function (accounts) { const data = this.token.contract.methods.transfer(other, 10).encodeABI(); await expectRevert( this.token.flashLoan(receiver.address, this.token.address, loanAmount, data), - 'ERC20: burn amount exceeds balance', + 'ERC20: transfer amount exceeds balance', ); }); diff --git a/test/token/ERC20/extensions/ERC20Wrapper.test.js b/test/token/ERC20/extensions/ERC20Wrapper.test.js index ceb813e08..291b3615d 100644 --- a/test/token/ERC20/extensions/ERC20Wrapper.test.js +++ b/test/token/ERC20/extensions/ERC20Wrapper.test.js @@ -105,7 +105,7 @@ contract('ERC20', function (accounts) { it('missing balance', async function () { await expectRevert( this.token.withdrawTo(initialHolder, MAX_UINT256, { from: initialHolder }), - 'ERC20: burn amount exceeds balance', + 'ERC20: transfer amount exceeds balance', ); }); From 7c6e2897824728d4f056ebf33cc8b63236738c5d Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Thu, 12 Jan 2023 09:51:29 +0100 Subject: [PATCH 013/182] Prepare tests for hardhat-exposed transition (#3930) Co-authored-by: Francisco --- contracts/mocks/ERC721VotesMock.sol | 2 +- contracts/mocks/VotesMock.sol | 19 +- package-lock.json | 123 ++---- package.json | 1 + test/finance/VestingWallet.test.js | 5 +- test/governance/utils/Votes.behavior.js | 404 +++++++++--------- test/governance/utils/Votes.test.js | 60 ++- test/helpers/enums.js | 4 +- test/helpers/math.js | 11 + .../token/ERC20/extensions/ERC20Votes.test.js | 15 +- .../ERC20/extensions/ERC20VotesComp.test.js | 15 +- .../extensions/ERC721Consecutive.test.js | 16 +- .../ERC721/extensions/ERC721Votes.test.js | 62 ++- 13 files changed, 344 insertions(+), 393 deletions(-) create mode 100644 test/helpers/math.js diff --git a/contracts/mocks/ERC721VotesMock.sol b/contracts/mocks/ERC721VotesMock.sol index 2d9447428..acb51ebfb 100644 --- a/contracts/mocks/ERC721VotesMock.sol +++ b/contracts/mocks/ERC721VotesMock.sol @@ -15,7 +15,7 @@ contract ERC721VotesMock is ERC721Votes { _mint(account, tokenId); } - function burn(address, uint256 tokenId) public { + function burn(uint256 tokenId) public { _burn(tokenId); } diff --git a/contracts/mocks/VotesMock.sol b/contracts/mocks/VotesMock.sol index f080bfb72..09862ad48 100644 --- a/contracts/mocks/VotesMock.sol +++ b/contracts/mocks/VotesMock.sol @@ -5,8 +5,7 @@ pragma solidity ^0.8.0; import "../governance/utils/Votes.sol"; contract VotesMock is Votes { - mapping(address => uint256) private _balances; - mapping(uint256 => address) private _owners; + mapping(address => uint256) private _votingUnits; constructor(string memory name) EIP712(name, "1") {} @@ -19,19 +18,17 @@ contract VotesMock is Votes { } function _getVotingUnits(address account) internal view override returns (uint256) { - return _balances[account]; + return _votingUnits[account]; } - function mint(address account, uint256 voteId) external { - _balances[account] += 1; - _owners[voteId] = account; - _transferVotingUnits(address(0), account, 1); + function mint(address account, uint256 votes) external { + _votingUnits[account] += votes; + _transferVotingUnits(address(0), account, votes); } - function burn(address, uint256 voteId) external { - address owner = _owners[voteId]; - _balances[owner] -= 1; - _transferVotingUnits(owner, address(0), 1); + function burn(address account, uint256 votes) external { + _votingUnits[account] += votes; + _transferVotingUnits(account, address(0), votes); } function getChainId() external view returns (uint256) { diff --git a/package-lock.json b/package-lock.json index 8d8a2ca16..029393a1b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,8 +8,8 @@ "name": "openzeppelin-solidity", "version": "4.8.0", "license": "MIT", - "bin": { - "openzeppelin-contracts-migrate-imports": "scripts/migrate-imports.js" + "dependencies": { + "array.prototype.at": "^1.1.1" }, "devDependencies": { "@nomicfoundation/hardhat-network-helpers": "^1.0.3", @@ -2628,6 +2628,20 @@ "node": ">=0.10.0" } }, + "node_modules/array.prototype.at": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array.prototype.at/-/array.prototype.at-1.1.1.tgz", + "integrity": "sha512-n/wYNLJy/fVEU9EGPt2ww920hy1XX3XB2yTREFy1QsxctBgQV/tZIwg1G8jVxELna4pLCzg/xvvS/DDXtI4NNg==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/array.prototype.flat": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz", @@ -3241,7 +3255,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, "dependencies": { "function-bind": "^1.1.1", "get-intrinsic": "^1.0.2" @@ -4197,7 +4210,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", - "dev": true, "dependencies": { "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" @@ -4506,7 +4518,6 @@ "version": "1.20.5", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.5.tgz", "integrity": "sha512-7h8MM2EQhsCA7pU/Nv78qOXFpD8Rhqd12gYiSJVkrH9+e8VuA8JlPJK/hQjjlLv6pJvx/z1iRFKzYb0XT/RuAQ==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "es-to-primitive": "^1.2.1", @@ -4551,7 +4562,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", - "dev": true, "dependencies": { "has": "^1.0.3" } @@ -4560,7 +4570,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, "dependencies": { "is-callable": "^1.1.4", "is-date-object": "^1.0.1", @@ -6524,14 +6533,12 @@ "node_modules/function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, "node_modules/function.prototype.name": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.3", @@ -6555,7 +6562,6 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -6582,7 +6588,6 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", - "dev": true, "dependencies": { "function-bind": "^1.1.1", "has": "^1.0.3", @@ -6617,7 +6622,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "get-intrinsic": "^1.1.1" @@ -6889,7 +6893,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dev": true, "dependencies": { "get-intrinsic": "^1.1.3" }, @@ -7399,7 +7402,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, "dependencies": { "function-bind": "^1.1.1" }, @@ -7411,7 +7413,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", - "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -7429,7 +7430,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", - "dev": true, "dependencies": { "get-intrinsic": "^1.1.1" }, @@ -7441,7 +7441,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -7453,7 +7452,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "dev": true, "dependencies": { "has-symbols": "^1.0.2" }, @@ -7907,7 +7905,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.4.tgz", "integrity": "sha512-tA8URYccNzMo94s5MQZgH8NB/XTa6HsOo0MLfXTKKEnHVVdegzaQoFZ7Jp44bdvLvY2waT5dc+j5ICEswhi7UQ==", - "dev": true, "dependencies": { "get-intrinsic": "^1.1.3", "has": "^1.0.3", @@ -7979,7 +7976,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", - "dev": true, "dependencies": { "has-bigints": "^1.0.1" }, @@ -8003,7 +7999,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "has-tostringtag": "^1.0.0" @@ -8042,7 +8037,6 @@ "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -8066,7 +8060,6 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", - "dev": true, "dependencies": { "has-tostringtag": "^1.0.0" }, @@ -8160,7 +8153,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -8181,7 +8173,6 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", - "dev": true, "dependencies": { "has-tostringtag": "^1.0.0" }, @@ -8214,7 +8205,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "has-tostringtag": "^1.0.0" @@ -8230,7 +8220,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", - "dev": true, "dependencies": { "call-bind": "^1.0.2" }, @@ -8242,7 +8231,6 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", - "dev": true, "dependencies": { "has-tostringtag": "^1.0.0" }, @@ -8257,7 +8245,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "dev": true, "dependencies": { "has-symbols": "^1.0.2" }, @@ -8324,7 +8311,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", - "dev": true, "dependencies": { "call-bind": "^1.0.2" }, @@ -9724,7 +9710,6 @@ "version": "1.12.2", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", - "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -9733,7 +9718,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, "engines": { "node": ">= 0.4" } @@ -9742,7 +9726,6 @@ "version": "4.1.4", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", @@ -10622,7 +10605,6 @@ "version": "1.4.3", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.3", @@ -11033,7 +11015,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "get-intrinsic": "^1.1.3", @@ -11442,7 +11423,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dev": true, "dependencies": { "call-bind": "^1.0.0", "get-intrinsic": "^1.0.2", @@ -13235,7 +13215,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", @@ -13249,7 +13228,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", @@ -13867,7 +13845,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "has-bigints": "^1.0.2", @@ -14509,7 +14486,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dev": true, "dependencies": { "is-bigint": "^1.0.1", "is-boolean-object": "^1.1.0", @@ -16797,6 +16773,17 @@ "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==", "dev": true }, + "array.prototype.at": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array.prototype.at/-/array.prototype.at-1.1.1.tgz", + "integrity": "sha512-n/wYNLJy/fVEU9EGPt2ww920hy1XX3XB2yTREFy1QsxctBgQV/tZIwg1G8jVxELna4pLCzg/xvvS/DDXtI4NNg==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" + } + }, "array.prototype.flat": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz", @@ -17308,7 +17295,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, "requires": { "function-bind": "^1.1.1", "get-intrinsic": "^1.0.2" @@ -18079,7 +18065,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", - "dev": true, "requires": { "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" @@ -18319,7 +18304,6 @@ "version": "1.20.5", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.5.tgz", "integrity": "sha512-7h8MM2EQhsCA7pU/Nv78qOXFpD8Rhqd12gYiSJVkrH9+e8VuA8JlPJK/hQjjlLv6pJvx/z1iRFKzYb0XT/RuAQ==", - "dev": true, "requires": { "call-bind": "^1.0.2", "es-to-primitive": "^1.2.1", @@ -18358,7 +18342,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", - "dev": true, "requires": { "has": "^1.0.3" } @@ -18367,7 +18350,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, "requires": { "is-callable": "^1.1.4", "is-date-object": "^1.0.1", @@ -19964,14 +19946,12 @@ "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, "function.prototype.name": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", - "dev": true, "requires": { "call-bind": "^1.0.2", "define-properties": "^1.1.3", @@ -19988,8 +19968,7 @@ "functions-have-names": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "dev": true + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==" }, "get-caller-file": { "version": "2.0.5", @@ -20007,7 +19986,6 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", - "dev": true, "requires": { "function-bind": "^1.1.1", "has": "^1.0.3", @@ -20030,7 +20008,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", - "dev": true, "requires": { "call-bind": "^1.0.2", "get-intrinsic": "^1.1.1" @@ -20247,7 +20224,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dev": true, "requires": { "get-intrinsic": "^1.1.3" } @@ -20651,7 +20627,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, "requires": { "function-bind": "^1.1.1" } @@ -20659,8 +20634,7 @@ "has-bigints": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", - "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", - "dev": true + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==" }, "has-flag": { "version": "4.0.0", @@ -20672,7 +20646,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", - "dev": true, "requires": { "get-intrinsic": "^1.1.1" } @@ -20680,14 +20653,12 @@ "has-symbols": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" }, "has-tostringtag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "dev": true, "requires": { "has-symbols": "^1.0.2" } @@ -21045,7 +21016,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.4.tgz", "integrity": "sha512-tA8URYccNzMo94s5MQZgH8NB/XTa6HsOo0MLfXTKKEnHVVdegzaQoFZ7Jp44bdvLvY2waT5dc+j5ICEswhi7UQ==", - "dev": true, "requires": { "get-intrinsic": "^1.1.3", "has": "^1.0.3", @@ -21099,7 +21069,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", - "dev": true, "requires": { "has-bigints": "^1.0.1" } @@ -21117,7 +21086,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", - "dev": true, "requires": { "call-bind": "^1.0.2", "has-tostringtag": "^1.0.0" @@ -21132,8 +21100,7 @@ "is-callable": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dev": true + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==" }, "is-core-module": { "version": "2.11.0", @@ -21148,7 +21115,6 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", - "dev": true, "requires": { "has-tostringtag": "^1.0.0" } @@ -21213,8 +21179,7 @@ "is-negative-zero": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", - "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", - "dev": true + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==" }, "is-number": { "version": "7.0.0", @@ -21226,7 +21191,6 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", - "dev": true, "requires": { "has-tostringtag": "^1.0.0" } @@ -21247,7 +21211,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", - "dev": true, "requires": { "call-bind": "^1.0.2", "has-tostringtag": "^1.0.0" @@ -21257,7 +21220,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", - "dev": true, "requires": { "call-bind": "^1.0.2" } @@ -21266,7 +21228,6 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", - "dev": true, "requires": { "has-tostringtag": "^1.0.0" } @@ -21275,7 +21236,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "dev": true, "requires": { "has-symbols": "^1.0.2" } @@ -21324,7 +21284,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", - "dev": true, "requires": { "call-bind": "^1.0.2" } @@ -22439,20 +22398,17 @@ "object-inspect": { "version": "1.12.2", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", - "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", - "dev": true + "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==" }, "object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" }, "object.assign": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", - "dev": true, "requires": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", @@ -23121,7 +23077,6 @@ "version": "1.4.3", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", - "dev": true, "requires": { "call-bind": "^1.0.2", "define-properties": "^1.1.3", @@ -23403,7 +23358,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", - "dev": true, "requires": { "call-bind": "^1.0.2", "get-intrinsic": "^1.1.3", @@ -23739,7 +23693,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dev": true, "requires": { "call-bind": "^1.0.0", "get-intrinsic": "^1.0.2", @@ -25108,7 +25061,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", - "dev": true, "requires": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", @@ -25119,7 +25071,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", - "dev": true, "requires": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", @@ -25616,7 +25567,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", - "dev": true, "requires": { "call-bind": "^1.0.2", "has-bigints": "^1.0.2", @@ -26163,7 +26113,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dev": true, "requires": { "is-bigint": "^1.0.1", "is-boolean-object": "^1.1.0", diff --git a/package.json b/package.json index a51037840..a873c16d7 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,7 @@ "@nomiclabs/hardhat-web3": "^2.0.0", "@openzeppelin/docs-utils": "^0.1.3", "@openzeppelin/test-helpers": "^0.5.13", + "array.prototype.at": "^1.1.1", "chai": "^4.2.0", "eslint": "^7.32.0", "eslint-config-standard": "^16.0.3", diff --git a/test/finance/VestingWallet.test.js b/test/finance/VestingWallet.test.js index 6aa737805..e3cf9975a 100644 --- a/test/finance/VestingWallet.test.js +++ b/test/finance/VestingWallet.test.js @@ -1,14 +1,13 @@ const { constants, expectEvent, expectRevert, time } = require('@openzeppelin/test-helpers'); const { web3 } = require('@openzeppelin/test-helpers/src/setup'); const { expect } = require('chai'); +const { BNmin } = require('../helpers/math'); const ERC20Mock = artifacts.require('ERC20Mock'); const VestingWallet = artifacts.require('VestingWallet'); const { shouldBehaveLikeVesting } = require('./VestingWallet.behavior'); -const min = (...args) => args.slice(1).reduce((x, y) => x.lt(y) ? x : y, args[0]); - contract('VestingWallet', function (accounts) { const [ sender, beneficiary ] = accounts; @@ -36,7 +35,7 @@ contract('VestingWallet', function (accounts) { describe('vesting schedule', function () { beforeEach(async function () { this.schedule = Array(64).fill().map((_, i) => web3.utils.toBN(i).mul(duration).divn(60).add(this.start)); - this.vestingFn = timestamp => min(amount, amount.mul(timestamp.sub(this.start)).div(duration)); + this.vestingFn = timestamp => BNmin(amount, amount.mul(timestamp.sub(this.start)).div(duration)); }); describe('Eth vesting', function () { diff --git a/test/governance/utils/Votes.behavior.js b/test/governance/utils/Votes.behavior.js index 40fa4c152..331cb7275 100644 --- a/test/governance/utils/Votes.behavior.js +++ b/test/governance/utils/Votes.behavior.js @@ -17,10 +17,12 @@ const Delegation = [ const version = '1'; -function shouldBehaveLikeVotes () { +function shouldBehaveLikeVotes (accounts, tokens, fungible = true) { + const getWeight = token => web3.utils.toBN(fungible ? token : 1); + describe('run votes workflow', function () { it('initial nonce is 0', async function () { - expect(await this.votes.nonces(this.account1)).to.be.bignumber.equal('0'); + expect(await this.votes.nonces(accounts[0])).to.be.bignumber.equal('0'); }); it('domain separator', async function () { @@ -31,207 +33,207 @@ function shouldBehaveLikeVotes () { ); }); - describe('delegation with signature', function () { - const delegator = Wallet.generate(); - const delegatorAddress = web3.utils.toChecksumAddress(delegator.getAddressString()); - const nonce = 0; - - const buildData = (chainId, verifyingContract, name, message) => ({ - data: { - primaryType: 'Delegation', - types: { EIP712Domain, Delegation }, - domain: { name, version, chainId, verifyingContract }, - message, - }, - }); + describe('delegation', function () { + const token = tokens[0]; - beforeEach(async function () { - await this.votes.mint(delegatorAddress, this.token0); + it('delegation without tokens', async function () { + expect(await this.votes.delegates(accounts[1])).to.be.equal(ZERO_ADDRESS); + + const { receipt } = await this.votes.delegate(accounts[1], { from: accounts[1] }); + expectEvent(receipt, 'DelegateChanged', { + delegator: accounts[1], + fromDelegate: ZERO_ADDRESS, + toDelegate: accounts[1], + }); + expectEvent.notEmitted(receipt, 'DelegateVotesChanged'); + + expect(await this.votes.delegates(accounts[1])).to.be.equal(accounts[1]); }); - it('accept signed delegation', async function () { - const { v, r, s } = fromRpcSig(ethSigUtil.signTypedMessage( - delegator.getPrivateKey(), - buildData(this.chainId, this.votes.address, this.name, { - delegatee: delegatorAddress, - nonce, - expiry: MAX_UINT256, - }), - )); + it('delegation with tokens', async function () { + await this.votes.mint(accounts[1], token); + const weight = getWeight(token); - expect(await this.votes.delegates(delegatorAddress)).to.be.equal(ZERO_ADDRESS); + expect(await this.votes.delegates(accounts[1])).to.be.equal(ZERO_ADDRESS); - const { receipt } = await this.votes.delegateBySig(delegatorAddress, nonce, MAX_UINT256, v, r, s); + const { receipt } = await this.votes.delegate(accounts[1], { from: accounts[1] }); expectEvent(receipt, 'DelegateChanged', { - delegator: delegatorAddress, + delegator: accounts[1], fromDelegate: ZERO_ADDRESS, - toDelegate: delegatorAddress, + toDelegate: accounts[1], }); expectEvent(receipt, 'DelegateVotesChanged', { - delegate: delegatorAddress, + delegate: accounts[1], previousBalance: '0', - newBalance: '1', + newBalance: weight, }); - expect(await this.votes.delegates(delegatorAddress)).to.be.equal(delegatorAddress); - - expect(await this.votes.getVotes(delegatorAddress)).to.be.bignumber.equal('1'); - expect(await this.votes.getPastVotes(delegatorAddress, receipt.blockNumber - 1)).to.be.bignumber.equal('0'); + expect(await this.votes.delegates(accounts[1])).to.be.equal(accounts[1]); + expect(await this.votes.getVotes(accounts[1])).to.be.bignumber.equal(weight); + expect(await this.votes.getPastVotes(accounts[1], receipt.blockNumber - 1)).to.be.bignumber.equal('0'); await time.advanceBlock(); - expect(await this.votes.getPastVotes(delegatorAddress, receipt.blockNumber)).to.be.bignumber.equal('1'); + expect(await this.votes.getPastVotes(accounts[1], receipt.blockNumber)).to.be.bignumber.equal(weight); }); - it('rejects reused signature', async function () { - const { v, r, s } = fromRpcSig(ethSigUtil.signTypedMessage( - delegator.getPrivateKey(), - buildData(this.chainId, this.votes.address, this.name, { - delegatee: delegatorAddress, - nonce, - expiry: MAX_UINT256, - }), - )); + it('delegation update', async function () { + await this.votes.delegate(accounts[1], { from: accounts[1] }); + await this.votes.mint(accounts[1], token); + const weight = getWeight(token); - await this.votes.delegateBySig(delegatorAddress, nonce, MAX_UINT256, v, r, s); + expect(await this.votes.delegates(accounts[1])).to.be.equal(accounts[1]); + expect(await this.votes.getVotes(accounts[1])).to.be.bignumber.equal(weight); + expect(await this.votes.getVotes(accounts[2])).to.be.bignumber.equal('0'); - await expectRevert( - this.votes.delegateBySig(delegatorAddress, nonce, MAX_UINT256, v, r, s), - 'Votes: invalid nonce', - ); - }); + const { receipt } = await this.votes.delegate(accounts[2], { from: accounts[1] }); + expectEvent(receipt, 'DelegateChanged', { + delegator: accounts[1], + fromDelegate: accounts[1], + toDelegate: accounts[2], + }); + expectEvent(receipt, 'DelegateVotesChanged', { + delegate: accounts[1], + previousBalance: weight, + newBalance: '0', + }); + expectEvent(receipt, 'DelegateVotesChanged', { + delegate: accounts[2], + previousBalance: '0', + newBalance: weight, + }); - it('rejects bad delegatee', async function () { - const { v, r, s } = fromRpcSig(ethSigUtil.signTypedMessage( - delegator.getPrivateKey(), - buildData(this.chainId, this.votes.address, this.name, { - delegatee: delegatorAddress, - nonce, - expiry: MAX_UINT256, - }), - )); - - const receipt = await this.votes.delegateBySig(this.account1Delegatee, nonce, MAX_UINT256, v, r, s); - const { args } = receipt.logs.find(({ event }) => event === 'DelegateChanged'); - expect(args.delegator).to.not.be.equal(delegatorAddress); - expect(args.fromDelegate).to.be.equal(ZERO_ADDRESS); - expect(args.toDelegate).to.be.equal(this.account1Delegatee); - }); + expect(await this.votes.delegates(accounts[1])).to.be.equal(accounts[2]); + expect(await this.votes.getVotes(accounts[1])).to.be.bignumber.equal('0'); + expect(await this.votes.getVotes(accounts[2])).to.be.bignumber.equal(weight); - it('rejects bad nonce', async function () { - const { v, r, s } = fromRpcSig(ethSigUtil.signTypedMessage( - delegator.getPrivateKey(), - buildData(this.chainId, this.votes.address, this.name, { - delegatee: delegatorAddress, - nonce, - expiry: MAX_UINT256, - }), - )); - await expectRevert( - this.votes.delegateBySig(delegatorAddress, nonce + 1, MAX_UINT256, v, r, s), - 'Votes: invalid nonce', - ); + expect(await this.votes.getPastVotes(accounts[1], receipt.blockNumber - 1)).to.be.bignumber.equal(weight); + expect(await this.votes.getPastVotes(accounts[2], receipt.blockNumber - 1)).to.be.bignumber.equal('0'); + await time.advanceBlock(); + expect(await this.votes.getPastVotes(accounts[1], receipt.blockNumber)).to.be.bignumber.equal('0'); + expect(await this.votes.getPastVotes(accounts[2], receipt.blockNumber)).to.be.bignumber.equal(weight); }); - it('rejects expired permit', async function () { - const expiry = (await time.latest()) - time.duration.weeks(1); - const { v, r, s } = fromRpcSig(ethSigUtil.signTypedMessage( - delegator.getPrivateKey(), - buildData(this.chainId, this.votes.address, this.name, { - delegatee: delegatorAddress, - nonce, - expiry, - }), - )); + describe('with signature', function () { + const delegator = Wallet.generate(); + const [delegatee, other] = accounts; + const nonce = 0; + delegator.address = web3.utils.toChecksumAddress(delegator.getAddressString()); + + const buildData = (chainId, verifyingContract, name, message) => ({ + data: { + primaryType: 'Delegation', + types: { EIP712Domain, Delegation }, + domain: { name, version, chainId, verifyingContract }, + message, + }, + }); - await expectRevert( - this.votes.delegateBySig(delegatorAddress, nonce, expiry, v, r, s), - 'Votes: signature expired', - ); - }); - }); + it('accept signed delegation', async function () { + await this.votes.mint(delegator.address, token); + const weight = getWeight(token); - describe('set delegation', function () { - describe('call', function () { - it('delegation with tokens', async function () { - await this.votes.mint(this.account1, this.token0); - expect(await this.votes.delegates(this.account1)).to.be.equal(ZERO_ADDRESS); + const { v, r, s } = fromRpcSig(ethSigUtil.signTypedMessage( + delegator.getPrivateKey(), + buildData(this.chainId, this.votes.address, this.name, { + delegatee, + nonce, + expiry: MAX_UINT256, + }), + )); - const { receipt } = await this.votes.delegate(this.account1, { from: this.account1 }); + expect(await this.votes.delegates(delegator.address)).to.be.equal(ZERO_ADDRESS); + + const { receipt } = await this.votes.delegateBySig(delegatee, nonce, MAX_UINT256, v, r, s); expectEvent(receipt, 'DelegateChanged', { - delegator: this.account1, + delegator: delegator.address, fromDelegate: ZERO_ADDRESS, - toDelegate: this.account1, + toDelegate: delegatee, }); expectEvent(receipt, 'DelegateVotesChanged', { - delegate: this.account1, + delegate: delegatee, previousBalance: '0', - newBalance: '1', + newBalance: weight, }); - expect(await this.votes.delegates(this.account1)).to.be.equal(this.account1); - - expect(await this.votes.getVotes(this.account1)).to.be.bignumber.equal('1'); - expect(await this.votes.getPastVotes(this.account1, receipt.blockNumber - 1)).to.be.bignumber.equal('0'); + expect(await this.votes.delegates(delegator.address)).to.be.equal(delegatee); + expect(await this.votes.getVotes(delegator.address)).to.be.bignumber.equal('0'); + expect(await this.votes.getVotes(delegatee)).to.be.bignumber.equal(weight); + expect(await this.votes.getPastVotes(delegatee, receipt.blockNumber - 1)).to.be.bignumber.equal('0'); await time.advanceBlock(); - expect(await this.votes.getPastVotes(this.account1, receipt.blockNumber)).to.be.bignumber.equal('1'); + expect(await this.votes.getPastVotes(delegatee, receipt.blockNumber)).to.be.bignumber.equal(weight); }); - it('delegation without tokens', async function () { - expect(await this.votes.delegates(this.account1)).to.be.equal(ZERO_ADDRESS); + it('rejects reused signature', async function () { + const { v, r, s } = fromRpcSig(ethSigUtil.signTypedMessage( + delegator.getPrivateKey(), + buildData(this.chainId, this.votes.address, this.name, { + delegatee, + nonce, + expiry: MAX_UINT256, + }), + )); - const { receipt } = await this.votes.delegate(this.account1, { from: this.account1 }); - expectEvent(receipt, 'DelegateChanged', { - delegator: this.account1, - fromDelegate: ZERO_ADDRESS, - toDelegate: this.account1, - }); - expectEvent.notEmitted(receipt, 'DelegateVotesChanged'); + await this.votes.delegateBySig(delegatee, nonce, MAX_UINT256, v, r, s); + + await expectRevert( + this.votes.delegateBySig(delegatee, nonce, MAX_UINT256, v, r, s), + 'Votes: invalid nonce', + ); + }); - expect(await this.votes.delegates(this.account1)).to.be.equal(this.account1); + it('rejects bad delegatee', async function () { + const { v, r, s } = fromRpcSig(ethSigUtil.signTypedMessage( + delegator.getPrivateKey(), + buildData(this.chainId, this.votes.address, this.name, { + delegatee, + nonce, + expiry: MAX_UINT256, + }), + )); + + const receipt = await this.votes.delegateBySig(other, nonce, MAX_UINT256, v, r, s); + const { args } = receipt.logs.find(({ event }) => event === 'DelegateChanged'); + expect(args.delegator).to.not.be.equal(delegator.address); + expect(args.fromDelegate).to.be.equal(ZERO_ADDRESS); + expect(args.toDelegate).to.be.equal(other); }); - }); - }); - describe('change delegation', function () { - beforeEach(async function () { - await this.votes.mint(this.account1, this.token0); - await this.votes.delegate(this.account1, { from: this.account1 }); - }); + it('rejects bad nonce', async function () { + const { v, r, s } = fromRpcSig(ethSigUtil.signTypedMessage( + delegator.getPrivateKey(), + buildData(this.chainId, this.votes.address, this.name, { + delegatee, + nonce: nonce + 1, + expiry: MAX_UINT256, + }), + )); + await expectRevert( + this.votes.delegateBySig(delegatee, nonce + 1, MAX_UINT256, v, r, s), + 'Votes: invalid nonce', + ); + }); - it('call', async function () { - expect(await this.votes.delegates(this.account1)).to.be.equal(this.account1); + it('rejects expired permit', async function () { + const expiry = (await time.latest()) - time.duration.weeks(1); + const { v, r, s } = fromRpcSig(ethSigUtil.signTypedMessage( + delegator.getPrivateKey(), + buildData(this.chainId, this.votes.address, this.name, { + delegatee, + nonce, + expiry, + }), + )); - const { receipt } = await this.votes.delegate(this.account1Delegatee, { from: this.account1 }); - expectEvent(receipt, 'DelegateChanged', { - delegator: this.account1, - fromDelegate: this.account1, - toDelegate: this.account1Delegatee, - }); - expectEvent(receipt, 'DelegateVotesChanged', { - delegate: this.account1, - previousBalance: '1', - newBalance: '0', - }); - expectEvent(receipt, 'DelegateVotesChanged', { - delegate: this.account1Delegatee, - previousBalance: '0', - newBalance: '1', + await expectRevert( + this.votes.delegateBySig(delegatee, nonce, expiry, v, r, s), + 'Votes: signature expired', + ); }); - const prevBlock = receipt.blockNumber - 1; - expect(await this.votes.delegates(this.account1)).to.be.equal(this.account1Delegatee); - - expect(await this.votes.getVotes(this.account1)).to.be.bignumber.equal('0'); - expect(await this.votes.getVotes(this.account1Delegatee)).to.be.bignumber.equal('1'); - expect(await this.votes.getPastVotes(this.account1, receipt.blockNumber - 1)).to.be.bignumber.equal('1'); - expect(await this.votes.getPastVotes(this.account1Delegatee, prevBlock)).to.be.bignumber.equal('0'); - await time.advanceBlock(); - expect(await this.votes.getPastVotes(this.account1, receipt.blockNumber)).to.be.bignumber.equal('0'); - expect(await this.votes.getPastVotes(this.account1Delegatee, receipt.blockNumber)).to.be.bignumber.equal('1'); }); }); describe('getPastTotalSupply', function () { beforeEach(async function () { - await this.votes.delegate(this.account1, { from: this.account1 }); + await this.votes.delegate(accounts[1], { from: accounts[1] }); }); it('reverts if block number >= current block', async function () { @@ -245,95 +247,77 @@ function shouldBehaveLikeVotes () { expect(await this.votes.getPastTotalSupply(0)).to.be.bignumber.equal('0'); }); - it('returns the latest block if >= last checkpoint block', async function () { - const t1 = await this.votes.mint(this.account1, this.token0); - await time.advanceBlock(); - await time.advanceBlock(); + it('returns the correct checkpointed total supply', async function () { + const blockNumber = Number(await time.latestBlock()); - expect(await this.votes.getPastTotalSupply(t1.receipt.blockNumber - 1)).to.be.bignumber.equal('0'); - expect(await this.votes.getPastTotalSupply(t1.receipt.blockNumber + 1)).to.be.bignumber.equal('1'); - }); - - it('returns zero if < first checkpoint block', async function () { - await time.advanceBlock(); - const t2 = await this.votes.mint(this.account1, this.token1); - await time.advanceBlock(); - await time.advanceBlock(); - - expect(await this.votes.getPastTotalSupply(t2.receipt.blockNumber - 1)).to.be.bignumber.equal('0'); - expect(await this.votes.getPastTotalSupply(t2.receipt.blockNumber + 1)).to.be.bignumber.equal('1'); - }); - - it('generally returns the voting balance at the appropriate checkpoint', async function () { - const t1 = await this.votes.mint(this.account1, this.token1); - await time.advanceBlock(); - await time.advanceBlock(); - const t2 = await this.votes.burn(this.account1, this.token1); - await time.advanceBlock(); - await time.advanceBlock(); - const t3 = await this.votes.mint(this.account1, this.token2); + await this.votes.mint(accounts[1], tokens[0]); // mint 0 await time.advanceBlock(); + await this.votes.mint(accounts[1], tokens[1]); // mint 1 await time.advanceBlock(); - const t4 = await this.votes.burn(this.account1, this.token2); + await this.votes.burn(...(fungible ? [accounts[1]] : []), tokens[1]); // burn 1 await time.advanceBlock(); + await this.votes.mint(accounts[1], tokens[2]); // mint 2 await time.advanceBlock(); - const t5 = await this.votes.mint(this.account1, this.token3); + await this.votes.burn(...(fungible ? [accounts[1]] : []), tokens[0]); // burn 0 await time.advanceBlock(); + await this.votes.burn(...(fungible ? [accounts[1]] : []), tokens[2]); // burn 2 await time.advanceBlock(); - expect(await this.votes.getPastTotalSupply(t1.receipt.blockNumber - 1)).to.be.bignumber.equal('0'); - expect(await this.votes.getPastTotalSupply(t1.receipt.blockNumber)).to.be.bignumber.equal('1'); - expect(await this.votes.getPastTotalSupply(t1.receipt.blockNumber + 1)).to.be.bignumber.equal('1'); - expect(await this.votes.getPastTotalSupply(t2.receipt.blockNumber)).to.be.bignumber.equal('0'); - expect(await this.votes.getPastTotalSupply(t2.receipt.blockNumber + 1)).to.be.bignumber.equal('0'); - expect(await this.votes.getPastTotalSupply(t3.receipt.blockNumber)).to.be.bignumber.equal('1'); - expect(await this.votes.getPastTotalSupply(t3.receipt.blockNumber + 1)).to.be.bignumber.equal('1'); - expect(await this.votes.getPastTotalSupply(t4.receipt.blockNumber)).to.be.bignumber.equal('0'); - expect(await this.votes.getPastTotalSupply(t4.receipt.blockNumber + 1)).to.be.bignumber.equal('0'); - expect(await this.votes.getPastTotalSupply(t5.receipt.blockNumber)).to.be.bignumber.equal('1'); - expect(await this.votes.getPastTotalSupply(t5.receipt.blockNumber + 1)).to.be.bignumber.equal('1'); + const weight = tokens.map(getWeight); + + expect(await this.votes.getPastTotalSupply(blockNumber)).to.be.bignumber.equal('0'); + expect(await this.votes.getPastTotalSupply(blockNumber + 1)).to.be.bignumber.equal(weight[0]); + expect(await this.votes.getPastTotalSupply(blockNumber + 2)).to.be.bignumber.equal(weight[0]); + expect(await this.votes.getPastTotalSupply(blockNumber + 3)).to.be.bignumber.equal(weight[0].add(weight[1])); + expect(await this.votes.getPastTotalSupply(blockNumber + 4)).to.be.bignumber.equal(weight[0].add(weight[1])); + expect(await this.votes.getPastTotalSupply(blockNumber + 5)).to.be.bignumber.equal(weight[0]); + expect(await this.votes.getPastTotalSupply(blockNumber + 6)).to.be.bignumber.equal(weight[0]); + expect(await this.votes.getPastTotalSupply(blockNumber + 7)).to.be.bignumber.equal(weight[0].add(weight[2])); + expect(await this.votes.getPastTotalSupply(blockNumber + 8)).to.be.bignumber.equal(weight[0].add(weight[2])); + expect(await this.votes.getPastTotalSupply(blockNumber + 9)).to.be.bignumber.equal(weight[2]); + expect(await this.votes.getPastTotalSupply(blockNumber + 10)).to.be.bignumber.equal(weight[2]); + expect(await this.votes.getPastTotalSupply(blockNumber + 11)).to.be.bignumber.equal('0'); + await expectRevert(this.votes.getPastTotalSupply(blockNumber + 12), 'Votes: block not yet mined'); }); }); - // The following tests are a adaptation of + // The following tests are an adaptation of // https://github.com/compound-finance/compound-protocol/blob/master/tests/Governance/CompTest.js. describe('Compound test suite', function () { beforeEach(async function () { - await this.votes.mint(this.account1, this.token0); - await this.votes.mint(this.account1, this.token1); - await this.votes.mint(this.account1, this.token2); - await this.votes.mint(this.account1, this.token3); + await this.votes.mint(accounts[1], tokens[0]); + await this.votes.mint(accounts[1], tokens[1]); + await this.votes.mint(accounts[1], tokens[2]); }); describe('getPastVotes', function () { it('reverts if block number >= current block', async function () { await expectRevert( - this.votes.getPastVotes(this.account2, 5e10), + this.votes.getPastVotes(accounts[2], 5e10), 'block not yet mined', ); }); it('returns 0 if there are no checkpoints', async function () { - expect(await this.votes.getPastVotes(this.account2, 0)).to.be.bignumber.equal('0'); + expect(await this.votes.getPastVotes(accounts[2], 0)).to.be.bignumber.equal('0'); }); it('returns the latest block if >= last checkpoint block', async function () { - const t1 = await this.votes.delegate(this.account2, { from: this.account1 }); + const tx = await this.votes.delegate(accounts[2], { from: accounts[1] }); await time.advanceBlock(); await time.advanceBlock(); - const latest = await this.votes.getVotes(this.account2); - const nextBlock = t1.receipt.blockNumber + 1; - expect(await this.votes.getPastVotes(this.account2, t1.receipt.blockNumber)).to.be.bignumber.equal(latest); - expect(await this.votes.getPastVotes(this.account2, nextBlock)).to.be.bignumber.equal(latest); + const latest = await this.votes.getVotes(accounts[2]); + expect(await this.votes.getPastVotes(accounts[2], tx.receipt.blockNumber)).to.be.bignumber.equal(latest); + expect(await this.votes.getPastVotes(accounts[2], tx.receipt.blockNumber + 1)).to.be.bignumber.equal(latest); }); it('returns zero if < first checkpoint block', async function () { await time.advanceBlock(); - const t1 = await this.votes.delegate(this.account2, { from: this.account1 }); + const tx = await this.votes.delegate(accounts[2], { from: accounts[1] }); await time.advanceBlock(); await time.advanceBlock(); - expect(await this.votes.getPastVotes(this.account2, t1.receipt.blockNumber - 1)).to.be.bignumber.equal('0'); + expect(await this.votes.getPastVotes(accounts[2], tx.receipt.blockNumber - 1)).to.be.bignumber.equal('0'); }); }); }); diff --git a/test/governance/utils/Votes.test.js b/test/governance/utils/Votes.test.js index 8d5de78f7..81c92387f 100644 --- a/test/governance/utils/Votes.test.js +++ b/test/governance/utils/Votes.test.js @@ -1,6 +1,8 @@ -const { expectRevert, BN } = require('@openzeppelin/test-helpers'); - +const { constants, expectRevert } = require('@openzeppelin/test-helpers'); const { expect } = require('chai'); +const { BNsum } = require('../../helpers/math'); + +require('array.prototype.at/auto'); const { shouldBehaveLikeVotes, @@ -10,6 +12,12 @@ const Votes = artifacts.require('VotesMock'); contract('Votes', function (accounts) { const [ account1, account2, account3 ] = accounts; + const amounts = { + [account1]: web3.utils.toBN('10000000000000000000000000'), + [account2]: web3.utils.toBN('10'), + [account3]: web3.utils.toBN('20'), + }; + beforeEach(async function () { this.name = 'My Vote'; this.votes = await Votes.new(this.name); @@ -21,41 +29,59 @@ contract('Votes', function (accounts) { describe('performs voting operations', function () { beforeEach(async function () { - this.tx1 = await this.votes.mint(account1, 1); - this.tx2 = await this.votes.mint(account2, 1); - this.tx3 = await this.votes.mint(account3, 1); + this.txs = []; + for (const [account, amount] of Object.entries(amounts)) { + this.txs.push(await this.votes.mint(account, amount)); + } }); it('reverts if block number >= current block', async function () { await expectRevert( - this.votes.getPastTotalSupply(this.tx3.receipt.blockNumber + 1), + this.votes.getPastTotalSupply(this.txs.at(-1).receipt.blockNumber + 1), 'Votes: block not yet mined', ); }); it('delegates', async function () { - await this.votes.delegate(account3, account2); + expect(await this.votes.getVotes(account1)).to.be.bignumber.equal('0'); + expect(await this.votes.getVotes(account2)).to.be.bignumber.equal('0'); + expect(await this.votes.delegates(account1)).to.be.equal(constants.ZERO_ADDRESS); + expect(await this.votes.delegates(account2)).to.be.equal(constants.ZERO_ADDRESS); + + await this.votes.delegate(account1, account1); + + expect(await this.votes.getVotes(account1)).to.be.bignumber.equal(amounts[account1]); + expect(await this.votes.getVotes(account2)).to.be.bignumber.equal('0'); + expect(await this.votes.delegates(account1)).to.be.equal(account1); + expect(await this.votes.delegates(account2)).to.be.equal(constants.ZERO_ADDRESS); + + await this.votes.delegate(account2, account1); + + expect(await this.votes.getVotes(account1)).to.be.bignumber.equal(amounts[account1].add(amounts[account2])); + expect(await this.votes.getVotes(account2)).to.be.bignumber.equal('0'); + expect(await this.votes.delegates(account1)).to.be.equal(account1); + expect(await this.votes.delegates(account2)).to.be.equal(account1); + }); + + it('cross delegates', async function () { + await this.votes.delegate(account1, account2); + await this.votes.delegate(account2, account1); - expect(await this.votes.delegates(account3)).to.be.equal(account2); + expect(await this.votes.getVotes(account1)).to.be.bignumber.equal(amounts[account2]); + expect(await this.votes.getVotes(account2)).to.be.bignumber.equal(amounts[account1]); }); it('returns total amount of votes', async function () { - expect(await this.votes.getTotalSupply()).to.be.bignumber.equal('3'); + const totalSupply = BNsum(...Object.values(amounts)); + expect(await this.votes.getTotalSupply()).to.be.bignumber.equal(totalSupply); }); }); describe('performs voting workflow', function () { beforeEach(async function () { this.chainId = await this.votes.getChainId(); - this.account1 = account1; - this.account2 = account2; - this.account1Delegatee = account2; - this.token0 = new BN('10000000000000000000000000'); - this.token1 = new BN('10'); - this.token2 = new BN('20'); - this.token3 = new BN('30'); }); - shouldBehaveLikeVotes(); + shouldBehaveLikeVotes(accounts, Object.values(amounts)); }); }); diff --git a/test/helpers/enums.js b/test/helpers/enums.js index 26825de33..ce1ab3c22 100644 --- a/test/helpers/enums.js +++ b/test/helpers/enums.js @@ -1,7 +1,5 @@ -const { BN } = require('@openzeppelin/test-helpers'); - function Enum (...options) { - return Object.fromEntries(options.map((key, i) => [ key, new BN(i) ])); + return Object.fromEntries(options.map((key, i) => [ key, web3.utils.toBN(i) ])); } module.exports = { diff --git a/test/helpers/math.js b/test/helpers/math.js new file mode 100644 index 000000000..596986bc6 --- /dev/null +++ b/test/helpers/math.js @@ -0,0 +1,11 @@ +module.exports = { + // sum of integer / bignumber + sum: (...args) => args.reduce((acc, n) => acc + n, 0), + BNsum: (...args) => args.reduce((acc, n) => acc.add(n), web3.utils.toBN(0)), + // min of integer / bignumber + min: (...args) => args.slice(1).reduce((x, y) => x < y ? x : y, args[0]), + BNmin: (...args) => args.slice(1).reduce((x, y) => x.lt(y) ? x : y, args[0]), + // max of integer / bignumber + max: (...args) => args.slice(1).reduce((x, y) => x > y ? x : y, args[0]), + BNmax: (...args) => args.slice(1).reduce((x, y) => x.gt(y) ? x : y, args[0]), +}; diff --git a/test/token/ERC20/extensions/ERC20Votes.test.js b/test/token/ERC20/extensions/ERC20Votes.test.js index 4cbf186e3..8a7e9096f 100644 --- a/test/token/ERC20/extensions/ERC20Votes.test.js +++ b/test/token/ERC20/extensions/ERC20Votes.test.js @@ -348,7 +348,7 @@ contract('ERC20Votes', function (accounts) { const t1 = await this.token.delegate(other1, { from: recipient }); expect(await this.token.numCheckpoints(other1)).to.be.bignumber.equal('1'); - + const t2 = await this.token.transfer(other2, 10, { from: recipient }); expect(await this.token.numCheckpoints(other1)).to.be.bignumber.equal('2'); @@ -518,17 +518,10 @@ contract('ERC20Votes', function (accounts) { describe('Voting workflow', function () { beforeEach(async function () { - this.account1 = holder; - this.account1Delegatee = holderDelegatee; - this.account2 = recipient; - this.name = 'My Token'; - this.votes = this.token - this.token0 = 1; - this.token1 = 1; - this.token2 = 1; - this.token3 = 1; + this.name = name; + this.votes = this.token; }); - shouldBehaveLikeVotes(); + shouldBehaveLikeVotes(accounts, [ 1, 17, 42]); }); }); diff --git a/test/token/ERC20/extensions/ERC20VotesComp.test.js b/test/token/ERC20/extensions/ERC20VotesComp.test.js index ac7f63e77..19efa3bea 100644 --- a/test/token/ERC20/extensions/ERC20VotesComp.test.js +++ b/test/token/ERC20/extensions/ERC20VotesComp.test.js @@ -374,7 +374,7 @@ contract('ERC20VotesComp', function (accounts) { expect(await this.token.checkpoints(other1, 1)).to.be.deep.equal([ t4.receipt.blockNumber.toString(), '100' ]); }); }); - + describe('getPriorVotes', function () { it('reverts if block number >= current block', async function () { await expectRevert( @@ -497,17 +497,10 @@ contract('ERC20VotesComp', function (accounts) { describe('Voting workflow', function () { beforeEach(async function () { - this.account1 = holder; - this.account1Delegatee = holderDelegatee; - this.account2 = recipient; - this.name = 'My Token'; - this.votes = this.token - this.token0 = 1; - this.token1 = 1; - this.token2 = 1; - this.token3 = 1; + this.name = name; + this.votes = this.token; }); - shouldBehaveLikeVotes(); + shouldBehaveLikeVotes(accounts, [1, 17, 42]); }); }); diff --git a/test/token/ERC721/extensions/ERC721Consecutive.test.js b/test/token/ERC721/extensions/ERC721Consecutive.test.js index 5e480abb8..37000ae91 100644 --- a/test/token/ERC721/extensions/ERC721Consecutive.test.js +++ b/test/token/ERC721/extensions/ERC721Consecutive.test.js @@ -1,5 +1,6 @@ const { constants, expectEvent, expectRevert } = require('@openzeppelin/test-helpers'); const { expect } = require('chai'); +const { sum } = require('../../../helpers/math'); const ERC721ConsecutiveMock = artifacts.require('ERC721ConsecutiveMock'); const ERC721ConsecutiveEnumerableMock = artifacts.require('ERC721ConsecutiveEnumerableMock'); @@ -60,10 +61,11 @@ contract('ERC721Consecutive', function (accounts) { it('balance & voting power are set', async function () { for (const account of accounts) { - const balance = batches - .filter(({ receiver }) => receiver === account) - .map(({ amount }) => amount) - .reduce((a, b) => a + b, 0); + const balance = sum( + ...batches + .filter(({ receiver }) => receiver === account) + .map(({ amount }) => amount), + ); expect(await this.token.balanceOf(account)) .to.be.bignumber.equal(web3.utils.toBN(balance)); @@ -92,7 +94,7 @@ contract('ERC721Consecutive', function (accounts) { }); it('simple minting is possible after construction', async function () { - const tokenId = batches.reduce((acc, { amount }) => acc + amount, 0); + const tokenId = sum(...batches.map(b => b.amount)); expect(await this.token.exists(tokenId)).to.be.equal(false); @@ -104,7 +106,7 @@ contract('ERC721Consecutive', function (accounts) { }); it('cannot mint a token that has been batched minted', async function () { - const tokenId = batches.reduce((acc, { amount }) => acc + amount, 0) - 1; + const tokenId = sum(...batches.map(b => b.amount)) - 1; expect(await this.token.exists(tokenId)).to.be.equal(true); @@ -141,7 +143,7 @@ contract('ERC721Consecutive', function (accounts) { }); it('tokens can be burned and re-minted #2', async function () { - const tokenId = batches.reduce((acc, { amount }) => acc.addn(amount), web3.utils.toBN(0)); + const tokenId = web3.utils.toBN(sum(...batches.map(({ amount }) => amount))); expect(await this.token.exists(tokenId)).to.be.equal(false); await expectRevert(this.token.ownerOf(tokenId), 'ERC721: invalid token ID'); diff --git a/test/token/ERC721/extensions/ERC721Votes.test.js b/test/token/ERC721/extensions/ERC721Votes.test.js index c6a9bbb32..be1880d48 100644 --- a/test/token/ERC721/extensions/ERC721Votes.test.js +++ b/test/token/ERC721/extensions/ERC721Votes.test.js @@ -1,6 +1,6 @@ /* eslint-disable */ -const { BN, expectEvent, time } = require('@openzeppelin/test-helpers'); +const { expectEvent, time } = require('@openzeppelin/test-helpers'); const { expect } = require('chai'); const ERC721VotesMock = artifacts.require('ERC721VotesMock'); @@ -9,8 +9,14 @@ const { shouldBehaveLikeVotes } = require('../../../governance/utils/Votes.behav contract('ERC721Votes', function (accounts) { const [ account1, account2, account1Delegatee, other1, other2 ] = accounts; - this.name = 'My Vote'; + const name = 'My Vote'; const symbol = 'MTKN'; + const tokens = [ + '10000000000000000000000000', + '10', + '20', + '30', + ].map(n => web3.utils.toBN(n)); beforeEach(async function () { this.votes = await ERC721VotesMock.new(name, symbol); @@ -19,19 +25,14 @@ contract('ERC721Votes', function (accounts) { // from within the EVM as from the JSON RPC interface. // See https://github.com/trufflesuite/ganache-core/issues/515 this.chainId = await this.votes.getChainId(); - - this.token0 = new BN('10000000000000000000000000'); - this.token1 = new BN('10'); - this.token2 = new BN('20'); - this.token3 = new BN('30'); }); describe('balanceOf', function () { beforeEach(async function () { - await this.votes.mint(account1, this.token0); - await this.votes.mint(account1, this.token1); - await this.votes.mint(account1, this.token2); - await this.votes.mint(account1, this.token3); + await this.votes.mint(account1, tokens[0]); + await this.votes.mint(account1, tokens[1]); + await this.votes.mint(account1, tokens[2]); + await this.votes.mint(account1, tokens[3]); }); it('grants to initial account', async function () { @@ -41,12 +42,12 @@ contract('ERC721Votes', function (accounts) { describe('transfers', function () { beforeEach(async function () { - await this.votes.mint(account1, this.token0); + await this.votes.mint(account1, tokens[0]); }); it('no delegation', async function () { - const { receipt } = await this.votes.transferFrom(account1, account2, this.token0, { from: account1 }); - expectEvent(receipt, 'Transfer', { from: account1, to: account2, tokenId: this.token0 }); + const { receipt } = await this.votes.transferFrom(account1, account2, tokens[0], { from: account1 }); + expectEvent(receipt, 'Transfer', { from: account1, to: account2, tokenId: tokens[0] }); expectEvent.notEmitted(receipt, 'DelegateVotesChanged'); this.account1Votes = '0'; @@ -56,8 +57,8 @@ contract('ERC721Votes', function (accounts) { it('sender delegation', async function () { await this.votes.delegate(account1, { from: account1 }); - const { receipt } = await this.votes.transferFrom(account1, account2, this.token0, { from: account1 }); - expectEvent(receipt, 'Transfer', { from: account1, to: account2, tokenId: this.token0 }); + const { receipt } = await this.votes.transferFrom(account1, account2, tokens[0], { from: account1 }); + expectEvent(receipt, 'Transfer', { from: account1, to: account2, tokenId: tokens[0] }); expectEvent(receipt, 'DelegateVotesChanged', { delegate: account1, previousBalance: '1', newBalance: '0' }); const { logIndex: transferLogIndex } = receipt.logs.find(({ event }) => event == 'Transfer'); @@ -70,8 +71,8 @@ contract('ERC721Votes', function (accounts) { it('receiver delegation', async function () { await this.votes.delegate(account2, { from: account2 }); - const { receipt } = await this.votes.transferFrom(account1, account2, this.token0, { from: account1 }); - expectEvent(receipt, 'Transfer', { from: account1, to: account2, tokenId: this.token0 }); + const { receipt } = await this.votes.transferFrom(account1, account2, tokens[0], { from: account1 }); + expectEvent(receipt, 'Transfer', { from: account1, to: account2, tokenId: tokens[0] }); expectEvent(receipt, 'DelegateVotesChanged', { delegate: account2, previousBalance: '0', newBalance: '1' }); const { logIndex: transferLogIndex } = receipt.logs.find(({ event }) => event == 'Transfer'); @@ -85,8 +86,8 @@ contract('ERC721Votes', function (accounts) { await this.votes.delegate(account1, { from: account1 }); await this.votes.delegate(account2, { from: account2 }); - const { receipt } = await this.votes.transferFrom(account1, account2, this.token0, { from: account1 }); - expectEvent(receipt, 'Transfer', { from: account1, to: account2, tokenId: this.token0 }); + const { receipt } = await this.votes.transferFrom(account1, account2, tokens[0], { from: account1 }); + expectEvent(receipt, 'Transfer', { from: account1, to: account2, tokenId: tokens[0] }); expectEvent(receipt, 'DelegateVotesChanged', { delegate: account1, previousBalance: '1', newBalance: '0'}); expectEvent(receipt, 'DelegateVotesChanged', { delegate: account2, previousBalance: '0', newBalance: '1' }); @@ -100,7 +101,7 @@ contract('ERC721Votes', function (accounts) { it('returns the same total supply on transfers', async function () { await this.votes.delegate(account1, { from: account1 }); - const { receipt } = await this.votes.transferFrom(account1, account2, this.token0, { from: account1 }); + const { receipt } = await this.votes.transferFrom(account1, account2, tokens[0], { from: account1 }); await time.advanceBlock(); await time.advanceBlock(); @@ -113,22 +114,22 @@ contract('ERC721Votes', function (accounts) { }); it('generally returns the voting balance at the appropriate checkpoint', async function () { - await this.votes.mint(account1, this.token1); - await this.votes.mint(account1, this.token2); - await this.votes.mint(account1, this.token3); + await this.votes.mint(account1, tokens[1]); + await this.votes.mint(account1, tokens[2]); + await this.votes.mint(account1, tokens[3]); const total = await this.votes.balanceOf(account1); const t1 = await this.votes.delegate(other1, { from: account1 }); await time.advanceBlock(); await time.advanceBlock(); - const t2 = await this.votes.transferFrom(account1, other2, this.token0, { from: account1 }); + const t2 = await this.votes.transferFrom(account1, other2, tokens[0], { from: account1 }); await time.advanceBlock(); await time.advanceBlock(); - const t3 = await this.votes.transferFrom(account1, other2, this.token2, { from: account1 }); + const t3 = await this.votes.transferFrom(account1, other2, tokens[2], { from: account1 }); await time.advanceBlock(); await time.advanceBlock(); - const t4 = await this.votes.transferFrom(other2, account1, this.token2, { from: other2 }); + const t4 = await this.votes.transferFrom(other2, account1, tokens[2], { from: other2 }); await time.advanceBlock(); await time.advanceBlock(); @@ -160,12 +161,9 @@ contract('ERC721Votes', function (accounts) { describe('Voting workflow', function () { beforeEach(async function () { - this.account1 = account1; - this.account1Delegatee = account1Delegatee; - this.account2 = account2; - this.name = 'My Vote'; + this.name = name; }); - shouldBehaveLikeVotes(); + shouldBehaveLikeVotes(accounts, tokens, false); }); }); From 30c3c6c16e77eb60e3f367abd83141b5e494b1b3 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Sun, 15 Jan 2023 01:19:47 +0100 Subject: [PATCH 014/182] Clean up after merging master into next-v5.0 (#3956) Co-authored-by: Francisco --- .../{ERC20ReturnFalseMock copy.sol => ERC20ReturnFalseMock.sol} | 0 slither.config.json | 2 +- test/helpers/map-values.js | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename contracts/mocks/token/{ERC20ReturnFalseMock copy.sol => ERC20ReturnFalseMock.sol} (100%) diff --git a/contracts/mocks/token/ERC20ReturnFalseMock copy.sol b/contracts/mocks/token/ERC20ReturnFalseMock.sol similarity index 100% rename from contracts/mocks/token/ERC20ReturnFalseMock copy.sol rename to contracts/mocks/token/ERC20ReturnFalseMock.sol diff --git a/slither.config.json b/slither.config.json index 2b618794a..80ecda71b 100644 --- a/slither.config.json +++ b/slither.config.json @@ -2,4 +2,4 @@ "detectors_to_run": "reentrancy-eth,reentrancy-no-eth,reentrancy-unlimited-gas", "filter_paths": "contracts/mocks", "compile_force_framework": "hardhat" -} \ No newline at end of file +} diff --git a/test/helpers/map-values.js b/test/helpers/map-values.js index d2f7b2a3f..84d95fc65 100644 --- a/test/helpers/map-values.js +++ b/test/helpers/map-values.js @@ -1,5 +1,5 @@ function mapValues(obj, fn) { - return Object.fromEntries([...Object.entries(obj)].map(([k, v]) => [k, fn(v)])); + return Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, fn(v)])); } module.exports = { From c5d040beb9a951b00e9cb57c4e7dd97cd04b45ac Mon Sep 17 00:00:00 2001 From: JulissaDantes Date: Tue, 24 Jan 2023 15:59:07 -0500 Subject: [PATCH 015/182] Remove Address.isContract (#3945) Co-authored-by: Hadrien Croubois Co-authored-by: Francisco Giordano --- .changeset/unlucky-snakes-drive.md | 5 +++ contracts/mocks/proxy/UUPSLegacy.sol | 4 +- contracts/proxy/ERC1967/ERC1967Upgrade.sol | 6 +-- contracts/proxy/beacon/UpgradeableBeacon.sol | 5 +-- contracts/proxy/utils/Initializable.sol | 2 +- contracts/token/ERC1155/ERC1155.sol | 9 ++-- contracts/token/ERC721/ERC721.sol | 6 +-- .../ERC721/extensions/ERC721Consecutive.sol | 6 +-- contracts/token/ERC777/ERC777.sol | 7 +++- contracts/utils/Address.sol | 42 +------------------ contracts/utils/StorageSlot.sol | 2 +- docs/modules/ROOT/nav.adoc | 2 + docs/modules/ROOT/pages/faq.adoc | 13 ++++++ docs/modules/ROOT/pages/utilities.adoc | 2 - test/utils/Address.test.js | 10 ----- 15 files changed, 44 insertions(+), 77 deletions(-) create mode 100644 .changeset/unlucky-snakes-drive.md create mode 100644 docs/modules/ROOT/pages/faq.adoc diff --git a/.changeset/unlucky-snakes-drive.md b/.changeset/unlucky-snakes-drive.md new file mode 100644 index 000000000..d4c78dd50 --- /dev/null +++ b/.changeset/unlucky-snakes-drive.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': patch +--- + +`Address`: Removed `isContract` because of its ambiguous nature and potential for misuse. diff --git a/contracts/mocks/proxy/UUPSLegacy.sol b/contracts/mocks/proxy/UUPSLegacy.sol index 7a3002889..ba68e6046 100644 --- a/contracts/mocks/proxy/UUPSLegacy.sol +++ b/contracts/mocks/proxy/UUPSLegacy.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.1; import "./UUPSUpgradeableMock.sol"; @@ -13,7 +13,7 @@ contract UUPSUpgradeableLegacyMock is UUPSUpgradeableMock { // ERC1967Upgrade._setImplementation is private so we reproduce it here. // An extra underscore prevents a name clash error. function __setImplementation(address newImplementation) private { - require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract"); + require(newImplementation.code.length > 0, "ERC1967: new implementation is not a contract"); StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation; } diff --git a/contracts/proxy/ERC1967/ERC1967Upgrade.sol b/contracts/proxy/ERC1967/ERC1967Upgrade.sol index 38ea7ea1b..58f65fe88 100644 --- a/contracts/proxy/ERC1967/ERC1967Upgrade.sol +++ b/contracts/proxy/ERC1967/ERC1967Upgrade.sol @@ -43,7 +43,7 @@ abstract contract ERC1967Upgrade { * @dev Stores a new address in the EIP1967 implementation slot. */ function _setImplementation(address newImplementation) private { - require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract"); + require(newImplementation.code.length > 0, "ERC1967: new implementation is not a contract"); StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation; } @@ -153,9 +153,9 @@ abstract contract ERC1967Upgrade { * @dev Stores a new beacon in the EIP1967 beacon slot. */ function _setBeacon(address newBeacon) private { - require(Address.isContract(newBeacon), "ERC1967: new beacon is not a contract"); + require(newBeacon.code.length > 0, "ERC1967: new beacon is not a contract"); require( - Address.isContract(IBeacon(newBeacon).implementation()), + IBeacon(newBeacon).implementation().code.length > 0, "ERC1967: beacon implementation is not a contract" ); StorageSlot.getAddressSlot(_BEACON_SLOT).value = newBeacon; diff --git a/contracts/proxy/beacon/UpgradeableBeacon.sol b/contracts/proxy/beacon/UpgradeableBeacon.sol index 5d83ceb3b..9eeb149a6 100644 --- a/contracts/proxy/beacon/UpgradeableBeacon.sol +++ b/contracts/proxy/beacon/UpgradeableBeacon.sol @@ -1,11 +1,10 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (proxy/beacon/UpgradeableBeacon.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.1; import "./IBeacon.sol"; import "../../access/Ownable.sol"; -import "../../utils/Address.sol"; /** * @dev This contract is used in conjunction with one or more instances of {BeaconProxy} to determine their @@ -59,7 +58,7 @@ contract UpgradeableBeacon is IBeacon, Ownable { * - `newImplementation` must be a contract. */ function _setImplementation(address newImplementation) private { - require(Address.isContract(newImplementation), "UpgradeableBeacon: implementation is not a contract"); + require(newImplementation.code.length > 0, "UpgradeableBeacon: implementation is not a contract"); _implementation = newImplementation; } } diff --git a/contracts/proxy/utils/Initializable.sol b/contracts/proxy/utils/Initializable.sol index b454b5d95..4ad0b9826 100644 --- a/contracts/proxy/utils/Initializable.sol +++ b/contracts/proxy/utils/Initializable.sol @@ -83,7 +83,7 @@ abstract contract Initializable { modifier initializer() { bool isTopLevelCall = !_initializing; require( - (isTopLevelCall && _initialized < 1) || (!Address.isContract(address(this)) && _initialized == 1), + (isTopLevelCall && _initialized < 1) || (address(this).code.length == 0 && _initialized == 1), "Initializable: contract is already initialized" ); _initialized = 1; diff --git a/contracts/token/ERC1155/ERC1155.sol b/contracts/token/ERC1155/ERC1155.sol index e491a2310..c72026734 100644 --- a/contracts/token/ERC1155/ERC1155.sol +++ b/contracts/token/ERC1155/ERC1155.sol @@ -1,12 +1,11 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.8.0) (token/ERC1155/ERC1155.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.1; import "./IERC1155.sol"; import "./IERC1155Receiver.sol"; import "./extensions/IERC1155MetadataURI.sol"; -import "../../utils/Address.sol"; import "../../utils/Context.sol"; import "../../utils/introspection/ERC165.sol"; @@ -18,8 +17,6 @@ import "../../utils/introspection/ERC165.sol"; * _Available since v3.1._ */ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { - using Address for address; - // Mapping from token ID to account balances mapping(uint256 => mapping(address => uint256)) private _balances; @@ -344,7 +341,7 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { uint256 amount, bytes memory data ) private { - if (to.isContract()) { + if (to.code.length > 0) { try IERC1155Receiver(to).onERC1155Received(operator, from, id, amount, data) returns (bytes4 response) { if (response != IERC1155Receiver.onERC1155Received.selector) { revert("ERC1155: ERC1155Receiver rejected tokens"); @@ -365,7 +362,7 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { uint256[] memory amounts, bytes memory data ) private { - if (to.isContract()) { + if (to.code.length > 0) { try IERC1155Receiver(to).onERC1155BatchReceived(operator, from, ids, amounts, data) returns ( bytes4 response ) { diff --git a/contracts/token/ERC721/ERC721.sol b/contracts/token/ERC721/ERC721.sol index 6bf620b4f..0d159f609 100644 --- a/contracts/token/ERC721/ERC721.sol +++ b/contracts/token/ERC721/ERC721.sol @@ -1,12 +1,11 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.8.0) (token/ERC721/ERC721.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.1; import "./IERC721.sol"; import "./IERC721Receiver.sol"; import "./extensions/IERC721Metadata.sol"; -import "../../utils/Address.sol"; import "../../utils/Context.sol"; import "../../utils/Strings.sol"; import "../../utils/introspection/ERC165.sol"; @@ -17,7 +16,6 @@ import "../../utils/introspection/ERC165.sol"; * {ERC721Enumerable}. */ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata { - using Address for address; using Strings for uint256; // Token name @@ -402,7 +400,7 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata { uint256 tokenId, bytes memory data ) private returns (bool) { - if (to.isContract()) { + if (to.code.length > 0) { try IERC721Receiver(to).onERC721Received(_msgSender(), from, tokenId, data) returns (bytes4 retval) { return retval == IERC721Receiver.onERC721Received.selector; } catch (bytes memory reason) { diff --git a/contracts/token/ERC721/extensions/ERC721Consecutive.sol b/contracts/token/ERC721/extensions/ERC721Consecutive.sol index e6843f1fa..fe67032ef 100644 --- a/contracts/token/ERC721/extensions/ERC721Consecutive.sol +++ b/contracts/token/ERC721/extensions/ERC721Consecutive.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.8.0) (token/ERC721/extensions/ERC721Consecutive.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.1; import "../ERC721.sol"; import "../../../interfaces/IERC2309.sol"; @@ -86,7 +86,7 @@ abstract contract ERC721Consecutive is IERC2309, ERC721 { // minting a batch of size 0 is a no-op if (batchSize > 0) { - require(!Address.isContract(address(this)), "ERC721Consecutive: batch minting restricted to constructor"); + require(address(this).code.length == 0, "ERC721Consecutive: batch minting restricted to constructor"); require(to != address(0), "ERC721Consecutive: mint to the zero address"); require(batchSize <= _maxBatchSize(), "ERC721Consecutive: batch too large"); @@ -112,7 +112,7 @@ abstract contract ERC721Consecutive is IERC2309, ERC721 { * After construction, {_mintConsecutive} is no longer available and {_mint} becomes available. */ function _mint(address to, uint256 tokenId) internal virtual override { - require(Address.isContract(address(this)), "ERC721Consecutive: can't mint during construction"); + require(address(this).code.length > 0, "ERC721Consecutive: can't mint during construction"); super._mint(to, tokenId); } diff --git a/contracts/token/ERC777/ERC777.sol b/contracts/token/ERC777/ERC777.sol index c1503c4df..1f426c3ba 100644 --- a/contracts/token/ERC777/ERC777.sol +++ b/contracts/token/ERC777/ERC777.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.8.0) (token/ERC777/ERC777.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.1; import "./IERC777.sol"; import "./IERC777Recipient.sol"; @@ -472,7 +472,10 @@ contract ERC777 is Context, IERC777, IERC20 { if (implementer != address(0)) { IERC777Recipient(implementer).tokensReceived(operator, from, to, amount, userData, operatorData); } else if (requireReceptionAck) { - require(!to.isContract(), "ERC777: token recipient contract has no implementer for ERC777TokensRecipient"); + require( + to.code.length == 0, + "ERC777: token recipient contract has no implementer for ERC777TokensRecipient" + ); } } diff --git a/contracts/utils/Address.sol b/contracts/utils/Address.sol index 433a866d7..e08cdc754 100644 --- a/contracts/utils/Address.sol +++ b/contracts/utils/Address.sol @@ -7,44 +7,6 @@ pragma solidity ^0.8.1; * @dev Collection of functions related to the address type */ library Address { - /** - * @dev Returns true if `account` is a contract. - * - * [IMPORTANT] - * ==== - * It is unsafe to assume that an address for which this function returns - * false is an externally-owned account (EOA) and not a contract. - * - * Among others, `isContract` will return false for the following - * types of addresses: - * - * - an externally-owned account - * - a contract in construction - * - an address where a contract will be created - * - an address where a contract lived, but was destroyed - * - * Furthermore, `isContract` will also return true if the target contract within - * the same transaction is already scheduled for destruction by `SELFDESTRUCT`, - * which only has an effect at the end of a transaction. - * ==== - * - * [IMPORTANT] - * ==== - * You shouldn't rely on `isContract` to protect against flash loan attacks! - * - * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets - * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract - * constructor. - * ==== - */ - function isContract(address account) internal view returns (bool) { - // This method relies on extcodesize/address.code.length, which returns 0 - // for contracts in construction, since the code is only stored at the end - // of the constructor execution. - - return account.code.length > 0; - } - /** * @dev Replacement for Solidity's `transfer`: sends `amount` wei to * `recipient`, forwarding all available gas and reverting on errors. @@ -200,9 +162,9 @@ library Address { ) internal view returns (bytes memory) { if (success) { if (returndata.length == 0) { - // only check isContract if the call was successful and the return data is empty + // only check if target is a contract if the call was successful and the return data is empty // otherwise we already know that it was a contract - require(isContract(target), "Address: call to non-contract"); + require(target.code.length > 0, "Address: call to non-contract"); } return returndata; } else { diff --git a/contracts/utils/StorageSlot.sol b/contracts/utils/StorageSlot.sol index 6ab8f5dc6..5941ee9d4 100644 --- a/contracts/utils/StorageSlot.sol +++ b/contracts/utils/StorageSlot.sol @@ -21,7 +21,7 @@ pragma solidity ^0.8.0; * } * * function _setImplementation(address newImplementation) internal { - * require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract"); + * require(newImplementation.code.length > 0, "ERC1967: new implementation is not a contract"); * StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation; * } * } diff --git a/docs/modules/ROOT/nav.adoc b/docs/modules/ROOT/nav.adoc index 6604c2de5..a6249d334 100644 --- a/docs/modules/ROOT/nav.adoc +++ b/docs/modules/ROOT/nav.adoc @@ -19,3 +19,5 @@ * xref:crosschain.adoc[Crosschain] * xref:utilities.adoc[Utilities] + +* xref:fag.adoc[FAQ] diff --git a/docs/modules/ROOT/pages/faq.adoc b/docs/modules/ROOT/pages/faq.adoc new file mode 100644 index 000000000..81c34bbf5 --- /dev/null +++ b/docs/modules/ROOT/pages/faq.adoc @@ -0,0 +1,13 @@ += Frequently Asked Questions + +== Can I restrict a function to EOAs only? + +When calling external addresses from your contract it is unsafe to assume that an address is an externally-owned account (EOA) and not a contract. Attempting to prevent calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract constructor. + +Although checking that the address has code, `address.code.length > 0`, may seem to differentiate contracts from EOAs, it can only say that an address is currently a contract, and its negation (that an address is not currently a contract) does not imply that the address is an EOA. Some counterexamples are: + + - address of a contract in construction + - address where a contract will be created + - address where a contract lived, but was destroyed + +Furthermore, an address will be considered a contract within the same transaction where it is scheduled for destruction by `SELFDESTRUCT`, which only has an effect at the end of the entire transaction. diff --git a/docs/modules/ROOT/pages/utilities.adoc b/docs/modules/ROOT/pages/utilities.adoc index 4231a6a74..88207f0e1 100644 --- a/docs/modules/ROOT/pages/utilities.adoc +++ b/docs/modules/ROOT/pages/utilities.adoc @@ -99,8 +99,6 @@ If you need support for more powerful collections than Solidity's native arrays [[misc]] == Misc -Want to check if an address is a contract? Use xref:api:utils.adoc#Address[`Address`] and xref:api:utils.adoc#Address-isContract-address-[`Address.isContract()`]. - Want to keep track of some numbers that increment by 1 every time you want another one? Check out xref:api:utils.adoc#Counters[`Counters`]. This is useful for lots of things, like creating incremental identifiers, as shown on the xref:erc721.adoc[ERC721 guide]. === Base64 diff --git a/test/utils/Address.test.js b/test/utils/Address.test.js index a78ae14e6..2f8115c16 100644 --- a/test/utils/Address.test.js +++ b/test/utils/Address.test.js @@ -12,16 +12,6 @@ contract('Address', function (accounts) { this.mock = await Address.new(); }); - describe('isContract', function () { - it('returns false for account address', async function () { - expect(await this.mock.$isContract(other)).to.equal(false); - }); - - it('returns true for contract address', async function () { - expect(await this.mock.$isContract(this.mock.address)).to.equal(true); - }); - }); - describe('sendValue', function () { beforeEach(async function () { this.recipientTracker = await balance.tracker(recipient); From e919d96ff27729fbe0cc9f194965f94fd37c57ac Mon Sep 17 00:00:00 2001 From: JulissaDantes Date: Wed, 25 Jan 2023 15:39:02 -0500 Subject: [PATCH 016/182] Add ERC1155.totalSupply that returns overall supply count (#3962) --- .changeset/chilled-spiders-attack.md | 5 +++ .../ERC1155/extensions/ERC1155Supply.sol | 22 ++++++++++- .../ERC1155/extensions/ERC1155Supply.test.js | 37 +++++++++++++++---- 3 files changed, 55 insertions(+), 9 deletions(-) create mode 100644 .changeset/chilled-spiders-attack.md diff --git a/.changeset/chilled-spiders-attack.md b/.changeset/chilled-spiders-attack.md new file mode 100644 index 000000000..ef3fc4f55 --- /dev/null +++ b/.changeset/chilled-spiders-attack.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': major +--- + +`ERC1155Supply`: add a `totalSupply()` function that returns the total amount of token circulating, this change will restrict the total tokens minted across all ids to 2\*\*256-1 . diff --git a/contracts/token/ERC1155/extensions/ERC1155Supply.sol b/contracts/token/ERC1155/extensions/ERC1155Supply.sol index 77690b59d..6596c4aed 100644 --- a/contracts/token/ERC1155/extensions/ERC1155Supply.sol +++ b/contracts/token/ERC1155/extensions/ERC1155Supply.sol @@ -15,6 +15,7 @@ import "../ERC1155.sol"; */ abstract contract ERC1155Supply is ERC1155 { mapping(uint256 => uint256) private _totalSupply; + uint256 private _totalSupplyAll; /** * @dev Total amount of tokens in with a given id. @@ -23,6 +24,13 @@ abstract contract ERC1155Supply is ERC1155 { return _totalSupply[id]; } + /** + * @dev Total amount of tokens. + */ + function totalSupply() public view virtual returns (uint256) { + return _totalSupplyAll; + } + /** * @dev Indicates whether any token exist with a given id, or not. */ @@ -41,21 +49,33 @@ abstract contract ERC1155Supply is ERC1155 { bytes memory data ) internal virtual override { if (from == address(0)) { + uint256 totalMintAmount = 0; for (uint256 i = 0; i < ids.length; ++i) { - _totalSupply[ids[i]] += amounts[i]; + uint256 amount = amounts[i]; + _totalSupply[ids[i]] += amount; + totalMintAmount += amount; } + _totalSupplyAll += totalMintAmount; } if (to == address(0)) { + uint256 totalBurnAmount = 0; for (uint256 i = 0; i < ids.length; ++i) { uint256 id = ids[i]; uint256 amount = amounts[i]; uint256 supply = _totalSupply[id]; require(supply >= amount, "ERC1155: burn amount exceeds totalSupply"); unchecked { + // Overflow not possible: amounts[i] <= totalSupply(i) _totalSupply[id] = supply - amount; + // Overflow not possible: sum(amounts[i]) <= sum(totalSupply(i)) <= totalSupplyAll + totalBurnAmount += amount; } } + unchecked { + // Overflow not possible: totalBurnAmount = sum(amounts[i]) <= sum(totalSupply(i)) <= totalSupplyAll + _totalSupplyAll -= totalBurnAmount; + } } super._update(from, to, ids, amounts, data); } diff --git a/test/token/ERC1155/extensions/ERC1155Supply.test.js b/test/token/ERC1155/extensions/ERC1155Supply.test.js index 721d5a782..22a75c84f 100644 --- a/test/token/ERC1155/extensions/ERC1155Supply.test.js +++ b/test/token/ERC1155/extensions/ERC1155Supply.test.js @@ -1,7 +1,9 @@ -const { BN } = require('@openzeppelin/test-helpers'); +const { BN, constants } = require('@openzeppelin/test-helpers'); const { expect } = require('chai'); +const { ZERO_ADDRESS } = constants; + const ERC1155Supply = artifacts.require('$ERC1155Supply'); contract('ERC1155Supply', function (accounts) { @@ -25,7 +27,8 @@ contract('ERC1155Supply', function (accounts) { }); it('totalSupply', async function () { - expect(await this.token.totalSupply(firstTokenId)).to.be.bignumber.equal('0'); + expect(await this.token.methods['totalSupply(uint256)'](firstTokenId)).to.be.bignumber.equal('0'); + expect(await this.token.methods['totalSupply()']()).to.be.bignumber.equal('0'); }); }); @@ -40,7 +43,8 @@ contract('ERC1155Supply', function (accounts) { }); it('totalSupply', async function () { - expect(await this.token.totalSupply(firstTokenId)).to.be.bignumber.equal(firstTokenAmount); + expect(await this.token.methods['totalSupply(uint256)'](firstTokenId)).to.be.bignumber.equal(firstTokenAmount); + expect(await this.token.methods['totalSupply()']()).to.be.bignumber.equal(firstTokenAmount); }); }); @@ -60,8 +64,13 @@ contract('ERC1155Supply', function (accounts) { }); it('totalSupply', async function () { - expect(await this.token.totalSupply(firstTokenId)).to.be.bignumber.equal(firstTokenAmount); - expect(await this.token.totalSupply(secondTokenId)).to.be.bignumber.equal(secondTokenAmount); + expect(await this.token.methods['totalSupply(uint256)'](firstTokenId)).to.be.bignumber.equal(firstTokenAmount); + expect(await this.token.methods['totalSupply(uint256)'](secondTokenId)).to.be.bignumber.equal( + secondTokenAmount, + ); + expect(await this.token.methods['totalSupply()']()).to.be.bignumber.equal( + firstTokenAmount.add(secondTokenAmount), + ); }); }); }); @@ -78,7 +87,8 @@ contract('ERC1155Supply', function (accounts) { }); it('totalSupply', async function () { - expect(await this.token.totalSupply(firstTokenId)).to.be.bignumber.equal('0'); + expect(await this.token.methods['totalSupply(uint256)'](firstTokenId)).to.be.bignumber.equal('0'); + expect(await this.token.methods['totalSupply()']()).to.be.bignumber.equal('0'); }); }); @@ -99,9 +109,20 @@ contract('ERC1155Supply', function (accounts) { }); it('totalSupply', async function () { - expect(await this.token.totalSupply(firstTokenId)).to.be.bignumber.equal('0'); - expect(await this.token.totalSupply(secondTokenId)).to.be.bignumber.equal('0'); + expect(await this.token.methods['totalSupply(uint256)'](firstTokenId)).to.be.bignumber.equal('0'); + expect(await this.token.methods['totalSupply(uint256)'](secondTokenId)).to.be.bignumber.equal('0'); + expect(await this.token.methods['totalSupply()']()).to.be.bignumber.equal('0'); + }); + }); + }); + + context('other', function () { + it('supply unaffected by no-op', async function () { + this.token.safeTransferFrom(ZERO_ADDRESS, ZERO_ADDRESS, firstTokenId, firstTokenAmount, '0x', { + from: ZERO_ADDRESS, }); + expect(await this.token.methods['totalSupply(uint256)'](firstTokenId)).to.be.bignumber.equal('0'); + expect(await this.token.methods['totalSupply()']()).to.be.bignumber.equal('0'); }); }); }); From 2d05db171a99e93132abef98bda15534129f7805 Mon Sep 17 00:00:00 2001 From: Francisco Giordano Date: Tue, 31 Jan 2023 17:27:05 -0300 Subject: [PATCH 017/182] Add note about supply limit in ERC1155Supply --- contracts/token/ERC1155/extensions/ERC1155Supply.sol | 3 +++ 1 file changed, 3 insertions(+) diff --git a/contracts/token/ERC1155/extensions/ERC1155Supply.sol b/contracts/token/ERC1155/extensions/ERC1155Supply.sol index 6596c4aed..fadc8f9ff 100644 --- a/contracts/token/ERC1155/extensions/ERC1155Supply.sol +++ b/contracts/token/ERC1155/extensions/ERC1155Supply.sol @@ -12,6 +12,9 @@ import "../ERC1155.sol"; * clearly identified. Note: While a totalSupply of 1 might mean the * corresponding is an NFT, there is no guarantees that no other token with the * same id are not going to be minted. + * + * NOTE: This contract implies a global limit of 2**256 - 1 to the number of tokens + * that can be minted. */ abstract contract ERC1155Supply is ERC1155 { mapping(uint256 => uint256) private _totalSupply; From 7e7060e00e107460fc57c178859d3cf0c6ac64ef Mon Sep 17 00:00:00 2001 From: Antonio Viggiano Date: Thu, 30 Mar 2023 15:57:09 -0300 Subject: [PATCH 018/182] Update IERC3156FlashBorrower.sol (#4145) --- contracts/interfaces/IERC3156FlashBorrower.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/interfaces/IERC3156FlashBorrower.sol b/contracts/interfaces/IERC3156FlashBorrower.sol index c3b4f1eb1..0428391fc 100644 --- a/contracts/interfaces/IERC3156FlashBorrower.sol +++ b/contracts/interfaces/IERC3156FlashBorrower.sol @@ -17,7 +17,7 @@ interface IERC3156FlashBorrower { * @param amount The amount of tokens lent. * @param fee The additional amount of tokens to repay. * @param data Arbitrary data structure, intended to contain user-defined parameters. - * @return The keccak256 hash of "IERC3156FlashBorrower.onFlashLoan" + * @return The keccak256 hash of "ERC3156FlashBorrower.onFlashLoan" */ function onFlashLoan( address initiator, From ead3bcaccbc0ae02f0aaa1b294b432ac55f758cf Mon Sep 17 00:00:00 2001 From: Francisco Date: Tue, 4 Apr 2023 23:05:39 -0300 Subject: [PATCH 019/182] Fix spurious CI check failures (#4160) --- .github/workflows/checks.yml | 2 ++ foundry.toml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index c37bf935b..290c6a943 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -83,6 +83,8 @@ jobs: uses: ./.github/actions/setup - run: rm foundry.toml - uses: crytic/slither-action@v0.3.0 + with: + node-version: 18 codespell: if: github.repository != 'OpenZeppelin/openzeppelin-contracts-upgradeable' diff --git a/foundry.toml b/foundry.toml index c0da48773..d0aa4ea39 100644 --- a/foundry.toml +++ b/foundry.toml @@ -1,3 +1,3 @@ [fuzz] runs = 10000 -max_test_rejects = 100000 +max_test_rejects = 150000 From 5523c1482bd0a503e4c4dfe821bd2f6b523f3c86 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Wed, 5 Apr 2023 16:57:08 +0200 Subject: [PATCH 020/182] Fix TransparentUpgradeableProxy's transparency (#4154) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Francisco Co-authored-by: Ernesto García --- .changeset/thirty-shrimps-mix.md | 5 + contracts/interfaces/IERC1967.sol | 25 +++++ contracts/proxy/ERC1967/ERC1967Upgrade.sol | 18 +-- contracts/proxy/transparent/ProxyAdmin.sol | 10 +- .../TransparentUpgradeableProxy.sol | 103 +++++++++++++----- test/proxy/transparent/ProxyAdmin.test.js | 4 +- .../TransparentUpgradeableProxy.behaviour.js | 22 ++-- .../TransparentUpgradeableProxy.test.js | 4 +- 8 files changed, 132 insertions(+), 59 deletions(-) create mode 100644 .changeset/thirty-shrimps-mix.md create mode 100644 contracts/interfaces/IERC1967.sol diff --git a/.changeset/thirty-shrimps-mix.md b/.changeset/thirty-shrimps-mix.md new file mode 100644 index 000000000..54256d52c --- /dev/null +++ b/.changeset/thirty-shrimps-mix.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': patch +--- + +`TransparentUpgradeableProxy`: Fix transparency in case of selector clash with non-decodable calldata. diff --git a/contracts/interfaces/IERC1967.sol b/contracts/interfaces/IERC1967.sol new file mode 100644 index 000000000..e5deebee9 --- /dev/null +++ b/contracts/interfaces/IERC1967.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +/** + * @dev ERC-1967: Proxy Storage Slots. This interface contains the events defined in the ERC. + * + * _Available since v4.9._ + */ +interface IERC1967 { + /** + * @dev Emitted when the implementation is upgraded. + */ + event Upgraded(address indexed implementation); + + /** + * @dev Emitted when the admin account has changed. + */ + event AdminChanged(address previousAdmin, address newAdmin); + + /** + * @dev Emitted when the beacon is changed. + */ + event BeaconUpgraded(address indexed beacon); +} diff --git a/contracts/proxy/ERC1967/ERC1967Upgrade.sol b/contracts/proxy/ERC1967/ERC1967Upgrade.sol index 0680f3549..a79c10502 100644 --- a/contracts/proxy/ERC1967/ERC1967Upgrade.sol +++ b/contracts/proxy/ERC1967/ERC1967Upgrade.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.2; import "../beacon/IBeacon.sol"; +import "../../interfaces/IERC1967.sol"; import "../../interfaces/draft-IERC1822.sol"; import "../../utils/Address.sol"; import "../../utils/StorageSlot.sol"; @@ -14,7 +15,7 @@ import "../../utils/StorageSlot.sol"; * * _Available since v4.1._ */ -abstract contract ERC1967Upgrade { +abstract contract ERC1967Upgrade is IERC1967 { // This is the keccak-256 hash of "eip1967.proxy.rollback" subtracted by 1 bytes32 private constant _ROLLBACK_SLOT = 0x4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd9143; @@ -25,11 +26,6 @@ abstract contract ERC1967Upgrade { */ bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; - /** - * @dev Emitted when the implementation is upgraded. - */ - event Upgraded(address indexed implementation); - /** * @dev Returns the current implementation address. */ @@ -95,11 +91,6 @@ abstract contract ERC1967Upgrade { */ bytes32 internal constant _ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; - /** - * @dev Emitted when the admin account has changed. - */ - event AdminChanged(address previousAdmin, address newAdmin); - /** * @dev Returns the current admin. */ @@ -131,11 +122,6 @@ abstract contract ERC1967Upgrade { */ bytes32 internal constant _BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50; - /** - * @dev Emitted when the beacon is upgraded. - */ - event BeaconUpgraded(address indexed beacon); - /** * @dev Returns the current beacon. */ diff --git a/contracts/proxy/transparent/ProxyAdmin.sol b/contracts/proxy/transparent/ProxyAdmin.sol index 839534298..1a9698196 100644 --- a/contracts/proxy/transparent/ProxyAdmin.sol +++ b/contracts/proxy/transparent/ProxyAdmin.sol @@ -18,7 +18,7 @@ contract ProxyAdmin is Ownable { * * - This contract must be the admin of `proxy`. */ - function getProxyImplementation(TransparentUpgradeableProxy proxy) public view virtual returns (address) { + function getProxyImplementation(ITransparentUpgradeableProxy proxy) public view virtual returns (address) { // We need to manually run the static call since the getter cannot be flagged as view // bytes4(keccak256("implementation()")) == 0x5c60da1b (bool success, bytes memory returndata) = address(proxy).staticcall(hex"5c60da1b"); @@ -33,7 +33,7 @@ contract ProxyAdmin is Ownable { * * - This contract must be the admin of `proxy`. */ - function getProxyAdmin(TransparentUpgradeableProxy proxy) public view virtual returns (address) { + function getProxyAdmin(ITransparentUpgradeableProxy proxy) public view virtual returns (address) { // We need to manually run the static call since the getter cannot be flagged as view // bytes4(keccak256("admin()")) == 0xf851a440 (bool success, bytes memory returndata) = address(proxy).staticcall(hex"f851a440"); @@ -48,7 +48,7 @@ contract ProxyAdmin is Ownable { * * - This contract must be the current admin of `proxy`. */ - function changeProxyAdmin(TransparentUpgradeableProxy proxy, address newAdmin) public virtual onlyOwner { + function changeProxyAdmin(ITransparentUpgradeableProxy proxy, address newAdmin) public virtual onlyOwner { proxy.changeAdmin(newAdmin); } @@ -59,7 +59,7 @@ contract ProxyAdmin is Ownable { * * - This contract must be the admin of `proxy`. */ - function upgrade(TransparentUpgradeableProxy proxy, address implementation) public virtual onlyOwner { + function upgrade(ITransparentUpgradeableProxy proxy, address implementation) public virtual onlyOwner { proxy.upgradeTo(implementation); } @@ -72,7 +72,7 @@ contract ProxyAdmin is Ownable { * - This contract must be the admin of `proxy`. */ function upgradeAndCall( - TransparentUpgradeableProxy proxy, + ITransparentUpgradeableProxy proxy, address implementation, bytes memory data ) public payable virtual onlyOwner { diff --git a/contracts/proxy/transparent/TransparentUpgradeableProxy.sol b/contracts/proxy/transparent/TransparentUpgradeableProxy.sol index 155a22e01..a89b233f2 100644 --- a/contracts/proxy/transparent/TransparentUpgradeableProxy.sol +++ b/contracts/proxy/transparent/TransparentUpgradeableProxy.sol @@ -5,6 +5,24 @@ pragma solidity ^0.8.0; import "../ERC1967/ERC1967Proxy.sol"; +/** + * @dev Interface for the {TransparentUpgradeableProxy}. This is useful because {TransparentUpgradeableProxy} uses a + * custom call-routing mechanism, the compiler is unaware of the functions being exposed, and cannot list them. Also + * {TransparentUpgradeableProxy} does not inherit from this interface because it's implemented in a way that the + * compiler doesn't understand and cannot verify. + */ +interface ITransparentUpgradeableProxy is IERC1967 { + function admin() external view returns (address); + + function implementation() external view returns (address); + + function changeAdmin(address) external; + + function upgradeTo(address) external; + + function upgradeToAndCall(address, bytes memory) external payable; +} + /** * @dev This contract implements a proxy that is upgradeable by an admin. * @@ -25,6 +43,13 @@ import "../ERC1967/ERC1967Proxy.sol"; * * Our recommendation is for the dedicated account to be an instance of the {ProxyAdmin} contract. If set up this way, * you should think of the `ProxyAdmin` instance as the real administrative interface of your proxy. + * + * WARNING: This contract does not inherit from {ITransparentUpgradeableProxy}, and the admin function is implicitly + * implemented using a custom call-routing mechanism in `_fallback`. Consequently, the compiler will not produce an + * ABI for this contract. Also, if you inherit from this contract and add additional functions, the compiler will not + * check that there are no selector conflicts. A selector clash between any new function and the functions declared in + * {ITransparentUpgradeableProxy} will be resolved in favor of the new one. This could render the admin operations + * inaccessible, which could prevent upgradeability. */ contract TransparentUpgradeableProxy is ERC1967Proxy { /** @@ -37,6 +62,9 @@ contract TransparentUpgradeableProxy is ERC1967Proxy { /** * @dev Modifier used internally that will delegate the call to the implementation unless the sender is the admin. + * + * CAUTION: This modifier is deprecated, as it could cause issues if the modified function has arguments, and the + * implementation provides a function with the same selector. */ modifier ifAdmin() { if (msg.sender == _getAdmin()) { @@ -46,65 +74,98 @@ contract TransparentUpgradeableProxy is ERC1967Proxy { } } + /** + * @dev If caller is the admin process the call internally, otherwise transparently fallback to the proxy behavior + */ + function _fallback() internal virtual override { + if (msg.sender == _getAdmin()) { + bytes memory ret; + bytes4 selector = msg.sig; + if (selector == ITransparentUpgradeableProxy.upgradeTo.selector) { + ret = _dispatchUpgradeTo(); + } else if (selector == ITransparentUpgradeableProxy.upgradeToAndCall.selector) { + ret = _dispatchUpgradeToAndCall(); + } else if (selector == ITransparentUpgradeableProxy.changeAdmin.selector) { + ret = _dispatchChangeAdmin(); + } else if (selector == ITransparentUpgradeableProxy.admin.selector) { + ret = _dispatchAdmin(); + } else if (selector == ITransparentUpgradeableProxy.implementation.selector) { + ret = _dispatchImplementation(); + } else { + revert("TransparentUpgradeableProxy: admin cannot fallback to proxy target"); + } + assembly { + return(add(ret, 0x20), mload(ret)) + } + } else { + super._fallback(); + } + } + /** * @dev Returns the current admin. * - * NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyAdmin}. - * * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call. * `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103` */ - function admin() external payable ifAdmin returns (address admin_) { + function _dispatchAdmin() private returns (bytes memory) { _requireZeroValue(); - admin_ = _getAdmin(); + + address admin = _getAdmin(); + return abi.encode(admin); } /** * @dev Returns the current implementation. * - * NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyImplementation}. - * * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call. * `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc` */ - function implementation() external payable ifAdmin returns (address implementation_) { + function _dispatchImplementation() private returns (bytes memory) { _requireZeroValue(); - implementation_ = _implementation(); + + address implementation = _implementation(); + return abi.encode(implementation); } /** * @dev Changes the admin of the proxy. * * Emits an {AdminChanged} event. - * - * NOTE: Only the admin can call this function. See {ProxyAdmin-changeProxyAdmin}. */ - function changeAdmin(address newAdmin) external payable virtual ifAdmin { + function _dispatchChangeAdmin() private returns (bytes memory) { _requireZeroValue(); + + address newAdmin = abi.decode(msg.data[4:], (address)); _changeAdmin(newAdmin); + + return ""; } /** * @dev Upgrade the implementation of the proxy. - * - * NOTE: Only the admin can call this function. See {ProxyAdmin-upgrade}. */ - function upgradeTo(address newImplementation) external payable ifAdmin { + function _dispatchUpgradeTo() private returns (bytes memory) { _requireZeroValue(); + + address newImplementation = abi.decode(msg.data[4:], (address)); _upgradeToAndCall(newImplementation, bytes(""), false); + + return ""; } /** * @dev Upgrade the implementation of the proxy, and then call a function from the new implementation as specified * by `data`, which should be an encoded function call. This is useful to initialize new storage variables in the * proxied contract. - * - * NOTE: Only the admin can call this function. See {ProxyAdmin-upgradeAndCall}. */ - function upgradeToAndCall(address newImplementation, bytes calldata data) external payable ifAdmin { + function _dispatchUpgradeToAndCall() private returns (bytes memory) { + (address newImplementation, bytes memory data) = abi.decode(msg.data[4:], (address, bytes)); _upgradeToAndCall(newImplementation, data, true); + + return ""; } /** @@ -114,14 +175,6 @@ contract TransparentUpgradeableProxy is ERC1967Proxy { return _getAdmin(); } - /** - * @dev Makes sure the admin cannot access the fallback function. See {Proxy-_beforeFallback}. - */ - function _beforeFallback() internal virtual override { - require(msg.sender != _getAdmin(), "TransparentUpgradeableProxy: admin cannot fallback to proxy target"); - super._beforeFallback(); - } - /** * @dev To keep this contract fully transparent, all `ifAdmin` functions must be payable. This helper is here to * emulate some proxy functions being non-payable while still allowing value to pass through. diff --git a/test/proxy/transparent/ProxyAdmin.test.js b/test/proxy/transparent/ProxyAdmin.test.js index 811dc5671..6d473d893 100644 --- a/test/proxy/transparent/ProxyAdmin.test.js +++ b/test/proxy/transparent/ProxyAdmin.test.js @@ -6,6 +6,7 @@ const ImplV1 = artifacts.require('DummyImplementation'); const ImplV2 = artifacts.require('DummyImplementationV2'); const ProxyAdmin = artifacts.require('ProxyAdmin'); const TransparentUpgradeableProxy = artifacts.require('TransparentUpgradeableProxy'); +const ITransparentUpgradeableProxy = artifacts.require('ITransparentUpgradeableProxy'); contract('ProxyAdmin', function (accounts) { const [proxyAdminOwner, newAdmin, anotherAccount] = accounts; @@ -18,12 +19,13 @@ contract('ProxyAdmin', function (accounts) { beforeEach(async function () { const initializeData = Buffer.from(''); this.proxyAdmin = await ProxyAdmin.new({ from: proxyAdminOwner }); - this.proxy = await TransparentUpgradeableProxy.new( + const proxy = await TransparentUpgradeableProxy.new( this.implementationV1.address, this.proxyAdmin.address, initializeData, { from: proxyAdminOwner }, ); + this.proxy = await ITransparentUpgradeableProxy.at(proxy.address); }); it('has an owner', async function () { diff --git a/test/proxy/transparent/TransparentUpgradeableProxy.behaviour.js b/test/proxy/transparent/TransparentUpgradeableProxy.behaviour.js index 3a10357a9..66c0a406c 100644 --- a/test/proxy/transparent/TransparentUpgradeableProxy.behaviour.js +++ b/test/proxy/transparent/TransparentUpgradeableProxy.behaviour.js @@ -34,7 +34,7 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy(createProx describe('implementation', function () { it('returns the current implementation address', async function () { - const implementation = await this.proxy.implementation.call({ from: proxyAdminAddress }); + const implementation = await this.proxy.implementation({ from: proxyAdminAddress }); expect(implementation).to.be.equal(this.implementationV0); }); @@ -55,7 +55,7 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy(createProx it('upgrades to the requested implementation', async function () { await this.proxy.upgradeTo(this.implementationV1, { from }); - const implementation = await this.proxy.implementation.call({ from: proxyAdminAddress }); + const implementation = await this.proxy.implementation({ from: proxyAdminAddress }); expect(implementation).to.be.equal(this.implementationV1); }); @@ -103,7 +103,7 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy(createProx }); it('upgrades to the requested implementation', async function () { - const implementation = await this.proxy.implementation.call({ from: proxyAdminAddress }); + const implementation = await this.proxy.implementation({ from: proxyAdminAddress }); expect(implementation).to.be.equal(this.behavior.address); }); @@ -168,7 +168,7 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy(createProx }); it('upgrades to the requested version and emits an event', async function () { - const implementation = await this.proxy.implementation.call({ from: proxyAdminAddress }); + const implementation = await this.proxy.implementation({ from: proxyAdminAddress }); expect(implementation).to.be.equal(this.behaviorV1.address); expectEvent(this.receipt, 'Upgraded', { implementation: this.behaviorV1.address }); }); @@ -196,7 +196,7 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy(createProx }); it('upgrades to the requested version and emits an event', async function () { - const implementation = await this.proxy.implementation.call({ from: proxyAdminAddress }); + const implementation = await this.proxy.implementation({ from: proxyAdminAddress }); expect(implementation).to.be.equal(this.behaviorV2.address); expectEvent(this.receipt, 'Upgraded', { implementation: this.behaviorV2.address }); }); @@ -227,7 +227,7 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy(createProx }); it('upgrades to the requested version and emits an event', async function () { - const implementation = await this.proxy.implementation.call({ from: proxyAdminAddress }); + const implementation = await this.proxy.implementation({ from: proxyAdminAddress }); expect(implementation).to.be.equal(this.behaviorV3.address); expectEvent(this.receipt, 'Upgraded', { implementation: this.behaviorV3.address }); }); @@ -271,7 +271,7 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy(createProx }); it('assigns new proxy admin', async function () { - const newProxyAdmin = await this.proxy.admin.call({ from: newAdmin }); + const newProxyAdmin = await this.proxy.admin({ from: newAdmin }); expect(newProxyAdmin).to.be.equal(anotherAccount); }); @@ -332,21 +332,21 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy(createProx describe('when function names clash', function () { it('when sender is proxy admin should run the proxy function', async function () { - const value = await this.proxy.admin.call({ from: proxyAdminAddress, value: 0 }); + const value = await this.proxy.admin({ from: proxyAdminAddress, value: 0 }); expect(value).to.be.equal(proxyAdminAddress); }); it('when sender is other should delegate to implementation', async function () { - const value = await this.proxy.admin.call({ from: anotherAccount, value: 0 }); + const value = await this.proxy.admin({ from: anotherAccount, value: 0 }); expect(value).to.be.equal('0x0000000000000000000000000000000011111142'); }); it('when sender is proxy admin value should not be accepted', async function () { - await expectRevert.unspecified(this.proxy.admin.call({ from: proxyAdminAddress, value: 1 })); + await expectRevert.unspecified(this.proxy.admin({ from: proxyAdminAddress, value: 1 })); }); it('when sender is other value should be accepted', async function () { - const value = await this.proxy.admin.call({ from: anotherAccount, value: 1 }); + const value = await this.proxy.admin({ from: anotherAccount, value: 1 }); expect(value).to.be.equal('0x0000000000000000000000000000000011111142'); }); }); diff --git a/test/proxy/transparent/TransparentUpgradeableProxy.test.js b/test/proxy/transparent/TransparentUpgradeableProxy.test.js index 86dd55d32..d60a31a21 100644 --- a/test/proxy/transparent/TransparentUpgradeableProxy.test.js +++ b/test/proxy/transparent/TransparentUpgradeableProxy.test.js @@ -2,12 +2,14 @@ const shouldBehaveLikeProxy = require('../Proxy.behaviour'); const shouldBehaveLikeTransparentUpgradeableProxy = require('./TransparentUpgradeableProxy.behaviour'); const TransparentUpgradeableProxy = artifacts.require('TransparentUpgradeableProxy'); +const ITransparentUpgradeableProxy = artifacts.require('ITransparentUpgradeableProxy'); contract('TransparentUpgradeableProxy', function (accounts) { const [proxyAdminAddress, proxyAdminOwner] = accounts; const createProxy = async function (logic, admin, initData, opts) { - return TransparentUpgradeableProxy.new(logic, admin, initData, opts); + const { address } = await TransparentUpgradeableProxy.new(logic, admin, initData, opts); + return ITransparentUpgradeableProxy.at(address); }; shouldBehaveLikeProxy(createProxy, proxyAdminAddress, proxyAdminOwner); From 31723ed608878c2e30710a9c46b51d529c0ba958 Mon Sep 17 00:00:00 2001 From: Renan Souza Date: Wed, 5 Apr 2023 15:47:18 -0300 Subject: [PATCH 021/182] Reenable skipped TransparentUpgradeableProxy test (#4161) Co-authored-by: Francisco --- .../TransparentUpgradeableProxy.behaviour.js | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/test/proxy/transparent/TransparentUpgradeableProxy.behaviour.js b/test/proxy/transparent/TransparentUpgradeableProxy.behaviour.js index 66c0a406c..5e3f561d5 100644 --- a/test/proxy/transparent/TransparentUpgradeableProxy.behaviour.js +++ b/test/proxy/transparent/TransparentUpgradeableProxy.behaviour.js @@ -3,8 +3,8 @@ const { ZERO_ADDRESS } = constants; const { getSlot, ImplementationSlot, AdminSlot } = require('../../helpers/erc1967'); const { expect } = require('chai'); +const { web3 } = require('hardhat'); -const Proxy = artifacts.require('Proxy'); const Implementation1 = artifacts.require('Implementation1'); const Implementation2 = artifacts.require('Implementation2'); const Implementation3 = artifacts.require('Implementation3'); @@ -122,13 +122,11 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy(createProx expect(balance.toString()).to.be.bignumber.equal(value.toString()); }); - it.skip('uses the storage of the proxy', async function () { + it('uses the storage of the proxy', async function () { // storage layout should look as follows: - // - 0: Initializable storage - // - 1-50: Initailizable reserved storage (50 slots) - // - 51: initializerRan - // - 52: x - const storedValue = await Proxy.at(this.proxyAddress).getStorageAt(52); + // - 0: Initializable storage ++ initializerRan ++ onlyInitializingRan + // - 1: x + const storedValue = await web3.eth.getStorageAt(this.proxyAddress, 1); expect(parseInt(storedValue)).to.eq(42); }); }); From cf86fd9962701396457e50ab0d6cc78aa29a5ebc Mon Sep 17 00:00:00 2001 From: Francisco Date: Wed, 5 Apr 2023 17:20:34 -0300 Subject: [PATCH 022/182] Merge changesets for transparency improvements (#4165) --- .changeset/many-panthers-hide.md | 5 ----- .changeset/thirty-shrimps-mix.md | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) delete mode 100644 .changeset/many-panthers-hide.md diff --git a/.changeset/many-panthers-hide.md b/.changeset/many-panthers-hide.md deleted file mode 100644 index 5f04c99df..000000000 --- a/.changeset/many-panthers-hide.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`TransparentUpgradeableProxy`: support value passthrough for all ifAdmin function. diff --git a/.changeset/thirty-shrimps-mix.md b/.changeset/thirty-shrimps-mix.md index 54256d52c..656edbf92 100644 --- a/.changeset/thirty-shrimps-mix.md +++ b/.changeset/thirty-shrimps-mix.md @@ -2,4 +2,4 @@ 'openzeppelin-solidity': patch --- -`TransparentUpgradeableProxy`: Fix transparency in case of selector clash with non-decodable calldata. +`TransparentUpgradeableProxy`: Fix transparency in case of selector clash with non-decodable calldata or payable mutability. From f2346b6749546707af883e2c0acbc8a487e16ae4 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Tue, 11 Apr 2023 11:21:53 +0200 Subject: [PATCH 023/182] Add fuzz tests for the Checkpoints library (#4146) Co-authored-by: Francisco --- scripts/generate/run.js | 28 +- scripts/generate/templates/Checkpoints.js | 23 +- .../generate/templates/Checkpoints.opts.js | 22 ++ scripts/generate/templates/Checkpoints.t.js | 252 +++++++++++++ test/utils/Checkpoints.t.sol | 341 ++++++++++++++++++ 5 files changed, 636 insertions(+), 30 deletions(-) create mode 100644 scripts/generate/templates/Checkpoints.opts.js create mode 100644 scripts/generate/templates/Checkpoints.t.js create mode 100644 test/utils/Checkpoints.t.sol diff --git a/scripts/generate/run.js b/scripts/generate/run.js index e68681e9d..368b53ad1 100755 --- a/scripts/generate/run.js +++ b/scripts/generate/run.js @@ -13,16 +13,10 @@ function getVersion(path) { } } -for (const [file, template] of Object.entries({ - 'utils/math/SafeCast.sol': './templates/SafeCast.js', - 'utils/structs/EnumerableSet.sol': './templates/EnumerableSet.js', - 'utils/structs/EnumerableMap.sol': './templates/EnumerableMap.js', - 'utils/Checkpoints.sol': './templates/Checkpoints.js', - 'utils/StorageSlot.sol': './templates/StorageSlot.js', -})) { +function generateFromTemplate(file, template, outputPrefix = '') { const script = path.relative(path.join(__dirname, '../..'), __filename); const input = path.join(path.dirname(script), template); - const output = `./contracts/${file}`; + const output = path.join(outputPrefix, file); const version = getVersion(output); const content = format( '// SPDX-License-Identifier: MIT', @@ -35,3 +29,21 @@ for (const [file, template] of Object.entries({ fs.writeFileSync(output, content); cp.execFileSync('prettier', ['--write', output]); } + +// Contracts +for (const [file, template] of Object.entries({ + 'utils/math/SafeCast.sol': './templates/SafeCast.js', + 'utils/structs/EnumerableSet.sol': './templates/EnumerableSet.js', + 'utils/structs/EnumerableMap.sol': './templates/EnumerableMap.js', + 'utils/Checkpoints.sol': './templates/Checkpoints.js', + 'utils/StorageSlot.sol': './templates/StorageSlot.js', +})) { + generateFromTemplate(file, template, './contracts/'); +} + +// Tests +for (const [file, template] of Object.entries({ + 'utils/Checkpoints.t.sol': './templates/Checkpoints.t.js', +})) { + generateFromTemplate(file, template, './test/'); +} diff --git a/scripts/generate/templates/Checkpoints.js b/scripts/generate/templates/Checkpoints.js index e51e8b8d1..18999682d 100644 --- a/scripts/generate/templates/Checkpoints.js +++ b/scripts/generate/templates/Checkpoints.js @@ -1,26 +1,5 @@ const format = require('../format-lines'); - -// OPTIONS -const defaultOpts = size => ({ - historyTypeName: `Trace${size}`, - checkpointTypeName: `Checkpoint${size}`, - checkpointFieldName: '_checkpoints', - keyTypeName: `uint${256 - size}`, - keyFieldName: '_key', - valueTypeName: `uint${size}`, - valueFieldName: '_value', -}); - -const VALUE_SIZES = [224, 160]; - -const OPTS = VALUE_SIZES.map(size => defaultOpts(size)); - -const LEGACY_OPTS = { - ...defaultOpts(224), - historyTypeName: 'History', - checkpointTypeName: 'Checkpoint', - keyFieldName: '_blockNumber', -}; +const { OPTS, LEGACY_OPTS } = require('./Checkpoints.opts.js'); // TEMPLATE const header = `\ diff --git a/scripts/generate/templates/Checkpoints.opts.js b/scripts/generate/templates/Checkpoints.opts.js new file mode 100644 index 000000000..03c5a9569 --- /dev/null +++ b/scripts/generate/templates/Checkpoints.opts.js @@ -0,0 +1,22 @@ +// OPTIONS +const VALUE_SIZES = [224, 160]; + +const defaultOpts = size => ({ + historyTypeName: `Trace${size}`, + checkpointTypeName: `Checkpoint${size}`, + checkpointFieldName: '_checkpoints', + keyTypeName: `uint${256 - size}`, + keyFieldName: '_key', + valueTypeName: `uint${size}`, + valueFieldName: '_value', +}); + +module.exports = { + OPTS: VALUE_SIZES.map(size => defaultOpts(size)), + LEGACY_OPTS: { + ...defaultOpts(224), + historyTypeName: 'History', + checkpointTypeName: 'Checkpoint', + keyFieldName: '_blockNumber', + }, +}; diff --git a/scripts/generate/templates/Checkpoints.t.js b/scripts/generate/templates/Checkpoints.t.js new file mode 100644 index 000000000..84b5992ad --- /dev/null +++ b/scripts/generate/templates/Checkpoints.t.js @@ -0,0 +1,252 @@ +const format = require('../format-lines'); +const { capitalize } = require('../../helpers'); +const { OPTS, LEGACY_OPTS } = require('./Checkpoints.opts.js'); + +// TEMPLATE +const header = `\ +pragma solidity ^0.8.0; + +import "forge-std/Test.sol"; +import "../../contracts/utils/Checkpoints.sol"; +import "../../contracts/utils/math/SafeCast.sol"; +`; + +/* eslint-disable max-len */ +const common = opts => `\ +using Checkpoints for Checkpoints.${opts.historyTypeName}; + +// Maximum gap between keys used during the fuzzing tests: the \`_prepareKeys\` function with make sure that +// key#n+1 is in the [key#n, key#n + _KEY_MAX_GAP] range. +uint8 internal constant _KEY_MAX_GAP = 64; + +Checkpoints.${opts.historyTypeName} internal _ckpts; + +// helpers +function _bound${capitalize(opts.keyTypeName)}( + ${opts.keyTypeName} x, + ${opts.keyTypeName} min, + ${opts.keyTypeName} max +) internal view returns (${opts.keyTypeName}) { + return SafeCast.to${capitalize(opts.keyTypeName)}(bound(uint256(x), uint256(min), uint256(max))); +} + +function _prepareKeys( + ${opts.keyTypeName}[] memory keys, + ${opts.keyTypeName} maxSpread +) internal view { + ${opts.keyTypeName} lastKey = 0; + for (uint256 i = 0; i < keys.length; ++i) { + ${opts.keyTypeName} key = _bound${capitalize(opts.keyTypeName)}(keys[i], lastKey, lastKey + maxSpread); + keys[i] = key; + lastKey = key; + } +} + +function _assertLatestCheckpoint( + bool exist, + ${opts.keyTypeName} key, + ${opts.valueTypeName} value +) internal { + (bool _exist, ${opts.keyTypeName} _key, ${opts.valueTypeName} _value) = _ckpts.latestCheckpoint(); + assertEq(_exist, exist); + assertEq(_key, key); + assertEq(_value, value); +} +`; + +const testTrace = opts => `\ +// tests +function testPush( + ${opts.keyTypeName}[] memory keys, + ${opts.valueTypeName}[] memory values, + ${opts.keyTypeName} pastKey +) public { + vm.assume(values.length > 0 && values.length <= keys.length); + _prepareKeys(keys, _KEY_MAX_GAP); + + // initial state + assertEq(_ckpts.length(), 0); + assertEq(_ckpts.latest(), 0); + _assertLatestCheckpoint(false, 0, 0); + + uint256 duplicates = 0; + for (uint256 i = 0; i < keys.length; ++i) { + ${opts.keyTypeName} key = keys[i]; + ${opts.valueTypeName} value = values[i % values.length]; + if (i > 0 && key == keys[i-1]) ++duplicates; + + // push + _ckpts.push(key, value); + + // check length & latest + assertEq(_ckpts.length(), i + 1 - duplicates); + assertEq(_ckpts.latest(), value); + _assertLatestCheckpoint(true, key, value); + } + + if (keys.length > 0) { + ${opts.keyTypeName} lastKey = keys[keys.length - 1]; + pastKey = _bound${capitalize(opts.keyTypeName)}(pastKey, 0, lastKey - 1); + + vm.expectRevert(); + this.push(pastKey, values[keys.length % values.length]); + } +} + +// used to test reverts +function push(${opts.keyTypeName} key, ${opts.valueTypeName} value) external { + _ckpts.push(key, value); +} + +function testLookup( + ${opts.keyTypeName}[] memory keys, + ${opts.valueTypeName}[] memory values, + ${opts.keyTypeName} lookup +) public { + vm.assume(values.length > 0 && values.length <= keys.length); + _prepareKeys(keys, _KEY_MAX_GAP); + + ${opts.keyTypeName} lastKey = keys.length == 0 ? 0 : keys[keys.length - 1]; + lookup = _bound${capitalize(opts.keyTypeName)}(lookup, 0, lastKey + _KEY_MAX_GAP); + + ${opts.valueTypeName} upper = 0; + ${opts.valueTypeName} lower = 0; + ${opts.keyTypeName} lowerKey = type(${opts.keyTypeName}).max; + for (uint256 i = 0; i < keys.length; ++i) { + ${opts.keyTypeName} key = keys[i]; + ${opts.valueTypeName} value = values[i % values.length]; + + // push + _ckpts.push(key, value); + + // track expected result of lookups + if (key <= lookup) { + upper = value; + } + // find the first key that is not smaller than the lookup key + if (key >= lookup && (i == 0 || keys[i-1] < lookup)) { + lowerKey = key; + } + if (key == lowerKey) { + lower = value; + } + } + + // check lookup + assertEq(_ckpts.lowerLookup(lookup), lower); + assertEq(_ckpts.upperLookup(lookup), upper); + assertEq(_ckpts.upperLookupRecent(lookup), upper); +} +`; + +const testHistory = opts => `\ +// tests +function testPush( + ${opts.keyTypeName}[] memory keys, + ${opts.valueTypeName}[] memory values, + ${opts.keyTypeName} pastKey +) public { + vm.assume(values.length > 0 && values.length <= keys.length); + _prepareKeys(keys, _KEY_MAX_GAP); + + // initial state + assertEq(_ckpts.length(), 0); + assertEq(_ckpts.latest(), 0); + _assertLatestCheckpoint(false, 0, 0); + + uint256 duplicates = 0; + for (uint256 i = 0; i < keys.length; ++i) { + ${opts.keyTypeName} key = keys[i]; + ${opts.valueTypeName} value = values[i % values.length]; + if (i > 0 && key == keys[i - 1]) ++duplicates; + + // push + vm.roll(key); + _ckpts.push(value); + + // check length & latest + assertEq(_ckpts.length(), i + 1 - duplicates); + assertEq(_ckpts.latest(), value); + _assertLatestCheckpoint(true, key, value); + } + + // Can't push any key in the past + if (keys.length > 0) { + ${opts.keyTypeName} lastKey = keys[keys.length - 1]; + pastKey = _bound${capitalize(opts.keyTypeName)}(pastKey, 0, lastKey - 1); + + vm.roll(pastKey); + vm.expectRevert(); + this.push(values[keys.length % values.length]); + } +} + +// used to test reverts +function push(${opts.valueTypeName} value) external { + _ckpts.push(value); +} + +function testLookup( + ${opts.keyTypeName}[] memory keys, + ${opts.valueTypeName}[] memory values, + ${opts.keyTypeName} lookup +) public { + vm.assume(keys.length > 0); + vm.assume(values.length > 0 && values.length <= keys.length); + _prepareKeys(keys, _KEY_MAX_GAP); + + ${opts.keyTypeName} lastKey = keys[keys.length - 1]; + vm.assume(lastKey > 0); + lookup = _bound${capitalize(opts.keyTypeName)}(lookup, 0, lastKey - 1); + + ${opts.valueTypeName} upper = 0; + for (uint256 i = 0; i < keys.length; ++i) { + ${opts.keyTypeName} key = keys[i]; + ${opts.valueTypeName} value = values[i % values.length]; + + // push + vm.roll(key); + _ckpts.push(value); + + // track expected result of lookups + if (key <= lookup) { + upper = value; + } + } + + // check lookup + assertEq(_ckpts.getAtBlock(lookup), upper); + assertEq(_ckpts.getAtProbablyRecentBlock(lookup), upper); + + vm.expectRevert(); this.getAtBlock(lastKey); + vm.expectRevert(); this.getAtBlock(lastKey + 1); + vm.expectRevert(); this.getAtProbablyRecentBlock(lastKey); + vm.expectRevert(); this.getAtProbablyRecentBlock(lastKey + 1); +} + +// used to test reverts +function getAtBlock(${opts.keyTypeName} key) external view { + _ckpts.getAtBlock(key); +} + +// used to test reverts +function getAtProbablyRecentBlock(${opts.keyTypeName} key) external view { + _ckpts.getAtProbablyRecentBlock(key); +} +`; +/* eslint-enable max-len */ + +// GENERATE +module.exports = format( + header, + // HISTORY + `contract Checkpoints${LEGACY_OPTS.historyTypeName}Test is Test {`, + [common(LEGACY_OPTS), testHistory(LEGACY_OPTS)], + '}', + // TRACEXXX + ...OPTS.flatMap(opts => [ + `contract Checkpoints${opts.historyTypeName}Test is Test {`, + [common(opts), testTrace(opts)], + '}', + ]), +); diff --git a/test/utils/Checkpoints.t.sol b/test/utils/Checkpoints.t.sol new file mode 100644 index 000000000..abdf8b436 --- /dev/null +++ b/test/utils/Checkpoints.t.sol @@ -0,0 +1,341 @@ +// SPDX-License-Identifier: MIT +// This file was procedurally generated from scripts/generate/templates/Checkpoints.t.js. + +pragma solidity ^0.8.0; + +import "forge-std/Test.sol"; +import "../../contracts/utils/Checkpoints.sol"; +import "../../contracts/utils/math/SafeCast.sol"; + +contract CheckpointsHistoryTest is Test { + using Checkpoints for Checkpoints.History; + + // Maximum gap between keys used during the fuzzing tests: the `_prepareKeys` function with make sure that + // key#n+1 is in the [key#n, key#n + _KEY_MAX_GAP] range. + uint8 internal constant _KEY_MAX_GAP = 64; + + Checkpoints.History internal _ckpts; + + // helpers + function _boundUint32(uint32 x, uint32 min, uint32 max) internal view returns (uint32) { + return SafeCast.toUint32(bound(uint256(x), uint256(min), uint256(max))); + } + + function _prepareKeys(uint32[] memory keys, uint32 maxSpread) internal view { + uint32 lastKey = 0; + for (uint256 i = 0; i < keys.length; ++i) { + uint32 key = _boundUint32(keys[i], lastKey, lastKey + maxSpread); + keys[i] = key; + lastKey = key; + } + } + + function _assertLatestCheckpoint(bool exist, uint32 key, uint224 value) internal { + (bool _exist, uint32 _key, uint224 _value) = _ckpts.latestCheckpoint(); + assertEq(_exist, exist); + assertEq(_key, key); + assertEq(_value, value); + } + + // tests + function testPush(uint32[] memory keys, uint224[] memory values, uint32 pastKey) public { + vm.assume(values.length > 0 && values.length <= keys.length); + _prepareKeys(keys, _KEY_MAX_GAP); + + // initial state + assertEq(_ckpts.length(), 0); + assertEq(_ckpts.latest(), 0); + _assertLatestCheckpoint(false, 0, 0); + + uint256 duplicates = 0; + for (uint256 i = 0; i < keys.length; ++i) { + uint32 key = keys[i]; + uint224 value = values[i % values.length]; + if (i > 0 && key == keys[i - 1]) ++duplicates; + + // push + vm.roll(key); + _ckpts.push(value); + + // check length & latest + assertEq(_ckpts.length(), i + 1 - duplicates); + assertEq(_ckpts.latest(), value); + _assertLatestCheckpoint(true, key, value); + } + + // Can't push any key in the past + if (keys.length > 0) { + uint32 lastKey = keys[keys.length - 1]; + pastKey = _boundUint32(pastKey, 0, lastKey - 1); + + vm.roll(pastKey); + vm.expectRevert(); + this.push(values[keys.length % values.length]); + } + } + + // used to test reverts + function push(uint224 value) external { + _ckpts.push(value); + } + + function testLookup(uint32[] memory keys, uint224[] memory values, uint32 lookup) public { + vm.assume(keys.length > 0); + vm.assume(values.length > 0 && values.length <= keys.length); + _prepareKeys(keys, _KEY_MAX_GAP); + + uint32 lastKey = keys[keys.length - 1]; + vm.assume(lastKey > 0); + lookup = _boundUint32(lookup, 0, lastKey - 1); + + uint224 upper = 0; + for (uint256 i = 0; i < keys.length; ++i) { + uint32 key = keys[i]; + uint224 value = values[i % values.length]; + + // push + vm.roll(key); + _ckpts.push(value); + + // track expected result of lookups + if (key <= lookup) { + upper = value; + } + } + + // check lookup + assertEq(_ckpts.getAtBlock(lookup), upper); + assertEq(_ckpts.getAtProbablyRecentBlock(lookup), upper); + + vm.expectRevert(); + this.getAtBlock(lastKey); + vm.expectRevert(); + this.getAtBlock(lastKey + 1); + vm.expectRevert(); + this.getAtProbablyRecentBlock(lastKey); + vm.expectRevert(); + this.getAtProbablyRecentBlock(lastKey + 1); + } + + // used to test reverts + function getAtBlock(uint32 key) external view { + _ckpts.getAtBlock(key); + } + + // used to test reverts + function getAtProbablyRecentBlock(uint32 key) external view { + _ckpts.getAtProbablyRecentBlock(key); + } +} + +contract CheckpointsTrace224Test is Test { + using Checkpoints for Checkpoints.Trace224; + + // Maximum gap between keys used during the fuzzing tests: the `_prepareKeys` function with make sure that + // key#n+1 is in the [key#n, key#n + _KEY_MAX_GAP] range. + uint8 internal constant _KEY_MAX_GAP = 64; + + Checkpoints.Trace224 internal _ckpts; + + // helpers + function _boundUint32(uint32 x, uint32 min, uint32 max) internal view returns (uint32) { + return SafeCast.toUint32(bound(uint256(x), uint256(min), uint256(max))); + } + + function _prepareKeys(uint32[] memory keys, uint32 maxSpread) internal view { + uint32 lastKey = 0; + for (uint256 i = 0; i < keys.length; ++i) { + uint32 key = _boundUint32(keys[i], lastKey, lastKey + maxSpread); + keys[i] = key; + lastKey = key; + } + } + + function _assertLatestCheckpoint(bool exist, uint32 key, uint224 value) internal { + (bool _exist, uint32 _key, uint224 _value) = _ckpts.latestCheckpoint(); + assertEq(_exist, exist); + assertEq(_key, key); + assertEq(_value, value); + } + + // tests + function testPush(uint32[] memory keys, uint224[] memory values, uint32 pastKey) public { + vm.assume(values.length > 0 && values.length <= keys.length); + _prepareKeys(keys, _KEY_MAX_GAP); + + // initial state + assertEq(_ckpts.length(), 0); + assertEq(_ckpts.latest(), 0); + _assertLatestCheckpoint(false, 0, 0); + + uint256 duplicates = 0; + for (uint256 i = 0; i < keys.length; ++i) { + uint32 key = keys[i]; + uint224 value = values[i % values.length]; + if (i > 0 && key == keys[i - 1]) ++duplicates; + + // push + _ckpts.push(key, value); + + // check length & latest + assertEq(_ckpts.length(), i + 1 - duplicates); + assertEq(_ckpts.latest(), value); + _assertLatestCheckpoint(true, key, value); + } + + if (keys.length > 0) { + uint32 lastKey = keys[keys.length - 1]; + pastKey = _boundUint32(pastKey, 0, lastKey - 1); + + vm.expectRevert(); + this.push(pastKey, values[keys.length % values.length]); + } + } + + // used to test reverts + function push(uint32 key, uint224 value) external { + _ckpts.push(key, value); + } + + function testLookup(uint32[] memory keys, uint224[] memory values, uint32 lookup) public { + vm.assume(values.length > 0 && values.length <= keys.length); + _prepareKeys(keys, _KEY_MAX_GAP); + + uint32 lastKey = keys.length == 0 ? 0 : keys[keys.length - 1]; + lookup = _boundUint32(lookup, 0, lastKey + _KEY_MAX_GAP); + + uint224 upper = 0; + uint224 lower = 0; + uint32 lowerKey = type(uint32).max; + for (uint256 i = 0; i < keys.length; ++i) { + uint32 key = keys[i]; + uint224 value = values[i % values.length]; + + // push + _ckpts.push(key, value); + + // track expected result of lookups + if (key <= lookup) { + upper = value; + } + // find the first key that is not smaller than the lookup key + if (key >= lookup && (i == 0 || keys[i - 1] < lookup)) { + lowerKey = key; + } + if (key == lowerKey) { + lower = value; + } + } + + // check lookup + assertEq(_ckpts.lowerLookup(lookup), lower); + assertEq(_ckpts.upperLookup(lookup), upper); + assertEq(_ckpts.upperLookupRecent(lookup), upper); + } +} + +contract CheckpointsTrace160Test is Test { + using Checkpoints for Checkpoints.Trace160; + + // Maximum gap between keys used during the fuzzing tests: the `_prepareKeys` function with make sure that + // key#n+1 is in the [key#n, key#n + _KEY_MAX_GAP] range. + uint8 internal constant _KEY_MAX_GAP = 64; + + Checkpoints.Trace160 internal _ckpts; + + // helpers + function _boundUint96(uint96 x, uint96 min, uint96 max) internal view returns (uint96) { + return SafeCast.toUint96(bound(uint256(x), uint256(min), uint256(max))); + } + + function _prepareKeys(uint96[] memory keys, uint96 maxSpread) internal view { + uint96 lastKey = 0; + for (uint256 i = 0; i < keys.length; ++i) { + uint96 key = _boundUint96(keys[i], lastKey, lastKey + maxSpread); + keys[i] = key; + lastKey = key; + } + } + + function _assertLatestCheckpoint(bool exist, uint96 key, uint160 value) internal { + (bool _exist, uint96 _key, uint160 _value) = _ckpts.latestCheckpoint(); + assertEq(_exist, exist); + assertEq(_key, key); + assertEq(_value, value); + } + + // tests + function testPush(uint96[] memory keys, uint160[] memory values, uint96 pastKey) public { + vm.assume(values.length > 0 && values.length <= keys.length); + _prepareKeys(keys, _KEY_MAX_GAP); + + // initial state + assertEq(_ckpts.length(), 0); + assertEq(_ckpts.latest(), 0); + _assertLatestCheckpoint(false, 0, 0); + + uint256 duplicates = 0; + for (uint256 i = 0; i < keys.length; ++i) { + uint96 key = keys[i]; + uint160 value = values[i % values.length]; + if (i > 0 && key == keys[i - 1]) ++duplicates; + + // push + _ckpts.push(key, value); + + // check length & latest + assertEq(_ckpts.length(), i + 1 - duplicates); + assertEq(_ckpts.latest(), value); + _assertLatestCheckpoint(true, key, value); + } + + if (keys.length > 0) { + uint96 lastKey = keys[keys.length - 1]; + pastKey = _boundUint96(pastKey, 0, lastKey - 1); + + vm.expectRevert(); + this.push(pastKey, values[keys.length % values.length]); + } + } + + // used to test reverts + function push(uint96 key, uint160 value) external { + _ckpts.push(key, value); + } + + function testLookup(uint96[] memory keys, uint160[] memory values, uint96 lookup) public { + vm.assume(values.length > 0 && values.length <= keys.length); + _prepareKeys(keys, _KEY_MAX_GAP); + + uint96 lastKey = keys.length == 0 ? 0 : keys[keys.length - 1]; + lookup = _boundUint96(lookup, 0, lastKey + _KEY_MAX_GAP); + + uint160 upper = 0; + uint160 lower = 0; + uint96 lowerKey = type(uint96).max; + for (uint256 i = 0; i < keys.length; ++i) { + uint96 key = keys[i]; + uint160 value = values[i % values.length]; + + // push + _ckpts.push(key, value); + + // track expected result of lookups + if (key <= lookup) { + upper = value; + } + // find the first key that is not smaller than the lookup key + if (key >= lookup && (i == 0 || keys[i - 1] < lookup)) { + lowerKey = key; + } + if (key == lowerKey) { + lower = value; + } + } + + // check lookup + assertEq(_ckpts.lowerLookup(lookup), lower); + assertEq(_ckpts.upperLookup(lookup), upper); + assertEq(_ckpts.upperLookupRecent(lookup), upper); + } +} From 473d0b6884ce538f534a0fc04c97b78e8cc0f48b Mon Sep 17 00:00:00 2001 From: Francisco Giordano Date: Tue, 11 Apr 2023 20:36:58 -0300 Subject: [PATCH 024/182] Add Codecov token --- .github/workflows/checks.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 290c6a943..c6b42dd82 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -73,6 +73,8 @@ jobs: env: NODE_OPTIONS: --max_old_space_size=4096 - uses: codecov/codecov-action@v3 + with: + token: ${{ secrets.CODECOV_TOKEN }} slither: if: github.repository != 'OpenZeppelin/openzeppelin-contracts-upgradeable' From 661343f74ca798c0ac5037411fe39d8ef15f3377 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Wed, 12 Apr 2023 04:17:10 +0200 Subject: [PATCH 025/182] Add DoubleEndedQueue FV (#4147) Co-authored-by: Francisco Co-authored-by: Hadrien Croubois --- certora/harnesses/DoubleEndedQueueHarness.sol | 59 +++ certora/specs.json | 5 + certora/specs/DoubleEndedQueue.spec | 366 ++++++++++++++++++ 3 files changed, 430 insertions(+) create mode 100644 certora/harnesses/DoubleEndedQueueHarness.sol create mode 100644 certora/specs/DoubleEndedQueue.spec diff --git a/certora/harnesses/DoubleEndedQueueHarness.sol b/certora/harnesses/DoubleEndedQueueHarness.sol new file mode 100644 index 000000000..b4f0bc841 --- /dev/null +++ b/certora/harnesses/DoubleEndedQueueHarness.sol @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import "../patched/utils/structs/DoubleEndedQueue.sol"; + +contract DoubleEndedQueueHarness { + using DoubleEndedQueue for DoubleEndedQueue.Bytes32Deque; + + DoubleEndedQueue.Bytes32Deque private _deque; + + function pushFront(bytes32 value) external { + _deque.pushFront(value); + } + + function pushBack(bytes32 value) external { + _deque.pushBack(value); + } + + function popFront() external returns (bytes32 value) { + return _deque.popFront(); + } + + function popBack() external returns (bytes32 value) { + return _deque.popBack(); + } + + function clear() external { + _deque.clear(); + } + + function begin() external view returns (int128) { + return _deque._begin; + } + + function end() external view returns (int128) { + return _deque._end; + } + + function length() external view returns (uint256) { + return _deque.length(); + } + + function empty() external view returns (bool) { + return _deque.empty(); + } + + function front() external view returns (bytes32 value) { + return _deque.front(); + } + + function back() external view returns (bytes32 value) { + return _deque.back(); + } + + function at_(uint256 index) external view returns (bytes32 value) { + return _deque.at(index); + } +} diff --git a/certora/specs.json b/certora/specs.json index 80fbf26af..cd68af65e 100644 --- a/certora/specs.json +++ b/certora/specs.json @@ -9,6 +9,11 @@ "contract": "AccessControlHarness", "files": ["certora/harnesses/AccessControlHarness.sol"] }, + { + "spec": "DoubleEndedQueue", + "contract": "DoubleEndedQueueHarness", + "files": ["certora/harnesses/DoubleEndedQueueHarness.sol"] + }, { "spec": "Ownable", "contract": "OwnableHarness", diff --git a/certora/specs/DoubleEndedQueue.spec b/certora/specs/DoubleEndedQueue.spec new file mode 100644 index 000000000..2412b4cc8 --- /dev/null +++ b/certora/specs/DoubleEndedQueue.spec @@ -0,0 +1,366 @@ +import "helpers.spec" + +methods { + pushFront(bytes32) envfree + pushBack(bytes32) envfree + popFront() returns (bytes32) envfree + popBack() returns (bytes32) envfree + clear() envfree + + // exposed for FV + begin() returns (int128) envfree + end() returns (int128) envfree + + // view + length() returns (uint256) envfree + empty() returns (bool) envfree + front() returns (bytes32) envfree + back() returns (bytes32) envfree + at_(uint256) returns (bytes32) envfree // at is a reserved word +} + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Helpers │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ + +function min_int128() returns mathint { + return -(1 << 127); +} + +function max_int128() returns mathint { + return (1 << 127) - 1; +} + +// Could be broken in theory, but not in practice +function boundedQueue() returns bool { + return + max_int128() > to_mathint(end()) && + min_int128() < to_mathint(begin()); +} + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Invariant: end is larger or equal than begin │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +invariant boundariesConsistency() + end() >= begin() + filtered { f -> !f.isView } + { preserved { require boundedQueue(); } } + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Invariant: length is end minus begin │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +invariant lengthConsistency() + length() == to_mathint(end()) - to_mathint(begin()) + filtered { f -> !f.isView } + { preserved { require boundedQueue(); } } + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Invariant: empty() is length 0 and no element exists │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +invariant emptiness() + empty() <=> length() == 0 + filtered { f -> !f.isView } + { preserved { require boundedQueue(); } } + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Invariant: front points to the first index and back points to the last one │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +invariant queueEndings() + at_(length() - 1) == back() && at_(0) == front() + filtered { f -> !f.isView } + { + preserved { + requireInvariant boundariesConsistency(); + require boundedQueue(); + } + } + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Function correctness: pushFront adds an element at the beginning of the queue │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +rule pushFront(bytes32 value) { + require boundedQueue(); + + uint256 lengthBefore = length(); + + pushFront@withrevert(value); + + // liveness + assert !lastReverted, "never reverts"; + + // effect + assert front() == value, "front set to value"; + assert length() == lengthBefore + 1, "queue extended"; +} + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Rule: pushFront preserves the previous values in the queue with a +1 offset │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +rule pushFrontConsistency(uint256 index) { + require boundedQueue(); + + bytes32 beforeAt = at_(index); + + bytes32 value; + pushFront(value); + + // try to read value + bytes32 afterAt = at_@withrevert(index + 1); + + assert !lastReverted, "value still there"; + assert afterAt == beforeAt, "data is preserved"; +} + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Function correctness: pushBack adds an element at the end of the queue │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +rule pushBack(bytes32 value) { + require boundedQueue(); + + uint256 lengthBefore = length(); + + pushBack@withrevert(value); + + // liveness + assert !lastReverted, "never reverts"; + + // effect + assert back() == value, "back set to value"; + assert length() == lengthBefore + 1, "queue increased"; +} + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Rule: pushBack preserves the previous values in the queue │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +rule pushBackConsistency(uint256 index) { + require boundedQueue(); + + bytes32 beforeAt = at_(index); + + bytes32 value; + pushBack(value); + + // try to read value + bytes32 afterAt = at_@withrevert(index); + + assert !lastReverted, "value still there"; + assert afterAt == beforeAt, "data is preserved"; +} + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Function correctness: popFront removes an element from the beginning of the queue │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +rule popFront { + requireInvariant boundariesConsistency(); + require boundedQueue(); + + uint256 lengthBefore = length(); + bytes32 frontBefore = front@withrevert(); + + bytes32 popped = popFront@withrevert(); + bool success = !lastReverted; + + // liveness + assert success <=> lengthBefore != 0, "never reverts if not previously empty"; + + // effect + assert success => frontBefore == popped, "previous front is returned"; + assert success => length() == lengthBefore - 1, "queue decreased"; +} + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Rule: at(x) is preserved and offset to at(x - 1) after calling popFront | +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +rule popFrontConsistency(uint256 index) { + requireInvariant boundariesConsistency(); + require boundedQueue(); + + // Read (any) value that is not the front (this asserts the value exists / the queue is long enough) + require index > 1; + bytes32 before = at_(index); + + popFront(); + + // try to read value + bytes32 after = at_@withrevert(index - 1); + + assert !lastReverted, "value still exists in the queue"; + assert before == after, "values are offset and not modified"; +} + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Function correctness: popBack removes an element from the end of the queue │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +rule popBack { + requireInvariant boundariesConsistency(); + require boundedQueue(); + + uint256 lengthBefore = length(); + bytes32 backBefore = back@withrevert(); + + bytes32 popped = popBack@withrevert(); + bool success = !lastReverted; + + // liveness + assert success <=> lengthBefore != 0, "never reverts if not previously empty"; + + // effect + assert success => backBefore == popped, "previous back is returned"; + assert success => length() == lengthBefore - 1, "queue decreased"; +} + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Rule: at(x) is preserved after calling popBack | +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +rule popBackConsistency(uint256 index) { + requireInvariant boundariesConsistency(); + require boundedQueue(); + + // Read (any) value that is not the back (this asserts the value exists / the queue is long enough) + require index < length() - 1; + bytes32 before = at_(index); + + popBack(); + + // try to read value + bytes32 after = at_@withrevert(index); + + assert !lastReverted, "value still exists in the queue"; + assert before == after, "values are offset and not modified"; +} + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Function correctness: clear sets length to 0 │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +rule clear { + clear@withrevert(); + + // liveness + assert !lastReverted, "never reverts"; + + // effect + assert length() == 0, "sets length to 0"; +} + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Rule: front/back access reverts only if the queue is empty or querying out of bounds │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +rule onlyEmptyRevert(env e) { + require nonpayable(e); + requireInvariant boundariesConsistency(); + require boundedQueue(); + + method f; + calldataarg args; + + bool emptyBefore = empty(); + + f@withrevert(e, args); + + assert lastReverted => ( + (f.selector == front().selector && emptyBefore) || + (f.selector == back().selector && emptyBefore) || + (f.selector == popFront().selector && emptyBefore) || + (f.selector == popBack().selector && emptyBefore) || + f.selector == at_(uint256).selector // revert conditions are verified in onlyOutOfBoundsRevert + ), "only revert if empty or out of bounds"; +} + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Rule: at(index) only reverts if index is out of bounds | +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +rule onlyOutOfBoundsRevert(uint256 index) { + requireInvariant boundariesConsistency(); + require boundedQueue(); + + at_@withrevert(index); + + assert lastReverted <=> index >= length(), "only reverts if index is out of bounds"; +} + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Rule: only clear/push/pop operations can change the length of the queue │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +rule noLengthChange(env e) { + requireInvariant boundariesConsistency(); + require boundedQueue(); + + method f; + calldataarg args; + + uint256 lengthBefore = length(); + f(e, args); + uint256 lengthAfter = length(); + + assert lengthAfter != lengthBefore => ( + (f.selector == pushFront(bytes32).selector && lengthAfter == lengthBefore + 1) || + (f.selector == pushBack(bytes32).selector && lengthAfter == lengthBefore + 1) || + (f.selector == popBack().selector && lengthAfter == lengthBefore - 1) || + (f.selector == popFront().selector && lengthAfter == lengthBefore - 1) || + (f.selector == clear().selector && lengthAfter == 0) + ), "length is only affected by clear/pop/push operations"; +} + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Rule: only push/pop can change values bounded in the queue (outside values aren't cleared) │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +rule noDataChange(env e) { + requireInvariant boundariesConsistency(); + require boundedQueue(); + + method f; + calldataarg args; + + uint256 index; + bytes32 atBefore = at_(index); + f(e, args); + bytes32 atAfter = at_@withrevert(index); + bool atAfterSuccess = !lastReverted; + + assert !atAfterSuccess <=> ( + f.selector == clear().selector || + (f.selector == popBack().selector && index == length()) || + (f.selector == popFront().selector && index == length()) + ), "indexes of the queue are only removed by clear or pop"; + + assert atAfterSuccess && atAfter != atBefore => ( + f.selector == popFront().selector || + f.selector == pushFront(bytes32).selector + ), "values of the queue are only changed by popFront or pushFront"; +} From 86f6eb2c9c0eb741de18ef75a290ff340acf7148 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Wed, 12 Apr 2023 05:29:36 +0200 Subject: [PATCH 026/182] Add FV specification for ERC721 (#4104) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ernesto García --- certora/diff/token_ERC721_ERC721.sol.patch | 14 + certora/harnesses/ERC721Harness.sol | 37 ++ certora/harnesses/ERC721ReceiverHarness.sol | 11 + certora/specs.json | 6 + certora/specs/ERC721.spec | 589 ++++++++++++++++++++ certora/specs/methods/IERC721.spec | 20 + 6 files changed, 677 insertions(+) create mode 100644 certora/diff/token_ERC721_ERC721.sol.patch create mode 100644 certora/harnesses/ERC721Harness.sol create mode 100644 certora/harnesses/ERC721ReceiverHarness.sol create mode 100644 certora/specs/ERC721.spec create mode 100644 certora/specs/methods/IERC721.spec diff --git a/certora/diff/token_ERC721_ERC721.sol.patch b/certora/diff/token_ERC721_ERC721.sol.patch new file mode 100644 index 000000000..c3eae357a --- /dev/null +++ b/certora/diff/token_ERC721_ERC721.sol.patch @@ -0,0 +1,14 @@ +--- token/ERC721/ERC721.sol 2023-03-07 10:48:47.736822221 +0100 ++++ token/ERC721/ERC721.sol 2023-03-09 19:49:39.669338673 +0100 +@@ -199,6 +199,11 @@ + return _owners[tokenId]; + } + ++ // FV ++ function _getApproved(uint256 tokenId) internal view returns (address) { ++ return _tokenApprovals[tokenId]; ++ } ++ + /** + * @dev Returns whether `tokenId` exists. + * diff --git a/certora/harnesses/ERC721Harness.sol b/certora/harnesses/ERC721Harness.sol new file mode 100644 index 000000000..3307369a8 --- /dev/null +++ b/certora/harnesses/ERC721Harness.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import "../patched/token/ERC721/ERC721.sol"; + +contract ERC721Harness is ERC721 { + constructor(string memory name, string memory symbol) ERC721(name, symbol) {} + + function mint(address account, uint256 tokenId) external { + _mint(account, tokenId); + } + + function safeMint(address to, uint256 tokenId) external { + _safeMint(to, tokenId); + } + + function safeMint(address to, uint256 tokenId, bytes memory data) external { + _safeMint(to, tokenId, data); + } + + function burn(uint256 tokenId) external { + _burn(tokenId); + } + + function tokenExists(uint256 tokenId) external view returns (bool) { + return _exists(tokenId); + } + + function unsafeOwnerOf(uint256 tokenId) external view returns (address) { + return _ownerOf(tokenId); + } + + function unsafeGetApproved(uint256 tokenId) external view returns (address) { + return _getApproved(tokenId); + } +} diff --git a/certora/harnesses/ERC721ReceiverHarness.sol b/certora/harnesses/ERC721ReceiverHarness.sol new file mode 100644 index 000000000..7e5739ee3 --- /dev/null +++ b/certora/harnesses/ERC721ReceiverHarness.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import "../patched/interfaces/IERC721Receiver.sol"; + +contract ERC721ReceiverHarness is IERC721Receiver { + function onERC721Received(address, address, uint256, bytes calldata) external pure returns (bytes4) { + return this.onERC721Received.selector; + } +} diff --git a/certora/specs.json b/certora/specs.json index cd68af65e..39ba8c235 100644 --- a/certora/specs.json +++ b/certora/specs.json @@ -51,6 +51,12 @@ "--optimistic_loop" ] }, + { + "spec": "ERC721", + "contract": "ERC721Harness", + "files": ["certora/harnesses/ERC721Harness.sol", "certora/harnesses/ERC721ReceiverHarness.sol"], + "options": ["--optimistic_loop"] + }, { "spec": "Initializable", "contract": "InitializableHarness", diff --git a/certora/specs/ERC721.spec b/certora/specs/ERC721.spec new file mode 100644 index 000000000..48503469b --- /dev/null +++ b/certora/specs/ERC721.spec @@ -0,0 +1,589 @@ +import "helpers.spec" +import "methods/IERC721.spec" + +methods { + // exposed for FV + mint(address,uint256) + safeMint(address,uint256) + safeMint(address,uint256,bytes) + burn(uint256) + + tokenExists(uint256) returns (bool) envfree + unsafeOwnerOf(uint256) returns (address) envfree + unsafeGetApproved(uint256) returns (address) envfree +} + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Helpers │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ + +// Could be broken in theory, but not in practice +function balanceLimited(address account) returns bool { + return balanceOf(account) < max_uint256; +} + +function helperTransferWithRevert(env e, method f, address from, address to, uint256 tokenId) { + if (f.selector == transferFrom(address,address,uint256).selector) { + transferFrom@withrevert(e, from, to, tokenId); + } else if (f.selector == safeTransferFrom(address,address,uint256).selector) { + safeTransferFrom@withrevert(e, from, to, tokenId); + } else if (f.selector == safeTransferFrom(address,address,uint256,bytes).selector) { + bytes params; + require params.length < 0xffff; + safeTransferFrom@withrevert(e, from, to, tokenId, params); + } else { + calldataarg args; + f@withrevert(e, args); + } +} + +function helperMintWithRevert(env e, method f, address to, uint256 tokenId) { + if (f.selector == mint(address,uint256).selector) { + mint@withrevert(e, to, tokenId); + } else if (f.selector == safeMint(address,uint256).selector) { + safeMint@withrevert(e, to, tokenId); + } else if (f.selector == safeMint(address,uint256,bytes).selector) { + bytes params; + require params.length < 0xffff; + safeMint@withrevert(e, to, tokenId, params); + } else { + require false; + } +} + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Ghost & hooks: ownership count │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +ghost ownedTotal() returns uint256 { + init_state axiom ownedTotal() == 0; +} + +ghost mapping(address => uint256) ownedByUser { + init_state axiom forall address a. ownedByUser[a] == 0; +} + +hook Sstore _owners[KEY uint256 tokenId] address newOwner (address oldOwner) STORAGE { + ownedByUser[newOwner] = ownedByUser[newOwner] + to_uint256(newOwner != 0 ? 1 : 0); + ownedByUser[oldOwner] = ownedByUser[oldOwner] - to_uint256(oldOwner != 0 ? 1 : 0); + + havoc ownedTotal assuming ownedTotal@new() == ownedTotal@old() + + to_uint256(newOwner != 0 ? 1 : 0) + - to_uint256(oldOwner != 0 ? 1 : 0); +} + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Ghost & hooks: sum of all balances │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +ghost sumOfBalances() returns uint256 { + init_state axiom sumOfBalances() == 0; +} + +hook Sstore _balances[KEY address addr] uint256 newValue (uint256 oldValue) STORAGE { + havoc sumOfBalances assuming sumOfBalances@new() == sumOfBalances@old() + newValue - oldValue; +} + +ghost mapping(address => uint256) ghostBalanceOf { + init_state axiom forall address a. ghostBalanceOf[a] == 0; +} + +hook Sload uint256 value _balances[KEY address user] STORAGE { + require ghostBalanceOf[user] == value; +} + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Invariant: ownedTotal is the sum of all balances │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +invariant ownedTotalIsSumOfBalances() + ownedTotal() == sumOfBalances() + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Invariant: balanceOf is the number of tokens owned │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +invariant balanceOfConsistency(address user) + balanceOf(user) == ownedByUser[user] && + balanceOf(user) == ghostBalanceOf[user] + { + preserved { + require balanceLimited(user); + } + } + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Invariant: owner of a token must have some balance │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +invariant ownerHasBalance(uint256 tokenId) + balanceOf(ownerOf(tokenId)) > 0 + { + preserved { + requireInvariant balanceOfConsistency(ownerOf(tokenId)); + require balanceLimited(ownerOf(tokenId)); + } + } + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Invariant: tokens that do not exist are not owned and not approved │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +invariant notMintedUnset(uint256 tokenId) + (!tokenExists(tokenId) <=> unsafeOwnerOf(tokenId) == 0) && + (!tokenExists(tokenId) => unsafeGetApproved(tokenId) == 0) + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Rule: ownerOf and getApproved revert if token does not exist │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +rule notMintedRevert(uint256 tokenId) { + requireInvariant notMintedUnset(tokenId); + + bool e = tokenExists(tokenId); + + address owner = ownerOf@withrevert(tokenId); + assert e <=> !lastReverted; + assert e => owner == unsafeOwnerOf(tokenId); // notMintedUnset tells us this is non-zero + + address approved = getApproved@withrevert(tokenId); + assert e <=> !lastReverted; + assert e => approved == unsafeGetApproved(tokenId); +} + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Rule: unsafeOwnerOf and unsafeGetApproved don't revert │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +rule unsafeDontRevert(uint256 tokenId) { + unsafeOwnerOf@withrevert(tokenId); + assert !lastReverted; + + unsafeGetApproved@withrevert(tokenId); + assert !lastReverted; +} + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Rule: balance of address(0) is 0 │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +rule zeroAddressBalanceRevert() { + balanceOf@withrevert(0); + assert lastReverted; +} + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Rules: total supply can only change through mint and burn │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +rule supplyChange(env e) { + uint256 supplyBefore = ownedTotal(); + method f; calldataarg args; f(e, args); + uint256 supplyAfter = ownedTotal(); + + assert supplyAfter > supplyBefore => ( + supplyAfter == supplyBefore + 1 && + ( + f.selector == mint(address,uint256).selector || + f.selector == safeMint(address,uint256).selector || + f.selector == safeMint(address,uint256,bytes).selector + ) + ); + assert supplyAfter < supplyBefore => ( + supplyAfter == supplyBefore - 1 && + f.selector == burn(uint256).selector + ); +} + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Rules: balanceOf can only change through mint, burn or transfers. balanceOf cannot change by more than 1. │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +rule balanceChange(env e, address account) { + requireInvariant balanceOfConsistency(account); + require balanceLimited(account); + + uint256 balanceBefore = balanceOf(account); + method f; calldataarg args; f(e, args); + uint256 balanceAfter = balanceOf(account); + + // balance can change by at most 1 + assert balanceBefore != balanceAfter => ( + balanceAfter == balanceBefore - 1 || + balanceAfter == balanceBefore + 1 + ); + + // only selected function can change balances + assert balanceBefore != balanceAfter => ( + f.selector == transferFrom(address,address,uint256).selector || + f.selector == safeTransferFrom(address,address,uint256).selector || + f.selector == safeTransferFrom(address,address,uint256,bytes).selector || + f.selector == mint(address,uint256).selector || + f.selector == safeMint(address,uint256).selector || + f.selector == safeMint(address,uint256,bytes).selector || + f.selector == burn(uint256).selector + ); +} + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Rules: ownership can only change through mint, burn or transfers. │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +rule ownershipChange(env e, uint256 tokenId) { + address ownerBefore = unsafeOwnerOf(tokenId); + method f; calldataarg args; f(e, args); + address ownerAfter = unsafeOwnerOf(tokenId); + + assert ownerBefore == 0 && ownerAfter != 0 => ( + f.selector == mint(address,uint256).selector || + f.selector == safeMint(address,uint256).selector || + f.selector == safeMint(address,uint256,bytes).selector + ); + + assert ownerBefore != 0 && ownerAfter == 0 => ( + f.selector == burn(uint256).selector + ); + + assert (ownerBefore != ownerAfter && ownerBefore != 0 && ownerAfter != 0) => ( + f.selector == transferFrom(address,address,uint256).selector || + f.selector == safeTransferFrom(address,address,uint256).selector || + f.selector == safeTransferFrom(address,address,uint256,bytes).selector + ); +} + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Rules: token approval can only change through approve or transfers (implicitly). │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +rule approvalChange(env e, uint256 tokenId) { + address approvalBefore = unsafeGetApproved(tokenId); + method f; calldataarg args; f(e, args); + address approvalAfter = unsafeGetApproved(tokenId); + + // approve can set any value, other functions reset + assert approvalBefore != approvalAfter => ( + f.selector == approve(address,uint256).selector || + ( + ( + f.selector == transferFrom(address,address,uint256).selector || + f.selector == safeTransferFrom(address,address,uint256).selector || + f.selector == safeTransferFrom(address,address,uint256,bytes).selector || + f.selector == burn(uint256).selector + ) && approvalAfter == 0 + ) + ); +} + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Rules: approval for all tokens can only change through isApprovedForAll. │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +rule approvedForAllChange(env e, address owner, address spender) { + bool approvedForAllBefore = isApprovedForAll(owner, spender); + method f; calldataarg args; f(e, args); + bool approvedForAllAfter = isApprovedForAll(owner, spender); + + assert approvedForAllBefore != approvedForAllAfter => f.selector == setApprovalForAll(address,bool).selector; +} + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Rule: transferFrom behavior and side effects │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +rule transferFrom(env e, address from, address to, uint256 tokenId) { + require nonpayable(e); + + address operator = e.msg.sender; + uint256 otherTokenId; + address otherAccount; + + requireInvariant ownerHasBalance(tokenId); + require balanceLimited(to); + + uint256 balanceOfFromBefore = balanceOf(from); + uint256 balanceOfToBefore = balanceOf(to); + uint256 balanceOfOtherBefore = balanceOf(otherAccount); + address ownerBefore = unsafeOwnerOf(tokenId); + address otherOwnerBefore = unsafeOwnerOf(otherTokenId); + address approvalBefore = unsafeGetApproved(tokenId); + address otherApprovalBefore = unsafeGetApproved(otherTokenId); + + transferFrom@withrevert(e, from, to, tokenId); + bool success = !lastReverted; + + // liveness + assert success <=> ( + from == ownerBefore && + from != 0 && + to != 0 && + (operator == from || operator == approvalBefore || isApprovedForAll(ownerBefore, operator)) + ); + + // effect + assert success => ( + balanceOf(from) == balanceOfFromBefore - to_uint256(from != to ? 1 : 0) && + balanceOf(to) == balanceOfToBefore + to_uint256(from != to ? 1 : 0) && + unsafeOwnerOf(tokenId) == to && + unsafeGetApproved(tokenId) == 0 + ); + + // no side effect + assert balanceOf(otherAccount) != balanceOfOtherBefore => (otherAccount == from || otherAccount == to); + assert unsafeOwnerOf(otherTokenId) != otherOwnerBefore => otherTokenId == tokenId; + assert unsafeGetApproved(otherTokenId) != otherApprovalBefore => otherTokenId == tokenId; +} + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Rule: safeTransferFrom behavior and side effects │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +rule safeTransferFrom(env e, method f, address from, address to, uint256 tokenId) filtered { f -> + f.selector == safeTransferFrom(address,address,uint256).selector || + f.selector == safeTransferFrom(address,address,uint256,bytes).selector +} { + require nonpayable(e); + + address operator = e.msg.sender; + uint256 otherTokenId; + address otherAccount; + + requireInvariant ownerHasBalance(tokenId); + require balanceLimited(to); + + uint256 balanceOfFromBefore = balanceOf(from); + uint256 balanceOfToBefore = balanceOf(to); + uint256 balanceOfOtherBefore = balanceOf(otherAccount); + address ownerBefore = unsafeOwnerOf(tokenId); + address otherOwnerBefore = unsafeOwnerOf(otherTokenId); + address approvalBefore = unsafeGetApproved(tokenId); + address otherApprovalBefore = unsafeGetApproved(otherTokenId); + + helperTransferWithRevert(e, f, from, to, tokenId); + bool success = !lastReverted; + + assert success <=> ( + from == ownerBefore && + from != 0 && + to != 0 && + (operator == from || operator == approvalBefore || isApprovedForAll(ownerBefore, operator)) + ); + + // effect + assert success => ( + balanceOf(from) == balanceOfFromBefore - to_uint256(from != to ? 1: 0) && + balanceOf(to) == balanceOfToBefore + to_uint256(from != to ? 1: 0) && + unsafeOwnerOf(tokenId) == to && + unsafeGetApproved(tokenId) == 0 + ); + + // no side effect + assert balanceOf(otherAccount) != balanceOfOtherBefore => (otherAccount == from || otherAccount == to); + assert unsafeOwnerOf(otherTokenId) != otherOwnerBefore => otherTokenId == tokenId; + assert unsafeGetApproved(otherTokenId) != otherApprovalBefore => otherTokenId == tokenId; +} + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Rule: mint behavior and side effects │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +rule mint(env e, address to, uint256 tokenId) { + require nonpayable(e); + requireInvariant notMintedUnset(tokenId); + + uint256 otherTokenId; + address otherAccount; + + require balanceLimited(to); + + uint256 supplyBefore = ownedTotal(); + uint256 balanceOfToBefore = balanceOf(to); + uint256 balanceOfOtherBefore = balanceOf(otherAccount); + address ownerBefore = unsafeOwnerOf(tokenId); + address otherOwnerBefore = unsafeOwnerOf(otherTokenId); + + mint@withrevert(e, to, tokenId); + bool success = !lastReverted; + + // liveness + assert success <=> ( + ownerBefore == 0 && + to != 0 + ); + + // effect + assert success => ( + ownedTotal() == supplyBefore + 1 && + balanceOf(to) == balanceOfToBefore + 1 && + unsafeOwnerOf(tokenId) == to + ); + + // no side effect + assert balanceOf(otherAccount) != balanceOfOtherBefore => otherAccount == to; + assert unsafeOwnerOf(otherTokenId) != otherOwnerBefore => otherTokenId == tokenId; +} + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Rule: safeMint behavior and side effects │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +rule safeMint(env e, method f, address to, uint256 tokenId) filtered { f -> + f.selector == safeMint(address,uint256).selector || + f.selector == safeMint(address,uint256,bytes).selector +} { + require nonpayable(e); + requireInvariant notMintedUnset(tokenId); + + uint256 otherTokenId; + address otherAccount; + + require balanceLimited(to); + + uint256 supplyBefore = ownedTotal(); + uint256 balanceOfToBefore = balanceOf(to); + uint256 balanceOfOtherBefore = balanceOf(otherAccount); + address ownerBefore = unsafeOwnerOf(tokenId); + address otherOwnerBefore = unsafeOwnerOf(otherTokenId); + + helperMintWithRevert(e, f, to, tokenId); + bool success = !lastReverted; + + assert success <=> ( + ownerBefore == 0 && + to != 0 + ); + + // effect + assert success => ( + ownedTotal() == supplyBefore + 1 && + balanceOf(to) == balanceOfToBefore + 1 && + unsafeOwnerOf(tokenId) == to + ); + + // no side effect + assert balanceOf(otherAccount) != balanceOfOtherBefore => otherAccount == to; + assert unsafeOwnerOf(otherTokenId) != otherOwnerBefore => otherTokenId == tokenId; +} + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Rule: burn behavior and side effects │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +rule burn(env e, uint256 tokenId) { + require nonpayable(e); + + address from = unsafeOwnerOf(tokenId); + uint256 otherTokenId; + address otherAccount; + + requireInvariant ownerHasBalance(tokenId); + + uint256 supplyBefore = ownedTotal(); + uint256 balanceOfFromBefore = balanceOf(from); + uint256 balanceOfOtherBefore = balanceOf(otherAccount); + address ownerBefore = unsafeOwnerOf(tokenId); + address otherOwnerBefore = unsafeOwnerOf(otherTokenId); + address otherApprovalBefore = unsafeGetApproved(otherTokenId); + + burn@withrevert(e, tokenId); + bool success = !lastReverted; + + // liveness + assert success <=> ( + ownerBefore != 0 + ); + + // effect + assert success => ( + ownedTotal() == supplyBefore - 1 && + balanceOf(from) == balanceOfFromBefore - 1 && + unsafeOwnerOf(tokenId) == 0 && + unsafeGetApproved(tokenId) == 0 + ); + + // no side effect + assert balanceOf(otherAccount) != balanceOfOtherBefore => otherAccount == from; + assert unsafeOwnerOf(otherTokenId) != otherOwnerBefore => otherTokenId == tokenId; + assert unsafeGetApproved(otherTokenId) != otherApprovalBefore => otherTokenId == tokenId; +} + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Rule: approve behavior and side effects │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +rule approve(env e, address spender, uint256 tokenId) { + require nonpayable(e); + + address caller = e.msg.sender; + address owner = unsafeOwnerOf(tokenId); + uint256 otherTokenId; + + address otherApprovalBefore = unsafeGetApproved(otherTokenId); + + approve@withrevert(e, spender, tokenId); + bool success = !lastReverted; + + // liveness + assert success <=> ( + owner != 0 && + owner != spender && + (owner == caller || isApprovedForAll(owner, caller)) + ); + + // effect + assert success => unsafeGetApproved(tokenId) == spender; + + // no side effect + assert unsafeGetApproved(otherTokenId) != otherApprovalBefore => otherTokenId == tokenId; +} + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Rule: setApprovalForAll behavior and side effects │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +rule setApprovalForAll(env e, address operator, bool approved) { + require nonpayable(e); + + address owner = e.msg.sender; + address otherOwner; + address otherOperator; + + bool otherIsApprovedForAllBefore = isApprovedForAll(otherOwner, otherOperator); + + setApprovalForAll@withrevert(e, operator, approved); + bool success = !lastReverted; + + // liveness + assert success <=> owner != operator; + + // effect + assert success => isApprovedForAll(owner, operator) == approved; + + // no side effect + assert isApprovedForAll(otherOwner, otherOperator) != otherIsApprovedForAllBefore => ( + otherOwner == owner && + otherOperator == operator + ); +} diff --git a/certora/specs/methods/IERC721.spec b/certora/specs/methods/IERC721.spec new file mode 100644 index 000000000..e6d4e1e04 --- /dev/null +++ b/certora/specs/methods/IERC721.spec @@ -0,0 +1,20 @@ +methods { + // IERC721 + balanceOf(address) returns (uint256) envfree => DISPATCHER(true) + ownerOf(uint256) returns (address) envfree => DISPATCHER(true) + getApproved(uint256) returns (address) envfree => DISPATCHER(true) + isApprovedForAll(address,address) returns (bool) envfree => DISPATCHER(true) + safeTransferFrom(address,address,uint256,bytes) => DISPATCHER(true) + safeTransferFrom(address,address,uint256) => DISPATCHER(true) + transferFrom(address,address,uint256) => DISPATCHER(true) + approve(address,uint256) => DISPATCHER(true) + setApprovalForAll(address,bool) => DISPATCHER(true) + + // IERC721Metadata + name() returns (string) => DISPATCHER(true) + symbol() returns (string) => DISPATCHER(true) + tokenURI(uint256) returns (string) => DISPATCHER(true) + + // IERC721Receiver + onERC721Received(address,address,uint256,bytes) returns (bytes4) => DISPATCHER(true) +} From 788d6a129a342d7a23a91ad553479e861845a9b2 Mon Sep 17 00:00:00 2001 From: Francisco Date: Wed, 12 Apr 2023 12:09:30 -0300 Subject: [PATCH 027/182] Add fuzz tests for ShortString (#4175) --- test/utils/ShortStrings.t.sol | 55 +++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 test/utils/ShortStrings.t.sol diff --git a/test/utils/ShortStrings.t.sol b/test/utils/ShortStrings.t.sol new file mode 100644 index 000000000..7c4faa89d --- /dev/null +++ b/test/utils/ShortStrings.t.sol @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import "forge-std/Test.sol"; + +import "../../contracts/utils/ShortStrings.sol"; + +contract ShortStringsTest is Test { + string _fallback; + + function testRoundtripShort(string memory input) external { + vm.assume(_isShort(input)); + ShortString short = ShortStrings.toShortString(input); + string memory output = ShortStrings.toString(short); + assertEq(input, output); + } + + function testRoundtripWithFallback(string memory input, string memory fallbackInitial) external { + _fallback = fallbackInitial; // Make sure that the initial value has no effect + ShortString short = ShortStrings.toShortStringWithFallback(input, _fallback); + string memory output = ShortStrings.toStringWithFallback(short, _fallback); + assertEq(input, output); + } + + function testRevertLong(string memory input) external { + vm.assume(!_isShort(input)); + vm.expectRevert(abi.encodeWithSelector(ShortStrings.StringTooLong.selector, input)); + this.toShortString(input); + } + + function testLengthShort(string memory input) external { + vm.assume(_isShort(input)); + uint256 inputLength = bytes(input).length; + ShortString short = ShortStrings.toShortString(input); + uint256 shortLength = ShortStrings.byteLength(short); + assertEq(inputLength, shortLength); + } + + function testLengthWithFallback(string memory input, string memory fallbackInitial) external { + _fallback = fallbackInitial; + uint256 inputLength = bytes(input).length; + ShortString short = ShortStrings.toShortStringWithFallback(input, _fallback); + uint256 shortLength = ShortStrings.byteLengthWithFallback(short, _fallback); + assertEq(inputLength, shortLength); + } + + function toShortString(string memory input) external pure returns (ShortString) { + return ShortStrings.toShortString(input); + } + + function _isShort(string memory input) internal pure returns (bool) { + return bytes(input).length < 32; + } +} From dd1265cb1dc834b129c6daf6aae7bdae28521d9e Mon Sep 17 00:00:00 2001 From: Pascal Marco Caversaccio Date: Wed, 12 Apr 2023 22:33:50 +0200 Subject: [PATCH 028/182] Improve `ERC4626` test coverage (#4134) Signed-off-by: Pascal Marco Caversaccio --- .../mocks/token/ERC20ExcessDecimalsMock.sol | 9 +++++ remappings.txt | 1 + test/token/ERC20/extensions/ERC4626.t.sol | 34 ++++++++++++++++--- test/token/ERC20/extensions/ERC4626.test.js | 23 +++++++++++++ 4 files changed, 62 insertions(+), 5 deletions(-) create mode 100644 contracts/mocks/token/ERC20ExcessDecimalsMock.sol create mode 100644 remappings.txt diff --git a/contracts/mocks/token/ERC20ExcessDecimalsMock.sol b/contracts/mocks/token/ERC20ExcessDecimalsMock.sol new file mode 100644 index 000000000..0fb35a607 --- /dev/null +++ b/contracts/mocks/token/ERC20ExcessDecimalsMock.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +contract ERC20ExcessDecimalsMock { + function decimals() public pure returns (uint256) { + return type(uint256).max; + } +} diff --git a/remappings.txt b/remappings.txt new file mode 100644 index 000000000..2479e3d26 --- /dev/null +++ b/remappings.txt @@ -0,0 +1 @@ +openzeppelin/=contracts/ diff --git a/test/token/ERC20/extensions/ERC4626.t.sol b/test/token/ERC20/extensions/ERC4626.t.sol index 95514531c..f220086d1 100644 --- a/test/token/ERC20/extensions/ERC4626.t.sol +++ b/test/token/ERC20/extensions/ERC4626.t.sol @@ -1,18 +1,42 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import "erc4626-tests/ERC4626.test.sol"; +import {ERC4626Test} from "erc4626-tests/ERC4626.test.sol"; -import {SafeCast} from "../../../../contracts/utils/math/SafeCast.sol"; -import {ERC20Mock} from "../../../../contracts/mocks/ERC20Mock.sol"; -import {ERC4626Mock} from "../../../../contracts/mocks/ERC4626Mock.sol"; +import {SafeCast} from "openzeppelin/utils/math/SafeCast.sol"; +import {ERC20} from "openzeppelin/token/ERC20/ERC20.sol"; +import {ERC4626} from "openzeppelin/token/ERC20/extensions/ERC4626.sol"; + +import {ERC20Mock} from "openzeppelin/mocks/ERC20Mock.sol"; +import {ERC4626Mock} from "openzeppelin/mocks/ERC4626Mock.sol"; +import {ERC4626OffsetMock} from "openzeppelin/mocks/token/ERC4626OffsetMock.sol"; + +contract ERC4626VaultOffsetMock is ERC4626OffsetMock { + constructor( + ERC20 underlying_, + uint8 offset_ + ) ERC20("My Token Vault", "MTKNV") ERC4626(underlying_) ERC4626OffsetMock(offset_) {} +} contract ERC4626StdTest is ERC4626Test { + ERC20 private _underlying = new ERC20Mock(); + function setUp() public override { - _underlying_ = address(new ERC20Mock()); + _underlying_ = address(_underlying); _vault_ = address(new ERC4626Mock(_underlying_)); _delta_ = 0; _vaultMayBeEmpty = true; _unlimitedAmount = true; } + + /** + * @dev Check the case where calculated `decimals` value overflows the `uint8` type. + */ + function testFuzzDecimalsOverflow(uint8 offset) public { + /// @dev Remember that the `_underlying` exhibits a `decimals` value of 18. + offset = uint8(bound(uint256(offset), 238, uint256(type(uint8).max))); + ERC4626VaultOffsetMock erc4626VaultOffsetMock = new ERC4626VaultOffsetMock(_underlying, offset); + vm.expectRevert(); + erc4626VaultOffsetMock.decimals(); + } } diff --git a/test/token/ERC20/extensions/ERC4626.test.js b/test/token/ERC20/extensions/ERC4626.test.js index c9e5a4098..ee0998717 100644 --- a/test/token/ERC20/extensions/ERC4626.test.js +++ b/test/token/ERC20/extensions/ERC4626.test.js @@ -5,6 +5,7 @@ const ERC20Decimals = artifacts.require('$ERC20DecimalsMock'); const ERC4626 = artifacts.require('$ERC4626'); const ERC4626OffsetMock = artifacts.require('$ERC4626OffsetMock'); const ERC4626FeesMock = artifacts.require('$ERC4626FeesMock'); +const ERC20ExcessDecimalsMock = artifacts.require('ERC20ExcessDecimalsMock'); contract('ERC4626', function (accounts) { const [holder, recipient, spender, other, user1, user2] = accounts; @@ -21,6 +22,28 @@ contract('ERC4626', function (accounts) { } }); + it('asset has not yet been created', async function () { + const vault = await ERC4626.new('', '', other); + expect(await vault.decimals()).to.be.bignumber.equal(decimals); + }); + + it('underlying excess decimals', async function () { + const token = await ERC20ExcessDecimalsMock.new(); + const vault = await ERC4626.new('', '', token.address); + expect(await vault.decimals()).to.be.bignumber.equal(decimals); + }); + + it('decimals overflow', async function () { + for (const offset of [243, 250, 255].map(web3.utils.toBN)) { + const token = await ERC20Decimals.new('', '', decimals); + const vault = await ERC4626OffsetMock.new(name + ' Vault', symbol + 'V', token.address, offset); + await expectRevert( + vault.decimals(), + 'reverted with panic code 0x11 (Arithmetic operation underflowed or overflowed outside of an unchecked block)', + ); + } + }); + for (const offset of [0, 6, 18].map(web3.utils.toBN)) { const parseToken = token => web3.utils.toBN(10).pow(decimals).muln(token); const parseShare = share => web3.utils.toBN(10).pow(decimals.add(offset)).muln(share); From 3b117992e17ae67fcd19ea2bd988f64520813cd1 Mon Sep 17 00:00:00 2001 From: Francisco Date: Thu, 13 Apr 2023 11:04:04 -0300 Subject: [PATCH 029/182] Improve docs for transparent proxy (#4181) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ernesto García --- contracts/proxy/README.adoc | 2 ++ .../TransparentUpgradeableProxy.sol | 24 +++++++++++-------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/contracts/proxy/README.adoc b/contracts/proxy/README.adoc index 5ada16e38..89717a7bf 100644 --- a/contracts/proxy/README.adoc +++ b/contracts/proxy/README.adoc @@ -56,6 +56,8 @@ The current implementation of this security mechanism uses https://eips.ethereum == ERC1967 +{{IERC1967}} + {{ERC1967Proxy}} {{ERC1967Upgrade}} diff --git a/contracts/proxy/transparent/TransparentUpgradeableProxy.sol b/contracts/proxy/transparent/TransparentUpgradeableProxy.sol index a89b233f2..d8765fc9e 100644 --- a/contracts/proxy/transparent/TransparentUpgradeableProxy.sol +++ b/contracts/proxy/transparent/TransparentUpgradeableProxy.sol @@ -6,10 +6,10 @@ pragma solidity ^0.8.0; import "../ERC1967/ERC1967Proxy.sol"; /** - * @dev Interface for the {TransparentUpgradeableProxy}. This is useful because {TransparentUpgradeableProxy} uses a - * custom call-routing mechanism, the compiler is unaware of the functions being exposed, and cannot list them. Also - * {TransparentUpgradeableProxy} does not inherit from this interface because it's implemented in a way that the - * compiler doesn't understand and cannot verify. + * @dev Interface for {TransparentUpgradeableProxy}. In order to implement transparency, {TransparentUpgradeableProxy} + * does not implement this interface directly, and some of its functions are implemented by an internal dispatch + * mechanism. The compiler is unaware that these functions are implemented by {TransparentUpgradeableProxy} and will not + * include them in the ABI so this interface must be used to interact with it. */ interface ITransparentUpgradeableProxy is IERC1967 { function admin() external view returns (address); @@ -44,12 +44,16 @@ interface ITransparentUpgradeableProxy is IERC1967 { * Our recommendation is for the dedicated account to be an instance of the {ProxyAdmin} contract. If set up this way, * you should think of the `ProxyAdmin` instance as the real administrative interface of your proxy. * - * WARNING: This contract does not inherit from {ITransparentUpgradeableProxy}, and the admin function is implicitly - * implemented using a custom call-routing mechanism in `_fallback`. Consequently, the compiler will not produce an - * ABI for this contract. Also, if you inherit from this contract and add additional functions, the compiler will not - * check that there are no selector conflicts. A selector clash between any new function and the functions declared in - * {ITransparentUpgradeableProxy} will be resolved in favor of the new one. This could render the admin operations - * inaccessible, which could prevent upgradeability. + * NOTE: The real interface of this proxy is that defined in `ITransparentUpgradeableProxy`. This contract does not + * inherit from that interface, and instead the admin functions are implicitly implemented using a custom dispatch + * mechanism in `_fallback`. Consequently, the compiler will not produce an ABI for this contract. This is necessary to + * fully implement transparency without decoding reverts caused by selector clashes between the proxy and the + * implementation. + * + * WARNING: It is not recommended to extend this contract to add additional external functions. If you do so, the compiler + * will not check that there are no selector conflicts, due to the note above. A selector clash between any new function + * and the functions declared in {ITransparentUpgradeableProxy} will be resolved in favor of the new one. This could + * render the admin operations inaccessible, which could prevent upgradeability. Transparency may also be compromised. */ contract TransparentUpgradeableProxy is ERC1967Proxy { /** From 8d633cb7d169f2f8595b273660b00b69e845c2fe Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Thu, 13 Apr 2023 20:47:51 +0200 Subject: [PATCH 030/182] Merge pull request from GHSA-93hq-5wgc-jc82 Co-authored-by: Francisco --- .changeset/silent-pugs-scream.md | 5 +++++ .../compatibility/GovernorCompatibilityBravo.sol | 4 ++-- .../GovernorCompatibilityBravo.test.js | 15 +++++++++++++++ 3 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 .changeset/silent-pugs-scream.md diff --git a/.changeset/silent-pugs-scream.md b/.changeset/silent-pugs-scream.md new file mode 100644 index 000000000..c92d12486 --- /dev/null +++ b/.changeset/silent-pugs-scream.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': patch +--- + +`GovernorCompatibilityBravo`: Fix encoding of proposal data when signatures are missing. diff --git a/contracts/governance/compatibility/GovernorCompatibilityBravo.sol b/contracts/governance/compatibility/GovernorCompatibilityBravo.sol index 25f404403..7fd8ce0c4 100644 --- a/contracts/governance/compatibility/GovernorCompatibilityBravo.sol +++ b/contracts/governance/compatibility/GovernorCompatibilityBravo.sol @@ -70,6 +70,7 @@ abstract contract GovernorCompatibilityBravo is IGovernorTimelock, IGovernorComp bytes[] memory calldatas, string memory description ) public virtual override returns (uint256) { + require(signatures.length == calldatas.length, "GovernorBravo: invalid signatures length"); // Stores the full proposal and fallback to the public (possibly overridden) propose. The fallback is done // after the full proposal is stored, so the store operation included in the fallback will be skipped. Here we // call `propose` and not `super.propose` to make sure if a child contract override `propose`, whatever code @@ -149,8 +150,7 @@ abstract contract GovernorCompatibilityBravo is IGovernorTimelock, IGovernorComp bytes[] memory calldatas ) private pure returns (bytes[] memory) { bytes[] memory fullcalldatas = new bytes[](calldatas.length); - - for (uint256 i = 0; i < signatures.length; ++i) { + for (uint256 i = 0; i < fullcalldatas.length; ++i) { fullcalldatas[i] = bytes(signatures[i]).length == 0 ? calldatas[i] : abi.encodePacked(bytes4(keccak256(bytes(signatures[i]))), calldatas[i]); diff --git a/test/governance/compatibility/GovernorCompatibilityBravo.test.js b/test/governance/compatibility/GovernorCompatibilityBravo.test.js index 9e551949f..5a8104a98 100644 --- a/test/governance/compatibility/GovernorCompatibilityBravo.test.js +++ b/test/governance/compatibility/GovernorCompatibilityBravo.test.js @@ -224,6 +224,21 @@ contract('GovernorCompatibilityBravo', function (accounts) { }); }); + it('with inconsistent array size for selector and arguments', async function () { + const target = this.receiver.address; + this.helper.setProposal( + { + targets: [target, target], + values: [0, 0], + signatures: ['mockFunction()'], // One signature + data: ['0x', this.receiver.contract.methods.mockFunctionWithArgs(17, 42).encodeABI()], // Two data entries + }, + '', + ); + + await expectRevert(this.helper.propose({ from: proposer }), 'GovernorBravo: invalid signatures length'); + }); + describe('should revert', function () { describe('on propose', function () { it('if proposal does not meet proposalThreshold', async function () { From 91df66c4a9dfd0425ff923cbeb3a20155f1355ea Mon Sep 17 00:00:00 2001 From: Francisco Date: Fri, 21 Apr 2023 12:35:07 +0100 Subject: [PATCH 031/182] Implement suggestions from audit of 4.9 (#4176) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ernesto García --- contracts/governance/Governor.sol | 54 +++++++++++-------- contracts/governance/IGovernor.sol | 8 ++- contracts/governance/TimelockController.sol | 25 +++++---- .../GovernorCompatibilityBravo.sol | 10 ++-- .../IGovernorCompatibilityBravo.sol | 5 ++ .../extensions/GovernorPreventLateQuorum.sol | 12 ++--- .../extensions/GovernorTimelockCompound.sol | 14 +++-- .../extensions/GovernorTimelockControl.sol | 10 ++-- .../GovernorVotesQuorumFraction.sol | 7 ++- contracts/governance/utils/IVotes.sol | 4 +- contracts/governance/utils/Votes.sol | 6 +-- .../token/ERC20/extensions/ERC20Votes.sol | 2 +- .../token/ERC721/extensions/ERC721Votes.sol | 2 + test/governance/Governor.test.js | 6 +-- .../SupportsInterface.behavior.js | 8 ++- 15 files changed, 97 insertions(+), 76 deletions(-) diff --git a/contracts/governance/Governor.sol b/contracts/governance/Governor.sol index a2911bf98..241d6139b 100644 --- a/contracts/governance/Governor.sol +++ b/contracts/governance/Governor.sol @@ -17,7 +17,7 @@ import "./IGovernor.sol"; /** * @dev Core of the governance system, designed to be extended though various modules. * - * This contract is abstract and requires several function to be implemented in various modules: + * This contract is abstract and requires several functions to be implemented in various modules: * * - A counting module must implement {quorum}, {_quorumReached}, {_voteSucceeded} and {_countVote} * - A voting module must implement {_getVotes} @@ -27,7 +27,6 @@ import "./IGovernor.sol"; */ abstract contract Governor is Context, ERC165, EIP712, IGovernor, IERC721Receiver, IERC1155Receiver { using DoubleEndedQueue for DoubleEndedQueue.Bytes32Deque; - using SafeCast for uint256; bytes32 public constant BALLOT_TYPEHASH = keccak256("Ballot(uint256 proposalId,uint8 support)"); bytes32 public constant EXTENDED_BALLOT_TYPEHASH = @@ -90,25 +89,34 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor, IERC721Receive * @dev Function to receive ETH that will be handled by the governor (disabled if executor is a third party contract) */ receive() external payable virtual { - require(_executor() == address(this)); + require(_executor() == address(this), "Governor: must send to executor"); } /** * @dev See {IERC165-supportsInterface}. */ function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC165) returns (bool) { - // In addition to the current interfaceId, also support previous version of the interfaceId that did not - // include the castVoteWithReasonAndParams() function as standard + bytes4 governorCancelId = this.cancel.selector ^ this.proposalProposer.selector; + + bytes4 governorParamsId = this.castVoteWithReasonAndParams.selector ^ + this.castVoteWithReasonAndParamsBySig.selector ^ + this.getVotesWithParams.selector; + + // The original interface id in v4.3. + bytes4 governor43Id = type(IGovernor).interfaceId ^ + type(IERC6372).interfaceId ^ + governorCancelId ^ + governorParamsId; + + // An updated interface id in v4.6, with params added. + bytes4 governor46Id = type(IGovernor).interfaceId ^ type(IERC6372).interfaceId ^ governorCancelId; + + // For the updated interface id in v4.9, we use governorCancelId directly. + return - interfaceId == - (type(IGovernor).interfaceId ^ - type(IERC6372).interfaceId ^ - this.cancel.selector ^ - this.castVoteWithReasonAndParams.selector ^ - this.castVoteWithReasonAndParamsBySig.selector ^ - this.getVotesWithParams.selector) || - // Previous interface for backwards compatibility - interfaceId == (type(IGovernor).interfaceId ^ type(IERC6372).interfaceId ^ this.cancel.selector) || + interfaceId == governor43Id || + interfaceId == governor46Id || + interfaceId == governorCancelId || interfaceId == type(IERC1155Receiver).interfaceId || super.supportsInterface(interfaceId); } @@ -210,9 +218,9 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor, IERC721Receive } /** - * @dev Address of the proposer + * @dev Returns the account that created a given proposal. */ - function _proposalProposer(uint256 proposalId) internal view virtual returns (address) { + function proposalProposer(uint256 proposalId) public view virtual override returns (address) { return _proposals[proposalId].proposer; } @@ -283,8 +291,8 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor, IERC721Receive _proposals[proposalId] = ProposalCore({ proposer: proposer, - voteStart: snapshot.toUint64(), - voteEnd: deadline.toUint64(), + voteStart: SafeCast.toUint64(snapshot), + voteEnd: SafeCast.toUint64(deadline), executed: false, canceled: false, __gap_unused0: 0, @@ -317,9 +325,9 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor, IERC721Receive ) public payable virtual override returns (uint256) { uint256 proposalId = hashProposal(targets, values, calldatas, descriptionHash); - ProposalState status = state(proposalId); + ProposalState currentState = state(proposalId); require( - status == ProposalState.Succeeded || status == ProposalState.Queued, + currentState == ProposalState.Succeeded || currentState == ProposalState.Queued, "Governor: proposal not successful" ); _proposals[proposalId].executed = true; @@ -415,10 +423,12 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor, IERC721Receive ) internal virtual returns (uint256) { uint256 proposalId = hashProposal(targets, values, calldatas, descriptionHash); - ProposalState status = state(proposalId); + ProposalState currentState = state(proposalId); require( - status != ProposalState.Canceled && status != ProposalState.Expired && status != ProposalState.Executed, + currentState != ProposalState.Canceled && + currentState != ProposalState.Expired && + currentState != ProposalState.Executed, "Governor: proposal not active" ); _proposals[proposalId].canceled = true; diff --git a/contracts/governance/IGovernor.sol b/contracts/governance/IGovernor.sol index 70f81efe8..e4ad83e87 100644 --- a/contracts/governance/IGovernor.sol +++ b/contracts/governance/IGovernor.sol @@ -152,6 +152,12 @@ abstract contract IGovernor is IERC165, IERC6372 { */ function proposalDeadline(uint256 proposalId) public view virtual returns (uint256); + /** + * @notice module:core + * @dev The account that created a proposal. + */ + function proposalProposer(uint256 proposalId) public view virtual returns (address); + /** * @notice module:user-config * @dev Delay, between the proposal is created and the vote starts. The unit this duration is expressed in depends @@ -164,7 +170,7 @@ abstract contract IGovernor is IERC165, IERC6372 { /** * @notice module:user-config - * @dev Delay, between the vote start and vote ends. The unit this duration is expressed in depends on the clock + * @dev Delay between the vote start and vote end. The unit this duration is expressed in depends on the clock * (see EIP-6372) this contract uses. * * NOTE: The {votingDelay} can delay the start of the vote. This must be considered when setting the voting diff --git a/contracts/governance/TimelockController.sol b/contracts/governance/TimelockController.sol index 18ca81e5f..e330cca06 100644 --- a/contracts/governance/TimelockController.sol +++ b/contracts/governance/TimelockController.sol @@ -6,7 +6,6 @@ pragma solidity ^0.8.0; import "../access/AccessControl.sol"; import "../token/ERC721/IERC721Receiver.sol"; import "../token/ERC1155/IERC1155Receiver.sol"; -import "../utils/Address.sol"; /** * @dev Contract module which acts as a timelocked controller. When set as the @@ -137,21 +136,21 @@ contract TimelockController is AccessControl, IERC721Receiver, IERC1155Receiver * @dev Returns whether an id correspond to a registered operation. This * includes both Pending, Ready and Done operations. */ - function isOperation(bytes32 id) public view virtual returns (bool registered) { + function isOperation(bytes32 id) public view virtual returns (bool) { return getTimestamp(id) > 0; } /** - * @dev Returns whether an operation is pending or not. + * @dev Returns whether an operation is pending or not. Note that a "pending" operation may also be "ready". */ - function isOperationPending(bytes32 id) public view virtual returns (bool pending) { + function isOperationPending(bytes32 id) public view virtual returns (bool) { return getTimestamp(id) > _DONE_TIMESTAMP; } /** - * @dev Returns whether an operation is ready or not. + * @dev Returns whether an operation is ready for execution. Note that a "ready" operation is also "pending". */ - function isOperationReady(bytes32 id) public view virtual returns (bool ready) { + function isOperationReady(bytes32 id) public view virtual returns (bool) { uint256 timestamp = getTimestamp(id); return timestamp > _DONE_TIMESTAMP && timestamp <= block.timestamp; } @@ -159,7 +158,7 @@ contract TimelockController is AccessControl, IERC721Receiver, IERC1155Receiver /** * @dev Returns whether an operation is done or not. */ - function isOperationDone(bytes32 id) public view virtual returns (bool done) { + function isOperationDone(bytes32 id) public view virtual returns (bool) { return getTimestamp(id) == _DONE_TIMESTAMP; } @@ -167,7 +166,7 @@ contract TimelockController is AccessControl, IERC721Receiver, IERC1155Receiver * @dev Returns the timestamp at which an operation becomes ready (0 for * unset operations, 1 for done operations). */ - function getTimestamp(bytes32 id) public view virtual returns (uint256 timestamp) { + function getTimestamp(bytes32 id) public view virtual returns (uint256) { return _timestamps[id]; } @@ -176,7 +175,7 @@ contract TimelockController is AccessControl, IERC721Receiver, IERC1155Receiver * * This value can be changed by executing an operation that calls `updateDelay`. */ - function getMinDelay() public view virtual returns (uint256 duration) { + function getMinDelay() public view virtual returns (uint256) { return _minDelay; } @@ -190,7 +189,7 @@ contract TimelockController is AccessControl, IERC721Receiver, IERC1155Receiver bytes calldata data, bytes32 predecessor, bytes32 salt - ) public pure virtual returns (bytes32 hash) { + ) public pure virtual returns (bytes32) { return keccak256(abi.encode(target, value, data, predecessor, salt)); } @@ -204,14 +203,14 @@ contract TimelockController is AccessControl, IERC721Receiver, IERC1155Receiver bytes[] calldata payloads, bytes32 predecessor, bytes32 salt - ) public pure virtual returns (bytes32 hash) { + ) public pure virtual returns (bytes32) { return keccak256(abi.encode(targets, values, payloads, predecessor, salt)); } /** * @dev Schedule an operation containing a single transaction. * - * Emits events {CallScheduled} and {CallSalt}. + * Emits {CallSalt} if salt is nonzero, and {CallScheduled}. * * Requirements: * @@ -236,7 +235,7 @@ contract TimelockController is AccessControl, IERC721Receiver, IERC1155Receiver /** * @dev Schedule an operation containing a batch of transactions. * - * Emits a {CallSalt} event and one {CallScheduled} event per transaction in the batch. + * Emits {CallSalt} if salt is nonzero, and one {CallScheduled} event per transaction in the batch. * * Requirements: * diff --git a/contracts/governance/compatibility/GovernorCompatibilityBravo.sol b/contracts/governance/compatibility/GovernorCompatibilityBravo.sol index 7fd8ce0c4..0fbb4e17c 100644 --- a/contracts/governance/compatibility/GovernorCompatibilityBravo.sol +++ b/contracts/governance/compatibility/GovernorCompatibilityBravo.sol @@ -74,7 +74,7 @@ abstract contract GovernorCompatibilityBravo is IGovernorTimelock, IGovernorComp // Stores the full proposal and fallback to the public (possibly overridden) propose. The fallback is done // after the full proposal is stored, so the store operation included in the fallback will be skipped. Here we // call `propose` and not `super.propose` to make sure if a child contract override `propose`, whatever code - // is added their is also executed when calling this alternative interface. + // is added there is also executed when calling this alternative interface. _storeProposal(_msgSender(), targets, values, signatures, calldatas, description); return propose(targets, values, _encodeCalldata(signatures, calldatas), description); } @@ -110,7 +110,7 @@ abstract contract GovernorCompatibilityBravo is IGovernorTimelock, IGovernorComp /** * @dev Cancel a proposal with GovernorBravo logic. */ - function cancel(uint256 proposalId) public virtual { + function cancel(uint256 proposalId) public virtual override { ( address[] memory targets, uint256[] memory values, @@ -238,9 +238,9 @@ abstract contract GovernorCompatibilityBravo is IGovernorTimelock, IGovernorComp againstVotes = details.againstVotes; abstainVotes = details.abstainVotes; - ProposalState status = state(proposalId); - canceled = status == ProposalState.Canceled; - executed = status == ProposalState.Executed; + ProposalState currentState = state(proposalId); + canceled = currentState == ProposalState.Canceled; + executed = currentState == ProposalState.Executed; } /** diff --git a/contracts/governance/compatibility/IGovernorCompatibilityBravo.sol b/contracts/governance/compatibility/IGovernorCompatibilityBravo.sol index 159c55100..7aa806a18 100644 --- a/contracts/governance/compatibility/IGovernorCompatibilityBravo.sol +++ b/contracts/governance/compatibility/IGovernorCompatibilityBravo.sol @@ -90,6 +90,11 @@ abstract contract IGovernorCompatibilityBravo is IGovernor { */ function execute(uint256 proposalId) public payable virtual; + /** + * @dev Cancels a proposal only if the sender is the proposer or the proposer delegates' voting power dropped below the proposal threshold. + */ + function cancel(uint256 proposalId) public virtual; + /** * @dev Part of the Governor Bravo's interface: _"Gets actions of a proposal"_. */ diff --git a/contracts/governance/extensions/GovernorPreventLateQuorum.sol b/contracts/governance/extensions/GovernorPreventLateQuorum.sol index 676d2b13e..68496ca1e 100644 --- a/contracts/governance/extensions/GovernorPreventLateQuorum.sol +++ b/contracts/governance/extensions/GovernorPreventLateQuorum.sol @@ -12,14 +12,12 @@ import "../../utils/math/Math.sol"; * and try to oppose the decision. * * If a vote causes quorum to be reached, the proposal's voting period may be extended so that it does not end before at - * least a given number of blocks have passed (the "vote extension" parameter). This parameter can be set by the - * governance executor (e.g. through a governance proposal). + * least a specified time has passed (the "vote extension" parameter). This parameter can be set through a governance + * proposal. * * _Available since v4.5._ */ abstract contract GovernorPreventLateQuorum is Governor { - using SafeCast for uint256; - uint64 private _voteExtension; /// @custom:oz-retyped-from mapping(uint256 => Timers.BlockNumber) @@ -32,9 +30,9 @@ abstract contract GovernorPreventLateQuorum is Governor { event LateQuorumVoteExtensionSet(uint64 oldVoteExtension, uint64 newVoteExtension); /** - * @dev Initializes the vote extension parameter: the number of blocks that are required to pass since a proposal - * reaches quorum until its voting period ends. If necessary the voting period will be extended beyond the one set - * at proposal creation. + * @dev Initializes the vote extension parameter: the time in either number of blocks or seconds (depending on the governor + * clock mode) that is required to pass since the moment a proposal reaches quorum until its voting period ends. If + * necessary the voting period will be extended beyond the one set during proposal creation. */ constructor(uint64 initialVoteExtension) { _setLateQuorumVoteExtension(initialVoteExtension); diff --git a/contracts/governance/extensions/GovernorTimelockCompound.sol b/contracts/governance/extensions/GovernorTimelockCompound.sol index 629f8f800..912171cc3 100644 --- a/contracts/governance/extensions/GovernorTimelockCompound.sol +++ b/contracts/governance/extensions/GovernorTimelockCompound.sol @@ -21,8 +21,6 @@ import "../../vendor/compound/ICompoundTimelock.sol"; * _Available since v4.3._ */ abstract contract GovernorTimelockCompound is IGovernorTimelock, Governor { - using SafeCast for uint256; - ICompoundTimelock private _timelock; /// @custom:oz-retyped-from mapping(uint256 => GovernorTimelockCompound.ProposalTimelock) @@ -48,18 +46,18 @@ abstract contract GovernorTimelockCompound is IGovernorTimelock, Governor { } /** - * @dev Overridden version of the {Governor-state} function with added support for the `Queued` and `Expired` status. + * @dev Overridden version of the {Governor-state} function with added support for the `Queued` and `Expired` state. */ function state(uint256 proposalId) public view virtual override(IGovernor, Governor) returns (ProposalState) { - ProposalState status = super.state(proposalId); + ProposalState currentState = super.state(proposalId); - if (status != ProposalState.Succeeded) { - return status; + if (currentState != ProposalState.Succeeded) { + return currentState; } uint256 eta = proposalEta(proposalId); if (eta == 0) { - return status; + return currentState; } else if (block.timestamp >= eta + _timelock.GRACE_PERIOD()) { return ProposalState.Expired; } else { @@ -95,7 +93,7 @@ abstract contract GovernorTimelockCompound is IGovernorTimelock, Governor { require(state(proposalId) == ProposalState.Succeeded, "Governor: proposal not successful"); uint256 eta = block.timestamp + _timelock.delay(); - _proposalTimelocks[proposalId] = eta.toUint64(); + _proposalTimelocks[proposalId] = SafeCast.toUint64(eta); for (uint256 i = 0; i < targets.length; ++i) { require( diff --git a/contracts/governance/extensions/GovernorTimelockControl.sol b/contracts/governance/extensions/GovernorTimelockControl.sol index 6aa2556ab..0cf2ea5f0 100644 --- a/contracts/governance/extensions/GovernorTimelockControl.sol +++ b/contracts/governance/extensions/GovernorTimelockControl.sol @@ -47,19 +47,19 @@ abstract contract GovernorTimelockControl is IGovernorTimelock, Governor { } /** - * @dev Overridden version of the {Governor-state} function with added support for the `Queued` status. + * @dev Overridden version of the {Governor-state} function with added support for the `Queued` state. */ function state(uint256 proposalId) public view virtual override(IGovernor, Governor) returns (ProposalState) { - ProposalState status = super.state(proposalId); + ProposalState currentState = super.state(proposalId); - if (status != ProposalState.Succeeded) { - return status; + if (currentState != ProposalState.Succeeded) { + return currentState; } // core tracks execution, so we just have to check if successful proposal have been queued. bytes32 queueid = _timelockIds[proposalId]; if (queueid == bytes32(0)) { - return status; + return currentState; } else if (_timelock.isOperationDone(queueid)) { return ProposalState.Executed; } else if (_timelock.isOperationPending(queueid)) { diff --git a/contracts/governance/extensions/GovernorVotesQuorumFraction.sol b/contracts/governance/extensions/GovernorVotesQuorumFraction.sol index 19c5b194d..403570260 100644 --- a/contracts/governance/extensions/GovernorVotesQuorumFraction.sol +++ b/contracts/governance/extensions/GovernorVotesQuorumFraction.sol @@ -14,7 +14,6 @@ import "../../utils/math/SafeCast.sol"; * _Available since v4.3._ */ abstract contract GovernorVotesQuorumFraction is GovernorVotes { - using SafeCast for *; using Checkpoints for Checkpoints.Trace224; uint256 private _quorumNumerator; // DEPRECATED in favor of _quorumNumeratorHistory @@ -59,7 +58,7 @@ abstract contract GovernorVotesQuorumFraction is GovernorVotes { } // Otherwise, do the binary search - return _quorumNumeratorHistory.upperLookupRecent(timepoint.toUint32()); + return _quorumNumeratorHistory.upperLookupRecent(SafeCast.toUint32(timepoint)); } /** @@ -110,12 +109,12 @@ abstract contract GovernorVotesQuorumFraction is GovernorVotes { // Make sure we keep track of the original numerator in contracts upgraded from a version without checkpoints. if (oldQuorumNumerator != 0 && _quorumNumeratorHistory._checkpoints.length == 0) { _quorumNumeratorHistory._checkpoints.push( - Checkpoints.Checkpoint224({_key: 0, _value: oldQuorumNumerator.toUint224()}) + Checkpoints.Checkpoint224({_key: 0, _value: SafeCast.toUint224(oldQuorumNumerator)}) ); } // Set new quorum for future proposals - _quorumNumeratorHistory.push(clock().toUint32(), newQuorumNumerator.toUint224()); + _quorumNumeratorHistory.push(SafeCast.toUint32(clock()), SafeCast.toUint224(newQuorumNumerator)); emit QuorumNumeratorUpdated(oldQuorumNumerator, newQuorumNumerator); } diff --git a/contracts/governance/utils/IVotes.sol b/contracts/governance/utils/IVotes.sol index c73d0732b..4f2b7eb85 100644 --- a/contracts/governance/utils/IVotes.sol +++ b/contracts/governance/utils/IVotes.sol @@ -25,13 +25,13 @@ interface IVotes { /** * @dev Returns the amount of votes that `account` had at a specific moment in the past. If the `clock()` is - * configured to use block numbers, this will return the value the end of the corresponding block. + * configured to use block numbers, this will return the value at the end of the corresponding block. */ function getPastVotes(address account, uint256 timepoint) external view returns (uint256); /** * @dev Returns the total supply of votes available at a specific moment in the past. If the `clock()` is - * configured to use block numbers, this will return the value the end of the corresponding block. + * configured to use block numbers, this will return the value at the end of the corresponding block. * * NOTE: This value is the sum of all available votes, which is not necessarily the sum of all delegated votes. * Votes that have not been delegated are still part of total supply, even though they would not participate in a diff --git a/contracts/governance/utils/Votes.sol b/contracts/governance/utils/Votes.sol index f70cf3831..b24ce824a 100644 --- a/contracts/governance/utils/Votes.sol +++ b/contracts/governance/utils/Votes.sol @@ -59,7 +59,7 @@ abstract contract Votes is Context, EIP712, IERC5805 { // solhint-disable-next-line func-name-mixedcase function CLOCK_MODE() public view virtual override returns (string memory) { // Check that the clock was not modified - require(clock() == block.number); + require(clock() == block.number, "Votes: broken clock mode"); return "mode=blocknumber&from=default"; } @@ -72,7 +72,7 @@ abstract contract Votes is Context, EIP712, IERC5805 { /** * @dev Returns the amount of votes that `account` had at a specific moment in the past. If the `clock()` is - * configured to use block numbers, this will return the value the end of the corresponding block. + * configured to use block numbers, this will return the value at the end of the corresponding block. * * Requirements: * @@ -85,7 +85,7 @@ abstract contract Votes is Context, EIP712, IERC5805 { /** * @dev Returns the total supply of votes available at a specific moment in the past. If the `clock()` is - * configured to use block numbers, this will return the value the end of the corresponding block. + * configured to use block numbers, this will return the value at the end of the corresponding block. * * NOTE: This value is the sum of all available votes, which is not necessarily the sum of all delegated votes. * Votes that have not been delegated are still part of total supply, even though they would not participate in a diff --git a/contracts/token/ERC20/extensions/ERC20Votes.sol b/contracts/token/ERC20/extensions/ERC20Votes.sol index f78938e6d..d72d34f81 100644 --- a/contracts/token/ERC20/extensions/ERC20Votes.sol +++ b/contracts/token/ERC20/extensions/ERC20Votes.sol @@ -50,7 +50,7 @@ abstract contract ERC20Votes is ERC20Permit, IERC5805 { // solhint-disable-next-line func-name-mixedcase function CLOCK_MODE() public view virtual override returns (string memory) { // Check that the clock was not modified - require(clock() == block.number); + require(clock() == block.number, "ERC20Votes: broken clock mode"); return "mode=blocknumber&from=default"; } diff --git a/contracts/token/ERC721/extensions/ERC721Votes.sol b/contracts/token/ERC721/extensions/ERC721Votes.sol index 8e6500ec3..31397f107 100644 --- a/contracts/token/ERC721/extensions/ERC721Votes.sol +++ b/contracts/token/ERC721/extensions/ERC721Votes.sol @@ -34,6 +34,8 @@ abstract contract ERC721Votes is ERC721, Votes { /** * @dev Returns the balance of `account`. + * + * WARNING: Overriding this function will likely result in incorrect vote tracking. */ function _getVotingUnits(address account) internal view virtual override returns (uint256) { return balanceOf(account); diff --git a/test/governance/Governor.test.js b/test/governance/Governor.test.js index 1eabaccf0..f867958b5 100644 --- a/test/governance/Governor.test.js +++ b/test/governance/Governor.test.js @@ -70,7 +70,7 @@ contract('Governor', function (accounts) { ); }); - shouldSupportInterfaces(['ERC165', 'ERC1155Receiver', 'Governor', 'GovernorWithParams']); + shouldSupportInterfaces(['ERC165', 'ERC1155Receiver', 'Governor', 'GovernorWithParams', 'GovernorCancel']); shouldBehaveLikeEIP6372(mode); it('deployment check', async function () { @@ -84,7 +84,7 @@ contract('Governor', function (accounts) { it('nominal workflow', async function () { // Before - expect(await this.mock.$_proposalProposer(this.proposal.id)).to.be.equal(constants.ZERO_ADDRESS); + expect(await this.mock.proposalProposer(this.proposal.id)).to.be.equal(constants.ZERO_ADDRESS); expect(await this.mock.hasVoted(this.proposal.id, owner)).to.be.equal(false); expect(await this.mock.hasVoted(this.proposal.id, voter1)).to.be.equal(false); expect(await this.mock.hasVoted(this.proposal.id, voter2)).to.be.equal(false); @@ -149,7 +149,7 @@ contract('Governor', function (accounts) { await expectEvent.inTransaction(txExecute.tx, this.receiver, 'MockFunctionCalled'); // After - expect(await this.mock.$_proposalProposer(this.proposal.id)).to.be.equal(proposer); + expect(await this.mock.proposalProposer(this.proposal.id)).to.be.equal(proposer); expect(await this.mock.hasVoted(this.proposal.id, owner)).to.be.equal(false); expect(await this.mock.hasVoted(this.proposal.id, voter1)).to.be.equal(true); expect(await this.mock.hasVoted(this.proposal.id, voter2)).to.be.equal(true); diff --git a/test/utils/introspection/SupportsInterface.behavior.js b/test/utils/introspection/SupportsInterface.behavior.js index 541f6c611..201a55f47 100644 --- a/test/utils/introspection/SupportsInterface.behavior.js +++ b/test/utils/introspection/SupportsInterface.behavior.js @@ -90,6 +90,7 @@ const INTERFACES = { 'castVoteBySig(uint256,uint8,uint8,bytes32,bytes32)', 'castVoteWithReasonAndParamsBySig(uint256,uint8,string,bytes,uint8,bytes32,bytes32)', ], + GovernorCancel: ['proposalProposer(uint256)', 'cancel(address[],uint256[],bytes[],bytes32)'], GovernorTimelock: ['timelock()', 'proposalEta(uint256)', 'queue(address[],uint256[],bytes[],bytes32)'], ERC2981: ['royaltyInfo(uint256,uint256)'], }; @@ -120,7 +121,7 @@ function shouldSupportInterfaces(interfaces = []) { it('all interfaces are reported as supported', async function () { for (const k of interfaces) { const interfaceId = INTERFACE_IDS[k] ?? k; - expect(await this.contractUnderTest.supportsInterface(interfaceId)).to.equal(true); + expect(await this.contractUnderTest.supportsInterface(interfaceId)).to.equal(true, `does not support ${k}`); } }); @@ -130,7 +131,10 @@ function shouldSupportInterfaces(interfaces = []) { if (INTERFACES[k] === undefined) continue; for (const fnName of INTERFACES[k]) { const fnSig = FN_SIGNATURES[fnName]; - expect(this.contractUnderTest.abi.filter(fn => fn.signature === fnSig).length).to.equal(1); + expect(this.contractUnderTest.abi.filter(fn => fn.signature === fnSig).length).to.equal( + 1, + `did not find ${fnName}`, + ); } } }); From 6aac66d065dc39795fbad1f680f80e3a3d70f2d1 Mon Sep 17 00:00:00 2001 From: Francisco Date: Mon, 24 Apr 2023 13:18:27 +0100 Subject: [PATCH 032/182] Merge release-v4.8 (#4188) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Hadrien Croubois Co-authored-by: Benjamin Co-authored-by: Owen Co-authored-by: Hadrien Croubois Co-authored-by: JulissaDantes Co-authored-by: Ernesto García Co-authored-by: Yamen Merhi Co-authored-by: Pascal Marco Caversaccio Co-authored-by: alpharush <0xalpharush@protonmail.com> Co-authored-by: Paul Razvan Berg --- .changeset/silent-pugs-scream.md | 5 ----- .changeset/thirty-shrimps-mix.md | 5 ----- CHANGELOG.md | 5 +++++ .../governance/compatibility/GovernorCompatibilityBravo.sol | 2 +- contracts/proxy/ERC1967/ERC1967Upgrade.sol | 2 +- contracts/proxy/transparent/ProxyAdmin.sol | 2 +- contracts/proxy/transparent/TransparentUpgradeableProxy.sol | 2 +- 7 files changed, 9 insertions(+), 14 deletions(-) delete mode 100644 .changeset/silent-pugs-scream.md delete mode 100644 .changeset/thirty-shrimps-mix.md diff --git a/.changeset/silent-pugs-scream.md b/.changeset/silent-pugs-scream.md deleted file mode 100644 index c92d12486..000000000 --- a/.changeset/silent-pugs-scream.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': patch ---- - -`GovernorCompatibilityBravo`: Fix encoding of proposal data when signatures are missing. diff --git a/.changeset/thirty-shrimps-mix.md b/.changeset/thirty-shrimps-mix.md deleted file mode 100644 index 656edbf92..000000000 --- a/.changeset/thirty-shrimps-mix.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': patch ---- - -`TransparentUpgradeableProxy`: Fix transparency in case of selector clash with non-decodable calldata or payable mutability. diff --git a/CHANGELOG.md b/CHANGELOG.md index 333b6d4a3..ffbd3acea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,11 @@ - `ERC777`: The `ERC777` token standard is no longer supported by OpenZeppelin. Our implementation is now deprecated and will be removed in the next major release. The corresponding standard interfaces remain available. ([#4066](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4066)) - `ERC1820Implementer`: The `ERC1820` pseudo-introspection mechanism is no longer supported by OpenZeppelin. Our implementation is now deprecated and will be removed in the next major release. The corresponding standard interfaces remain available. ([#4066](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4066)) +## 4.8.3 (2023-04-13) + +- `GovernorCompatibilityBravo`: Fix encoding of proposal data when signatures are missing. +- `TransparentUpgradeableProxy`: Fix transparency in case of selector clash with non-decodable calldata or payable mutability. ([#4154](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4154)) + ## 4.8.2 (2023-03-02) - `ERC721Consecutive`: Fixed a bug when `_mintConsecutive` is used for batches of size 1 that could lead to balance overflow. Refer to the breaking changes section in the changelog for a note on the behavior of `ERC721._beforeTokenTransfer`. diff --git a/contracts/governance/compatibility/GovernorCompatibilityBravo.sol b/contracts/governance/compatibility/GovernorCompatibilityBravo.sol index 0fbb4e17c..1332ac79d 100644 --- a/contracts/governance/compatibility/GovernorCompatibilityBravo.sol +++ b/contracts/governance/compatibility/GovernorCompatibilityBravo.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.8.0) (governance/compatibility/GovernorCompatibilityBravo.sol) +// OpenZeppelin Contracts (last updated v4.8.3) (governance/compatibility/GovernorCompatibilityBravo.sol) pragma solidity ^0.8.0; diff --git a/contracts/proxy/ERC1967/ERC1967Upgrade.sol b/contracts/proxy/ERC1967/ERC1967Upgrade.sol index a79c10502..3942ca699 100644 --- a/contracts/proxy/ERC1967/ERC1967Upgrade.sol +++ b/contracts/proxy/ERC1967/ERC1967Upgrade.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.5.0) (proxy/ERC1967/ERC1967Upgrade.sol) +// OpenZeppelin Contracts (last updated v4.8.3) (proxy/ERC1967/ERC1967Upgrade.sol) pragma solidity ^0.8.2; diff --git a/contracts/proxy/transparent/ProxyAdmin.sol b/contracts/proxy/transparent/ProxyAdmin.sol index 1a9698196..571530595 100644 --- a/contracts/proxy/transparent/ProxyAdmin.sol +++ b/contracts/proxy/transparent/ProxyAdmin.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts v4.4.1 (proxy/transparent/ProxyAdmin.sol) +// OpenZeppelin Contracts (last updated v4.8.3) (proxy/transparent/ProxyAdmin.sol) pragma solidity ^0.8.0; diff --git a/contracts/proxy/transparent/TransparentUpgradeableProxy.sol b/contracts/proxy/transparent/TransparentUpgradeableProxy.sol index d8765fc9e..4e2b0c759 100644 --- a/contracts/proxy/transparent/TransparentUpgradeableProxy.sol +++ b/contracts/proxy/transparent/TransparentUpgradeableProxy.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.7.0) (proxy/transparent/TransparentUpgradeableProxy.sol) +// OpenZeppelin Contracts (last updated v4.8.3) (proxy/transparent/TransparentUpgradeableProxy.sol) pragma solidity ^0.8.0; From 1a079d258b15409970f181702669fa8738d0edef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Tue, 25 Apr 2023 13:31:01 +0200 Subject: [PATCH 033/182] Improve Address tests (#4191) --- contracts/mocks/CallReceiverMock.sol | 4 +++ contracts/utils/Address.sol | 2 +- test/utils/Address.test.js | 40 ++++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 1 deletion(-) diff --git a/contracts/mocks/CallReceiverMock.sol b/contracts/mocks/CallReceiverMock.sol index 344a1054b..492adbe92 100644 --- a/contracts/mocks/CallReceiverMock.sol +++ b/contracts/mocks/CallReceiverMock.sol @@ -14,6 +14,10 @@ contract CallReceiverMock { return "0x1234"; } + function mockFunctionEmptyReturn() public payable { + emit MockFunctionCalled(); + } + function mockFunctionWithArgs(uint256 a, uint256 b) public payable returns (string memory) { emit MockFunctionCalledWithArgs(a, b); diff --git a/contracts/utils/Address.sol b/contracts/utils/Address.sol index 433a866d7..5ff14140a 100644 --- a/contracts/utils/Address.sol +++ b/contracts/utils/Address.sol @@ -59,7 +59,7 @@ library Address { * IMPORTANT: because control is transferred to `recipient`, care must be * taken to not create reentrancy vulnerabilities. Consider using * {ReentrancyGuard} or the - * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. + * https://solidity.readthedocs.io/en/v0.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. */ function sendValue(address payable recipient, uint256 amount) internal { require(address(this).balance >= amount, "Address: insufficient balance"); diff --git a/test/utils/Address.test.js b/test/utils/Address.test.js index a78ae14e6..4f9f9eea1 100644 --- a/test/utils/Address.test.js +++ b/test/utils/Address.test.js @@ -107,6 +107,14 @@ contract('Address', function (accounts) { await expectEvent.inTransaction(receipt.tx, CallReceiverMock, 'MockFunctionCalled'); }); + it('calls the requested empty return function', async function () { + const abiEncodedCall = this.target.contract.methods.mockFunctionEmptyReturn().encodeABI(); + + const receipt = await this.mock.$functionCall(this.target.address, abiEncodedCall); + + await expectEvent.inTransaction(receipt.tx, CallReceiverMock, 'MockFunctionCalled'); + }); + it('reverts when the called function reverts with no reason', async function () { const abiEncodedCall = this.target.contract.methods.mockFunctionRevertsNoReason().encodeABI(); @@ -137,6 +145,11 @@ contract('Address', function (accounts) { await expectRevert.unspecified(this.mock.$functionCall(this.target.address, abiEncodedCall)); }); + it('bubbles up error message if specified', async function () { + const errorMsg = 'Address: expected error'; + await expectRevert(this.mock.$functionCall(this.target.address, '0x12345678', errorMsg), errorMsg); + }); + it('reverts when function does not exist', async function () { const abiEncodedCall = web3.eth.abi.encodeFunctionCall( { @@ -237,6 +250,11 @@ contract('Address', function (accounts) { 'Address: low-level call with value failed', ); }); + + it('bubbles up error message if specified', async function () { + const errorMsg = 'Address: expected error'; + await expectRevert(this.mock.$functionCallWithValue(this.target.address, '0x12345678', 0, errorMsg), errorMsg); + }); }); }); @@ -277,6 +295,11 @@ contract('Address', function (accounts) { await expectRevert(this.mock.$functionStaticCall(recipient, abiEncodedCall), 'Address: call to non-contract'); }); + + it('bubbles up error message if specified', async function () { + const errorMsg = 'Address: expected error'; + await expectRevert(this.mock.$functionCallWithValue(this.target.address, '0x12345678', 0, errorMsg), errorMsg); + }); }); describe('functionDelegateCall', function () { @@ -317,5 +340,22 @@ contract('Address', function (accounts) { await expectRevert(this.mock.$functionDelegateCall(recipient, abiEncodedCall), 'Address: call to non-contract'); }); + + it('bubbles up error message if specified', async function () { + const errorMsg = 'Address: expected error'; + await expectRevert(this.mock.$functionCallWithValue(this.target.address, '0x12345678', 0, errorMsg), errorMsg); + }); + }); + + describe('verifyCallResult', function () { + it('returns returndata on success', async function () { + const returndata = '0x123abc'; + expect(await this.mock.$verifyCallResult(true, returndata, '')).to.equal(returndata); + }); + + it('reverts with return data and error m', async function () { + const errorMsg = 'Address: expected error'; + await expectRevert(this.mock.$verifyCallResult(false, '0x', errorMsg), errorMsg); + }); }); }); From 8f14d52b7366eb8dcdbf40a92b2d1cb14ded9a13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Tue, 25 Apr 2023 17:57:16 +0200 Subject: [PATCH 034/182] Fix Checkpoints fuzz overflow (#4184) Co-authored-by: Francisco --- scripts/generate/templates/Checkpoints.t.js | 22 ++++++++++------- test/utils/Checkpoints.t.sol | 26 +++++++++++++-------- 2 files changed, 29 insertions(+), 19 deletions(-) diff --git a/scripts/generate/templates/Checkpoints.t.js b/scripts/generate/templates/Checkpoints.t.js index 84b5992ad..b3da933a1 100644 --- a/scripts/generate/templates/Checkpoints.t.js +++ b/scripts/generate/templates/Checkpoints.t.js @@ -86,10 +86,12 @@ function testPush( if (keys.length > 0) { ${opts.keyTypeName} lastKey = keys[keys.length - 1]; - pastKey = _bound${capitalize(opts.keyTypeName)}(pastKey, 0, lastKey - 1); - - vm.expectRevert(); - this.push(pastKey, values[keys.length % values.length]); + if (lastKey > 0) { + pastKey = _bound${capitalize(opts.keyTypeName)}(pastKey, 0, lastKey - 1); + + vm.expectRevert(); + this.push(pastKey, values[keys.length % values.length]); + } } } @@ -173,11 +175,13 @@ function testPush( // Can't push any key in the past if (keys.length > 0) { ${opts.keyTypeName} lastKey = keys[keys.length - 1]; - pastKey = _bound${capitalize(opts.keyTypeName)}(pastKey, 0, lastKey - 1); - - vm.roll(pastKey); - vm.expectRevert(); - this.push(values[keys.length % values.length]); + if (lastKey > 0) { + pastKey = _bound${capitalize(opts.keyTypeName)}(pastKey, 0, lastKey - 1); + + vm.roll(pastKey); + vm.expectRevert(); + this.push(values[keys.length % values.length]); + } } } diff --git a/test/utils/Checkpoints.t.sol b/test/utils/Checkpoints.t.sol index abdf8b436..f2cb587d5 100644 --- a/test/utils/Checkpoints.t.sol +++ b/test/utils/Checkpoints.t.sol @@ -66,11 +66,13 @@ contract CheckpointsHistoryTest is Test { // Can't push any key in the past if (keys.length > 0) { uint32 lastKey = keys[keys.length - 1]; - pastKey = _boundUint32(pastKey, 0, lastKey - 1); + if (lastKey > 0) { + pastKey = _boundUint32(pastKey, 0, lastKey - 1); - vm.roll(pastKey); - vm.expectRevert(); - this.push(values[keys.length % values.length]); + vm.roll(pastKey); + vm.expectRevert(); + this.push(values[keys.length % values.length]); + } } } @@ -185,10 +187,12 @@ contract CheckpointsTrace224Test is Test { if (keys.length > 0) { uint32 lastKey = keys[keys.length - 1]; - pastKey = _boundUint32(pastKey, 0, lastKey - 1); + if (lastKey > 0) { + pastKey = _boundUint32(pastKey, 0, lastKey - 1); - vm.expectRevert(); - this.push(pastKey, values[keys.length % values.length]); + vm.expectRevert(); + this.push(pastKey, values[keys.length % values.length]); + } } } @@ -291,10 +295,12 @@ contract CheckpointsTrace160Test is Test { if (keys.length > 0) { uint96 lastKey = keys[keys.length - 1]; - pastKey = _boundUint96(pastKey, 0, lastKey - 1); + if (lastKey > 0) { + pastKey = _boundUint96(pastKey, 0, lastKey - 1); - vm.expectRevert(); - this.push(pastKey, values[keys.length % values.length]); + vm.expectRevert(); + this.push(pastKey, values[keys.length % values.length]); + } } } From f959d7e4e6ee0b022b41e5b644c79369869d8411 Mon Sep 17 00:00:00 2001 From: Pascal Marco Caversaccio Date: Tue, 25 Apr 2023 17:57:50 +0200 Subject: [PATCH 035/182] Fix release note in `IERC1967` (#4183) --- contracts/interfaces/IERC1967.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/interfaces/IERC1967.sol b/contracts/interfaces/IERC1967.sol index e5deebee9..ab4450eec 100644 --- a/contracts/interfaces/IERC1967.sol +++ b/contracts/interfaces/IERC1967.sol @@ -5,7 +5,7 @@ pragma solidity ^0.8.0; /** * @dev ERC-1967: Proxy Storage Slots. This interface contains the events defined in the ERC. * - * _Available since v4.9._ + * _Available since v4.8.3._ */ interface IERC1967 { /** From 44d6053b4376421a5b45ace021934b72d649d09b Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Fri, 28 Apr 2023 14:01:41 +0200 Subject: [PATCH 036/182] Only run FV on new or updated specs (#4195) --- .github/workflows/formal-verification.yml | 14 ++++- certora/run.js | 64 ++++++++++++++++------- certora/specs/Ownable.spec | 4 +- 3 files changed, 61 insertions(+), 21 deletions(-) diff --git a/.github/workflows/formal-verification.yml b/.github/workflows/formal-verification.yml index 29c02541c..f94152a52 100644 --- a/.github/workflows/formal-verification.yml +++ b/.github/workflows/formal-verification.yml @@ -33,8 +33,20 @@ jobs: if: github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'formal-verification') steps: - uses: actions/checkout@v3 + with: + fetch-depth: 0 - name: Set up environment uses: ./.github/actions/setup + - name: identify specs that need to be run + id: arguments + run: | + if [[ ${{ github.event_name }} = 'pull_request' ]]; + then + RESULT=$(git diff ${{ github.event.pull_request.head.sha }}..${{ github.event.pull_request.base.sha }} --name-only certora/specs/*.spec | while IFS= read -r file; do [[ -f $file ]] && basename "${file%.spec}"; done | tr "\n" " ") + else + RESULT='--all' + fi + echo "result=$RESULT" >> "$GITHUB_OUTPUT" - name: Install python uses: actions/setup-python@v4 with: @@ -55,6 +67,6 @@ jobs: - name: Verify specification run: | make -C certora apply - node certora/run.js >> "$GITHUB_STEP_SUMMARY" + node certora/run.js ${{ steps.arguments.outputs.result }} >> "$GITHUB_STEP_SUMMARY" env: CERTORAKEY: ${{ secrets.CERTORAKEY }} diff --git a/certora/run.js b/certora/run.js index f3234c1a3..2dc7d0181 100644 --- a/certora/run.js +++ b/certora/run.js @@ -1,37 +1,65 @@ #!/usr/bin/env node // USAGE: -// node certora/run.js [[CONTRACT_NAME:]SPEC_NAME] [OPTIONS...] +// node certora/run.js [[CONTRACT_NAME:]SPEC_NAME]* [--all] [--options OPTIONS...] [--specs PATH] // EXAMPLES: +// node certora/run.js --all // node certora/run.js AccessControl // node certora/run.js AccessControlHarness:AccessControl -const MAX_PARALLEL = 4; - -let specs = require(__dirname + '/specs.json'); - const proc = require('child_process'); const { PassThrough } = require('stream'); const events = require('events'); -const limit = require('p-limit')(MAX_PARALLEL); -let [, , request = '', ...extraOptions] = process.argv; -if (request.startsWith('-')) { - extraOptions.unshift(request); - request = ''; +const argv = require('yargs') + .env('') + .options({ + all: { + alias: 'a', + type: 'boolean', + }, + spec: { + alias: 's', + type: 'string', + default: __dirname + '/specs.json', + }, + parallel: { + alias: 'p', + type: 'number', + default: 4, + }, + options: { + alias: 'o', + type: 'array', + default: [], + }, + }).argv; + +function match(entry, request) { + const [reqSpec, reqContract] = request.split(':').reverse(); + return entry.spec == reqSpec && (!reqContract || entry.contract == reqContract); } -if (request) { - const [reqSpec, reqContract] = request.split(':').reverse(); - specs = Object.values(specs).filter(s => reqSpec === s.spec && (!reqContract || reqContract === s.contract)); - if (specs.length === 0) { - console.error(`Error: Requested spec '${request}' not found in specs.json`); - process.exit(1); +const specs = require(argv.spec).filter(s => argv.all || argv._.some(r => match(s, r))); +const limit = require('p-limit')(argv.parallel); + +if (argv._.length == 0 && !argv.all) { + console.error(`Warning: No specs requested. Did you forgot to toggle '--all'?`); +} + +for (const r of argv._) { + if (!specs.some(s => match(s, r))) { + console.error(`Error: Requested spec '${r}' not found in ${argv.spec}`); + process.exitCode = 1; } } -for (const { spec, contract, files, options = [] } of Object.values(specs)) { - limit(runCertora, spec, contract, files, [...options.flatMap(opt => opt.split(' ')), ...extraOptions]); +if (process.exitCode) { + process.exit(process.exitCode); +} + +for (const { spec, contract, files, options = [] } of specs) { + limit(runCertora, spec, contract, files, [...options.flatMap(opt => opt.split(' ')), ...argv.options]); } // Run certora, aggregate the output and print it at the end diff --git a/certora/specs/Ownable.spec b/certora/specs/Ownable.spec index 48bd84d13..4fdfeb09c 100644 --- a/certora/specs/Ownable.spec +++ b/certora/specs/Ownable.spec @@ -62,10 +62,10 @@ rule onlyCurrentOwnerCanCallOnlyOwner(env e) { │ Rule: ownership can only change in specific ways │ └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ */ -rule onlyOwnerOrPendingOwnerCanChangeOwnership(env e, method f) { +rule onlyOwnerOrPendingOwnerCanChangeOwnership(env e) { address oldCurrent = owner(); - calldataarg args; + method f; calldataarg args; f(e, args); address newCurrent = owner(); From d23f818a59b47e496a431e3e7c8b89dffe4f74d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Fri, 28 Apr 2023 15:09:58 +0200 Subject: [PATCH 037/182] Fix AccessControlDefaultAdminRules admin consistency (#4177) Co-authored-by: Francisco Co-authored-by: Hadrien Croubois --- .../access/AccessControlDefaultAdminRules.sol | 2 +- test/access/AccessControl.behavior.js | 30 +++++++++++-------- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/contracts/access/AccessControlDefaultAdminRules.sol b/contracts/access/AccessControlDefaultAdminRules.sol index 43fca9350..0c640fb99 100644 --- a/contracts/access/AccessControlDefaultAdminRules.sol +++ b/contracts/access/AccessControlDefaultAdminRules.sol @@ -136,7 +136,7 @@ abstract contract AccessControlDefaultAdminRules is IAccessControlDefaultAdminRu * @dev See {AccessControl-_revokeRole}. */ function _revokeRole(bytes32 role, address account) internal virtual override { - if (role == DEFAULT_ADMIN_ROLE) { + if (role == DEFAULT_ADMIN_ROLE && account == _currentDefaultAdmin) { delete _currentDefaultAdmin; } super._revokeRole(role, account); diff --git a/test/access/AccessControl.behavior.js b/test/access/AccessControl.behavior.js index 6c88aa274..49ab44b58 100644 --- a/test/access/AccessControl.behavior.js +++ b/test/access/AccessControl.behavior.js @@ -513,15 +513,12 @@ function shouldBehaveLikeAccessControlDefaultAdminRules(errorPrefix, delay, defa }); describe('when caller is pending default admin and delay has passed', function () { - let from; - beforeEach(async function () { await time.setNextBlockTimestamp(acceptSchedule.addn(1)); - from = newDefaultAdmin; }); it('accepts a transfer and changes default admin', async function () { - const receipt = await this.accessControl.acceptDefaultAdminTransfer({ from }); + const receipt = await this.accessControl.acceptDefaultAdminTransfer({ from: newDefaultAdmin }); // Storage changes expect(await this.accessControl.hasRole(DEFAULT_ADMIN_ROLE, defaultAdmin)).to.be.false; @@ -625,10 +622,9 @@ function shouldBehaveLikeAccessControlDefaultAdminRules(errorPrefix, delay, defa describe('renounces admin', function () { let delayPassed; - let from = defaultAdmin; beforeEach(async function () { - await this.accessControl.beginDefaultAdminTransfer(constants.ZERO_ADDRESS, { from }); + await this.accessControl.beginDefaultAdminTransfer(constants.ZERO_ADDRESS, { from: defaultAdmin }); delayPassed = web3.utils .toBN(await time.latest()) .add(delay) @@ -638,27 +634,37 @@ function shouldBehaveLikeAccessControlDefaultAdminRules(errorPrefix, delay, defa it('reverts if caller is not default admin', async function () { await time.setNextBlockTimestamp(delayPassed); await expectRevert( - this.accessControl.renounceRole(DEFAULT_ADMIN_ROLE, other, { from }), + this.accessControl.renounceRole(DEFAULT_ADMIN_ROLE, other, { from: defaultAdmin }), `${errorPrefix}: can only renounce roles for self`, ); }); + it('keeps defaultAdmin consistent with hasRole if another non-defaultAdmin user renounces the DEFAULT_ADMIN_ROLE', async function () { + await time.setNextBlockTimestamp(delayPassed); + + // This passes because it's a noop + await this.accessControl.renounceRole(DEFAULT_ADMIN_ROLE, other, { from: other }); + + expect(await this.accessControl.hasRole(DEFAULT_ADMIN_ROLE, defaultAdmin)).to.be.true; + expect(await this.accessControl.defaultAdmin()).to.be.equal(defaultAdmin); + }); + it('renounces role', async function () { await time.setNextBlockTimestamp(delayPassed); - const receipt = await this.accessControl.renounceRole(DEFAULT_ADMIN_ROLE, from, { from }); + const receipt = await this.accessControl.renounceRole(DEFAULT_ADMIN_ROLE, defaultAdmin, { from: defaultAdmin }); expect(await this.accessControl.hasRole(DEFAULT_ADMIN_ROLE, defaultAdmin)).to.be.false; - expect(await this.accessControl.hasRole(constants.ZERO_ADDRESS, defaultAdmin)).to.be.false; + expect(await this.accessControl.defaultAdmin()).to.be.equal(constants.ZERO_ADDRESS); expectEvent(receipt, 'RoleRevoked', { role: DEFAULT_ADMIN_ROLE, - account: from, + account: defaultAdmin, }); expect(await this.accessControl.owner()).to.equal(constants.ZERO_ADDRESS); }); it('allows to recover access using the internal _grantRole', async function () { await time.setNextBlockTimestamp(delayPassed); - await this.accessControl.renounceRole(DEFAULT_ADMIN_ROLE, from, { from }); + await this.accessControl.renounceRole(DEFAULT_ADMIN_ROLE, defaultAdmin, { from: defaultAdmin }); const grantRoleReceipt = await this.accessControl.$_grantRole(DEFAULT_ADMIN_ROLE, other); expectEvent(grantRoleReceipt, 'RoleGranted', { @@ -681,7 +687,7 @@ function shouldBehaveLikeAccessControlDefaultAdminRules(errorPrefix, delay, defa it(`reverts if block.timestamp is ${tag} to schedule`, async function () { await time.setNextBlockTimestamp(delayNotPassed.toNumber() + fromSchedule); await expectRevert( - this.accessControl.renounceRole(DEFAULT_ADMIN_ROLE, defaultAdmin, { from }), + this.accessControl.renounceRole(DEFAULT_ADMIN_ROLE, defaultAdmin, { from: defaultAdmin }), `${errorPrefix}: only can renounce in two delayed steps`, ); }); From 6ff415de6be00f52900c8573cc761b807acfd88b Mon Sep 17 00:00:00 2001 From: Francisco Date: Fri, 28 Apr 2023 21:15:15 +0100 Subject: [PATCH 038/182] Downgrade Node for Slither (#4202) --- .github/workflows/checks.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index c6b42dd82..2d5c15159 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -86,7 +86,7 @@ jobs: - run: rm foundry.toml - uses: crytic/slither-action@v0.3.0 with: - node-version: 18 + node-version: 18.15 codespell: if: github.repository != 'OpenZeppelin/openzeppelin-contracts-upgradeable' From ab2604ac5b791adf3c5e2397e65128cb56954edd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Tue, 2 May 2023 11:36:56 +0200 Subject: [PATCH 039/182] Add reentrancy test cases for TimelockController (#4200) Co-authored-by: Francisco --- contracts/mocks/TimelockReentrant.sol | 26 ++++ test/governance/TimelockController.test.js | 155 +++++++++++++++++++++ 2 files changed, 181 insertions(+) create mode 100644 contracts/mocks/TimelockReentrant.sol diff --git a/contracts/mocks/TimelockReentrant.sol b/contracts/mocks/TimelockReentrant.sol new file mode 100644 index 000000000..a9344f50d --- /dev/null +++ b/contracts/mocks/TimelockReentrant.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "../utils/Address.sol"; + +contract TimelockReentrant { + address private _reenterTarget; + bytes private _reenterData; + bool _reentered; + + function disableReentrancy() external { + _reentered = true; + } + + function enableRentrancy(address target, bytes calldata data) external { + _reenterTarget = target; + _reenterData = data; + } + + function reenter() external { + if (!_reentered) { + _reentered = true; + Address.functionCall(_reenterTarget, _reenterData); + } + } +} diff --git a/test/governance/TimelockController.test.js b/test/governance/TimelockController.test.js index dde923564..82a84746e 100644 --- a/test/governance/TimelockController.test.js +++ b/test/governance/TimelockController.test.js @@ -10,6 +10,7 @@ const CallReceiverMock = artifacts.require('CallReceiverMock'); const Implementation2 = artifacts.require('Implementation2'); const ERC721 = artifacts.require('$ERC721'); const ERC1155 = artifacts.require('$ERC1155'); +const TimelockReentrant = artifacts.require('$TimelockReentrant'); const MINDELAY = time.duration.days(1); @@ -345,6 +346,82 @@ contract('TimelockController', function (accounts) { `AccessControl: account ${other.toLowerCase()} is missing role ${EXECUTOR_ROLE}`, ); }); + + it('prevents reentrancy execution', async function () { + // Create operation + const reentrant = await TimelockReentrant.new(); + const reentrantOperation = genOperation( + reentrant.address, + 0, + reentrant.contract.methods.reenter().encodeABI(), + ZERO_BYTES32, + salt, + ); + + // Schedule so it can be executed + await this.mock.schedule( + reentrantOperation.target, + reentrantOperation.value, + reentrantOperation.data, + reentrantOperation.predecessor, + reentrantOperation.salt, + MINDELAY, + { from: proposer }, + ); + + // Advance on time to make the operation executable + const timestamp = await this.mock.getTimestamp(reentrantOperation.id); + await time.increaseTo(timestamp); + + // Grant executor role to the reentrant contract + await this.mock.grantRole(EXECUTOR_ROLE, reentrant.address, { from: admin }); + + // Prepare reenter + const data = this.mock.contract.methods + .execute( + reentrantOperation.target, + reentrantOperation.value, + reentrantOperation.data, + reentrantOperation.predecessor, + reentrantOperation.salt, + ) + .encodeABI(); + await reentrant.enableRentrancy(this.mock.address, data); + + // Expect to fail + await expectRevert( + this.mock.execute( + reentrantOperation.target, + reentrantOperation.value, + reentrantOperation.data, + reentrantOperation.predecessor, + reentrantOperation.salt, + { from: executor }, + ), + 'TimelockController: operation is not ready', + ); + + // Disable reentrancy + await reentrant.disableReentrancy(); + const nonReentrantOperation = reentrantOperation; // Not anymore + + // Try again successfully + const receipt = await this.mock.execute( + nonReentrantOperation.target, + nonReentrantOperation.value, + nonReentrantOperation.data, + nonReentrantOperation.predecessor, + nonReentrantOperation.salt, + { from: executor }, + ); + expectEvent(receipt, 'CallExecuted', { + id: nonReentrantOperation.id, + index: web3.utils.toBN(0), + target: nonReentrantOperation.target, + value: web3.utils.toBN(nonReentrantOperation.value), + data: nonReentrantOperation.data, + }); + }); }); }); }); @@ -632,6 +709,84 @@ contract('TimelockController', function (accounts) { 'TimelockController: length mismatch', ); }); + + it('prevents reentrancy execution', async function () { + // Create operation + const reentrant = await TimelockReentrant.new(); + const reentrantBatchOperation = genOperationBatch( + [reentrant.address], + [0], + [reentrant.contract.methods.reenter().encodeABI()], + ZERO_BYTES32, + salt, + ); + + // Schedule so it can be executed + await this.mock.scheduleBatch( + reentrantBatchOperation.targets, + reentrantBatchOperation.values, + reentrantBatchOperation.payloads, + reentrantBatchOperation.predecessor, + reentrantBatchOperation.salt, + MINDELAY, + { from: proposer }, + ); + + // Advance on time to make the operation executable + const timestamp = await this.mock.getTimestamp(reentrantBatchOperation.id); + await time.increaseTo(timestamp); + + // Grant executor role to the reentrant contract + await this.mock.grantRole(EXECUTOR_ROLE, reentrant.address, { from: admin }); + + // Prepare reenter + const data = this.mock.contract.methods + .executeBatch( + reentrantBatchOperation.targets, + reentrantBatchOperation.values, + reentrantBatchOperation.payloads, + reentrantBatchOperation.predecessor, + reentrantBatchOperation.salt, + ) + .encodeABI(); + await reentrant.enableRentrancy(this.mock.address, data); + + // Expect to fail + await expectRevert( + this.mock.executeBatch( + reentrantBatchOperation.targets, + reentrantBatchOperation.values, + reentrantBatchOperation.payloads, + reentrantBatchOperation.predecessor, + reentrantBatchOperation.salt, + { from: executor }, + ), + 'TimelockController: operation is not ready', + ); + + // Disable reentrancy + await reentrant.disableReentrancy(); + const nonReentrantBatchOperation = reentrantBatchOperation; // Not anymore + + // Try again successfully + const receipt = await this.mock.executeBatch( + nonReentrantBatchOperation.targets, + nonReentrantBatchOperation.values, + nonReentrantBatchOperation.payloads, + nonReentrantBatchOperation.predecessor, + nonReentrantBatchOperation.salt, + { from: executor }, + ); + for (const i in nonReentrantBatchOperation.targets) { + expectEvent(receipt, 'CallExecuted', { + id: nonReentrantBatchOperation.id, + index: web3.utils.toBN(i), + target: nonReentrantBatchOperation.targets[i], + value: web3.utils.toBN(nonReentrantBatchOperation.values[i]), + data: nonReentrantBatchOperation.payloads[i], + }); + } + }); }); }); From 0a2a33be301720ae6a0aabcd739e74606bc35ef6 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Wed, 3 May 2023 04:54:23 +0200 Subject: [PATCH 040/182] Add formal verification specs for EnumerableSet & EnumerableMap (#4167) Co-authored-by: Francisco --- certora/harnesses/EnumerableMapHarness.sol | 55 ++++ certora/harnesses/EnumerableSetHarness.sol | 35 +++ certora/run.js | 0 certora/specs.json | 10 + certora/specs/EnumerableMap.spec | 334 +++++++++++++++++++++ certora/specs/EnumerableSet.spec | 247 +++++++++++++++ 6 files changed, 681 insertions(+) create mode 100644 certora/harnesses/EnumerableMapHarness.sol create mode 100644 certora/harnesses/EnumerableSetHarness.sol mode change 100644 => 100755 certora/run.js create mode 100644 certora/specs/EnumerableMap.spec create mode 100644 certora/specs/EnumerableSet.spec diff --git a/certora/harnesses/EnumerableMapHarness.sol b/certora/harnesses/EnumerableMapHarness.sol new file mode 100644 index 000000000..3bcf1b50a --- /dev/null +++ b/certora/harnesses/EnumerableMapHarness.sol @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import "../patched/utils/structs/EnumerableMap.sol"; + +contract EnumerableMapHarness { + using EnumerableMap for EnumerableMap.Bytes32ToBytes32Map; + + EnumerableMap.Bytes32ToBytes32Map private _map; + + function set(bytes32 key, bytes32 value) public returns (bool) { + return _map.set(key, value); + } + + function remove(bytes32 key) public returns (bool) { + return _map.remove(key); + } + + function contains(bytes32 key) public view returns (bool) { + return _map.contains(key); + } + + function length() public view returns (uint256) { + return _map.length(); + } + + function key_at(uint256 index) public view returns (bytes32) { + (bytes32 key,) = _map.at(index); + return key; + } + + function value_at(uint256 index) public view returns (bytes32) { + (,bytes32 value) = _map.at(index); + return value; + } + + function tryGet_contains(bytes32 key) public view returns (bool) { + (bool contained,) = _map.tryGet(key); + return contained; + } + + function tryGet_value(bytes32 key) public view returns (bytes32) { + (,bytes32 value) = _map.tryGet(key); + return value; + } + + function get(bytes32 key) public view returns (bytes32) { + return _map.get(key); + } + + function _indexOf(bytes32 key) public view returns (uint256) { + return _map._keys._inner._indexes[key]; + } +} diff --git a/certora/harnesses/EnumerableSetHarness.sol b/certora/harnesses/EnumerableSetHarness.sol new file mode 100644 index 000000000..64383e6a4 --- /dev/null +++ b/certora/harnesses/EnumerableSetHarness.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import "../patched/utils/structs/EnumerableSet.sol"; + +contract EnumerableSetHarness { + using EnumerableSet for EnumerableSet.Bytes32Set; + + EnumerableSet.Bytes32Set private _set; + + function add(bytes32 value) public returns (bool) { + return _set.add(value); + } + + function remove(bytes32 value) public returns (bool) { + return _set.remove(value); + } + + function contains(bytes32 value) public view returns (bool) { + return _set.contains(value); + } + + function length() public view returns (uint256) { + return _set.length(); + } + + function at_(uint256 index) public view returns (bytes32) { + return _set.at(index); + } + + function _indexOf(bytes32 value) public view returns (uint256) { + return _set._inner._indexes[value]; + } +} diff --git a/certora/run.js b/certora/run.js old mode 100644 new mode 100755 diff --git a/certora/specs.json b/certora/specs.json index 39ba8c235..6f8f57bdf 100644 --- a/certora/specs.json +++ b/certora/specs.json @@ -62,6 +62,16 @@ "contract": "InitializableHarness", "files": ["certora/harnesses/InitializableHarness.sol"] }, + { + "spec": "EnumerableSet", + "contract": "EnumerableSetHarness", + "files": ["certora/harnesses/EnumerableSetHarness.sol"] + }, + { + "spec": "EnumerableMap", + "contract": "EnumerableMapHarness", + "files": ["certora/harnesses/EnumerableMapHarness.sol"] + }, { "spec": "TimelockController", "contract": "TimelockControllerHarness", diff --git a/certora/specs/EnumerableMap.spec b/certora/specs/EnumerableMap.spec new file mode 100644 index 000000000..56ef854c6 --- /dev/null +++ b/certora/specs/EnumerableMap.spec @@ -0,0 +1,334 @@ +import "helpers.spec" + +methods { + // library + set(bytes32,bytes32) returns (bool) envfree + remove(bytes32) returns (bool) envfree + contains(bytes32) returns (bool) envfree + length() returns (uint256) envfree + key_at(uint256) returns (bytes32) envfree + value_at(uint256) returns (bytes32) envfree + tryGet_contains(bytes32) returns (bool) envfree + tryGet_value(bytes32) returns (bytes32) envfree + get(bytes32) returns (bytes32) envfree + + // FV + _indexOf(bytes32) returns (uint256) envfree +} + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Helpers │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +function sanity() returns bool { + return length() < max_uint256; +} + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Invariant: the value mapping is empty for keys that are not in the EnumerableMap. │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +invariant noValueIfNotContained(bytes32 key) + !contains(key) => tryGet_value(key) == 0 + { + preserved set(bytes32 otherKey, bytes32 someValue) { + require sanity(); + } + } + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Invariant: All indexed keys are contained │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +invariant indexedContained(uint256 index) + index < length() => contains(key_at(index)) + { + preserved { + requireInvariant consistencyIndex(index); + requireInvariant consistencyIndex(to_uint256(length() - 1)); + } + } + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Invariant: A value can only be stored at a single location │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +invariant atUniqueness(uint256 index1, uint256 index2) + index1 == index2 <=> key_at(index1) == key_at(index2) + { + preserved remove(bytes32 key) { + requireInvariant atUniqueness(index1, to_uint256(length() - 1)); + requireInvariant atUniqueness(index2, to_uint256(length() - 1)); + } + } + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Invariant: index <> value relationship is consistent │ +│ │ +│ Note that the two consistencyXxx invariants, put together, prove that at_ and _indexOf are inverse of one another. │ +│ This proves that we have a bijection between indices (the enumerability part) and keys (the entries that are set │ +│ and removed from the EnumerableMap). │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +invariant consistencyIndex(uint256 index) + index < length() => _indexOf(key_at(index)) == index + 1 + { + preserved remove(bytes32 key) { + requireInvariant consistencyIndex(to_uint256(length() - 1)); + } + } + +invariant consistencyKey(bytes32 key) + contains(key) => ( + _indexOf(key) > 0 && + _indexOf(key) <= length() && + key_at(to_uint256(_indexOf(key) - 1)) == key + ) + { + preserved remove(bytes32 otherKey) { + requireInvariant consistencyKey(otherKey); + requireInvariant atUniqueness( + to_uint256(_indexOf(key) - 1), + to_uint256(_indexOf(otherKey) - 1) + ); + } + } + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Rule: state only changes by setting or removing elements │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +rule stateChange(env e, bytes32 key) { + require sanity(); + requireInvariant consistencyKey(key); + + uint256 lengthBefore = length(); + bool containsBefore = contains(key); + bytes32 valueBefore = tryGet_value(key); + + method f; + calldataarg args; + f(e, args); + + uint256 lengthAfter = length(); + bool containsAfter = contains(key); + bytes32 valueAfter = tryGet_value(key); + + assert lengthBefore != lengthAfter => ( + (f.selector == set(bytes32,bytes32).selector && lengthAfter == lengthBefore + 1) || + (f.selector == remove(bytes32).selector && lengthAfter == lengthBefore - 1) + ); + + assert containsBefore != containsAfter => ( + (f.selector == set(bytes32,bytes32).selector && containsAfter) || + (f.selector == remove(bytes32).selector && !containsAfter) + ); + + assert valueBefore != valueAfter => ( + (f.selector == set(bytes32,bytes32).selector && containsAfter) || + (f.selector == remove(bytes32).selector && !containsAfter && valueAfter == 0) + ); +} + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Rule: check liveness of view functions. │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +rule liveness_1(bytes32 key) { + requireInvariant consistencyKey(key); + + // contains never revert + bool contains = contains@withrevert(key); + assert !lastReverted; + + // tryGet never reverts (key) + tryGet_contains@withrevert(key); + assert !lastReverted; + + // tryGet never reverts (value) + tryGet_value@withrevert(key); + assert !lastReverted; + + // get reverts iff the key is not in the map + get@withrevert(key); + assert !lastReverted <=> contains; +} + +rule liveness_2(uint256 index) { + requireInvariant consistencyIndex(index); + + // length never revert + uint256 length = length@withrevert(); + assert !lastReverted; + + // key_at reverts iff the index is out of bound + key_at@withrevert(index); + assert !lastReverted <=> index < length; + + // value_at reverts iff the index is out of bound + value_at@withrevert(index); + assert !lastReverted <=> index < length; +} + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Rule: get and tryGet return the expected values. │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +rule getAndTryGet(bytes32 key) { + requireInvariant noValueIfNotContained(key); + + bool contained = contains(key); + bool tryContained = tryGet_contains(key); + bytes32 tryValue = tryGet_value(key); + bytes32 value = get@withrevert(key); // revert is not contained + + assert contained == tryContained; + assert contained => tryValue == value; + assert !contained => tryValue == 0; +} + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Rule: set key-value in EnumerableMap │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +rule set(bytes32 key, bytes32 value, bytes32 otherKey) { + require sanity(); + + uint256 lengthBefore = length(); + bool containsBefore = contains(key); + bool containsOtherBefore = contains(otherKey); + bytes32 otherValueBefore = tryGet_value(otherKey); + + bool added = set@withrevert(key, value); + bool success = !lastReverted; + + assert success && contains(key) && get(key) == value, + "liveness & immediate effect"; + + assert added <=> !containsBefore, + "return value: added iff not contained"; + + assert length() == lengthBefore + to_mathint(added ? 1 : 0), + "effect: length increases iff added"; + + assert added => (key_at(lengthBefore) == key && value_at(lengthBefore) == value), + "effect: add at the end"; + + assert containsOtherBefore != contains(otherKey) => (added && key == otherKey), + "side effect: other keys are not affected"; + + assert otherValueBefore != tryGet_value(otherKey) => key == otherKey, + "side effect: values attached to other keys are not affected"; +} + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Rule: remove key from EnumerableMap │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +rule remove(bytes32 key, bytes32 otherKey) { + requireInvariant consistencyKey(key); + requireInvariant consistencyKey(otherKey); + + uint256 lengthBefore = length(); + bool containsBefore = contains(key); + bool containsOtherBefore = contains(otherKey); + bytes32 otherValueBefore = tryGet_value(otherKey); + + bool removed = remove@withrevert(key); + bool success = !lastReverted; + + assert success && !contains(key), + "liveness & immediate effect"; + + assert removed <=> containsBefore, + "return value: removed iff contained"; + + assert length() == lengthBefore - to_mathint(removed ? 1 : 0), + "effect: length decreases iff removed"; + + assert containsOtherBefore != contains(otherKey) => (removed && key == otherKey), + "side effect: other keys are not affected"; + + assert otherValueBefore != tryGet_value(otherKey) => key == otherKey, + "side effect: values attached to other keys are not affected"; +} + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Rule: when adding a new key, the other keys remain in set, at the same index. │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +rule setEnumerability(bytes32 key, bytes32 value, uint256 index) { + require sanity(); + + bytes32 atKeyBefore = key_at(index); + bytes32 atValueBefore = value_at(index); + + set(key, value); + + bytes32 atKeyAfter = key_at@withrevert(index); + assert !lastReverted; + + bytes32 atValueAfter = value_at@withrevert(index); + assert !lastReverted; + + assert atKeyAfter == atKeyBefore; + assert atValueAfter != atValueBefore => ( + key == atKeyBefore && + value == atValueAfter + ); +} + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Rule: when removing a existing key, the other keys remain in set, at the same index (except for the last one). │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +rule removeEnumerability(bytes32 key, uint256 index) { + uint256 last = length() - 1; + + requireInvariant consistencyKey(key); + requireInvariant consistencyIndex(index); + requireInvariant consistencyIndex(last); + + bytes32 atKeyBefore = key_at(index); + bytes32 atValueBefore = value_at(index); + bytes32 lastKeyBefore = key_at(last); + bytes32 lastValueBefore = value_at(last); + + remove(key); + + // can't read last value & keys (length decreased) + bytes32 atKeyAfter = key_at@withrevert(index); + assert lastReverted <=> index == last; + + bytes32 atValueAfter = value_at@withrevert(index); + assert lastReverted <=> index == last; + + // One value that is allowed to change is if previous value was removed, + // in that case the last value before took its place. + assert ( + index != last && + atKeyBefore != atKeyAfter + ) => ( + atKeyBefore == key && + atKeyAfter == lastKeyBefore + ); + + assert ( + index != last && + atValueBefore != atValueAfter + ) => ( + atValueAfter == lastValueBefore + ); +} diff --git a/certora/specs/EnumerableSet.spec b/certora/specs/EnumerableSet.spec new file mode 100644 index 000000000..c94ba2437 --- /dev/null +++ b/certora/specs/EnumerableSet.spec @@ -0,0 +1,247 @@ +import "helpers.spec" + +methods { + // library + add(bytes32) returns (bool) envfree + remove(bytes32) returns (bool) envfree + contains(bytes32) returns (bool) envfree + length() returns (uint256) envfree + at_(uint256) returns (bytes32) envfree + + // FV + _indexOf(bytes32) returns (uint256) envfree +} + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Helpers │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +function sanity() returns bool { + return length() < max_uint256; +} + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Invariant: All indexed keys are contained │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +invariant indexedContained(uint256 index) + index < length() => contains(at_(index)) + { + preserved { + requireInvariant consistencyIndex(index); + requireInvariant consistencyIndex(to_uint256(length() - 1)); + } + } + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Invariant: A value can only be stored at a single location │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +invariant atUniqueness(uint256 index1, uint256 index2) + index1 == index2 <=> at_(index1) == at_(index2) + { + preserved remove(bytes32 key) { + requireInvariant atUniqueness(index1, to_uint256(length() - 1)); + requireInvariant atUniqueness(index2, to_uint256(length() - 1)); + } + } + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Invariant: index <> key relationship is consistent │ +│ │ +│ Note that the two consistencyXxx invariants, put together, prove that at_ and _indexOf are inverse of one another. │ +│ This proves that we have a bijection between indices (the enumerability part) and keys (the entries that are added │ +│ and removed from the EnumerableSet). │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +invariant consistencyIndex(uint256 index) + index < length() => _indexOf(at_(index)) == index + 1 + { + preserved remove(bytes32 key) { + requireInvariant consistencyIndex(to_uint256(length() - 1)); + } + } + +invariant consistencyKey(bytes32 key) + contains(key) => ( + _indexOf(key) > 0 && + _indexOf(key) <= length() && + at_(to_uint256(_indexOf(key) - 1)) == key + ) + { + preserved remove(bytes32 otherKey) { + requireInvariant consistencyKey(otherKey); + requireInvariant atUniqueness( + to_uint256(_indexOf(key) - 1), + to_uint256(_indexOf(otherKey) - 1) + ); + } + } + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Rule: state only changes by adding or removing elements │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +rule stateChange(env e, bytes32 key) { + require sanity(); + requireInvariant consistencyKey(key); + + uint256 lengthBefore = length(); + bool containsBefore = contains(key); + + method f; + calldataarg args; + f(e, args); + + uint256 lengthAfter = length(); + bool containsAfter = contains(key); + + assert lengthBefore != lengthAfter => ( + (f.selector == add(bytes32).selector && lengthAfter == lengthBefore + 1) || + (f.selector == remove(bytes32).selector && lengthAfter == lengthBefore - 1) + ); + + assert containsBefore != containsAfter => ( + (f.selector == add(bytes32).selector && containsAfter) || + (f.selector == remove(bytes32).selector && containsBefore) + ); +} + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Rule: check liveness of view functions. │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +rule liveness_1(bytes32 key) { + requireInvariant consistencyKey(key); + + // contains never revert + contains@withrevert(key); + assert !lastReverted; +} + +rule liveness_2(uint256 index) { + requireInvariant consistencyIndex(index); + + // length never revert + uint256 length = length@withrevert(); + assert !lastReverted; + + // at reverts iff the index is out of bound + at_@withrevert(index); + assert !lastReverted <=> index < length; +} + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Rule: add key to EnumerableSet if not already contained │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +rule add(bytes32 key, bytes32 otherKey) { + require sanity(); + + uint256 lengthBefore = length(); + bool containsBefore = contains(key); + bool containsOtherBefore = contains(otherKey); + + bool added = add@withrevert(key); + bool success = !lastReverted; + + assert success && contains(key), + "liveness & immediate effect"; + + assert added <=> !containsBefore, + "return value: added iff not contained"; + + assert length() == lengthBefore + to_mathint(added ? 1 : 0), + "effect: length increases iff added"; + + assert added => at_(lengthBefore) == key, + "effect: add at the end"; + + assert containsOtherBefore != contains(otherKey) => (added && key == otherKey), + "side effect: other keys are not affected"; +} + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Rule: remove key from EnumerableSet if already contained │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +rule remove(bytes32 key, bytes32 otherKey) { + requireInvariant consistencyKey(key); + requireInvariant consistencyKey(otherKey); + + uint256 lengthBefore = length(); + bool containsBefore = contains(key); + bool containsOtherBefore = contains(otherKey); + + bool removed = remove@withrevert(key); + bool success = !lastReverted; + + assert success && !contains(key), + "liveness & immediate effect"; + + assert removed <=> containsBefore, + "return value: removed iff contained"; + + assert length() == lengthBefore - to_mathint(removed ? 1 : 0), + "effect: length decreases iff removed"; + + assert containsOtherBefore != contains(otherKey) => (removed && key == otherKey), + "side effect: other keys are not affected"; +} + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Rule: when adding a new key, the other keys remain in set, at the same index. │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +rule addEnumerability(bytes32 key, uint256 index) { + require sanity(); + + bytes32 atBefore = at_(index); + add(key); + bytes32 atAfter = at_@withrevert(index); + bool atAfterSuccess = !lastReverted; + + assert atAfterSuccess; + assert atBefore == atAfter; +} + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Rule: when removing a existing key, the other keys remain in set, at the same index (except for the last one). │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +rule removeEnumerability(bytes32 key, uint256 index) { + uint256 last = length() - 1; + + requireInvariant consistencyKey(key); + requireInvariant consistencyIndex(index); + requireInvariant consistencyIndex(last); + + bytes32 atBefore = at_(index); + bytes32 lastBefore = at_(last); + + remove(key); + + // can't read last value (length decreased) + bytes32 atAfter = at_@withrevert(index); + assert lastReverted <=> index == last; + + // One value that is allowed to change is if previous value was removed, + // in that case the last value before took its place. + assert ( + index != last && + atBefore != atAfter + ) => ( + atBefore == key && + atAfter == lastBefore + ); +} From 538655c3c06b615143141552b2d6d6f8515e4499 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Wed, 3 May 2023 09:35:48 +0200 Subject: [PATCH 041/182] Add reentrancy test cases for ERC4626 (#4197) Co-authored-by: Hadrien Croubois Co-authored-by: Francisco Giordano --- contracts/mocks/ERC20Reentrant.sol | 43 +++++ test/token/ERC20/extensions/ERC4626.test.js | 175 ++++++++++++++++++++ 2 files changed, 218 insertions(+) create mode 100644 contracts/mocks/ERC20Reentrant.sol diff --git a/contracts/mocks/ERC20Reentrant.sol b/contracts/mocks/ERC20Reentrant.sol new file mode 100644 index 000000000..c0184b77b --- /dev/null +++ b/contracts/mocks/ERC20Reentrant.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "../token/ERC20/ERC20.sol"; +import "../token/ERC20/extensions/ERC4626.sol"; + +contract ERC20Reentrant is ERC20("TEST", "TST") { + enum Type { + No, + Before, + After + } + + Type private _reenterType; + address private _reenterTarget; + bytes private _reenterData; + + function scheduleReenter(Type when, address target, bytes calldata data) external { + _reenterType = when; + _reenterTarget = target; + _reenterData = data; + } + + function functionCall(address target, bytes memory data) public returns (bytes memory) { + return Address.functionCall(target, data); + } + + function _beforeTokenTransfer(address from, address to, uint256 amount) internal override { + if (_reenterType == Type.Before) { + _reenterType = Type.No; + functionCall(_reenterTarget, _reenterData); + } + super._beforeTokenTransfer(from, to, amount); + } + + function _afterTokenTransfer(address from, address to, uint256 amount) internal override { + super._afterTokenTransfer(from, to, amount); + if (_reenterType == Type.After) { + _reenterType = Type.No; + functionCall(_reenterTarget, _reenterData); + } + } +} diff --git a/test/token/ERC20/extensions/ERC4626.test.js b/test/token/ERC20/extensions/ERC4626.test.js index ee0998717..55b3e5d20 100644 --- a/test/token/ERC20/extensions/ERC4626.test.js +++ b/test/token/ERC20/extensions/ERC4626.test.js @@ -1,11 +1,14 @@ const { constants, expectEvent, expectRevert } = require('@openzeppelin/test-helpers'); const { expect } = require('chai'); +const { Enum } = require('../../../helpers/enums'); + const ERC20Decimals = artifacts.require('$ERC20DecimalsMock'); const ERC4626 = artifacts.require('$ERC4626'); const ERC4626OffsetMock = artifacts.require('$ERC4626OffsetMock'); const ERC4626FeesMock = artifacts.require('$ERC4626FeesMock'); const ERC20ExcessDecimalsMock = artifacts.require('ERC20ExcessDecimalsMock'); +const ERC20Reentrant = artifacts.require('$ERC20Reentrant'); contract('ERC4626', function (accounts) { const [holder, recipient, spender, other, user1, user2] = accounts; @@ -44,6 +47,178 @@ contract('ERC4626', function (accounts) { } }); + describe('reentrancy', async function () { + const reenterType = Enum('No', 'Before', 'After'); + + const amount = web3.utils.toBN(1000000000000000000); + const reenterAmount = web3.utils.toBN(1000000000); + let token; + let vault; + + beforeEach(async function () { + token = await ERC20Reentrant.new(); + // Use offset 1 so the rate is not 1:1 and we can't possibly confuse assets and shares + vault = await ERC4626OffsetMock.new('', '', token.address, 1); + // Funds and approval for tests + await token.$_mint(holder, amount); + await token.$_mint(other, amount); + await token.$_approve(holder, vault.address, constants.MAX_UINT256); + await token.$_approve(other, vault.address, constants.MAX_UINT256); + await token.$_approve(token.address, vault.address, constants.MAX_UINT256); + }); + + // During a `_deposit`, the vault does `transferFrom(depositor, vault, assets)` -> `_mint(receiver, shares)` + // such that a reentrancy BEFORE the transfer guarantees the price is kept the same. + // If the order of transfer -> mint is changed to mint -> transfer, the reentrancy could be triggered on an + // intermediate state in which the ratio of assets/shares has been decreased (more shares than assets). + it('correct share price is observed during reentrancy before deposit', async function () { + // mint token for deposit + await token.$_mint(token.address, reenterAmount); + + // Schedules a reentrancy from the token contract + await token.scheduleReenter( + reenterType.Before, + vault.address, + vault.contract.methods.deposit(reenterAmount, holder).encodeABI(), + ); + + // Initial share price + const sharesForDeposit = await vault.previewDeposit(amount, { from: holder }); + const sharesForReenter = await vault.previewDeposit(reenterAmount, { from: holder }); + + // Do deposit normally, triggering the _beforeTokenTransfer hook + const receipt = await vault.deposit(amount, holder, { from: holder }); + + // Main deposit event + await expectEvent(receipt, 'Deposit', { + sender: holder, + owner: holder, + assets: amount, + shares: sharesForDeposit, + }); + // Reentrant deposit event → uses the same price + await expectEvent(receipt, 'Deposit', { + sender: token.address, + owner: holder, + assets: reenterAmount, + shares: sharesForReenter, + }); + + // Assert prices is kept + const sharesAfter = await vault.previewDeposit(amount, { from: holder }); + expect(sharesForDeposit).to.be.bignumber.eq(sharesAfter); + }); + + // During a `_withdraw`, the vault does `_burn(owner, shares)` -> `transfer(receiver, assets)` + // such that a reentrancy AFTER the transfer guarantees the price is kept the same. + // If the order of burn -> transfer is changed to transfer -> burn, the reentrancy could be triggered on an + // intermediate state in which the ratio of shares/assets has been decreased (more assets than shares). + it('correct share price is observed during reentrancy after withdraw', async function () { + // Deposit into the vault: holder gets `amount` share, token.address gets `reenterAmount` shares + await vault.deposit(amount, holder, { from: holder }); + await vault.deposit(reenterAmount, token.address, { from: other }); + + // Schedules a reentrancy from the token contract + await token.scheduleReenter( + reenterType.After, + vault.address, + vault.contract.methods.withdraw(reenterAmount, holder, token.address).encodeABI(), + ); + + // Initial share price + const sharesForWithdraw = await vault.previewWithdraw(amount, { from: holder }); + const sharesForReenter = await vault.previewWithdraw(reenterAmount, { from: holder }); + + // Do withdraw normally, triggering the _afterTokenTransfer hook + const receipt = await vault.withdraw(amount, holder, holder, { from: holder }); + + // Main withdraw event + await expectEvent(receipt, 'Withdraw', { + sender: holder, + receiver: holder, + owner: holder, + assets: amount, + shares: sharesForWithdraw, + }); + // Reentrant withdraw event → uses the same price + await expectEvent(receipt, 'Withdraw', { + sender: token.address, + receiver: holder, + owner: token.address, + assets: reenterAmount, + shares: sharesForReenter, + }); + + // Assert price is kept + const sharesAfter = await vault.previewWithdraw(amount, { from: holder }); + expect(sharesForWithdraw).to.be.bignumber.eq(sharesAfter); + }); + + // Donate newly minted tokens to the vault during the reentracy causes the share price to increase. + // Still, the deposit that trigger the reentracy is not affected and get the previewed price. + // Further deposits will get a different price (getting fewer shares for the same amount of assets) + it('share price change during reentracy does not affect deposit', async function () { + // Schedules a reentrancy from the token contract that mess up the share price + await token.scheduleReenter( + reenterType.Before, + token.address, + token.contract.methods.$_mint(vault.address, reenterAmount).encodeABI(), + ); + + // Price before + const sharesBefore = await vault.previewDeposit(amount); + + // Deposit, triggering the _beforeTokenTransfer hook + const receipt = await vault.deposit(amount, holder, { from: holder }); + + // Price is as previewed + await expectEvent(receipt, 'Deposit', { + sender: holder, + owner: holder, + assets: amount, + shares: sharesBefore, + }); + + // Price was modified during reentrancy + const sharesAfter = await vault.previewDeposit(amount); + expect(sharesAfter).to.be.bignumber.lt(sharesBefore); + }); + + // Burn some tokens from the vault during the reentracy causes the share price to drop. + // Still, the withdraw that trigger the reentracy is not affected and get the previewed price. + // Further withdraw will get a different price (needing more shares for the same amount of assets) + it('share price change during reentracy does not affect withdraw', async function () { + await vault.deposit(amount, other, { from: other }); + await vault.deposit(amount, holder, { from: holder }); + + // Schedules a reentrancy from the token contract that mess up the share price + await token.scheduleReenter( + reenterType.After, + token.address, + token.contract.methods.$_burn(vault.address, reenterAmount).encodeABI(), + ); + + // Price before + const sharesBefore = await vault.previewWithdraw(amount); + + // Withdraw, triggering the _afterTokenTransfer hook + const receipt = await vault.withdraw(amount, holder, holder, { from: holder }); + + // Price is as previewed + await expectEvent(receipt, 'Withdraw', { + sender: holder, + receiver: holder, + owner: holder, + assets: amount, + shares: sharesBefore, + }); + + // Price was modified during reentrancy + const sharesAfter = await vault.previewWithdraw(amount); + expect(sharesAfter).to.be.bignumber.gt(sharesBefore); + }); + }); + for (const offset of [0, 6, 18].map(web3.utils.toBN)) { const parseToken = token => web3.utils.toBN(10).pow(decimals).muln(token); const parseShare = share => web3.utils.toBN(10).pow(decimals.add(offset)).muln(share); From 10022da83d75a5ae0cae5222dced463a6678cf61 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Wed, 3 May 2023 16:13:42 +0200 Subject: [PATCH 042/182] Disable automatic formal verification workflow on push (#4208) --- .github/workflows/formal-verification.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/formal-verification.yml b/.github/workflows/formal-verification.yml index f94152a52..ae5eba006 100644 --- a/.github/workflows/formal-verification.yml +++ b/.github/workflows/formal-verification.yml @@ -1,10 +1,6 @@ name: formal verification on: - push: - branches: - - master - - release-v* pull_request: types: - opened From a7ee03565b4ee14265f4406f9e38a04e0143656f Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Wed, 3 May 2023 16:34:14 +0200 Subject: [PATCH 043/182] Move certora helpers to a dedicated folder (#4211) --- certora/specs/AccessControl.spec | 2 +- certora/specs/DoubleEndedQueue.spec | 36 ++++++++++++------------ certora/specs/ERC20.spec | 2 +- certora/specs/ERC20FlashMint.spec | 2 +- certora/specs/ERC20Wrapper.spec | 2 +- certora/specs/ERC721.spec | 2 +- certora/specs/EnumerableMap.spec | 2 +- certora/specs/EnumerableSet.spec | 2 +- certora/specs/Initializable.spec | 2 +- certora/specs/Ownable.spec | 2 +- certora/specs/Ownable2Step.spec | 2 +- certora/specs/Pausable.spec | 8 +++--- certora/specs/TimelockController.spec | 2 +- certora/specs/{ => helpers}/helpers.spec | 0 14 files changed, 33 insertions(+), 33 deletions(-) rename certora/specs/{ => helpers}/helpers.spec (100%) diff --git a/certora/specs/AccessControl.spec b/certora/specs/AccessControl.spec index 35927c5d3..81b6d3604 100644 --- a/certora/specs/AccessControl.spec +++ b/certora/specs/AccessControl.spec @@ -1,4 +1,4 @@ -import "helpers.spec" +import "helpers/helpers.spec" import "methods/IAccessControl.spec" /* diff --git a/certora/specs/DoubleEndedQueue.spec b/certora/specs/DoubleEndedQueue.spec index 2412b4cc8..2a196772d 100644 --- a/certora/specs/DoubleEndedQueue.spec +++ b/certora/specs/DoubleEndedQueue.spec @@ -1,4 +1,4 @@ -import "helpers.spec" +import "helpers/helpers.spec" methods { pushFront(bytes32) envfree @@ -10,7 +10,7 @@ methods { // exposed for FV begin() returns (int128) envfree end() returns (int128) envfree - + // view length() returns (uint256) envfree empty() returns (bool) envfree @@ -35,7 +35,7 @@ function max_int128() returns mathint { // Could be broken in theory, but not in practice function boundedQueue() returns bool { - return + return max_int128() > to_mathint(end()) && min_int128() < to_mathint(begin()); } @@ -78,11 +78,11 @@ invariant emptiness() invariant queueEndings() at_(length() - 1) == back() && at_(0) == front() filtered { f -> !f.isView } - { - preserved { - requireInvariant boundariesConsistency(); - require boundedQueue(); - } + { + preserved { + requireInvariant boundariesConsistency(); + require boundedQueue(); + } } /* @@ -94,12 +94,12 @@ rule pushFront(bytes32 value) { require boundedQueue(); uint256 lengthBefore = length(); - + pushFront@withrevert(value); - + // liveness assert !lastReverted, "never reverts"; - + // effect assert front() == value, "front set to value"; assert length() == lengthBefore + 1, "queue extended"; @@ -134,12 +134,12 @@ rule pushBack(bytes32 value) { require boundedQueue(); uint256 lengthBefore = length(); - + pushBack@withrevert(value); - + // liveness assert !lastReverted, "never reverts"; - + // effect assert back() == value, "back set to value"; assert length() == lengthBefore + 1, "queue increased"; @@ -205,7 +205,7 @@ rule popFrontConsistency(uint256 index) { // try to read value bytes32 after = at_@withrevert(index - 1); - + assert !lastReverted, "value still exists in the queue"; assert before == after, "values are offset and not modified"; } @@ -250,7 +250,7 @@ rule popBackConsistency(uint256 index) { // try to read value bytes32 after = at_@withrevert(index); - + assert !lastReverted, "value still exists in the queue"; assert before == after, "values are offset and not modified"; } @@ -262,10 +262,10 @@ rule popBackConsistency(uint256 index) { */ rule clear { clear@withrevert(); - + // liveness assert !lastReverted, "never reverts"; - + // effect assert length() == 0, "sets length to 0"; } diff --git a/certora/specs/ERC20.spec b/certora/specs/ERC20.spec index 85f95e706..3bd2b38ba 100644 --- a/certora/specs/ERC20.spec +++ b/certora/specs/ERC20.spec @@ -1,4 +1,4 @@ -import "helpers.spec" +import "helpers/helpers.spec" import "methods/IERC20.spec" import "methods/IERC2612.spec" diff --git a/certora/specs/ERC20FlashMint.spec b/certora/specs/ERC20FlashMint.spec index 64d97342a..70a7c0795 100644 --- a/certora/specs/ERC20FlashMint.spec +++ b/certora/specs/ERC20FlashMint.spec @@ -1,4 +1,4 @@ -import "helpers.spec" +import "helpers/helpers.spec" import "methods/IERC20.spec" import "methods/IERC3156.spec" diff --git a/certora/specs/ERC20Wrapper.spec b/certora/specs/ERC20Wrapper.spec index c10173766..badfa7a28 100644 --- a/certora/specs/ERC20Wrapper.spec +++ b/certora/specs/ERC20Wrapper.spec @@ -1,4 +1,4 @@ -import "helpers.spec" +import "helpers/helpers.spec" import "ERC20.spec" methods { diff --git a/certora/specs/ERC721.spec b/certora/specs/ERC721.spec index 48503469b..9db13f45c 100644 --- a/certora/specs/ERC721.spec +++ b/certora/specs/ERC721.spec @@ -1,4 +1,4 @@ -import "helpers.spec" +import "helpers/helpers.spec" import "methods/IERC721.spec" methods { diff --git a/certora/specs/EnumerableMap.spec b/certora/specs/EnumerableMap.spec index 56ef854c6..dea5d85ec 100644 --- a/certora/specs/EnumerableMap.spec +++ b/certora/specs/EnumerableMap.spec @@ -1,4 +1,4 @@ -import "helpers.spec" +import "helpers/helpers.spec" methods { // library diff --git a/certora/specs/EnumerableSet.spec b/certora/specs/EnumerableSet.spec index c94ba2437..d63c556aa 100644 --- a/certora/specs/EnumerableSet.spec +++ b/certora/specs/EnumerableSet.spec @@ -1,4 +1,4 @@ -import "helpers.spec" +import "helpers/helpers.spec" methods { // library diff --git a/certora/specs/Initializable.spec b/certora/specs/Initializable.spec index 1ba8d54e8..0e0b1b714 100644 --- a/certora/specs/Initializable.spec +++ b/certora/specs/Initializable.spec @@ -1,4 +1,4 @@ -import "helpers.spec" +import "helpers/helpers.spec" methods { // initialize, reinitialize, disable diff --git a/certora/specs/Ownable.spec b/certora/specs/Ownable.spec index 4fdfeb09c..4bf9e3005 100644 --- a/certora/specs/Ownable.spec +++ b/certora/specs/Ownable.spec @@ -1,4 +1,4 @@ -import "helpers.spec" +import "helpers/helpers.spec" import "methods/IOwnable.spec" methods { diff --git a/certora/specs/Ownable2Step.spec b/certora/specs/Ownable2Step.spec index 70c520a03..47b1b8d75 100644 --- a/certora/specs/Ownable2Step.spec +++ b/certora/specs/Ownable2Step.spec @@ -1,4 +1,4 @@ -import "helpers.spec" +import "helpers/helpers.spec" import "methods/IOwnable2Step.spec" methods { diff --git a/certora/specs/Pausable.spec b/certora/specs/Pausable.spec index e49293ffc..aea38003f 100644 --- a/certora/specs/Pausable.spec +++ b/certora/specs/Pausable.spec @@ -1,4 +1,4 @@ -import "helpers.spec" +import "helpers/helpers.spec" methods { paused() returns (bool) envfree @@ -22,7 +22,7 @@ rule pause(env e) { bool success = !lastReverted; bool pausedAfter = paused(); - + // liveness assert success <=> !pausedBefore, "works if and only if the contract was not paused before"; @@ -44,7 +44,7 @@ rule unpause(env e) { bool success = !lastReverted; bool pausedAfter = paused(); - + // liveness assert success <=> pausedBefore, "works if and only if the contract was paused before"; @@ -71,7 +71,7 @@ rule whenPaused(env e) { */ rule whenNotPaused(env e) { require nonpayable(e); - + onlyWhenNotPaused@withrevert(e); assert !lastReverted <=> !paused(), "works if and only if the contract is not paused"; } diff --git a/certora/specs/TimelockController.spec b/certora/specs/TimelockController.spec index e140c11de..05ecb1340 100644 --- a/certora/specs/TimelockController.spec +++ b/certora/specs/TimelockController.spec @@ -1,4 +1,4 @@ -import "helpers.spec" +import "helpers/helpers.spec" import "methods/IAccessControl.spec" methods { diff --git a/certora/specs/helpers.spec b/certora/specs/helpers/helpers.spec similarity index 100% rename from certora/specs/helpers.spec rename to certora/specs/helpers/helpers.spec From 9e8b74a0e2c8a6e87f7fa88318f15017d4b14524 Mon Sep 17 00:00:00 2001 From: Francisco Date: Thu, 4 May 2023 14:33:57 +0100 Subject: [PATCH 044/182] Add more test cases for EIP712 (#4212) --- contracts/utils/cryptography/EIP712.sol | 2 + test/utils/cryptography/EIP712.test.js | 135 +++++++++++++++--------- 2 files changed, 86 insertions(+), 51 deletions(-) diff --git a/contracts/utils/cryptography/EIP712.sol b/contracts/utils/cryptography/EIP712.sol index e06d0066b..56dfceaf4 100644 --- a/contracts/utils/cryptography/EIP712.sol +++ b/contracts/utils/cryptography/EIP712.sol @@ -111,6 +111,8 @@ abstract contract EIP712 is IERC5267 { /** * @dev See {EIP-5267}. + * + * _Available since v4.9._ */ function eip712Domain() public diff --git a/test/utils/cryptography/EIP712.test.js b/test/utils/cryptography/EIP712.test.js index f12f22673..52e322d3d 100644 --- a/test/utils/cryptography/EIP712.test.js +++ b/test/utils/cryptography/EIP712.test.js @@ -6,65 +6,98 @@ const { getChainId } = require('../../helpers/chainid'); const { mapValues } = require('../../helpers/map-values'); const EIP712Verifier = artifacts.require('$EIP712Verifier'); +const Clones = artifacts.require('$Clones'); contract('EIP712', function (accounts) { const [mailTo] = accounts; - const name = 'A Name'; - const version = '1'; + const shortName = 'A Name'; + const shortVersion = '1'; - beforeEach('deploying', async function () { - this.eip712 = await EIP712Verifier.new(name, version); + const longName = 'A'.repeat(40); + const longVersion = 'B'.repeat(40); - this.domain = { - name, - version, - chainId: await getChainId(), - verifyingContract: this.eip712.address, - }; - this.domainType = domainType(this.domain); - }); + const cases = [ + ['short', shortName, shortVersion], + ['long', longName, longVersion], + ]; - describe('domain separator', function () { - it('is internally available', async function () { - const expected = await domainSeparator(this.domain); + for (const [shortOrLong, name, version] of cases) { + describe(`with ${shortOrLong} name and version`, function () { + beforeEach('deploying', async function () { + this.eip712 = await EIP712Verifier.new(name, version); - expect(await this.eip712.$_domainSeparatorV4()).to.equal(expected); - }); + this.domain = { + name, + version, + chainId: await getChainId(), + verifyingContract: this.eip712.address, + }; + this.domainType = domainType(this.domain); + }); + + describe('domain separator', function () { + it('is internally available', async function () { + const expected = await domainSeparator(this.domain); + + expect(await this.eip712.$_domainSeparatorV4()).to.equal(expected); + }); + + it("can be rebuilt using EIP-5267's eip712Domain", async function () { + const rebuildDomain = await getDomain(this.eip712); + expect(mapValues(rebuildDomain, String)).to.be.deep.equal(mapValues(this.domain, String)); + }); + + if (shortOrLong === 'short') { + // Long strings are in storage, and the proxy will not be properly initialized unless + // the upgradeable contract variant is used and the initializer is invoked. + + it('adjusts when behind proxy', async function () { + const factory = await Clones.new(); + const cloneReceipt = await factory.$clone(this.eip712.address); + const cloneAddress = cloneReceipt.logs.find(({ event }) => event === 'return$clone').args.instance; + const clone = new EIP712Verifier(cloneAddress); + + const cloneDomain = { ...this.domain, verifyingContract: clone.address }; + + const expectedSeparator = await domainSeparator(cloneDomain); + expect(await clone.$_domainSeparatorV4()).to.equal(expectedSeparator); + + const reportedDomain = await getDomain(clone); + expect(mapValues(reportedDomain, String)).to.be.deep.equal(mapValues(cloneDomain, String)); + }); + } + }); + + it('hash digest', async function () { + const structhash = web3.utils.randomHex(32); + expect(await this.eip712.$_hashTypedDataV4(structhash)).to.be.equal(hashTypedData(this.domain, structhash)); + }); + + it('digest', async function () { + const message = { + to: mailTo, + contents: 'very interesting', + }; + + const data = { + types: { + EIP712Domain: this.domainType, + Mail: [ + { name: 'to', type: 'address' }, + { name: 'contents', type: 'string' }, + ], + }, + domain: this.domain, + primaryType: 'Mail', + message, + }; + + const wallet = Wallet.generate(); + const signature = ethSigUtil.signTypedMessage(wallet.getPrivateKey(), { data }); - it("can be rebuilt using EIP-5267's eip712Domain", async function () { - const rebuildDomain = await getDomain(this.eip712); - expect(mapValues(rebuildDomain, String)).to.be.deep.equal(mapValues(this.domain, String)); + await this.eip712.verify(signature, wallet.getAddressString(), message.to, message.contents); + }); }); - }); - - it('hash digest', async function () { - const structhash = web3.utils.randomHex(32); - expect(await this.eip712.$_hashTypedDataV4(structhash)).to.be.equal(hashTypedData(this.domain, structhash)); - }); - - it('digest', async function () { - const message = { - to: mailTo, - contents: 'very interesting', - }; - - const data = { - types: { - EIP712Domain: this.domainType, - Mail: [ - { name: 'to', type: 'address' }, - { name: 'contents', type: 'string' }, - ], - }, - domain: this.domain, - primaryType: 'Mail', - message, - }; - - const wallet = Wallet.generate(); - const signature = ethSigUtil.signTypedMessage(wallet.getPrivateKey(), { data }); - - await this.eip712.verify(signature, wallet.getAddressString(), message.to, message.contents); - }); + } }); From 8b2ed0f5706fe1a7ab6ade84b27bf875dc611fda Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Thu, 4 May 2023 18:54:22 +0200 Subject: [PATCH 045/182] Fix early reporting of FV prover's output (#4213) Co-authored-by: Francisco --- certora/run.js | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/certora/run.js b/certora/run.js index 2dc7d0181..fdee42d2e 100755 --- a/certora/run.js +++ b/certora/run.js @@ -73,15 +73,18 @@ async function runCertora(spec, contract, files, options = []) { child.stdout.pipe(stream, { end: false }); child.stderr.pipe(stream, { end: false }); - // as soon as we have a jobStatus link, print it + // as soon as we have a job id, print the output link stream.on('data', function logStatusUrl(data) { - const urls = data.toString('utf8').match(/https?:\S*/g); - for (const url of urls ?? []) { - if (url.includes('/jobStatus/')) { - console.error(`[${spec}] ${url.replace('/jobStatus/', '/output/')}`); - stream.off('data', logStatusUrl); - break; - } + const { '-DjobId': jobId, '-DuserId': userId } = Object.fromEntries( + data + .toString('utf8') + .match(/-D\S+=\S+/g) + ?.map(s => s.split('=')) || [], + ); + + if (jobId && userId) { + console.error(`[${spec}] https://prover.certora.com/output/${userId}/${jobId}/`); + stream.off('data', logStatusUrl); } }); @@ -98,7 +101,7 @@ async function runCertora(spec, contract, files, options = []) { stream.end(); // write results in markdown format - writeEntry(spec, contract, code || signal, (await output).match(/https:\S*/)?.[0]); + writeEntry(spec, contract, code || signal, (await output).match(/https:\/\/prover.certora.com\/output\/\S*/)?.[0]); // write all details console.error(`+ certoraRun ${args.join(' ')}\n` + (await output)); @@ -136,8 +139,8 @@ function writeEntry(spec, contract, success, url) { spec, contract, success ? ':x:' : ':heavy_check_mark:', + url ? `[link](${url?.replace('/output/', '/jobStatus/')})` : 'error', url ? `[link](${url})` : 'error', - url ? `[link](${url?.replace('/jobStatus/', '/output/')})` : 'error', ), ); } From dcba9f995ffa83433cc523a8e3d1fcfeac426377 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Fri, 5 May 2023 21:27:43 +0200 Subject: [PATCH 046/182] Add AccessControlDefaultAdminRules FV (#4180) Co-authored-by: Hadrien Croubois Co-authored-by: Francisco --- .../AccessControlDefaultAdminRulesHarness.sol | 47 ++ certora/specs.json | 5 + certora/specs/AccessControl.spec | 20 +- .../specs/AccessControlDefaultAdminRules.spec | 500 ++++++++++++++++++ .../IAccessControlDefaultAdminRules.spec | 36 ++ certora/specs/methods/IERC5313.spec | 3 + 6 files changed, 603 insertions(+), 8 deletions(-) create mode 100644 certora/harnesses/AccessControlDefaultAdminRulesHarness.sol create mode 100644 certora/specs/AccessControlDefaultAdminRules.spec create mode 100644 certora/specs/methods/IAccessControlDefaultAdminRules.spec create mode 100644 certora/specs/methods/IERC5313.spec diff --git a/certora/harnesses/AccessControlDefaultAdminRulesHarness.sol b/certora/harnesses/AccessControlDefaultAdminRulesHarness.sol new file mode 100644 index 000000000..29fd3a709 --- /dev/null +++ b/certora/harnesses/AccessControlDefaultAdminRulesHarness.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import "../patched/access/AccessControlDefaultAdminRules.sol"; + +contract AccessControlDefaultAdminRulesHarness is AccessControlDefaultAdminRules { + uint48 private _delayIncreaseWait; + + constructor( + uint48 initialDelay, + address initialDefaultAdmin, + uint48 delayIncreaseWait + ) AccessControlDefaultAdminRules(initialDelay, initialDefaultAdmin) { + _delayIncreaseWait = delayIncreaseWait; + } + + // FV + function pendingDefaultAdmin_() external view returns (address) { + (address newAdmin, ) = pendingDefaultAdmin(); + return newAdmin; + } + + function pendingDefaultAdminSchedule_() external view returns (uint48) { + (, uint48 schedule) = pendingDefaultAdmin(); + return schedule; + } + + function pendingDelay_() external view returns (uint48) { + (uint48 newDelay, ) = pendingDefaultAdminDelay(); + return newDelay; + } + + function pendingDelaySchedule_() external view returns (uint48) { + (, uint48 schedule) = pendingDefaultAdminDelay(); + return schedule; + } + + function delayChangeWait_(uint48 newDelay) external view returns (uint48) { + return _delayChangeWait(newDelay); + } + + // Overrides + function defaultAdminDelayIncreaseWait() public view override returns (uint48) { + return _delayIncreaseWait; + } +} diff --git a/certora/specs.json b/certora/specs.json index 6f8f57bdf..3e5acb568 100644 --- a/certora/specs.json +++ b/certora/specs.json @@ -9,6 +9,11 @@ "contract": "AccessControlHarness", "files": ["certora/harnesses/AccessControlHarness.sol"] }, + { + "spec": "AccessControlDefaultAdminRules", + "contract": "AccessControlDefaultAdminRulesHarness", + "files": ["certora/harnesses/AccessControlDefaultAdminRulesHarness.sol"] + }, { "spec": "DoubleEndedQueue", "contract": "DoubleEndedQueueHarness", diff --git a/certora/specs/AccessControl.spec b/certora/specs/AccessControl.spec index 81b6d3604..cd5af2a99 100644 --- a/certora/specs/AccessControl.spec +++ b/certora/specs/AccessControl.spec @@ -1,13 +1,20 @@ import "helpers/helpers.spec" import "methods/IAccessControl.spec" +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Definitions │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +definition DEFAULT_ADMIN_ROLE() returns bytes32 = 0x0000000000000000000000000000000000000000000000000000000000000000; + /* ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ │ Identify entrypoints: only grantRole, revokeRole and renounceRole can alter permissions │ └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ */ -rule onlyGrantCanGrant(env e, bytes32 role, address account) { - method f; calldataarg args; +rule onlyGrantCanGrant(env e, method f, bytes32 role, address account) { + calldataarg args; bool hasRoleBefore = hasRole(role, account); f(e, args); @@ -34,10 +41,9 @@ rule onlyGrantCanGrant(env e, bytes32 role, address account) { │ Function correctness: grantRole only affects the specified user/role combo │ └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ */ -rule grantRoleEffect(env e) { +rule grantRoleEffect(env e, bytes32 role) { require nonpayable(e); - bytes32 role; bytes32 otherRole; address account; address otherAccount; @@ -65,10 +71,9 @@ rule grantRoleEffect(env e) { │ Function correctness: revokeRole only affects the specified user/role combo │ └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ */ -rule revokeRoleEffect(env e) { +rule revokeRoleEffect(env e, bytes32 role) { require nonpayable(e); - bytes32 role; bytes32 otherRole; address account; address otherAccount; @@ -96,10 +101,9 @@ rule revokeRoleEffect(env e) { │ Function correctness: renounceRole only affects the specified user/role combo │ └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ */ -rule renounceRoleEffect(env e) { +rule renounceRoleEffect(env e, bytes32 role) { require nonpayable(e); - bytes32 role; bytes32 otherRole; address account; address otherAccount; diff --git a/certora/specs/AccessControlDefaultAdminRules.spec b/certora/specs/AccessControlDefaultAdminRules.spec new file mode 100644 index 000000000..a4baa1871 --- /dev/null +++ b/certora/specs/AccessControlDefaultAdminRules.spec @@ -0,0 +1,500 @@ +import "helpers/helpers.spec" +import "methods/IAccessControlDefaultAdminRules.spec" +import "methods/IAccessControl.spec" +import "AccessControl.spec" + +use rule onlyGrantCanGrant filtered { + f -> f.selector != acceptDefaultAdminTransfer().selector +} + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Helpers │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ + +function max_uint48() returns mathint { + return (1 << 48) - 1; +} + +function nonZeroAccount(address account) returns bool { + return account != 0; +} + +function timeSanity(env e) returns bool { + return + e.block.timestamp > 0 && // Avoids 0 schedules + e.block.timestamp + defaultAdminDelay(e) < max_uint48(); +} + +function delayChangeWaitSanity(env e, uint48 newDelay) returns bool { + return e.block.timestamp + delayChangeWait_(e, newDelay) < max_uint48(); +} + +function isSet(uint48 schedule) returns bool { + return schedule != 0; +} + +function hasPassed(env e, uint48 schedule) returns bool { + return schedule < e.block.timestamp; +} + +function min(uint48 a, uint48 b) returns mathint { + return a < b ? a : b; +} + +function increasingDelaySchedule(env e, uint48 newDelay) returns mathint { + return e.block.timestamp + min(newDelay, defaultAdminDelayIncreaseWait()); +} + +function decreasingDelaySchedule(env e, uint48 newDelay) returns mathint { + return e.block.timestamp + defaultAdminDelay(e) - newDelay; +} + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Invariant: defaultAdmin holds the DEFAULT_ADMIN_ROLE │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +invariant defaultAdminConsistency(address account) + defaultAdmin() == account <=> hasRole(DEFAULT_ADMIN_ROLE(), account) + { + preserved { + // defaultAdmin() returns the zero address when there's no default admin + require nonZeroAccount(account); + } + } + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Invariant: Only one account holds the DEFAULT_ADMIN_ROLE │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +invariant singleDefaultAdmin(address account, address another) + hasRole(DEFAULT_ADMIN_ROLE(), account) && hasRole(DEFAULT_ADMIN_ROLE(), another) => another == account + // We filter here because we couldn't find a way to force Certora to have an initial state with + // only one DEFAULT_ADMIN_ROLE enforced, so a counter example is a different default admin since inception + // triggering the transfer, which is known to be impossible by definition. + filtered { f -> f.selector != acceptDefaultAdminTransfer().selector } + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Invariant: DEFAULT_ADMIN_ROLE's admin is always DEFAULT_ADMIN_ROLE │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +invariant defaultAdminRoleAdminConsistency() + getRoleAdmin(DEFAULT_ADMIN_ROLE()) == DEFAULT_ADMIN_ROLE() + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Invariant: owner is the defaultAdmin │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +invariant ownerConsistency() + defaultAdmin() == owner() + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Function correctness: revokeRole only affects the specified user/role combo │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +rule revokeRoleEffect(env e, bytes32 role) { + require nonpayable(e); + + bytes32 otherRole; + address account; + address otherAccount; + + bool isCallerAdmin = hasRole(getRoleAdmin(role), e.msg.sender); + bool hasOtherRoleBefore = hasRole(otherRole, otherAccount); + + revokeRole@withrevert(e, role, account); + bool success = !lastReverted; + + bool hasOtherRoleAfter = hasRole(otherRole, otherAccount); + + // liveness + assert success <=> isCallerAdmin && role != DEFAULT_ADMIN_ROLE(), + "roles can only be revoked by their owner except for the default admin role"; + + // effect + assert success => !hasRole(role, account), "role is revoked"; + + // no side effect + assert hasOtherRoleBefore != hasOtherRoleAfter => (role == otherRole && account == otherAccount), + "no other role is affected"; +} + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Function correctness: renounceRole only affects the specified user/role combo │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +rule renounceRoleEffect(env e, bytes32 role) { + require nonpayable(e); + + bytes32 otherRole; + address account; + address otherAccount; + + bool hasOtherRoleBefore = hasRole(otherRole, otherAccount); + uint48 scheduleBefore = pendingDefaultAdminSchedule_(); + address pendingAdminBefore = pendingDefaultAdmin_(); + + renounceRole@withrevert(e, role, account); + bool success = !lastReverted; + + bool hasOtherRoleAfter = hasRole(otherRole, otherAccount); + + // liveness + assert success <=> ( + account == e.msg.sender && + ( + ( + role != DEFAULT_ADMIN_ROLE() + ) || ( + role == DEFAULT_ADMIN_ROLE() && + pendingAdminBefore == 0 && + isSet(scheduleBefore) && + hasPassed(e, scheduleBefore) + ) + ) + ), "an account only can renounce by itself with a delay for the default admin role"; + + // effect + assert success => !hasRole(role, account), "role is renounced"; + + // no side effect + assert hasOtherRoleBefore != hasOtherRoleAfter => (role == otherRole && account == otherAccount), + "no other role is affected"; +} + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Rule: defaultAdmin is only affected by accepting an admin transfer or renoucing │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +rule noDefaultAdminChange(env e, method f, calldataarg args) { + require nonZeroAccount(e.msg.sender); + requireInvariant defaultAdminConsistency(defaultAdmin()); + requireInvariant singleDefaultAdmin(e.msg.sender, defaultAdmin()); + + address adminBefore = defaultAdmin(); + f(e, args); + address adminAfter = defaultAdmin(); + + assert adminBefore != adminAfter => ( + f.selector == acceptDefaultAdminTransfer().selector || + f.selector == renounceRole(bytes32,address).selector + ), "default admin is only affected by accepting an admin transfer or renoucing"; +} + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Rule: pendingDefaultAdmin is only affected by beginning, accepting or canceling an admin transfer │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +rule noPendingDefaultAdminChange(env e, method f, calldataarg args) { + requireInvariant defaultAdminConsistency(defaultAdmin()); + requireInvariant singleDefaultAdmin(e.msg.sender, defaultAdmin()); + + address pendingAdminBefore = pendingDefaultAdmin_(); + address scheduleBefore = pendingDefaultAdminSchedule_(); + f(e, args); + address pendingAdminAfter = pendingDefaultAdmin_(); + address scheduleAfter = pendingDefaultAdminSchedule_(); + + assert ( + pendingAdminBefore != pendingAdminAfter || + scheduleBefore != scheduleAfter + ) => ( + f.selector == beginDefaultAdminTransfer(address).selector || + f.selector == acceptDefaultAdminTransfer().selector || + f.selector == cancelDefaultAdminTransfer().selector + ), "pending admin and its schedule is only affected by beginning, accepting or cancelling an admin transfer"; +} + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Rule: defaultAdminDelay can't be changed atomically by any function │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +rule noDefaultAdminDelayChange(env e, method f, calldataarg args) { + uint48 delayBefore = defaultAdminDelay(e); + f(e, args); + uint48 delayAfter = defaultAdminDelay(e); + + assert delayBefore == delayAfter, "delay can't be changed atomically by any function"; +} + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Rule: pendingDefaultAdminDelay is only affected by changeDefaultAdminDelay or rollbackDefaultAdminDelay │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +rule noPendingDefaultAdminDelayChange(env e, method f, calldataarg args) { + uint48 pendingDelayBefore = pendingDelay_(e); + f(e, args); + uint48 pendingDelayAfter = pendingDelay_(e); + + assert pendingDelayBefore != pendingDelayAfter => ( + f.selector == changeDefaultAdminDelay(uint48).selector || + f.selector == rollbackDefaultAdminDelay().selector + ), "pending delay is only affected by changeDefaultAdminDelay or rollbackDefaultAdminDelay"; +} + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Rule: defaultAdminDelayIncreaseWait can't be changed atomically by any function │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +rule noDefaultAdminDelayIncreaseWaitChange(env e, method f, calldataarg args) { + uint48 delayIncreaseWaitBefore = defaultAdminDelayIncreaseWait(); + f(e, args); + uint48 delayIncreaseWaitAfter = defaultAdminDelayIncreaseWait(); + + assert delayIncreaseWaitBefore == delayIncreaseWaitAfter, + "delay increase wait can't be changed atomically by any function"; +} + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Function correctness: beginDefaultAdminTransfer sets a pending default admin and its schedule │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +rule beginDefaultAdminTransfer(env e, address newAdmin) { + require nonpayable(e); + require timeSanity(e); + requireInvariant defaultAdminConsistency(defaultAdmin()); + requireInvariant singleDefaultAdmin(e.msg.sender, defaultAdmin()); + + beginDefaultAdminTransfer@withrevert(e, newAdmin); + bool success = !lastReverted; + + // liveness + assert success <=> e.msg.sender == defaultAdmin(), + "only the current default admin can begin a transfer"; + + // effect + assert success => pendingDefaultAdmin_() == newAdmin, + "pending default admin is set"; + assert success => pendingDefaultAdminSchedule_() == e.block.timestamp + defaultAdminDelay(e), + "pending default admin delay is set"; +} + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Rule: A default admin can't change in less than the applied schedule │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +rule pendingDefaultAdminDelayEnforced(env e1, env e2, method f, calldataarg args, address newAdmin) { + require e1.block.timestamp < e2.block.timestamp; + + uint48 delayBefore = defaultAdminDelay(e1); + address adminBefore = defaultAdmin(); + // There might be a better way to generalize this without requiring `beginDefaultAdminTransfer`, but currently + // it's the only way in which we can attest that only `delayBefore` has passed before a change. + beginDefaultAdminTransfer(e1, newAdmin); + f(e2, args); + address adminAfter = defaultAdmin(); + + assert adminAfter == newAdmin => ((e2.block.timestamp >= e1.block.timestamp + delayBefore) || adminBefore == newAdmin), + "A delay can't change in less than applied schedule"; +} + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Function correctness: acceptDefaultAdminTransfer updates defaultAdmin resetting the pending admin and its schedule │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +rule acceptDefaultAdminTransfer(env e) { + require nonpayable(e); + requireInvariant defaultAdminConsistency(defaultAdmin()); + requireInvariant singleDefaultAdmin(e.msg.sender, defaultAdmin()); + + address pendingAdminBefore = pendingDefaultAdmin_(); + uint48 scheduleAfter = pendingDefaultAdminSchedule_(); + + acceptDefaultAdminTransfer@withrevert(e); + bool success = !lastReverted; + + // liveness + assert success <=> e.msg.sender == pendingAdminBefore && isSet(scheduleAfter) && hasPassed(e, scheduleAfter), + "only the pending default admin can accept the role after the schedule has been set and passed"; + + // effect + assert success => defaultAdmin() == pendingAdminBefore, + "Default admin is set to the previous pending default admin"; + assert success => pendingDefaultAdmin_() == 0, + "Pending default admin is reset"; + assert success => pendingDefaultAdminSchedule_() == 0, + "Pending default admin delay is reset"; +} + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Function correctness: cancelDefaultAdminTransfer resets pending default admin and its schedule │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +rule cancelDefaultAdminTransfer(env e) { + require nonpayable(e); + requireInvariant defaultAdminConsistency(defaultAdmin()); + requireInvariant singleDefaultAdmin(e.msg.sender, defaultAdmin()); + + cancelDefaultAdminTransfer@withrevert(e); + bool success = !lastReverted; + + // liveness + assert success <=> e.msg.sender == defaultAdmin(), + "only the current default admin can cancel a transfer"; + + // effect + assert success => pendingDefaultAdmin_() == 0, + "Pending default admin is reset"; + assert success => pendingDefaultAdminSchedule_() == 0, + "Pending default admin delay is reset"; +} + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Function correctness: changeDefaultAdminDelay sets a pending default admin delay and its schedule │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +rule changeDefaultAdminDelay(env e, uint48 newDelay) { + require nonpayable(e); + require timeSanity(e); + require delayChangeWaitSanity(e, newDelay); + requireInvariant defaultAdminConsistency(defaultAdmin()); + requireInvariant singleDefaultAdmin(e.msg.sender, defaultAdmin()); + + uint48 delayBefore = defaultAdminDelay(e); + + changeDefaultAdminDelay@withrevert(e, newDelay); + bool success = !lastReverted; + + // liveness + assert success <=> e.msg.sender == defaultAdmin(), + "only the current default admin can begin a delay change"; + + // effect + assert success => pendingDelay_(e) == newDelay, "pending delay is set"; + assert success => ( + pendingDelaySchedule_(e) > e.block.timestamp || + delayBefore == newDelay || // Interpreted as decreasing, x - x = 0 + defaultAdminDelayIncreaseWait() == 0 + ), + "pending delay schedule is set in the future unless accepted edge cases"; +} + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Rule: A delay can't change in less than the applied schedule │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +rule pendingDelayWaitEnforced(env e1, env e2, method f, calldataarg args, uint48 newDelay) { + require e1.block.timestamp < e2.block.timestamp; + + uint48 delayBefore = defaultAdminDelay(e1); + changeDefaultAdminDelay(e1, newDelay); + f(e2, args); + uint48 delayAfter = defaultAdminDelay(e2); + + mathint delayWait = newDelay > delayBefore ? increasingDelaySchedule(e1, newDelay) : decreasingDelaySchedule(e1, newDelay); + + assert delayAfter == newDelay => (e2.block.timestamp >= delayWait || delayBefore == newDelay), + "A delay can't change in less than applied schedule"; +} + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Rule: pending delay wait is set depending on increasing or decreasing the delay │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +rule pendingDelayWait(env e, uint48 newDelay) { + uint48 oldDelay = defaultAdminDelay(e); + changeDefaultAdminDelay(e, newDelay); + + assert newDelay > oldDelay => pendingDelaySchedule_(e) == increasingDelaySchedule(e, newDelay), + "Delay wait is the minimum between the new delay and a threshold when the delay is increased"; + assert newDelay <= oldDelay => pendingDelaySchedule_(e) == decreasingDelaySchedule(e, newDelay), + "Delay wait is the difference between the current and the new delay when the delay is decreased"; +} + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Function correctness: rollbackDefaultAdminDelay resets the delay and its schedule │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +rule rollbackDefaultAdminDelay(env e) { + require nonpayable(e); + requireInvariant defaultAdminConsistency(defaultAdmin()); + requireInvariant singleDefaultAdmin(e.msg.sender, defaultAdmin()); + + rollbackDefaultAdminDelay@withrevert(e); + bool success = !lastReverted; + + // liveness + assert success <=> e.msg.sender == defaultAdmin(), + "only the current default admin can rollback a delay change"; + + // effect + assert success => pendingDelay_(e) == 0, + "Pending default admin is reset"; + assert success => pendingDelaySchedule_(e) == 0, + "Pending default admin delay is reset"; +} + +/* +┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Rule: pending default admin and the delay can only change along with their corresponding schedules │ +└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +*/ +rule pendingValueAndScheduleCoupling(env e, address newAdmin, uint48 newDelay) { + requireInvariant defaultAdminConsistency(defaultAdmin()); + requireInvariant singleDefaultAdmin(e.msg.sender, defaultAdmin()); + + // Pending admin + address pendingAdminBefore = pendingDefaultAdmin_(); + uint48 pendingAdminScheduleBefore = pendingDefaultAdminSchedule_(); + + beginDefaultAdminTransfer(e, newAdmin); + + address pendingAdminAfter = pendingDefaultAdmin_(); + uint48 pendingAdminScheduleAfter = pendingDefaultAdminSchedule_(); + + assert ( + pendingAdminScheduleBefore != pendingDefaultAdminSchedule_() && + pendingAdminBefore == pendingAdminAfter + ) => newAdmin == pendingAdminBefore, "pending admin stays the same if the new admin set is the same"; + + assert ( + pendingAdminBefore != pendingAdminAfter && + pendingAdminScheduleBefore == pendingDefaultAdminSchedule_() + ) => ( + // Schedule doesn't change if: + // - The defaultAdminDelay was reduced to a value such that added to the block.timestamp is equal to previous schedule + e.block.timestamp + defaultAdminDelay(e) == pendingAdminScheduleBefore + ), "pending admin stays the same if a default admin transfer is begun on accepted edge cases"; + + // Pending delay + address pendingDelayBefore = pendingDelay_(e); + uint48 pendingDelayScheduleBefore = pendingDelaySchedule_(e); + + changeDefaultAdminDelay(e, newDelay); + + address pendingDelayAfter = pendingDelay_(e); + uint48 pendingDelayScheduleAfter = pendingDelaySchedule_(e); + + assert ( + pendingDelayScheduleBefore != pendingDelayScheduleAfter && + pendingDelayBefore == pendingDelayAfter + ) => newDelay == pendingDelayBefore || pendingDelayBefore == 0, "pending delay stays the same if the new delay set is the same"; + + assert ( + pendingDelayBefore != pendingDelayAfter && + pendingDelayScheduleBefore == pendingDelayScheduleAfter + ) => ( + increasingDelaySchedule(e, newDelay) == pendingDelayScheduleBefore || + decreasingDelaySchedule(e, newDelay) == pendingDelayScheduleBefore + ), "pending delay stays the same if a default admin transfer is begun on accepted edge cases"; +} diff --git a/certora/specs/methods/IAccessControlDefaultAdminRules.spec b/certora/specs/methods/IAccessControlDefaultAdminRules.spec new file mode 100644 index 000000000..a9dd08b7f --- /dev/null +++ b/certora/specs/methods/IAccessControlDefaultAdminRules.spec @@ -0,0 +1,36 @@ +import "./IERC5313.spec" + +methods { + // === View == + + // Default Admin + defaultAdmin() returns(address) envfree + pendingDefaultAdmin() returns(address, uint48) envfree + + // Default Admin Delay + defaultAdminDelay() returns(uint48) + pendingDefaultAdminDelay() returns(uint48, uint48) + defaultAdminDelayIncreaseWait() returns(uint48) envfree + + // === Mutations == + + // Default Admin + beginDefaultAdminTransfer(address) + cancelDefaultAdminTransfer() + acceptDefaultAdminTransfer() + + // Default Admin Delay + changeDefaultAdminDelay(uint48) + rollbackDefaultAdminDelay() + + // == FV == + + // Default Admin + pendingDefaultAdmin_() returns (address) envfree + pendingDefaultAdminSchedule_() returns (uint48) envfree + + // Default Admin Delay + pendingDelay_() returns (uint48) + pendingDelaySchedule_() returns (uint48) + delayChangeWait_(uint48) returns (uint48) +} diff --git a/certora/specs/methods/IERC5313.spec b/certora/specs/methods/IERC5313.spec new file mode 100644 index 000000000..d4c5a0412 --- /dev/null +++ b/certora/specs/methods/IERC5313.spec @@ -0,0 +1,3 @@ +methods { + owner() returns (address) envfree +} From 72ed4ca67a99bba17852fb6a7dea558980260757 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Fri, 5 May 2023 21:31:23 +0200 Subject: [PATCH 047/182] Ensure AccessControlDefaultAdminRules's initialDefaultAdmin is non-zero (#4220) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ernesto García --- contracts/access/AccessControlDefaultAdminRules.sol | 1 + test/access/AccessControlDefaultAdminRules.test.js | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/contracts/access/AccessControlDefaultAdminRules.sol b/contracts/access/AccessControlDefaultAdminRules.sol index 0c640fb99..6cdda81a1 100644 --- a/contracts/access/AccessControlDefaultAdminRules.sol +++ b/contracts/access/AccessControlDefaultAdminRules.sol @@ -53,6 +53,7 @@ abstract contract AccessControlDefaultAdminRules is IAccessControlDefaultAdminRu * @dev Sets the initial values for {defaultAdminDelay} and {defaultAdmin} address. */ constructor(uint48 initialDelay, address initialDefaultAdmin) { + require(initialDefaultAdmin != address(0), "AccessControl: 0 default admin"); _currentDelay = initialDelay; _grantRole(DEFAULT_ADMIN_ROLE, initialDefaultAdmin); } diff --git a/test/access/AccessControlDefaultAdminRules.test.js b/test/access/AccessControlDefaultAdminRules.test.js index 4e3167a46..be112481e 100644 --- a/test/access/AccessControlDefaultAdminRules.test.js +++ b/test/access/AccessControlDefaultAdminRules.test.js @@ -1,4 +1,4 @@ -const { time } = require('@openzeppelin/test-helpers'); +const { time, constants, expectRevert } = require('@openzeppelin/test-helpers'); const { shouldBehaveLikeAccessControl, shouldBehaveLikeAccessControlDefaultAdminRules, @@ -13,6 +13,13 @@ contract('AccessControlDefaultAdminRules', function (accounts) { this.accessControl = await AccessControlDefaultAdminRules.new(delay, accounts[0], { from: accounts[0] }); }); + it('initial admin not zero', async function () { + await expectRevert( + AccessControlDefaultAdminRules.new(delay, constants.ZERO_ADDRESS), + 'AccessControl: 0 default admin', + ); + }); + shouldBehaveLikeAccessControl('AccessControl', ...accounts); shouldBehaveLikeAccessControlDefaultAdminRules('AccessControl', delay, ...accounts); }); From 692d8c85a494f78b83aa4de44dcce4e3e63997d1 Mon Sep 17 00:00:00 2001 From: ToonVanHove Date: Fri, 5 May 2023 22:25:23 +0200 Subject: [PATCH 048/182] Fix lookup documentation in ERC20Votes and Checkpoints (#4218) Co-authored-by: Hadrien Croubois --- .../token/ERC20/extensions/ERC20Votes.sol | 2 +- contracts/utils/Checkpoints.sol | 24 +++++++++---------- scripts/generate/templates/Checkpoints.js | 10 ++++---- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/contracts/token/ERC20/extensions/ERC20Votes.sol b/contracts/token/ERC20/extensions/ERC20Votes.sol index d72d34f81..f44b89604 100644 --- a/contracts/token/ERC20/extensions/ERC20Votes.sol +++ b/contracts/token/ERC20/extensions/ERC20Votes.sol @@ -114,7 +114,7 @@ abstract contract ERC20Votes is ERC20Permit, IERC5805 { * @dev Lookup a value in a list of (sorted) checkpoints. */ function _checkpointsLookup(Checkpoint[] storage ckpts, uint256 timepoint) private view returns (uint256) { - // We run a binary search to look for the earliest checkpoint taken after `timepoint`. + // We run a binary search to look for the last (most recent) checkpoint taken before (or at) `timepoint`. // // Initially we check if the block is recent to narrow the search range. // During the loop, the index of the wanted checkpoint remains in the range [low-1, high). diff --git a/contracts/utils/Checkpoints.sol b/contracts/utils/Checkpoints.sol index 7d7905fd1..27da4b517 100644 --- a/contracts/utils/Checkpoints.sol +++ b/contracts/utils/Checkpoints.sol @@ -151,7 +151,7 @@ library Checkpoints { } /** - * @dev Return the index of the oldest checkpoint whose key is greater than the search key, or `high` if there is none. + * @dev Return the index of the last (most recent) checkpoint with key lower or equal than the search key, or `high` if there is none. * `low` and `high` define a section where to do the search, with inclusive `low` and exclusive `high`. * * WARNING: `high` should not be greater than the array's length. @@ -174,7 +174,7 @@ library Checkpoints { } /** - * @dev Return the index of the oldest checkpoint whose key is greater or equal than the search key, or `high` if there is none. + * @dev Return the index of the first (oldest) checkpoint with key is greater or equal than the search key, or `high` if there is none. * `low` and `high` define a section where to do the search, with inclusive `low` and exclusive `high`. * * WARNING: `high` should not be greater than the array's length. @@ -225,7 +225,7 @@ library Checkpoints { } /** - * @dev Returns the value in the oldest checkpoint with key greater or equal than the search key, or zero if there is none. + * @dev Returns the value in the first (oldest) checkpoint with key greater or equal than the search key, or zero if there is none. */ function lowerLookup(Trace224 storage self, uint32 key) internal view returns (uint224) { uint256 len = self._checkpoints.length; @@ -234,7 +234,7 @@ library Checkpoints { } /** - * @dev Returns the value in the most recent checkpoint with key lower or equal than the search key. + * @dev Returns the value in the last (most recent) checkpoint with key lower or equal than the search key. */ function upperLookup(Trace224 storage self, uint32 key) internal view returns (uint224) { uint256 len = self._checkpoints.length; @@ -243,7 +243,7 @@ library Checkpoints { } /** - * @dev Returns the value in the most recent checkpoint with key lower or equal than the search key. + * @dev Returns the value in the last (most recent) checkpoint with key lower or equal than the search key. * * NOTE: This is a variant of {upperLookup} that is optimised to find "recent" checkpoint (checkpoints with high keys). */ @@ -324,7 +324,7 @@ library Checkpoints { } /** - * @dev Return the index of the oldest checkpoint whose key is greater than the search key, or `high` if there is none. + * @dev Return the index of the last (most recent) checkpoint with key lower or equal than the search key, or `high` if there is none. * `low` and `high` define a section where to do the search, with inclusive `low` and exclusive `high`. * * WARNING: `high` should not be greater than the array's length. @@ -347,7 +347,7 @@ library Checkpoints { } /** - * @dev Return the index of the oldest checkpoint whose key is greater or equal than the search key, or `high` if there is none. + * @dev Return the index of the first (oldest) checkpoint with key is greater or equal than the search key, or `high` if there is none. * `low` and `high` define a section where to do the search, with inclusive `low` and exclusive `high`. * * WARNING: `high` should not be greater than the array's length. @@ -401,7 +401,7 @@ library Checkpoints { } /** - * @dev Returns the value in the oldest checkpoint with key greater or equal than the search key, or zero if there is none. + * @dev Returns the value in the first (oldest) checkpoint with key greater or equal than the search key, or zero if there is none. */ function lowerLookup(Trace160 storage self, uint96 key) internal view returns (uint160) { uint256 len = self._checkpoints.length; @@ -410,7 +410,7 @@ library Checkpoints { } /** - * @dev Returns the value in the most recent checkpoint with key lower or equal than the search key. + * @dev Returns the value in the last (most recent) checkpoint with key lower or equal than the search key. */ function upperLookup(Trace160 storage self, uint96 key) internal view returns (uint160) { uint256 len = self._checkpoints.length; @@ -419,7 +419,7 @@ library Checkpoints { } /** - * @dev Returns the value in the most recent checkpoint with key lower or equal than the search key. + * @dev Returns the value in the last (most recent) checkpoint with key lower or equal than the search key. * * NOTE: This is a variant of {upperLookup} that is optimised to find "recent" checkpoint (checkpoints with high keys). */ @@ -500,7 +500,7 @@ library Checkpoints { } /** - * @dev Return the index of the oldest checkpoint whose key is greater than the search key, or `high` if there is none. + * @dev Return the index of the last (most recent) checkpoint with key lower or equal than the search key, or `high` if there is none. * `low` and `high` define a section where to do the search, with inclusive `low` and exclusive `high`. * * WARNING: `high` should not be greater than the array's length. @@ -523,7 +523,7 @@ library Checkpoints { } /** - * @dev Return the index of the oldest checkpoint whose key is greater or equal than the search key, or `high` if there is none. + * @dev Return the index of the first (oldest) checkpoint with key is greater or equal than the search key, or `high` if there is none. * `low` and `high` define a section where to do the search, with inclusive `low` and exclusive `high`. * * WARNING: `high` should not be greater than the array's length. diff --git a/scripts/generate/templates/Checkpoints.js b/scripts/generate/templates/Checkpoints.js index 18999682d..1d79031da 100644 --- a/scripts/generate/templates/Checkpoints.js +++ b/scripts/generate/templates/Checkpoints.js @@ -46,7 +46,7 @@ function push( } /** - * @dev Returns the value in the oldest checkpoint with key greater or equal than the search key, or zero if there is none. + * @dev Returns the value in the first (oldest) checkpoint with key greater or equal than the search key, or zero if there is none. */ function lowerLookup(${opts.historyTypeName} storage self, ${opts.keyTypeName} key) internal view returns (${opts.valueTypeName}) { uint256 len = self.${opts.checkpointFieldName}.length; @@ -55,7 +55,7 @@ function lowerLookup(${opts.historyTypeName} storage self, ${opts.keyTypeName} k } /** - * @dev Returns the value in the most recent checkpoint with key lower or equal than the search key. + * @dev Returns the value in the last (most recent) checkpoint with key lower or equal than the search key. */ function upperLookup(${opts.historyTypeName} storage self, ${opts.keyTypeName} key) internal view returns (${opts.valueTypeName}) { uint256 len = self.${opts.checkpointFieldName}.length; @@ -64,7 +64,7 @@ function upperLookup(${opts.historyTypeName} storage self, ${opts.keyTypeName} k } /** - * @dev Returns the value in the most recent checkpoint with key lower or equal than the search key. + * @dev Returns the value in the last (most recent) checkpoint with key lower or equal than the search key. * * NOTE: This is a variant of {upperLookup} that is optimised to find "recent" checkpoint (checkpoints with high keys). */ @@ -227,7 +227,7 @@ function _insert( } /** - * @dev Return the index of the oldest checkpoint whose key is greater than the search key, or \`high\` if there is none. + * @dev Return the index of the last (most recent) checkpoint with key lower or equal than the search key, or \`high\` if there is none. * \`low\` and \`high\` define a section where to do the search, with inclusive \`low\` and exclusive \`high\`. * * WARNING: \`high\` should not be greater than the array's length. @@ -250,7 +250,7 @@ function _upperBinaryLookup( } /** - * @dev Return the index of the oldest checkpoint whose key is greater or equal than the search key, or \`high\` if there is none. + * @dev Return the index of the first (oldest) checkpoint with key is greater or equal than the search key, or \`high\` if there is none. * \`low\` and \`high\` define a section where to do the search, with inclusive \`low\` and exclusive \`high\`. * * WARNING: \`high\` should not be greater than the array's length. From 908f78d07b7b28c79af04bf0fc90501a81246b0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Mon, 8 May 2023 23:00:49 +0200 Subject: [PATCH 049/182] Enable more Slither detectors (#4219) --- package.json | 2 +- slither.config.json | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 56798f788..a3b98fe37 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "test:inheritance": "scripts/checks/inheritance-ordering.js artifacts/build-info/*", "test:generation": "scripts/checks/generation.sh", "gas-report": "env ENABLE_GAS_REPORT=true npm run test", - "slither": "npm run clean && slither . --detect reentrancy-eth,reentrancy-no-eth,reentrancy-unlimited-gas" + "slither": "npm run clean && slither ." }, "repository": { "type": "git", diff --git a/slither.config.json b/slither.config.json index 2b618794a..069da1f3a 100644 --- a/slither.config.json +++ b/slither.config.json @@ -1,5 +1,5 @@ { - "detectors_to_run": "reentrancy-eth,reentrancy-no-eth,reentrancy-unlimited-gas", - "filter_paths": "contracts/mocks", + "detectors_to_run": "arbitrary-send-erc20,array-by-reference,incorrect-shift,name-reused,rtlo,suicidal,uninitialized-state,uninitialized-storage,arbitrary-send-erc20-permit,controlled-array-length,controlled-delegatecall,delegatecall-loop,msg-value-loop,reentrancy-eth,unchecked-transfer,weak-prng,domain-separator-collision,erc20-interface,erc721-interface,locked-ether,mapping-deletion,shadowing-abstract,tautology,write-after-write,boolean-cst,reentrancy-no-eth,reused-constructor,tx-origin,unchecked-lowlevel,unchecked-send,variable-scope,void-cst,events-access,events-maths,incorrect-unary,boolean-equal,cyclomatic-complexity,deprecated-standards,erc20-indexed,function-init-state,pragma,unused-state,reentrancy-unlimited-gas,constable-states,immutable-states,var-read-using-this", + "filter_paths": "contracts/mocks,contracts-exposed", "compile_force_framework": "hardhat" -} \ No newline at end of file +} From 832c352c7d68c75a89974bd1e4623bd1a1618ab0 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 9 May 2023 00:04:41 -0300 Subject: [PATCH 050/182] Update lockfile (#4203) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package-lock.json | 3438 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 2593 insertions(+), 845 deletions(-) diff --git a/package-lock.json b/package-lock.json index f44a1b939..2219b08f2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -53,9 +53,9 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz", + "integrity": "sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==", "dev": true, "dependencies": { "@babel/highlight": "^7.18.6" @@ -88,9 +88,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz", - "integrity": "sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==", + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.5.tgz", + "integrity": "sha512-8jI69toZqqcsnqGGqwGS4Qb1VwLOEp4hz+CXPywcvjs60u3B4Pom/U/7rm4W8tMOYEB+E9wgD0mW1l3r8qlI9Q==", "dev": true, "dependencies": { "regenerator-runtime": "^0.13.11" @@ -99,6 +99,32 @@ "node": ">=6.9.0" } }, + "node_modules/@chainsafe/as-sha256": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@chainsafe/as-sha256/-/as-sha256-0.3.1.tgz", + "integrity": "sha512-hldFFYuf49ed7DAakWVXSJODuq3pzJEguD8tQ7h+sGkM18vja+OFoJI9krnGmgzyuZC2ETX0NOIcCTy31v2Mtg==", + "dev": true + }, + "node_modules/@chainsafe/persistent-merkle-tree": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@chainsafe/persistent-merkle-tree/-/persistent-merkle-tree-0.4.2.tgz", + "integrity": "sha512-lLO3ihKPngXLTus/L7WHKaw9PnNJWizlOF1H9NNzHP6Xvh82vzg9F2bzkXhYIFshMZ2gTCEz8tq6STe7r5NDfQ==", + "dev": true, + "dependencies": { + "@chainsafe/as-sha256": "^0.3.1" + } + }, + "node_modules/@chainsafe/ssz": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/@chainsafe/ssz/-/ssz-0.9.4.tgz", + "integrity": "sha512-77Qtg2N1ayqs4Bg/wvnWfg5Bta7iy7IRh8XqXh7oNMeP2HBbBwx8m6yTpA8p0EHItWPEBkgZd5S5/LSlp3GXuQ==", + "dev": true, + "dependencies": { + "@chainsafe/as-sha256": "^0.3.1", + "@chainsafe/persistent-merkle-tree": "^0.4.2", + "case": "^1.6.3" + } + }, "node_modules/@changesets/apply-release-plan": { "version": "6.1.3", "resolved": "https://registry.npmjs.org/@changesets/apply-release-plan/-/apply-release-plan-6.1.3.tgz", @@ -173,9 +199,9 @@ } }, "node_modules/@changesets/cli": { - "version": "2.26.0", - "resolved": "https://registry.npmjs.org/@changesets/cli/-/cli-2.26.0.tgz", - "integrity": "sha512-0cbTiDms+ICTVtEwAFLNW0jBNex9f5+fFv3I771nBvdnV/mOjd1QJ4+f8KtVSOrwD9SJkk9xbDkWFb0oXd8d1Q==", + "version": "2.26.1", + "resolved": "https://registry.npmjs.org/@changesets/cli/-/cli-2.26.1.tgz", + "integrity": "sha512-XnTa+b51vt057fyAudvDKGB0Sh72xutQZNAdXkCqPBKO2zvs2yYZx5hFZj1u9cbtpwM6Sxtcr02/FQJfZOzemQ==", "dev": true, "dependencies": { "@babel/runtime": "^7.20.1", @@ -500,9 +526,9 @@ "dev": true }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.2.0.tgz", - "integrity": "sha512-gB8T4H4DEfX2IV9zGDJPOBgP1e/DbfCPDTtEqUMckpvzS1OYtva8JdFYBqMwYk7xAQ429WGF/UPqn8uQ//h2vQ==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", "dev": true, "dependencies": { "eslint-visitor-keys": "^3.3.0" @@ -515,23 +541,23 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.4.0.tgz", - "integrity": "sha512-A9983Q0LnDGdLPjxyXQ00sbV+K+O+ko2Dr+CZigbHWtX9pNfxlaBkMR8X1CztI73zuEyEBXTVjx7CE+/VSwDiQ==", + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.1.tgz", + "integrity": "sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==", "dev": true, "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, "node_modules/@eslint/eslintrc": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.1.tgz", - "integrity": "sha512-eFRmABvW2E5Ho6f5fHLqgena46rOj7r7OKHYfLElqcBfGFHHpjBhivyi5+jOEQuSpdc/1phIZJlbC2te+tZNIw==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.2.tgz", + "integrity": "sha512-3W4f5tDUra+pA+FzgugqL2pRimUTDJWKr7BINqOpkZrC0uYI0NIc0/JFgBROCU07HR6GieA5m3/rsPIhDmCXTQ==", "dev": true, "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.5.0", + "espree": "^9.5.1", "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", @@ -565,9 +591,9 @@ } }, "node_modules/@eslint/js": { - "version": "8.36.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.36.0.tgz", - "integrity": "sha512-lxJ9R5ygVm8ZWgYdUweoq5ownDlJ4upvoWmO4eLxBYHdMo+vZ/Rx0EN6MbKWDJOSUGrqJy2Gt+Dyv/VKml0fjg==", + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.39.0.tgz", + "integrity": "sha512-kf9RB0Fg7NZfap83B3QOqOGg9QmD9yBudqQXzzOtn3i4y7ZUXe5ONeW34Gwi+TxhH4mvj72R1Zc300KUMa9Bng==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -1536,34 +1562,84 @@ } }, "node_modules/@nomicfoundation/ethereumjs-block": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-block/-/ethereumjs-block-4.0.0.tgz", - "integrity": "sha512-bk8uP8VuexLgyIZAHExH1QEovqx0Lzhc9Ntm63nCRKLHXIZkobaFaeCVwTESV7YkPKUk7NiK11s8ryed4CS9yA==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-block/-/ethereumjs-block-5.0.1.tgz", + "integrity": "sha512-u1Yioemi6Ckj3xspygu/SfFvm8vZEO8/Yx5a1QLzi6nVU0jz3Pg2OmHKJ5w+D9Ogk1vhwRiqEBAqcb0GVhCyHw==", "dev": true, "dependencies": { - "@nomicfoundation/ethereumjs-common": "^3.0.0", - "@nomicfoundation/ethereumjs-rlp": "^4.0.0", - "@nomicfoundation/ethereumjs-trie": "^5.0.0", - "@nomicfoundation/ethereumjs-tx": "^4.0.0", - "@nomicfoundation/ethereumjs-util": "^8.0.0", - "ethereum-cryptography": "0.1.3" + "@nomicfoundation/ethereumjs-common": "4.0.1", + "@nomicfoundation/ethereumjs-rlp": "5.0.1", + "@nomicfoundation/ethereumjs-trie": "6.0.1", + "@nomicfoundation/ethereumjs-tx": "5.0.1", + "@nomicfoundation/ethereumjs-util": "9.0.1", + "ethereum-cryptography": "0.1.3", + "ethers": "^5.7.1" }, "engines": { "node": ">=14" } }, + "node_modules/@nomicfoundation/ethereumjs-block/node_modules/ethers": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-5.7.2.tgz", + "integrity": "sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/abi": "5.7.0", + "@ethersproject/abstract-provider": "5.7.0", + "@ethersproject/abstract-signer": "5.7.0", + "@ethersproject/address": "5.7.0", + "@ethersproject/base64": "5.7.0", + "@ethersproject/basex": "5.7.0", + "@ethersproject/bignumber": "5.7.0", + "@ethersproject/bytes": "5.7.0", + "@ethersproject/constants": "5.7.0", + "@ethersproject/contracts": "5.7.0", + "@ethersproject/hash": "5.7.0", + "@ethersproject/hdnode": "5.7.0", + "@ethersproject/json-wallets": "5.7.0", + "@ethersproject/keccak256": "5.7.0", + "@ethersproject/logger": "5.7.0", + "@ethersproject/networks": "5.7.1", + "@ethersproject/pbkdf2": "5.7.0", + "@ethersproject/properties": "5.7.0", + "@ethersproject/providers": "5.7.2", + "@ethersproject/random": "5.7.0", + "@ethersproject/rlp": "5.7.0", + "@ethersproject/sha2": "5.7.0", + "@ethersproject/signing-key": "5.7.0", + "@ethersproject/solidity": "5.7.0", + "@ethersproject/strings": "5.7.0", + "@ethersproject/transactions": "5.7.0", + "@ethersproject/units": "5.7.0", + "@ethersproject/wallet": "5.7.0", + "@ethersproject/web": "5.7.1", + "@ethersproject/wordlists": "5.7.0" + } + }, "node_modules/@nomicfoundation/ethereumjs-blockchain": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-blockchain/-/ethereumjs-blockchain-6.0.0.tgz", - "integrity": "sha512-pLFEoea6MWd81QQYSReLlLfH7N9v7lH66JC/NMPN848ySPPQA5renWnE7wPByfQFzNrPBuDDRFFULMDmj1C0xw==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-blockchain/-/ethereumjs-blockchain-7.0.1.tgz", + "integrity": "sha512-NhzndlGg829XXbqJEYrF1VeZhAwSPgsK/OB7TVrdzft3y918hW5KNd7gIZ85sn6peDZOdjBsAXIpXZ38oBYE5A==", "dev": true, "dependencies": { - "@nomicfoundation/ethereumjs-block": "^4.0.0", - "@nomicfoundation/ethereumjs-common": "^3.0.0", - "@nomicfoundation/ethereumjs-ethash": "^2.0.0", - "@nomicfoundation/ethereumjs-rlp": "^4.0.0", - "@nomicfoundation/ethereumjs-trie": "^5.0.0", - "@nomicfoundation/ethereumjs-util": "^8.0.0", + "@nomicfoundation/ethereumjs-block": "5.0.1", + "@nomicfoundation/ethereumjs-common": "4.0.1", + "@nomicfoundation/ethereumjs-ethash": "3.0.1", + "@nomicfoundation/ethereumjs-rlp": "5.0.1", + "@nomicfoundation/ethereumjs-trie": "6.0.1", + "@nomicfoundation/ethereumjs-tx": "5.0.1", + "@nomicfoundation/ethereumjs-util": "9.0.1", "abstract-level": "^1.0.3", "debug": "^4.3.3", "ethereum-cryptography": "0.1.3", @@ -1576,24 +1652,24 @@ } }, "node_modules/@nomicfoundation/ethereumjs-common": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-common/-/ethereumjs-common-3.0.0.tgz", - "integrity": "sha512-WS7qSshQfxoZOpHG/XqlHEGRG1zmyjYrvmATvc4c62+gZXgre1ymYP8ZNgx/3FyZY0TWe9OjFlKOfLqmgOeYwA==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-common/-/ethereumjs-common-4.0.1.tgz", + "integrity": "sha512-OBErlkfp54GpeiE06brBW/TTbtbuBJV5YI5Nz/aB2evTDo+KawyEzPjBlSr84z/8MFfj8wS2wxzQX1o32cev5g==", "dev": true, "dependencies": { - "@nomicfoundation/ethereumjs-util": "^8.0.0", + "@nomicfoundation/ethereumjs-util": "9.0.1", "crc-32": "^1.2.0" } }, "node_modules/@nomicfoundation/ethereumjs-ethash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-ethash/-/ethereumjs-ethash-2.0.0.tgz", - "integrity": "sha512-WpDvnRncfDUuXdsAXlI4lXbqUDOA+adYRQaEezIkxqDkc+LDyYDbd/xairmY98GnQzo1zIqsIL6GB5MoMSJDew==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-ethash/-/ethereumjs-ethash-3.0.1.tgz", + "integrity": "sha512-KDjGIB5igzWOp8Ik5I6QiRH5DH+XgILlplsHR7TEuWANZA759G6krQ6o8bvj+tRUz08YygMQu/sGd9mJ1DYT8w==", "dev": true, "dependencies": { - "@nomicfoundation/ethereumjs-block": "^4.0.0", - "@nomicfoundation/ethereumjs-rlp": "^4.0.0", - "@nomicfoundation/ethereumjs-util": "^8.0.0", + "@nomicfoundation/ethereumjs-block": "5.0.1", + "@nomicfoundation/ethereumjs-rlp": "5.0.1", + "@nomicfoundation/ethereumjs-util": "9.0.1", "abstract-level": "^1.0.3", "bigint-crypto-utils": "^3.0.23", "ethereum-cryptography": "0.1.3" @@ -1603,15 +1679,15 @@ } }, "node_modules/@nomicfoundation/ethereumjs-evm": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-evm/-/ethereumjs-evm-1.0.0.tgz", - "integrity": "sha512-hVS6qRo3V1PLKCO210UfcEQHvlG7GqR8iFzp0yyjTg2TmJQizcChKgWo8KFsdMw6AyoLgLhHGHw4HdlP8a4i+Q==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-evm/-/ethereumjs-evm-2.0.1.tgz", + "integrity": "sha512-oL8vJcnk0Bx/onl+TgQOQ1t/534GKFaEG17fZmwtPFeH8S5soiBYPCLUrvANOl4sCp9elYxIMzIiTtMtNNN8EQ==", "dev": true, "dependencies": { - "@nomicfoundation/ethereumjs-common": "^3.0.0", - "@nomicfoundation/ethereumjs-util": "^8.0.0", - "@types/async-eventemitter": "^0.2.1", - "async-eventemitter": "^0.2.4", + "@ethersproject/providers": "^5.7.1", + "@nomicfoundation/ethereumjs-common": "4.0.1", + "@nomicfoundation/ethereumjs-tx": "5.0.1", + "@nomicfoundation/ethereumjs-util": "9.0.1", "debug": "^4.3.3", "ethereum-cryptography": "0.1.3", "mcl-wasm": "^0.7.1", @@ -1622,9 +1698,9 @@ } }, "node_modules/@nomicfoundation/ethereumjs-rlp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-rlp/-/ethereumjs-rlp-4.0.0.tgz", - "integrity": "sha512-GaSOGk5QbUk4eBP5qFbpXoZoZUj/NrW7MRa0tKY4Ew4c2HAS0GXArEMAamtFrkazp0BO4K5p2ZCG3b2FmbShmw==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-rlp/-/ethereumjs-rlp-5.0.1.tgz", + "integrity": "sha512-xtxrMGa8kP4zF5ApBQBtjlSbN5E2HI8m8FYgVSYAnO6ssUoY5pVPGy2H8+xdf/bmMa22Ce8nWMH3aEW8CcqMeQ==", "dev": true, "bin": { "rlp": "bin/rlp" @@ -1634,28 +1710,76 @@ } }, "node_modules/@nomicfoundation/ethereumjs-statemanager": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-statemanager/-/ethereumjs-statemanager-1.0.0.tgz", - "integrity": "sha512-jCtqFjcd2QejtuAMjQzbil/4NHf5aAWxUc+CvS0JclQpl+7M0bxMofR2AJdtz+P3u0ke2euhYREDiE7iSO31vQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-statemanager/-/ethereumjs-statemanager-2.0.1.tgz", + "integrity": "sha512-B5ApMOnlruVOR7gisBaYwFX+L/AP7i/2oAahatssjPIBVDF6wTX1K7Qpa39E/nzsH8iYuL3krkYeUFIdO3EMUQ==", "dev": true, "dependencies": { - "@nomicfoundation/ethereumjs-common": "^3.0.0", - "@nomicfoundation/ethereumjs-rlp": "^4.0.0", - "@nomicfoundation/ethereumjs-trie": "^5.0.0", - "@nomicfoundation/ethereumjs-util": "^8.0.0", + "@nomicfoundation/ethereumjs-common": "4.0.1", + "@nomicfoundation/ethereumjs-rlp": "5.0.1", "debug": "^4.3.3", "ethereum-cryptography": "0.1.3", - "functional-red-black-tree": "^1.0.1" + "ethers": "^5.7.1", + "js-sdsl": "^4.1.4" + } + }, + "node_modules/@nomicfoundation/ethereumjs-statemanager/node_modules/ethers": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-5.7.2.tgz", + "integrity": "sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/abi": "5.7.0", + "@ethersproject/abstract-provider": "5.7.0", + "@ethersproject/abstract-signer": "5.7.0", + "@ethersproject/address": "5.7.0", + "@ethersproject/base64": "5.7.0", + "@ethersproject/basex": "5.7.0", + "@ethersproject/bignumber": "5.7.0", + "@ethersproject/bytes": "5.7.0", + "@ethersproject/constants": "5.7.0", + "@ethersproject/contracts": "5.7.0", + "@ethersproject/hash": "5.7.0", + "@ethersproject/hdnode": "5.7.0", + "@ethersproject/json-wallets": "5.7.0", + "@ethersproject/keccak256": "5.7.0", + "@ethersproject/logger": "5.7.0", + "@ethersproject/networks": "5.7.1", + "@ethersproject/pbkdf2": "5.7.0", + "@ethersproject/properties": "5.7.0", + "@ethersproject/providers": "5.7.2", + "@ethersproject/random": "5.7.0", + "@ethersproject/rlp": "5.7.0", + "@ethersproject/sha2": "5.7.0", + "@ethersproject/signing-key": "5.7.0", + "@ethersproject/solidity": "5.7.0", + "@ethersproject/strings": "5.7.0", + "@ethersproject/transactions": "5.7.0", + "@ethersproject/units": "5.7.0", + "@ethersproject/wallet": "5.7.0", + "@ethersproject/web": "5.7.1", + "@ethersproject/wordlists": "5.7.0" } }, "node_modules/@nomicfoundation/ethereumjs-trie": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-trie/-/ethereumjs-trie-5.0.0.tgz", - "integrity": "sha512-LIj5XdE+s+t6WSuq/ttegJzZ1vliwg6wlb+Y9f4RlBpuK35B9K02bO7xU+E6Rgg9RGptkWd6TVLdedTI4eNc2A==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-trie/-/ethereumjs-trie-6.0.1.tgz", + "integrity": "sha512-A64It/IMpDVODzCgxDgAAla8jNjNtsoQZIzZUfIV5AY6Coi4nvn7+VReBn5itlxMiL2yaTlQr9TRWp3CSI6VoA==", "dev": true, "dependencies": { - "@nomicfoundation/ethereumjs-rlp": "^4.0.0", - "@nomicfoundation/ethereumjs-util": "^8.0.0", + "@nomicfoundation/ethereumjs-rlp": "5.0.1", + "@nomicfoundation/ethereumjs-util": "9.0.1", + "@types/readable-stream": "^2.3.13", "ethereum-cryptography": "0.1.3", "readable-stream": "^3.6.0" }, @@ -1664,14 +1788,16 @@ } }, "node_modules/@nomicfoundation/ethereumjs-tx": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-tx/-/ethereumjs-tx-4.0.0.tgz", - "integrity": "sha512-Gg3Lir2lNUck43Kp/3x6TfBNwcWC9Z1wYue9Nz3v4xjdcv6oDW9QSMJxqsKw9QEGoBBZ+gqwpW7+F05/rs/g1w==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-tx/-/ethereumjs-tx-5.0.1.tgz", + "integrity": "sha512-0HwxUF2u2hrsIM1fsasjXvlbDOq1ZHFV2dd1yGq8CA+MEYhaxZr8OTScpVkkxqMwBcc5y83FyPl0J9MZn3kY0w==", "dev": true, "dependencies": { - "@nomicfoundation/ethereumjs-common": "^3.0.0", - "@nomicfoundation/ethereumjs-rlp": "^4.0.0", - "@nomicfoundation/ethereumjs-util": "^8.0.0", + "@chainsafe/ssz": "^0.9.2", + "@ethersproject/providers": "^5.7.2", + "@nomicfoundation/ethereumjs-common": "4.0.1", + "@nomicfoundation/ethereumjs-rlp": "5.0.1", + "@nomicfoundation/ethereumjs-util": "9.0.1", "ethereum-cryptography": "0.1.3" }, "engines": { @@ -1679,38 +1805,55 @@ } }, "node_modules/@nomicfoundation/ethereumjs-util": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-util/-/ethereumjs-util-8.0.0.tgz", - "integrity": "sha512-2emi0NJ/HmTG+CGY58fa+DQuAoroFeSH9gKu9O6JnwTtlzJtgfTixuoOqLEgyyzZVvwfIpRueuePb8TonL1y+A==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-util/-/ethereumjs-util-9.0.1.tgz", + "integrity": "sha512-TwbhOWQ8QoSCFhV/DDfSmyfFIHjPjFBj957219+V3jTZYZ2rf9PmDtNOeZWAE3p3vlp8xb02XGpd0v6nTUPbsA==", "dev": true, "dependencies": { - "@nomicfoundation/ethereumjs-rlp": "^4.0.0-beta.2", + "@chainsafe/ssz": "^0.10.0", + "@nomicfoundation/ethereumjs-rlp": "5.0.1", "ethereum-cryptography": "0.1.3" }, "engines": { "node": ">=14" } }, + "node_modules/@nomicfoundation/ethereumjs-util/node_modules/@chainsafe/persistent-merkle-tree": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@chainsafe/persistent-merkle-tree/-/persistent-merkle-tree-0.5.0.tgz", + "integrity": "sha512-l0V1b5clxA3iwQLXP40zYjyZYospQLZXzBVIhhr9kDg/1qHZfzzHw0jj4VPBijfYCArZDlPkRi1wZaV2POKeuw==", + "dev": true, + "dependencies": { + "@chainsafe/as-sha256": "^0.3.1" + } + }, + "node_modules/@nomicfoundation/ethereumjs-util/node_modules/@chainsafe/ssz": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@chainsafe/ssz/-/ssz-0.10.2.tgz", + "integrity": "sha512-/NL3Lh8K+0q7A3LsiFq09YXS9fPE+ead2rr7vM2QK8PLzrNsw3uqrif9bpRX5UxgeRjM+vYi+boCM3+GM4ovXg==", + "dev": true, + "dependencies": { + "@chainsafe/as-sha256": "^0.3.1", + "@chainsafe/persistent-merkle-tree": "^0.5.0" + } + }, "node_modules/@nomicfoundation/ethereumjs-vm": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-vm/-/ethereumjs-vm-6.0.0.tgz", - "integrity": "sha512-JMPxvPQ3fzD063Sg3Tp+UdwUkVxMoo1uML6KSzFhMH3hoQi/LMuXBoEHAoW83/vyNS9BxEe6jm6LmT5xdeEJ6w==", - "dev": true, - "dependencies": { - "@nomicfoundation/ethereumjs-block": "^4.0.0", - "@nomicfoundation/ethereumjs-blockchain": "^6.0.0", - "@nomicfoundation/ethereumjs-common": "^3.0.0", - "@nomicfoundation/ethereumjs-evm": "^1.0.0", - "@nomicfoundation/ethereumjs-rlp": "^4.0.0", - "@nomicfoundation/ethereumjs-statemanager": "^1.0.0", - "@nomicfoundation/ethereumjs-trie": "^5.0.0", - "@nomicfoundation/ethereumjs-tx": "^4.0.0", - "@nomicfoundation/ethereumjs-util": "^8.0.0", - "@types/async-eventemitter": "^0.2.1", - "async-eventemitter": "^0.2.4", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-vm/-/ethereumjs-vm-7.0.1.tgz", + "integrity": "sha512-rArhyn0jPsS/D+ApFsz3yVJMQ29+pVzNZ0VJgkzAZ+7FqXSRtThl1C1prhmlVr3YNUlfpZ69Ak+RUT4g7VoOuQ==", + "dev": true, + "dependencies": { + "@nomicfoundation/ethereumjs-block": "5.0.1", + "@nomicfoundation/ethereumjs-blockchain": "7.0.1", + "@nomicfoundation/ethereumjs-common": "4.0.1", + "@nomicfoundation/ethereumjs-evm": "2.0.1", + "@nomicfoundation/ethereumjs-rlp": "5.0.1", + "@nomicfoundation/ethereumjs-statemanager": "2.0.1", + "@nomicfoundation/ethereumjs-trie": "6.0.1", + "@nomicfoundation/ethereumjs-tx": "5.0.1", + "@nomicfoundation/ethereumjs-util": "9.0.1", "debug": "^4.3.3", "ethereum-cryptography": "0.1.3", - "functional-red-black-tree": "^1.0.1", "mcl-wasm": "^0.7.1", "rustbn.js": "~0.2.0" }, @@ -2115,9 +2258,9 @@ } }, "node_modules/@openzeppelin/upgrades-core": { - "version": "1.24.1", - "resolved": "https://registry.npmjs.org/@openzeppelin/upgrades-core/-/upgrades-core-1.24.1.tgz", - "integrity": "sha512-QhdIQDUykJ3vQauB6CheV7vk4zgn0e1iY+IDg7r1KqpA1m2bqIGjQCpzidW33K4bZc9zdJSPx2/Z6Um5KxCB7A==", + "version": "1.25.0", + "resolved": "https://registry.npmjs.org/@openzeppelin/upgrades-core/-/upgrades-core-1.25.0.tgz", + "integrity": "sha512-vSxOSm1k+P156nNm15ydhOmSPGC37mnl092FMVOH+eGaoqLjr2Za6ULVjDMFzvMnG+sGE1UoDOqBNPfTm0ch8w==", "dev": true, "dependencies": { "cbor": "^8.0.0", @@ -2390,16 +2533,40 @@ "web3-utils": "1.8.2" } }, + "node_modules/@truffle/abi-utils/node_modules/bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", + "dev": true + }, + "node_modules/@truffle/abi-utils/node_modules/web3-utils": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.8.2.tgz", + "integrity": "sha512-v7j6xhfLQfY7xQDrUP0BKbaNrmZ2/+egbqP9q3KYmOiPpnvAfol+32slgL0WX/5n8VPvKCK5EZ1HGrAVICSToA==", + "dev": true, + "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" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/@truffle/blockchain-utils": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/@truffle/blockchain-utils/-/blockchain-utils-0.1.6.tgz", - "integrity": "sha512-SldoNRIFSm3+HMBnSc2jFsu5TWDkCN4X6vL3wrd0t6DIeF7nD6EoPPjxwbFSoqCnkkRxMuZeL6sUx7UMJS/wSA==", + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/@truffle/blockchain-utils/-/blockchain-utils-0.1.7.tgz", + "integrity": "sha512-1nibqGjEHC7KAyDThEFvbm2+EO8zAHee/VjCtxkYBE3ySwP50joh0QCEBjy7K/9z+icpMoDucfxmgaKToBFUgQ==", "dev": true }, "node_modules/@truffle/codec": { - "version": "0.14.16", - "resolved": "https://registry.npmjs.org/@truffle/codec/-/codec-0.14.16.tgz", - "integrity": "sha512-a9UY3n/FnkKN3Q4zOuMFOOcLWb80mdknj+voim4vvXYtJm1aAZQZE5sG9aLnMBTl4TiGLzUtfNDVYY7WgWgDag==", + "version": "0.14.17", + "resolved": "https://registry.npmjs.org/@truffle/codec/-/codec-0.14.17.tgz", + "integrity": "sha512-kD4dD86huLeaBEq5R8D1zleJEu6NsXbyYLdXl1V1TKdiO8odw5CBC6Y/+wdu5d3t1dyEYrTbhn1dqknZa52pmw==", "dev": true, "dependencies": { "@truffle/abi-utils": "^0.3.9", @@ -2478,6 +2645,24 @@ "node": ">=10" } }, + "node_modules/@truffle/codec/node_modules/web3-utils": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.8.2.tgz", + "integrity": "sha512-v7j6xhfLQfY7xQDrUP0BKbaNrmZ2/+egbqP9q3KYmOiPpnvAfol+32slgL0WX/5n8VPvKCK5EZ1HGrAVICSToA==", + "dev": true, + "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" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/@truffle/codec/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", @@ -2501,17 +2686,17 @@ "dev": true }, "node_modules/@truffle/contract": { - "version": "4.6.17", - "resolved": "https://registry.npmjs.org/@truffle/contract/-/contract-4.6.17.tgz", - "integrity": "sha512-sIMam5Wqr9AEiqHfOcWGJGqTv8Qy+BT765PaNHUUT6JBAY+tpHM3FlQd2nM6zLJ8paR3SLDGIthkhCBH/KNgDA==", + "version": "4.6.20", + "resolved": "https://registry.npmjs.org/@truffle/contract/-/contract-4.6.20.tgz", + "integrity": "sha512-s7Mbc37L/CF5Apy/cjPnalkgACmG9tTAmcIW28cIZLRLOUAze18pqhtdHryxAQhEOtKGaDAho6TriqL7/74uHw==", "dev": true, "dependencies": { "@ensdomains/ensjs": "^2.1.0", - "@truffle/blockchain-utils": "^0.1.6", + "@truffle/blockchain-utils": "^0.1.7", "@truffle/contract-schema": "^3.4.13", - "@truffle/debug-utils": "^6.0.47", + "@truffle/debug-utils": "^6.0.48", "@truffle/error": "^0.2.0", - "@truffle/interface-adapter": "^0.5.30", + "@truffle/interface-adapter": "^0.5.32", "bignumber.js": "^7.2.1", "debug": "^4.3.1", "ethers": "^4.0.32", @@ -2538,80 +2723,824 @@ "integrity": "sha512-Fe0/z4WWb7IP2gBnv3l6zqP87Y0kSMs7oiSLakKJq17q3GUunrHSdioKuNspdggxkXIBhEQLhi8C+LJdwmHKWQ==", "dev": true }, - "node_modules/@truffle/debug-utils": { - "version": "6.0.47", - "resolved": "https://registry.npmjs.org/@truffle/debug-utils/-/debug-utils-6.0.47.tgz", - "integrity": "sha512-bUjdzLPdEKtoUCDzaXkrOoi+PbyAJlMBzGequBK8tirT7xL9bCP2Pd/WxvnmRd7AnfroxGNvXwVXWTItW5SMWQ==", + "node_modules/@truffle/contract/node_modules/eth-lib": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/eth-lib/-/eth-lib-0.2.8.tgz", + "integrity": "sha512-ArJ7x1WcWOlSpzdoTBX8vkwlkSQ85CjjifSZtV4co64vWxSV8geWfPI9x4SVYu3DSxnX4yWFVTtGL+j9DUFLNw==", "dev": true, "dependencies": { - "@truffle/codec": "^0.14.16", - "@trufflesuite/chromafi": "^3.0.0", - "bn.js": "^5.1.3", - "chalk": "^2.4.2", - "debug": "^4.3.1", - "highlightjs-solidity": "^2.0.6" + "bn.js": "^4.11.6", + "elliptic": "^6.4.0", + "xhr-request-promise": "^0.1.2" } }, - "node_modules/@truffle/debug-utils/node_modules/bn.js": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", - "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", - "dev": true - }, - "node_modules/@truffle/error": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@truffle/error/-/error-0.1.1.tgz", - "integrity": "sha512-sE7c9IHIGdbK4YayH4BC8i8qMjoAOeg6nUXUDZZp8wlU21/EMpaG+CLx+KqcIPyR+GSWIW3Dm0PXkr2nlggFDA==", - "dev": true - }, - "node_modules/@truffle/interface-adapter": { - "version": "0.5.30", - "resolved": "https://registry.npmjs.org/@truffle/interface-adapter/-/interface-adapter-0.5.30.tgz", - "integrity": "sha512-wyCcESeZJBkAfuSGU8GHCusIWDUDyQjJZMcyShv59ZTSAwQR7xx0+a0Q1LlS532G/pGFLYe2VzKSY1pRHRwgug==", + "node_modules/@truffle/contract/node_modules/uuid": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", "dev": true, - "dependencies": { - "bn.js": "^5.1.3", - "ethers": "^4.0.32", - "web3": "1.8.2" + "bin": { + "uuid": "dist/bin/uuid" } }, - "node_modules/@truffle/interface-adapter/node_modules/bn.js": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", - "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", - "dev": true - }, - "node_modules/@trufflesuite/chromafi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@trufflesuite/chromafi/-/chromafi-3.0.0.tgz", - "integrity": "sha512-oqWcOqn8nT1bwlPPfidfzS55vqcIDdpfzo3HbU9EnUmcSTX+I8z0UyUFI3tZQjByVJulbzxHxUGS3ZJPwK/GPQ==", + "node_modules/@truffle/contract/node_modules/web3": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3/-/web3-1.8.2.tgz", + "integrity": "sha512-92h0GdEHW9wqDICQQKyG4foZBYi0OQkyg4CRml2F7XBl/NG+fu9o6J19kzfFXzSBoA4DnJXbyRgj/RHZv5LRiw==", "dev": true, + "hasInstallScript": true, "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" + "web3-bzz": "1.8.2", + "web3-core": "1.8.2", + "web3-eth": "1.8.2", + "web3-eth-personal": "1.8.2", + "web3-net": "1.8.2", + "web3-shh": "1.8.2", + "web3-utils": "1.8.2" + }, + "engines": { + "node": ">=8.0.0" } }, - "node_modules/@trufflesuite/chromafi/node_modules/detect-indent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-5.0.0.tgz", + "node_modules/@truffle/contract/node_modules/web3-bzz": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-bzz/-/web3-bzz-1.8.2.tgz", + "integrity": "sha512-1EEnxjPnFnvNWw3XeeKuTR8PBxYd0+XWzvaLK7OJC/Go9O8llLGxrxICbKV+8cgIE0sDRBxiYx02X+6OhoAQ9w==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "@types/node": "^12.12.6", + "got": "12.1.0", + "swarm-js": "^0.1.40" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@truffle/contract/node_modules/web3-core": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-core/-/web3-core-1.8.2.tgz", + "integrity": "sha512-DJTVEAYcNqxkqruJE+Rxp3CIv0y5AZMwPHQmOkz/cz+MM75SIzMTc0AUdXzGyTS8xMF8h3YWMQGgGEy8SBf1PQ==", + "dev": true, + "dependencies": { + "@types/bn.js": "^5.1.0", + "@types/node": "^12.12.6", + "bignumber.js": "^9.0.0", + "web3-core-helpers": "1.8.2", + "web3-core-method": "1.8.2", + "web3-core-requestmanager": "1.8.2", + "web3-utils": "1.8.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@truffle/contract/node_modules/web3-core-helpers": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-core-helpers/-/web3-core-helpers-1.8.2.tgz", + "integrity": "sha512-6B1eLlq9JFrfealZBomd1fmlq1o4A09vrCVQSa51ANoib/jllT3atZrRDr0zt1rfI7TSZTZBXdN/aTdeN99DWw==", + "dev": true, + "dependencies": { + "web3-eth-iban": "1.8.2", + "web3-utils": "1.8.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@truffle/contract/node_modules/web3-core-method": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-core-method/-/web3-core-method-1.8.2.tgz", + "integrity": "sha512-1qnr5mw5wVyULzLOrk4B+ryO3gfGjGd/fx8NR+J2xCGLf1e6OSjxT9vbfuQ3fErk/NjSTWWreieYWLMhaogcRA==", + "dev": true, + "dependencies": { + "@ethersproject/transactions": "^5.6.2", + "web3-core-helpers": "1.8.2", + "web3-core-promievent": "1.8.2", + "web3-core-subscriptions": "1.8.2", + "web3-utils": "1.8.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@truffle/contract/node_modules/web3-core-promievent": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-core-promievent/-/web3-core-promievent-1.8.2.tgz", + "integrity": "sha512-nvkJWDVgoOSsolJldN33tKW6bKKRJX3MCPDYMwP5SUFOA/mCzDEoI88N0JFofDTXkh1k7gOqp1pvwi9heuaxGg==", + "dev": true, + "dependencies": { + "eventemitter3": "4.0.4" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@truffle/contract/node_modules/web3-core-requestmanager": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-core-requestmanager/-/web3-core-requestmanager-1.8.2.tgz", + "integrity": "sha512-p1d090RYs5Mu7DK1yyc3GCBVZB/03rBtFhYFoS2EruGzOWs/5Q0grgtpwS/DScdRAm8wB8mYEBhY/RKJWF6B2g==", + "dev": true, + "dependencies": { + "util": "^0.12.5", + "web3-core-helpers": "1.8.2", + "web3-providers-http": "1.8.2", + "web3-providers-ipc": "1.8.2", + "web3-providers-ws": "1.8.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@truffle/contract/node_modules/web3-core-subscriptions": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-core-subscriptions/-/web3-core-subscriptions-1.8.2.tgz", + "integrity": "sha512-vXQogHDmAIQcKpXvGiMddBUeP9lnKgYF64+yQJhPNE5PnWr1sAibXuIPV7mIPihpFr/n/DORRj6Wh1pUv9zaTw==", + "dev": true, + "dependencies": { + "eventemitter3": "4.0.4", + "web3-core-helpers": "1.8.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@truffle/contract/node_modules/web3-core/node_modules/bignumber.js": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.1.tgz", + "integrity": "sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/@truffle/contract/node_modules/web3-eth": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-eth/-/web3-eth-1.8.2.tgz", + "integrity": "sha512-JoTiWWc4F4TInpbvDUGb0WgDYJsFhuIjJlinc5ByjWD88Gvh+GKLsRjjFdbqe5YtwIGT4NymwoC5LQd1K6u/QQ==", + "dev": true, + "dependencies": { + "web3-core": "1.8.2", + "web3-core-helpers": "1.8.2", + "web3-core-method": "1.8.2", + "web3-core-subscriptions": "1.8.2", + "web3-eth-abi": "1.8.2", + "web3-eth-accounts": "1.8.2", + "web3-eth-contract": "1.8.2", + "web3-eth-ens": "1.8.2", + "web3-eth-iban": "1.8.2", + "web3-eth-personal": "1.8.2", + "web3-net": "1.8.2", + "web3-utils": "1.8.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@truffle/contract/node_modules/web3-eth-abi": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-eth-abi/-/web3-eth-abi-1.8.2.tgz", + "integrity": "sha512-Om9g3kaRNjqiNPAgKwGT16y+ZwtBzRe4ZJFGjLiSs6v5I7TPNF+rRMWuKnR6jq0azQZDj6rblvKFMA49/k48Og==", + "dev": true, + "dependencies": { + "@ethersproject/abi": "^5.6.3", + "web3-utils": "1.8.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@truffle/contract/node_modules/web3-eth-accounts": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-eth-accounts/-/web3-eth-accounts-1.8.2.tgz", + "integrity": "sha512-c367Ij63VCz9YdyjiHHWLFtN85l6QghgwMQH2B1eM/p9Y5lTlTX7t/Eg/8+f1yoIStXbk2w/PYM2lk+IkbqdLA==", + "dev": true, + "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.8.2", + "web3-core-helpers": "1.8.2", + "web3-core-method": "1.8.2", + "web3-utils": "1.8.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@truffle/contract/node_modules/web3-eth-contract": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-eth-contract/-/web3-eth-contract-1.8.2.tgz", + "integrity": "sha512-ID5A25tHTSBNwOPjiXSVzxruz006ULRIDbzWTYIFTp7NJ7vXu/kynKK2ag/ObuTqBpMbobP8nXcA9b5EDkIdQA==", + "dev": true, + "dependencies": { + "@types/bn.js": "^5.1.0", + "web3-core": "1.8.2", + "web3-core-helpers": "1.8.2", + "web3-core-method": "1.8.2", + "web3-core-promievent": "1.8.2", + "web3-core-subscriptions": "1.8.2", + "web3-eth-abi": "1.8.2", + "web3-utils": "1.8.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@truffle/contract/node_modules/web3-eth-ens": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-eth-ens/-/web3-eth-ens-1.8.2.tgz", + "integrity": "sha512-PWph7C/CnqdWuu1+SH4U4zdrK4t2HNt0I4XzPYFdv9ugE8EuojselioPQXsVGvjql+Nt3jDLvQvggPqlMbvwRw==", + "dev": true, + "dependencies": { + "content-hash": "^2.5.2", + "eth-ens-namehash": "2.0.8", + "web3-core": "1.8.2", + "web3-core-helpers": "1.8.2", + "web3-core-promievent": "1.8.2", + "web3-eth-abi": "1.8.2", + "web3-eth-contract": "1.8.2", + "web3-utils": "1.8.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@truffle/contract/node_modules/web3-eth-iban": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-eth-iban/-/web3-eth-iban-1.8.2.tgz", + "integrity": "sha512-h3vNblDWkWMuYx93Q27TAJz6lhzpP93EiC3+45D6xoz983p6si773vntoQ+H+5aZhwglBtoiBzdh7PSSOnP/xQ==", + "dev": true, + "dependencies": { + "bn.js": "^5.2.1", + "web3-utils": "1.8.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@truffle/contract/node_modules/web3-eth-iban/node_modules/bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", + "dev": true + }, + "node_modules/@truffle/contract/node_modules/web3-eth-personal": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-eth-personal/-/web3-eth-personal-1.8.2.tgz", + "integrity": "sha512-Vg4HfwCr7doiUF/RC+Jz0wT4+cYaXcOWMAW2AHIjHX6Z7Xwa8nrURIeQgeEE62qcEHAzajyAdB1u6bJyTfuCXw==", + "dev": true, + "dependencies": { + "@types/node": "^12.12.6", + "web3-core": "1.8.2", + "web3-core-helpers": "1.8.2", + "web3-core-method": "1.8.2", + "web3-net": "1.8.2", + "web3-utils": "1.8.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@truffle/contract/node_modules/web3-net": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-net/-/web3-net-1.8.2.tgz", + "integrity": "sha512-1itkDMGmbgb83Dg9nporFes9/fxsU7smJ3oRXlFkg4ZHn8YJyP1MSQFPJWWwSc+GrcCFt4O5IrUTvEkHqE3xag==", + "dev": true, + "dependencies": { + "web3-core": "1.8.2", + "web3-core-method": "1.8.2", + "web3-utils": "1.8.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@truffle/contract/node_modules/web3-providers-http": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-providers-http/-/web3-providers-http-1.8.2.tgz", + "integrity": "sha512-2xY94IIEQd16+b+vIBF4IC1p7GVaz9q4EUFscvMUjtEq4ru4Atdzjs9GP+jmcoo49p70II0UV3bqQcz0TQfVyQ==", + "dev": true, + "dependencies": { + "abortcontroller-polyfill": "^1.7.3", + "cross-fetch": "^3.1.4", + "es6-promise": "^4.2.8", + "web3-core-helpers": "1.8.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@truffle/contract/node_modules/web3-providers-ipc": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-providers-ipc/-/web3-providers-ipc-1.8.2.tgz", + "integrity": "sha512-p6fqKVGFg+WiXGHWnB1hu43PbvPkDHTz4RgoEzbXugv5rtv5zfYLqm8Ba6lrJOS5ks9kGKR21a0y3NzE3u7V4w==", + "dev": true, + "dependencies": { + "oboe": "2.1.5", + "web3-core-helpers": "1.8.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@truffle/contract/node_modules/web3-providers-ws": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-providers-ws/-/web3-providers-ws-1.8.2.tgz", + "integrity": "sha512-3s/4K+wHgbiN+Zrp9YjMq2eqAF6QGABw7wFftPdx+m5hWImV27/MoIx57c6HffNRqZXmCHnfWWFCNHHsi7wXnA==", + "dev": true, + "dependencies": { + "eventemitter3": "4.0.4", + "web3-core-helpers": "1.8.2", + "websocket": "^1.0.32" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@truffle/contract/node_modules/web3-shh": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-shh/-/web3-shh-1.8.2.tgz", + "integrity": "sha512-uZ+3MAoNcaJsXXNCDnizKJ5viBNeHOFYsCbFhV755Uu52FswzTOw6DtE7yK9nYXMtIhiSgi7nwl1RYzP8pystw==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "web3-core": "1.8.2", + "web3-core-method": "1.8.2", + "web3-core-subscriptions": "1.8.2", + "web3-net": "1.8.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@truffle/contract/node_modules/web3-utils": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.8.2.tgz", + "integrity": "sha512-v7j6xhfLQfY7xQDrUP0BKbaNrmZ2/+egbqP9q3KYmOiPpnvAfol+32slgL0WX/5n8VPvKCK5EZ1HGrAVICSToA==", + "dev": true, + "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" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@truffle/contract/node_modules/web3-utils/node_modules/bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", + "dev": true + }, + "node_modules/@truffle/debug-utils": { + "version": "6.0.48", + "resolved": "https://registry.npmjs.org/@truffle/debug-utils/-/debug-utils-6.0.48.tgz", + "integrity": "sha512-HdK/7eH5EFrcTPeZVEgKaKkkzuZ4xsrH8yw+EoLEsScLsOEuQeKynY61NctjuU93voATWrYmV99Sfb/MRq2i2g==", + "dev": true, + "dependencies": { + "@truffle/codec": "^0.14.17", + "@trufflesuite/chromafi": "^3.0.0", + "bn.js": "^5.1.3", + "chalk": "^2.4.2", + "debug": "^4.3.1", + "highlightjs-solidity": "^2.0.6" + } + }, + "node_modules/@truffle/debug-utils/node_modules/bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", + "dev": true + }, + "node_modules/@truffle/error": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@truffle/error/-/error-0.1.1.tgz", + "integrity": "sha512-sE7c9IHIGdbK4YayH4BC8i8qMjoAOeg6nUXUDZZp8wlU21/EMpaG+CLx+KqcIPyR+GSWIW3Dm0PXkr2nlggFDA==", + "dev": true + }, + "node_modules/@truffle/interface-adapter": { + "version": "0.5.32", + "resolved": "https://registry.npmjs.org/@truffle/interface-adapter/-/interface-adapter-0.5.32.tgz", + "integrity": "sha512-7Hgmdb4nJUcWtq4vvgWY7Mr2RLOTOp5FZaWyMiFmjkcEEhDlezm2QstALWAXgT0W6q7tDmDBpw3vTIFenRhHBA==", + "dev": true, + "dependencies": { + "bn.js": "^5.1.3", + "ethers": "^4.0.32", + "web3": "1.8.2" + } + }, + "node_modules/@truffle/interface-adapter/node_modules/bignumber.js": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.1.tgz", + "integrity": "sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/@truffle/interface-adapter/node_modules/bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", + "dev": true + }, + "node_modules/@truffle/interface-adapter/node_modules/eth-lib": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/eth-lib/-/eth-lib-0.2.8.tgz", + "integrity": "sha512-ArJ7x1WcWOlSpzdoTBX8vkwlkSQ85CjjifSZtV4co64vWxSV8geWfPI9x4SVYu3DSxnX4yWFVTtGL+j9DUFLNw==", + "dev": true, + "dependencies": { + "bn.js": "^4.11.6", + "elliptic": "^6.4.0", + "xhr-request-promise": "^0.1.2" + } + }, + "node_modules/@truffle/interface-adapter/node_modules/eth-lib/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + }, + "node_modules/@truffle/interface-adapter/node_modules/uuid": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@truffle/interface-adapter/node_modules/web3": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3/-/web3-1.8.2.tgz", + "integrity": "sha512-92h0GdEHW9wqDICQQKyG4foZBYi0OQkyg4CRml2F7XBl/NG+fu9o6J19kzfFXzSBoA4DnJXbyRgj/RHZv5LRiw==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "web3-bzz": "1.8.2", + "web3-core": "1.8.2", + "web3-eth": "1.8.2", + "web3-eth-personal": "1.8.2", + "web3-net": "1.8.2", + "web3-shh": "1.8.2", + "web3-utils": "1.8.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@truffle/interface-adapter/node_modules/web3-bzz": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-bzz/-/web3-bzz-1.8.2.tgz", + "integrity": "sha512-1EEnxjPnFnvNWw3XeeKuTR8PBxYd0+XWzvaLK7OJC/Go9O8llLGxrxICbKV+8cgIE0sDRBxiYx02X+6OhoAQ9w==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "@types/node": "^12.12.6", + "got": "12.1.0", + "swarm-js": "^0.1.40" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@truffle/interface-adapter/node_modules/web3-core": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-core/-/web3-core-1.8.2.tgz", + "integrity": "sha512-DJTVEAYcNqxkqruJE+Rxp3CIv0y5AZMwPHQmOkz/cz+MM75SIzMTc0AUdXzGyTS8xMF8h3YWMQGgGEy8SBf1PQ==", + "dev": true, + "dependencies": { + "@types/bn.js": "^5.1.0", + "@types/node": "^12.12.6", + "bignumber.js": "^9.0.0", + "web3-core-helpers": "1.8.2", + "web3-core-method": "1.8.2", + "web3-core-requestmanager": "1.8.2", + "web3-utils": "1.8.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@truffle/interface-adapter/node_modules/web3-core-helpers": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-core-helpers/-/web3-core-helpers-1.8.2.tgz", + "integrity": "sha512-6B1eLlq9JFrfealZBomd1fmlq1o4A09vrCVQSa51ANoib/jllT3atZrRDr0zt1rfI7TSZTZBXdN/aTdeN99DWw==", + "dev": true, + "dependencies": { + "web3-eth-iban": "1.8.2", + "web3-utils": "1.8.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@truffle/interface-adapter/node_modules/web3-core-method": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-core-method/-/web3-core-method-1.8.2.tgz", + "integrity": "sha512-1qnr5mw5wVyULzLOrk4B+ryO3gfGjGd/fx8NR+J2xCGLf1e6OSjxT9vbfuQ3fErk/NjSTWWreieYWLMhaogcRA==", + "dev": true, + "dependencies": { + "@ethersproject/transactions": "^5.6.2", + "web3-core-helpers": "1.8.2", + "web3-core-promievent": "1.8.2", + "web3-core-subscriptions": "1.8.2", + "web3-utils": "1.8.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@truffle/interface-adapter/node_modules/web3-core-promievent": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-core-promievent/-/web3-core-promievent-1.8.2.tgz", + "integrity": "sha512-nvkJWDVgoOSsolJldN33tKW6bKKRJX3MCPDYMwP5SUFOA/mCzDEoI88N0JFofDTXkh1k7gOqp1pvwi9heuaxGg==", + "dev": true, + "dependencies": { + "eventemitter3": "4.0.4" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@truffle/interface-adapter/node_modules/web3-core-requestmanager": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-core-requestmanager/-/web3-core-requestmanager-1.8.2.tgz", + "integrity": "sha512-p1d090RYs5Mu7DK1yyc3GCBVZB/03rBtFhYFoS2EruGzOWs/5Q0grgtpwS/DScdRAm8wB8mYEBhY/RKJWF6B2g==", + "dev": true, + "dependencies": { + "util": "^0.12.5", + "web3-core-helpers": "1.8.2", + "web3-providers-http": "1.8.2", + "web3-providers-ipc": "1.8.2", + "web3-providers-ws": "1.8.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@truffle/interface-adapter/node_modules/web3-core-subscriptions": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-core-subscriptions/-/web3-core-subscriptions-1.8.2.tgz", + "integrity": "sha512-vXQogHDmAIQcKpXvGiMddBUeP9lnKgYF64+yQJhPNE5PnWr1sAibXuIPV7mIPihpFr/n/DORRj6Wh1pUv9zaTw==", + "dev": true, + "dependencies": { + "eventemitter3": "4.0.4", + "web3-core-helpers": "1.8.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@truffle/interface-adapter/node_modules/web3-eth": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-eth/-/web3-eth-1.8.2.tgz", + "integrity": "sha512-JoTiWWc4F4TInpbvDUGb0WgDYJsFhuIjJlinc5ByjWD88Gvh+GKLsRjjFdbqe5YtwIGT4NymwoC5LQd1K6u/QQ==", + "dev": true, + "dependencies": { + "web3-core": "1.8.2", + "web3-core-helpers": "1.8.2", + "web3-core-method": "1.8.2", + "web3-core-subscriptions": "1.8.2", + "web3-eth-abi": "1.8.2", + "web3-eth-accounts": "1.8.2", + "web3-eth-contract": "1.8.2", + "web3-eth-ens": "1.8.2", + "web3-eth-iban": "1.8.2", + "web3-eth-personal": "1.8.2", + "web3-net": "1.8.2", + "web3-utils": "1.8.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@truffle/interface-adapter/node_modules/web3-eth-abi": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-eth-abi/-/web3-eth-abi-1.8.2.tgz", + "integrity": "sha512-Om9g3kaRNjqiNPAgKwGT16y+ZwtBzRe4ZJFGjLiSs6v5I7TPNF+rRMWuKnR6jq0azQZDj6rblvKFMA49/k48Og==", + "dev": true, + "dependencies": { + "@ethersproject/abi": "^5.6.3", + "web3-utils": "1.8.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@truffle/interface-adapter/node_modules/web3-eth-accounts": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-eth-accounts/-/web3-eth-accounts-1.8.2.tgz", + "integrity": "sha512-c367Ij63VCz9YdyjiHHWLFtN85l6QghgwMQH2B1eM/p9Y5lTlTX7t/Eg/8+f1yoIStXbk2w/PYM2lk+IkbqdLA==", + "dev": true, + "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.8.2", + "web3-core-helpers": "1.8.2", + "web3-core-method": "1.8.2", + "web3-utils": "1.8.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@truffle/interface-adapter/node_modules/web3-eth-contract": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-eth-contract/-/web3-eth-contract-1.8.2.tgz", + "integrity": "sha512-ID5A25tHTSBNwOPjiXSVzxruz006ULRIDbzWTYIFTp7NJ7vXu/kynKK2ag/ObuTqBpMbobP8nXcA9b5EDkIdQA==", + "dev": true, + "dependencies": { + "@types/bn.js": "^5.1.0", + "web3-core": "1.8.2", + "web3-core-helpers": "1.8.2", + "web3-core-method": "1.8.2", + "web3-core-promievent": "1.8.2", + "web3-core-subscriptions": "1.8.2", + "web3-eth-abi": "1.8.2", + "web3-utils": "1.8.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@truffle/interface-adapter/node_modules/web3-eth-ens": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-eth-ens/-/web3-eth-ens-1.8.2.tgz", + "integrity": "sha512-PWph7C/CnqdWuu1+SH4U4zdrK4t2HNt0I4XzPYFdv9ugE8EuojselioPQXsVGvjql+Nt3jDLvQvggPqlMbvwRw==", + "dev": true, + "dependencies": { + "content-hash": "^2.5.2", + "eth-ens-namehash": "2.0.8", + "web3-core": "1.8.2", + "web3-core-helpers": "1.8.2", + "web3-core-promievent": "1.8.2", + "web3-eth-abi": "1.8.2", + "web3-eth-contract": "1.8.2", + "web3-utils": "1.8.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@truffle/interface-adapter/node_modules/web3-eth-iban": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-eth-iban/-/web3-eth-iban-1.8.2.tgz", + "integrity": "sha512-h3vNblDWkWMuYx93Q27TAJz6lhzpP93EiC3+45D6xoz983p6si773vntoQ+H+5aZhwglBtoiBzdh7PSSOnP/xQ==", + "dev": true, + "dependencies": { + "bn.js": "^5.2.1", + "web3-utils": "1.8.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@truffle/interface-adapter/node_modules/web3-eth-personal": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-eth-personal/-/web3-eth-personal-1.8.2.tgz", + "integrity": "sha512-Vg4HfwCr7doiUF/RC+Jz0wT4+cYaXcOWMAW2AHIjHX6Z7Xwa8nrURIeQgeEE62qcEHAzajyAdB1u6bJyTfuCXw==", + "dev": true, + "dependencies": { + "@types/node": "^12.12.6", + "web3-core": "1.8.2", + "web3-core-helpers": "1.8.2", + "web3-core-method": "1.8.2", + "web3-net": "1.8.2", + "web3-utils": "1.8.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@truffle/interface-adapter/node_modules/web3-net": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-net/-/web3-net-1.8.2.tgz", + "integrity": "sha512-1itkDMGmbgb83Dg9nporFes9/fxsU7smJ3oRXlFkg4ZHn8YJyP1MSQFPJWWwSc+GrcCFt4O5IrUTvEkHqE3xag==", + "dev": true, + "dependencies": { + "web3-core": "1.8.2", + "web3-core-method": "1.8.2", + "web3-utils": "1.8.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@truffle/interface-adapter/node_modules/web3-providers-http": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-providers-http/-/web3-providers-http-1.8.2.tgz", + "integrity": "sha512-2xY94IIEQd16+b+vIBF4IC1p7GVaz9q4EUFscvMUjtEq4ru4Atdzjs9GP+jmcoo49p70II0UV3bqQcz0TQfVyQ==", + "dev": true, + "dependencies": { + "abortcontroller-polyfill": "^1.7.3", + "cross-fetch": "^3.1.4", + "es6-promise": "^4.2.8", + "web3-core-helpers": "1.8.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@truffle/interface-adapter/node_modules/web3-providers-ipc": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-providers-ipc/-/web3-providers-ipc-1.8.2.tgz", + "integrity": "sha512-p6fqKVGFg+WiXGHWnB1hu43PbvPkDHTz4RgoEzbXugv5rtv5zfYLqm8Ba6lrJOS5ks9kGKR21a0y3NzE3u7V4w==", + "dev": true, + "dependencies": { + "oboe": "2.1.5", + "web3-core-helpers": "1.8.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@truffle/interface-adapter/node_modules/web3-providers-ws": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-providers-ws/-/web3-providers-ws-1.8.2.tgz", + "integrity": "sha512-3s/4K+wHgbiN+Zrp9YjMq2eqAF6QGABw7wFftPdx+m5hWImV27/MoIx57c6HffNRqZXmCHnfWWFCNHHsi7wXnA==", + "dev": true, + "dependencies": { + "eventemitter3": "4.0.4", + "web3-core-helpers": "1.8.2", + "websocket": "^1.0.32" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@truffle/interface-adapter/node_modules/web3-shh": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-shh/-/web3-shh-1.8.2.tgz", + "integrity": "sha512-uZ+3MAoNcaJsXXNCDnizKJ5viBNeHOFYsCbFhV755Uu52FswzTOw6DtE7yK9nYXMtIhiSgi7nwl1RYzP8pystw==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "web3-core": "1.8.2", + "web3-core-method": "1.8.2", + "web3-core-subscriptions": "1.8.2", + "web3-net": "1.8.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@truffle/interface-adapter/node_modules/web3-utils": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.8.2.tgz", + "integrity": "sha512-v7j6xhfLQfY7xQDrUP0BKbaNrmZ2/+egbqP9q3KYmOiPpnvAfol+32slgL0WX/5n8VPvKCK5EZ1HGrAVICSToA==", + "dev": true, + "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" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@trufflesuite/chromafi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@trufflesuite/chromafi/-/chromafi-3.0.0.tgz", + "integrity": "sha512-oqWcOqn8nT1bwlPPfidfzS55vqcIDdpfzo3HbU9EnUmcSTX+I8z0UyUFI3tZQjByVJulbzxHxUGS3ZJPwK/GPQ==", + "dev": true, + "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" + } + }, + "node_modules/@trufflesuite/chromafi/node_modules/detect-indent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-5.0.0.tgz", "integrity": "sha512-rlpvsxUtM0PQvy9iZe640/IWwWYyBsTApREbA1pHOpmOUIl9MkP/U4z7vTtg4Oaojvqhxt7sdufnT0EzGaR31g==", "dev": true, "engines": { "node": ">=4" } }, - "node_modules/@types/async-eventemitter": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@types/async-eventemitter/-/async-eventemitter-0.2.1.tgz", - "integrity": "sha512-M2P4Ng26QbAeITiH7w1d7OxtldgfAe0wobpyJzVK/XOb0cUGKU2R4pfAhqcJBXAe2ife5ZOhSv4wk7p+ffURtg==", - "dev": true - }, "node_modules/@types/bignumber.js": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/@types/bignumber.js/-/bignumber.js-5.0.0.tgz", @@ -2644,9 +3573,9 @@ } }, "node_modules/@types/chai": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.4.tgz", - "integrity": "sha512-KnRanxnpfpjUTqTCXslZSEdLfXExwgNxYPdiO2WGUj8+HDjFi8R3k5RVKPeSCzLjCcshCAtVO2QBbVuAV4kTnw==", + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.5.tgz", + "integrity": "sha512-mEo1sAde+UCE6b2hxn332f1g1E8WfYRu6p5SvTKr2ZKC1f7gFJXk4h5PyGP9Dt6gCaG8y8XhwnXWC6Iy2cmBng==", "dev": true }, "node_modules/@types/concat-stream": { @@ -2746,6 +3675,22 @@ "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", "dev": true }, + "node_modules/@types/readable-stream": { + "version": "2.3.15", + "resolved": "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-2.3.15.tgz", + "integrity": "sha512-oM5JSKQCcICF1wvGgmecmHldZ48OZamtMxcGGVICOJA8o8cahXC1zEVAif8iwoc5j8etxFaRFnf095+CDsuoFQ==", + "dev": true, + "dependencies": { + "@types/node": "*", + "safe-buffer": "~5.1.1" + } + }, + "node_modules/@types/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, "node_modules/@types/responselike": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", @@ -3171,22 +4116,10 @@ } }, "node_modules/async": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", - "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", - "dev": true, - "dependencies": { - "lodash": "^4.17.14" - } - }, - "node_modules/async-eventemitter": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/async-eventemitter/-/async-eventemitter-0.2.4.tgz", - "integrity": "sha512-pd20BwL7Yt1zwDFy+8MX8F1+WCT8aQeKj0kQnTrH9WaeRETlRamVhD0JtRPmrV4GfOJ2F9CvdQkZeZhnh2TuHw==", - "dev": true, - "dependencies": { - "async": "^2.4.0" - } + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha512-nSVgobk4rv61R9PUSDtYt7mPVB2olxNR5RWJcAsH676/ef11bUZwvu7+RGYrYauVdDPcO519v68wRhXQtxsV9w==", + "dev": true }, "node_modules/async-limiter": { "version": "1.0.1", @@ -3318,24 +4251,12 @@ } }, "node_modules/bigint-crypto-utils": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/bigint-crypto-utils/-/bigint-crypto-utils-3.1.8.tgz", - "integrity": "sha512-+VMV9Laq8pXLBKKKK49nOoq9bfR3j7NNQAtbA617a4nw9bVLo8rsqkKMBgM2AJWlNX9fEIyYaYX+d0laqYV4tw==", - "dev": true, - "dependencies": { - "bigint-mod-arith": "^3.1.0" - }, - "engines": { - "node": ">=10.4.0" - } - }, - "node_modules/bigint-mod-arith": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bigint-mod-arith/-/bigint-mod-arith-3.1.2.tgz", - "integrity": "sha512-nx8J8bBeiRR+NlsROFH9jHswW5HO8mgfOSqW0AmjicMMvaONDa8AO+5ViKDUUNytBPWiwfvZP4/Bj4Y3lUfvgQ==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/bigint-crypto-utils/-/bigint-crypto-utils-3.2.2.tgz", + "integrity": "sha512-U1RbE3aX9ayCUVcIPHuPDPKcK3SFOXf93J1UK/iHlJuQB7bhagPIX06/CLpLEsDThJ7KA4Dhrnzynl+d2weTiw==", "dev": true, "engines": { - "node": ">=10.4.0" + "node": ">=14.0.0" } }, "node_modules/bignumber.js": { @@ -3723,6 +4644,15 @@ "node": ">=6" } }, + "node_modules/case": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/case/-/case-1.6.3.tgz", + "integrity": "sha512-mzDSXIPaFwVDvZAHqZ9VlbyF4yyXRuX6IvB06WvPYkqJVO24kX1PPhv9bfpKNFZyxYFmmgo03HUiD8iklmJYRQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", @@ -3974,16 +4904,16 @@ "dev": true }, "node_modules/classic-level": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/classic-level/-/classic-level-1.2.0.tgz", - "integrity": "sha512-qw5B31ANxSluWz9xBzklRWTUAJ1SXIdaVKTVS7HcTGKOAmExx65Wo5BUICW+YGORe2FOUaDghoI9ZDxj82QcFg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/classic-level/-/classic-level-1.3.0.tgz", + "integrity": "sha512-iwFAJQYtqRTRM0F6L8h4JCt00ZSGdOyqh7yVrhhjrOpFhmBjNlRUey64MCiyo6UmQHMJ+No3c81nujPv+n9yrg==", "dev": true, "hasInstallScript": true, "dependencies": { "abstract-level": "^1.0.2", "catering": "^2.1.0", "module-error": "^1.0.1", - "napi-macros": "~2.0.0", + "napi-macros": "^2.2.2", "node-gyp-build": "^4.3.0" }, "engines": { @@ -4146,9 +5076,9 @@ "dev": true }, "node_modules/commander": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.0.tgz", - "integrity": "sha512-zS5PnTI22FIRM6ylNW8G4Ap0IEOyk62fhLSD0+uHRT9McRCLGpkVNvao4bjimpK/GShynyQkFFxHhwMcETmduA==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", "dev": true, "engines": { "node": ">=14" @@ -4288,9 +5218,9 @@ } }, "node_modules/cosmiconfig": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.1.0.tgz", - "integrity": "sha512-0tLZ9URlPGU7JsKq0DQOQ3FoRsYX8xDZ7xMiATQfaiGMz7EHowNkbU9u1coAOmnh9p/1ySpm0RB3JNWRXM5GCg==", + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.1.3.tgz", + "integrity": "sha512-/UkO2JKI18b5jVMJUp0lvKFMpa/Gye+ZgZjKD+DGEN9y7NRcf/nK1A0sp67ONmKtnDCNMS44E6jrk0Yc3bDuUw==", "dev": true, "dependencies": { "import-fresh": "^3.2.1", @@ -4816,14 +5746,14 @@ } }, "node_modules/domutils": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.0.1.tgz", - "integrity": "sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", "dev": true, "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", - "domhandler": "^5.0.1" + "domhandler": "^5.0.3" }, "funding": { "url": "https://github.com/fb55/domutils?sponsor=1" @@ -4915,9 +5845,9 @@ } }, "node_modules/entities": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz", - "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", "dev": true, "engines": { "node": ">=0.12" @@ -5200,15 +6130,15 @@ } }, "node_modules/eslint": { - "version": "8.36.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.36.0.tgz", - "integrity": "sha512-Y956lmS7vDqomxlaaQAHVmeb4tNMp2FWIvU/RnU5BD3IKMD/MJPr76xdyr68P8tV1iNMvN2mRK0yy3c+UjL+bw==", + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.39.0.tgz", + "integrity": "sha512-mwiok6cy7KTW7rBpo05k6+p4YVZByLNjAZ/ACB9DRCu4YDRwjXI01tWHp6KAUWelsBetTxKK/2sHB0vdS8Z2Og==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.4.0", - "@eslint/eslintrc": "^2.0.1", - "@eslint/js": "8.36.0", + "@eslint/eslintrc": "^2.0.2", + "@eslint/js": "8.39.0", "@humanwhocodes/config-array": "^0.11.8", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", @@ -5218,9 +6148,9 @@ "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", - "eslint-visitor-keys": "^3.3.0", - "espree": "^9.5.0", + "eslint-scope": "^7.2.0", + "eslint-visitor-keys": "^3.4.0", + "espree": "^9.5.1", "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -5257,9 +6187,9 @@ } }, "node_modules/eslint-config-prettier": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.7.0.tgz", - "integrity": "sha512-HHVXLSlVUhMSmyW4ZzEuvjpwqamgmlfkutD53cYXLikh4pt/modINRcCIApJ84czDxM4GZInwUrromsDdTImTA==", + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.8.0.tgz", + "integrity": "sha512-wLbQiFre3tdGgpDv67NQKnJuTlcUVYHas3k+DZCc2U2BadthoEY4B7hLPvAxaqdyOGCzuLfii2fqGph10va7oA==", "dev": true, "bin": { "eslint-config-prettier": "bin/cli.js" @@ -5269,9 +6199,9 @@ } }, "node_modules/eslint-scope": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", - "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz", + "integrity": "sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==", "dev": true, "dependencies": { "esrecurse": "^4.3.0", @@ -5279,15 +6209,21 @@ }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz", + "integrity": "sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/eslint/node_modules/ansi-regex": { @@ -5470,14 +6406,14 @@ } }, "node_modules/espree": { - "version": "9.5.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.0.tgz", - "integrity": "sha512-JPbJGhKc47++oo4JkEoTe2wjy4fmMwvFpgJT9cQzmfXKp22Dr6Hf1tdCteLz1h0P3t+mGvWZ+4Uankvh8+c6zw==", + "version": "9.5.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.1.tgz", + "integrity": "sha512-5yxtHSZXRSW5pvv3hAlXM5+/Oswi1AUFqBmbibKb5s6bp3rGIDkyXU6xCoyuuLhijr4SFwPrXRoZjz0AZDN9tg==", "dev": true, "dependencies": { "acorn": "^8.8.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.3.0" + "eslint-visitor-keys": "^3.4.0" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -7165,9 +8101,9 @@ } }, "node_modules/graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true }, "node_modules/grapheme-splitter": { @@ -7257,23 +8193,23 @@ } }, "node_modules/hardhat": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.13.0.tgz", - "integrity": "sha512-ZlzBOLML1QGlm6JWyVAG8lVTEAoOaVm1in/RU2zoGAnYEoD1Rp4T+ZMvrLNhHaaeS9hfjJ1gJUBfiDr4cx+htQ==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.14.0.tgz", + "integrity": "sha512-73jsInY4zZahMSVFurSK+5TNCJTXMv+vemvGia0Ac34Mm19fYp6vEPVGF3sucbumszsYxiTT2TbS8Ii2dsDSoQ==", "dev": true, "dependencies": { "@ethersproject/abi": "^5.1.2", "@metamask/eth-sig-util": "^4.0.0", - "@nomicfoundation/ethereumjs-block": "^4.0.0", - "@nomicfoundation/ethereumjs-blockchain": "^6.0.0", - "@nomicfoundation/ethereumjs-common": "^3.0.0", - "@nomicfoundation/ethereumjs-evm": "^1.0.0", - "@nomicfoundation/ethereumjs-rlp": "^4.0.0", - "@nomicfoundation/ethereumjs-statemanager": "^1.0.0", - "@nomicfoundation/ethereumjs-trie": "^5.0.0", - "@nomicfoundation/ethereumjs-tx": "^4.0.0", - "@nomicfoundation/ethereumjs-util": "^8.0.0", - "@nomicfoundation/ethereumjs-vm": "^6.0.0", + "@nomicfoundation/ethereumjs-block": "5.0.1", + "@nomicfoundation/ethereumjs-blockchain": "7.0.1", + "@nomicfoundation/ethereumjs-common": "4.0.1", + "@nomicfoundation/ethereumjs-evm": "2.0.1", + "@nomicfoundation/ethereumjs-rlp": "5.0.1", + "@nomicfoundation/ethereumjs-statemanager": "2.0.1", + "@nomicfoundation/ethereumjs-trie": "6.0.1", + "@nomicfoundation/ethereumjs-tx": "5.0.1", + "@nomicfoundation/ethereumjs-util": "9.0.1", + "@nomicfoundation/ethereumjs-vm": "7.0.1", "@nomicfoundation/solidity-analyzer": "^0.1.0", "@sentry/node": "^5.18.1", "@types/bn.js": "^5.1.0", @@ -7749,9 +8685,9 @@ "dev": true }, "node_modules/htmlparser2": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.1.tgz", - "integrity": "sha512-4lVbmc1diZC7GUJQtRQ5yBAeUCL1exyMwmForWkRLnwyzWBFxN633SALPMGYaWZvKe9j1pRZJpauvmxENSp/EA==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", + "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", "dev": true, "funding": [ "https://github.com/fb55/htmlparser2?sponsor=1", @@ -7762,9 +8698,9 @@ ], "dependencies": { "domelementtype": "^2.3.0", - "domhandler": "^5.0.2", + "domhandler": "^5.0.3", "domutils": "^3.0.1", - "entities": "^4.3.0" + "entities": "^4.4.0" } }, "node_modules/http-basic": { @@ -8491,9 +9427,9 @@ "dev": true }, "node_modules/js-sdsl": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.3.0.tgz", - "integrity": "sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.0.tgz", + "integrity": "sha512-FfVSdx6pJ41Oa+CF7RDaFmTnCaFhua+SNYQX74riGOpl96x+2jQCqEfQ2bnXu/5DPCqlRuiqyvTJM0Qjz26IVg==", "dev": true, "funding": { "type": "opencollective", @@ -9337,9 +10273,9 @@ } }, "node_modules/mixme": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mixme/-/mixme-0.5.5.tgz", - "integrity": "sha512-/6IupbRx32s7jjEwHcycXikJwFD5UujbVNuJFkeKLYje+92OvtuPniF6JhnFm5JCTDUhS+kYK3W/4BWYQYXz7w==", + "version": "0.5.9", + "resolved": "https://registry.npmjs.org/mixme/-/mixme-0.5.9.tgz", + "integrity": "sha512-VC5fg6ySUscaWUpI4gxCBTQMH2RdUpNrk+MsbpCYtIvf9SBJdiUey4qE7BXviJsJR4nDQxCZ+3yaYNW3guz/Pw==", "dev": true, "engines": { "node": ">= 8.0.0" @@ -9758,9 +10694,9 @@ } }, "node_modules/napi-macros": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/napi-macros/-/napi-macros-2.0.0.tgz", - "integrity": "sha512-A0xLykHtARfueITVDernsAWdtIMbOJgKgcluwENp3AlsKN/PloyO10HtmoqnFAQAcxPkgZN7wdfPfEd0zNGxbg==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/napi-macros/-/napi-macros-2.2.2.tgz", + "integrity": "sha512-hmEVtAGYzVQpCKdbQea4skABsdXW4RUh5t5mJ2zzqowJS2OyXZTU1KhDVFhx+NlWZ4ap9mqR9TcDO3LTTttd+g==", "dev": true }, "node_modules/natural-compare": { @@ -10035,15 +10971,16 @@ } }, "node_modules/object.getownpropertydescriptors": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.5.tgz", - "integrity": "sha512-yDNzckpM6ntyQiGTik1fKV1DcVDRS+w8bvpWNCBanvH5LfRX9O8WTHqQzG4RZwRAM4I0oU7TV11Lj5v0g20ibw==", + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.6.tgz", + "integrity": "sha512-lq+61g26E/BgHv0ZTFgRvi7NMEPuAxLkFU7rukXjc/AlwH4Am5xXVnIXy3un1bg/JPbXHrixRkK1itUzzPiIjQ==", "dev": true, "dependencies": { "array.prototype.reduce": "^1.0.5", "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" + "define-properties": "^1.2.0", + "es-abstract": "^1.21.2", + "safe-array-concat": "^1.0.0" }, "engines": { "node": ">= 0.8" @@ -10551,9 +11488,9 @@ } }, "node_modules/prettier": { - "version": "2.8.4", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.4.tgz", - "integrity": "sha512-vIS4Rlc2FNh0BySk3Wkd6xmwxB0FpOndW5fisM5H8hsZSxU2VWVB5CWIkIjWvrHjIhxk2g3bfMKM87zNTrZddw==", + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", "dev": true, "bin": { "prettier": "bin-prettier.js" @@ -10924,14 +11861,14 @@ "dev": true }, "node_modules/regexp.prototype.flags": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", - "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz", + "integrity": "sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==", "dev": true, "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "functions-have-names": "^1.2.2" + "define-properties": "^1.2.0", + "functions-have-names": "^1.2.3" }, "engines": { "node": ">= 0.4" @@ -11272,6 +12209,30 @@ "integrity": "sha512-4VlvkRUuCJvr2J6Y0ImW7NvTCriMi7ErOAqWk1y69vAdoNIzCF3yPmgeNzx+RQTLEDFq5sHfscn1MwHxP9hNfA==", "dev": true }, + "node_modules/safe-array-concat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.0.tgz", + "integrity": "sha512-9dVEFruWIsnie89yym+xWTAYASdpw3CJV7Li/6zBewGf9z2i1j31rP6jnY0pHEO4QZh6N0K11bFjWmdR8UGdPQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.0", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-array-concat/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -11337,12 +12298,6 @@ "istanbul": "lib/cli.js" } }, - "node_modules/sc-istanbul/node_modules/async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha512-nSVgobk4rv61R9PUSDtYt7mPVB2olxNR5RWJcAsH676/ef11bUZwvu7+RGYrYauVdDPcO519v68wRhXQtxsV9w==", - "dev": true - }, "node_modules/sc-istanbul/node_modules/esprima": { "version": "2.7.3", "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", @@ -11433,9 +12388,9 @@ } }, "node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -13963,18 +14918,18 @@ "dev": true }, "node_modules/tty-table": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/tty-table/-/tty-table-4.1.6.tgz", - "integrity": "sha512-kRj5CBzOrakV4VRRY5kUWbNYvo/FpOsz65DzI5op9P+cHov3+IqPbo1JE1ZnQGkHdZgNFDsrEjrfqqy/Ply9fw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/tty-table/-/tty-table-4.2.1.tgz", + "integrity": "sha512-xz0uKo+KakCQ+Dxj1D/tKn2FSyreSYWzdkL/BYhgN6oMW808g8QRMuh1atAV9fjTPbWBjfbkKQpI/5rEcnAc7g==", "dev": true, "dependencies": { "chalk": "^4.1.2", - "csv": "^5.5.0", - "kleur": "^4.1.4", + "csv": "^5.5.3", + "kleur": "^4.1.5", "smartwrap": "^2.0.2", - "strip-ansi": "^6.0.0", + "strip-ansi": "^6.0.1", "wcwidth": "^1.0.1", - "yargs": "^17.1.1" + "yargs": "^17.7.1" }, "bin": { "tty-table": "adapters/terminal-adapter.js" @@ -14220,15 +15175,15 @@ "dev": true }, "node_modules/undici": { - "version": "5.21.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.21.0.tgz", - "integrity": "sha512-HOjK8l6a57b2ZGXOcUsI5NLfoTrfmbOl90ixJDl0AEFG4wgHNDQxtZy15/ZQp7HhjkpaGlp/eneMgtsu1dIlUA==", + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.22.0.tgz", + "integrity": "sha512-fR9RXCc+6Dxav4P9VV/sp5w3eFiSdOjJYsbtWfd4s5L5C4ogyuVpdKIVHeW0vV1MloM65/f7W45nR9ZxwVdyiA==", "dev": true, "dependencies": { "busboy": "^1.6.0" }, "engines": { - "node": ">=12.18" + "node": ">=14.0" } }, "node_modules/universalify": { @@ -14384,28 +15339,28 @@ } }, "node_modules/web3": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3/-/web3-1.8.2.tgz", - "integrity": "sha512-92h0GdEHW9wqDICQQKyG4foZBYi0OQkyg4CRml2F7XBl/NG+fu9o6J19kzfFXzSBoA4DnJXbyRgj/RHZv5LRiw==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/web3/-/web3-1.9.0.tgz", + "integrity": "sha512-E9IvVy/d2ozfQQsCiV+zh/LmlZGv9fQxI0UedDVjm87yOKf4AYbBNEn1iWtHveiGzAk2CEMZMUzAZzaQNSSYog==", "dev": true, "hasInstallScript": true, "dependencies": { - "web3-bzz": "1.8.2", - "web3-core": "1.8.2", - "web3-eth": "1.8.2", - "web3-eth-personal": "1.8.2", - "web3-net": "1.8.2", - "web3-shh": "1.8.2", - "web3-utils": "1.8.2" + "web3-bzz": "1.9.0", + "web3-core": "1.9.0", + "web3-eth": "1.9.0", + "web3-eth-personal": "1.9.0", + "web3-net": "1.9.0", + "web3-shh": "1.9.0", + "web3-utils": "1.9.0" }, "engines": { "node": ">=8.0.0" } }, "node_modules/web3-bzz": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-bzz/-/web3-bzz-1.8.2.tgz", - "integrity": "sha512-1EEnxjPnFnvNWw3XeeKuTR8PBxYd0+XWzvaLK7OJC/Go9O8llLGxrxICbKV+8cgIE0sDRBxiYx02X+6OhoAQ9w==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/web3-bzz/-/web3-bzz-1.9.0.tgz", + "integrity": "sha512-9Zli9dikX8GdHwBb5/WPzpSVuy3EWMKY3P4EokCQra31fD7DLizqAAaTUsFwnK7xYkw5ogpHgelw9uKHHzNajg==", "dev": true, "hasInstallScript": true, "dependencies": { @@ -14418,56 +15373,56 @@ } }, "node_modules/web3-core": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-core/-/web3-core-1.8.2.tgz", - "integrity": "sha512-DJTVEAYcNqxkqruJE+Rxp3CIv0y5AZMwPHQmOkz/cz+MM75SIzMTc0AUdXzGyTS8xMF8h3YWMQGgGEy8SBf1PQ==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/web3-core/-/web3-core-1.9.0.tgz", + "integrity": "sha512-DZ+TPmq/ZLlx4LSVzFgrHCP/QFpKDbGWO4HoquZSdu24cjk5SZ+FEU1SZB2OaK3/bgBh+25mRbmv8y56ysUu1w==", "dev": true, "dependencies": { - "@types/bn.js": "^5.1.0", + "@types/bn.js": "^5.1.1", "@types/node": "^12.12.6", "bignumber.js": "^9.0.0", - "web3-core-helpers": "1.8.2", - "web3-core-method": "1.8.2", - "web3-core-requestmanager": "1.8.2", - "web3-utils": "1.8.2" + "web3-core-helpers": "1.9.0", + "web3-core-method": "1.9.0", + "web3-core-requestmanager": "1.9.0", + "web3-utils": "1.9.0" }, "engines": { "node": ">=8.0.0" } }, "node_modules/web3-core-helpers": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-core-helpers/-/web3-core-helpers-1.8.2.tgz", - "integrity": "sha512-6B1eLlq9JFrfealZBomd1fmlq1o4A09vrCVQSa51ANoib/jllT3atZrRDr0zt1rfI7TSZTZBXdN/aTdeN99DWw==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/web3-core-helpers/-/web3-core-helpers-1.9.0.tgz", + "integrity": "sha512-NeJzylAp9Yj9xAt2uTT+kyug3X0DLnfBdnAcGZuY6HhoNPDIfQRA9CkJjLngVRlGTLZGjNp9x9eR+RyZQgUlXg==", "dev": true, "dependencies": { - "web3-eth-iban": "1.8.2", - "web3-utils": "1.8.2" + "web3-eth-iban": "1.9.0", + "web3-utils": "1.9.0" }, "engines": { "node": ">=8.0.0" } }, "node_modules/web3-core-method": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-core-method/-/web3-core-method-1.8.2.tgz", - "integrity": "sha512-1qnr5mw5wVyULzLOrk4B+ryO3gfGjGd/fx8NR+J2xCGLf1e6OSjxT9vbfuQ3fErk/NjSTWWreieYWLMhaogcRA==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/web3-core-method/-/web3-core-method-1.9.0.tgz", + "integrity": "sha512-sswbNsY2xRBBhGeaLt9c/eDc+0yDDhi6keUBAkgIRa9ueSx/VKzUY9HMqiV6bXDcGT2fJyejq74FfEB4lc/+/w==", "dev": true, "dependencies": { "@ethersproject/transactions": "^5.6.2", - "web3-core-helpers": "1.8.2", - "web3-core-promievent": "1.8.2", - "web3-core-subscriptions": "1.8.2", - "web3-utils": "1.8.2" + "web3-core-helpers": "1.9.0", + "web3-core-promievent": "1.9.0", + "web3-core-subscriptions": "1.9.0", + "web3-utils": "1.9.0" }, "engines": { "node": ">=8.0.0" } }, "node_modules/web3-core-promievent": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-core-promievent/-/web3-core-promievent-1.8.2.tgz", - "integrity": "sha512-nvkJWDVgoOSsolJldN33tKW6bKKRJX3MCPDYMwP5SUFOA/mCzDEoI88N0JFofDTXkh1k7gOqp1pvwi9heuaxGg==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/web3-core-promievent/-/web3-core-promievent-1.9.0.tgz", + "integrity": "sha512-PHG1Mn23IGwMZhnPDN8dETKypqsFbHfiyRqP+XsVMPmTHkVfzDQTCBU/c2r6hUktBDoGKut5xZQpGfhFk71KbQ==", "dev": true, "dependencies": { "eventemitter3": "4.0.4" @@ -14477,29 +15432,29 @@ } }, "node_modules/web3-core-requestmanager": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-core-requestmanager/-/web3-core-requestmanager-1.8.2.tgz", - "integrity": "sha512-p1d090RYs5Mu7DK1yyc3GCBVZB/03rBtFhYFoS2EruGzOWs/5Q0grgtpwS/DScdRAm8wB8mYEBhY/RKJWF6B2g==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/web3-core-requestmanager/-/web3-core-requestmanager-1.9.0.tgz", + "integrity": "sha512-hcJ5PCtTIJpj+8qWxoseqlCovDo94JJjTX7dZOLXgwp8ah7E3WRYozhGyZocerx+KebKyg1mCQIhkDpMwjfo9Q==", "dev": true, "dependencies": { "util": "^0.12.5", - "web3-core-helpers": "1.8.2", - "web3-providers-http": "1.8.2", - "web3-providers-ipc": "1.8.2", - "web3-providers-ws": "1.8.2" + "web3-core-helpers": "1.9.0", + "web3-providers-http": "1.9.0", + "web3-providers-ipc": "1.9.0", + "web3-providers-ws": "1.9.0" }, "engines": { "node": ">=8.0.0" } }, "node_modules/web3-core-subscriptions": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-core-subscriptions/-/web3-core-subscriptions-1.8.2.tgz", - "integrity": "sha512-vXQogHDmAIQcKpXvGiMddBUeP9lnKgYF64+yQJhPNE5PnWr1sAibXuIPV7mIPihpFr/n/DORRj6Wh1pUv9zaTw==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/web3-core-subscriptions/-/web3-core-subscriptions-1.9.0.tgz", + "integrity": "sha512-MaIo29yz7hTV8X8bioclPDbHFOVuHmnbMv+D3PDH12ceJFJAXGyW8GL5KU1DYyWIj4TD1HM4WknyVA/YWBiiLA==", "dev": true, "dependencies": { "eventemitter3": "4.0.4", - "web3-core-helpers": "1.8.2" + "web3-core-helpers": "1.9.0" }, "engines": { "node": ">=8.0.0" @@ -14515,45 +15470,45 @@ } }, "node_modules/web3-eth": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-eth/-/web3-eth-1.8.2.tgz", - "integrity": "sha512-JoTiWWc4F4TInpbvDUGb0WgDYJsFhuIjJlinc5ByjWD88Gvh+GKLsRjjFdbqe5YtwIGT4NymwoC5LQd1K6u/QQ==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/web3-eth/-/web3-eth-1.9.0.tgz", + "integrity": "sha512-c5gSWk9bLNr6VPATHmZ1n7LTIefIZQnJMzfnvkoBcIFGKJbGmsuRhv6lEXsKdAO/FlqYnSbaw3fOq1fVFiIOFQ==", "dev": true, "dependencies": { - "web3-core": "1.8.2", - "web3-core-helpers": "1.8.2", - "web3-core-method": "1.8.2", - "web3-core-subscriptions": "1.8.2", - "web3-eth-abi": "1.8.2", - "web3-eth-accounts": "1.8.2", - "web3-eth-contract": "1.8.2", - "web3-eth-ens": "1.8.2", - "web3-eth-iban": "1.8.2", - "web3-eth-personal": "1.8.2", - "web3-net": "1.8.2", - "web3-utils": "1.8.2" + "web3-core": "1.9.0", + "web3-core-helpers": "1.9.0", + "web3-core-method": "1.9.0", + "web3-core-subscriptions": "1.9.0", + "web3-eth-abi": "1.9.0", + "web3-eth-accounts": "1.9.0", + "web3-eth-contract": "1.9.0", + "web3-eth-ens": "1.9.0", + "web3-eth-iban": "1.9.0", + "web3-eth-personal": "1.9.0", + "web3-net": "1.9.0", + "web3-utils": "1.9.0" }, "engines": { "node": ">=8.0.0" } }, "node_modules/web3-eth-abi": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-eth-abi/-/web3-eth-abi-1.8.2.tgz", - "integrity": "sha512-Om9g3kaRNjqiNPAgKwGT16y+ZwtBzRe4ZJFGjLiSs6v5I7TPNF+rRMWuKnR6jq0azQZDj6rblvKFMA49/k48Og==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/web3-eth-abi/-/web3-eth-abi-1.9.0.tgz", + "integrity": "sha512-0BLQ3FKMrzJkA930jOX3fMaybAyubk06HChclLpiR0NWmgWXm1tmBrJdkyRy2ZTZpmfuZc9xTFRfl0yZID1voA==", "dev": true, "dependencies": { "@ethersproject/abi": "^5.6.3", - "web3-utils": "1.8.2" + "web3-utils": "1.9.0" }, "engines": { "node": ">=8.0.0" } }, "node_modules/web3-eth-accounts": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-eth-accounts/-/web3-eth-accounts-1.8.2.tgz", - "integrity": "sha512-c367Ij63VCz9YdyjiHHWLFtN85l6QghgwMQH2B1eM/p9Y5lTlTX7t/Eg/8+f1yoIStXbk2w/PYM2lk+IkbqdLA==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/web3-eth-accounts/-/web3-eth-accounts-1.9.0.tgz", + "integrity": "sha512-VeIZVevmnSll0AC1k5F/y398ZE89d1SRuYk8IewLUhL/tVAsFEsjl2SGgm0+aDcHmgPrkW+qsCJ+C7rWg/N4ZA==", "dev": true, "dependencies": { "@ethereumjs/common": "2.5.0", @@ -14562,10 +15517,10 @@ "ethereumjs-util": "^7.1.5", "scrypt-js": "^3.0.1", "uuid": "^9.0.0", - "web3-core": "1.8.2", - "web3-core-helpers": "1.8.2", - "web3-core-method": "1.8.2", - "web3-utils": "1.8.2" + "web3-core": "1.9.0", + "web3-core-helpers": "1.9.0", + "web3-core-method": "1.9.0", + "web3-utils": "1.9.0" }, "engines": { "node": ">=8.0.0" @@ -14592,51 +15547,51 @@ } }, "node_modules/web3-eth-contract": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-eth-contract/-/web3-eth-contract-1.8.2.tgz", - "integrity": "sha512-ID5A25tHTSBNwOPjiXSVzxruz006ULRIDbzWTYIFTp7NJ7vXu/kynKK2ag/ObuTqBpMbobP8nXcA9b5EDkIdQA==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/web3-eth-contract/-/web3-eth-contract-1.9.0.tgz", + "integrity": "sha512-+j26hpSaEtAdUed0TN5rnc+YZOcjPxMjFX4ZBKatvFkImdbVv/tzTvcHlltubSpgb2ZLyZ89lSL6phKYwd2zNQ==", "dev": true, "dependencies": { - "@types/bn.js": "^5.1.0", - "web3-core": "1.8.2", - "web3-core-helpers": "1.8.2", - "web3-core-method": "1.8.2", - "web3-core-promievent": "1.8.2", - "web3-core-subscriptions": "1.8.2", - "web3-eth-abi": "1.8.2", - "web3-utils": "1.8.2" + "@types/bn.js": "^5.1.1", + "web3-core": "1.9.0", + "web3-core-helpers": "1.9.0", + "web3-core-method": "1.9.0", + "web3-core-promievent": "1.9.0", + "web3-core-subscriptions": "1.9.0", + "web3-eth-abi": "1.9.0", + "web3-utils": "1.9.0" }, "engines": { "node": ">=8.0.0" } }, "node_modules/web3-eth-ens": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-eth-ens/-/web3-eth-ens-1.8.2.tgz", - "integrity": "sha512-PWph7C/CnqdWuu1+SH4U4zdrK4t2HNt0I4XzPYFdv9ugE8EuojselioPQXsVGvjql+Nt3jDLvQvggPqlMbvwRw==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/web3-eth-ens/-/web3-eth-ens-1.9.0.tgz", + "integrity": "sha512-LOJZeN+AGe9arhuExnrPPFYQr4WSxXEkpvYIlst/joOEUNLDwfndHnJIK6PI5mXaYSROBtTx6erv+HupzGo7vA==", "dev": true, "dependencies": { "content-hash": "^2.5.2", "eth-ens-namehash": "2.0.8", - "web3-core": "1.8.2", - "web3-core-helpers": "1.8.2", - "web3-core-promievent": "1.8.2", - "web3-eth-abi": "1.8.2", - "web3-eth-contract": "1.8.2", - "web3-utils": "1.8.2" + "web3-core": "1.9.0", + "web3-core-helpers": "1.9.0", + "web3-core-promievent": "1.9.0", + "web3-eth-abi": "1.9.0", + "web3-eth-contract": "1.9.0", + "web3-utils": "1.9.0" }, "engines": { "node": ">=8.0.0" } }, "node_modules/web3-eth-iban": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-eth-iban/-/web3-eth-iban-1.8.2.tgz", - "integrity": "sha512-h3vNblDWkWMuYx93Q27TAJz6lhzpP93EiC3+45D6xoz983p6si773vntoQ+H+5aZhwglBtoiBzdh7PSSOnP/xQ==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/web3-eth-iban/-/web3-eth-iban-1.9.0.tgz", + "integrity": "sha512-jPAm77PuEs1kE/UrrBFJdPD2PN42pwfXA0gFuuw35bZezhskYML9W4QCxcqnUtceyEA4FUn7K2qTMuCk+23fog==", "dev": true, "dependencies": { "bn.js": "^5.2.1", - "web3-utils": "1.8.2" + "web3-utils": "1.9.0" }, "engines": { "node": ">=8.0.0" @@ -14649,72 +15604,72 @@ "dev": true }, "node_modules/web3-eth-personal": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-eth-personal/-/web3-eth-personal-1.8.2.tgz", - "integrity": "sha512-Vg4HfwCr7doiUF/RC+Jz0wT4+cYaXcOWMAW2AHIjHX6Z7Xwa8nrURIeQgeEE62qcEHAzajyAdB1u6bJyTfuCXw==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/web3-eth-personal/-/web3-eth-personal-1.9.0.tgz", + "integrity": "sha512-r9Ldo/luBqJlv1vCUEQnUS+C3a3ZdbYxVHyfDkj6RWMyCqqo8JE41HWE+pfa0RmB1xnGL2g8TbYcHcqItck/qg==", "dev": true, "dependencies": { "@types/node": "^12.12.6", - "web3-core": "1.8.2", - "web3-core-helpers": "1.8.2", - "web3-core-method": "1.8.2", - "web3-net": "1.8.2", - "web3-utils": "1.8.2" + "web3-core": "1.9.0", + "web3-core-helpers": "1.9.0", + "web3-core-method": "1.9.0", + "web3-net": "1.9.0", + "web3-utils": "1.9.0" }, "engines": { "node": ">=8.0.0" } }, "node_modules/web3-net": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-net/-/web3-net-1.8.2.tgz", - "integrity": "sha512-1itkDMGmbgb83Dg9nporFes9/fxsU7smJ3oRXlFkg4ZHn8YJyP1MSQFPJWWwSc+GrcCFt4O5IrUTvEkHqE3xag==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/web3-net/-/web3-net-1.9.0.tgz", + "integrity": "sha512-L+fDZFgrLM5Y15aonl2q6L+RvfaImAngmC0Jv45hV2FJ5IfRT0/2ob9etxZmvEBWvOpbqSvghfOhJIT3XZ37Pg==", "dev": true, "dependencies": { - "web3-core": "1.8.2", - "web3-core-method": "1.8.2", - "web3-utils": "1.8.2" + "web3-core": "1.9.0", + "web3-core-method": "1.9.0", + "web3-utils": "1.9.0" }, "engines": { "node": ">=8.0.0" } }, "node_modules/web3-providers-http": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-providers-http/-/web3-providers-http-1.8.2.tgz", - "integrity": "sha512-2xY94IIEQd16+b+vIBF4IC1p7GVaz9q4EUFscvMUjtEq4ru4Atdzjs9GP+jmcoo49p70II0UV3bqQcz0TQfVyQ==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/web3-providers-http/-/web3-providers-http-1.9.0.tgz", + "integrity": "sha512-5+dMNDAE0rRFz6SJpfnBqlVi2J5bB/Ivr2SanMt2YUrkxW5t8betZbzVwRkTbwtUvkqgj3xeUQzqpOttiv+IqQ==", "dev": true, "dependencies": { "abortcontroller-polyfill": "^1.7.3", "cross-fetch": "^3.1.4", "es6-promise": "^4.2.8", - "web3-core-helpers": "1.8.2" + "web3-core-helpers": "1.9.0" }, "engines": { "node": ">=8.0.0" } }, "node_modules/web3-providers-ipc": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-providers-ipc/-/web3-providers-ipc-1.8.2.tgz", - "integrity": "sha512-p6fqKVGFg+WiXGHWnB1hu43PbvPkDHTz4RgoEzbXugv5rtv5zfYLqm8Ba6lrJOS5ks9kGKR21a0y3NzE3u7V4w==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/web3-providers-ipc/-/web3-providers-ipc-1.9.0.tgz", + "integrity": "sha512-cPXU93Du40HCylvjaa5x62DbnGqH+86HpK/+kMcFIzF6sDUBhKpag2tSbYhGbj7GMpfkmDTUiiMLdWnFV6+uBA==", "dev": true, "dependencies": { "oboe": "2.1.5", - "web3-core-helpers": "1.8.2" + "web3-core-helpers": "1.9.0" }, "engines": { "node": ">=8.0.0" } }, "node_modules/web3-providers-ws": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-providers-ws/-/web3-providers-ws-1.8.2.tgz", - "integrity": "sha512-3s/4K+wHgbiN+Zrp9YjMq2eqAF6QGABw7wFftPdx+m5hWImV27/MoIx57c6HffNRqZXmCHnfWWFCNHHsi7wXnA==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/web3-providers-ws/-/web3-providers-ws-1.9.0.tgz", + "integrity": "sha512-JRVsnQZ7j2k1a2yzBNHe39xqk1ijOv01dfIBFw52VeEkSRzvrOcsPIM/ttSyBuJqt70ntMxXY0ekCrqfleKH/w==", "dev": true, "dependencies": { "eventemitter3": "4.0.4", - "web3-core-helpers": "1.8.2", + "web3-core-helpers": "1.9.0", "websocket": "^1.0.32" }, "engines": { @@ -14722,25 +15677,25 @@ } }, "node_modules/web3-shh": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-shh/-/web3-shh-1.8.2.tgz", - "integrity": "sha512-uZ+3MAoNcaJsXXNCDnizKJ5viBNeHOFYsCbFhV755Uu52FswzTOw6DtE7yK9nYXMtIhiSgi7nwl1RYzP8pystw==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/web3-shh/-/web3-shh-1.9.0.tgz", + "integrity": "sha512-bIBZlralgz4ICCrwkefB2nPPJWfx28NuHIpjB7d9ADKynElubQuqudYhKtSEkKXACuME/BJm0pIFJcJs/gDnMg==", "dev": true, "hasInstallScript": true, "dependencies": { - "web3-core": "1.8.2", - "web3-core-method": "1.8.2", - "web3-core-subscriptions": "1.8.2", - "web3-net": "1.8.2" + "web3-core": "1.9.0", + "web3-core-method": "1.9.0", + "web3-core-subscriptions": "1.9.0", + "web3-net": "1.9.0" }, "engines": { "node": ">=8.0.0" } }, "node_modules/web3-utils": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.8.2.tgz", - "integrity": "sha512-v7j6xhfLQfY7xQDrUP0BKbaNrmZ2/+egbqP9q3KYmOiPpnvAfol+32slgL0WX/5n8VPvKCK5EZ1HGrAVICSToA==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.9.0.tgz", + "integrity": "sha512-p++69rCNNfu2jM9n5+VD/g26l+qkEOQ1m6cfRQCbH8ZRrtquTmrirJMgTmyOoax5a5XRYOuws14aypCOs51pdQ==", "dev": true, "dependencies": { "bn.js": "^5.2.1", @@ -14841,9 +15796,9 @@ } }, "node_modules/which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", "dev": true }, "node_modules/which-pm": { @@ -15121,9 +16076,9 @@ "dev": true }, "node_modules/yargs": { - "version": "17.7.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", - "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, "dependencies": { "cliui": "^8.0.1", @@ -15276,9 +16231,9 @@ }, "dependencies": { "@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz", + "integrity": "sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==", "dev": true, "requires": { "@babel/highlight": "^7.18.6" @@ -15302,14 +16257,40 @@ } }, "@babel/runtime": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz", - "integrity": "sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==", + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.5.tgz", + "integrity": "sha512-8jI69toZqqcsnqGGqwGS4Qb1VwLOEp4hz+CXPywcvjs60u3B4Pom/U/7rm4W8tMOYEB+E9wgD0mW1l3r8qlI9Q==", "dev": true, "requires": { "regenerator-runtime": "^0.13.11" } }, + "@chainsafe/as-sha256": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@chainsafe/as-sha256/-/as-sha256-0.3.1.tgz", + "integrity": "sha512-hldFFYuf49ed7DAakWVXSJODuq3pzJEguD8tQ7h+sGkM18vja+OFoJI9krnGmgzyuZC2ETX0NOIcCTy31v2Mtg==", + "dev": true + }, + "@chainsafe/persistent-merkle-tree": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@chainsafe/persistent-merkle-tree/-/persistent-merkle-tree-0.4.2.tgz", + "integrity": "sha512-lLO3ihKPngXLTus/L7WHKaw9PnNJWizlOF1H9NNzHP6Xvh82vzg9F2bzkXhYIFshMZ2gTCEz8tq6STe7r5NDfQ==", + "dev": true, + "requires": { + "@chainsafe/as-sha256": "^0.3.1" + } + }, + "@chainsafe/ssz": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/@chainsafe/ssz/-/ssz-0.9.4.tgz", + "integrity": "sha512-77Qtg2N1ayqs4Bg/wvnWfg5Bta7iy7IRh8XqXh7oNMeP2HBbBwx8m6yTpA8p0EHItWPEBkgZd5S5/LSlp3GXuQ==", + "dev": true, + "requires": { + "@chainsafe/as-sha256": "^0.3.1", + "@chainsafe/persistent-merkle-tree": "^0.4.2", + "case": "^1.6.3" + } + }, "@changesets/apply-release-plan": { "version": "6.1.3", "resolved": "https://registry.npmjs.org/@changesets/apply-release-plan/-/apply-release-plan-6.1.3.tgz", @@ -15382,9 +16363,9 @@ } }, "@changesets/cli": { - "version": "2.26.0", - "resolved": "https://registry.npmjs.org/@changesets/cli/-/cli-2.26.0.tgz", - "integrity": "sha512-0cbTiDms+ICTVtEwAFLNW0jBNex9f5+fFv3I771nBvdnV/mOjd1QJ4+f8KtVSOrwD9SJkk9xbDkWFb0oXd8d1Q==", + "version": "2.26.1", + "resolved": "https://registry.npmjs.org/@changesets/cli/-/cli-2.26.1.tgz", + "integrity": "sha512-XnTa+b51vt057fyAudvDKGB0Sh72xutQZNAdXkCqPBKO2zvs2yYZx5hFZj1u9cbtpwM6Sxtcr02/FQJfZOzemQ==", "dev": true, "requires": { "@babel/runtime": "^7.20.1", @@ -15688,29 +16669,29 @@ "dev": true }, "@eslint-community/eslint-utils": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.2.0.tgz", - "integrity": "sha512-gB8T4H4DEfX2IV9zGDJPOBgP1e/DbfCPDTtEqUMckpvzS1OYtva8JdFYBqMwYk7xAQ429WGF/UPqn8uQ//h2vQ==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", "dev": true, "requires": { "eslint-visitor-keys": "^3.3.0" } }, "@eslint-community/regexpp": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.4.0.tgz", - "integrity": "sha512-A9983Q0LnDGdLPjxyXQ00sbV+K+O+ko2Dr+CZigbHWtX9pNfxlaBkMR8X1CztI73zuEyEBXTVjx7CE+/VSwDiQ==", + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.1.tgz", + "integrity": "sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==", "dev": true }, "@eslint/eslintrc": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.1.tgz", - "integrity": "sha512-eFRmABvW2E5Ho6f5fHLqgena46rOj7r7OKHYfLElqcBfGFHHpjBhivyi5+jOEQuSpdc/1phIZJlbC2te+tZNIw==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.2.tgz", + "integrity": "sha512-3W4f5tDUra+pA+FzgugqL2pRimUTDJWKr7BINqOpkZrC0uYI0NIc0/JFgBROCU07HR6GieA5m3/rsPIhDmCXTQ==", "dev": true, "requires": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.5.0", + "espree": "^9.5.1", "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", @@ -15737,9 +16718,9 @@ } }, "@eslint/js": { - "version": "8.36.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.36.0.tgz", - "integrity": "sha512-lxJ9R5ygVm8ZWgYdUweoq5ownDlJ4upvoWmO4eLxBYHdMo+vZ/Rx0EN6MbKWDJOSUGrqJy2Gt+Dyv/VKml0fjg==", + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.39.0.tgz", + "integrity": "sha512-kf9RB0Fg7NZfap83B3QOqOGg9QmD9yBudqQXzzOtn3i4y7ZUXe5ONeW34Gwi+TxhH4mvj72R1Zc300KUMa9Bng==", "dev": true }, "@ethereumjs/common": { @@ -16361,31 +17342,73 @@ } }, "@nomicfoundation/ethereumjs-block": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-block/-/ethereumjs-block-4.0.0.tgz", - "integrity": "sha512-bk8uP8VuexLgyIZAHExH1QEovqx0Lzhc9Ntm63nCRKLHXIZkobaFaeCVwTESV7YkPKUk7NiK11s8ryed4CS9yA==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-block/-/ethereumjs-block-5.0.1.tgz", + "integrity": "sha512-u1Yioemi6Ckj3xspygu/SfFvm8vZEO8/Yx5a1QLzi6nVU0jz3Pg2OmHKJ5w+D9Ogk1vhwRiqEBAqcb0GVhCyHw==", "dev": true, "requires": { - "@nomicfoundation/ethereumjs-common": "^3.0.0", - "@nomicfoundation/ethereumjs-rlp": "^4.0.0", - "@nomicfoundation/ethereumjs-trie": "^5.0.0", - "@nomicfoundation/ethereumjs-tx": "^4.0.0", - "@nomicfoundation/ethereumjs-util": "^8.0.0", - "ethereum-cryptography": "0.1.3" + "@nomicfoundation/ethereumjs-common": "4.0.1", + "@nomicfoundation/ethereumjs-rlp": "5.0.1", + "@nomicfoundation/ethereumjs-trie": "6.0.1", + "@nomicfoundation/ethereumjs-tx": "5.0.1", + "@nomicfoundation/ethereumjs-util": "9.0.1", + "ethereum-cryptography": "0.1.3", + "ethers": "^5.7.1" + }, + "dependencies": { + "ethers": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-5.7.2.tgz", + "integrity": "sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg==", + "dev": true, + "requires": { + "@ethersproject/abi": "5.7.0", + "@ethersproject/abstract-provider": "5.7.0", + "@ethersproject/abstract-signer": "5.7.0", + "@ethersproject/address": "5.7.0", + "@ethersproject/base64": "5.7.0", + "@ethersproject/basex": "5.7.0", + "@ethersproject/bignumber": "5.7.0", + "@ethersproject/bytes": "5.7.0", + "@ethersproject/constants": "5.7.0", + "@ethersproject/contracts": "5.7.0", + "@ethersproject/hash": "5.7.0", + "@ethersproject/hdnode": "5.7.0", + "@ethersproject/json-wallets": "5.7.0", + "@ethersproject/keccak256": "5.7.0", + "@ethersproject/logger": "5.7.0", + "@ethersproject/networks": "5.7.1", + "@ethersproject/pbkdf2": "5.7.0", + "@ethersproject/properties": "5.7.0", + "@ethersproject/providers": "5.7.2", + "@ethersproject/random": "5.7.0", + "@ethersproject/rlp": "5.7.0", + "@ethersproject/sha2": "5.7.0", + "@ethersproject/signing-key": "5.7.0", + "@ethersproject/solidity": "5.7.0", + "@ethersproject/strings": "5.7.0", + "@ethersproject/transactions": "5.7.0", + "@ethersproject/units": "5.7.0", + "@ethersproject/wallet": "5.7.0", + "@ethersproject/web": "5.7.1", + "@ethersproject/wordlists": "5.7.0" + } + } } }, "@nomicfoundation/ethereumjs-blockchain": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-blockchain/-/ethereumjs-blockchain-6.0.0.tgz", - "integrity": "sha512-pLFEoea6MWd81QQYSReLlLfH7N9v7lH66JC/NMPN848ySPPQA5renWnE7wPByfQFzNrPBuDDRFFULMDmj1C0xw==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-blockchain/-/ethereumjs-blockchain-7.0.1.tgz", + "integrity": "sha512-NhzndlGg829XXbqJEYrF1VeZhAwSPgsK/OB7TVrdzft3y918hW5KNd7gIZ85sn6peDZOdjBsAXIpXZ38oBYE5A==", "dev": true, "requires": { - "@nomicfoundation/ethereumjs-block": "^4.0.0", - "@nomicfoundation/ethereumjs-common": "^3.0.0", - "@nomicfoundation/ethereumjs-ethash": "^2.0.0", - "@nomicfoundation/ethereumjs-rlp": "^4.0.0", - "@nomicfoundation/ethereumjs-trie": "^5.0.0", - "@nomicfoundation/ethereumjs-util": "^8.0.0", + "@nomicfoundation/ethereumjs-block": "5.0.1", + "@nomicfoundation/ethereumjs-common": "4.0.1", + "@nomicfoundation/ethereumjs-ethash": "3.0.1", + "@nomicfoundation/ethereumjs-rlp": "5.0.1", + "@nomicfoundation/ethereumjs-trie": "6.0.1", + "@nomicfoundation/ethereumjs-tx": "5.0.1", + "@nomicfoundation/ethereumjs-util": "9.0.1", "abstract-level": "^1.0.3", "debug": "^4.3.3", "ethereum-cryptography": "0.1.3", @@ -16395,39 +17418,39 @@ } }, "@nomicfoundation/ethereumjs-common": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-common/-/ethereumjs-common-3.0.0.tgz", - "integrity": "sha512-WS7qSshQfxoZOpHG/XqlHEGRG1zmyjYrvmATvc4c62+gZXgre1ymYP8ZNgx/3FyZY0TWe9OjFlKOfLqmgOeYwA==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-common/-/ethereumjs-common-4.0.1.tgz", + "integrity": "sha512-OBErlkfp54GpeiE06brBW/TTbtbuBJV5YI5Nz/aB2evTDo+KawyEzPjBlSr84z/8MFfj8wS2wxzQX1o32cev5g==", "dev": true, "requires": { - "@nomicfoundation/ethereumjs-util": "^8.0.0", + "@nomicfoundation/ethereumjs-util": "9.0.1", "crc-32": "^1.2.0" } }, "@nomicfoundation/ethereumjs-ethash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-ethash/-/ethereumjs-ethash-2.0.0.tgz", - "integrity": "sha512-WpDvnRncfDUuXdsAXlI4lXbqUDOA+adYRQaEezIkxqDkc+LDyYDbd/xairmY98GnQzo1zIqsIL6GB5MoMSJDew==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-ethash/-/ethereumjs-ethash-3.0.1.tgz", + "integrity": "sha512-KDjGIB5igzWOp8Ik5I6QiRH5DH+XgILlplsHR7TEuWANZA759G6krQ6o8bvj+tRUz08YygMQu/sGd9mJ1DYT8w==", "dev": true, "requires": { - "@nomicfoundation/ethereumjs-block": "^4.0.0", - "@nomicfoundation/ethereumjs-rlp": "^4.0.0", - "@nomicfoundation/ethereumjs-util": "^8.0.0", + "@nomicfoundation/ethereumjs-block": "5.0.1", + "@nomicfoundation/ethereumjs-rlp": "5.0.1", + "@nomicfoundation/ethereumjs-util": "9.0.1", "abstract-level": "^1.0.3", "bigint-crypto-utils": "^3.0.23", "ethereum-cryptography": "0.1.3" } }, "@nomicfoundation/ethereumjs-evm": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-evm/-/ethereumjs-evm-1.0.0.tgz", - "integrity": "sha512-hVS6qRo3V1PLKCO210UfcEQHvlG7GqR8iFzp0yyjTg2TmJQizcChKgWo8KFsdMw6AyoLgLhHGHw4HdlP8a4i+Q==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-evm/-/ethereumjs-evm-2.0.1.tgz", + "integrity": "sha512-oL8vJcnk0Bx/onl+TgQOQ1t/534GKFaEG17fZmwtPFeH8S5soiBYPCLUrvANOl4sCp9elYxIMzIiTtMtNNN8EQ==", "dev": true, "requires": { - "@nomicfoundation/ethereumjs-common": "^3.0.0", - "@nomicfoundation/ethereumjs-util": "^8.0.0", - "@types/async-eventemitter": "^0.2.1", - "async-eventemitter": "^0.2.4", + "@ethersproject/providers": "^5.7.1", + "@nomicfoundation/ethereumjs-common": "4.0.1", + "@nomicfoundation/ethereumjs-tx": "5.0.1", + "@nomicfoundation/ethereumjs-util": "9.0.1", "debug": "^4.3.3", "ethereum-cryptography": "0.1.3", "mcl-wasm": "^0.7.1", @@ -16435,80 +17458,141 @@ } }, "@nomicfoundation/ethereumjs-rlp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-rlp/-/ethereumjs-rlp-4.0.0.tgz", - "integrity": "sha512-GaSOGk5QbUk4eBP5qFbpXoZoZUj/NrW7MRa0tKY4Ew4c2HAS0GXArEMAamtFrkazp0BO4K5p2ZCG3b2FmbShmw==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-rlp/-/ethereumjs-rlp-5.0.1.tgz", + "integrity": "sha512-xtxrMGa8kP4zF5ApBQBtjlSbN5E2HI8m8FYgVSYAnO6ssUoY5pVPGy2H8+xdf/bmMa22Ce8nWMH3aEW8CcqMeQ==", "dev": true }, "@nomicfoundation/ethereumjs-statemanager": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-statemanager/-/ethereumjs-statemanager-1.0.0.tgz", - "integrity": "sha512-jCtqFjcd2QejtuAMjQzbil/4NHf5aAWxUc+CvS0JclQpl+7M0bxMofR2AJdtz+P3u0ke2euhYREDiE7iSO31vQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-statemanager/-/ethereumjs-statemanager-2.0.1.tgz", + "integrity": "sha512-B5ApMOnlruVOR7gisBaYwFX+L/AP7i/2oAahatssjPIBVDF6wTX1K7Qpa39E/nzsH8iYuL3krkYeUFIdO3EMUQ==", "dev": true, "requires": { - "@nomicfoundation/ethereumjs-common": "^3.0.0", - "@nomicfoundation/ethereumjs-rlp": "^4.0.0", - "@nomicfoundation/ethereumjs-trie": "^5.0.0", - "@nomicfoundation/ethereumjs-util": "^8.0.0", + "@nomicfoundation/ethereumjs-common": "4.0.1", + "@nomicfoundation/ethereumjs-rlp": "5.0.1", "debug": "^4.3.3", "ethereum-cryptography": "0.1.3", - "functional-red-black-tree": "^1.0.1" + "ethers": "^5.7.1", + "js-sdsl": "^4.1.4" + }, + "dependencies": { + "ethers": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-5.7.2.tgz", + "integrity": "sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg==", + "dev": true, + "requires": { + "@ethersproject/abi": "5.7.0", + "@ethersproject/abstract-provider": "5.7.0", + "@ethersproject/abstract-signer": "5.7.0", + "@ethersproject/address": "5.7.0", + "@ethersproject/base64": "5.7.0", + "@ethersproject/basex": "5.7.0", + "@ethersproject/bignumber": "5.7.0", + "@ethersproject/bytes": "5.7.0", + "@ethersproject/constants": "5.7.0", + "@ethersproject/contracts": "5.7.0", + "@ethersproject/hash": "5.7.0", + "@ethersproject/hdnode": "5.7.0", + "@ethersproject/json-wallets": "5.7.0", + "@ethersproject/keccak256": "5.7.0", + "@ethersproject/logger": "5.7.0", + "@ethersproject/networks": "5.7.1", + "@ethersproject/pbkdf2": "5.7.0", + "@ethersproject/properties": "5.7.0", + "@ethersproject/providers": "5.7.2", + "@ethersproject/random": "5.7.0", + "@ethersproject/rlp": "5.7.0", + "@ethersproject/sha2": "5.7.0", + "@ethersproject/signing-key": "5.7.0", + "@ethersproject/solidity": "5.7.0", + "@ethersproject/strings": "5.7.0", + "@ethersproject/transactions": "5.7.0", + "@ethersproject/units": "5.7.0", + "@ethersproject/wallet": "5.7.0", + "@ethersproject/web": "5.7.1", + "@ethersproject/wordlists": "5.7.0" + } + } } }, "@nomicfoundation/ethereumjs-trie": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-trie/-/ethereumjs-trie-5.0.0.tgz", - "integrity": "sha512-LIj5XdE+s+t6WSuq/ttegJzZ1vliwg6wlb+Y9f4RlBpuK35B9K02bO7xU+E6Rgg9RGptkWd6TVLdedTI4eNc2A==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-trie/-/ethereumjs-trie-6.0.1.tgz", + "integrity": "sha512-A64It/IMpDVODzCgxDgAAla8jNjNtsoQZIzZUfIV5AY6Coi4nvn7+VReBn5itlxMiL2yaTlQr9TRWp3CSI6VoA==", "dev": true, "requires": { - "@nomicfoundation/ethereumjs-rlp": "^4.0.0", - "@nomicfoundation/ethereumjs-util": "^8.0.0", + "@nomicfoundation/ethereumjs-rlp": "5.0.1", + "@nomicfoundation/ethereumjs-util": "9.0.1", + "@types/readable-stream": "^2.3.13", "ethereum-cryptography": "0.1.3", "readable-stream": "^3.6.0" } }, "@nomicfoundation/ethereumjs-tx": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-tx/-/ethereumjs-tx-4.0.0.tgz", - "integrity": "sha512-Gg3Lir2lNUck43Kp/3x6TfBNwcWC9Z1wYue9Nz3v4xjdcv6oDW9QSMJxqsKw9QEGoBBZ+gqwpW7+F05/rs/g1w==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-tx/-/ethereumjs-tx-5.0.1.tgz", + "integrity": "sha512-0HwxUF2u2hrsIM1fsasjXvlbDOq1ZHFV2dd1yGq8CA+MEYhaxZr8OTScpVkkxqMwBcc5y83FyPl0J9MZn3kY0w==", "dev": true, "requires": { - "@nomicfoundation/ethereumjs-common": "^3.0.0", - "@nomicfoundation/ethereumjs-rlp": "^4.0.0", - "@nomicfoundation/ethereumjs-util": "^8.0.0", + "@chainsafe/ssz": "^0.9.2", + "@ethersproject/providers": "^5.7.2", + "@nomicfoundation/ethereumjs-common": "4.0.1", + "@nomicfoundation/ethereumjs-rlp": "5.0.1", + "@nomicfoundation/ethereumjs-util": "9.0.1", "ethereum-cryptography": "0.1.3" } }, "@nomicfoundation/ethereumjs-util": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-util/-/ethereumjs-util-8.0.0.tgz", - "integrity": "sha512-2emi0NJ/HmTG+CGY58fa+DQuAoroFeSH9gKu9O6JnwTtlzJtgfTixuoOqLEgyyzZVvwfIpRueuePb8TonL1y+A==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-util/-/ethereumjs-util-9.0.1.tgz", + "integrity": "sha512-TwbhOWQ8QoSCFhV/DDfSmyfFIHjPjFBj957219+V3jTZYZ2rf9PmDtNOeZWAE3p3vlp8xb02XGpd0v6nTUPbsA==", "dev": true, "requires": { - "@nomicfoundation/ethereumjs-rlp": "^4.0.0-beta.2", + "@chainsafe/ssz": "^0.10.0", + "@nomicfoundation/ethereumjs-rlp": "5.0.1", "ethereum-cryptography": "0.1.3" + }, + "dependencies": { + "@chainsafe/persistent-merkle-tree": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@chainsafe/persistent-merkle-tree/-/persistent-merkle-tree-0.5.0.tgz", + "integrity": "sha512-l0V1b5clxA3iwQLXP40zYjyZYospQLZXzBVIhhr9kDg/1qHZfzzHw0jj4VPBijfYCArZDlPkRi1wZaV2POKeuw==", + "dev": true, + "requires": { + "@chainsafe/as-sha256": "^0.3.1" + } + }, + "@chainsafe/ssz": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@chainsafe/ssz/-/ssz-0.10.2.tgz", + "integrity": "sha512-/NL3Lh8K+0q7A3LsiFq09YXS9fPE+ead2rr7vM2QK8PLzrNsw3uqrif9bpRX5UxgeRjM+vYi+boCM3+GM4ovXg==", + "dev": true, + "requires": { + "@chainsafe/as-sha256": "^0.3.1", + "@chainsafe/persistent-merkle-tree": "^0.5.0" + } + } } }, "@nomicfoundation/ethereumjs-vm": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-vm/-/ethereumjs-vm-6.0.0.tgz", - "integrity": "sha512-JMPxvPQ3fzD063Sg3Tp+UdwUkVxMoo1uML6KSzFhMH3hoQi/LMuXBoEHAoW83/vyNS9BxEe6jm6LmT5xdeEJ6w==", - "dev": true, - "requires": { - "@nomicfoundation/ethereumjs-block": "^4.0.0", - "@nomicfoundation/ethereumjs-blockchain": "^6.0.0", - "@nomicfoundation/ethereumjs-common": "^3.0.0", - "@nomicfoundation/ethereumjs-evm": "^1.0.0", - "@nomicfoundation/ethereumjs-rlp": "^4.0.0", - "@nomicfoundation/ethereumjs-statemanager": "^1.0.0", - "@nomicfoundation/ethereumjs-trie": "^5.0.0", - "@nomicfoundation/ethereumjs-tx": "^4.0.0", - "@nomicfoundation/ethereumjs-util": "^8.0.0", - "@types/async-eventemitter": "^0.2.1", - "async-eventemitter": "^0.2.4", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-vm/-/ethereumjs-vm-7.0.1.tgz", + "integrity": "sha512-rArhyn0jPsS/D+ApFsz3yVJMQ29+pVzNZ0VJgkzAZ+7FqXSRtThl1C1prhmlVr3YNUlfpZ69Ak+RUT4g7VoOuQ==", + "dev": true, + "requires": { + "@nomicfoundation/ethereumjs-block": "5.0.1", + "@nomicfoundation/ethereumjs-blockchain": "7.0.1", + "@nomicfoundation/ethereumjs-common": "4.0.1", + "@nomicfoundation/ethereumjs-evm": "2.0.1", + "@nomicfoundation/ethereumjs-rlp": "5.0.1", + "@nomicfoundation/ethereumjs-statemanager": "2.0.1", + "@nomicfoundation/ethereumjs-trie": "6.0.1", + "@nomicfoundation/ethereumjs-tx": "5.0.1", + "@nomicfoundation/ethereumjs-util": "9.0.1", "debug": "^4.3.3", "ethereum-cryptography": "0.1.3", - "functional-red-black-tree": "^1.0.1", "mcl-wasm": "^0.7.1", "rustbn.js": "~0.2.0" } @@ -16774,9 +17858,9 @@ } }, "@openzeppelin/upgrades-core": { - "version": "1.24.1", - "resolved": "https://registry.npmjs.org/@openzeppelin/upgrades-core/-/upgrades-core-1.24.1.tgz", - "integrity": "sha512-QhdIQDUykJ3vQauB6CheV7vk4zgn0e1iY+IDg7r1KqpA1m2bqIGjQCpzidW33K4bZc9zdJSPx2/Z6Um5KxCB7A==", + "version": "1.25.0", + "resolved": "https://registry.npmjs.org/@openzeppelin/upgrades-core/-/upgrades-core-1.25.0.tgz", + "integrity": "sha512-vSxOSm1k+P156nNm15ydhOmSPGC37mnl092FMVOH+eGaoqLjr2Za6ULVjDMFzvMnG+sGE1UoDOqBNPfTm0ch8w==", "dev": true, "requires": { "cbor": "^8.0.0", @@ -16980,18 +18064,41 @@ "change-case": "3.0.2", "fast-check": "3.1.1", "web3-utils": "1.8.2" + }, + "dependencies": { + "bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", + "dev": true + }, + "web3-utils": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.8.2.tgz", + "integrity": "sha512-v7j6xhfLQfY7xQDrUP0BKbaNrmZ2/+egbqP9q3KYmOiPpnvAfol+32slgL0WX/5n8VPvKCK5EZ1HGrAVICSToA==", + "dev": true, + "requires": { + "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" + } + } } }, "@truffle/blockchain-utils": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/@truffle/blockchain-utils/-/blockchain-utils-0.1.6.tgz", - "integrity": "sha512-SldoNRIFSm3+HMBnSc2jFsu5TWDkCN4X6vL3wrd0t6DIeF7nD6EoPPjxwbFSoqCnkkRxMuZeL6sUx7UMJS/wSA==", + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/@truffle/blockchain-utils/-/blockchain-utils-0.1.7.tgz", + "integrity": "sha512-1nibqGjEHC7KAyDThEFvbm2+EO8zAHee/VjCtxkYBE3ySwP50joh0QCEBjy7K/9z+icpMoDucfxmgaKToBFUgQ==", "dev": true }, "@truffle/codec": { - "version": "0.14.16", - "resolved": "https://registry.npmjs.org/@truffle/codec/-/codec-0.14.16.tgz", - "integrity": "sha512-a9UY3n/FnkKN3Q4zOuMFOOcLWb80mdknj+voim4vvXYtJm1aAZQZE5sG9aLnMBTl4TiGLzUtfNDVYY7WgWgDag==", + "version": "0.14.17", + "resolved": "https://registry.npmjs.org/@truffle/codec/-/codec-0.14.17.tgz", + "integrity": "sha512-kD4dD86huLeaBEq5R8D1zleJEu6NsXbyYLdXl1V1TKdiO8odw5CBC6Y/+wdu5d3t1dyEYrTbhn1dqknZa52pmw==", "dev": true, "requires": { "@truffle/abi-utils": "^0.3.9", @@ -17052,6 +18159,21 @@ "lru-cache": "^6.0.0" } }, + "web3-utils": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.8.2.tgz", + "integrity": "sha512-v7j6xhfLQfY7xQDrUP0BKbaNrmZ2/+egbqP9q3KYmOiPpnvAfol+32slgL0WX/5n8VPvKCK5EZ1HGrAVICSToA==", + "dev": true, + "requires": { + "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" + } + }, "yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", @@ -17079,17 +18201,17 @@ } }, "@truffle/contract": { - "version": "4.6.17", - "resolved": "https://registry.npmjs.org/@truffle/contract/-/contract-4.6.17.tgz", - "integrity": "sha512-sIMam5Wqr9AEiqHfOcWGJGqTv8Qy+BT765PaNHUUT6JBAY+tpHM3FlQd2nM6zLJ8paR3SLDGIthkhCBH/KNgDA==", + "version": "4.6.20", + "resolved": "https://registry.npmjs.org/@truffle/contract/-/contract-4.6.20.tgz", + "integrity": "sha512-s7Mbc37L/CF5Apy/cjPnalkgACmG9tTAmcIW28cIZLRLOUAze18pqhtdHryxAQhEOtKGaDAho6TriqL7/74uHw==", "dev": true, "requires": { "@ensdomains/ensjs": "^2.1.0", - "@truffle/blockchain-utils": "^0.1.6", + "@truffle/blockchain-utils": "^0.1.7", "@truffle/contract-schema": "^3.4.13", - "@truffle/debug-utils": "^6.0.47", + "@truffle/debug-utils": "^6.0.48", "@truffle/error": "^0.2.0", - "@truffle/interface-adapter": "^0.5.30", + "@truffle/interface-adapter": "^0.5.32", "bignumber.js": "^7.2.1", "debug": "^4.3.1", "ethers": "^4.0.32", @@ -17105,6 +18227,318 @@ "resolved": "https://registry.npmjs.org/@truffle/error/-/error-0.2.0.tgz", "integrity": "sha512-Fe0/z4WWb7IP2gBnv3l6zqP87Y0kSMs7oiSLakKJq17q3GUunrHSdioKuNspdggxkXIBhEQLhi8C+LJdwmHKWQ==", "dev": true + }, + "eth-lib": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/eth-lib/-/eth-lib-0.2.8.tgz", + "integrity": "sha512-ArJ7x1WcWOlSpzdoTBX8vkwlkSQ85CjjifSZtV4co64vWxSV8geWfPI9x4SVYu3DSxnX4yWFVTtGL+j9DUFLNw==", + "dev": true, + "requires": { + "bn.js": "^4.11.6", + "elliptic": "^6.4.0", + "xhr-request-promise": "^0.1.2" + } + }, + "uuid": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", + "dev": true + }, + "web3": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3/-/web3-1.8.2.tgz", + "integrity": "sha512-92h0GdEHW9wqDICQQKyG4foZBYi0OQkyg4CRml2F7XBl/NG+fu9o6J19kzfFXzSBoA4DnJXbyRgj/RHZv5LRiw==", + "dev": true, + "requires": { + "web3-bzz": "1.8.2", + "web3-core": "1.8.2", + "web3-eth": "1.8.2", + "web3-eth-personal": "1.8.2", + "web3-net": "1.8.2", + "web3-shh": "1.8.2", + "web3-utils": "1.8.2" + } + }, + "web3-bzz": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-bzz/-/web3-bzz-1.8.2.tgz", + "integrity": "sha512-1EEnxjPnFnvNWw3XeeKuTR8PBxYd0+XWzvaLK7OJC/Go9O8llLGxrxICbKV+8cgIE0sDRBxiYx02X+6OhoAQ9w==", + "dev": true, + "requires": { + "@types/node": "^12.12.6", + "got": "12.1.0", + "swarm-js": "^0.1.40" + } + }, + "web3-core": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-core/-/web3-core-1.8.2.tgz", + "integrity": "sha512-DJTVEAYcNqxkqruJE+Rxp3CIv0y5AZMwPHQmOkz/cz+MM75SIzMTc0AUdXzGyTS8xMF8h3YWMQGgGEy8SBf1PQ==", + "dev": true, + "requires": { + "@types/bn.js": "^5.1.0", + "@types/node": "^12.12.6", + "bignumber.js": "^9.0.0", + "web3-core-helpers": "1.8.2", + "web3-core-method": "1.8.2", + "web3-core-requestmanager": "1.8.2", + "web3-utils": "1.8.2" + }, + "dependencies": { + "bignumber.js": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.1.tgz", + "integrity": "sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig==", + "dev": true + } + } + }, + "web3-core-helpers": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-core-helpers/-/web3-core-helpers-1.8.2.tgz", + "integrity": "sha512-6B1eLlq9JFrfealZBomd1fmlq1o4A09vrCVQSa51ANoib/jllT3atZrRDr0zt1rfI7TSZTZBXdN/aTdeN99DWw==", + "dev": true, + "requires": { + "web3-eth-iban": "1.8.2", + "web3-utils": "1.8.2" + } + }, + "web3-core-method": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-core-method/-/web3-core-method-1.8.2.tgz", + "integrity": "sha512-1qnr5mw5wVyULzLOrk4B+ryO3gfGjGd/fx8NR+J2xCGLf1e6OSjxT9vbfuQ3fErk/NjSTWWreieYWLMhaogcRA==", + "dev": true, + "requires": { + "@ethersproject/transactions": "^5.6.2", + "web3-core-helpers": "1.8.2", + "web3-core-promievent": "1.8.2", + "web3-core-subscriptions": "1.8.2", + "web3-utils": "1.8.2" + } + }, + "web3-core-promievent": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-core-promievent/-/web3-core-promievent-1.8.2.tgz", + "integrity": "sha512-nvkJWDVgoOSsolJldN33tKW6bKKRJX3MCPDYMwP5SUFOA/mCzDEoI88N0JFofDTXkh1k7gOqp1pvwi9heuaxGg==", + "dev": true, + "requires": { + "eventemitter3": "4.0.4" + } + }, + "web3-core-requestmanager": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-core-requestmanager/-/web3-core-requestmanager-1.8.2.tgz", + "integrity": "sha512-p1d090RYs5Mu7DK1yyc3GCBVZB/03rBtFhYFoS2EruGzOWs/5Q0grgtpwS/DScdRAm8wB8mYEBhY/RKJWF6B2g==", + "dev": true, + "requires": { + "util": "^0.12.5", + "web3-core-helpers": "1.8.2", + "web3-providers-http": "1.8.2", + "web3-providers-ipc": "1.8.2", + "web3-providers-ws": "1.8.2" + } + }, + "web3-core-subscriptions": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-core-subscriptions/-/web3-core-subscriptions-1.8.2.tgz", + "integrity": "sha512-vXQogHDmAIQcKpXvGiMddBUeP9lnKgYF64+yQJhPNE5PnWr1sAibXuIPV7mIPihpFr/n/DORRj6Wh1pUv9zaTw==", + "dev": true, + "requires": { + "eventemitter3": "4.0.4", + "web3-core-helpers": "1.8.2" + } + }, + "web3-eth": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-eth/-/web3-eth-1.8.2.tgz", + "integrity": "sha512-JoTiWWc4F4TInpbvDUGb0WgDYJsFhuIjJlinc5ByjWD88Gvh+GKLsRjjFdbqe5YtwIGT4NymwoC5LQd1K6u/QQ==", + "dev": true, + "requires": { + "web3-core": "1.8.2", + "web3-core-helpers": "1.8.2", + "web3-core-method": "1.8.2", + "web3-core-subscriptions": "1.8.2", + "web3-eth-abi": "1.8.2", + "web3-eth-accounts": "1.8.2", + "web3-eth-contract": "1.8.2", + "web3-eth-ens": "1.8.2", + "web3-eth-iban": "1.8.2", + "web3-eth-personal": "1.8.2", + "web3-net": "1.8.2", + "web3-utils": "1.8.2" + } + }, + "web3-eth-abi": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-eth-abi/-/web3-eth-abi-1.8.2.tgz", + "integrity": "sha512-Om9g3kaRNjqiNPAgKwGT16y+ZwtBzRe4ZJFGjLiSs6v5I7TPNF+rRMWuKnR6jq0azQZDj6rblvKFMA49/k48Og==", + "dev": true, + "requires": { + "@ethersproject/abi": "^5.6.3", + "web3-utils": "1.8.2" + } + }, + "web3-eth-accounts": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-eth-accounts/-/web3-eth-accounts-1.8.2.tgz", + "integrity": "sha512-c367Ij63VCz9YdyjiHHWLFtN85l6QghgwMQH2B1eM/p9Y5lTlTX7t/Eg/8+f1yoIStXbk2w/PYM2lk+IkbqdLA==", + "dev": true, + "requires": { + "@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.8.2", + "web3-core-helpers": "1.8.2", + "web3-core-method": "1.8.2", + "web3-utils": "1.8.2" + } + }, + "web3-eth-contract": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-eth-contract/-/web3-eth-contract-1.8.2.tgz", + "integrity": "sha512-ID5A25tHTSBNwOPjiXSVzxruz006ULRIDbzWTYIFTp7NJ7vXu/kynKK2ag/ObuTqBpMbobP8nXcA9b5EDkIdQA==", + "dev": true, + "requires": { + "@types/bn.js": "^5.1.0", + "web3-core": "1.8.2", + "web3-core-helpers": "1.8.2", + "web3-core-method": "1.8.2", + "web3-core-promievent": "1.8.2", + "web3-core-subscriptions": "1.8.2", + "web3-eth-abi": "1.8.2", + "web3-utils": "1.8.2" + } + }, + "web3-eth-ens": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-eth-ens/-/web3-eth-ens-1.8.2.tgz", + "integrity": "sha512-PWph7C/CnqdWuu1+SH4U4zdrK4t2HNt0I4XzPYFdv9ugE8EuojselioPQXsVGvjql+Nt3jDLvQvggPqlMbvwRw==", + "dev": true, + "requires": { + "content-hash": "^2.5.2", + "eth-ens-namehash": "2.0.8", + "web3-core": "1.8.2", + "web3-core-helpers": "1.8.2", + "web3-core-promievent": "1.8.2", + "web3-eth-abi": "1.8.2", + "web3-eth-contract": "1.8.2", + "web3-utils": "1.8.2" + } + }, + "web3-eth-iban": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-eth-iban/-/web3-eth-iban-1.8.2.tgz", + "integrity": "sha512-h3vNblDWkWMuYx93Q27TAJz6lhzpP93EiC3+45D6xoz983p6si773vntoQ+H+5aZhwglBtoiBzdh7PSSOnP/xQ==", + "dev": true, + "requires": { + "bn.js": "^5.2.1", + "web3-utils": "1.8.2" + }, + "dependencies": { + "bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", + "dev": true + } + } + }, + "web3-eth-personal": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-eth-personal/-/web3-eth-personal-1.8.2.tgz", + "integrity": "sha512-Vg4HfwCr7doiUF/RC+Jz0wT4+cYaXcOWMAW2AHIjHX6Z7Xwa8nrURIeQgeEE62qcEHAzajyAdB1u6bJyTfuCXw==", + "dev": true, + "requires": { + "@types/node": "^12.12.6", + "web3-core": "1.8.2", + "web3-core-helpers": "1.8.2", + "web3-core-method": "1.8.2", + "web3-net": "1.8.2", + "web3-utils": "1.8.2" + } + }, + "web3-net": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-net/-/web3-net-1.8.2.tgz", + "integrity": "sha512-1itkDMGmbgb83Dg9nporFes9/fxsU7smJ3oRXlFkg4ZHn8YJyP1MSQFPJWWwSc+GrcCFt4O5IrUTvEkHqE3xag==", + "dev": true, + "requires": { + "web3-core": "1.8.2", + "web3-core-method": "1.8.2", + "web3-utils": "1.8.2" + } + }, + "web3-providers-http": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-providers-http/-/web3-providers-http-1.8.2.tgz", + "integrity": "sha512-2xY94IIEQd16+b+vIBF4IC1p7GVaz9q4EUFscvMUjtEq4ru4Atdzjs9GP+jmcoo49p70II0UV3bqQcz0TQfVyQ==", + "dev": true, + "requires": { + "abortcontroller-polyfill": "^1.7.3", + "cross-fetch": "^3.1.4", + "es6-promise": "^4.2.8", + "web3-core-helpers": "1.8.2" + } + }, + "web3-providers-ipc": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-providers-ipc/-/web3-providers-ipc-1.8.2.tgz", + "integrity": "sha512-p6fqKVGFg+WiXGHWnB1hu43PbvPkDHTz4RgoEzbXugv5rtv5zfYLqm8Ba6lrJOS5ks9kGKR21a0y3NzE3u7V4w==", + "dev": true, + "requires": { + "oboe": "2.1.5", + "web3-core-helpers": "1.8.2" + } + }, + "web3-providers-ws": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-providers-ws/-/web3-providers-ws-1.8.2.tgz", + "integrity": "sha512-3s/4K+wHgbiN+Zrp9YjMq2eqAF6QGABw7wFftPdx+m5hWImV27/MoIx57c6HffNRqZXmCHnfWWFCNHHsi7wXnA==", + "dev": true, + "requires": { + "eventemitter3": "4.0.4", + "web3-core-helpers": "1.8.2", + "websocket": "^1.0.32" + } + }, + "web3-shh": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-shh/-/web3-shh-1.8.2.tgz", + "integrity": "sha512-uZ+3MAoNcaJsXXNCDnizKJ5viBNeHOFYsCbFhV755Uu52FswzTOw6DtE7yK9nYXMtIhiSgi7nwl1RYzP8pystw==", + "dev": true, + "requires": { + "web3-core": "1.8.2", + "web3-core-method": "1.8.2", + "web3-core-subscriptions": "1.8.2", + "web3-net": "1.8.2" + } + }, + "web3-utils": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.8.2.tgz", + "integrity": "sha512-v7j6xhfLQfY7xQDrUP0BKbaNrmZ2/+egbqP9q3KYmOiPpnvAfol+32slgL0WX/5n8VPvKCK5EZ1HGrAVICSToA==", + "dev": true, + "requires": { + "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" + }, + "dependencies": { + "bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", + "dev": true + } + } } } }, @@ -17119,12 +18553,12 @@ } }, "@truffle/debug-utils": { - "version": "6.0.47", - "resolved": "https://registry.npmjs.org/@truffle/debug-utils/-/debug-utils-6.0.47.tgz", - "integrity": "sha512-bUjdzLPdEKtoUCDzaXkrOoi+PbyAJlMBzGequBK8tirT7xL9bCP2Pd/WxvnmRd7AnfroxGNvXwVXWTItW5SMWQ==", + "version": "6.0.48", + "resolved": "https://registry.npmjs.org/@truffle/debug-utils/-/debug-utils-6.0.48.tgz", + "integrity": "sha512-HdK/7eH5EFrcTPeZVEgKaKkkzuZ4xsrH8yw+EoLEsScLsOEuQeKynY61NctjuU93voATWrYmV99Sfb/MRq2i2g==", "dev": true, "requires": { - "@truffle/codec": "^0.14.16", + "@truffle/codec": "^0.14.17", "@trufflesuite/chromafi": "^3.0.0", "bn.js": "^5.1.3", "chalk": "^2.4.2", @@ -17147,9 +18581,9 @@ "dev": true }, "@truffle/interface-adapter": { - "version": "0.5.30", - "resolved": "https://registry.npmjs.org/@truffle/interface-adapter/-/interface-adapter-0.5.30.tgz", - "integrity": "sha512-wyCcESeZJBkAfuSGU8GHCusIWDUDyQjJZMcyShv59ZTSAwQR7xx0+a0Q1LlS532G/pGFLYe2VzKSY1pRHRwgug==", + "version": "0.5.32", + "resolved": "https://registry.npmjs.org/@truffle/interface-adapter/-/interface-adapter-0.5.32.tgz", + "integrity": "sha512-7Hgmdb4nJUcWtq4vvgWY7Mr2RLOTOp5FZaWyMiFmjkcEEhDlezm2QstALWAXgT0W6q7tDmDBpw3vTIFenRhHBA==", "dev": true, "requires": { "bn.js": "^5.1.3", @@ -17157,11 +18591,313 @@ "web3": "1.8.2" }, "dependencies": { + "bignumber.js": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.1.tgz", + "integrity": "sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig==", + "dev": true + }, "bn.js": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", "dev": true + }, + "eth-lib": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/eth-lib/-/eth-lib-0.2.8.tgz", + "integrity": "sha512-ArJ7x1WcWOlSpzdoTBX8vkwlkSQ85CjjifSZtV4co64vWxSV8geWfPI9x4SVYu3DSxnX4yWFVTtGL+j9DUFLNw==", + "dev": true, + "requires": { + "bn.js": "^4.11.6", + "elliptic": "^6.4.0", + "xhr-request-promise": "^0.1.2" + }, + "dependencies": { + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + } + } + }, + "uuid": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", + "dev": true + }, + "web3": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3/-/web3-1.8.2.tgz", + "integrity": "sha512-92h0GdEHW9wqDICQQKyG4foZBYi0OQkyg4CRml2F7XBl/NG+fu9o6J19kzfFXzSBoA4DnJXbyRgj/RHZv5LRiw==", + "dev": true, + "requires": { + "web3-bzz": "1.8.2", + "web3-core": "1.8.2", + "web3-eth": "1.8.2", + "web3-eth-personal": "1.8.2", + "web3-net": "1.8.2", + "web3-shh": "1.8.2", + "web3-utils": "1.8.2" + } + }, + "web3-bzz": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-bzz/-/web3-bzz-1.8.2.tgz", + "integrity": "sha512-1EEnxjPnFnvNWw3XeeKuTR8PBxYd0+XWzvaLK7OJC/Go9O8llLGxrxICbKV+8cgIE0sDRBxiYx02X+6OhoAQ9w==", + "dev": true, + "requires": { + "@types/node": "^12.12.6", + "got": "12.1.0", + "swarm-js": "^0.1.40" + } + }, + "web3-core": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-core/-/web3-core-1.8.2.tgz", + "integrity": "sha512-DJTVEAYcNqxkqruJE+Rxp3CIv0y5AZMwPHQmOkz/cz+MM75SIzMTc0AUdXzGyTS8xMF8h3YWMQGgGEy8SBf1PQ==", + "dev": true, + "requires": { + "@types/bn.js": "^5.1.0", + "@types/node": "^12.12.6", + "bignumber.js": "^9.0.0", + "web3-core-helpers": "1.8.2", + "web3-core-method": "1.8.2", + "web3-core-requestmanager": "1.8.2", + "web3-utils": "1.8.2" + } + }, + "web3-core-helpers": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-core-helpers/-/web3-core-helpers-1.8.2.tgz", + "integrity": "sha512-6B1eLlq9JFrfealZBomd1fmlq1o4A09vrCVQSa51ANoib/jllT3atZrRDr0zt1rfI7TSZTZBXdN/aTdeN99DWw==", + "dev": true, + "requires": { + "web3-eth-iban": "1.8.2", + "web3-utils": "1.8.2" + } + }, + "web3-core-method": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-core-method/-/web3-core-method-1.8.2.tgz", + "integrity": "sha512-1qnr5mw5wVyULzLOrk4B+ryO3gfGjGd/fx8NR+J2xCGLf1e6OSjxT9vbfuQ3fErk/NjSTWWreieYWLMhaogcRA==", + "dev": true, + "requires": { + "@ethersproject/transactions": "^5.6.2", + "web3-core-helpers": "1.8.2", + "web3-core-promievent": "1.8.2", + "web3-core-subscriptions": "1.8.2", + "web3-utils": "1.8.2" + } + }, + "web3-core-promievent": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-core-promievent/-/web3-core-promievent-1.8.2.tgz", + "integrity": "sha512-nvkJWDVgoOSsolJldN33tKW6bKKRJX3MCPDYMwP5SUFOA/mCzDEoI88N0JFofDTXkh1k7gOqp1pvwi9heuaxGg==", + "dev": true, + "requires": { + "eventemitter3": "4.0.4" + } + }, + "web3-core-requestmanager": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-core-requestmanager/-/web3-core-requestmanager-1.8.2.tgz", + "integrity": "sha512-p1d090RYs5Mu7DK1yyc3GCBVZB/03rBtFhYFoS2EruGzOWs/5Q0grgtpwS/DScdRAm8wB8mYEBhY/RKJWF6B2g==", + "dev": true, + "requires": { + "util": "^0.12.5", + "web3-core-helpers": "1.8.2", + "web3-providers-http": "1.8.2", + "web3-providers-ipc": "1.8.2", + "web3-providers-ws": "1.8.2" + } + }, + "web3-core-subscriptions": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-core-subscriptions/-/web3-core-subscriptions-1.8.2.tgz", + "integrity": "sha512-vXQogHDmAIQcKpXvGiMddBUeP9lnKgYF64+yQJhPNE5PnWr1sAibXuIPV7mIPihpFr/n/DORRj6Wh1pUv9zaTw==", + "dev": true, + "requires": { + "eventemitter3": "4.0.4", + "web3-core-helpers": "1.8.2" + } + }, + "web3-eth": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-eth/-/web3-eth-1.8.2.tgz", + "integrity": "sha512-JoTiWWc4F4TInpbvDUGb0WgDYJsFhuIjJlinc5ByjWD88Gvh+GKLsRjjFdbqe5YtwIGT4NymwoC5LQd1K6u/QQ==", + "dev": true, + "requires": { + "web3-core": "1.8.2", + "web3-core-helpers": "1.8.2", + "web3-core-method": "1.8.2", + "web3-core-subscriptions": "1.8.2", + "web3-eth-abi": "1.8.2", + "web3-eth-accounts": "1.8.2", + "web3-eth-contract": "1.8.2", + "web3-eth-ens": "1.8.2", + "web3-eth-iban": "1.8.2", + "web3-eth-personal": "1.8.2", + "web3-net": "1.8.2", + "web3-utils": "1.8.2" + } + }, + "web3-eth-abi": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-eth-abi/-/web3-eth-abi-1.8.2.tgz", + "integrity": "sha512-Om9g3kaRNjqiNPAgKwGT16y+ZwtBzRe4ZJFGjLiSs6v5I7TPNF+rRMWuKnR6jq0azQZDj6rblvKFMA49/k48Og==", + "dev": true, + "requires": { + "@ethersproject/abi": "^5.6.3", + "web3-utils": "1.8.2" + } + }, + "web3-eth-accounts": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-eth-accounts/-/web3-eth-accounts-1.8.2.tgz", + "integrity": "sha512-c367Ij63VCz9YdyjiHHWLFtN85l6QghgwMQH2B1eM/p9Y5lTlTX7t/Eg/8+f1yoIStXbk2w/PYM2lk+IkbqdLA==", + "dev": true, + "requires": { + "@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.8.2", + "web3-core-helpers": "1.8.2", + "web3-core-method": "1.8.2", + "web3-utils": "1.8.2" + } + }, + "web3-eth-contract": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-eth-contract/-/web3-eth-contract-1.8.2.tgz", + "integrity": "sha512-ID5A25tHTSBNwOPjiXSVzxruz006ULRIDbzWTYIFTp7NJ7vXu/kynKK2ag/ObuTqBpMbobP8nXcA9b5EDkIdQA==", + "dev": true, + "requires": { + "@types/bn.js": "^5.1.0", + "web3-core": "1.8.2", + "web3-core-helpers": "1.8.2", + "web3-core-method": "1.8.2", + "web3-core-promievent": "1.8.2", + "web3-core-subscriptions": "1.8.2", + "web3-eth-abi": "1.8.2", + "web3-utils": "1.8.2" + } + }, + "web3-eth-ens": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-eth-ens/-/web3-eth-ens-1.8.2.tgz", + "integrity": "sha512-PWph7C/CnqdWuu1+SH4U4zdrK4t2HNt0I4XzPYFdv9ugE8EuojselioPQXsVGvjql+Nt3jDLvQvggPqlMbvwRw==", + "dev": true, + "requires": { + "content-hash": "^2.5.2", + "eth-ens-namehash": "2.0.8", + "web3-core": "1.8.2", + "web3-core-helpers": "1.8.2", + "web3-core-promievent": "1.8.2", + "web3-eth-abi": "1.8.2", + "web3-eth-contract": "1.8.2", + "web3-utils": "1.8.2" + } + }, + "web3-eth-iban": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-eth-iban/-/web3-eth-iban-1.8.2.tgz", + "integrity": "sha512-h3vNblDWkWMuYx93Q27TAJz6lhzpP93EiC3+45D6xoz983p6si773vntoQ+H+5aZhwglBtoiBzdh7PSSOnP/xQ==", + "dev": true, + "requires": { + "bn.js": "^5.2.1", + "web3-utils": "1.8.2" + } + }, + "web3-eth-personal": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-eth-personal/-/web3-eth-personal-1.8.2.tgz", + "integrity": "sha512-Vg4HfwCr7doiUF/RC+Jz0wT4+cYaXcOWMAW2AHIjHX6Z7Xwa8nrURIeQgeEE62qcEHAzajyAdB1u6bJyTfuCXw==", + "dev": true, + "requires": { + "@types/node": "^12.12.6", + "web3-core": "1.8.2", + "web3-core-helpers": "1.8.2", + "web3-core-method": "1.8.2", + "web3-net": "1.8.2", + "web3-utils": "1.8.2" + } + }, + "web3-net": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-net/-/web3-net-1.8.2.tgz", + "integrity": "sha512-1itkDMGmbgb83Dg9nporFes9/fxsU7smJ3oRXlFkg4ZHn8YJyP1MSQFPJWWwSc+GrcCFt4O5IrUTvEkHqE3xag==", + "dev": true, + "requires": { + "web3-core": "1.8.2", + "web3-core-method": "1.8.2", + "web3-utils": "1.8.2" + } + }, + "web3-providers-http": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-providers-http/-/web3-providers-http-1.8.2.tgz", + "integrity": "sha512-2xY94IIEQd16+b+vIBF4IC1p7GVaz9q4EUFscvMUjtEq4ru4Atdzjs9GP+jmcoo49p70II0UV3bqQcz0TQfVyQ==", + "dev": true, + "requires": { + "abortcontroller-polyfill": "^1.7.3", + "cross-fetch": "^3.1.4", + "es6-promise": "^4.2.8", + "web3-core-helpers": "1.8.2" + } + }, + "web3-providers-ipc": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-providers-ipc/-/web3-providers-ipc-1.8.2.tgz", + "integrity": "sha512-p6fqKVGFg+WiXGHWnB1hu43PbvPkDHTz4RgoEzbXugv5rtv5zfYLqm8Ba6lrJOS5ks9kGKR21a0y3NzE3u7V4w==", + "dev": true, + "requires": { + "oboe": "2.1.5", + "web3-core-helpers": "1.8.2" + } + }, + "web3-providers-ws": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-providers-ws/-/web3-providers-ws-1.8.2.tgz", + "integrity": "sha512-3s/4K+wHgbiN+Zrp9YjMq2eqAF6QGABw7wFftPdx+m5hWImV27/MoIx57c6HffNRqZXmCHnfWWFCNHHsi7wXnA==", + "dev": true, + "requires": { + "eventemitter3": "4.0.4", + "web3-core-helpers": "1.8.2", + "websocket": "^1.0.32" + } + }, + "web3-shh": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-shh/-/web3-shh-1.8.2.tgz", + "integrity": "sha512-uZ+3MAoNcaJsXXNCDnizKJ5viBNeHOFYsCbFhV755Uu52FswzTOw6DtE7yK9nYXMtIhiSgi7nwl1RYzP8pystw==", + "dev": true, + "requires": { + "web3-core": "1.8.2", + "web3-core-method": "1.8.2", + "web3-core-subscriptions": "1.8.2", + "web3-net": "1.8.2" + } + }, + "web3-utils": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.8.2.tgz", + "integrity": "sha512-v7j6xhfLQfY7xQDrUP0BKbaNrmZ2/+egbqP9q3KYmOiPpnvAfol+32slgL0WX/5n8VPvKCK5EZ1HGrAVICSToA==", + "dev": true, + "requires": { + "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" + } } } }, @@ -17189,12 +18925,6 @@ } } }, - "@types/async-eventemitter": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@types/async-eventemitter/-/async-eventemitter-0.2.1.tgz", - "integrity": "sha512-M2P4Ng26QbAeITiH7w1d7OxtldgfAe0wobpyJzVK/XOb0cUGKU2R4pfAhqcJBXAe2ife5ZOhSv4wk7p+ffURtg==", - "dev": true - }, "@types/bignumber.js": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/@types/bignumber.js/-/bignumber.js-5.0.0.tgz", @@ -17226,9 +18956,9 @@ } }, "@types/chai": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.4.tgz", - "integrity": "sha512-KnRanxnpfpjUTqTCXslZSEdLfXExwgNxYPdiO2WGUj8+HDjFi8R3k5RVKPeSCzLjCcshCAtVO2QBbVuAV4kTnw==", + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.5.tgz", + "integrity": "sha512-mEo1sAde+UCE6b2hxn332f1g1E8WfYRu6p5SvTKr2ZKC1f7gFJXk4h5PyGP9Dt6gCaG8y8XhwnXWC6Iy2cmBng==", "dev": true }, "@types/concat-stream": { @@ -17328,6 +19058,24 @@ "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", "dev": true }, + "@types/readable-stream": { + "version": "2.3.15", + "resolved": "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-2.3.15.tgz", + "integrity": "sha512-oM5JSKQCcICF1wvGgmecmHldZ48OZamtMxcGGVICOJA8o8cahXC1zEVAif8iwoc5j8etxFaRFnf095+CDsuoFQ==", + "dev": true, + "requires": { + "@types/node": "*", + "safe-buffer": "~5.1.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + } + } + }, "@types/responselike": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", @@ -17647,22 +19395,10 @@ "dev": true }, "async": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", - "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", - "dev": true, - "requires": { - "lodash": "^4.17.14" - } - }, - "async-eventemitter": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/async-eventemitter/-/async-eventemitter-0.2.4.tgz", - "integrity": "sha512-pd20BwL7Yt1zwDFy+8MX8F1+WCT8aQeKj0kQnTrH9WaeRETlRamVhD0JtRPmrV4GfOJ2F9CvdQkZeZhnh2TuHw==", - "dev": true, - "requires": { - "async": "^2.4.0" - } + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha512-nSVgobk4rv61R9PUSDtYt7mPVB2olxNR5RWJcAsH676/ef11bUZwvu7+RGYrYauVdDPcO519v68wRhXQtxsV9w==", + "dev": true }, "async-limiter": { "version": "1.0.1", @@ -17760,18 +19496,9 @@ "dev": true }, "bigint-crypto-utils": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/bigint-crypto-utils/-/bigint-crypto-utils-3.1.8.tgz", - "integrity": "sha512-+VMV9Laq8pXLBKKKK49nOoq9bfR3j7NNQAtbA617a4nw9bVLo8rsqkKMBgM2AJWlNX9fEIyYaYX+d0laqYV4tw==", - "dev": true, - "requires": { - "bigint-mod-arith": "^3.1.0" - } - }, - "bigint-mod-arith": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bigint-mod-arith/-/bigint-mod-arith-3.1.2.tgz", - "integrity": "sha512-nx8J8bBeiRR+NlsROFH9jHswW5HO8mgfOSqW0AmjicMMvaONDa8AO+5ViKDUUNytBPWiwfvZP4/Bj4Y3lUfvgQ==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/bigint-crypto-utils/-/bigint-crypto-utils-3.2.2.tgz", + "integrity": "sha512-U1RbE3aX9ayCUVcIPHuPDPKcK3SFOXf93J1UK/iHlJuQB7bhagPIX06/CLpLEsDThJ7KA4Dhrnzynl+d2weTiw==", "dev": true }, "bignumber.js": { @@ -18089,6 +19816,12 @@ } } }, + "case": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/case/-/case-1.6.3.tgz", + "integrity": "sha512-mzDSXIPaFwVDvZAHqZ9VlbyF4yyXRuX6IvB06WvPYkqJVO24kX1PPhv9bfpKNFZyxYFmmgo03HUiD8iklmJYRQ==", + "dev": true + }, "caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", @@ -18286,15 +20019,15 @@ "dev": true }, "classic-level": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/classic-level/-/classic-level-1.2.0.tgz", - "integrity": "sha512-qw5B31ANxSluWz9xBzklRWTUAJ1SXIdaVKTVS7HcTGKOAmExx65Wo5BUICW+YGORe2FOUaDghoI9ZDxj82QcFg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/classic-level/-/classic-level-1.3.0.tgz", + "integrity": "sha512-iwFAJQYtqRTRM0F6L8h4JCt00ZSGdOyqh7yVrhhjrOpFhmBjNlRUey64MCiyo6UmQHMJ+No3c81nujPv+n9yrg==", "dev": true, "requires": { "abstract-level": "^1.0.2", "catering": "^2.1.0", "module-error": "^1.0.1", - "napi-macros": "~2.0.0", + "napi-macros": "^2.2.2", "node-gyp-build": "^4.3.0" } }, @@ -18418,9 +20151,9 @@ "dev": true }, "commander": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.0.tgz", - "integrity": "sha512-zS5PnTI22FIRM6ylNW8G4Ap0IEOyk62fhLSD0+uHRT9McRCLGpkVNvao4bjimpK/GShynyQkFFxHhwMcETmduA==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", "dev": true }, "compare-versions": { @@ -18544,9 +20277,9 @@ } }, "cosmiconfig": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.1.0.tgz", - "integrity": "sha512-0tLZ9URlPGU7JsKq0DQOQ3FoRsYX8xDZ7xMiATQfaiGMz7EHowNkbU9u1coAOmnh9p/1ySpm0RB3JNWRXM5GCg==", + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.1.3.tgz", + "integrity": "sha512-/UkO2JKI18b5jVMJUp0lvKFMpa/Gye+ZgZjKD+DGEN9y7NRcf/nK1A0sp67ONmKtnDCNMS44E6jrk0Yc3bDuUw==", "dev": true, "requires": { "import-fresh": "^3.2.1", @@ -18939,14 +20672,14 @@ } }, "domutils": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.0.1.tgz", - "integrity": "sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", "dev": true, "requires": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", - "domhandler": "^5.0.1" + "domhandler": "^5.0.3" } }, "dot-case": { @@ -19026,9 +20759,9 @@ } }, "entities": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz", - "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", "dev": true }, "env-paths": { @@ -19248,15 +20981,15 @@ } }, "eslint": { - "version": "8.36.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.36.0.tgz", - "integrity": "sha512-Y956lmS7vDqomxlaaQAHVmeb4tNMp2FWIvU/RnU5BD3IKMD/MJPr76xdyr68P8tV1iNMvN2mRK0yy3c+UjL+bw==", + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.39.0.tgz", + "integrity": "sha512-mwiok6cy7KTW7rBpo05k6+p4YVZByLNjAZ/ACB9DRCu4YDRwjXI01tWHp6KAUWelsBetTxKK/2sHB0vdS8Z2Og==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.4.0", - "@eslint/eslintrc": "^2.0.1", - "@eslint/js": "8.36.0", + "@eslint/eslintrc": "^2.0.2", + "@eslint/js": "8.39.0", "@humanwhocodes/config-array": "^0.11.8", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", @@ -19266,9 +20999,9 @@ "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", - "eslint-visitor-keys": "^3.3.0", - "espree": "^9.5.0", + "eslint-scope": "^7.2.0", + "eslint-visitor-keys": "^3.4.0", + "espree": "^9.5.1", "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -19420,16 +21153,16 @@ } }, "eslint-config-prettier": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.7.0.tgz", - "integrity": "sha512-HHVXLSlVUhMSmyW4ZzEuvjpwqamgmlfkutD53cYXLikh4pt/modINRcCIApJ84czDxM4GZInwUrromsDdTImTA==", + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.8.0.tgz", + "integrity": "sha512-wLbQiFre3tdGgpDv67NQKnJuTlcUVYHas3k+DZCc2U2BadthoEY4B7hLPvAxaqdyOGCzuLfii2fqGph10va7oA==", "dev": true, "requires": {} }, "eslint-scope": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", - "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz", + "integrity": "sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==", "dev": true, "requires": { "esrecurse": "^4.3.0", @@ -19437,20 +21170,20 @@ } }, "eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz", + "integrity": "sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ==", "dev": true }, "espree": { - "version": "9.5.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.0.tgz", - "integrity": "sha512-JPbJGhKc47++oo4JkEoTe2wjy4fmMwvFpgJT9cQzmfXKp22Dr6Hf1tdCteLz1h0P3t+mGvWZ+4Uankvh8+c6zw==", + "version": "9.5.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.1.tgz", + "integrity": "sha512-5yxtHSZXRSW5pvv3hAlXM5+/Oswi1AUFqBmbibKb5s6bp3rGIDkyXU6xCoyuuLhijr4SFwPrXRoZjz0AZDN9tg==", "dev": true, "requires": { "acorn": "^8.8.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.3.0" + "eslint-visitor-keys": "^3.4.0" } }, "esprima": { @@ -20839,9 +22572,9 @@ } }, "graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true }, "grapheme-splitter": { @@ -20909,23 +22642,23 @@ "dev": true }, "hardhat": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.13.0.tgz", - "integrity": "sha512-ZlzBOLML1QGlm6JWyVAG8lVTEAoOaVm1in/RU2zoGAnYEoD1Rp4T+ZMvrLNhHaaeS9hfjJ1gJUBfiDr4cx+htQ==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.14.0.tgz", + "integrity": "sha512-73jsInY4zZahMSVFurSK+5TNCJTXMv+vemvGia0Ac34Mm19fYp6vEPVGF3sucbumszsYxiTT2TbS8Ii2dsDSoQ==", "dev": true, "requires": { "@ethersproject/abi": "^5.1.2", "@metamask/eth-sig-util": "^4.0.0", - "@nomicfoundation/ethereumjs-block": "^4.0.0", - "@nomicfoundation/ethereumjs-blockchain": "^6.0.0", - "@nomicfoundation/ethereumjs-common": "^3.0.0", - "@nomicfoundation/ethereumjs-evm": "^1.0.0", - "@nomicfoundation/ethereumjs-rlp": "^4.0.0", - "@nomicfoundation/ethereumjs-statemanager": "^1.0.0", - "@nomicfoundation/ethereumjs-trie": "^5.0.0", - "@nomicfoundation/ethereumjs-tx": "^4.0.0", - "@nomicfoundation/ethereumjs-util": "^8.0.0", - "@nomicfoundation/ethereumjs-vm": "^6.0.0", + "@nomicfoundation/ethereumjs-block": "5.0.1", + "@nomicfoundation/ethereumjs-blockchain": "7.0.1", + "@nomicfoundation/ethereumjs-common": "4.0.1", + "@nomicfoundation/ethereumjs-evm": "2.0.1", + "@nomicfoundation/ethereumjs-rlp": "5.0.1", + "@nomicfoundation/ethereumjs-statemanager": "2.0.1", + "@nomicfoundation/ethereumjs-trie": "6.0.1", + "@nomicfoundation/ethereumjs-tx": "5.0.1", + "@nomicfoundation/ethereumjs-util": "9.0.1", + "@nomicfoundation/ethereumjs-vm": "7.0.1", "@nomicfoundation/solidity-analyzer": "^0.1.0", "@sentry/node": "^5.18.1", "@types/bn.js": "^5.1.0", @@ -21299,15 +23032,15 @@ "dev": true }, "htmlparser2": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.1.tgz", - "integrity": "sha512-4lVbmc1diZC7GUJQtRQ5yBAeUCL1exyMwmForWkRLnwyzWBFxN633SALPMGYaWZvKe9j1pRZJpauvmxENSp/EA==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", + "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", "dev": true, "requires": { "domelementtype": "^2.3.0", - "domhandler": "^5.0.2", + "domhandler": "^5.0.3", "domutils": "^3.0.1", - "entities": "^4.3.0" + "entities": "^4.4.0" } }, "http-basic": { @@ -21830,9 +23563,9 @@ "dev": true }, "js-sdsl": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.3.0.tgz", - "integrity": "sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.0.tgz", + "integrity": "sha512-FfVSdx6pJ41Oa+CF7RDaFmTnCaFhua+SNYQX74riGOpl96x+2jQCqEfQ2bnXu/5DPCqlRuiqyvTJM0Qjz26IVg==", "dev": true }, "js-sha3": { @@ -22495,9 +24228,9 @@ } }, "mixme": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mixme/-/mixme-0.5.5.tgz", - "integrity": "sha512-/6IupbRx32s7jjEwHcycXikJwFD5UujbVNuJFkeKLYje+92OvtuPniF6JhnFm5JCTDUhS+kYK3W/4BWYQYXz7w==", + "version": "0.5.9", + "resolved": "https://registry.npmjs.org/mixme/-/mixme-0.5.9.tgz", + "integrity": "sha512-VC5fg6ySUscaWUpI4gxCBTQMH2RdUpNrk+MsbpCYtIvf9SBJdiUey4qE7BXviJsJR4nDQxCZ+3yaYNW3guz/Pw==", "dev": true }, "mkdirp": { @@ -22822,9 +24555,9 @@ "dev": true }, "napi-macros": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/napi-macros/-/napi-macros-2.0.0.tgz", - "integrity": "sha512-A0xLykHtARfueITVDernsAWdtIMbOJgKgcluwENp3AlsKN/PloyO10HtmoqnFAQAcxPkgZN7wdfPfEd0zNGxbg==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/napi-macros/-/napi-macros-2.2.2.tgz", + "integrity": "sha512-hmEVtAGYzVQpCKdbQea4skABsdXW4RUh5t5mJ2zzqowJS2OyXZTU1KhDVFhx+NlWZ4ap9mqR9TcDO3LTTttd+g==", "dev": true }, "natural-compare": { @@ -23034,15 +24767,16 @@ } }, "object.getownpropertydescriptors": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.5.tgz", - "integrity": "sha512-yDNzckpM6ntyQiGTik1fKV1DcVDRS+w8bvpWNCBanvH5LfRX9O8WTHqQzG4RZwRAM4I0oU7TV11Lj5v0g20ibw==", + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.6.tgz", + "integrity": "sha512-lq+61g26E/BgHv0ZTFgRvi7NMEPuAxLkFU7rukXjc/AlwH4Am5xXVnIXy3un1bg/JPbXHrixRkK1itUzzPiIjQ==", "dev": true, "requires": { "array.prototype.reduce": "^1.0.5", "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" + "define-properties": "^1.2.0", + "es-abstract": "^1.21.2", + "safe-array-concat": "^1.0.0" } }, "obliterator": { @@ -23424,9 +25158,9 @@ "dev": true }, "prettier": { - "version": "2.8.4", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.4.tgz", - "integrity": "sha512-vIS4Rlc2FNh0BySk3Wkd6xmwxB0FpOndW5fisM5H8hsZSxU2VWVB5CWIkIjWvrHjIhxk2g3bfMKM87zNTrZddw==", + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", "dev": true }, "prettier-plugin-solidity": { @@ -23703,14 +25437,14 @@ "dev": true }, "regexp.prototype.flags": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", - "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz", + "integrity": "sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==", "dev": true, "requires": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "functions-have-names": "^1.2.2" + "define-properties": "^1.2.0", + "functions-have-names": "^1.2.3" } }, "req-cwd": { @@ -23951,6 +25685,26 @@ "integrity": "sha512-4VlvkRUuCJvr2J6Y0ImW7NvTCriMi7ErOAqWk1y69vAdoNIzCF3yPmgeNzx+RQTLEDFq5sHfscn1MwHxP9hNfA==", "dev": true }, + "safe-array-concat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.0.tgz", + "integrity": "sha512-9dVEFruWIsnie89yym+xWTAYASdpw3CJV7Li/6zBewGf9z2i1j31rP6jnY0pHEO4QZh6N0K11bFjWmdR8UGdPQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.0", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + }, + "dependencies": { + "isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + } + } + }, "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -23996,12 +25750,6 @@ "wordwrap": "^1.0.0" }, "dependencies": { - "async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha512-nSVgobk4rv61R9PUSDtYt7mPVB2olxNR5RWJcAsH676/ef11bUZwvu7+RGYrYauVdDPcO519v68wRhXQtxsV9w==", - "dev": true - }, "esprima": { "version": "2.7.3", "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", @@ -24071,9 +25819,9 @@ } }, "semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -26041,18 +27789,18 @@ "dev": true }, "tty-table": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/tty-table/-/tty-table-4.1.6.tgz", - "integrity": "sha512-kRj5CBzOrakV4VRRY5kUWbNYvo/FpOsz65DzI5op9P+cHov3+IqPbo1JE1ZnQGkHdZgNFDsrEjrfqqy/Ply9fw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/tty-table/-/tty-table-4.2.1.tgz", + "integrity": "sha512-xz0uKo+KakCQ+Dxj1D/tKn2FSyreSYWzdkL/BYhgN6oMW808g8QRMuh1atAV9fjTPbWBjfbkKQpI/5rEcnAc7g==", "dev": true, "requires": { "chalk": "^4.1.2", - "csv": "^5.5.0", - "kleur": "^4.1.4", + "csv": "^5.5.3", + "kleur": "^4.1.5", "smartwrap": "^2.0.2", - "strip-ansi": "^6.0.0", + "strip-ansi": "^6.0.1", "wcwidth": "^1.0.1", - "yargs": "^17.1.1" + "yargs": "^17.7.1" }, "dependencies": { "ansi-regex": { @@ -26237,9 +27985,9 @@ "dev": true }, "undici": { - "version": "5.21.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.21.0.tgz", - "integrity": "sha512-HOjK8l6a57b2ZGXOcUsI5NLfoTrfmbOl90ixJDl0AEFG4wgHNDQxtZy15/ZQp7HhjkpaGlp/eneMgtsu1dIlUA==", + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.22.0.tgz", + "integrity": "sha512-fR9RXCc+6Dxav4P9VV/sp5w3eFiSdOjJYsbtWfd4s5L5C4ogyuVpdKIVHeW0vV1MloM65/f7W45nR9ZxwVdyiA==", "dev": true, "requires": { "busboy": "^1.6.0" @@ -26376,24 +28124,24 @@ } }, "web3": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3/-/web3-1.8.2.tgz", - "integrity": "sha512-92h0GdEHW9wqDICQQKyG4foZBYi0OQkyg4CRml2F7XBl/NG+fu9o6J19kzfFXzSBoA4DnJXbyRgj/RHZv5LRiw==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/web3/-/web3-1.9.0.tgz", + "integrity": "sha512-E9IvVy/d2ozfQQsCiV+zh/LmlZGv9fQxI0UedDVjm87yOKf4AYbBNEn1iWtHveiGzAk2CEMZMUzAZzaQNSSYog==", "dev": true, "requires": { - "web3-bzz": "1.8.2", - "web3-core": "1.8.2", - "web3-eth": "1.8.2", - "web3-eth-personal": "1.8.2", - "web3-net": "1.8.2", - "web3-shh": "1.8.2", - "web3-utils": "1.8.2" + "web3-bzz": "1.9.0", + "web3-core": "1.9.0", + "web3-eth": "1.9.0", + "web3-eth-personal": "1.9.0", + "web3-net": "1.9.0", + "web3-shh": "1.9.0", + "web3-utils": "1.9.0" } }, "web3-bzz": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-bzz/-/web3-bzz-1.8.2.tgz", - "integrity": "sha512-1EEnxjPnFnvNWw3XeeKuTR8PBxYd0+XWzvaLK7OJC/Go9O8llLGxrxICbKV+8cgIE0sDRBxiYx02X+6OhoAQ9w==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/web3-bzz/-/web3-bzz-1.9.0.tgz", + "integrity": "sha512-9Zli9dikX8GdHwBb5/WPzpSVuy3EWMKY3P4EokCQra31fD7DLizqAAaTUsFwnK7xYkw5ogpHgelw9uKHHzNajg==", "dev": true, "requires": { "@types/node": "^12.12.6", @@ -26402,18 +28150,18 @@ } }, "web3-core": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-core/-/web3-core-1.8.2.tgz", - "integrity": "sha512-DJTVEAYcNqxkqruJE+Rxp3CIv0y5AZMwPHQmOkz/cz+MM75SIzMTc0AUdXzGyTS8xMF8h3YWMQGgGEy8SBf1PQ==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/web3-core/-/web3-core-1.9.0.tgz", + "integrity": "sha512-DZ+TPmq/ZLlx4LSVzFgrHCP/QFpKDbGWO4HoquZSdu24cjk5SZ+FEU1SZB2OaK3/bgBh+25mRbmv8y56ysUu1w==", "dev": true, "requires": { - "@types/bn.js": "^5.1.0", + "@types/bn.js": "^5.1.1", "@types/node": "^12.12.6", "bignumber.js": "^9.0.0", - "web3-core-helpers": "1.8.2", - "web3-core-method": "1.8.2", - "web3-core-requestmanager": "1.8.2", - "web3-utils": "1.8.2" + "web3-core-helpers": "1.9.0", + "web3-core-method": "1.9.0", + "web3-core-requestmanager": "1.9.0", + "web3-utils": "1.9.0" }, "dependencies": { "bignumber.js": { @@ -26425,94 +28173,94 @@ } }, "web3-core-helpers": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-core-helpers/-/web3-core-helpers-1.8.2.tgz", - "integrity": "sha512-6B1eLlq9JFrfealZBomd1fmlq1o4A09vrCVQSa51ANoib/jllT3atZrRDr0zt1rfI7TSZTZBXdN/aTdeN99DWw==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/web3-core-helpers/-/web3-core-helpers-1.9.0.tgz", + "integrity": "sha512-NeJzylAp9Yj9xAt2uTT+kyug3X0DLnfBdnAcGZuY6HhoNPDIfQRA9CkJjLngVRlGTLZGjNp9x9eR+RyZQgUlXg==", "dev": true, "requires": { - "web3-eth-iban": "1.8.2", - "web3-utils": "1.8.2" + "web3-eth-iban": "1.9.0", + "web3-utils": "1.9.0" } }, "web3-core-method": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-core-method/-/web3-core-method-1.8.2.tgz", - "integrity": "sha512-1qnr5mw5wVyULzLOrk4B+ryO3gfGjGd/fx8NR+J2xCGLf1e6OSjxT9vbfuQ3fErk/NjSTWWreieYWLMhaogcRA==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/web3-core-method/-/web3-core-method-1.9.0.tgz", + "integrity": "sha512-sswbNsY2xRBBhGeaLt9c/eDc+0yDDhi6keUBAkgIRa9ueSx/VKzUY9HMqiV6bXDcGT2fJyejq74FfEB4lc/+/w==", "dev": true, "requires": { "@ethersproject/transactions": "^5.6.2", - "web3-core-helpers": "1.8.2", - "web3-core-promievent": "1.8.2", - "web3-core-subscriptions": "1.8.2", - "web3-utils": "1.8.2" + "web3-core-helpers": "1.9.0", + "web3-core-promievent": "1.9.0", + "web3-core-subscriptions": "1.9.0", + "web3-utils": "1.9.0" } }, "web3-core-promievent": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-core-promievent/-/web3-core-promievent-1.8.2.tgz", - "integrity": "sha512-nvkJWDVgoOSsolJldN33tKW6bKKRJX3MCPDYMwP5SUFOA/mCzDEoI88N0JFofDTXkh1k7gOqp1pvwi9heuaxGg==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/web3-core-promievent/-/web3-core-promievent-1.9.0.tgz", + "integrity": "sha512-PHG1Mn23IGwMZhnPDN8dETKypqsFbHfiyRqP+XsVMPmTHkVfzDQTCBU/c2r6hUktBDoGKut5xZQpGfhFk71KbQ==", "dev": true, "requires": { "eventemitter3": "4.0.4" } }, "web3-core-requestmanager": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-core-requestmanager/-/web3-core-requestmanager-1.8.2.tgz", - "integrity": "sha512-p1d090RYs5Mu7DK1yyc3GCBVZB/03rBtFhYFoS2EruGzOWs/5Q0grgtpwS/DScdRAm8wB8mYEBhY/RKJWF6B2g==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/web3-core-requestmanager/-/web3-core-requestmanager-1.9.0.tgz", + "integrity": "sha512-hcJ5PCtTIJpj+8qWxoseqlCovDo94JJjTX7dZOLXgwp8ah7E3WRYozhGyZocerx+KebKyg1mCQIhkDpMwjfo9Q==", "dev": true, "requires": { "util": "^0.12.5", - "web3-core-helpers": "1.8.2", - "web3-providers-http": "1.8.2", - "web3-providers-ipc": "1.8.2", - "web3-providers-ws": "1.8.2" + "web3-core-helpers": "1.9.0", + "web3-providers-http": "1.9.0", + "web3-providers-ipc": "1.9.0", + "web3-providers-ws": "1.9.0" } }, "web3-core-subscriptions": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-core-subscriptions/-/web3-core-subscriptions-1.8.2.tgz", - "integrity": "sha512-vXQogHDmAIQcKpXvGiMddBUeP9lnKgYF64+yQJhPNE5PnWr1sAibXuIPV7mIPihpFr/n/DORRj6Wh1pUv9zaTw==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/web3-core-subscriptions/-/web3-core-subscriptions-1.9.0.tgz", + "integrity": "sha512-MaIo29yz7hTV8X8bioclPDbHFOVuHmnbMv+D3PDH12ceJFJAXGyW8GL5KU1DYyWIj4TD1HM4WknyVA/YWBiiLA==", "dev": true, "requires": { "eventemitter3": "4.0.4", - "web3-core-helpers": "1.8.2" + "web3-core-helpers": "1.9.0" } }, "web3-eth": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-eth/-/web3-eth-1.8.2.tgz", - "integrity": "sha512-JoTiWWc4F4TInpbvDUGb0WgDYJsFhuIjJlinc5ByjWD88Gvh+GKLsRjjFdbqe5YtwIGT4NymwoC5LQd1K6u/QQ==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/web3-eth/-/web3-eth-1.9.0.tgz", + "integrity": "sha512-c5gSWk9bLNr6VPATHmZ1n7LTIefIZQnJMzfnvkoBcIFGKJbGmsuRhv6lEXsKdAO/FlqYnSbaw3fOq1fVFiIOFQ==", "dev": true, "requires": { - "web3-core": "1.8.2", - "web3-core-helpers": "1.8.2", - "web3-core-method": "1.8.2", - "web3-core-subscriptions": "1.8.2", - "web3-eth-abi": "1.8.2", - "web3-eth-accounts": "1.8.2", - "web3-eth-contract": "1.8.2", - "web3-eth-ens": "1.8.2", - "web3-eth-iban": "1.8.2", - "web3-eth-personal": "1.8.2", - "web3-net": "1.8.2", - "web3-utils": "1.8.2" + "web3-core": "1.9.0", + "web3-core-helpers": "1.9.0", + "web3-core-method": "1.9.0", + "web3-core-subscriptions": "1.9.0", + "web3-eth-abi": "1.9.0", + "web3-eth-accounts": "1.9.0", + "web3-eth-contract": "1.9.0", + "web3-eth-ens": "1.9.0", + "web3-eth-iban": "1.9.0", + "web3-eth-personal": "1.9.0", + "web3-net": "1.9.0", + "web3-utils": "1.9.0" } }, "web3-eth-abi": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-eth-abi/-/web3-eth-abi-1.8.2.tgz", - "integrity": "sha512-Om9g3kaRNjqiNPAgKwGT16y+ZwtBzRe4ZJFGjLiSs6v5I7TPNF+rRMWuKnR6jq0azQZDj6rblvKFMA49/k48Og==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/web3-eth-abi/-/web3-eth-abi-1.9.0.tgz", + "integrity": "sha512-0BLQ3FKMrzJkA930jOX3fMaybAyubk06HChclLpiR0NWmgWXm1tmBrJdkyRy2ZTZpmfuZc9xTFRfl0yZID1voA==", "dev": true, "requires": { "@ethersproject/abi": "^5.6.3", - "web3-utils": "1.8.2" + "web3-utils": "1.9.0" } }, "web3-eth-accounts": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-eth-accounts/-/web3-eth-accounts-1.8.2.tgz", - "integrity": "sha512-c367Ij63VCz9YdyjiHHWLFtN85l6QghgwMQH2B1eM/p9Y5lTlTX7t/Eg/8+f1yoIStXbk2w/PYM2lk+IkbqdLA==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/web3-eth-accounts/-/web3-eth-accounts-1.9.0.tgz", + "integrity": "sha512-VeIZVevmnSll0AC1k5F/y398ZE89d1SRuYk8IewLUhL/tVAsFEsjl2SGgm0+aDcHmgPrkW+qsCJ+C7rWg/N4ZA==", "dev": true, "requires": { "@ethereumjs/common": "2.5.0", @@ -26521,10 +28269,10 @@ "ethereumjs-util": "^7.1.5", "scrypt-js": "^3.0.1", "uuid": "^9.0.0", - "web3-core": "1.8.2", - "web3-core-helpers": "1.8.2", - "web3-core-method": "1.8.2", - "web3-utils": "1.8.2" + "web3-core": "1.9.0", + "web3-core-helpers": "1.9.0", + "web3-core-method": "1.9.0", + "web3-utils": "1.9.0" }, "dependencies": { "eth-lib": { @@ -26547,45 +28295,45 @@ } }, "web3-eth-contract": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-eth-contract/-/web3-eth-contract-1.8.2.tgz", - "integrity": "sha512-ID5A25tHTSBNwOPjiXSVzxruz006ULRIDbzWTYIFTp7NJ7vXu/kynKK2ag/ObuTqBpMbobP8nXcA9b5EDkIdQA==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/web3-eth-contract/-/web3-eth-contract-1.9.0.tgz", + "integrity": "sha512-+j26hpSaEtAdUed0TN5rnc+YZOcjPxMjFX4ZBKatvFkImdbVv/tzTvcHlltubSpgb2ZLyZ89lSL6phKYwd2zNQ==", "dev": true, "requires": { - "@types/bn.js": "^5.1.0", - "web3-core": "1.8.2", - "web3-core-helpers": "1.8.2", - "web3-core-method": "1.8.2", - "web3-core-promievent": "1.8.2", - "web3-core-subscriptions": "1.8.2", - "web3-eth-abi": "1.8.2", - "web3-utils": "1.8.2" + "@types/bn.js": "^5.1.1", + "web3-core": "1.9.0", + "web3-core-helpers": "1.9.0", + "web3-core-method": "1.9.0", + "web3-core-promievent": "1.9.0", + "web3-core-subscriptions": "1.9.0", + "web3-eth-abi": "1.9.0", + "web3-utils": "1.9.0" } }, "web3-eth-ens": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-eth-ens/-/web3-eth-ens-1.8.2.tgz", - "integrity": "sha512-PWph7C/CnqdWuu1+SH4U4zdrK4t2HNt0I4XzPYFdv9ugE8EuojselioPQXsVGvjql+Nt3jDLvQvggPqlMbvwRw==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/web3-eth-ens/-/web3-eth-ens-1.9.0.tgz", + "integrity": "sha512-LOJZeN+AGe9arhuExnrPPFYQr4WSxXEkpvYIlst/joOEUNLDwfndHnJIK6PI5mXaYSROBtTx6erv+HupzGo7vA==", "dev": true, "requires": { "content-hash": "^2.5.2", "eth-ens-namehash": "2.0.8", - "web3-core": "1.8.2", - "web3-core-helpers": "1.8.2", - "web3-core-promievent": "1.8.2", - "web3-eth-abi": "1.8.2", - "web3-eth-contract": "1.8.2", - "web3-utils": "1.8.2" + "web3-core": "1.9.0", + "web3-core-helpers": "1.9.0", + "web3-core-promievent": "1.9.0", + "web3-eth-abi": "1.9.0", + "web3-eth-contract": "1.9.0", + "web3-utils": "1.9.0" } }, "web3-eth-iban": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-eth-iban/-/web3-eth-iban-1.8.2.tgz", - "integrity": "sha512-h3vNblDWkWMuYx93Q27TAJz6lhzpP93EiC3+45D6xoz983p6si773vntoQ+H+5aZhwglBtoiBzdh7PSSOnP/xQ==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/web3-eth-iban/-/web3-eth-iban-1.9.0.tgz", + "integrity": "sha512-jPAm77PuEs1kE/UrrBFJdPD2PN42pwfXA0gFuuw35bZezhskYML9W4QCxcqnUtceyEA4FUn7K2qTMuCk+23fog==", "dev": true, "requires": { "bn.js": "^5.2.1", - "web3-utils": "1.8.2" + "web3-utils": "1.9.0" }, "dependencies": { "bn.js": { @@ -26597,79 +28345,79 @@ } }, "web3-eth-personal": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-eth-personal/-/web3-eth-personal-1.8.2.tgz", - "integrity": "sha512-Vg4HfwCr7doiUF/RC+Jz0wT4+cYaXcOWMAW2AHIjHX6Z7Xwa8nrURIeQgeEE62qcEHAzajyAdB1u6bJyTfuCXw==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/web3-eth-personal/-/web3-eth-personal-1.9.0.tgz", + "integrity": "sha512-r9Ldo/luBqJlv1vCUEQnUS+C3a3ZdbYxVHyfDkj6RWMyCqqo8JE41HWE+pfa0RmB1xnGL2g8TbYcHcqItck/qg==", "dev": true, "requires": { "@types/node": "^12.12.6", - "web3-core": "1.8.2", - "web3-core-helpers": "1.8.2", - "web3-core-method": "1.8.2", - "web3-net": "1.8.2", - "web3-utils": "1.8.2" + "web3-core": "1.9.0", + "web3-core-helpers": "1.9.0", + "web3-core-method": "1.9.0", + "web3-net": "1.9.0", + "web3-utils": "1.9.0" } }, "web3-net": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-net/-/web3-net-1.8.2.tgz", - "integrity": "sha512-1itkDMGmbgb83Dg9nporFes9/fxsU7smJ3oRXlFkg4ZHn8YJyP1MSQFPJWWwSc+GrcCFt4O5IrUTvEkHqE3xag==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/web3-net/-/web3-net-1.9.0.tgz", + "integrity": "sha512-L+fDZFgrLM5Y15aonl2q6L+RvfaImAngmC0Jv45hV2FJ5IfRT0/2ob9etxZmvEBWvOpbqSvghfOhJIT3XZ37Pg==", "dev": true, "requires": { - "web3-core": "1.8.2", - "web3-core-method": "1.8.2", - "web3-utils": "1.8.2" + "web3-core": "1.9.0", + "web3-core-method": "1.9.0", + "web3-utils": "1.9.0" } }, "web3-providers-http": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-providers-http/-/web3-providers-http-1.8.2.tgz", - "integrity": "sha512-2xY94IIEQd16+b+vIBF4IC1p7GVaz9q4EUFscvMUjtEq4ru4Atdzjs9GP+jmcoo49p70II0UV3bqQcz0TQfVyQ==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/web3-providers-http/-/web3-providers-http-1.9.0.tgz", + "integrity": "sha512-5+dMNDAE0rRFz6SJpfnBqlVi2J5bB/Ivr2SanMt2YUrkxW5t8betZbzVwRkTbwtUvkqgj3xeUQzqpOttiv+IqQ==", "dev": true, "requires": { "abortcontroller-polyfill": "^1.7.3", "cross-fetch": "^3.1.4", "es6-promise": "^4.2.8", - "web3-core-helpers": "1.8.2" + "web3-core-helpers": "1.9.0" } }, "web3-providers-ipc": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-providers-ipc/-/web3-providers-ipc-1.8.2.tgz", - "integrity": "sha512-p6fqKVGFg+WiXGHWnB1hu43PbvPkDHTz4RgoEzbXugv5rtv5zfYLqm8Ba6lrJOS5ks9kGKR21a0y3NzE3u7V4w==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/web3-providers-ipc/-/web3-providers-ipc-1.9.0.tgz", + "integrity": "sha512-cPXU93Du40HCylvjaa5x62DbnGqH+86HpK/+kMcFIzF6sDUBhKpag2tSbYhGbj7GMpfkmDTUiiMLdWnFV6+uBA==", "dev": true, "requires": { "oboe": "2.1.5", - "web3-core-helpers": "1.8.2" + "web3-core-helpers": "1.9.0" } }, "web3-providers-ws": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-providers-ws/-/web3-providers-ws-1.8.2.tgz", - "integrity": "sha512-3s/4K+wHgbiN+Zrp9YjMq2eqAF6QGABw7wFftPdx+m5hWImV27/MoIx57c6HffNRqZXmCHnfWWFCNHHsi7wXnA==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/web3-providers-ws/-/web3-providers-ws-1.9.0.tgz", + "integrity": "sha512-JRVsnQZ7j2k1a2yzBNHe39xqk1ijOv01dfIBFw52VeEkSRzvrOcsPIM/ttSyBuJqt70ntMxXY0ekCrqfleKH/w==", "dev": true, "requires": { "eventemitter3": "4.0.4", - "web3-core-helpers": "1.8.2", + "web3-core-helpers": "1.9.0", "websocket": "^1.0.32" } }, "web3-shh": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-shh/-/web3-shh-1.8.2.tgz", - "integrity": "sha512-uZ+3MAoNcaJsXXNCDnizKJ5viBNeHOFYsCbFhV755Uu52FswzTOw6DtE7yK9nYXMtIhiSgi7nwl1RYzP8pystw==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/web3-shh/-/web3-shh-1.9.0.tgz", + "integrity": "sha512-bIBZlralgz4ICCrwkefB2nPPJWfx28NuHIpjB7d9ADKynElubQuqudYhKtSEkKXACuME/BJm0pIFJcJs/gDnMg==", "dev": true, "requires": { - "web3-core": "1.8.2", - "web3-core-method": "1.8.2", - "web3-core-subscriptions": "1.8.2", - "web3-net": "1.8.2" + "web3-core": "1.9.0", + "web3-core-method": "1.9.0", + "web3-core-subscriptions": "1.9.0", + "web3-net": "1.9.0" } }, "web3-utils": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.8.2.tgz", - "integrity": "sha512-v7j6xhfLQfY7xQDrUP0BKbaNrmZ2/+egbqP9q3KYmOiPpnvAfol+32slgL0WX/5n8VPvKCK5EZ1HGrAVICSToA==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.9.0.tgz", + "integrity": "sha512-p++69rCNNfu2jM9n5+VD/g26l+qkEOQ1m6cfRQCbH8ZRrtquTmrirJMgTmyOoax5a5XRYOuws14aypCOs51pdQ==", "dev": true, "requires": { "bn.js": "^5.2.1", @@ -26759,9 +28507,9 @@ } }, "which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", "dev": true }, "which-pm": { @@ -26970,9 +28718,9 @@ "dev": true }, "yargs": { - "version": "17.7.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", - "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, "requires": { "cliui": "^8.0.1", From 34d926dd7e8ba8b6fb306d88a319368050a34bc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Tue, 9 May 2023 18:19:35 +0200 Subject: [PATCH 051/182] Implement extra suggestions from audit review of 4.9 (#4224) --- .../proxy/transparent/TransparentUpgradeableProxy.sol | 2 ++ contracts/utils/Checkpoints.sol | 8 ++++---- contracts/utils/ShortStrings.sol | 10 ++++++++-- scripts/generate/templates/Checkpoints.js | 4 ++-- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/contracts/proxy/transparent/TransparentUpgradeableProxy.sol b/contracts/proxy/transparent/TransparentUpgradeableProxy.sol index 4e2b0c759..e49768ab9 100644 --- a/contracts/proxy/transparent/TransparentUpgradeableProxy.sol +++ b/contracts/proxy/transparent/TransparentUpgradeableProxy.sol @@ -174,6 +174,8 @@ contract TransparentUpgradeableProxy is ERC1967Proxy { /** * @dev Returns the current admin. + * + * CAUTION: This function is deprecated. Use {ERC1967Upgrade-_getAdmin} instead. */ function _admin() internal view virtual returns (address) { return _getAdmin(); diff --git a/contracts/utils/Checkpoints.sol b/contracts/utils/Checkpoints.sol index 27da4b517..1bb66dec9 100644 --- a/contracts/utils/Checkpoints.sol +++ b/contracts/utils/Checkpoints.sol @@ -234,7 +234,7 @@ library Checkpoints { } /** - * @dev Returns the value in the last (most recent) checkpoint with key lower or equal than the search key. + * @dev Returns the value in the last (most recent) checkpoint with key lower or equal than the search key, or zero if there is none. */ function upperLookup(Trace224 storage self, uint32 key) internal view returns (uint224) { uint256 len = self._checkpoints.length; @@ -243,7 +243,7 @@ library Checkpoints { } /** - * @dev Returns the value in the last (most recent) checkpoint with key lower or equal than the search key. + * @dev Returns the value in the last (most recent) checkpoint with key lower or equal than the search key, or zero if there is none. * * NOTE: This is a variant of {upperLookup} that is optimised to find "recent" checkpoint (checkpoints with high keys). */ @@ -410,7 +410,7 @@ library Checkpoints { } /** - * @dev Returns the value in the last (most recent) checkpoint with key lower or equal than the search key. + * @dev Returns the value in the last (most recent) checkpoint with key lower or equal than the search key, or zero if there is none. */ function upperLookup(Trace160 storage self, uint96 key) internal view returns (uint160) { uint256 len = self._checkpoints.length; @@ -419,7 +419,7 @@ library Checkpoints { } /** - * @dev Returns the value in the last (most recent) checkpoint with key lower or equal than the search key. + * @dev Returns the value in the last (most recent) checkpoint with key lower or equal than the search key, or zero if there is none. * * NOTE: This is a variant of {upperLookup} that is optimised to find "recent" checkpoint (checkpoints with high keys). */ diff --git a/contracts/utils/ShortStrings.sol b/contracts/utils/ShortStrings.sol index 450c7985e..a8904777a 100644 --- a/contracts/utils/ShortStrings.sol +++ b/contracts/utils/ShortStrings.sol @@ -4,13 +4,18 @@ pragma solidity ^0.8.8; import "./StorageSlot.sol"; +// | string | 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA | +// | length | 0x BB | type ShortString is bytes32; /** * @dev This library provides functions to convert short memory strings * into a `ShortString` type that can be used as an immutable variable. - * Strings of arbitrary length can be optimized if they are short enough by - * the addition of a storage variable used as fallback. + * + * Strings of arbitrary length can be optimized using this library if + * they are short enough (up to 31 bytes) by packing them with their + * length (1 byte) in a single EVM word (32 bytes). Additionally, a + * fallback mechanism can be used for every other case. * * Usage example: * @@ -32,6 +37,7 @@ type ShortString is bytes32; * ``` */ library ShortStrings { + // Used as an identifier for strings longer than 31 bytes. bytes32 private constant _FALLBACK_SENTINEL = 0x00000000000000000000000000000000000000000000000000000000000000FF; error StringTooLong(string str); diff --git a/scripts/generate/templates/Checkpoints.js b/scripts/generate/templates/Checkpoints.js index 1d79031da..8c9f1f850 100644 --- a/scripts/generate/templates/Checkpoints.js +++ b/scripts/generate/templates/Checkpoints.js @@ -55,7 +55,7 @@ function lowerLookup(${opts.historyTypeName} storage self, ${opts.keyTypeName} k } /** - * @dev Returns the value in the last (most recent) checkpoint with key lower or equal than the search key. + * @dev Returns the value in the last (most recent) checkpoint with key lower or equal than the search key, or zero if there is none. */ function upperLookup(${opts.historyTypeName} storage self, ${opts.keyTypeName} key) internal view returns (${opts.valueTypeName}) { uint256 len = self.${opts.checkpointFieldName}.length; @@ -64,7 +64,7 @@ function upperLookup(${opts.historyTypeName} storage self, ${opts.keyTypeName} k } /** - * @dev Returns the value in the last (most recent) checkpoint with key lower or equal than the search key. + * @dev Returns the value in the last (most recent) checkpoint with key lower or equal than the search key, or zero if there is none. * * NOTE: This is a variant of {upperLookup} that is optimised to find "recent" checkpoint (checkpoints with high keys). */ From 51294b7480fd13e716207a621ac1d55a6290d56d Mon Sep 17 00:00:00 2001 From: Francisco Date: Tue, 9 May 2023 19:52:23 +0100 Subject: [PATCH 052/182] Make transpilation setup local to this repo (#4041) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ernesto García --- .github/workflows/checks.yml | 27 +- .github/workflows/upgradeable.yml | 31 +- contracts/utils/cryptography/EIP712.sol | 6 +- hardhat.config.js | 5 +- hardhat/env-artifacts.js | 24 ++ hardhat/task-test-get-files.js | 35 ++ package-lock.json | 14 +- package.json | 2 +- scripts/upgradeable/README.md | 21 + scripts/upgradeable/patch-apply.sh | 19 + scripts/upgradeable/patch-save.sh | 18 + scripts/upgradeable/transpile-onto.sh | 44 +++ scripts/upgradeable/transpile.sh | 35 ++ scripts/upgradeable/upgradeable.patch | 481 +++++++++++++++++++++++ test/migrate-imports.test.js | 1 + test/utils/Checkpoints.test.js | 161 ++++---- test/utils/cryptography/EIP712.test.js | 6 +- test/utils/structs/EnumerableMap.test.js | 93 ++--- test/utils/structs/EnumerableSet.test.js | 33 +- 19 files changed, 894 insertions(+), 162 deletions(-) create mode 100644 hardhat/env-artifacts.js create mode 100644 hardhat/task-test-get-files.js create mode 100644 scripts/upgradeable/README.md create mode 100755 scripts/upgradeable/patch-apply.sh create mode 100755 scripts/upgradeable/patch-save.sh create mode 100644 scripts/upgradeable/transpile-onto.sh create mode 100644 scripts/upgradeable/transpile.sh create mode 100644 scripts/upgradeable/upgradeable.patch diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 2d5c15159..ae48e9286 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -14,7 +14,6 @@ concurrency: jobs: lint: - if: github.repository != 'OpenZeppelin/openzeppelin-contracts-upgradeable' runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 @@ -37,19 +36,36 @@ jobs: - name: Check linearisation of the inheritance graph run: npm run test:inheritance - name: Check proceduraly generated contracts are up-to-date - if: github.repository != 'OpenZeppelin/openzeppelin-contracts-upgradeable' run: npm run test:generation - name: Compare gas costs uses: ./.github/actions/gas-compare with: token: ${{ github.token }} + + tests-upgradeable: + runs-on: ubuntu-latest + env: + FORCE_COLOR: 1 + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 # Include history so patch conflicts are resolved automatically + - name: Set up environment + uses: ./.github/actions/setup + - name: Transpile to upgradeable + run: bash scripts/upgradeable/transpile.sh + - name: Run tests + run: npm run test + env: + NODE_OPTIONS: --max_old_space_size=4096 + - name: Check linearisation of the inheritance graph + run: npm run test:inheritance - name: Check storage layout uses: ./.github/actions/storage-layout with: token: ${{ github.token }} - foundry-tests: - if: github.repository != 'OpenZeppelin/openzeppelin-contracts-upgradeable' + tests-foundry: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 @@ -63,7 +79,6 @@ jobs: run: forge test -vv coverage: - if: github.repository != 'OpenZeppelin/openzeppelin-contracts-upgradeable' runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 @@ -77,7 +92,6 @@ jobs: token: ${{ secrets.CODECOV_TOKEN }} slither: - if: github.repository != 'OpenZeppelin/openzeppelin-contracts-upgradeable' runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 @@ -89,7 +103,6 @@ jobs: node-version: 18.15 codespell: - if: github.repository != 'OpenZeppelin/openzeppelin-contracts-upgradeable' runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 diff --git a/.github/workflows/upgradeable.yml b/.github/workflows/upgradeable.yml index a7ed8da88..649596abb 100644 --- a/.github/workflows/upgradeable.yml +++ b/.github/workflows/upgradeable.yml @@ -1,4 +1,4 @@ -name: Upgradeable Trigger +name: transpile upgradeable on: push: @@ -7,17 +7,24 @@ on: - release-v* jobs: - trigger: + transpile: + environment: push-upgradeable runs-on: ubuntu-latest steps: - - id: app - uses: getsentry/action-github-app-token@v2 + - uses: actions/checkout@v3 with: - app_id: ${{ secrets.UPGRADEABLE_APP_ID }} - private_key: ${{ secrets.UPGRADEABLE_APP_PK }} - - run: | - curl -X POST \ - https://api.github.com/repos/OpenZeppelin/openzeppelin-contracts-upgradeable/dispatches \ - -H 'Accept: application/vnd.github.v3+json' \ - -H 'Authorization: token ${{ steps.app.outputs.token }}' \ - -d '{ "event_type": "Update", "client_payload": { "ref": "${{ github.ref }}" } }' + repository: OpenZeppelin/openzeppelin-contracts-upgradeable + fetch-depth: 0 + token: ${{ secrets.GH_TOKEN_UPGRADEABLE }} + - name: Fetch current non-upgradeable branch + run: | + git fetch "https://github.com/${{ github.repository }}.git" "$REF" + git checkout FETCH_HEAD + env: + REF: ${{ github.ref }} + - name: Set up environment + uses: ./.github/actions/setup + - run: bash scripts/git-user-config.sh + - name: Transpile to upgradeable + run: bash scripts/upgradeable/transpile-onto.sh ${{ github.ref_name }} origin/${{ github.ref_name }} + - run: git push origin ${{ github.ref_name }} diff --git a/contracts/utils/cryptography/EIP712.sol b/contracts/utils/cryptography/EIP712.sol index 56dfceaf4..6a4e1cad2 100644 --- a/contracts/utils/cryptography/EIP712.sol +++ b/contracts/utils/cryptography/EIP712.sol @@ -44,14 +44,14 @@ abstract contract EIP712 is IERC5267 { uint256 private immutable _cachedChainId; address private immutable _cachedThis; + bytes32 private immutable _hashedName; + bytes32 private immutable _hashedVersion; + ShortString private immutable _name; ShortString private immutable _version; string private _nameFallback; string private _versionFallback; - bytes32 private immutable _hashedName; - bytes32 private immutable _hashedVersion; - /** * @dev Initializes the domain separator and parameter caches. * diff --git a/hardhat.config.js b/hardhat.config.js index 32f721b65..639e10f95 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -89,10 +89,11 @@ module.exports = { }, }, exposed: { + initializers: true, exclude: [ 'vendor/**/*', - // overflow clash - 'utils/Timers.sol', + // Exclude Timers from hardhat-exposed because its overloaded functions are not transformed correctly + 'utils/Timers{,Upgradeable}.sol', ], }, docgen: require('./docs/config'), diff --git a/hardhat/env-artifacts.js b/hardhat/env-artifacts.js new file mode 100644 index 000000000..fbbea2e2d --- /dev/null +++ b/hardhat/env-artifacts.js @@ -0,0 +1,24 @@ +const { HardhatError } = require('hardhat/internal/core/errors'); + +// Modifies `artifacts.require(X)` so that instead of X it loads the XUpgradeable contract. +// This allows us to run the same test suite on both the original and the transpiled and renamed Upgradeable contracts. + +extendEnvironment(env => { + const artifactsRequire = env.artifacts.require; + + env.artifacts.require = name => { + for (const suffix of ['UpgradeableWithInit', 'Upgradeable', '']) { + try { + return artifactsRequire(name + suffix); + } catch (e) { + // HH700: Artifact not found - from https://hardhat.org/hardhat-runner/docs/errors#HH700 + if (HardhatError.isHardhatError(e) && e.number === 700 && suffix !== '') { + continue; + } else { + throw e; + } + } + } + throw new Error('Unreachable'); + }; +}); diff --git a/hardhat/task-test-get-files.js b/hardhat/task-test-get-files.js new file mode 100644 index 000000000..896bf1274 --- /dev/null +++ b/hardhat/task-test-get-files.js @@ -0,0 +1,35 @@ +const { internalTask } = require('hardhat/config'); +const { TASK_TEST_GET_TEST_FILES } = require('hardhat/builtin-tasks/task-names'); + +// Modifies `hardhat test` to skip the proxy tests after proxies are removed by the transpiler for upgradeability. + +internalTask(TASK_TEST_GET_TEST_FILES).setAction(async ({ testFiles }, { config }) => { + if (testFiles.length !== 0) { + return testFiles; + } + + const globAsync = require('glob'); + const path = require('path'); + const { promises: fs } = require('fs'); + const { promisify } = require('util'); + + const glob = promisify(globAsync); + + const hasProxies = await fs + .access(path.join(config.paths.sources, 'proxy/Proxy.sol')) + .then(() => true) + .catch(() => false); + + return await glob(path.join(config.paths.tests, '**/*.js'), { + ignore: hasProxies + ? [] + : [ + 'proxy/beacon/BeaconProxy.test.js', + 'proxy/beacon/UpgradeableBeacon.test.js', + 'proxy/ERC1967/ERC1967Proxy.test.js', + 'proxy/transparent/ProxyAdmin.test.js', + 'proxy/transparent/TransparentUpgradeableProxy.test.js', + 'proxy/utils/UUPSUpgradeable.test.js', + ].map(p => path.join(config.paths.tests, p)), + }); +}); diff --git a/package-lock.json b/package-lock.json index 2219b08f2..6611223fe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,7 +31,7 @@ "glob": "^8.0.3", "graphlib": "^2.1.8", "hardhat": "^2.9.1", - "hardhat-exposed": "^0.3.2", + "hardhat-exposed": "^0.3.3", "hardhat-gas-reporter": "^1.0.4", "hardhat-ignore-warnings": "^0.2.0", "keccak256": "^1.0.2", @@ -8269,9 +8269,9 @@ } }, "node_modules/hardhat-exposed": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/hardhat-exposed/-/hardhat-exposed-0.3.2.tgz", - "integrity": "sha512-qhi60I2bfjoPZwKgrY7BIpuUiBE7aC/bHN2MzHxPcZdxaeFnjKJ50n59LE7yK3GK2qYzE8DMjzqfnH6SlKPUjw==", + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/hardhat-exposed/-/hardhat-exposed-0.3.3.tgz", + "integrity": "sha512-AFBM3IUlSU9wkt3886kYbCSzZteB4OQt0ciehPUrCgY/Tazn6g2cxsmoIZT8O8va1R2PtXd9PRC4KZ6WiSVXOw==", "dev": true, "dependencies": { "micromatch": "^4.0.4", @@ -22857,9 +22857,9 @@ } }, "hardhat-exposed": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/hardhat-exposed/-/hardhat-exposed-0.3.2.tgz", - "integrity": "sha512-qhi60I2bfjoPZwKgrY7BIpuUiBE7aC/bHN2MzHxPcZdxaeFnjKJ50n59LE7yK3GK2qYzE8DMjzqfnH6SlKPUjw==", + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/hardhat-exposed/-/hardhat-exposed-0.3.3.tgz", + "integrity": "sha512-AFBM3IUlSU9wkt3886kYbCSzZteB4OQt0ciehPUrCgY/Tazn6g2cxsmoIZT8O8va1R2PtXd9PRC4KZ6WiSVXOw==", "dev": true, "requires": { "micromatch": "^4.0.4", diff --git a/package.json b/package.json index a3b98fe37..8ef2738b5 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,7 @@ "glob": "^8.0.3", "graphlib": "^2.1.8", "hardhat": "^2.9.1", - "hardhat-exposed": "^0.3.2", + "hardhat-exposed": "^0.3.3", "hardhat-gas-reporter": "^1.0.4", "hardhat-ignore-warnings": "^0.2.0", "keccak256": "^1.0.2", diff --git a/scripts/upgradeable/README.md b/scripts/upgradeable/README.md new file mode 100644 index 000000000..2309f9e10 --- /dev/null +++ b/scripts/upgradeable/README.md @@ -0,0 +1,21 @@ +The upgradeable variant of OpenZeppelin Contracts is automatically generated from the original Solidity code. We call this process "transpilation" and it is implemented by our [Upgradeability Transpiler](https://github.com/OpenZeppelin/openzeppelin-transpiler/). + +When the `master` branch or `release-v*` branches are updated, the code is transpiled and pushed to [OpenZeppelin/openzeppelin-contracts-upgradeable](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable) by the `upgradeable.yml` workflow. + +## `transpile.sh` + +Applies patches and invokes the transpiler with the command line flags we need for our requirements (for example, excluding certain files). + +## `transpile-onto.sh` + +``` +bash scripts/upgradeable/transpile-onto.sh [] +``` + +Transpiles the contents of the current git branch and commits the result as a new commit on branch ``. If branch `` doesn't exist, it will copy the commit history of `[]` (this is used in GitHub Actions, but is usually not necessary locally). + +## `patch-apply.sh` & `patch-save.sh` + +Some of the upgradeable contract variants require ad-hoc changes that are not implemented by the transpiler. These changes are implemented by patches stored in `upgradeable.patch` in this directory. `patch-apply.sh` applies these patches. + +If the patches fail to apply due to changes in the repo, the conflicts have to be resolved manually. Once fixed, `patch-save.sh` will take the changes staged in Git and update `upgradeable.patch` to match. diff --git a/scripts/upgradeable/patch-apply.sh b/scripts/upgradeable/patch-apply.sh new file mode 100755 index 000000000..d9e17589b --- /dev/null +++ b/scripts/upgradeable/patch-apply.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +set -euo pipefail + +DIRNAME="$(dirname -- "${BASH_SOURCE[0]}")" +PATCH="$DIRNAME/upgradeable.patch" + +error() { + echo Error: "$*" >&2 + exit 1 +} + +if ! git diff-files --quiet ":!$PATCH" || ! git diff-index --quiet HEAD ":!$PATCH"; then + error "Repository must have no staged or unstaged changes" +fi + +if ! git apply -3 "$PATCH"; then + error "Fix conflicts and run $DIRNAME/patch-save.sh" +fi diff --git a/scripts/upgradeable/patch-save.sh b/scripts/upgradeable/patch-save.sh new file mode 100755 index 000000000..111e6f157 --- /dev/null +++ b/scripts/upgradeable/patch-save.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +set -euo pipefail + +DIRNAME="$(dirname -- "${BASH_SOURCE[0]}")" +PATCH="$DIRNAME/upgradeable.patch" + +error() { + echo Error: "$*" >&2 + exit 1 +} + +if ! git diff-files --quiet ":!$PATCH"; then + error "Unstaged changes. Stage to include in patch or temporarily stash." +fi + +git diff-index --cached --patch --output="$PATCH" HEAD +git restore --staged --worktree ":!$PATCH" diff --git a/scripts/upgradeable/transpile-onto.sh b/scripts/upgradeable/transpile-onto.sh new file mode 100644 index 000000000..a966a9b9a --- /dev/null +++ b/scripts/upgradeable/transpile-onto.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash + +set -euo pipefail + +if [ $# -lt 1 ]; then + echo "usage: bash $0 []" >&2 + exit 1 +fi + +set -x + +target="$1" +base="${2-}" + +bash scripts/upgradeable/transpile.sh + +commit="$(git rev-parse --short HEAD)" +branch="$(git rev-parse --abbrev-ref HEAD)" + +git add contracts + +# detach from the current branch to avoid making changes to it +git checkout --quiet --detach + +# switch to the target branch, creating it if necessary +if git rev-parse -q --verify "$target"; then + # if the branch exists, make it the current HEAD without checking out its contents + git reset --soft "$target" + git checkout "$target" +else + # if the branch doesn't exist, create it as an orphan and check it out + git checkout --orphan "$target" + if [ -n "$base" ] && git rev-parse -q --verify "$base"; then + # if base was specified and it exists, set it as the branch history + git reset --soft "$base" + fi +fi + +# commit if there are changes to commit +if ! git diff --quiet --cached; then + git commit -m "Transpile $commit" +fi + +git checkout "$branch" diff --git a/scripts/upgradeable/transpile.sh b/scripts/upgradeable/transpile.sh new file mode 100644 index 000000000..e3aa31cc0 --- /dev/null +++ b/scripts/upgradeable/transpile.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash + +set -euo pipefail -x + +DIRNAME="$(dirname -- "${BASH_SOURCE[0]}")" + +bash "$DIRNAME/patch-apply.sh" + +npm run clean +npm run compile + +build_info=($(jq -r '.input.sources | keys | if any(test("^contracts/mocks/.*\\bunreachable\\b")) then empty else input_filename end' artifacts/build-info/*)) +build_info_num=${#build_info[@]} + +if [ $build_info_num -ne 1 ]; then + echo "found $build_info_num relevant build info files but expected just 1" + exit 1 +fi + +# -D: delete original and excluded files +# -b: use this build info file +# -i: use included Initializable +# -x: exclude proxy-related contracts with a few exceptions +# -p: emit public initializer +npx @openzeppelin/upgrade-safe-transpiler@latest -D \ + -b "$build_info" \ + -i contracts/proxy/utils/Initializable.sol \ + -x 'contracts-exposed/**/*' \ + -x 'contracts/proxy/**/*' \ + -x '!contracts/proxy/Clones.sol' \ + -x '!contracts/proxy/ERC1967/ERC1967Storage.sol' \ + -x '!contracts/proxy/ERC1967/ERC1967Upgrade.sol' \ + -x '!contracts/proxy/utils/UUPSUpgradeable.sol' \ + -x '!contracts/proxy/beacon/IBeacon.sol' \ + -p 'contracts/**/presets/**/*' diff --git a/scripts/upgradeable/upgradeable.patch b/scripts/upgradeable/upgradeable.patch new file mode 100644 index 000000000..e1988c8e9 --- /dev/null +++ b/scripts/upgradeable/upgradeable.patch @@ -0,0 +1,481 @@ +diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md +deleted file mode 100644 +index 2797a088..00000000 +--- a/.github/ISSUE_TEMPLATE/bug_report.md ++++ /dev/null +@@ -1,21 +0,0 @@ +---- +-name: Bug report +-about: Report a bug in OpenZeppelin Contracts +- +---- +- +- +- +- +- +-**💻 Environment** +- +- +- +-**📝 Details** +- +- +- +-**🔢 Code to reproduce bug** +- +- +diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml +index 4018cef2..d343a53d 100644 +--- a/.github/ISSUE_TEMPLATE/config.yml ++++ b/.github/ISSUE_TEMPLATE/config.yml +@@ -1,4 +1,8 @@ ++blank_issues_enabled: false + contact_links: ++ - name: Bug Reports & Feature Requests ++ url: https://github.com/OpenZeppelin/openzeppelin-contracts/issues/new/choose ++ about: Visit the OpenZeppelin Contracts repository + - name: Questions & Support Requests + url: https://forum.openzeppelin.com/c/support/contracts/18 + about: Ask in the OpenZeppelin Forum +diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md +deleted file mode 100644 +index ff596b0c..00000000 +--- a/.github/ISSUE_TEMPLATE/feature_request.md ++++ /dev/null +@@ -1,14 +0,0 @@ +---- +-name: Feature request +-about: Suggest an idea for OpenZeppelin Contracts +- +---- +- +-**🧐 Motivation** +- +- +-**📝 Details** +- +- +- +- +diff --git a/README.md b/README.md +index 9fc95518..53130e3c 100644 +--- a/README.md ++++ b/README.md +@@ -16,17 +16,20 @@ + + :building_construction: **Want to scale your decentralized application?** Check out [OpenZeppelin Defender](https://openzeppelin.com/defender) — a secure platform for automating and monitoring your operations. + ++> **Note** ++> You are looking at the upgradeable variant of OpenZeppelin Contracts. Be sure to review the documentation on [Using OpenZeppelin Contracts with Upgrades](https://docs.openzeppelin.com/contracts/4.x/upgradeable). ++ + ## Overview + + ### Installation + + ``` +-$ npm install @openzeppelin/contracts ++$ npm install @openzeppelin/contracts-upgradeable + ``` + + OpenZeppelin Contracts features a [stable API](https://docs.openzeppelin.com/contracts/releases-stability#api-stability), which means that your contracts won't break unexpectedly when upgrading to a newer minor version. + +-An alternative to npm is to use the GitHub repository (`openzeppelin/openzeppelin-contracts`) to retrieve the contracts. When doing this, make sure to specify the tag for a release such as `v4.5.0`, instead of using the `master` branch. ++An alternative to npm is to use the GitHub repository (`openzeppelin/openzeppelin-contracts-upgradeable`) to retrieve the contracts. When doing this, make sure to specify the tag for a release such as `v4.5.0`, instead of using the `master` branch. + + ### Usage + +@@ -35,10 +38,11 @@ Once installed, you can use the contracts in the library by importing them: + ```solidity + pragma solidity ^0.8.0; + +-import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; ++import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol"; + +-contract MyCollectible is ERC721 { +- constructor() ERC721("MyCollectible", "MCO") { ++contract MyCollectible is ERC721Upgradeable { ++ function initialize() initializer public { ++ __ERC721_init("MyCollectible", "MCO"); + } + } + ``` +diff --git a/contracts/finance/VestingWallet.sol b/contracts/finance/VestingWallet.sol +index fe67eb54..d26ea4e1 100644 +--- a/contracts/finance/VestingWallet.sol ++++ b/contracts/finance/VestingWallet.sol +@@ -15,6 +15,8 @@ import "../utils/Context.sol"; + * Any token transferred to this contract will follow the vesting schedule as if they were locked from the beginning. + * Consequently, if the vesting has already started, any amount of tokens sent to this contract will (at least partly) + * be immediately releasable. ++ * ++ * @custom:storage-size 52 + */ + contract VestingWallet is Context { + event EtherReleased(uint256 amount); +diff --git a/contracts/governance/TimelockControllerWith46Migration.sol b/contracts/governance/TimelockControllerWith46Migration.sol +new file mode 100644 +index 00000000..3315e7bd +--- /dev/null ++++ b/contracts/governance/TimelockControllerWith46Migration.sol +@@ -0,0 +1,39 @@ ++// SPDX-License-Identifier: MIT ++// OpenZeppelin Contracts v4.6.0 (governance/TimelockControllerWith46Migration.sol) ++ ++pragma solidity ^0.8.0; ++ ++import "./TimelockController.sol"; ++ ++/** ++ * @dev Extension of the TimelockController that includes an additional ++ * function to migrate from OpenZeppelin Upgradeable Contracts <4.6 to >=4.6. ++ * ++ * This migration is necessary to setup administration rights over the new ++ * `CANCELLER_ROLE`. ++ * ++ * The migration is trustless and can be performed by anyone. ++ * ++ * _Available since v4.6._ ++ */ ++contract TimelockControllerWith46Migration is TimelockController { ++ constructor( ++ uint256 minDelay, ++ address[] memory proposers, ++ address[] memory executors, ++ address admin ++ ) TimelockController(minDelay, proposers, executors, admin) {} ++ ++ /** ++ * @dev Migration function. Running it is necessary for upgradeable ++ * instances that were initially setup with code <4.6 and that upgraded ++ * to >=4.6. ++ */ ++ function migrateTo46() public virtual { ++ require( ++ getRoleAdmin(PROPOSER_ROLE) == TIMELOCK_ADMIN_ROLE && getRoleAdmin(CANCELLER_ROLE) == DEFAULT_ADMIN_ROLE, ++ "TimelockController: already migrated" ++ ); ++ _setRoleAdmin(CANCELLER_ROLE, TIMELOCK_ADMIN_ROLE); ++ } ++} +diff --git a/contracts/governance/extensions/GovernorVotes.sol b/contracts/governance/extensions/GovernorVotes.sol +index 64431711..885f0e42 100644 +--- a/contracts/governance/extensions/GovernorVotes.sol ++++ b/contracts/governance/extensions/GovernorVotes.sol +@@ -10,6 +10,8 @@ import "../../interfaces/IERC5805.sol"; + * @dev Extension of {Governor} for voting weight extraction from an {ERC20Votes} token, or since v4.5 an {ERC721Votes} token. + * + * _Available since v4.3._ ++ * ++ * @custom:storage-size 51 + */ + abstract contract GovernorVotes is Governor { + IERC5805 public immutable token; +diff --git a/contracts/governance/extensions/GovernorVotesComp.sol b/contracts/governance/extensions/GovernorVotesComp.sol +index 17250ad7..1d26b72e 100644 +--- a/contracts/governance/extensions/GovernorVotesComp.sol ++++ b/contracts/governance/extensions/GovernorVotesComp.sol +@@ -10,6 +10,8 @@ import "../../token/ERC20/extensions/ERC20VotesComp.sol"; + * @dev Extension of {Governor} for voting weight extraction from a Comp token. + * + * _Available since v4.3._ ++ * ++ * @custom:storage-size 51 + */ + abstract contract GovernorVotesComp is Governor { + ERC20VotesComp public immutable token; +diff --git a/contracts/package.json b/contracts/package.json +index 55e70b17..ceefb984 100644 +--- a/contracts/package.json ++++ b/contracts/package.json +@@ -1,5 +1,5 @@ + { +- "name": "@openzeppelin/contracts", ++ "name": "@openzeppelin/contracts-upgradeable", + "description": "Secure Smart Contract library for Solidity", + "version": "4.8.2", + "files": [ +@@ -13,7 +13,7 @@ + }, + "repository": { + "type": "git", +- "url": "https://github.com/OpenZeppelin/openzeppelin-contracts.git" ++ "url": "https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable.git" + }, + "keywords": [ + "solidity", +diff --git a/contracts/security/PullPayment.sol b/contracts/security/PullPayment.sol +index 65b4980f..f336592e 100644 +--- a/contracts/security/PullPayment.sol ++++ b/contracts/security/PullPayment.sol +@@ -22,6 +22,8 @@ import "../utils/escrow/Escrow.sol"; + * To use, derive from the `PullPayment` contract, and use {_asyncTransfer} + * instead of Solidity's `transfer` function. Payees can query their due + * payments with {payments}, and retrieve them with {withdrawPayments}. ++ * ++ * @custom:storage-size 51 + */ + abstract contract PullPayment { + Escrow private immutable _escrow; +diff --git a/contracts/token/ERC20/extensions/ERC20Capped.sol b/contracts/token/ERC20/extensions/ERC20Capped.sol +index 16f830d1..9ef98148 100644 +--- a/contracts/token/ERC20/extensions/ERC20Capped.sol ++++ b/contracts/token/ERC20/extensions/ERC20Capped.sol +@@ -7,6 +7,8 @@ import "../ERC20.sol"; + + /** + * @dev Extension of {ERC20} that adds a cap to the supply of tokens. ++ * ++ * @custom:storage-size 51 + */ + abstract contract ERC20Capped is ERC20 { + uint256 private immutable _cap; +diff --git a/contracts/token/ERC20/extensions/ERC20Permit.sol b/contracts/token/ERC20/extensions/ERC20Permit.sol +index a357199b..9dc8e894 100644 +--- a/contracts/token/ERC20/extensions/ERC20Permit.sol ++++ b/contracts/token/ERC20/extensions/ERC20Permit.sol +@@ -18,6 +18,8 @@ import "../../../utils/Counters.sol"; + * need to send a transaction, and thus is not required to hold Ether at all. + * + * _Available since v3.4._ ++ * ++ * @custom:storage-size 51 + */ + abstract contract ERC20Permit is ERC20, IERC20Permit, EIP712 { + using Counters for Counters.Counter; +diff --git a/contracts/token/ERC20/extensions/ERC20Wrapper.sol b/contracts/token/ERC20/extensions/ERC20Wrapper.sol +index bfe782e4..7264fe32 100644 +--- a/contracts/token/ERC20/extensions/ERC20Wrapper.sol ++++ b/contracts/token/ERC20/extensions/ERC20Wrapper.sol +@@ -14,6 +14,8 @@ import "../utils/SafeERC20.sol"; + * wrapping of an existing "basic" ERC20 into a governance token. + * + * _Available since v4.2._ ++ * ++ * @custom:storage-size 51 + */ + abstract contract ERC20Wrapper is ERC20 { + IERC20 private immutable _underlying; +diff --git a/contracts/token/ERC20/utils/TokenTimelock.sol b/contracts/token/ERC20/utils/TokenTimelock.sol +index ed855b7b..3d30f59d 100644 +--- a/contracts/token/ERC20/utils/TokenTimelock.sol ++++ b/contracts/token/ERC20/utils/TokenTimelock.sol +@@ -11,6 +11,8 @@ import "./SafeERC20.sol"; + * + * Useful for simple vesting schedules like "advisors get all of their tokens + * after 1 year". ++ * ++ * @custom:storage-size 53 + */ + contract TokenTimelock { + using SafeERC20 for IERC20; +diff --git a/contracts/utils/cryptography/EIP712.sol b/contracts/utils/cryptography/EIP712.sol +index 6a4e1cad..55d8eced 100644 +--- a/contracts/utils/cryptography/EIP712.sol ++++ b/contracts/utils/cryptography/EIP712.sol +@@ -4,7 +4,6 @@ + pragma solidity ^0.8.8; + + import "./ECDSA.sol"; +-import "../ShortStrings.sol"; + import "../../interfaces/IERC5267.sol"; + + /** +@@ -30,27 +29,19 @@ import "../../interfaces/IERC5267.sol"; + * + * _Available since v3.4._ + * +- * @custom:oz-upgrades-unsafe-allow state-variable-immutable state-variable-assignment ++ * @custom:storage-size 52 + */ + abstract contract EIP712 is IERC5267 { +- using ShortStrings for *; +- + bytes32 private constant _TYPE_HASH = + keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"); + +- // Cache the domain separator as an immutable value, but also store the chain id that it corresponds to, in order to +- // invalidate the cached domain separator if the chain id changes. +- bytes32 private immutable _cachedDomainSeparator; +- uint256 private immutable _cachedChainId; +- address private immutable _cachedThis; +- ++ /// @custom:oz-renamed-from _HASHED_NAME + bytes32 private immutable _hashedName; ++ /// @custom:oz-renamed-from _HASHED_VERSION + bytes32 private immutable _hashedVersion; + +- ShortString private immutable _name; +- ShortString private immutable _version; +- string private _nameFallback; +- string private _versionFallback; ++ string private _name; ++ string private _version; + + /** + * @dev Initializes the domain separator and parameter caches. +@@ -65,29 +56,23 @@ abstract contract EIP712 is IERC5267 { + * contract upgrade]. + */ + constructor(string memory name, string memory version) { +- _name = name.toShortStringWithFallback(_nameFallback); +- _version = version.toShortStringWithFallback(_versionFallback); +- _hashedName = keccak256(bytes(name)); +- _hashedVersion = keccak256(bytes(version)); +- +- _cachedChainId = block.chainid; +- _cachedDomainSeparator = _buildDomainSeparator(); +- _cachedThis = address(this); ++ _name = name; ++ _version = version; ++ ++ // Reset prior values in storage if upgrading ++ _hashedName = 0; ++ _hashedVersion = 0; + } + + /** + * @dev Returns the domain separator for the current chain. + */ + function _domainSeparatorV4() internal view returns (bytes32) { +- if (address(this) == _cachedThis && block.chainid == _cachedChainId) { +- return _cachedDomainSeparator; +- } else { +- return _buildDomainSeparator(); +- } ++ return _buildDomainSeparator(); + } + + function _buildDomainSeparator() private view returns (bytes32) { +- return keccak256(abi.encode(_TYPE_HASH, _hashedName, _hashedVersion, block.chainid, address(this))); ++ return keccak256(abi.encode(_TYPE_HASH, _EIP712NameHash(), _EIP712VersionHash(), block.chainid, address(this))); + } + + /** +@@ -129,14 +114,80 @@ abstract contract EIP712 is IERC5267 { + uint256[] memory extensions + ) + { ++ // If the hashed name and version in storage are non-zero, the contract hasn't been properly initialized ++ // and the EIP712 domain is not reliable, as it will be missing name and version. ++ require(_hashedName == 0 && _hashedVersion == 0, "EIP712: Uninitialized"); ++ + return ( + hex"0f", // 01111 +- _name.toStringWithFallback(_nameFallback), +- _version.toStringWithFallback(_versionFallback), ++ _EIP712Name(), ++ _EIP712Version(), + block.chainid, + address(this), + bytes32(0), + new uint256[](0) + ); + } ++ ++ /** ++ * @dev The name parameter for the EIP712 domain. ++ * ++ * NOTE: This function reads from storage by default, but can be redefined to return a constant value if gas costs ++ * are a concern. ++ */ ++ function _EIP712Name() internal virtual view returns (string memory) { ++ return _name; ++ } ++ ++ /** ++ * @dev The version parameter for the EIP712 domain. ++ * ++ * NOTE: This function reads from storage by default, but can be redefined to return a constant value if gas costs ++ * are a concern. ++ */ ++ function _EIP712Version() internal virtual view returns (string memory) { ++ return _version; ++ } ++ ++ /** ++ * @dev The hash of the name parameter for the EIP712 domain. ++ * ++ * NOTE: In previous versions this function was virtual. In this version you should override `_EIP712Name` instead. ++ */ ++ function _EIP712NameHash() internal view returns (bytes32) { ++ string memory name = _EIP712Name(); ++ if (bytes(name).length > 0) { ++ return keccak256(bytes(name)); ++ } else { ++ // If the name is empty, the contract may have been upgraded without initializing the new storage. ++ // We return the name hash in storage if non-zero, otherwise we assume the name is empty by design. ++ bytes32 hashedName = _hashedName; ++ if (hashedName != 0) { ++ return hashedName; ++ } else { ++ return keccak256(""); ++ } ++ } ++ } ++ ++ /** ++ * @dev The hash of the version parameter for the EIP712 domain. ++ * ++ * NOTE: In previous versions this function was virtual. In this version you should override `_EIP712Version` instead. ++ */ ++ function _EIP712VersionHash() internal view returns (bytes32) { ++ string memory version = _EIP712Version(); ++ if (bytes(version).length > 0) { ++ return keccak256(bytes(version)); ++ } else { ++ // If the version is empty, the contract may have been upgraded without initializing the new storage. ++ // We return the version hash in storage if non-zero, otherwise we assume the version is empty by design. ++ bytes32 hashedVersion = _hashedVersion; ++ if (hashedVersion != 0) { ++ return hashedVersion; ++ } else { ++ return keccak256(""); ++ } ++ } ++ } + } +diff --git a/package.json b/package.json +index 8458dd61..b4672240 100644 +--- a/package.json ++++ b/package.json +@@ -36,7 +36,7 @@ + }, + "repository": { + "type": "git", +- "url": "https://github.com/OpenZeppelin/openzeppelin-contracts.git" ++ "url": "https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable.git" + }, + "keywords": [ + "solidity", +diff --git a/test/utils/cryptography/EIP712.test.js b/test/utils/cryptography/EIP712.test.js +index 54a4e772..ba4602ed 100644 +--- a/test/utils/cryptography/EIP712.test.js ++++ b/test/utils/cryptography/EIP712.test.js +@@ -47,26 +47,6 @@ contract('EIP712', function (accounts) { + const rebuildDomain = await getDomain(this.eip712); + expect(mapValues(rebuildDomain, String)).to.be.deep.equal(mapValues(this.domain, String)); + }); +- +- if (shortOrLong === 'short') { +- // Long strings are in storage, and the proxy will not be properly initialized unless +- // the upgradeable contract variant is used and the initializer is invoked. +- +- it('adjusts when behind proxy', async function () { +- const factory = await Clones.new(); +- const cloneReceipt = await factory.$clone(this.eip712.address); +- const cloneAddress = cloneReceipt.logs.find(({ event }) => event === 'return$clone').args.instance; +- const clone = new EIP712Verifier(cloneAddress); +- +- const cloneDomain = { ...this.domain, verifyingContract: clone.address }; +- +- const reportedDomain = await getDomain(clone); +- expect(mapValues(reportedDomain, String)).to.be.deep.equal(mapValues(cloneDomain, String)); +- +- const expectedSeparator = await domainSeparator(cloneDomain); +- expect(await clone.$_domainSeparatorV4()).to.equal(expectedSeparator); +- }); +- } + }); + + it('hash digest', async function () { diff --git a/test/migrate-imports.test.js b/test/migrate-imports.test.js index 7ec9a97ab..5a5c37a23 100644 --- a/test/migrate-imports.test.js +++ b/test/migrate-imports.test.js @@ -13,6 +13,7 @@ describe('migrate-imports.js', function () { try { await fs.access(path.join('contracts', p), F_OK); } catch (e) { + if (p.startsWith('proxy/')) continue; // excluded from transpilation of upgradeable contracts await fs.access(path.join('contracts', getUpgradeablePath(p)), F_OK); } } diff --git a/test/utils/Checkpoints.test.js b/test/utils/Checkpoints.test.js index 48a00e31a..f395663be 100644 --- a/test/utils/Checkpoints.test.js +++ b/test/utils/Checkpoints.test.js @@ -6,28 +6,48 @@ const { batchInBlock } = require('../helpers/txpool'); const $Checkpoints = artifacts.require('$Checkpoints'); +// The library name may be 'Checkpoints' or 'CheckpointsUpgradeable' +const libraryName = $Checkpoints._json.contractName.replace(/^\$/, ''); + +const traceLengths = [160, 224]; + const first = array => (array.length ? array[0] : undefined); const last = array => (array.length ? array[array.length - 1] : undefined); contract('Checkpoints', function () { beforeEach(async function () { this.mock = await $Checkpoints.new(); + + this.methods = { trace: {} }; + this.methods.history = { + latest: (...args) => this.mock.methods[`$latest_${libraryName}_History(uint256)`](0, ...args), + latestCheckpoint: (...args) => this.mock.methods[`$latestCheckpoint_${libraryName}_History(uint256)`](0, ...args), + length: (...args) => this.mock.methods[`$length_${libraryName}_History(uint256)`](0, ...args), + push: (...args) => this.mock.methods['$push(uint256,uint256)'](0, ...args), + getAtBlock: (...args) => this.mock.$getAtBlock(0, ...args), + getAtRecentBlock: (...args) => this.mock.$getAtProbablyRecentBlock(0, ...args), + }; + for (const length of traceLengths) { + this.methods.trace[length] = { + latest: (...args) => this.mock.methods[`$latest_${libraryName}_Trace${length}(uint256)`](0, ...args), + latestCheckpoint: (...args) => + this.mock.methods[`$latestCheckpoint_${libraryName}_Trace${length}(uint256)`](0, ...args), + length: (...args) => this.mock.methods[`$length_${libraryName}_Trace${length}(uint256)`](0, ...args), + push: (...args) => this.mock.methods[`$push(uint256,uint${256 - length},uint${length})`](0, ...args), + lowerLookup: (...args) => this.mock.methods[`$lowerLookup(uint256,uint${256 - length})`](0, ...args), + upperLookup: (...args) => this.mock.methods[`$upperLookup(uint256,uint${256 - length})`](0, ...args), + upperLookupRecent: (...args) => + this.mock.methods[`$upperLookupRecent(uint256,uint${256 - length})`](0, ...args), + }; + } }); describe('History checkpoints', function () { - const latest = (self, ...args) => self.methods['$latest_Checkpoints_History(uint256)'](0, ...args); - const latestCheckpoint = (self, ...args) => - self.methods['$latestCheckpoint_Checkpoints_History(uint256)'](0, ...args); - const push = (self, ...args) => self.methods['$push(uint256,uint256)'](0, ...args); - const getAtBlock = (self, ...args) => self.methods['$getAtBlock(uint256,uint256)'](0, ...args); - const getAtRecentBlock = (self, ...args) => self.methods['$getAtProbablyRecentBlock(uint256,uint256)'](0, ...args); - const getLength = (self, ...args) => self.methods['$length_Checkpoints_History(uint256)'](0, ...args); - describe('without checkpoints', function () { it('returns zero as latest value', async function () { - expect(await latest(this.mock)).to.be.bignumber.equal('0'); + expect(await this.methods.history.latest()).to.be.bignumber.equal('0'); - const ckpt = await latestCheckpoint(this.mock); + const ckpt = await this.methods.history.latestCheckpoint(); expect(ckpt[0]).to.be.equal(false); expect(ckpt[1]).to.be.bignumber.equal('0'); expect(ckpt[2]).to.be.bignumber.equal('0'); @@ -35,49 +55,63 @@ contract('Checkpoints', function () { it('returns zero as past value', async function () { await time.advanceBlock(); - expect(await getAtBlock(this.mock, (await web3.eth.getBlockNumber()) - 1)).to.be.bignumber.equal('0'); - expect(await getAtRecentBlock(this.mock, (await web3.eth.getBlockNumber()) - 1)).to.be.bignumber.equal('0'); + expect(await this.methods.history.getAtBlock((await web3.eth.getBlockNumber()) - 1)).to.be.bignumber.equal('0'); + expect( + await this.methods.history.getAtRecentBlock((await web3.eth.getBlockNumber()) - 1), + ).to.be.bignumber.equal('0'); }); }); describe('with checkpoints', function () { beforeEach('pushing checkpoints', async function () { - this.tx1 = await push(this.mock, 1); - this.tx2 = await push(this.mock, 2); + this.tx1 = await this.methods.history.push(1); + this.tx2 = await this.methods.history.push(2); await time.advanceBlock(); - this.tx3 = await push(this.mock, 3); + this.tx3 = await this.methods.history.push(3); await time.advanceBlock(); await time.advanceBlock(); }); it('returns latest value', async function () { - expect(await latest(this.mock)).to.be.bignumber.equal('3'); + expect(await this.methods.history.latest()).to.be.bignumber.equal('3'); - const ckpt = await latestCheckpoint(this.mock); + const ckpt = await this.methods.history.latestCheckpoint(); expect(ckpt[0]).to.be.equal(true); expect(ckpt[1]).to.be.bignumber.equal(web3.utils.toBN(this.tx3.receipt.blockNumber)); expect(ckpt[2]).to.be.bignumber.equal(web3.utils.toBN('3')); }); - for (const getAtBlockVariant of [getAtBlock, getAtRecentBlock]) { + for (const getAtBlockVariant of ['getAtBlock', 'getAtRecentBlock']) { describe(`lookup: ${getAtBlockVariant}`, function () { it('returns past values', async function () { - expect(await getAtBlockVariant(this.mock, this.tx1.receipt.blockNumber - 1)).to.be.bignumber.equal('0'); - expect(await getAtBlockVariant(this.mock, this.tx1.receipt.blockNumber)).to.be.bignumber.equal('1'); - expect(await getAtBlockVariant(this.mock, this.tx2.receipt.blockNumber)).to.be.bignumber.equal('2'); + expect( + await this.methods.history[getAtBlockVariant](this.tx1.receipt.blockNumber - 1), + ).to.be.bignumber.equal('0'); + expect(await this.methods.history[getAtBlockVariant](this.tx1.receipt.blockNumber)).to.be.bignumber.equal( + '1', + ); + expect(await this.methods.history[getAtBlockVariant](this.tx2.receipt.blockNumber)).to.be.bignumber.equal( + '2', + ); // Block with no new checkpoints - expect(await getAtBlockVariant(this.mock, this.tx2.receipt.blockNumber + 1)).to.be.bignumber.equal('2'); - expect(await getAtBlockVariant(this.mock, this.tx3.receipt.blockNumber)).to.be.bignumber.equal('3'); - expect(await getAtBlockVariant(this.mock, this.tx3.receipt.blockNumber + 1)).to.be.bignumber.equal('3'); + expect( + await this.methods.history[getAtBlockVariant](this.tx2.receipt.blockNumber + 1), + ).to.be.bignumber.equal('2'); + expect(await this.methods.history[getAtBlockVariant](this.tx3.receipt.blockNumber)).to.be.bignumber.equal( + '3', + ); + expect( + await this.methods.history[getAtBlockVariant](this.tx3.receipt.blockNumber + 1), + ).to.be.bignumber.equal('3'); }); it('reverts if block number >= current block', async function () { await expectRevert( - getAtBlockVariant(this.mock, await web3.eth.getBlockNumber()), + this.methods.history[getAtBlockVariant](await web3.eth.getBlockNumber()), 'Checkpoints: block not yet mined', ); await expectRevert( - getAtBlockVariant(this.mock, (await web3.eth.getBlockNumber()) + 1), + this.methods.history[getAtBlockVariant]((await web3.eth.getBlockNumber()) + 1), 'Checkpoints: block not yet mined', ); }); @@ -85,58 +119,48 @@ contract('Checkpoints', function () { } it('multiple checkpoints in the same block', async function () { - const lengthBefore = await getLength(this.mock); + const lengthBefore = await this.methods.history.length(); await batchInBlock([ - () => push(this.mock, 8, { gas: 100000 }), - () => push(this.mock, 9, { gas: 100000 }), - () => push(this.mock, 10, { gas: 100000 }), + () => this.methods.history.push(8, { gas: 100000 }), + () => this.methods.history.push(9, { gas: 100000 }), + () => this.methods.history.push(10, { gas: 100000 }), ]); - expect(await getLength(this.mock)).to.be.bignumber.equal(lengthBefore.addn(1)); - expect(await latest(this.mock)).to.be.bignumber.equal('10'); + expect(await this.methods.history.length()).to.be.bignumber.equal(lengthBefore.addn(1)); + expect(await this.methods.history.latest()).to.be.bignumber.equal('10'); }); it('more than 5 checkpoints', async function () { for (let i = 4; i <= 6; i++) { - await push(this.mock, i); + await this.methods.history.push(i); } - expect(await getLength(this.mock)).to.be.bignumber.equal('6'); + expect(await this.methods.history.length()).to.be.bignumber.equal('6'); const block = await web3.eth.getBlockNumber(); // recent - expect(await getAtRecentBlock(this.mock, block - 1)).to.be.bignumber.equal('5'); + expect(await this.methods.history.getAtRecentBlock(block - 1)).to.be.bignumber.equal('5'); // non-recent - expect(await getAtRecentBlock(this.mock, block - 9)).to.be.bignumber.equal('0'); + expect(await this.methods.history.getAtRecentBlock(block - 9)).to.be.bignumber.equal('0'); }); }); }); - for (const length of [160, 224]) { + for (const length of traceLengths) { describe(`Trace${length}`, function () { - const latest = (self, ...args) => self.methods[`$latest_Checkpoints_Trace${length}(uint256)`](0, ...args); - const latestCheckpoint = (self, ...args) => - self.methods[`$latestCheckpoint_Checkpoints_Trace${length}(uint256)`](0, ...args); - const push = (self, ...args) => self.methods[`$push(uint256,uint${256 - length},uint${length})`](0, ...args); - const lowerLookup = (self, ...args) => self.methods[`$lowerLookup(uint256,uint${256 - length})`](0, ...args); - const upperLookup = (self, ...args) => self.methods[`$upperLookup(uint256,uint${256 - length})`](0, ...args); - const upperLookupRecent = (self, ...args) => - self.methods[`$upperLookupRecent(uint256,uint${256 - length})`](0, ...args); - const getLength = (self, ...args) => self.methods[`$length_Checkpoints_Trace${length}(uint256)`](0, ...args); - describe('without checkpoints', function () { it('returns zero as latest value', async function () { - expect(await latest(this.mock)).to.be.bignumber.equal('0'); + expect(await this.methods.trace[length].latest()).to.be.bignumber.equal('0'); - const ckpt = await latestCheckpoint(this.mock); + const ckpt = await this.methods.trace[length].latestCheckpoint(); expect(ckpt[0]).to.be.equal(false); expect(ckpt[1]).to.be.bignumber.equal('0'); expect(ckpt[2]).to.be.bignumber.equal('0'); }); it('lookup returns 0', async function () { - expect(await lowerLookup(this.mock, 0)).to.be.bignumber.equal('0'); - expect(await upperLookup(this.mock, 0)).to.be.bignumber.equal('0'); - expect(await upperLookupRecent(this.mock, 0)).to.be.bignumber.equal('0'); + expect(await this.methods.trace[length].lowerLookup(0)).to.be.bignumber.equal('0'); + expect(await this.methods.trace[length].upperLookup(0)).to.be.bignumber.equal('0'); + expect(await this.methods.trace[length].upperLookupRecent(0)).to.be.bignumber.equal('0'); }); }); @@ -150,46 +174,49 @@ contract('Checkpoints', function () { { key: '11', value: '99' }, ]; for (const { key, value } of this.checkpoints) { - await push(this.mock, key, value); + await this.methods.trace[length].push(key, value); } }); it('length', async function () { - expect(await getLength(this.mock)).to.be.bignumber.equal(this.checkpoints.length.toString()); + expect(await this.methods.trace[length].length()).to.be.bignumber.equal(this.checkpoints.length.toString()); }); it('returns latest value', async function () { - expect(await latest(this.mock)).to.be.bignumber.equal(last(this.checkpoints).value); + expect(await this.methods.trace[length].latest()).to.be.bignumber.equal(last(this.checkpoints).value); - const ckpt = await latestCheckpoint(this.mock); + const ckpt = await this.methods.trace[length].latestCheckpoint(); expect(ckpt[0]).to.be.equal(true); expect(ckpt[1]).to.be.bignumber.equal(last(this.checkpoints).key); expect(ckpt[2]).to.be.bignumber.equal(last(this.checkpoints).value); }); it('cannot push values in the past', async function () { - await expectRevert(push(this.mock, last(this.checkpoints).key - 1, '0'), 'Checkpoint: decreasing keys'); + await expectRevert( + this.methods.trace[length].push(last(this.checkpoints).key - 1, '0'), + 'Checkpoint: decreasing keys', + ); }); it('can update last value', async function () { const newValue = '42'; // check length before the update - expect(await getLength(this.mock)).to.be.bignumber.equal(this.checkpoints.length.toString()); + expect(await this.methods.trace[length].length()).to.be.bignumber.equal(this.checkpoints.length.toString()); // update last key - await push(this.mock, last(this.checkpoints).key, newValue); - expect(await latest(this.mock)).to.be.bignumber.equal(newValue); + await this.methods.trace[length].push(last(this.checkpoints).key, newValue); + expect(await this.methods.trace[length].latest()).to.be.bignumber.equal(newValue); // check that length did not change - expect(await getLength(this.mock)).to.be.bignumber.equal(this.checkpoints.length.toString()); + expect(await this.methods.trace[length].length()).to.be.bignumber.equal(this.checkpoints.length.toString()); }); it('lower lookup', async function () { for (let i = 0; i < 14; ++i) { const value = first(this.checkpoints.filter(x => i <= x.key))?.value || '0'; - expect(await lowerLookup(this.mock, i)).to.be.bignumber.equal(value); + expect(await this.methods.trace[length].lowerLookup(i)).to.be.bignumber.equal(value); } }); @@ -197,8 +224,8 @@ contract('Checkpoints', function () { for (let i = 0; i < 14; ++i) { const value = last(this.checkpoints.filter(x => i >= x.key))?.value || '0'; - expect(await upperLookup(this.mock, i)).to.be.bignumber.equal(value); - expect(await upperLookupRecent(this.mock, i)).to.be.bignumber.equal(value); + expect(await this.methods.trace[length].upperLookup(i)).to.be.bignumber.equal(value); + expect(await this.methods.trace[length].upperLookupRecent(i)).to.be.bignumber.equal(value); } }); @@ -213,13 +240,13 @@ contract('Checkpoints', function () { const allCheckpoints = [].concat(this.checkpoints, moreCheckpoints); for (const { key, value } of moreCheckpoints) { - await push(this.mock, key, value); + await this.methods.trace[length].push(key, value); } for (let i = 0; i < 25; ++i) { const value = last(allCheckpoints.filter(x => i >= x.key))?.value || '0'; - expect(await upperLookup(this.mock, i)).to.be.bignumber.equal(value); - expect(await upperLookupRecent(this.mock, i)).to.be.bignumber.equal(value); + expect(await this.methods.trace[length].upperLookup(i)).to.be.bignumber.equal(value); + expect(await this.methods.trace[length].upperLookupRecent(i)).to.be.bignumber.equal(value); } }); }); diff --git a/test/utils/cryptography/EIP712.test.js b/test/utils/cryptography/EIP712.test.js index 52e322d3d..54a4e7723 100644 --- a/test/utils/cryptography/EIP712.test.js +++ b/test/utils/cryptography/EIP712.test.js @@ -60,11 +60,11 @@ contract('EIP712', function (accounts) { const cloneDomain = { ...this.domain, verifyingContract: clone.address }; - const expectedSeparator = await domainSeparator(cloneDomain); - expect(await clone.$_domainSeparatorV4()).to.equal(expectedSeparator); - const reportedDomain = await getDomain(clone); expect(mapValues(reportedDomain, String)).to.be.deep.equal(mapValues(cloneDomain, String)); + + const expectedSeparator = await domainSeparator(cloneDomain); + expect(await clone.$_domainSeparatorV4()).to.equal(expectedSeparator); }); } }); diff --git a/test/utils/structs/EnumerableMap.test.js b/test/utils/structs/EnumerableMap.test.js index 548e73775..f540ec99e 100644 --- a/test/utils/structs/EnumerableMap.test.js +++ b/test/utils/structs/EnumerableMap.test.js @@ -14,6 +14,9 @@ const getMethods = ms => { ); }; +// Get the name of the library. In the transpiled code it will be EnumerableMapUpgradeable. +const library = EnumerableMap._json.contractName.replace(/^\$/, ''); + contract('EnumerableMap', function (accounts) { const [accountA, accountB, accountC] = accounts; @@ -41,14 +44,14 @@ contract('EnumerableMap', function (accounts) { getWithMessage: '$get(uint256,address,string)', tryGet: '$tryGet(uint256,address)', remove: '$remove(uint256,address)', - length: '$length_EnumerableMap_AddressToUintMap(uint256)', - at: '$at_EnumerableMap_AddressToUintMap(uint256,uint256)', + length: `$length_${library}_AddressToUintMap(uint256)`, + at: `$at_${library}_AddressToUintMap(uint256,uint256)`, contains: '$contains(uint256,address)', - keys: '$keys_EnumerableMap_AddressToUintMap(uint256)', + keys: `$keys_${library}_AddressToUintMap(uint256)`, }), { - setReturn: 'return$set_EnumerableMap_AddressToUintMap_address_uint256', - removeReturn: 'return$remove_EnumerableMap_AddressToUintMap_address', + setReturn: `return$set_${library}_AddressToUintMap_address_uint256`, + removeReturn: `return$remove_${library}_AddressToUintMap_address`, }, ); }); @@ -61,18 +64,18 @@ contract('EnumerableMap', function (accounts) { constants.ZERO_ADDRESS, getMethods({ set: '$set(uint256,uint256,address)', - get: '$get_EnumerableMap_UintToAddressMap(uint256,uint256)', - getWithMessage: '$get_EnumerableMap_UintToAddressMap(uint256,uint256,string)', - tryGet: '$tryGet_EnumerableMap_UintToAddressMap(uint256,uint256)', - remove: '$remove_EnumerableMap_UintToAddressMap(uint256,uint256)', - length: '$length_EnumerableMap_UintToAddressMap(uint256)', - at: '$at_EnumerableMap_UintToAddressMap(uint256,uint256)', - contains: '$contains_EnumerableMap_UintToAddressMap(uint256,uint256)', - keys: '$keys_EnumerableMap_UintToAddressMap(uint256)', + get: `$get_${library}_UintToAddressMap(uint256,uint256)`, + getWithMessage: `$get_${library}_UintToAddressMap(uint256,uint256,string)`, + tryGet: `$tryGet_${library}_UintToAddressMap(uint256,uint256)`, + remove: `$remove_${library}_UintToAddressMap(uint256,uint256)`, + length: `$length_${library}_UintToAddressMap(uint256)`, + at: `$at_${library}_UintToAddressMap(uint256,uint256)`, + contains: `$contains_${library}_UintToAddressMap(uint256,uint256)`, + keys: `$keys_${library}_UintToAddressMap(uint256)`, }), { - setReturn: 'return$set_EnumerableMap_UintToAddressMap_uint256_address', - removeReturn: 'return$remove_EnumerableMap_UintToAddressMap_uint256', + setReturn: `return$set_${library}_UintToAddressMap_uint256_address`, + removeReturn: `return$remove_${library}_UintToAddressMap_uint256`, }, ); }); @@ -85,18 +88,18 @@ contract('EnumerableMap', function (accounts) { constants.ZERO_BYTES32, getMethods({ set: '$set(uint256,bytes32,bytes32)', - get: '$get_EnumerableMap_Bytes32ToBytes32Map(uint256,bytes32)', - getWithMessage: '$get_EnumerableMap_Bytes32ToBytes32Map(uint256,bytes32,string)', - tryGet: '$tryGet_EnumerableMap_Bytes32ToBytes32Map(uint256,bytes32)', - remove: '$remove_EnumerableMap_Bytes32ToBytes32Map(uint256,bytes32)', - length: '$length_EnumerableMap_Bytes32ToBytes32Map(uint256)', - at: '$at_EnumerableMap_Bytes32ToBytes32Map(uint256,uint256)', - contains: '$contains_EnumerableMap_Bytes32ToBytes32Map(uint256,bytes32)', - keys: '$keys_EnumerableMap_Bytes32ToBytes32Map(uint256)', + get: `$get_${library}_Bytes32ToBytes32Map(uint256,bytes32)`, + getWithMessage: `$get_${library}_Bytes32ToBytes32Map(uint256,bytes32,string)`, + tryGet: `$tryGet_${library}_Bytes32ToBytes32Map(uint256,bytes32)`, + remove: `$remove_${library}_Bytes32ToBytes32Map(uint256,bytes32)`, + length: `$length_${library}_Bytes32ToBytes32Map(uint256)`, + at: `$at_${library}_Bytes32ToBytes32Map(uint256,uint256)`, + contains: `$contains_${library}_Bytes32ToBytes32Map(uint256,bytes32)`, + keys: `$keys_${library}_Bytes32ToBytes32Map(uint256)`, }), { - setReturn: 'return$set_EnumerableMap_Bytes32ToBytes32Map_bytes32_bytes32', - removeReturn: 'return$remove_EnumerableMap_Bytes32ToBytes32Map_bytes32', + setReturn: `return$set_${library}_Bytes32ToBytes32Map_bytes32_bytes32`, + removeReturn: `return$remove_${library}_Bytes32ToBytes32Map_bytes32`, }, ); }); @@ -109,18 +112,18 @@ contract('EnumerableMap', function (accounts) { new BN('0'), getMethods({ set: '$set(uint256,uint256,uint256)', - get: '$get_EnumerableMap_UintToUintMap(uint256,uint256)', - getWithMessage: '$get_EnumerableMap_UintToUintMap(uint256,uint256,string)', - tryGet: '$tryGet_EnumerableMap_UintToUintMap(uint256,uint256)', - remove: '$remove_EnumerableMap_UintToUintMap(uint256,uint256)', - length: '$length_EnumerableMap_UintToUintMap(uint256)', - at: '$at_EnumerableMap_UintToUintMap(uint256,uint256)', - contains: '$contains_EnumerableMap_UintToUintMap(uint256,uint256)', - keys: '$keys_EnumerableMap_UintToUintMap(uint256)', + get: `$get_${library}_UintToUintMap(uint256,uint256)`, + getWithMessage: `$get_${library}_UintToUintMap(uint256,uint256,string)`, + tryGet: `$tryGet_${library}_UintToUintMap(uint256,uint256)`, + remove: `$remove_${library}_UintToUintMap(uint256,uint256)`, + length: `$length_${library}_UintToUintMap(uint256)`, + at: `$at_${library}_UintToUintMap(uint256,uint256)`, + contains: `$contains_${library}_UintToUintMap(uint256,uint256)`, + keys: `$keys_${library}_UintToUintMap(uint256)`, }), { - setReturn: 'return$set_EnumerableMap_UintToUintMap_uint256_uint256', - removeReturn: 'return$remove_EnumerableMap_UintToUintMap_uint256', + setReturn: `return$set_${library}_UintToUintMap_uint256_uint256`, + removeReturn: `return$remove_${library}_UintToUintMap_uint256`, }, ); }); @@ -133,18 +136,18 @@ contract('EnumerableMap', function (accounts) { new BN('0'), getMethods({ set: '$set(uint256,bytes32,uint256)', - get: '$get_EnumerableMap_Bytes32ToUintMap(uint256,bytes32)', - getWithMessage: '$get_EnumerableMap_Bytes32ToUintMap(uint256,bytes32,string)', - tryGet: '$tryGet_EnumerableMap_Bytes32ToUintMap(uint256,bytes32)', - remove: '$remove_EnumerableMap_Bytes32ToUintMap(uint256,bytes32)', - length: '$length_EnumerableMap_Bytes32ToUintMap(uint256)', - at: '$at_EnumerableMap_Bytes32ToUintMap(uint256,uint256)', - contains: '$contains_EnumerableMap_Bytes32ToUintMap(uint256,bytes32)', - keys: '$keys_EnumerableMap_Bytes32ToUintMap(uint256)', + get: `$get_${library}_Bytes32ToUintMap(uint256,bytes32)`, + getWithMessage: `$get_${library}_Bytes32ToUintMap(uint256,bytes32,string)`, + tryGet: `$tryGet_${library}_Bytes32ToUintMap(uint256,bytes32)`, + remove: `$remove_${library}_Bytes32ToUintMap(uint256,bytes32)`, + length: `$length_${library}_Bytes32ToUintMap(uint256)`, + at: `$at_${library}_Bytes32ToUintMap(uint256,uint256)`, + contains: `$contains_${library}_Bytes32ToUintMap(uint256,bytes32)`, + keys: `$keys_${library}_Bytes32ToUintMap(uint256)`, }), { - setReturn: 'return$set_EnumerableMap_Bytes32ToUintMap_bytes32_uint256', - removeReturn: 'return$remove_EnumerableMap_Bytes32ToUintMap_bytes32', + setReturn: `return$set_${library}_Bytes32ToUintMap_bytes32_uint256`, + removeReturn: `return$remove_${library}_Bytes32ToUintMap_bytes32`, }, ); }); diff --git a/test/utils/structs/EnumerableSet.test.js b/test/utils/structs/EnumerableSet.test.js index 862cb6d9e..3ba9d7ff7 100644 --- a/test/utils/structs/EnumerableSet.test.js +++ b/test/utils/structs/EnumerableSet.test.js @@ -12,6 +12,9 @@ const getMethods = ms => { ); }; +// Get the name of the library. In the transpiled code it will be EnumerableSetUpgradeable. +const library = EnumerableSet._json.contractName.replace(/^\$/, ''); + contract('EnumerableSet', function (accounts) { beforeEach(async function () { this.set = await EnumerableSet.new(); @@ -25,13 +28,13 @@ contract('EnumerableSet', function (accounts) { add: '$add(uint256,bytes32)', remove: '$remove(uint256,bytes32)', contains: '$contains(uint256,bytes32)', - length: '$length_EnumerableSet_Bytes32Set(uint256)', - at: '$at_EnumerableSet_Bytes32Set(uint256,uint256)', - values: '$values_EnumerableSet_Bytes32Set(uint256)', + length: `$length_${library}_Bytes32Set(uint256)`, + at: `$at_${library}_Bytes32Set(uint256,uint256)`, + values: `$values_${library}_Bytes32Set(uint256)`, }), { - addReturn: 'return$add_EnumerableSet_Bytes32Set_bytes32', - removeReturn: 'return$remove_EnumerableSet_Bytes32Set_bytes32', + addReturn: `return$add_${library}_Bytes32Set_bytes32`, + removeReturn: `return$remove_${library}_Bytes32Set_bytes32`, }, ); }); @@ -44,13 +47,13 @@ contract('EnumerableSet', function (accounts) { add: '$add(uint256,address)', remove: '$remove(uint256,address)', contains: '$contains(uint256,address)', - length: '$length_EnumerableSet_AddressSet(uint256)', - at: '$at_EnumerableSet_AddressSet(uint256,uint256)', - values: '$values_EnumerableSet_AddressSet(uint256)', + length: `$length_${library}_AddressSet(uint256)`, + at: `$at_${library}_AddressSet(uint256,uint256)`, + values: `$values_${library}_AddressSet(uint256)`, }), { - addReturn: 'return$add_EnumerableSet_AddressSet_address', - removeReturn: 'return$remove_EnumerableSet_AddressSet_address', + addReturn: `return$add_${library}_AddressSet_address`, + removeReturn: `return$remove_${library}_AddressSet_address`, }, ); }); @@ -63,13 +66,13 @@ contract('EnumerableSet', function (accounts) { add: '$add(uint256,uint256)', remove: '$remove(uint256,uint256)', contains: '$contains(uint256,uint256)', - length: '$length_EnumerableSet_UintSet(uint256)', - at: '$at_EnumerableSet_UintSet(uint256,uint256)', - values: '$values_EnumerableSet_UintSet(uint256)', + length: `$length_${library}_UintSet(uint256)`, + at: `$at_${library}_UintSet(uint256,uint256)`, + values: `$values_${library}_UintSet(uint256)`, }), { - addReturn: 'return$add_EnumerableSet_UintSet_uint256', - removeReturn: 'return$remove_EnumerableSet_UintSet_uint256', + addReturn: `return$add_${library}_UintSet_uint256`, + removeReturn: `return$remove_${library}_UintSet_uint256`, }, ); }); From 0ee84342b72e7874433b47a591d460d3f22ebf8e Mon Sep 17 00:00:00 2001 From: Francisco Date: Tue, 9 May 2023 20:09:52 +0100 Subject: [PATCH 053/182] Add PDF report for v4.9 audit (#4227) --- audits/2023-05-v4.9.pdf | Bin 0 -> 483005 bytes audits/README.md | 1 + 2 files changed, 1 insertion(+) create mode 100644 audits/2023-05-v4.9.pdf diff --git a/audits/2023-05-v4.9.pdf b/audits/2023-05-v4.9.pdf new file mode 100644 index 0000000000000000000000000000000000000000..e7e6d715b04b0fffb92f424b63120a01e2207f72 GIT binary patch literal 483005 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 literal 0 HcmV?d00001 diff --git a/audits/README.md b/audits/README.md index 40098a44d..c629f51d3 100644 --- a/audits/README.md +++ b/audits/README.md @@ -2,6 +2,7 @@ | Date | Version | Commit | Auditor | Scope | Links | | ------------ | ------- | --------- | ------------ | -------------------- | ----------------------------------------------------------- | +| May 2023 | v4.9.0 | `91df66c` | OpenZeppelin | v4.9 Changes | [🔗](./2023-05-v4.9.pdf) | | October 2022 | v4.8.0 | `14f98db` | OpenZeppelin | ERC4626, Checkpoints | [🔗](./2022-10-ERC4626.pdf) [🔗](./2022-10-Checkpoints.pdf) | | October 2018 | v2.0.0 | `dac5bcc` | LevelK | Everything | [🔗](./2018-10.pdf) | | March 2017 | v1.0.4 | `9c5975a` | New Alchemy | Everything | [🔗](./2017-03.md) | From df3f1fc4db6955d0fe4736b6c43d4861b7436cda Mon Sep 17 00:00:00 2001 From: Francisco Giordano Date: Tue, 9 May 2023 16:37:25 -0300 Subject: [PATCH 054/182] Specify changeset commit manually --- .changeset/tender-needles-dance.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.changeset/tender-needles-dance.md b/.changeset/tender-needles-dance.md index d75adec95..04d1784a4 100644 --- a/.changeset/tender-needles-dance.md +++ b/.changeset/tender-needles-dance.md @@ -3,3 +3,5 @@ --- `ERC20Wrapper`: self wrapping and deposit by the wrapper itself are now explicitelly forbiden. + +commit: 3214f6c25 From dff520afae768f1e94471e1ca4e08293d4d8e159 Mon Sep 17 00:00:00 2001 From: Francisco Giordano Date: Tue, 9 May 2023 16:53:55 -0300 Subject: [PATCH 055/182] Specify changeset PRs manually --- .changeset/beige-buses-drop.md | 4 +++- .changeset/curvy-shrimps-enjoy.md | 4 +++- .changeset/curvy-suns-sort.md | 4 +++- .changeset/famous-rules-burn.md | 4 +++- .changeset/funny-rockets-compete.md | 4 +++- .changeset/gold-chicken-clean.md | 4 +++- .changeset/healthy-squids-stare.md | 4 +++- .changeset/lemon-dogs-kiss.md | 4 +++- .changeset/little-kiwis-ring.md | 4 +++- .changeset/pretty-hornets-play.md | 4 +++- .changeset/tame-ladybugs-sit.md | 4 +++- 11 files changed, 33 insertions(+), 11 deletions(-) diff --git a/.changeset/beige-buses-drop.md b/.changeset/beige-buses-drop.md index 4566eccb0..ecfd08b35 100644 --- a/.changeset/beige-buses-drop.md +++ b/.changeset/beige-buses-drop.md @@ -2,4 +2,6 @@ 'openzeppelin-solidity': patch --- -`Initializable`: optimize `_disableInitializers` by using `!=` instead of `<`. ([#3787](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3787)) +`Initializable`: optimize `_disableInitializers` by using `!=` instead of `<`. + +pr: #3787 diff --git a/.changeset/curvy-shrimps-enjoy.md b/.changeset/curvy-shrimps-enjoy.md index 4bc410abf..22c2bc54c 100644 --- a/.changeset/curvy-shrimps-enjoy.md +++ b/.changeset/curvy-shrimps-enjoy.md @@ -2,4 +2,6 @@ 'openzeppelin-solidity': minor --- -`ReentrancyGuard`: Add a `_reentrancyGuardEntered` function to expose the guard status. ([#3714](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3714)) +`ReentrancyGuard`: Add a `_reentrancyGuardEntered` function to expose the guard status. + +pr: #3714 diff --git a/.changeset/curvy-suns-sort.md b/.changeset/curvy-suns-sort.md index 97b51fed7..201f45ca7 100644 --- a/.changeset/curvy-suns-sort.md +++ b/.changeset/curvy-suns-sort.md @@ -2,4 +2,6 @@ 'openzeppelin-solidity': patch --- -`Ownable2Step`: make `acceptOwnership` public virtual to enable usecases that require overriding it. ([#3960](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3960)) +`Ownable2Step`: make `acceptOwnership` public virtual to enable usecases that require overriding it. + +pr: #3960 diff --git a/.changeset/famous-rules-burn.md b/.changeset/famous-rules-burn.md index a746dc21d..a97aca0b3 100644 --- a/.changeset/famous-rules-burn.md +++ b/.changeset/famous-rules-burn.md @@ -2,4 +2,6 @@ 'openzeppelin-solidity': minor --- -`EnumerableMap`: add a `keys()` function that returns an array containing all the keys. ([#3920](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3920)) +`EnumerableMap`: add a `keys()` function that returns an array containing all the keys. + +pr: #3920 diff --git a/.changeset/funny-rockets-compete.md b/.changeset/funny-rockets-compete.md index a8c77c619..3f665bc9e 100644 --- a/.changeset/funny-rockets-compete.md +++ b/.changeset/funny-rockets-compete.md @@ -2,4 +2,6 @@ 'openzeppelin-solidity': patch --- -Reformatted codebase with latest version of Prettier Solidity. ([#3898](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3898)) +Reformatted codebase with latest version of Prettier Solidity. + +pr: #3898 diff --git a/.changeset/gold-chicken-clean.md b/.changeset/gold-chicken-clean.md index 0d64fde6d..1353e9c9c 100644 --- a/.changeset/gold-chicken-clean.md +++ b/.changeset/gold-chicken-clean.md @@ -2,4 +2,6 @@ 'openzeppelin-solidity': minor --- -`Strings`: add `equal` method. ([#3774](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3774)) +`Strings`: add `equal` method. + +pr: #3774 diff --git a/.changeset/healthy-squids-stare.md b/.changeset/healthy-squids-stare.md index fad0872e2..9e2c9f3dd 100644 --- a/.changeset/healthy-squids-stare.md +++ b/.changeset/healthy-squids-stare.md @@ -2,4 +2,6 @@ 'openzeppelin-solidity': patch --- -`Math`: optimize `log256` rounding check. ([#3745](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3745)) +`Math`: optimize `log256` rounding check. + +pr: #3745 diff --git a/.changeset/lemon-dogs-kiss.md b/.changeset/lemon-dogs-kiss.md index 976949d2c..5e2787cf1 100644 --- a/.changeset/lemon-dogs-kiss.md +++ b/.changeset/lemon-dogs-kiss.md @@ -2,4 +2,6 @@ 'openzeppelin-solidity': patch --- -`ERC20Votes`: optimize by using unchecked arithmetic. ([#3748](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3748)) +`ERC20Votes`: optimize by using unchecked arithmetic. + +pr: #3748 diff --git a/.changeset/little-kiwis-ring.md b/.changeset/little-kiwis-ring.md index a1cb7bb95..81909a513 100644 --- a/.changeset/little-kiwis-ring.md +++ b/.changeset/little-kiwis-ring.md @@ -2,4 +2,6 @@ 'openzeppelin-solidity': patch --- -`Multicall`: annotate `multicall` function as upgrade safe to not raise a flag for its delegatecall. ([#3961](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3961)) +`Multicall`: annotate `multicall` function as upgrade safe to not raise a flag for its delegatecall. + +pr: #3961 diff --git a/.changeset/pretty-hornets-play.md b/.changeset/pretty-hornets-play.md index 230a53bb2..e7d15c24a 100644 --- a/.changeset/pretty-hornets-play.md +++ b/.changeset/pretty-hornets-play.md @@ -2,4 +2,6 @@ 'openzeppelin-solidity': minor --- -`Strings`: add `toString` method for signed integers. ([#3773](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3773)) +`Strings`: add `toString` method for signed integers. + +pr: #3773 diff --git a/.changeset/tame-ladybugs-sit.md b/.changeset/tame-ladybugs-sit.md index 8a1e416de..4cddc219e 100644 --- a/.changeset/tame-ladybugs-sit.md +++ b/.changeset/tame-ladybugs-sit.md @@ -2,4 +2,6 @@ 'openzeppelin-solidity': patch --- -`MerkleProof`: optimize by using unchecked arithmetic. ([#3745](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3745)) +`MerkleProof`: optimize by using unchecked arithmetic. + +pr: #3745 From d095542fa4e35212e822fca1df166211f9b68186 Mon Sep 17 00:00:00 2001 From: Francisco Giordano Date: Tue, 9 May 2023 17:36:33 -0300 Subject: [PATCH 056/182] Disable code size warnings on exposed contracts --- hardhat.config.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/hardhat.config.js b/hardhat.config.js index 639e10f95..5fd703d06 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -76,6 +76,9 @@ module.exports = { }, }, warnings: { + 'contracts-exposed/**/*': { + 'code-size': 'off', + }, '*': { 'code-size': withOptimizations, 'unused-param': !argv.coverage, // coverage causes unused-param warnings From 9a2e4cb3a71326bc91a177f7b1f184448ccc799f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 9 May 2023 20:59:22 -0300 Subject: [PATCH 057/182] Update codespell-project/actions-codespell action to v2 (#4229) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/checks.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index ae48e9286..1f03aa38c 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -107,7 +107,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Run CodeSpell - uses: codespell-project/actions-codespell@v1.0 + uses: codespell-project/actions-codespell@v2.0 with: check_filenames: true skip: package-lock.json,*.pdf From e5dbc7435e5995cd539d49fe51a508b48856da64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Wed, 10 May 2023 12:59:11 -0600 Subject: [PATCH 058/182] Add final PDF report for v4.9 audit (#4235) --- audits/2023-05-v4.9.pdf | Bin 483005 -> 485395 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/audits/2023-05-v4.9.pdf b/audits/2023-05-v4.9.pdf index e7e6d715b04b0fffb92f424b63120a01e2207f72..21cd7f59307483e93957a3b2c94aa11c0efe40ec 100644 GIT binary patch delta 257244 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%0AkFbk-zM@;TDwre>t|o-6^}I z*ki*@DEp*1rd}O0dj{ES!)+)B205<6fA`qp5ClrZDA~jl6iB3WZIR|p6|+hZ4iM%= zu=H!>Eqz2Ex**_?s8BMWAx7`N>__Pj>6h*w>Zk25k;t1gVJBt`vZ3j>>_6-W2>Vm| zHT$`@ZH!xgMgFS%mC4eTY*A07mj~uvPd3pp(6P`l(Xr7n(y`JpD-JR{2r^6#GF|95 zF%F%y)3MVrbS6+i$5SVq4yTW>%Nsc0C?sYJPw8hcZe$rqwjO1xWZffHC-&}tw@tLI zwavTjxsA9ju}x*1^Q&&se#K}7uv#%&v0E`*v1DL9W5pwu2`}hh?PuL4+t%9_G;XTt z*03yRRU@_ze~Lf~Z|L{$=QVDt>2e>uAcX7BzO26dds$pF=|1Yd;@<5(CHPGcU>9T+

BP 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 H0c80ad(Q`15Ip#=?z_OZ#$>nl6p`M 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�M8zeDZ}&1+D=A%IIMgIpe~I80@5ZO~$e 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 VEgG)VO%I|lsoS};$B@3d{{y8PI!*up delta 255652 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%0Dmu(TMqrK4N+HQ*K(LrjJqp=6bmLE6%w-{ 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 z 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 From f7b236dca87865752f20585e532c0bffa3fe39d1 Mon Sep 17 00:00:00 2001 From: Francisco Date: Wed, 10 May 2023 20:58:56 +0100 Subject: [PATCH 059/182] Configure Codespell to check hidden files (#4236) --- .changeset/tender-needles-dance.md | 2 +- .github/workflows/checks.yml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.changeset/tender-needles-dance.md b/.changeset/tender-needles-dance.md index 04d1784a4..75ce9fbf8 100644 --- a/.changeset/tender-needles-dance.md +++ b/.changeset/tender-needles-dance.md @@ -2,6 +2,6 @@ 'openzeppelin-solidity': minor --- -`ERC20Wrapper`: self wrapping and deposit by the wrapper itself are now explicitelly forbiden. +`ERC20Wrapper`: self wrapping and deposit by the wrapper itself are now explicitly forbidden. commit: 3214f6c25 diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 1f03aa38c..ec17fa2f7 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -109,5 +109,6 @@ jobs: - name: Run CodeSpell uses: codespell-project/actions-codespell@v2.0 with: + check_hidden: true check_filenames: true skip: package-lock.json,*.pdf From 3e1b25a5cf3be6671014ebb61a716669890bab4a Mon Sep 17 00:00:00 2001 From: Francisco Date: Wed, 10 May 2023 21:08:05 +0100 Subject: [PATCH 060/182] Clean up pending admin schedule on renounce in DefaultAdminRules (#4230) --- .changeset/loud-wolves-promise.md | 5 +++++ contracts/access/AccessControlDefaultAdminRules.sol | 1 + test/access/AccessControl.behavior.js | 7 +++++-- 3 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 .changeset/loud-wolves-promise.md diff --git a/.changeset/loud-wolves-promise.md b/.changeset/loud-wolves-promise.md new file mode 100644 index 000000000..544b52c5f --- /dev/null +++ b/.changeset/loud-wolves-promise.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': patch +--- + +`AccessControlDefaultAdminRules`: Clean up pending admin schedule on renounce. diff --git a/contracts/access/AccessControlDefaultAdminRules.sol b/contracts/access/AccessControlDefaultAdminRules.sol index 6cdda81a1..33ad56e40 100644 --- a/contracts/access/AccessControlDefaultAdminRules.sol +++ b/contracts/access/AccessControlDefaultAdminRules.sol @@ -112,6 +112,7 @@ abstract contract AccessControlDefaultAdminRules is IAccessControlDefaultAdminRu newDefaultAdmin == address(0) && _isScheduleSet(schedule) && _hasSchedulePassed(schedule), "AccessControl: only can renounce in two delayed steps" ); + delete _pendingDefaultAdminSchedule; } super.renounceRole(role, account); } diff --git a/test/access/AccessControl.behavior.js b/test/access/AccessControl.behavior.js index 49ab44b58..e7a91957e 100644 --- a/test/access/AccessControl.behavior.js +++ b/test/access/AccessControl.behavior.js @@ -267,7 +267,7 @@ function shouldBehaveLikeAccessControlDefaultAdminRules(errorPrefix, delay, defa [0, 'exactly when'], [1, 'after'], ]) { - it(`returns pending admin and delay ${tag} delay schedule passes if not accepted`, async function () { + it(`returns pending admin and schedule ${tag} it passes if not accepted`, async function () { // Wait until schedule + fromSchedule const { schedule: firstSchedule } = await this.accessControl.pendingDefaultAdmin(); await time.setNextBlockTimestamp(firstSchedule.toNumber() + fromSchedule); @@ -279,7 +279,7 @@ function shouldBehaveLikeAccessControlDefaultAdminRules(errorPrefix, delay, defa }); } - it('returns 0 after delay schedule passes and the transfer was accepted', async function () { + it('returns 0 after schedule passes and the transfer was accepted', async function () { // Wait after schedule const { schedule: firstSchedule } = await this.accessControl.pendingDefaultAdmin(); await time.setNextBlockTimestamp(firstSchedule.addn(1)); @@ -660,6 +660,9 @@ function shouldBehaveLikeAccessControlDefaultAdminRules(errorPrefix, delay, defa account: defaultAdmin, }); expect(await this.accessControl.owner()).to.equal(constants.ZERO_ADDRESS); + const { newAdmin, schedule } = await this.accessControl.pendingDefaultAdmin(); + expect(newAdmin).to.eq(ZERO_ADDRESS); + expect(schedule).to.be.bignumber.eq(ZERO); }); it('allows to recover access using the internal _grantRole', async function () { From f355bd3a2a4969a78cb20ab721a1cd2b3c6da4c9 Mon Sep 17 00:00:00 2001 From: Francisco Date: Thu, 11 May 2023 17:41:02 +0100 Subject: [PATCH 061/182] Prevent attempt to publish to npm (#4239) --- package-lock.json | 13 +++++++------ package.json | 1 + scripts/release/workflow/state.js | 14 +++++++++++--- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6611223fe..139ce68e9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -48,6 +48,7 @@ "solidity-ast": "^0.4.25", "solidity-coverage": "^0.8.0", "solidity-docgen": "^0.6.0-beta.29", + "undici": "^5.22.1", "web3": "^1.3.0", "yargs": "^17.0.0" } @@ -15175,9 +15176,9 @@ "dev": true }, "node_modules/undici": { - "version": "5.22.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.22.0.tgz", - "integrity": "sha512-fR9RXCc+6Dxav4P9VV/sp5w3eFiSdOjJYsbtWfd4s5L5C4ogyuVpdKIVHeW0vV1MloM65/f7W45nR9ZxwVdyiA==", + "version": "5.22.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.22.1.tgz", + "integrity": "sha512-Ji2IJhFXZY0x/0tVBXeQwgPlLWw13GVzpsWPQ3rV50IFMMof2I55PZZxtm4P6iNq+L5znYN9nSTAq0ZyE6lSJw==", "dev": true, "dependencies": { "busboy": "^1.6.0" @@ -27985,9 +27986,9 @@ "dev": true }, "undici": { - "version": "5.22.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.22.0.tgz", - "integrity": "sha512-fR9RXCc+6Dxav4P9VV/sp5w3eFiSdOjJYsbtWfd4s5L5C4ogyuVpdKIVHeW0vV1MloM65/f7W45nR9ZxwVdyiA==", + "version": "5.22.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.22.1.tgz", + "integrity": "sha512-Ji2IJhFXZY0x/0tVBXeQwgPlLWw13GVzpsWPQ3rV50IFMMof2I55PZZxtm4P6iNq+L5znYN9nSTAq0ZyE6lSJw==", "dev": true, "requires": { "busboy": "^1.6.0" diff --git a/package.json b/package.json index 8ef2738b5..b198a0823 100644 --- a/package.json +++ b/package.json @@ -89,6 +89,7 @@ "solidity-ast": "^0.4.25", "solidity-coverage": "^0.8.0", "solidity-docgen": "^0.6.0-beta.29", + "undici": "^5.22.1", "web3": "^1.3.0", "yargs": "^17.0.0" } diff --git a/scripts/release/workflow/state.js b/scripts/release/workflow/state.js index 4d905e260..5cfaafb86 100644 --- a/scripts/release/workflow/state.js +++ b/scripts/release/workflow/state.js @@ -1,7 +1,8 @@ const { readPreState } = require('@changesets/pre'); const { default: readChangesets } = require('@changesets/read'); const { join } = require('path'); -const { version } = require(join(__dirname, '../../../package.json')); +const { fetch } = require('undici'); +const { version, name: packageName } = require(join(__dirname, '../../../contracts/package.json')); module.exports = async ({ github, context, core }) => { const state = await getState({ github, context, core }); @@ -34,8 +35,8 @@ function shouldRunChangesets({ isReleaseBranch, isPush, isWorkflowDispatch, botR return (isReleaseBranch && isPush) || (isReleaseBranch && isWorkflowDispatch && botRun); } -function shouldRunPublish({ isReleaseBranch, isPush, hasPendingChangesets }) { - return isReleaseBranch && isPush && !hasPendingChangesets; +function shouldRunPublish({ isReleaseBranch, isPush, hasPendingChangesets, isPublishedOnNpm }) { + return isReleaseBranch && isPush && !hasPendingChangesets && !isPublishedOnNpm; } function shouldRunMerge({ @@ -80,6 +81,8 @@ async function getState({ github, context, core }) { state.prBackExists = prs.length === 0; + state.isPublishedOnNpm = await isPublishedOnNpm(packageName, version); + // Log every state value in debug mode if (core.isDebug()) for (const [key, value] of Object.entries(state)) core.debug(`${key}: ${value}`); @@ -102,3 +105,8 @@ async function readChangesetState(cwd = process.cwd()) { changesets, }; } + +async function isPublishedOnNpm(package, version) { + const res = await fetch(`https://registry.npmjs.com/${package}/${version}`); + return res.ok; +} From 3ec4307c8a79ddfee9bbaf1f26435c908fae0cae Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Thu, 11 May 2023 19:17:06 +0200 Subject: [PATCH 062/182] Fix bug allowing anyone to cancel an admin renounce (#4238) Co-authored-by: Francisco Giordano --- .../access/AccessControlDefaultAdminRules.sol | 4 ++-- test/access/AccessControl.behavior.js | 24 +++++++++++-------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/contracts/access/AccessControlDefaultAdminRules.sol b/contracts/access/AccessControlDefaultAdminRules.sol index 33ad56e40..07a5b4f70 100644 --- a/contracts/access/AccessControlDefaultAdminRules.sol +++ b/contracts/access/AccessControlDefaultAdminRules.sol @@ -106,7 +106,7 @@ abstract contract AccessControlDefaultAdminRules is IAccessControlDefaultAdminRu * non-administrated role. */ function renounceRole(bytes32 role, address account) public virtual override(AccessControl, IAccessControl) { - if (role == DEFAULT_ADMIN_ROLE) { + if (role == DEFAULT_ADMIN_ROLE && account == defaultAdmin()) { (address newDefaultAdmin, uint48 schedule) = pendingDefaultAdmin(); require( newDefaultAdmin == address(0) && _isScheduleSet(schedule) && _hasSchedulePassed(schedule), @@ -138,7 +138,7 @@ abstract contract AccessControlDefaultAdminRules is IAccessControlDefaultAdminRu * @dev See {AccessControl-_revokeRole}. */ function _revokeRole(bytes32 role, address account) internal virtual override { - if (role == DEFAULT_ADMIN_ROLE && account == _currentDefaultAdmin) { + if (role == DEFAULT_ADMIN_ROLE && account == defaultAdmin()) { delete _currentDefaultAdmin; } super._revokeRole(role, account); diff --git a/test/access/AccessControl.behavior.js b/test/access/AccessControl.behavior.js index e7a91957e..3e61616a7 100644 --- a/test/access/AccessControl.behavior.js +++ b/test/access/AccessControl.behavior.js @@ -621,14 +621,15 @@ function shouldBehaveLikeAccessControlDefaultAdminRules(errorPrefix, delay, defa }); describe('renounces admin', function () { + let expectedSchedule; let delayPassed; + let delayNotPassed; beforeEach(async function () { await this.accessControl.beginDefaultAdminTransfer(constants.ZERO_ADDRESS, { from: defaultAdmin }); - delayPassed = web3.utils - .toBN(await time.latest()) - .add(delay) - .addn(1); + expectedSchedule = web3.utils.toBN(await time.latest()).add(delay); + delayNotPassed = expectedSchedule; + delayPassed = expectedSchedule.addn(1); }); it('reverts if caller is not default admin', async function () { @@ -639,6 +640,15 @@ function shouldBehaveLikeAccessControlDefaultAdminRules(errorPrefix, delay, defa ); }); + it("renouncing the admin role when not an admin doesn't affect the schedule", async function () { + await time.setNextBlockTimestamp(delayPassed); + await this.accessControl.renounceRole(DEFAULT_ADMIN_ROLE, other, { from: other }); + + const { newAdmin, schedule } = await this.accessControl.pendingDefaultAdmin(); + expect(newAdmin).to.equal(constants.ZERO_ADDRESS); + expect(schedule).to.be.bignumber.equal(expectedSchedule); + }); + it('keeps defaultAdmin consistent with hasRole if another non-defaultAdmin user renounces the DEFAULT_ADMIN_ROLE', async function () { await time.setNextBlockTimestamp(delayPassed); @@ -677,12 +687,6 @@ function shouldBehaveLikeAccessControlDefaultAdminRules(errorPrefix, delay, defa }); describe('schedule not passed', function () { - let delayNotPassed; - - beforeEach(function () { - delayNotPassed = delayPassed.subn(1); - }); - for (const [fromSchedule, tag] of [ [-1, 'less'], [0, 'equal'], From 1642b6639b93e3b97be163d49827e1f56b81ca11 Mon Sep 17 00:00:00 2001 From: Francisco Date: Fri, 12 May 2023 18:22:26 +0100 Subject: [PATCH 063/182] Reduce frequency of version comment updates (#4244) --- scripts/release/update-comment.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/release/update-comment.js b/scripts/release/update-comment.js index eb9f937ca..9d6df2694 100755 --- a/scripts/release/update-comment.js +++ b/scripts/release/update-comment.js @@ -16,7 +16,7 @@ const { version } = require('../../package.json'); const [tag] = run('git', 'tag') .split(/\r?\n/) .filter(semver.coerce) // check version can be processed - .filter(v => semver.lt(semver.coerce(v), version)) // only consider older tags, ignore current prereleases + .filter(v => semver.satisfies(v, `< ${version}`)) // ignores prereleases unless currently a prerelease .sort(semver.rcompare); // Ordering tag → HEAD is important here. From 96b95592c300a1b38953b276f0b6c77017f5c942 Mon Sep 17 00:00:00 2001 From: Bhaskar Kashyap <31563474+bskrksyp9@users.noreply.github.com> Date: Wed, 17 May 2023 07:53:52 +0530 Subject: [PATCH 064/182] Fix grammar in docs (#4250) --- docs/modules/ROOT/pages/access-control.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/modules/ROOT/pages/access-control.adoc b/docs/modules/ROOT/pages/access-control.adoc index 56d91e1c1..e52c10401 100644 --- a/docs/modules/ROOT/pages/access-control.adoc +++ b/docs/modules/ROOT/pages/access-control.adoc @@ -171,7 +171,7 @@ Dynamic role allocation is often a desirable property, for example in systems wh [[querying-privileged-accounts]] === Querying Privileged Accounts -Because accounts might <> dynamically, it is not always possible to determine which accounts hold a particular role. This is important as it allows to prove certain properties about a system, such as that an administrative account is a multisig or a DAO, or that a certain role has been removed from all users, effectively disabling any associated functionality. +Because accounts might <> dynamically, it is not always possible to determine which accounts hold a particular role. This is important as it allows proving certain properties about a system, such as that an administrative account is a multisig or a DAO, or that a certain role has been removed from all users, effectively disabling any associated functionality. Under the hood, `AccessControl` uses `EnumerableSet`, a more powerful variant of Solidity's `mapping` type, which allows for key enumeration. `getRoleMemberCount` can be used to retrieve the number of accounts that have a particular role, and `getRoleMember` can then be called to get the address of each of these accounts. From 0f10efe2326dfb79442967b82bde4ff1b28c69cd Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Fri, 19 May 2023 22:48:05 +0200 Subject: [PATCH 065/182] Remove code in preparation for v5.0 (#4258) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ernesto García Co-authored-by: Francisco --- .changeset/beige-ducks-flow.md | 5 + .changeset/fluffy-gifts-build.md | 5 + .changeset/friendly-suits-camp.md | 5 + .changeset/selfish-queens-rest.md | 5 + .changeset/spicy-ducks-cough.md | 5 + .changeset/swift-berries-sort.md | 5 + .changeset/tame-geckos-search.md | 5 + .changeset/three-weeks-double.md | 5 + contracts/access/AccessControl.sol | 24 - contracts/access/AccessControlCrossChain.sol | 45 -- contracts/access/README.adoc | 2 - contracts/crosschain/CrossChainEnabled.sol | 54 -- contracts/crosschain/README.adoc | 34 - .../crosschain/amb/CrossChainEnabledAMB.sol | 49 -- contracts/crosschain/amb/LibAMB.sol | 35 - .../arbitrum/CrossChainEnabledArbitrumL1.sol | 44 -- .../arbitrum/CrossChainEnabledArbitrumL2.sol | 40 -- .../crosschain/arbitrum/LibArbitrumL1.sol | 42 -- .../crosschain/arbitrum/LibArbitrumL2.sol | 45 -- contracts/crosschain/errors.sol | 7 - .../optimism/CrossChainEnabledOptimism.sol | 41 -- contracts/crosschain/optimism/LibOptimism.sol | 36 -- .../polygon/CrossChainEnabledPolygonChild.sol | 72 --- contracts/governance/README.adoc | 4 - contracts/governance/TimelockController.sol | 6 +- .../GovernorCompatibilityBravo.sol | 11 +- .../extensions/GovernorProposalThreshold.sol | 23 - .../GovernorVotesQuorumFraction.sol | 16 +- contracts/interfaces/IERC1820Implementer.sol | 18 +- contracts/interfaces/IERC1820Registry.sol | 110 +++- contracts/interfaces/IERC777.sol | 197 +++++- contracts/interfaces/IERC777Recipient.sol | 32 +- contracts/interfaces/IERC777Sender.sol | 32 +- contracts/interfaces/draft-IERC2612.sol | 7 - .../mocks/AccessControlCrossChainMock.sol | 8 - contracts/mocks/ConditionalEscrowMock.sol | 18 - contracts/mocks/PullPaymentMock.sol | 15 - contracts/mocks/SafeMathMemoryCheck.sol | 72 --- contracts/mocks/TimersBlockNumberImpl.sol | 39 -- contracts/mocks/TimersTimestampImpl.sol | 39 -- contracts/mocks/crosschain/bridges.sol | 94 --- contracts/mocks/crosschain/receivers.sol | 54 -- contracts/mocks/governance/GovernorMock.sol | 17 +- .../mocks/token/ERC20PermitNoRevertMock.sol | 2 +- .../mocks/token/ERC721ConsecutiveMock.sol | 2 +- contracts/mocks/token/ERC777Mock.sol | 13 - .../mocks/token/ERC777SenderRecipientMock.sol | 152 ----- contracts/mocks/wizard/MyGovernor1.sol | 79 --- contracts/mocks/wizard/MyGovernor2.sol | 85 --- contracts/mocks/wizard/MyGovernor3.sol | 94 --- contracts/proxy/beacon/BeaconProxy.sol | 21 - .../TransparentUpgradeableProxy.sol | 27 +- contracts/security/PullPayment.sol | 74 --- contracts/security/README.adoc | 3 - .../token/ERC20/extensions/ERC20Permit.sol | 8 - .../ERC20/extensions/draft-ERC20Permit.sol | 8 - .../ERC20/extensions/draft-IERC20Permit.sol | 7 - contracts/token/ERC20/utils/SafeERC20.sol | 18 - .../ERC721/extensions/draft-ERC721Votes.sol | 9 - contracts/token/ERC777/ERC777.sol | 517 --------------- contracts/token/ERC777/IERC777.sol | 200 ------ contracts/token/ERC777/IERC777Recipient.sol | 35 - contracts/token/ERC777/IERC777Sender.sol | 35 - contracts/token/ERC777/README.adoc | 26 - contracts/utils/Checkpoints.sol | 196 ------ contracts/utils/README.adoc | 25 - contracts/utils/Timers.sol | 75 --- contracts/utils/cryptography/ECDSA.sol | 3 +- contracts/utils/cryptography/draft-EIP712.sol | 8 - contracts/utils/escrow/ConditionalEscrow.sol | 25 - contracts/utils/escrow/Escrow.sol | 67 -- contracts/utils/escrow/RefundEscrow.sol | 100 --- .../introspection/ERC1820Implementer.sol | 43 -- .../introspection/IERC1820Implementer.sol | 20 - .../utils/introspection/IERC1820Registry.sol | 112 ---- contracts/utils/math/Math.sol | 66 ++ contracts/utils/math/SafeCast.sol | 3 - contracts/utils/math/SafeMath.sol | 215 ------- contracts/utils/math/SignedSafeMath.sol | 68 -- contracts/utils/structs/EnumerableMap.sol | 68 -- contracts/vendor/amb/IAMB.sol | 41 -- contracts/vendor/arbitrum/IArbSys.sol | 134 ---- contracts/vendor/arbitrum/IBridge.sol | 102 --- .../arbitrum/IDelayedMessageProvider.sol | 16 - contracts/vendor/arbitrum/IInbox.sol | 152 ----- contracts/vendor/arbitrum/IOutbox.sol | 117 ---- .../vendor/optimism/ICrossDomainMessenger.sol | 34 - contracts/vendor/optimism/LICENSE | 22 - .../vendor/polygon/IFxMessageProcessor.sol | 7 - docs/modules/ROOT/nav.adoc | 3 - docs/modules/ROOT/pages/crosschain.adoc | 210 ------ docs/modules/ROOT/pages/erc1155.adoc | 8 +- docs/modules/ROOT/pages/erc777.adoc | 75 --- docs/modules/ROOT/pages/tokens.adoc | 1 - docs/modules/ROOT/pages/utilities.adoc | 10 +- hardhat.config.js | 6 +- scripts/generate/templates/Checkpoints.js | 86 +-- .../generate/templates/Checkpoints.opts.js | 7 +- scripts/generate/templates/Checkpoints.t.js | 118 +--- scripts/generate/templates/EnumerableMap.js | 30 - scripts/generate/templates/SafeCast.js | 3 - scripts/upgradeable/upgradeable.patch | 13 - test/access/AccessControlCrossChain.test.js | 49 -- test/crosschain/CrossChainEnabled.test.js | 78 --- test/helpers/crosschain.js | 61 -- test/security/PullPayment.test.js | 51 -- test/token/ERC20/utils/SafeERC20.test.js | 37 -- test/token/ERC777/ERC777.behavior.js | 597 ------------------ test/token/ERC777/ERC777.test.js | 556 ---------------- test/utils/Checkpoints.t.sol | 123 ---- test/utils/Checkpoints.test.js | 190 ++---- test/utils/Create2.test.js | 13 +- test/utils/TimersBlockNumberImpl.test.js | 55 -- test/utils/TimersTimestamp.test.js | 55 -- test/utils/escrow/ConditionalEscrow.test.js | 37 -- test/utils/escrow/Escrow.behavior.js | 90 --- test/utils/escrow/Escrow.test.js | 14 - test/utils/escrow/RefundEscrow.test.js | 143 ----- .../introspection/ERC1820Implementer.test.js | 71 --- test/utils/math/Math.t.sol | 3 +- test/utils/math/Math.test.js | 139 ++++ test/utils/math/SafeMath.test.js | 433 ------------- test/utils/math/SignedSafeMath.test.js | 152 ----- test/utils/structs/EnumerableMap.behavior.js | 11 - test/utils/structs/EnumerableMap.test.js | 5 - 125 files changed, 700 insertions(+), 7389 deletions(-) create mode 100644 .changeset/beige-ducks-flow.md create mode 100644 .changeset/fluffy-gifts-build.md create mode 100644 .changeset/friendly-suits-camp.md create mode 100644 .changeset/selfish-queens-rest.md create mode 100644 .changeset/spicy-ducks-cough.md create mode 100644 .changeset/swift-berries-sort.md create mode 100644 .changeset/tame-geckos-search.md create mode 100644 .changeset/three-weeks-double.md delete mode 100644 contracts/access/AccessControlCrossChain.sol delete mode 100644 contracts/crosschain/CrossChainEnabled.sol delete mode 100644 contracts/crosschain/README.adoc delete mode 100644 contracts/crosschain/amb/CrossChainEnabledAMB.sol delete mode 100644 contracts/crosschain/amb/LibAMB.sol delete mode 100644 contracts/crosschain/arbitrum/CrossChainEnabledArbitrumL1.sol delete mode 100644 contracts/crosschain/arbitrum/CrossChainEnabledArbitrumL2.sol delete mode 100644 contracts/crosschain/arbitrum/LibArbitrumL1.sol delete mode 100644 contracts/crosschain/arbitrum/LibArbitrumL2.sol delete mode 100644 contracts/crosschain/errors.sol delete mode 100644 contracts/crosschain/optimism/CrossChainEnabledOptimism.sol delete mode 100644 contracts/crosschain/optimism/LibOptimism.sol delete mode 100644 contracts/crosschain/polygon/CrossChainEnabledPolygonChild.sol delete mode 100644 contracts/governance/extensions/GovernorProposalThreshold.sol delete mode 100644 contracts/interfaces/draft-IERC2612.sol delete mode 100644 contracts/mocks/AccessControlCrossChainMock.sol delete mode 100644 contracts/mocks/ConditionalEscrowMock.sol delete mode 100644 contracts/mocks/PullPaymentMock.sol delete mode 100644 contracts/mocks/SafeMathMemoryCheck.sol delete mode 100644 contracts/mocks/TimersBlockNumberImpl.sol delete mode 100644 contracts/mocks/TimersTimestampImpl.sol delete mode 100644 contracts/mocks/crosschain/bridges.sol delete mode 100644 contracts/mocks/crosschain/receivers.sol delete mode 100644 contracts/mocks/token/ERC777Mock.sol delete mode 100644 contracts/mocks/token/ERC777SenderRecipientMock.sol delete mode 100644 contracts/mocks/wizard/MyGovernor1.sol delete mode 100644 contracts/mocks/wizard/MyGovernor2.sol delete mode 100644 contracts/mocks/wizard/MyGovernor3.sol delete mode 100644 contracts/security/PullPayment.sol delete mode 100644 contracts/token/ERC20/extensions/draft-ERC20Permit.sol delete mode 100644 contracts/token/ERC20/extensions/draft-IERC20Permit.sol delete mode 100644 contracts/token/ERC721/extensions/draft-ERC721Votes.sol delete mode 100644 contracts/token/ERC777/ERC777.sol delete mode 100644 contracts/token/ERC777/IERC777.sol delete mode 100644 contracts/token/ERC777/IERC777Recipient.sol delete mode 100644 contracts/token/ERC777/IERC777Sender.sol delete mode 100644 contracts/token/ERC777/README.adoc delete mode 100644 contracts/utils/Timers.sol delete mode 100644 contracts/utils/cryptography/draft-EIP712.sol delete mode 100644 contracts/utils/escrow/ConditionalEscrow.sol delete mode 100644 contracts/utils/escrow/Escrow.sol delete mode 100644 contracts/utils/escrow/RefundEscrow.sol delete mode 100644 contracts/utils/introspection/ERC1820Implementer.sol delete mode 100644 contracts/utils/introspection/IERC1820Implementer.sol delete mode 100644 contracts/utils/introspection/IERC1820Registry.sol delete mode 100644 contracts/utils/math/SafeMath.sol delete mode 100644 contracts/utils/math/SignedSafeMath.sol delete mode 100644 contracts/vendor/amb/IAMB.sol delete mode 100644 contracts/vendor/arbitrum/IArbSys.sol delete mode 100644 contracts/vendor/arbitrum/IBridge.sol delete mode 100644 contracts/vendor/arbitrum/IDelayedMessageProvider.sol delete mode 100644 contracts/vendor/arbitrum/IInbox.sol delete mode 100644 contracts/vendor/arbitrum/IOutbox.sol delete mode 100644 contracts/vendor/optimism/ICrossDomainMessenger.sol delete mode 100644 contracts/vendor/optimism/LICENSE delete mode 100644 contracts/vendor/polygon/IFxMessageProcessor.sol delete mode 100644 docs/modules/ROOT/pages/crosschain.adoc delete mode 100644 docs/modules/ROOT/pages/erc777.adoc delete mode 100644 test/access/AccessControlCrossChain.test.js delete mode 100644 test/crosschain/CrossChainEnabled.test.js delete mode 100644 test/helpers/crosschain.js delete mode 100644 test/security/PullPayment.test.js delete mode 100644 test/token/ERC777/ERC777.behavior.js delete mode 100644 test/token/ERC777/ERC777.test.js delete mode 100644 test/utils/TimersBlockNumberImpl.test.js delete mode 100644 test/utils/TimersTimestamp.test.js delete mode 100644 test/utils/escrow/ConditionalEscrow.test.js delete mode 100644 test/utils/escrow/Escrow.behavior.js delete mode 100644 test/utils/escrow/Escrow.test.js delete mode 100644 test/utils/escrow/RefundEscrow.test.js delete mode 100644 test/utils/introspection/ERC1820Implementer.test.js delete mode 100644 test/utils/math/SafeMath.test.js delete mode 100644 test/utils/math/SignedSafeMath.test.js diff --git a/.changeset/beige-ducks-flow.md b/.changeset/beige-ducks-flow.md new file mode 100644 index 000000000..4edc870b5 --- /dev/null +++ b/.changeset/beige-ducks-flow.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': major +--- + +Remove deprecated GovernorProposalThreshold module. diff --git a/.changeset/fluffy-gifts-build.md b/.changeset/fluffy-gifts-build.md new file mode 100644 index 000000000..a1ffe8be1 --- /dev/null +++ b/.changeset/fluffy-gifts-build.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': major +--- + +Remove ERC1820Implementer. diff --git a/.changeset/friendly-suits-camp.md b/.changeset/friendly-suits-camp.md new file mode 100644 index 000000000..bcf1d7ee4 --- /dev/null +++ b/.changeset/friendly-suits-camp.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': major +--- + +Remove Checkpoints.History. diff --git a/.changeset/selfish-queens-rest.md b/.changeset/selfish-queens-rest.md new file mode 100644 index 000000000..739fa1356 --- /dev/null +++ b/.changeset/selfish-queens-rest.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': major +--- + +Remove PullPayment and Escrow contracts (Escrow, ConditionalEscrow, RefundEscrow). diff --git a/.changeset/spicy-ducks-cough.md b/.changeset/spicy-ducks-cough.md new file mode 100644 index 000000000..bf7296e4e --- /dev/null +++ b/.changeset/spicy-ducks-cough.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': major +--- + +Remove the Timers library. diff --git a/.changeset/swift-berries-sort.md b/.changeset/swift-berries-sort.md new file mode 100644 index 000000000..9af6cba64 --- /dev/null +++ b/.changeset/swift-berries-sort.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': major +--- + +Remove ERC777 implementation. diff --git a/.changeset/tame-geckos-search.md b/.changeset/tame-geckos-search.md new file mode 100644 index 000000000..6b6ae86d0 --- /dev/null +++ b/.changeset/tame-geckos-search.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': major +--- + +Remove SafeMath and SignedSafeMath libraries. diff --git a/.changeset/three-weeks-double.md b/.changeset/three-weeks-double.md new file mode 100644 index 000000000..d267e72c4 --- /dev/null +++ b/.changeset/three-weeks-double.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': major +--- + +Remove CrossChain contracts, including AccessControlCrossChain and all the vendored bridge interfaces. diff --git a/contracts/access/AccessControl.sol b/contracts/access/AccessControl.sol index 3a73de78b..24fe0c00c 100644 --- a/contracts/access/AccessControl.sol +++ b/contracts/access/AccessControl.sol @@ -183,30 +183,6 @@ abstract contract AccessControl is Context, IAccessControl, ERC165 { _revokeRole(role, account); } - /** - * @dev Grants `role` to `account`. - * - * If `account` had not been already granted `role`, emits a {RoleGranted} - * event. Note that unlike {grantRole}, this function doesn't perform any - * checks on the calling account. - * - * May emit a {RoleGranted} event. - * - * [WARNING] - * ==== - * This function should only be called from the constructor when setting - * up the initial roles for the system. - * - * Using this function in any other way is effectively circumventing the admin - * system imposed by {AccessControl}. - * ==== - * - * NOTE: This function is deprecated in favor of {_grantRole}. - */ - function _setupRole(bytes32 role, address account) internal virtual { - _grantRole(role, account); - } - /** * @dev Sets `adminRole` as ``role``'s admin role. * diff --git a/contracts/access/AccessControlCrossChain.sol b/contracts/access/AccessControlCrossChain.sol deleted file mode 100644 index 95be5091c..000000000 --- a/contracts/access/AccessControlCrossChain.sol +++ /dev/null @@ -1,45 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.6.0) (access/AccessControlCrossChain.sol) - -pragma solidity ^0.8.4; - -import "./AccessControl.sol"; -import "../crosschain/CrossChainEnabled.sol"; - -/** - * @dev An extension to {AccessControl} with support for cross-chain access management. - * For each role, is extension implements an equivalent "aliased" role that is used for - * restricting calls originating from other chains. - * - * For example, if a function `myFunction` is protected by `onlyRole(SOME_ROLE)`, and - * if an address `x` has role `SOME_ROLE`, it would be able to call `myFunction` directly. - * A wallet or contract at the same address on another chain would however not be able - * to call this function. In order to do so, it would require to have the role - * `_crossChainRoleAlias(SOME_ROLE)`. - * - * This aliasing is required to protect against multiple contracts living at the same - * address on different chains but controlled by conflicting entities. - * - * _Available since v4.6._ - */ -abstract contract AccessControlCrossChain is AccessControl, CrossChainEnabled { - bytes32 public constant CROSSCHAIN_ALIAS = keccak256("CROSSCHAIN_ALIAS"); - - /** - * @dev See {AccessControl-_checkRole}. - */ - function _checkRole(bytes32 role) internal view virtual override { - if (_isCrossChain()) { - _checkRole(_crossChainRoleAlias(role), _crossChainSender()); - } else { - super._checkRole(role); - } - } - - /** - * @dev Returns the aliased role corresponding to `role`. - */ - function _crossChainRoleAlias(bytes32 role) internal pure virtual returns (bytes32) { - return role ^ CROSSCHAIN_ALIAS; - } -} diff --git a/contracts/access/README.adoc b/contracts/access/README.adoc index 80ca0020f..117cd7c97 100644 --- a/contracts/access/README.adoc +++ b/contracts/access/README.adoc @@ -18,8 +18,6 @@ This directory provides ways to restrict who can access the functions of a contr {{AccessControl}} -{{AccessControlCrossChain}} - {{IAccessControlEnumerable}} {{AccessControlEnumerable}} diff --git a/contracts/crosschain/CrossChainEnabled.sol b/contracts/crosschain/CrossChainEnabled.sol deleted file mode 100644 index 4c9b9e5c5..000000000 --- a/contracts/crosschain/CrossChainEnabled.sol +++ /dev/null @@ -1,54 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.6.0) (crosschain/CrossChainEnabled.sol) - -pragma solidity ^0.8.4; - -import "./errors.sol"; - -/** - * @dev Provides information for building cross-chain aware contracts. This - * abstract contract provides accessors and modifiers to control the execution - * flow when receiving cross-chain messages. - * - * Actual implementations of cross-chain aware contracts, which are based on - * this abstraction, will have to inherit from a bridge-specific - * specialization. Such specializations are provided under - * `crosschain//CrossChainEnabled.sol`. - * - * _Available since v4.6._ - */ -abstract contract CrossChainEnabled { - /** - * @dev Throws if the current function call is not the result of a - * cross-chain execution. - */ - modifier onlyCrossChain() { - if (!_isCrossChain()) revert NotCrossChainCall(); - _; - } - - /** - * @dev Throws if the current function call is not the result of a - * cross-chain execution initiated by `account`. - */ - modifier onlyCrossChainSender(address expected) { - address actual = _crossChainSender(); - if (expected != actual) revert InvalidCrossChainSender(actual, expected); - _; - } - - /** - * @dev Returns whether the current function call is the result of a - * cross-chain message. - */ - function _isCrossChain() internal view virtual returns (bool); - - /** - * @dev Returns the address of the sender of the cross-chain message that - * triggered the current function call. - * - * IMPORTANT: Should revert with `NotCrossChainCall` if the current function - * call is not the result of a cross-chain message. - */ - function _crossChainSender() internal view virtual returns (address); -} diff --git a/contracts/crosschain/README.adoc b/contracts/crosschain/README.adoc deleted file mode 100644 index 266b1530f..000000000 --- a/contracts/crosschain/README.adoc +++ /dev/null @@ -1,34 +0,0 @@ -= Cross Chain Awareness - -[.readme-notice] -NOTE: This document is better viewed at https://docs.openzeppelin.com/contracts/api/crosschain - -This directory provides building blocks to improve cross-chain awareness of smart contracts. - -- {CrossChainEnabled} is an abstraction that contains accessors and modifiers to control the execution flow when receiving cross-chain messages. - -== CrossChainEnabled specializations - -The following specializations of {CrossChainEnabled} provide implementations of the {CrossChainEnabled} abstraction for specific bridges. This can be used to complex cross-chain aware components such as {AccessControlCrossChain}. - -{{CrossChainEnabledAMB}} - -{{CrossChainEnabledArbitrumL1}} - -{{CrossChainEnabledArbitrumL2}} - -{{CrossChainEnabledOptimism}} - -{{CrossChainEnabledPolygonChild}} - -== Libraries for cross-chain - -In addition to the {CrossChainEnabled} abstraction, cross-chain awareness is also available through libraries. These libraries can be used to build complex designs such as contracts with the ability to interact with multiple bridges. - -{{LibAMB}} - -{{LibArbitrumL1}} - -{{LibArbitrumL2}} - -{{LibOptimism}} diff --git a/contracts/crosschain/amb/CrossChainEnabledAMB.sol b/contracts/crosschain/amb/CrossChainEnabledAMB.sol deleted file mode 100644 index e69355d62..000000000 --- a/contracts/crosschain/amb/CrossChainEnabledAMB.sol +++ /dev/null @@ -1,49 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.7.0) (crosschain/amb/CrossChainEnabledAMB.sol) - -pragma solidity ^0.8.4; - -import "../CrossChainEnabled.sol"; -import "./LibAMB.sol"; - -/** - * @dev https://docs.tokenbridge.net/amb-bridge/about-amb-bridge[AMB] - * specialization or the {CrossChainEnabled} abstraction. - * - * As of february 2020, AMB bridges are available between the following chains: - * - * - https://docs.tokenbridge.net/eth-xdai-amb-bridge/about-the-eth-xdai-amb[ETH ⇌ xDai] - * - https://docs.tokenbridge.net/eth-qdai-bridge/about-the-eth-qdai-amb[ETH ⇌ qDai] - * - https://docs.tokenbridge.net/eth-etc-amb-bridge/about-the-eth-etc-amb[ETH ⇌ ETC] - * - https://docs.tokenbridge.net/eth-bsc-amb/about-the-eth-bsc-amb[ETH ⇌ BSC] - * - https://docs.tokenbridge.net/eth-poa-amb-bridge/about-the-eth-poa-amb[ETH ⇌ POA] - * - https://docs.tokenbridge.net/bsc-xdai-amb/about-the-bsc-xdai-amb[BSC ⇌ xDai] - * - https://docs.tokenbridge.net/poa-xdai-amb/about-the-poa-xdai-amb[POA ⇌ xDai] - * - https://docs.tokenbridge.net/rinkeby-xdai-amb-bridge/about-the-rinkeby-xdai-amb[Rinkeby ⇌ xDai] - * - https://docs.tokenbridge.net/kovan-sokol-amb-bridge/about-the-kovan-sokol-amb[Kovan ⇌ Sokol] - * - * _Available since v4.6._ - */ -contract CrossChainEnabledAMB is CrossChainEnabled { - /// @custom:oz-upgrades-unsafe-allow state-variable-immutable - address private immutable _bridge; - - /// @custom:oz-upgrades-unsafe-allow constructor - constructor(address bridge) { - _bridge = bridge; - } - - /** - * @dev see {CrossChainEnabled-_isCrossChain} - */ - function _isCrossChain() internal view virtual override returns (bool) { - return LibAMB.isCrossChain(_bridge); - } - - /** - * @dev see {CrossChainEnabled-_crossChainSender} - */ - function _crossChainSender() internal view virtual override onlyCrossChain returns (address) { - return LibAMB.crossChainSender(_bridge); - } -} diff --git a/contracts/crosschain/amb/LibAMB.sol b/contracts/crosschain/amb/LibAMB.sol deleted file mode 100644 index aef9c43ee..000000000 --- a/contracts/crosschain/amb/LibAMB.sol +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.7.0) (crosschain/amb/LibAMB.sol) - -pragma solidity ^0.8.4; - -import {IAMB as AMB_Bridge} from "../../vendor/amb/IAMB.sol"; -import "../errors.sol"; - -/** - * @dev Primitives for cross-chain aware contracts using the - * https://docs.tokenbridge.net/amb-bridge/about-amb-bridge[AMB] - * family of bridges. - */ -library LibAMB { - /** - * @dev Returns whether the current function call is the result of a - * cross-chain message relayed by `bridge`. - */ - function isCrossChain(address bridge) internal view returns (bool) { - return msg.sender == bridge; - } - - /** - * @dev Returns the address of the sender that triggered the current - * cross-chain message through `bridge`. - * - * NOTE: {isCrossChain} should be checked before trying to recover the - * sender, as it will revert with `NotCrossChainCall` if the current - * function call is not the result of a cross-chain message. - */ - function crossChainSender(address bridge) internal view returns (address) { - if (!isCrossChain(bridge)) revert NotCrossChainCall(); - return AMB_Bridge(bridge).messageSender(); - } -} diff --git a/contracts/crosschain/arbitrum/CrossChainEnabledArbitrumL1.sol b/contracts/crosschain/arbitrum/CrossChainEnabledArbitrumL1.sol deleted file mode 100644 index 5068da395..000000000 --- a/contracts/crosschain/arbitrum/CrossChainEnabledArbitrumL1.sol +++ /dev/null @@ -1,44 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.7.0) (crosschain/arbitrum/CrossChainEnabledArbitrumL1.sol) - -pragma solidity ^0.8.4; - -import "../CrossChainEnabled.sol"; -import "./LibArbitrumL1.sol"; - -/** - * @dev https://arbitrum.io/[Arbitrum] specialization or the - * {CrossChainEnabled} abstraction the L1 side (mainnet). - * - * This version should only be deployed on L1 to process cross-chain messages - * originating from L2. For the other side, use {CrossChainEnabledArbitrumL2}. - * - * The bridge contract is provided and maintained by the arbitrum team. You can - * find the address of this contract on the rinkeby testnet in - * https://developer.offchainlabs.com/docs/useful_addresses[Arbitrum's developer documentation]. - * - * _Available since v4.6._ - */ -abstract contract CrossChainEnabledArbitrumL1 is CrossChainEnabled { - /// @custom:oz-upgrades-unsafe-allow state-variable-immutable - address private immutable _bridge; - - /// @custom:oz-upgrades-unsafe-allow constructor - constructor(address bridge) { - _bridge = bridge; - } - - /** - * @dev see {CrossChainEnabled-_isCrossChain} - */ - function _isCrossChain() internal view virtual override returns (bool) { - return LibArbitrumL1.isCrossChain(_bridge); - } - - /** - * @dev see {CrossChainEnabled-_crossChainSender} - */ - function _crossChainSender() internal view virtual override onlyCrossChain returns (address) { - return LibArbitrumL1.crossChainSender(_bridge); - } -} diff --git a/contracts/crosschain/arbitrum/CrossChainEnabledArbitrumL2.sol b/contracts/crosschain/arbitrum/CrossChainEnabledArbitrumL2.sol deleted file mode 100644 index e85993dbb..000000000 --- a/contracts/crosschain/arbitrum/CrossChainEnabledArbitrumL2.sol +++ /dev/null @@ -1,40 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.8.0) (crosschain/arbitrum/CrossChainEnabledArbitrumL2.sol) - -pragma solidity ^0.8.4; - -import "../CrossChainEnabled.sol"; -import "./LibArbitrumL2.sol"; - -/** - * @dev https://arbitrum.io/[Arbitrum] specialization or the - * {CrossChainEnabled} abstraction the L2 side (arbitrum). - * - * This version should only be deployed on L2 to process cross-chain messages - * originating from L1. For the other side, use {CrossChainEnabledArbitrumL1}. - * - * Arbitrum L2 includes the `ArbSys` contract at a fixed address. Therefore, - * this specialization of {CrossChainEnabled} does not include a constructor. - * - * _Available since v4.6._ - * - * WARNING: There is currently a bug in Arbitrum that causes this contract to - * fail to detect cross-chain calls when deployed behind a proxy. This will be - * fixed when the network is upgraded to Arbitrum Nitro, currently scheduled for - * August 31st 2022. - */ -abstract contract CrossChainEnabledArbitrumL2 is CrossChainEnabled { - /** - * @dev see {CrossChainEnabled-_isCrossChain} - */ - function _isCrossChain() internal view virtual override returns (bool) { - return LibArbitrumL2.isCrossChain(LibArbitrumL2.ARBSYS); - } - - /** - * @dev see {CrossChainEnabled-_crossChainSender} - */ - function _crossChainSender() internal view virtual override onlyCrossChain returns (address) { - return LibArbitrumL2.crossChainSender(LibArbitrumL2.ARBSYS); - } -} diff --git a/contracts/crosschain/arbitrum/LibArbitrumL1.sol b/contracts/crosschain/arbitrum/LibArbitrumL1.sol deleted file mode 100644 index be7236b24..000000000 --- a/contracts/crosschain/arbitrum/LibArbitrumL1.sol +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.8.0) (crosschain/arbitrum/LibArbitrumL1.sol) - -pragma solidity ^0.8.4; - -import {IBridge as ArbitrumL1_Bridge} from "../../vendor/arbitrum/IBridge.sol"; -import {IOutbox as ArbitrumL1_Outbox} from "../../vendor/arbitrum/IOutbox.sol"; -import "../errors.sol"; - -/** - * @dev Primitives for cross-chain aware contracts for - * https://arbitrum.io/[Arbitrum]. - * - * This version should only be used on L1 to process cross-chain messages - * originating from L2. For the other side, use {LibArbitrumL2}. - */ -library LibArbitrumL1 { - /** - * @dev Returns whether the current function call is the result of a - * cross-chain message relayed by the `bridge`. - */ - function isCrossChain(address bridge) internal view returns (bool) { - return msg.sender == bridge; - } - - /** - * @dev Returns the address of the sender that triggered the current - * cross-chain message through the `bridge`. - * - * NOTE: {isCrossChain} should be checked before trying to recover the - * sender, as it will revert with `NotCrossChainCall` if the current - * function call is not the result of a cross-chain message. - */ - function crossChainSender(address bridge) internal view returns (address) { - if (!isCrossChain(bridge)) revert NotCrossChainCall(); - - address sender = ArbitrumL1_Outbox(ArbitrumL1_Bridge(bridge).activeOutbox()).l2ToL1Sender(); - require(sender != address(0), "LibArbitrumL1: system messages without sender"); - - return sender; - } -} diff --git a/contracts/crosschain/arbitrum/LibArbitrumL2.sol b/contracts/crosschain/arbitrum/LibArbitrumL2.sol deleted file mode 100644 index 715a3878f..000000000 --- a/contracts/crosschain/arbitrum/LibArbitrumL2.sol +++ /dev/null @@ -1,45 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.8.0) (crosschain/arbitrum/LibArbitrumL2.sol) - -pragma solidity ^0.8.4; - -import {IArbSys as ArbitrumL2_Bridge} from "../../vendor/arbitrum/IArbSys.sol"; -import "../errors.sol"; - -/** - * @dev Primitives for cross-chain aware contracts for - * https://arbitrum.io/[Arbitrum]. - * - * This version should only be used on L2 to process cross-chain messages - * originating from L1. For the other side, use {LibArbitrumL1}. - * - * WARNING: There is currently a bug in Arbitrum that causes this contract to - * fail to detect cross-chain calls when deployed behind a proxy. This will be - * fixed when the network is upgraded to Arbitrum Nitro, currently scheduled for - * August 31st 2022. - */ -library LibArbitrumL2 { - /** - * @dev Returns whether the current function call is the result of a - * cross-chain message relayed by `arbsys`. - */ - address public constant ARBSYS = 0x0000000000000000000000000000000000000064; - - function isCrossChain(address arbsys) internal view returns (bool) { - return ArbitrumL2_Bridge(arbsys).wasMyCallersAddressAliased(); - } - - /** - * @dev Returns the address of the sender that triggered the current - * cross-chain message through `arbsys`. - * - * NOTE: {isCrossChain} should be checked before trying to recover the - * sender, as it will revert with `NotCrossChainCall` if the current - * function call is not the result of a cross-chain message. - */ - function crossChainSender(address arbsys) internal view returns (address) { - if (!isCrossChain(arbsys)) revert NotCrossChainCall(); - - return ArbitrumL2_Bridge(arbsys).myCallersAddressWithoutAliasing(); - } -} diff --git a/contracts/crosschain/errors.sol b/contracts/crosschain/errors.sol deleted file mode 100644 index 004460e97..000000000 --- a/contracts/crosschain/errors.sol +++ /dev/null @@ -1,7 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.6.0) (crosschain/errors.sol) - -pragma solidity ^0.8.4; - -error NotCrossChainCall(); -error InvalidCrossChainSender(address actual, address expected); diff --git a/contracts/crosschain/optimism/CrossChainEnabledOptimism.sol b/contracts/crosschain/optimism/CrossChainEnabledOptimism.sol deleted file mode 100644 index 1005864ae..000000000 --- a/contracts/crosschain/optimism/CrossChainEnabledOptimism.sol +++ /dev/null @@ -1,41 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.7.0) (crosschain/optimism/CrossChainEnabledOptimism.sol) - -pragma solidity ^0.8.4; - -import "../CrossChainEnabled.sol"; -import "./LibOptimism.sol"; - -/** - * @dev https://www.optimism.io/[Optimism] specialization or the - * {CrossChainEnabled} abstraction. - * - * The messenger (`CrossDomainMessenger`) contract is provided and maintained by - * the optimism team. You can find the address of this contract on mainnet and - * kovan in the https://github.com/ethereum-optimism/optimism/tree/develop/packages/contracts/deployments[deployments section of Optimism monorepo]. - * - * _Available since v4.6._ - */ -abstract contract CrossChainEnabledOptimism is CrossChainEnabled { - /// @custom:oz-upgrades-unsafe-allow state-variable-immutable - address private immutable _messenger; - - /// @custom:oz-upgrades-unsafe-allow constructor - constructor(address messenger) { - _messenger = messenger; - } - - /** - * @dev see {CrossChainEnabled-_isCrossChain} - */ - function _isCrossChain() internal view virtual override returns (bool) { - return LibOptimism.isCrossChain(_messenger); - } - - /** - * @dev see {CrossChainEnabled-_crossChainSender} - */ - function _crossChainSender() internal view virtual override onlyCrossChain returns (address) { - return LibOptimism.crossChainSender(_messenger); - } -} diff --git a/contracts/crosschain/optimism/LibOptimism.sol b/contracts/crosschain/optimism/LibOptimism.sol deleted file mode 100644 index d963ade2a..000000000 --- a/contracts/crosschain/optimism/LibOptimism.sol +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.7.0) (crosschain/optimism/LibOptimism.sol) - -pragma solidity ^0.8.4; - -import {ICrossDomainMessenger as Optimism_Bridge} from "../../vendor/optimism/ICrossDomainMessenger.sol"; -import "../errors.sol"; - -/** - * @dev Primitives for cross-chain aware contracts for https://www.optimism.io/[Optimism]. - * See the https://community.optimism.io/docs/developers/bridge/messaging/#accessing-msg-sender[documentation] - * for the functionality used here. - */ -library LibOptimism { - /** - * @dev Returns whether the current function call is the result of a - * cross-chain message relayed by `messenger`. - */ - function isCrossChain(address messenger) internal view returns (bool) { - return msg.sender == messenger; - } - - /** - * @dev Returns the address of the sender that triggered the current - * cross-chain message through `messenger`. - * - * NOTE: {isCrossChain} should be checked before trying to recover the - * sender, as it will revert with `NotCrossChainCall` if the current - * function call is not the result of a cross-chain message. - */ - function crossChainSender(address messenger) internal view returns (address) { - if (!isCrossChain(messenger)) revert NotCrossChainCall(); - - return Optimism_Bridge(messenger).xDomainMessageSender(); - } -} diff --git a/contracts/crosschain/polygon/CrossChainEnabledPolygonChild.sol b/contracts/crosschain/polygon/CrossChainEnabledPolygonChild.sol deleted file mode 100644 index fa0994834..000000000 --- a/contracts/crosschain/polygon/CrossChainEnabledPolygonChild.sol +++ /dev/null @@ -1,72 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.7.0) (crosschain/polygon/CrossChainEnabledPolygonChild.sol) - -pragma solidity ^0.8.4; - -import "../CrossChainEnabled.sol"; -import "../../security/ReentrancyGuard.sol"; -import "../../utils/Address.sol"; -import "../../vendor/polygon/IFxMessageProcessor.sol"; - -address constant DEFAULT_SENDER = 0x000000000000000000000000000000000000dEaD; - -/** - * @dev https://polygon.technology/[Polygon] specialization or the - * {CrossChainEnabled} abstraction the child side (polygon/mumbai). - * - * This version should only be deployed on child chain to process cross-chain - * messages originating from the parent chain. - * - * The fxChild contract is provided and maintained by the polygon team. You can - * find the address of this contract polygon and mumbai in - * https://docs.polygon.technology/docs/develop/l1-l2-communication/fx-portal/#contract-addresses[Polygon's Fx-Portal documentation]. - * - * _Available since v4.6._ - */ -abstract contract CrossChainEnabledPolygonChild is IFxMessageProcessor, CrossChainEnabled, ReentrancyGuard { - /// @custom:oz-upgrades-unsafe-allow state-variable-immutable - address private immutable _fxChild; - address private _sender = DEFAULT_SENDER; - - /// @custom:oz-upgrades-unsafe-allow constructor - constructor(address fxChild) { - _fxChild = fxChild; - } - - /** - * @dev see {CrossChainEnabled-_isCrossChain} - */ - function _isCrossChain() internal view virtual override returns (bool) { - return msg.sender == _fxChild; - } - - /** - * @dev see {CrossChainEnabled-_crossChainSender} - */ - function _crossChainSender() internal view virtual override onlyCrossChain returns (address) { - return _sender; - } - - /** - * @dev External entry point to receive and relay messages originating - * from the fxChild. - * - * Non-reentrancy is crucial to avoid a cross-chain call being able - * to impersonate anyone by just looping through this with user-defined - * arguments. - * - * Note: if _fxChild calls any other function that does a delegate-call, - * then security could be compromised. - */ - function processMessageFromRoot( - uint256 /* stateId */, - address rootMessageSender, - bytes calldata data - ) external override nonReentrant { - if (!_isCrossChain()) revert NotCrossChainCall(); - - _sender = rootMessageSender; - Address.functionDelegateCall(address(this), data, "cross-chain execution failed"); - _sender = DEFAULT_SENDER; - } -} diff --git a/contracts/governance/README.adoc b/contracts/governance/README.adoc index d47f59a77..171283662 100644 --- a/contracts/governance/README.adoc +++ b/contracts/governance/README.adoc @@ -80,10 +80,6 @@ NOTE: Functions of the `Governor` contract do not include access control. If you {{GovernorCompatibilityBravo}} -=== Deprecated - -{{GovernorProposalThreshold}} - == Utils {{Votes}} diff --git a/contracts/governance/TimelockController.sol b/contracts/governance/TimelockController.sol index a8906a5ca..d2c2ae93a 100644 --- a/contracts/governance/TimelockController.sol +++ b/contracts/governance/TimelockController.sol @@ -88,13 +88,13 @@ contract TimelockController is AccessControl, IERC721Receiver, IERC1155Receiver // register proposers and cancellers for (uint256 i = 0; i < proposers.length; ++i) { - _setupRole(PROPOSER_ROLE, proposers[i]); - _setupRole(CANCELLER_ROLE, proposers[i]); + _grantRole(PROPOSER_ROLE, proposers[i]); + _grantRole(CANCELLER_ROLE, proposers[i]); } // register executors for (uint256 i = 0; i < executors.length; ++i) { - _setupRole(EXECUTOR_ROLE, executors[i]); + _grantRole(EXECUTOR_ROLE, executors[i]); } _minDelay = minDelay; diff --git a/contracts/governance/compatibility/GovernorCompatibilityBravo.sol b/contracts/governance/compatibility/GovernorCompatibilityBravo.sol index 1332ac79d..db7301ef4 100644 --- a/contracts/governance/compatibility/GovernorCompatibilityBravo.sol +++ b/contracts/governance/compatibility/GovernorCompatibilityBravo.sol @@ -26,7 +26,6 @@ abstract contract GovernorCompatibilityBravo is IGovernorTimelock, IGovernorComp } struct ProposalDetails { - address proposer; address[] targets; uint256[] values; string[] signatures; @@ -56,7 +55,7 @@ abstract contract GovernorCompatibilityBravo is IGovernorTimelock, IGovernorComp string memory description ) public virtual override(IGovernor, Governor) returns (uint256) { // Stores the proposal details (if not already present) and executes the propose logic from the core. - _storeProposal(_msgSender(), targets, values, new string[](calldatas.length), calldatas, description); + _storeProposal(targets, values, new string[](calldatas.length), calldatas, description); return super.propose(targets, values, calldatas, description); } @@ -75,7 +74,7 @@ abstract contract GovernorCompatibilityBravo is IGovernorTimelock, IGovernorComp // after the full proposal is stored, so the store operation included in the fallback will be skipped. Here we // call `propose` and not `super.propose` to make sure if a child contract override `propose`, whatever code // is added there is also executed when calling this alternative interface. - _storeProposal(_msgSender(), targets, values, signatures, calldatas, description); + _storeProposal(targets, values, signatures, calldatas, description); return propose(targets, values, _encodeCalldata(signatures, calldatas), description); } @@ -132,7 +131,7 @@ abstract contract GovernorCompatibilityBravo is IGovernorTimelock, IGovernorComp bytes32 descriptionHash ) public virtual override(IGovernor, Governor) returns (uint256) { uint256 proposalId = hashProposal(targets, values, calldatas, descriptionHash); - address proposer = _proposalDetails[proposalId].proposer; + address proposer = proposalProposer(proposalId); require( _msgSender() == proposer || getVotes(proposer, clock() - 1) < proposalThreshold(), @@ -182,7 +181,6 @@ abstract contract GovernorCompatibilityBravo is IGovernorTimelock, IGovernorComp * @dev Store proposal metadata (if not already present) for later lookup. */ function _storeProposal( - address proposer, address[] memory targets, uint256[] memory values, string[] memory signatures, @@ -194,7 +192,6 @@ abstract contract GovernorCompatibilityBravo is IGovernorTimelock, IGovernorComp ProposalDetails storage details = _proposalDetails[proposalId]; if (details.descriptionHash == bytes32(0)) { - details.proposer = proposer; details.targets = targets; details.values = values; details.signatures = signatures; @@ -228,12 +225,12 @@ abstract contract GovernorCompatibilityBravo is IGovernorTimelock, IGovernorComp ) { id = proposalId; + proposer = proposalProposer(proposalId); eta = proposalEta(proposalId); startBlock = proposalSnapshot(proposalId); endBlock = proposalDeadline(proposalId); ProposalDetails storage details = _proposalDetails[proposalId]; - proposer = details.proposer; forVotes = details.forVotes; againstVotes = details.againstVotes; abstainVotes = details.abstainVotes; diff --git a/contracts/governance/extensions/GovernorProposalThreshold.sol b/contracts/governance/extensions/GovernorProposalThreshold.sol deleted file mode 100644 index 3feebace0..000000000 --- a/contracts/governance/extensions/GovernorProposalThreshold.sol +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts v4.4.1 (governance/extensions/GovernorProposalThreshold.sol) - -pragma solidity ^0.8.0; - -import "../Governor.sol"; - -/** - * @dev Extension of {Governor} for proposal restriction to token holders with a minimum balance. - * - * _Available since v4.3._ - * _Deprecated since v4.4._ - */ -abstract contract GovernorProposalThreshold is Governor { - function propose( - address[] memory targets, - uint256[] memory values, - bytes[] memory calldatas, - string memory description - ) public virtual override returns (uint256) { - return super.propose(targets, values, calldatas, description); - } -} diff --git a/contracts/governance/extensions/GovernorVotesQuorumFraction.sol b/contracts/governance/extensions/GovernorVotesQuorumFraction.sol index 403570260..e103ea0bf 100644 --- a/contracts/governance/extensions/GovernorVotesQuorumFraction.sol +++ b/contracts/governance/extensions/GovernorVotesQuorumFraction.sol @@ -16,8 +16,6 @@ import "../../utils/math/SafeCast.sol"; abstract contract GovernorVotesQuorumFraction is GovernorVotes { using Checkpoints for Checkpoints.Trace224; - uint256 private _quorumNumerator; // DEPRECATED in favor of _quorumNumeratorHistory - /// @custom:oz-retyped-from Checkpoints.History Checkpoints.Trace224 private _quorumNumeratorHistory; @@ -38,7 +36,7 @@ abstract contract GovernorVotesQuorumFraction is GovernorVotes { * @dev Returns the current quorum numerator. See {quorumDenominator}. */ function quorumNumerator() public view virtual returns (uint256) { - return _quorumNumeratorHistory._checkpoints.length == 0 ? _quorumNumerator : _quorumNumeratorHistory.latest(); + return _quorumNumeratorHistory.latest(); } /** @@ -47,9 +45,6 @@ abstract contract GovernorVotesQuorumFraction is GovernorVotes { function quorumNumerator(uint256 timepoint) public view virtual returns (uint256) { // If history is empty, fallback to old storage uint256 length = _quorumNumeratorHistory._checkpoints.length; - if (length == 0) { - return _quorumNumerator; - } // Optimistic search, check the latest checkpoint Checkpoints.Checkpoint224 memory latest = _quorumNumeratorHistory._checkpoints[length - 1]; @@ -105,15 +100,6 @@ abstract contract GovernorVotesQuorumFraction is GovernorVotes { ); uint256 oldQuorumNumerator = quorumNumerator(); - - // Make sure we keep track of the original numerator in contracts upgraded from a version without checkpoints. - if (oldQuorumNumerator != 0 && _quorumNumeratorHistory._checkpoints.length == 0) { - _quorumNumeratorHistory._checkpoints.push( - Checkpoints.Checkpoint224({_key: 0, _value: SafeCast.toUint224(oldQuorumNumerator)}) - ); - } - - // Set new quorum for future proposals _quorumNumeratorHistory.push(SafeCast.toUint32(clock()), SafeCast.toUint224(newQuorumNumerator)); emit QuorumNumeratorUpdated(oldQuorumNumerator, newQuorumNumerator); diff --git a/contracts/interfaces/IERC1820Implementer.sol b/contracts/interfaces/IERC1820Implementer.sol index a83a7a304..c4d0b3028 100644 --- a/contracts/interfaces/IERC1820Implementer.sol +++ b/contracts/interfaces/IERC1820Implementer.sol @@ -1,6 +1,20 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts v4.4.1 (interfaces/IERC1820Implementer.sol) +// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC1820Implementer.sol) pragma solidity ^0.8.0; -import "../utils/introspection/IERC1820Implementer.sol"; +/** + * @dev Interface for an ERC1820 implementer, as defined in the + * https://eips.ethereum.org/EIPS/eip-1820#interface-implementation-erc1820implementerinterface[EIP]. + * Used by contracts that will be registered as implementers in the + * {IERC1820Registry}. + */ +interface IERC1820Implementer { + /** + * @dev Returns a special value (`ERC1820_ACCEPT_MAGIC`) if this contract + * implements `interfaceHash` for `account`. + * + * See {IERC1820Registry-setInterfaceImplementer}. + */ + function canImplementInterfaceForAddress(bytes32 interfaceHash, address account) external view returns (bytes32); +} diff --git a/contracts/interfaces/IERC1820Registry.sol b/contracts/interfaces/IERC1820Registry.sol index 1b1ba9fcf..a146bc2a6 100644 --- a/contracts/interfaces/IERC1820Registry.sol +++ b/contracts/interfaces/IERC1820Registry.sol @@ -1,6 +1,112 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts v4.4.1 (interfaces/IERC1820Registry.sol) +// OpenZeppelin Contracts (last updated v4.8.0) (utils/introspection/IERC1820Registry.sol) pragma solidity ^0.8.0; -import "../utils/introspection/IERC1820Registry.sol"; +/** + * @dev Interface of the global ERC1820 Registry, as defined in the + * https://eips.ethereum.org/EIPS/eip-1820[EIP]. Accounts may register + * implementers for interfaces in this registry, as well as query support. + * + * Implementers may be shared by multiple accounts, and can also implement more + * than a single interface for each account. Contracts can implement interfaces + * for themselves, but externally-owned accounts (EOA) must delegate this to a + * contract. + * + * {IERC165} interfaces can also be queried via the registry. + * + * For an in-depth explanation and source code analysis, see the EIP text. + */ +interface IERC1820Registry { + event InterfaceImplementerSet(address indexed account, bytes32 indexed interfaceHash, address indexed implementer); + + event ManagerChanged(address indexed account, address indexed newManager); + + /** + * @dev Sets `newManager` as the manager for `account`. A manager of an + * account is able to set interface implementers for it. + * + * By default, each account is its own manager. Passing a value of `0x0` in + * `newManager` will reset the manager to this initial state. + * + * Emits a {ManagerChanged} event. + * + * Requirements: + * + * - the caller must be the current manager for `account`. + */ + function setManager(address account, address newManager) external; + + /** + * @dev Returns the manager for `account`. + * + * See {setManager}. + */ + function getManager(address account) external view returns (address); + + /** + * @dev Sets the `implementer` contract as ``account``'s implementer for + * `interfaceHash`. + * + * `account` being the zero address is an alias for the caller's address. + * The zero address can also be used in `implementer` to remove an old one. + * + * See {interfaceHash} to learn how these are created. + * + * Emits an {InterfaceImplementerSet} event. + * + * Requirements: + * + * - the caller must be the current manager for `account`. + * - `interfaceHash` must not be an {IERC165} interface id (i.e. it must not + * end in 28 zeroes). + * - `implementer` must implement {IERC1820Implementer} and return true when + * queried for support, unless `implementer` is the caller. See + * {IERC1820Implementer-canImplementInterfaceForAddress}. + */ + function setInterfaceImplementer(address account, bytes32 _interfaceHash, address implementer) external; + + /** + * @dev Returns the implementer of `interfaceHash` for `account`. If no such + * implementer is registered, returns the zero address. + * + * If `interfaceHash` is an {IERC165} interface id (i.e. it ends with 28 + * zeroes), `account` will be queried for support of it. + * + * `account` being the zero address is an alias for the caller's address. + */ + function getInterfaceImplementer(address account, bytes32 _interfaceHash) external view returns (address); + + /** + * @dev Returns the interface hash for an `interfaceName`, as defined in the + * corresponding + * https://eips.ethereum.org/EIPS/eip-1820#interface-name[section of the EIP]. + */ + function interfaceHash(string calldata interfaceName) external pure returns (bytes32); + + /** + * @notice Updates the cache with whether the contract implements an ERC165 interface or not. + * @param account Address of the contract for which to update the cache. + * @param interfaceId ERC165 interface for which to update the cache. + */ + function updateERC165Cache(address account, bytes4 interfaceId) external; + + /** + * @notice Checks whether a contract implements an ERC165 interface or not. + * If the result is not cached a direct lookup on the contract address is performed. + * If the result is not cached or the cached value is out-of-date, the cache MUST be updated manually by calling + * {updateERC165Cache} with the contract address. + * @param account Address of the contract to check. + * @param interfaceId ERC165 interface to check. + * @return True if `account` implements `interfaceId`, false otherwise. + */ + function implementsERC165Interface(address account, bytes4 interfaceId) external view returns (bool); + + /** + * @notice Checks whether a contract implements an ERC165 interface or not without using or updating the cache. + * @param account Address of the contract to check. + * @param interfaceId ERC165 interface to check. + * @return True if `account` implements `interfaceId`, false otherwise. + */ + function implementsERC165InterfaceNoCache(address account, bytes4 interfaceId) external view returns (bool); +} diff --git a/contracts/interfaces/IERC777.sol b/contracts/interfaces/IERC777.sol index b97ba7b80..4d36da52c 100644 --- a/contracts/interfaces/IERC777.sol +++ b/contracts/interfaces/IERC777.sol @@ -1,6 +1,199 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts v4.4.1 (interfaces/IERC777.sol) pragma solidity ^0.8.0; -import "../token/ERC777/IERC777.sol"; +/** + * @dev Interface of the ERC777Token standard as defined in the EIP. + * + * This contract uses the + * https://eips.ethereum.org/EIPS/eip-1820[ERC1820 registry standard] to let + * token holders and recipients react to token movements by using setting implementers + * for the associated interfaces in said registry. See {IERC1820Registry} and + * {IERC1820Implementer}. + */ +interface IERC777 { + /** + * @dev Emitted when `amount` tokens are created by `operator` and assigned to `to`. + * + * Note that some additional user `data` and `operatorData` can be logged in the event. + */ + event Minted(address indexed operator, address indexed to, uint256 amount, bytes data, bytes operatorData); + + /** + * @dev Emitted when `operator` destroys `amount` tokens from `account`. + * + * Note that some additional user `data` and `operatorData` can be logged in the event. + */ + event Burned(address indexed operator, address indexed from, uint256 amount, bytes data, bytes operatorData); + + /** + * @dev Emitted when `operator` is made operator for `tokenHolder`. + */ + event AuthorizedOperator(address indexed operator, address indexed tokenHolder); + + /** + * @dev Emitted when `operator` is revoked its operator status for `tokenHolder`. + */ + event RevokedOperator(address indexed operator, address indexed tokenHolder); + + /** + * @dev Returns the name of the token. + */ + function name() external view returns (string memory); + + /** + * @dev Returns the symbol of the token, usually a shorter version of the + * name. + */ + function symbol() external view returns (string memory); + + /** + * @dev Returns the smallest part of the token that is not divisible. This + * means all token operations (creation, movement and destruction) must have + * amounts that are a multiple of this number. + * + * For most token contracts, this value will equal 1. + */ + function granularity() external view returns (uint256); + + /** + * @dev Returns the amount of tokens in existence. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns the amount of tokens owned by an account (`owner`). + */ + function balanceOf(address owner) external view returns (uint256); + + /** + * @dev Moves `amount` tokens from the caller's account to `recipient`. + * + * If send or receive hooks are registered for the caller and `recipient`, + * the corresponding functions will be called with `data` and empty + * `operatorData`. See {IERC777Sender} and {IERC777Recipient}. + * + * Emits a {Sent} event. + * + * Requirements + * + * - the caller must have at least `amount` tokens. + * - `recipient` cannot be the zero address. + * - if `recipient` is a contract, it must implement the {IERC777Recipient} + * interface. + */ + function send(address recipient, uint256 amount, bytes calldata data) external; + + /** + * @dev Destroys `amount` tokens from the caller's account, reducing the + * total supply. + * + * If a send hook is registered for the caller, the corresponding function + * will be called with `data` and empty `operatorData`. See {IERC777Sender}. + * + * Emits a {Burned} event. + * + * Requirements + * + * - the caller must have at least `amount` tokens. + */ + function burn(uint256 amount, bytes calldata data) external; + + /** + * @dev Returns true if an account is an operator of `tokenHolder`. + * Operators can send and burn tokens on behalf of their owners. All + * accounts are their own operator. + * + * See {operatorSend} and {operatorBurn}. + */ + function isOperatorFor(address operator, address tokenHolder) external view returns (bool); + + /** + * @dev Make an account an operator of the caller. + * + * See {isOperatorFor}. + * + * Emits an {AuthorizedOperator} event. + * + * Requirements + * + * - `operator` cannot be calling address. + */ + function authorizeOperator(address operator) external; + + /** + * @dev Revoke an account's operator status for the caller. + * + * See {isOperatorFor} and {defaultOperators}. + * + * Emits a {RevokedOperator} event. + * + * Requirements + * + * - `operator` cannot be calling address. + */ + function revokeOperator(address operator) external; + + /** + * @dev Returns the list of default operators. These accounts are operators + * for all token holders, even if {authorizeOperator} was never called on + * them. + * + * This list is immutable, but individual holders may revoke these via + * {revokeOperator}, in which case {isOperatorFor} will return false. + */ + function defaultOperators() external view returns (address[] memory); + + /** + * @dev Moves `amount` tokens from `sender` to `recipient`. The caller must + * be an operator of `sender`. + * + * If send or receive hooks are registered for `sender` and `recipient`, + * the corresponding functions will be called with `data` and + * `operatorData`. See {IERC777Sender} and {IERC777Recipient}. + * + * Emits a {Sent} event. + * + * Requirements + * + * - `sender` cannot be the zero address. + * - `sender` must have at least `amount` tokens. + * - the caller must be an operator for `sender`. + * - `recipient` cannot be the zero address. + * - if `recipient` is a contract, it must implement the {IERC777Recipient} + * interface. + */ + function operatorSend( + address sender, + address recipient, + uint256 amount, + bytes calldata data, + bytes calldata operatorData + ) external; + + /** + * @dev Destroys `amount` tokens from `account`, reducing the total supply. + * The caller must be an operator of `account`. + * + * If a send hook is registered for `account`, the corresponding function + * will be called with `data` and `operatorData`. See {IERC777Sender}. + * + * Emits a {Burned} event. + * + * Requirements + * + * - `account` cannot be the zero address. + * - `account` must have at least `amount` tokens. + * - the caller must be an operator for `account`. + */ + function operatorBurn(address account, uint256 amount, bytes calldata data, bytes calldata operatorData) external; + + event Sent( + address indexed operator, + address indexed from, + address indexed to, + uint256 amount, + bytes data, + bytes operatorData + ); +} diff --git a/contracts/interfaces/IERC777Recipient.sol b/contracts/interfaces/IERC777Recipient.sol index 0ce2704a8..069904855 100644 --- a/contracts/interfaces/IERC777Recipient.sol +++ b/contracts/interfaces/IERC777Recipient.sol @@ -1,6 +1,34 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts v4.4.1 (interfaces/IERC777Recipient.sol) pragma solidity ^0.8.0; -import "../token/ERC777/IERC777Recipient.sol"; +/** + * @dev Interface of the ERC777TokensRecipient standard as defined in the EIP. + * + * Accounts can be notified of {IERC777} tokens being sent to them by having a + * contract implement this interface (contract holders can be their own + * implementer) and registering it on the + * https://eips.ethereum.org/EIPS/eip-1820[ERC1820 global registry]. + * + * See {IERC1820Registry} and {IERC1820Implementer}. + */ +interface IERC777Recipient { + /** + * @dev Called by an {IERC777} token contract whenever tokens are being + * moved or created into a registered account (`to`). The type of operation + * is conveyed by `from` being the zero address or not. + * + * This call occurs _after_ the token contract's state is updated, so + * {IERC777-balanceOf}, etc., can be used to query the post-operation state. + * + * This function may revert to prevent the operation from being executed. + */ + function tokensReceived( + address operator, + address from, + address to, + uint256 amount, + bytes calldata userData, + bytes calldata operatorData + ) external; +} diff --git a/contracts/interfaces/IERC777Sender.sol b/contracts/interfaces/IERC777Sender.sol index f1f17a22e..c45477fcc 100644 --- a/contracts/interfaces/IERC777Sender.sol +++ b/contracts/interfaces/IERC777Sender.sol @@ -1,6 +1,34 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts v4.4.1 (interfaces/IERC777Sender.sol) pragma solidity ^0.8.0; -import "../token/ERC777/IERC777Sender.sol"; +/** + * @dev Interface of the ERC777TokensSender standard as defined in the EIP. + * + * {IERC777} Token holders can be notified of operations performed on their + * tokens by having a contract implement this interface (contract holders can be + * their own implementer) and registering it on the + * https://eips.ethereum.org/EIPS/eip-1820[ERC1820 global registry]. + * + * See {IERC1820Registry} and {IERC1820Implementer}. + */ +interface IERC777Sender { + /** + * @dev Called by an {IERC777} token contract whenever a registered holder's + * (`from`) tokens are about to be moved or destroyed. The type of operation + * is conveyed by `to` being the zero address or not. + * + * This call occurs _before_ the token contract's state is updated, so + * {IERC777-balanceOf}, etc., can be used to query the pre-operation state. + * + * This function may revert to prevent the operation from being executed. + */ + function tokensToSend( + address operator, + address from, + address to, + uint256 amount, + bytes calldata userData, + bytes calldata operatorData + ) external; +} diff --git a/contracts/interfaces/draft-IERC2612.sol b/contracts/interfaces/draft-IERC2612.sol deleted file mode 100644 index 1ea7bf1c0..000000000 --- a/contracts/interfaces/draft-IERC2612.sol +++ /dev/null @@ -1,7 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -// EIP-2612 is Final as of 2022-11-01. This file is deprecated. - -import "./IERC2612.sol"; diff --git a/contracts/mocks/AccessControlCrossChainMock.sol b/contracts/mocks/AccessControlCrossChainMock.sol deleted file mode 100644 index cd4c3a5d9..000000000 --- a/contracts/mocks/AccessControlCrossChainMock.sol +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.4; - -import "../access/AccessControlCrossChain.sol"; -import "../crosschain/arbitrum/CrossChainEnabledArbitrumL2.sol"; - -contract AccessControlCrossChainMock is AccessControlCrossChain, CrossChainEnabledArbitrumL2 {} diff --git a/contracts/mocks/ConditionalEscrowMock.sol b/contracts/mocks/ConditionalEscrowMock.sol deleted file mode 100644 index ececf0521..000000000 --- a/contracts/mocks/ConditionalEscrowMock.sol +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -import "../utils/escrow/ConditionalEscrow.sol"; - -// mock class using ConditionalEscrow -contract ConditionalEscrowMock is ConditionalEscrow { - mapping(address => bool) private _allowed; - - function setAllowed(address payee, bool allowed) public { - _allowed[payee] = allowed; - } - - function withdrawalAllowed(address payee) public view override returns (bool) { - return _allowed[payee]; - } -} diff --git a/contracts/mocks/PullPaymentMock.sol b/contracts/mocks/PullPaymentMock.sol deleted file mode 100644 index 8a708e30c..000000000 --- a/contracts/mocks/PullPaymentMock.sol +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -import "../security/PullPayment.sol"; - -// mock class using PullPayment -contract PullPaymentMock is PullPayment { - constructor() payable {} - - // test helper function to call asyncTransfer - function callTransfer(address dest, uint256 amount) public { - _asyncTransfer(dest, amount); - } -} diff --git a/contracts/mocks/SafeMathMemoryCheck.sol b/contracts/mocks/SafeMathMemoryCheck.sol deleted file mode 100644 index 96946881a..000000000 --- a/contracts/mocks/SafeMathMemoryCheck.sol +++ /dev/null @@ -1,72 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -import "../utils/math/SafeMath.sol"; - -library SafeMathMemoryCheck { - function addMemoryCheck() internal pure returns (uint256 mem) { - uint256 length = 32; - assembly { - mem := mload(0x40) - } - for (uint256 i = 0; i < length; ++i) { - SafeMath.add(1, 1); - } - assembly { - mem := sub(mload(0x40), mem) - } - } - - function subMemoryCheck() internal pure returns (uint256 mem) { - uint256 length = 32; - assembly { - mem := mload(0x40) - } - for (uint256 i = 0; i < length; ++i) { - SafeMath.sub(1, 1); - } - assembly { - mem := sub(mload(0x40), mem) - } - } - - function mulMemoryCheck() internal pure returns (uint256 mem) { - uint256 length = 32; - assembly { - mem := mload(0x40) - } - for (uint256 i = 0; i < length; ++i) { - SafeMath.mul(1, 1); - } - assembly { - mem := sub(mload(0x40), mem) - } - } - - function divMemoryCheck() internal pure returns (uint256 mem) { - uint256 length = 32; - assembly { - mem := mload(0x40) - } - for (uint256 i = 0; i < length; ++i) { - SafeMath.div(1, 1); - } - assembly { - mem := sub(mload(0x40), mem) - } - } - - function modMemoryCheck() internal pure returns (uint256 mem) { - uint256 length = 32; - assembly { - mem := mload(0x40) - } - for (uint256 i = 0; i < length; ++i) { - SafeMath.mod(1, 1); - } - assembly { - mem := sub(mload(0x40), mem) - } - } -} diff --git a/contracts/mocks/TimersBlockNumberImpl.sol b/contracts/mocks/TimersBlockNumberImpl.sol deleted file mode 100644 index 84633e6f8..000000000 --- a/contracts/mocks/TimersBlockNumberImpl.sol +++ /dev/null @@ -1,39 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -import "../utils/Timers.sol"; - -contract TimersBlockNumberImpl { - using Timers for Timers.BlockNumber; - - Timers.BlockNumber private _timer; - - function getDeadline() public view returns (uint64) { - return _timer.getDeadline(); - } - - function setDeadline(uint64 timestamp) public { - _timer.setDeadline(timestamp); - } - - function reset() public { - _timer.reset(); - } - - function isUnset() public view returns (bool) { - return _timer.isUnset(); - } - - function isStarted() public view returns (bool) { - return _timer.isStarted(); - } - - function isPending() public view returns (bool) { - return _timer.isPending(); - } - - function isExpired() public view returns (bool) { - return _timer.isExpired(); - } -} diff --git a/contracts/mocks/TimersTimestampImpl.sol b/contracts/mocks/TimersTimestampImpl.sol deleted file mode 100644 index 07f9a1b3f..000000000 --- a/contracts/mocks/TimersTimestampImpl.sol +++ /dev/null @@ -1,39 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -import "../utils/Timers.sol"; - -contract TimersTimestampImpl { - using Timers for Timers.Timestamp; - - Timers.Timestamp private _timer; - - function getDeadline() public view returns (uint64) { - return _timer.getDeadline(); - } - - function setDeadline(uint64 timestamp) public { - _timer.setDeadline(timestamp); - } - - function reset() public { - _timer.reset(); - } - - function isUnset() public view returns (bool) { - return _timer.isUnset(); - } - - function isStarted() public view returns (bool) { - return _timer.isStarted(); - } - - function isPending() public view returns (bool) { - return _timer.isPending(); - } - - function isExpired() public view returns (bool) { - return _timer.isExpired(); - } -} diff --git a/contracts/mocks/crosschain/bridges.sol b/contracts/mocks/crosschain/bridges.sol deleted file mode 100644 index 41baffed8..000000000 --- a/contracts/mocks/crosschain/bridges.sol +++ /dev/null @@ -1,94 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -import "../../utils/Address.sol"; -import "../../vendor/polygon/IFxMessageProcessor.sol"; - -abstract contract BaseRelayMock { - // needed to parse custom errors - error NotCrossChainCall(); - error InvalidCrossChainSender(address sender, address expected); - - address internal _currentSender; - - function relayAs(address target, bytes calldata data, address sender) external virtual { - address previousSender = _currentSender; - - _currentSender = sender; - - (bool success, bytes memory returndata) = target.call(data); - Address.verifyCallResultFromTarget(target, success, returndata, "low-level call reverted"); - - _currentSender = previousSender; - } -} - -/** - * AMB - */ -contract BridgeAMBMock is BaseRelayMock { - function messageSender() public view returns (address) { - return _currentSender; - } -} - -/** - * Arbitrum - */ -contract BridgeArbitrumL1Mock is BaseRelayMock { - /// @custom:oz-upgrades-unsafe-allow state-variable-immutable state-variable-assignment - address public immutable inbox = address(new BridgeArbitrumL1Inbox()); - /// @custom:oz-upgrades-unsafe-allow state-variable-immutable state-variable-assignment - address public immutable outbox = address(new BridgeArbitrumL1Outbox()); - - function activeOutbox() public view returns (address) { - return outbox; - } - - function currentSender() public view returns (address) { - return _currentSender; - } -} - -contract BridgeArbitrumL1Inbox { - /// @custom:oz-upgrades-unsafe-allow state-variable-immutable state-variable-assignment - address public immutable bridge = msg.sender; -} - -contract BridgeArbitrumL1Outbox { - /// @custom:oz-upgrades-unsafe-allow state-variable-immutable state-variable-assignment - address public immutable bridge = msg.sender; - - function l2ToL1Sender() public view returns (address) { - return BridgeArbitrumL1Mock(bridge).currentSender(); - } -} - -contract BridgeArbitrumL2Mock is BaseRelayMock { - function wasMyCallersAddressAliased() public view returns (bool) { - return _currentSender != address(0); - } - - function myCallersAddressWithoutAliasing() public view returns (address) { - return _currentSender; - } -} - -/** - * Optimism - */ -contract BridgeOptimismMock is BaseRelayMock { - function xDomainMessageSender() public view returns (address) { - return _currentSender; - } -} - -/** - * Polygon - */ -contract BridgePolygonChildMock is BaseRelayMock { - function relayAs(address target, bytes calldata data, address sender) external override { - IFxMessageProcessor(target).processMessageFromRoot(0, sender, data); - } -} diff --git a/contracts/mocks/crosschain/receivers.sol b/contracts/mocks/crosschain/receivers.sol deleted file mode 100644 index 601a2ac10..000000000 --- a/contracts/mocks/crosschain/receivers.sol +++ /dev/null @@ -1,54 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.4; - -import "../../access/Ownable.sol"; -import "../../crosschain/amb/CrossChainEnabledAMB.sol"; -import "../../crosschain/arbitrum/CrossChainEnabledArbitrumL1.sol"; -import "../../crosschain/arbitrum/CrossChainEnabledArbitrumL2.sol"; -import "../../crosschain/optimism/CrossChainEnabledOptimism.sol"; -import "../../crosschain/polygon/CrossChainEnabledPolygonChild.sol"; - -abstract contract Receiver is CrossChainEnabled { - // we don't use Ownable because it messes up testing for the upgradeable contracts - /// @custom:oz-upgrades-unsafe-allow state-variable-immutable state-variable-assignment - address public immutable owner = msg.sender; - - function crossChainRestricted() external onlyCrossChain {} - - function crossChainOwnerRestricted() external onlyCrossChainSender(owner) {} -} - -/** - * AMB - */ -contract CrossChainEnabledAMBMock is Receiver, CrossChainEnabledAMB { - /// @custom:oz-upgrades-unsafe-allow constructor - constructor(address bridge) CrossChainEnabledAMB(bridge) {} -} - -/** - * Arbitrum - */ -contract CrossChainEnabledArbitrumL1Mock is Receiver, CrossChainEnabledArbitrumL1 { - /// @custom:oz-upgrades-unsafe-allow constructor - constructor(address bridge) CrossChainEnabledArbitrumL1(bridge) {} -} - -contract CrossChainEnabledArbitrumL2Mock is Receiver, CrossChainEnabledArbitrumL2 {} - -/** - * Optimism - */ -contract CrossChainEnabledOptimismMock is Receiver, CrossChainEnabledOptimism { - /// @custom:oz-upgrades-unsafe-allow constructor - constructor(address bridge) CrossChainEnabledOptimism(bridge) {} -} - -/** - * Polygon - */ -contract CrossChainEnabledPolygonChildMock is Receiver, CrossChainEnabledPolygonChild { - /// @custom:oz-upgrades-unsafe-allow constructor - constructor(address bridge) CrossChainEnabledPolygonChild(bridge) {} -} diff --git a/contracts/mocks/governance/GovernorMock.sol b/contracts/mocks/governance/GovernorMock.sol index 8a1db4704..9284a24fe 100644 --- a/contracts/mocks/governance/GovernorMock.sol +++ b/contracts/mocks/governance/GovernorMock.sol @@ -2,27 +2,12 @@ pragma solidity ^0.8.0; -import "../../governance/extensions/GovernorProposalThreshold.sol"; import "../../governance/extensions/GovernorSettings.sol"; import "../../governance/extensions/GovernorCountingSimple.sol"; import "../../governance/extensions/GovernorVotesQuorumFraction.sol"; -abstract contract GovernorMock is - GovernorProposalThreshold, - GovernorSettings, - GovernorVotesQuorumFraction, - GovernorCountingSimple -{ +abstract contract GovernorMock is GovernorSettings, GovernorVotesQuorumFraction, GovernorCountingSimple { function proposalThreshold() public view override(Governor, GovernorSettings) returns (uint256) { return super.proposalThreshold(); } - - function propose( - address[] memory targets, - uint256[] memory values, - bytes[] memory calldatas, - string memory description - ) public override(Governor, GovernorProposalThreshold) returns (uint256) { - return super.propose(targets, values, calldatas, description); - } } diff --git a/contracts/mocks/token/ERC20PermitNoRevertMock.sol b/contracts/mocks/token/ERC20PermitNoRevertMock.sol index 2176c51ae..f2562d3cc 100644 --- a/contracts/mocks/token/ERC20PermitNoRevertMock.sol +++ b/contracts/mocks/token/ERC20PermitNoRevertMock.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.0; import "../../token/ERC20/ERC20.sol"; -import "../../token/ERC20/extensions/draft-ERC20Permit.sol"; +import "../../token/ERC20/extensions/ERC20Permit.sol"; abstract contract ERC20PermitNoRevertMock is ERC20Permit { function permitThatMayRevert( diff --git a/contracts/mocks/token/ERC721ConsecutiveMock.sol b/contracts/mocks/token/ERC721ConsecutiveMock.sol index 427f44a19..8bfa0cb9e 100644 --- a/contracts/mocks/token/ERC721ConsecutiveMock.sol +++ b/contracts/mocks/token/ERC721ConsecutiveMock.sol @@ -5,7 +5,7 @@ pragma solidity ^0.8.0; import "../../token/ERC721/extensions/ERC721Consecutive.sol"; import "../../token/ERC721/extensions/ERC721Enumerable.sol"; import "../../token/ERC721/extensions/ERC721Pausable.sol"; -import "../../token/ERC721/extensions/draft-ERC721Votes.sol"; +import "../../token/ERC721/extensions/ERC721Votes.sol"; /** * @title ERC721ConsecutiveMock diff --git a/contracts/mocks/token/ERC777Mock.sol b/contracts/mocks/token/ERC777Mock.sol deleted file mode 100644 index 685277e8b..000000000 --- a/contracts/mocks/token/ERC777Mock.sol +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -import "../../token/ERC777/ERC777.sol"; - -abstract contract ERC777Mock is ERC777 { - event BeforeTokenTransfer(); - - function _beforeTokenTransfer(address, address, address, uint256) internal override { - emit BeforeTokenTransfer(); - } -} diff --git a/contracts/mocks/token/ERC777SenderRecipientMock.sol b/contracts/mocks/token/ERC777SenderRecipientMock.sol deleted file mode 100644 index 3bec6d94b..000000000 --- a/contracts/mocks/token/ERC777SenderRecipientMock.sol +++ /dev/null @@ -1,152 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -import "../../token/ERC777/IERC777.sol"; -import "../../token/ERC777/IERC777Sender.sol"; -import "../../token/ERC777/IERC777Recipient.sol"; -import "../../utils/Context.sol"; -import "../../utils/introspection/IERC1820Registry.sol"; -import "../../utils/introspection/ERC1820Implementer.sol"; - -contract ERC777SenderRecipientMock is Context, IERC777Sender, IERC777Recipient, ERC1820Implementer { - event TokensToSendCalled( - address operator, - address from, - address to, - uint256 amount, - bytes data, - bytes operatorData, - address token, - uint256 fromBalance, - uint256 toBalance - ); - - event TokensReceivedCalled( - address operator, - address from, - address to, - uint256 amount, - bytes data, - bytes operatorData, - address token, - uint256 fromBalance, - uint256 toBalance - ); - - // Emitted in ERC777Mock. Here for easier decoding - event BeforeTokenTransfer(); - - bool private _shouldRevertSend; - bool private _shouldRevertReceive; - - IERC1820Registry private _erc1820 = IERC1820Registry(0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24); - - bytes32 private constant _TOKENS_SENDER_INTERFACE_HASH = keccak256("ERC777TokensSender"); - bytes32 private constant _TOKENS_RECIPIENT_INTERFACE_HASH = keccak256("ERC777TokensRecipient"); - - function tokensToSend( - address operator, - address from, - address to, - uint256 amount, - bytes calldata userData, - bytes calldata operatorData - ) external override { - if (_shouldRevertSend) { - revert(); - } - - IERC777 token = IERC777(_msgSender()); - - uint256 fromBalance = token.balanceOf(from); - // when called due to burn, to will be the zero address, which will have a balance of 0 - uint256 toBalance = token.balanceOf(to); - - emit TokensToSendCalled( - operator, - from, - to, - amount, - userData, - operatorData, - address(token), - fromBalance, - toBalance - ); - } - - function tokensReceived( - address operator, - address from, - address to, - uint256 amount, - bytes calldata userData, - bytes calldata operatorData - ) external override { - if (_shouldRevertReceive) { - revert(); - } - - IERC777 token = IERC777(_msgSender()); - - uint256 fromBalance = token.balanceOf(from); - // when called due to burn, to will be the zero address, which will have a balance of 0 - uint256 toBalance = token.balanceOf(to); - - emit TokensReceivedCalled( - operator, - from, - to, - amount, - userData, - operatorData, - address(token), - fromBalance, - toBalance - ); - } - - function senderFor(address account) public { - _registerInterfaceForAddress(_TOKENS_SENDER_INTERFACE_HASH, account); - - address self = address(this); - if (account == self) { - registerSender(self); - } - } - - function registerSender(address sender) public { - _erc1820.setInterfaceImplementer(address(this), _TOKENS_SENDER_INTERFACE_HASH, sender); - } - - function recipientFor(address account) public { - _registerInterfaceForAddress(_TOKENS_RECIPIENT_INTERFACE_HASH, account); - - address self = address(this); - if (account == self) { - registerRecipient(self); - } - } - - function registerRecipient(address recipient) public { - _erc1820.setInterfaceImplementer(address(this), _TOKENS_RECIPIENT_INTERFACE_HASH, recipient); - } - - function setShouldRevertSend(bool shouldRevert) public { - _shouldRevertSend = shouldRevert; - } - - function setShouldRevertReceive(bool shouldRevert) public { - _shouldRevertReceive = shouldRevert; - } - - function send(IERC777 token, address to, uint256 amount, bytes memory data) public { - // This is 777's send function, not the Solidity send function - token.send(to, amount, data); // solhint-disable-line check-send-result - } - - function burn(IERC777 token, uint256 amount, bytes memory data) public { - token.burn(amount, data); - } -} diff --git a/contracts/mocks/wizard/MyGovernor1.sol b/contracts/mocks/wizard/MyGovernor1.sol deleted file mode 100644 index 37ecfd57d..000000000 --- a/contracts/mocks/wizard/MyGovernor1.sol +++ /dev/null @@ -1,79 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.2; - -import "../../governance/Governor.sol"; -import "../../governance/extensions/GovernorCountingSimple.sol"; -import "../../governance/extensions/GovernorVotes.sol"; -import "../../governance/extensions/GovernorVotesQuorumFraction.sol"; -import "../../governance/extensions/GovernorTimelockControl.sol"; - -contract MyGovernor1 is - Governor, - GovernorTimelockControl, - GovernorVotes, - GovernorVotesQuorumFraction, - GovernorCountingSimple -{ - constructor( - IVotes _token, - TimelockController _timelock - ) Governor("MyGovernor") GovernorVotes(_token) GovernorVotesQuorumFraction(4) GovernorTimelockControl(_timelock) {} - - function votingDelay() public pure override returns (uint256) { - return 1; // 1 block - } - - function votingPeriod() public pure override returns (uint256) { - return 45818; // 1 week - } - - // The following functions are overrides required by Solidity. - - function quorum( - uint256 blockNumber - ) public view override(IGovernor, GovernorVotesQuorumFraction) returns (uint256) { - return super.quorum(blockNumber); - } - - function state(uint256 proposalId) public view override(Governor, GovernorTimelockControl) returns (ProposalState) { - return super.state(proposalId); - } - - function propose( - address[] memory targets, - uint256[] memory values, - bytes[] memory calldatas, - string memory description - ) public override(Governor, IGovernor) returns (uint256) { - return super.propose(targets, values, calldatas, description); - } - - function _execute( - uint256 proposalId, - address[] memory targets, - uint256[] memory values, - bytes[] memory calldatas, - bytes32 descriptionHash - ) internal override(Governor, GovernorTimelockControl) { - super._execute(proposalId, targets, values, calldatas, descriptionHash); - } - - function _cancel( - address[] memory targets, - uint256[] memory values, - bytes[] memory calldatas, - bytes32 descriptionHash - ) internal override(Governor, GovernorTimelockControl) returns (uint256) { - return super._cancel(targets, values, calldatas, descriptionHash); - } - - function _executor() internal view override(Governor, GovernorTimelockControl) returns (address) { - return super._executor(); - } - - function supportsInterface( - bytes4 interfaceId - ) public view override(Governor, GovernorTimelockControl) returns (bool) { - return super.supportsInterface(interfaceId); - } -} diff --git a/contracts/mocks/wizard/MyGovernor2.sol b/contracts/mocks/wizard/MyGovernor2.sol deleted file mode 100644 index 1472b67d5..000000000 --- a/contracts/mocks/wizard/MyGovernor2.sol +++ /dev/null @@ -1,85 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.2; - -import "../../governance/Governor.sol"; -import "../../governance/extensions/GovernorProposalThreshold.sol"; -import "../../governance/extensions/GovernorCountingSimple.sol"; -import "../../governance/extensions/GovernorVotes.sol"; -import "../../governance/extensions/GovernorVotesQuorumFraction.sol"; -import "../../governance/extensions/GovernorTimelockControl.sol"; - -contract MyGovernor2 is - Governor, - GovernorTimelockControl, - GovernorProposalThreshold, - GovernorVotes, - GovernorVotesQuorumFraction, - GovernorCountingSimple -{ - constructor( - IVotes _token, - TimelockController _timelock - ) Governor("MyGovernor") GovernorVotes(_token) GovernorVotesQuorumFraction(4) GovernorTimelockControl(_timelock) {} - - function votingDelay() public pure override returns (uint256) { - return 1; // 1 block - } - - function votingPeriod() public pure override returns (uint256) { - return 45818; // 1 week - } - - function proposalThreshold() public pure override returns (uint256) { - return 1000e18; - } - - // The following functions are overrides required by Solidity. - - function quorum( - uint256 blockNumber - ) public view override(IGovernor, GovernorVotesQuorumFraction) returns (uint256) { - return super.quorum(blockNumber); - } - - function state(uint256 proposalId) public view override(Governor, GovernorTimelockControl) returns (ProposalState) { - return super.state(proposalId); - } - - function propose( - address[] memory targets, - uint256[] memory values, - bytes[] memory calldatas, - string memory description - ) public override(Governor, GovernorProposalThreshold, IGovernor) returns (uint256) { - return super.propose(targets, values, calldatas, description); - } - - function _execute( - uint256 proposalId, - address[] memory targets, - uint256[] memory values, - bytes[] memory calldatas, - bytes32 descriptionHash - ) internal override(Governor, GovernorTimelockControl) { - super._execute(proposalId, targets, values, calldatas, descriptionHash); - } - - function _cancel( - address[] memory targets, - uint256[] memory values, - bytes[] memory calldatas, - bytes32 descriptionHash - ) internal override(Governor, GovernorTimelockControl) returns (uint256) { - return super._cancel(targets, values, calldatas, descriptionHash); - } - - function _executor() internal view override(Governor, GovernorTimelockControl) returns (address) { - return super._executor(); - } - - function supportsInterface( - bytes4 interfaceId - ) public view override(Governor, GovernorTimelockControl) returns (bool) { - return super.supportsInterface(interfaceId); - } -} diff --git a/contracts/mocks/wizard/MyGovernor3.sol b/contracts/mocks/wizard/MyGovernor3.sol deleted file mode 100644 index f4d295156..000000000 --- a/contracts/mocks/wizard/MyGovernor3.sol +++ /dev/null @@ -1,94 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.2; - -import "../../governance/Governor.sol"; -import "../../governance/compatibility/GovernorCompatibilityBravo.sol"; -import "../../governance/extensions/GovernorVotes.sol"; -import "../../governance/extensions/GovernorVotesQuorumFraction.sol"; -import "../../governance/extensions/GovernorTimelockControl.sol"; - -contract MyGovernor is - Governor, - GovernorTimelockControl, - GovernorCompatibilityBravo, - GovernorVotes, - GovernorVotesQuorumFraction -{ - constructor( - IVotes _token, - TimelockController _timelock - ) Governor("MyGovernor") GovernorVotes(_token) GovernorVotesQuorumFraction(4) GovernorTimelockControl(_timelock) {} - - function votingDelay() public pure override returns (uint256) { - return 1; // 1 block - } - - function votingPeriod() public pure override returns (uint256) { - return 45818; // 1 week - } - - function proposalThreshold() public pure override returns (uint256) { - return 1000e18; - } - - // The following functions are overrides required by Solidity. - - function quorum( - uint256 blockNumber - ) public view override(IGovernor, GovernorVotesQuorumFraction) returns (uint256) { - return super.quorum(blockNumber); - } - - function state( - uint256 proposalId - ) public view override(Governor, IGovernor, GovernorTimelockControl) returns (ProposalState) { - return super.state(proposalId); - } - - function propose( - address[] memory targets, - uint256[] memory values, - bytes[] memory calldatas, - string memory description - ) public override(Governor, GovernorCompatibilityBravo, IGovernor) returns (uint256) { - return super.propose(targets, values, calldatas, description); - } - - function cancel( - address[] memory targets, - uint256[] memory values, - bytes[] memory calldatas, - bytes32 descriptionHash - ) public override(Governor, GovernorCompatibilityBravo, IGovernor) returns (uint256) { - return super.cancel(targets, values, calldatas, descriptionHash); - } - - function _execute( - uint256 proposalId, - address[] memory targets, - uint256[] memory values, - bytes[] memory calldatas, - bytes32 descriptionHash - ) internal override(Governor, GovernorTimelockControl) { - super._execute(proposalId, targets, values, calldatas, descriptionHash); - } - - function _cancel( - address[] memory targets, - uint256[] memory values, - bytes[] memory calldatas, - bytes32 descriptionHash - ) internal override(Governor, GovernorTimelockControl) returns (uint256) { - return super._cancel(targets, values, calldatas, descriptionHash); - } - - function _executor() internal view override(Governor, GovernorTimelockControl) returns (address) { - return super._executor(); - } - - function supportsInterface( - bytes4 interfaceId - ) public view override(Governor, IERC165, GovernorTimelockControl) returns (bool) { - return super.supportsInterface(interfaceId); - } -} diff --git a/contracts/proxy/beacon/BeaconProxy.sol b/contracts/proxy/beacon/BeaconProxy.sol index d217b15ea..a278884f4 100644 --- a/contracts/proxy/beacon/BeaconProxy.sol +++ b/contracts/proxy/beacon/BeaconProxy.sol @@ -31,31 +31,10 @@ contract BeaconProxy is Proxy, ERC1967Upgrade { _upgradeBeaconToAndCall(beacon, data, false); } - /** - * @dev Returns the current beacon address. - */ - function _beacon() internal view virtual returns (address) { - return _getBeacon(); - } - /** * @dev Returns the current implementation address of the associated beacon. */ function _implementation() internal view virtual override returns (address) { return IBeacon(_getBeacon()).implementation(); } - - /** - * @dev Changes the proxy to use a new beacon. Deprecated: see {_upgradeBeaconToAndCall}. - * - * If `data` is nonempty, it's used as data in a delegate call to the implementation returned by the beacon. - * - * Requirements: - * - * - `beacon` must be a contract. - * - The implementation returned by `beacon` must be a contract. - */ - function _setBeacon(address beacon, bytes memory data) internal virtual { - _upgradeBeaconToAndCall(beacon, data, false); - } } diff --git a/contracts/proxy/transparent/TransparentUpgradeableProxy.sol b/contracts/proxy/transparent/TransparentUpgradeableProxy.sol index fbef40619..7bdef7c28 100644 --- a/contracts/proxy/transparent/TransparentUpgradeableProxy.sol +++ b/contracts/proxy/transparent/TransparentUpgradeableProxy.sol @@ -60,20 +60,6 @@ contract TransparentUpgradeableProxy is ERC1967Proxy { _changeAdmin(admin_); } - /** - * @dev Modifier used internally that will delegate the call to the implementation unless the sender is the admin. - * - * CAUTION: This modifier is deprecated, as it could cause issues if the modified function has arguments, and the - * implementation provides a function with the same selector. - */ - modifier ifAdmin() { - if (msg.sender == _getAdmin()) { - _; - } else { - _fallback(); - } - } - /** * @dev If caller is the admin process the call internally, otherwise transparently fallback to the proxy behavior */ @@ -137,17 +123,8 @@ contract TransparentUpgradeableProxy is ERC1967Proxy { } /** - * @dev Returns the current admin. - * - * CAUTION: This function is deprecated. Use {ERC1967Upgrade-_getAdmin} instead. - */ - function _admin() internal view virtual returns (address) { - return _getAdmin(); - } - - /** - * @dev To keep this contract fully transparent, all `ifAdmin` functions must be payable. This helper is here to - * emulate some proxy functions being non-payable while still allowing value to pass through. + * @dev To keep this contract fully transparent, the fallback is payable. This helper is here to enforce + * non-payability of function implemented through dispatchers while still allowing value to pass through. */ function _requireZeroValue() private { require(msg.value == 0); diff --git a/contracts/security/PullPayment.sol b/contracts/security/PullPayment.sol deleted file mode 100644 index 65b4980f6..000000000 --- a/contracts/security/PullPayment.sol +++ /dev/null @@ -1,74 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.8.0) (security/PullPayment.sol) - -pragma solidity ^0.8.0; - -import "../utils/escrow/Escrow.sol"; - -/** - * @dev Simple implementation of a - * https://consensys.github.io/smart-contract-best-practices/development-recommendations/general/external-calls/#favor-pull-over-push-for-external-calls[pull-payment] - * strategy, where the paying contract doesn't interact directly with the - * receiver account, which must withdraw its payments itself. - * - * Pull-payments are often considered the best practice when it comes to sending - * Ether, security-wise. It prevents recipients from blocking execution, and - * eliminates reentrancy concerns. - * - * TIP: If you would like to learn more about reentrancy and alternative ways - * to protect against it, check out our blog post - * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul]. - * - * To use, derive from the `PullPayment` contract, and use {_asyncTransfer} - * instead of Solidity's `transfer` function. Payees can query their due - * payments with {payments}, and retrieve them with {withdrawPayments}. - */ -abstract contract PullPayment { - Escrow private immutable _escrow; - - constructor() { - _escrow = new Escrow(); - } - - /** - * @dev Withdraw accumulated payments, forwarding all gas to the recipient. - * - * Note that _any_ account can call this function, not just the `payee`. - * This means that contracts unaware of the `PullPayment` protocol can still - * receive funds this way, by having a separate account call - * {withdrawPayments}. - * - * WARNING: Forwarding all gas opens the door to reentrancy vulnerabilities. - * Make sure you trust the recipient, or are either following the - * checks-effects-interactions pattern or using {ReentrancyGuard}. - * - * @param payee Whose payments will be withdrawn. - * - * Causes the `escrow` to emit a {Withdrawn} event. - */ - function withdrawPayments(address payable payee) public virtual { - _escrow.withdraw(payee); - } - - /** - * @dev Returns the payments owed to an address. - * @param dest The creditor's address. - */ - function payments(address dest) public view returns (uint256) { - return _escrow.depositsOf(dest); - } - - /** - * @dev Called by the payer to store the sent amount as credit to be pulled. - * Funds sent in this way are stored in an intermediate {Escrow} contract, so - * there is no danger of them being spent before withdrawal. - * - * @param dest The destination address of the funds. - * @param amount The amount to transfer. - * - * Causes the `escrow` to emit a {Deposited} event. - */ - function _asyncTransfer(address dest, uint256 amount) internal virtual { - _escrow.deposit{value: amount}(dest); - } -} diff --git a/contracts/security/README.adoc b/contracts/security/README.adoc index 66f398fec..7f4799eb8 100644 --- a/contracts/security/README.adoc +++ b/contracts/security/README.adoc @@ -5,7 +5,6 @@ NOTE: This document is better viewed at https://docs.openzeppelin.com/contracts/ These contracts aim to cover common security practices. -* {PullPayment}: A pattern that can be used to avoid reentrancy attacks. * {ReentrancyGuard}: A modifier that can prevent reentrancy during certain functions. * {Pausable}: A common emergency response mechanism that can pause functionality while a remediation is pending. @@ -13,8 +12,6 @@ TIP: For an overview on reentrancy and the possible mechanisms to prevent it, re == Contracts -{{PullPayment}} - {{ReentrancyGuard}} {{Pausable}} diff --git a/contracts/token/ERC20/extensions/ERC20Permit.sol b/contracts/token/ERC20/extensions/ERC20Permit.sol index 70b48a8d4..8f476b662 100644 --- a/contracts/token/ERC20/extensions/ERC20Permit.sol +++ b/contracts/token/ERC20/extensions/ERC20Permit.sol @@ -23,14 +23,6 @@ abstract contract ERC20Permit is ERC20, IERC20Permit, EIP712, Nonces { // solhint-disable-next-line var-name-mixedcase bytes32 private constant _PERMIT_TYPEHASH = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"); - /** - * @dev In previous versions `_PERMIT_TYPEHASH` was declared as `immutable`. - * However, to ensure consistency with the upgradeable transpiler, we will continue - * to reserve a slot. - * @custom:oz-renamed-from _PERMIT_TYPEHASH - */ - // solhint-disable-next-line var-name-mixedcase - bytes32 private _PERMIT_TYPEHASH_DEPRECATED_SLOT; /** * @dev Initializes the {EIP712} domain separator using the `name` parameter, and setting `version` to `"1"`. diff --git a/contracts/token/ERC20/extensions/draft-ERC20Permit.sol b/contracts/token/ERC20/extensions/draft-ERC20Permit.sol deleted file mode 100644 index 6579ef33f..000000000 --- a/contracts/token/ERC20/extensions/draft-ERC20Permit.sol +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC20/extensions/draft-ERC20Permit.sol) - -pragma solidity ^0.8.0; - -// EIP-2612 is Final as of 2022-11-01. This file is deprecated. - -import "./ERC20Permit.sol"; diff --git a/contracts/token/ERC20/extensions/draft-IERC20Permit.sol b/contracts/token/ERC20/extensions/draft-IERC20Permit.sol deleted file mode 100644 index 1df6c537d..000000000 --- a/contracts/token/ERC20/extensions/draft-IERC20Permit.sol +++ /dev/null @@ -1,7 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -// EIP-2612 is Final as of 2022-11-01. This file is deprecated. - -import "./IERC20Permit.sol"; diff --git a/contracts/token/ERC20/utils/SafeERC20.sol b/contracts/token/ERC20/utils/SafeERC20.sol index 805a1565f..5eb25932e 100644 --- a/contracts/token/ERC20/utils/SafeERC20.sol +++ b/contracts/token/ERC20/utils/SafeERC20.sol @@ -35,24 +35,6 @@ library SafeERC20 { _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value)); } - /** - * @dev Deprecated. This function has issues similar to the ones found in - * {IERC20-approve}, and its usage is discouraged. - * - * Whenever possible, use {safeIncreaseAllowance} and - * {safeDecreaseAllowance} instead. - */ - function safeApprove(IERC20 token, address spender, uint256 value) internal { - // safeApprove should only be called when setting an initial allowance, - // or when resetting it to zero. To increase and decrease it, use - // 'safeIncreaseAllowance' and 'safeDecreaseAllowance' - require( - (value == 0) || (token.allowance(address(this), spender) == 0), - "SafeERC20: approve from non-zero to non-zero allowance" - ); - _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value)); - } - /** * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value, * non-reverting calls are assumed to be successful. diff --git a/contracts/token/ERC721/extensions/draft-ERC721Votes.sol b/contracts/token/ERC721/extensions/draft-ERC721Votes.sol deleted file mode 100644 index c6aa7c564..000000000 --- a/contracts/token/ERC721/extensions/draft-ERC721Votes.sol +++ /dev/null @@ -1,9 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC721/extensions/draft-ERC721Votes.sol) - -pragma solidity ^0.8.0; - -// ERC721Votes was marked as draft due to the EIP-712 dependency. -// EIP-712 is Final as of 2022-08-11. This file is deprecated. - -import "./ERC721Votes.sol"; diff --git a/contracts/token/ERC777/ERC777.sol b/contracts/token/ERC777/ERC777.sol deleted file mode 100644 index 063337d7e..000000000 --- a/contracts/token/ERC777/ERC777.sol +++ /dev/null @@ -1,517 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC777/ERC777.sol) - -pragma solidity ^0.8.1; - -import "./IERC777.sol"; -import "./IERC777Recipient.sol"; -import "./IERC777Sender.sol"; -import "../ERC20/IERC20.sol"; -import "../../utils/Address.sol"; -import "../../utils/Context.sol"; -import "../../utils/introspection/IERC1820Registry.sol"; - -/** - * @dev Implementation of the {IERC777} interface. - * - * This implementation is agnostic to the way tokens are created. This means - * that a supply mechanism has to be added in a derived contract using {_mint}. - * - * Support for ERC20 is included in this contract, as specified by the EIP: both - * the ERC777 and ERC20 interfaces can be safely used when interacting with it. - * Both {IERC777-Sent} and {IERC20-Transfer} events are emitted on token - * movements. - * - * Additionally, the {IERC777-granularity} value is hard-coded to `1`, meaning that there - * are no special restrictions in the amount of tokens that created, moved, or - * destroyed. This makes integration with ERC20 applications seamless. - * - * CAUTION: This file is deprecated as of v4.9 and will be removed in the next major release. - */ -contract ERC777 is Context, IERC777, IERC20 { - using Address for address; - - IERC1820Registry internal constant _ERC1820_REGISTRY = IERC1820Registry(0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24); - - mapping(address => uint256) private _balances; - - uint256 private _totalSupply; - - string private _name; - string private _symbol; - - bytes32 private constant _TOKENS_SENDER_INTERFACE_HASH = keccak256("ERC777TokensSender"); - bytes32 private constant _TOKENS_RECIPIENT_INTERFACE_HASH = keccak256("ERC777TokensRecipient"); - - // This isn't ever read from - it's only used to respond to the defaultOperators query. - address[] private _defaultOperatorsArray; - - // Immutable, but accounts may revoke them (tracked in __revokedDefaultOperators). - mapping(address => bool) private _defaultOperators; - - // For each account, a mapping of its operators and revoked default operators. - mapping(address => mapping(address => bool)) private _operators; - mapping(address => mapping(address => bool)) private _revokedDefaultOperators; - - // ERC20-allowances - mapping(address => mapping(address => uint256)) private _allowances; - - /** - * @dev `defaultOperators` may be an empty array. - */ - constructor(string memory name_, string memory symbol_, address[] memory defaultOperators_) { - _name = name_; - _symbol = symbol_; - - _defaultOperatorsArray = defaultOperators_; - for (uint256 i = 0; i < defaultOperators_.length; i++) { - _defaultOperators[defaultOperators_[i]] = true; - } - - // register interfaces - _ERC1820_REGISTRY.setInterfaceImplementer(address(this), keccak256("ERC777Token"), address(this)); - _ERC1820_REGISTRY.setInterfaceImplementer(address(this), keccak256("ERC20Token"), address(this)); - } - - /** - * @dev See {IERC777-name}. - */ - function name() public view virtual override returns (string memory) { - return _name; - } - - /** - * @dev See {IERC777-symbol}. - */ - function symbol() public view virtual override returns (string memory) { - return _symbol; - } - - /** - * @dev See {ERC20-decimals}. - * - * Always returns 18, as per the - * [ERC777 EIP](https://eips.ethereum.org/EIPS/eip-777#backward-compatibility). - */ - function decimals() public pure virtual returns (uint8) { - return 18; - } - - /** - * @dev See {IERC777-granularity}. - * - * This implementation always returns `1`. - */ - function granularity() public view virtual override returns (uint256) { - return 1; - } - - /** - * @dev See {IERC777-totalSupply}. - */ - function totalSupply() public view virtual override(IERC20, IERC777) returns (uint256) { - return _totalSupply; - } - - /** - * @dev Returns the amount of tokens owned by an account (`tokenHolder`). - */ - function balanceOf(address tokenHolder) public view virtual override(IERC20, IERC777) returns (uint256) { - return _balances[tokenHolder]; - } - - /** - * @dev See {IERC777-send}. - * - * Also emits a {IERC20-Transfer} event for ERC20 compatibility. - */ - function send(address recipient, uint256 amount, bytes memory data) public virtual override { - _send(_msgSender(), recipient, amount, data, "", true); - } - - /** - * @dev See {IERC20-transfer}. - * - * Unlike `send`, `recipient` is _not_ required to implement the {IERC777Recipient} - * interface if it is a contract. - * - * Also emits a {Sent} event. - */ - function transfer(address recipient, uint256 amount) public virtual override returns (bool) { - _send(_msgSender(), recipient, amount, "", "", false); - return true; - } - - /** - * @dev See {IERC777-burn}. - * - * Also emits a {IERC20-Transfer} event for ERC20 compatibility. - */ - function burn(uint256 amount, bytes memory data) public virtual override { - _burn(_msgSender(), amount, data, ""); - } - - /** - * @dev See {IERC777-isOperatorFor}. - */ - function isOperatorFor(address operator, address tokenHolder) public view virtual override returns (bool) { - return - operator == tokenHolder || - (_defaultOperators[operator] && !_revokedDefaultOperators[tokenHolder][operator]) || - _operators[tokenHolder][operator]; - } - - /** - * @dev See {IERC777-authorizeOperator}. - */ - function authorizeOperator(address operator) public virtual override { - require(_msgSender() != operator, "ERC777: authorizing self as operator"); - - if (_defaultOperators[operator]) { - delete _revokedDefaultOperators[_msgSender()][operator]; - } else { - _operators[_msgSender()][operator] = true; - } - - emit AuthorizedOperator(operator, _msgSender()); - } - - /** - * @dev See {IERC777-revokeOperator}. - */ - function revokeOperator(address operator) public virtual override { - require(operator != _msgSender(), "ERC777: revoking self as operator"); - - if (_defaultOperators[operator]) { - _revokedDefaultOperators[_msgSender()][operator] = true; - } else { - delete _operators[_msgSender()][operator]; - } - - emit RevokedOperator(operator, _msgSender()); - } - - /** - * @dev See {IERC777-defaultOperators}. - */ - function defaultOperators() public view virtual override returns (address[] memory) { - return _defaultOperatorsArray; - } - - /** - * @dev See {IERC777-operatorSend}. - * - * Emits {Sent} and {IERC20-Transfer} events. - */ - function operatorSend( - address sender, - address recipient, - uint256 amount, - bytes memory data, - bytes memory operatorData - ) public virtual override { - require(isOperatorFor(_msgSender(), sender), "ERC777: caller is not an operator for holder"); - _send(sender, recipient, amount, data, operatorData, true); - } - - /** - * @dev See {IERC777-operatorBurn}. - * - * Emits {Burned} and {IERC20-Transfer} events. - */ - function operatorBurn( - address account, - uint256 amount, - bytes memory data, - bytes memory operatorData - ) public virtual override { - require(isOperatorFor(_msgSender(), account), "ERC777: caller is not an operator for holder"); - _burn(account, amount, data, operatorData); - } - - /** - * @dev See {IERC20-allowance}. - * - * Note that operator and allowance concepts are orthogonal: operators may - * not have allowance, and accounts with allowance may not be operators - * themselves. - */ - function allowance(address holder, address spender) public view virtual override returns (uint256) { - return _allowances[holder][spender]; - } - - /** - * @dev See {IERC20-approve}. - * - * NOTE: If `value` is the maximum `uint256`, the allowance is not updated on - * `transferFrom`. This is semantically equivalent to an infinite approval. - * - * Note that accounts cannot have allowance issued by their operators. - */ - function approve(address spender, uint256 value) public virtual override returns (bool) { - address holder = _msgSender(); - _approve(holder, spender, value); - return true; - } - - /** - * @dev See {IERC20-transferFrom}. - * - * NOTE: Does not update the allowance if the current allowance - * is the maximum `uint256`. - * - * Note that operator and allowance concepts are orthogonal: operators cannot - * call `transferFrom` (unless they have allowance), and accounts with - * allowance cannot call `operatorSend` (unless they are operators). - * - * Emits {Sent}, {IERC20-Transfer} and {IERC20-Approval} events. - */ - function transferFrom(address holder, address recipient, uint256 amount) public virtual override returns (bool) { - address spender = _msgSender(); - _spendAllowance(holder, spender, amount); - _send(holder, recipient, amount, "", "", false); - return true; - } - - /** - * @dev Creates `amount` tokens and assigns them to `account`, increasing - * the total supply. - * - * If a send hook is registered for `account`, the corresponding function - * will be called with the caller address as the `operator` and with - * `userData` and `operatorData`. - * - * See {IERC777Sender} and {IERC777Recipient}. - * - * Emits {Minted} and {IERC20-Transfer} events. - * - * Requirements - * - * - `account` cannot be the zero address. - * - if `account` is a contract, it must implement the {IERC777Recipient} - * interface. - */ - function _mint(address account, uint256 amount, bytes memory userData, bytes memory operatorData) internal virtual { - _mint(account, amount, userData, operatorData, true); - } - - /** - * @dev Creates `amount` tokens and assigns them to `account`, increasing - * the total supply. - * - * If `requireReceptionAck` is set to true, and if a send hook is - * registered for `account`, the corresponding function will be called with - * `operator`, `data` and `operatorData`. - * - * See {IERC777Sender} and {IERC777Recipient}. - * - * Emits {Minted} and {IERC20-Transfer} events. - * - * Requirements - * - * - `account` cannot be the zero address. - * - if `account` is a contract, it must implement the {IERC777Recipient} - * interface. - */ - function _mint( - address account, - uint256 amount, - bytes memory userData, - bytes memory operatorData, - bool requireReceptionAck - ) internal virtual { - require(account != address(0), "ERC777: mint to the zero address"); - - address operator = _msgSender(); - - _beforeTokenTransfer(operator, address(0), account, amount); - - // Update state variables - _totalSupply += amount; - _balances[account] += amount; - - _callTokensReceived(operator, address(0), account, amount, userData, operatorData, requireReceptionAck); - - emit Minted(operator, account, amount, userData, operatorData); - emit Transfer(address(0), account, amount); - } - - /** - * @dev Send tokens - * @param from address token holder address - * @param to address recipient address - * @param amount uint256 amount of tokens to transfer - * @param userData bytes extra information provided by the token holder (if any) - * @param operatorData bytes extra information provided by the operator (if any) - * @param requireReceptionAck if true, contract recipients are required to implement ERC777TokensRecipient - */ - function _send( - address from, - address to, - uint256 amount, - bytes memory userData, - bytes memory operatorData, - bool requireReceptionAck - ) internal virtual { - require(from != address(0), "ERC777: transfer from the zero address"); - require(to != address(0), "ERC777: transfer to the zero address"); - - address operator = _msgSender(); - - _callTokensToSend(operator, from, to, amount, userData, operatorData); - - _move(operator, from, to, amount, userData, operatorData); - - _callTokensReceived(operator, from, to, amount, userData, operatorData, requireReceptionAck); - } - - /** - * @dev Burn tokens - * @param from address token holder address - * @param amount uint256 amount of tokens to burn - * @param data bytes extra information provided by the token holder - * @param operatorData bytes extra information provided by the operator (if any) - */ - function _burn(address from, uint256 amount, bytes memory data, bytes memory operatorData) internal virtual { - require(from != address(0), "ERC777: burn from the zero address"); - - address operator = _msgSender(); - - _callTokensToSend(operator, from, address(0), amount, data, operatorData); - - _beforeTokenTransfer(operator, from, address(0), amount); - - // Update state variables - uint256 fromBalance = _balances[from]; - require(fromBalance >= amount, "ERC777: burn amount exceeds balance"); - unchecked { - _balances[from] = fromBalance - amount; - } - _totalSupply -= amount; - - emit Burned(operator, from, amount, data, operatorData); - emit Transfer(from, address(0), amount); - } - - function _move( - address operator, - address from, - address to, - uint256 amount, - bytes memory userData, - bytes memory operatorData - ) private { - _beforeTokenTransfer(operator, from, to, amount); - - uint256 fromBalance = _balances[from]; - require(fromBalance >= amount, "ERC777: transfer amount exceeds balance"); - unchecked { - _balances[from] = fromBalance - amount; - } - _balances[to] += amount; - - emit Sent(operator, from, to, amount, userData, operatorData); - emit Transfer(from, to, amount); - } - - /** - * @dev See {ERC20-_approve}. - * - * Note that accounts cannot have allowance issued by their operators. - */ - function _approve(address holder, address spender, uint256 value) internal virtual { - require(holder != address(0), "ERC777: approve from the zero address"); - require(spender != address(0), "ERC777: approve to the zero address"); - - _allowances[holder][spender] = value; - emit Approval(holder, spender, value); - } - - /** - * @dev Call from.tokensToSend() if the interface is registered - * @param operator address operator requesting the transfer - * @param from address token holder address - * @param to address recipient address - * @param amount uint256 amount of tokens to transfer - * @param userData bytes extra information provided by the token holder (if any) - * @param operatorData bytes extra information provided by the operator (if any) - */ - function _callTokensToSend( - address operator, - address from, - address to, - uint256 amount, - bytes memory userData, - bytes memory operatorData - ) private { - address implementer = _ERC1820_REGISTRY.getInterfaceImplementer(from, _TOKENS_SENDER_INTERFACE_HASH); - if (implementer != address(0)) { - IERC777Sender(implementer).tokensToSend(operator, from, to, amount, userData, operatorData); - } - } - - /** - * @dev Call to.tokensReceived() if the interface is registered. Reverts if the recipient is a contract but - * tokensReceived() was not registered for the recipient - * @param operator address operator requesting the transfer - * @param from address token holder address - * @param to address recipient address - * @param amount uint256 amount of tokens to transfer - * @param userData bytes extra information provided by the token holder (if any) - * @param operatorData bytes extra information provided by the operator (if any) - * @param requireReceptionAck if true, contract recipients are required to implement ERC777TokensRecipient - */ - function _callTokensReceived( - address operator, - address from, - address to, - uint256 amount, - bytes memory userData, - bytes memory operatorData, - bool requireReceptionAck - ) private { - address implementer = _ERC1820_REGISTRY.getInterfaceImplementer(to, _TOKENS_RECIPIENT_INTERFACE_HASH); - if (implementer != address(0)) { - IERC777Recipient(implementer).tokensReceived(operator, from, to, amount, userData, operatorData); - } else if (requireReceptionAck) { - require( - to.code.length == 0, - "ERC777: token recipient contract has no implementer for ERC777TokensRecipient" - ); - } - } - - /** - * @dev Updates `owner` s allowance for `spender` based on spent `amount`. - * - * Does not update the allowance amount in case of infinite allowance. - * Revert if not enough allowance is available. - * - * Might emit an {IERC20-Approval} event. - */ - function _spendAllowance(address owner, address spender, uint256 amount) internal virtual { - uint256 currentAllowance = allowance(owner, spender); - if (currentAllowance != type(uint256).max) { - require(currentAllowance >= amount, "ERC777: insufficient allowance"); - unchecked { - _approve(owner, spender, currentAllowance - amount); - } - } - } - - /** - * @dev Hook that is called before any token transfer. This includes - * calls to {send}, {transfer}, {operatorSend}, {transferFrom}, minting and burning. - * - * Calling conditions: - * - * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens - * will be to transferred to `to`. - * - when `from` is zero, `amount` tokens will be minted for `to`. - * - when `to` is zero, `amount` of ``from``'s tokens will be burned. - * - `from` and `to` are never both zero. - * - * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - */ - function _beforeTokenTransfer(address operator, address from, address to, uint256 amount) internal virtual {} -} diff --git a/contracts/token/ERC777/IERC777.sol b/contracts/token/ERC777/IERC777.sol deleted file mode 100644 index d3bede626..000000000 --- a/contracts/token/ERC777/IERC777.sol +++ /dev/null @@ -1,200 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC777/IERC777.sol) - -pragma solidity ^0.8.0; - -/** - * @dev Interface of the ERC777Token standard as defined in the EIP. - * - * This contract uses the - * https://eips.ethereum.org/EIPS/eip-1820[ERC1820 registry standard] to let - * token holders and recipients react to token movements by using setting implementers - * for the associated interfaces in said registry. See {IERC1820Registry} and - * {ERC1820Implementer}. - */ -interface IERC777 { - /** - * @dev Emitted when `amount` tokens are created by `operator` and assigned to `to`. - * - * Note that some additional user `data` and `operatorData` can be logged in the event. - */ - event Minted(address indexed operator, address indexed to, uint256 amount, bytes data, bytes operatorData); - - /** - * @dev Emitted when `operator` destroys `amount` tokens from `account`. - * - * Note that some additional user `data` and `operatorData` can be logged in the event. - */ - event Burned(address indexed operator, address indexed from, uint256 amount, bytes data, bytes operatorData); - - /** - * @dev Emitted when `operator` is made operator for `tokenHolder`. - */ - event AuthorizedOperator(address indexed operator, address indexed tokenHolder); - - /** - * @dev Emitted when `operator` is revoked its operator status for `tokenHolder`. - */ - event RevokedOperator(address indexed operator, address indexed tokenHolder); - - /** - * @dev Returns the name of the token. - */ - function name() external view returns (string memory); - - /** - * @dev Returns the symbol of the token, usually a shorter version of the - * name. - */ - function symbol() external view returns (string memory); - - /** - * @dev Returns the smallest part of the token that is not divisible. This - * means all token operations (creation, movement and destruction) must have - * amounts that are a multiple of this number. - * - * For most token contracts, this value will equal 1. - */ - function granularity() external view returns (uint256); - - /** - * @dev Returns the amount of tokens in existence. - */ - function totalSupply() external view returns (uint256); - - /** - * @dev Returns the amount of tokens owned by an account (`owner`). - */ - function balanceOf(address owner) external view returns (uint256); - - /** - * @dev Moves `amount` tokens from the caller's account to `recipient`. - * - * If send or receive hooks are registered for the caller and `recipient`, - * the corresponding functions will be called with `data` and empty - * `operatorData`. See {IERC777Sender} and {IERC777Recipient}. - * - * Emits a {Sent} event. - * - * Requirements - * - * - the caller must have at least `amount` tokens. - * - `recipient` cannot be the zero address. - * - if `recipient` is a contract, it must implement the {IERC777Recipient} - * interface. - */ - function send(address recipient, uint256 amount, bytes calldata data) external; - - /** - * @dev Destroys `amount` tokens from the caller's account, reducing the - * total supply. - * - * If a send hook is registered for the caller, the corresponding function - * will be called with `data` and empty `operatorData`. See {IERC777Sender}. - * - * Emits a {Burned} event. - * - * Requirements - * - * - the caller must have at least `amount` tokens. - */ - function burn(uint256 amount, bytes calldata data) external; - - /** - * @dev Returns true if an account is an operator of `tokenHolder`. - * Operators can send and burn tokens on behalf of their owners. All - * accounts are their own operator. - * - * See {operatorSend} and {operatorBurn}. - */ - function isOperatorFor(address operator, address tokenHolder) external view returns (bool); - - /** - * @dev Make an account an operator of the caller. - * - * See {isOperatorFor}. - * - * Emits an {AuthorizedOperator} event. - * - * Requirements - * - * - `operator` cannot be calling address. - */ - function authorizeOperator(address operator) external; - - /** - * @dev Revoke an account's operator status for the caller. - * - * See {isOperatorFor} and {defaultOperators}. - * - * Emits a {RevokedOperator} event. - * - * Requirements - * - * - `operator` cannot be calling address. - */ - function revokeOperator(address operator) external; - - /** - * @dev Returns the list of default operators. These accounts are operators - * for all token holders, even if {authorizeOperator} was never called on - * them. - * - * This list is immutable, but individual holders may revoke these via - * {revokeOperator}, in which case {isOperatorFor} will return false. - */ - function defaultOperators() external view returns (address[] memory); - - /** - * @dev Moves `amount` tokens from `sender` to `recipient`. The caller must - * be an operator of `sender`. - * - * If send or receive hooks are registered for `sender` and `recipient`, - * the corresponding functions will be called with `data` and - * `operatorData`. See {IERC777Sender} and {IERC777Recipient}. - * - * Emits a {Sent} event. - * - * Requirements - * - * - `sender` cannot be the zero address. - * - `sender` must have at least `amount` tokens. - * - the caller must be an operator for `sender`. - * - `recipient` cannot be the zero address. - * - if `recipient` is a contract, it must implement the {IERC777Recipient} - * interface. - */ - function operatorSend( - address sender, - address recipient, - uint256 amount, - bytes calldata data, - bytes calldata operatorData - ) external; - - /** - * @dev Destroys `amount` tokens from `account`, reducing the total supply. - * The caller must be an operator of `account`. - * - * If a send hook is registered for `account`, the corresponding function - * will be called with `data` and `operatorData`. See {IERC777Sender}. - * - * Emits a {Burned} event. - * - * Requirements - * - * - `account` cannot be the zero address. - * - `account` must have at least `amount` tokens. - * - the caller must be an operator for `account`. - */ - function operatorBurn(address account, uint256 amount, bytes calldata data, bytes calldata operatorData) external; - - event Sent( - address indexed operator, - address indexed from, - address indexed to, - uint256 amount, - bytes data, - bytes operatorData - ); -} diff --git a/contracts/token/ERC777/IERC777Recipient.sol b/contracts/token/ERC777/IERC777Recipient.sol deleted file mode 100644 index 717dd8f8c..000000000 --- a/contracts/token/ERC777/IERC777Recipient.sol +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts v4.4.1 (token/ERC777/IERC777Recipient.sol) - -pragma solidity ^0.8.0; - -/** - * @dev Interface of the ERC777TokensRecipient standard as defined in the EIP. - * - * Accounts can be notified of {IERC777} tokens being sent to them by having a - * contract implement this interface (contract holders can be their own - * implementer) and registering it on the - * https://eips.ethereum.org/EIPS/eip-1820[ERC1820 global registry]. - * - * See {IERC1820Registry} and {ERC1820Implementer}. - */ -interface IERC777Recipient { - /** - * @dev Called by an {IERC777} token contract whenever tokens are being - * moved or created into a registered account (`to`). The type of operation - * is conveyed by `from` being the zero address or not. - * - * This call occurs _after_ the token contract's state is updated, so - * {IERC777-balanceOf}, etc., can be used to query the post-operation state. - * - * This function may revert to prevent the operation from being executed. - */ - function tokensReceived( - address operator, - address from, - address to, - uint256 amount, - bytes calldata userData, - bytes calldata operatorData - ) external; -} diff --git a/contracts/token/ERC777/IERC777Sender.sol b/contracts/token/ERC777/IERC777Sender.sol deleted file mode 100644 index 969e3e367..000000000 --- a/contracts/token/ERC777/IERC777Sender.sol +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts v4.4.1 (token/ERC777/IERC777Sender.sol) - -pragma solidity ^0.8.0; - -/** - * @dev Interface of the ERC777TokensSender standard as defined in the EIP. - * - * {IERC777} Token holders can be notified of operations performed on their - * tokens by having a contract implement this interface (contract holders can be - * their own implementer) and registering it on the - * https://eips.ethereum.org/EIPS/eip-1820[ERC1820 global registry]. - * - * See {IERC1820Registry} and {ERC1820Implementer}. - */ -interface IERC777Sender { - /** - * @dev Called by an {IERC777} token contract whenever a registered holder's - * (`from`) tokens are about to be moved or destroyed. The type of operation - * is conveyed by `to` being the zero address or not. - * - * This call occurs _before_ the token contract's state is updated, so - * {IERC777-balanceOf}, etc., can be used to query the pre-operation state. - * - * This function may revert to prevent the operation from being executed. - */ - function tokensToSend( - address operator, - address from, - address to, - uint256 amount, - bytes calldata userData, - bytes calldata operatorData - ) external; -} diff --git a/contracts/token/ERC777/README.adoc b/contracts/token/ERC777/README.adoc deleted file mode 100644 index cc096f74c..000000000 --- a/contracts/token/ERC777/README.adoc +++ /dev/null @@ -1,26 +0,0 @@ -= ERC 777 - -[.readme-notice] -NOTE: This document is better viewed at https://docs.openzeppelin.com/contracts/api/token/erc777 - -CAUTION: As of v4.9, OpenZeppelin's implementation of ERC-777 is deprecated and will be removed in the next major release. - -This set of interfaces and contracts are all related to the https://eips.ethereum.org/EIPS/eip-777[ERC777 token standard]. - -TIP: For an overview of ERC777 tokens and a walk through on how to create a token contract read our xref:ROOT:erc777.adoc[ERC777 guide]. - -The token behavior itself is implemented in the core contracts: {IERC777}, {ERC777}. - -Additionally there are interfaces used to develop contracts that react to token movements: {IERC777Sender}, {IERC777Recipient}. - -== Core - -{{IERC777}} - -{{ERC777}} - -== Hooks - -{{IERC777Sender}} - -{{IERC777Recipient}} diff --git a/contracts/utils/Checkpoints.sol b/contracts/utils/Checkpoints.sol index 55ff42fd1..aff542585 100644 --- a/contracts/utils/Checkpoints.sol +++ b/contracts/utils/Checkpoints.sol @@ -17,202 +17,6 @@ import "./math/SafeCast.sol"; * _Available since v4.5._ */ library Checkpoints { - struct History { - Checkpoint[] _checkpoints; - } - - struct Checkpoint { - uint32 _blockNumber; - uint224 _value; - } - - /** - * @dev Returns the value at a given block number. If a checkpoint is not available at that block, the closest one - * before it is returned, or zero otherwise. Because the number returned corresponds to that at the end of the - * block, the requested block number must be in the past, excluding the current block. - */ - function getAtBlock(History storage self, uint256 blockNumber) internal view returns (uint256) { - require(blockNumber < block.number, "Checkpoints: block not yet mined"); - uint32 key = SafeCast.toUint32(blockNumber); - - uint256 len = self._checkpoints.length; - uint256 pos = _upperBinaryLookup(self._checkpoints, key, 0, len); - return pos == 0 ? 0 : _unsafeAccess(self._checkpoints, pos - 1)._value; - } - - /** - * @dev Returns the value at a given block number. If a checkpoint is not available at that block, the closest one - * before it is returned, or zero otherwise. Similar to {upperLookup} but optimized for the case when the searched - * checkpoint is probably "recent", defined as being among the last sqrt(N) checkpoints where N is the number of - * checkpoints. - */ - function getAtProbablyRecentBlock(History storage self, uint256 blockNumber) internal view returns (uint256) { - require(blockNumber < block.number, "Checkpoints: block not yet mined"); - uint32 key = SafeCast.toUint32(blockNumber); - - uint256 len = self._checkpoints.length; - - uint256 low = 0; - uint256 high = len; - - if (len > 5) { - uint256 mid = len - Math.sqrt(len); - if (key < _unsafeAccess(self._checkpoints, mid)._blockNumber) { - high = mid; - } else { - low = mid + 1; - } - } - - uint256 pos = _upperBinaryLookup(self._checkpoints, key, low, high); - - return pos == 0 ? 0 : _unsafeAccess(self._checkpoints, pos - 1)._value; - } - - /** - * @dev Pushes a value onto a History so that it is stored as the checkpoint for the current block. - * - * Returns previous value and new value. - */ - function push(History storage self, uint256 value) internal returns (uint256, uint256) { - return _insert(self._checkpoints, SafeCast.toUint32(block.number), SafeCast.toUint224(value)); - } - - /** - * @dev Pushes a value onto a History, by updating the latest value using binary operation `op`. The new value will - * be set to `op(latest, delta)`. - * - * Returns previous value and new value. - */ - function push( - History storage self, - function(uint256, uint256) view returns (uint256) op, - uint256 delta - ) internal returns (uint256, uint256) { - return push(self, op(latest(self), delta)); - } - - /** - * @dev Returns the value in the most recent checkpoint, or zero if there are no checkpoints. - */ - function latest(History storage self) internal view returns (uint224) { - uint256 pos = self._checkpoints.length; - return pos == 0 ? 0 : _unsafeAccess(self._checkpoints, pos - 1)._value; - } - - /** - * @dev Returns whether there is a checkpoint in the structure (i.e. it is not empty), and if so the key and value - * in the most recent checkpoint. - */ - function latestCheckpoint( - History storage self - ) internal view returns (bool exists, uint32 _blockNumber, uint224 _value) { - uint256 pos = self._checkpoints.length; - if (pos == 0) { - return (false, 0, 0); - } else { - Checkpoint memory ckpt = _unsafeAccess(self._checkpoints, pos - 1); - return (true, ckpt._blockNumber, ckpt._value); - } - } - - /** - * @dev Returns the number of checkpoint. - */ - function length(History storage self) internal view returns (uint256) { - return self._checkpoints.length; - } - - /** - * @dev Returns checkpoint at given position. - */ - function at(History storage self, uint32 pos) internal view returns (Checkpoint memory) { - return self._checkpoints[pos]; - } - - /** - * @dev Pushes a (`key`, `value`) pair into an ordered list of checkpoints, either by inserting a new checkpoint, - * or by updating the last one. - */ - function _insert(Checkpoint[] storage self, uint32 key, uint224 value) private returns (uint224, uint224) { - uint256 pos = self.length; - - if (pos > 0) { - // Copying to memory is important here. - Checkpoint memory last = _unsafeAccess(self, pos - 1); - - // Checkpoint keys must be non-decreasing. - require(last._blockNumber <= key, "Checkpoint: decreasing keys"); - - // Update or push new checkpoint - if (last._blockNumber == key) { - _unsafeAccess(self, pos - 1)._value = value; - } else { - self.push(Checkpoint({_blockNumber: key, _value: value})); - } - return (last._value, value); - } else { - self.push(Checkpoint({_blockNumber: key, _value: value})); - return (0, value); - } - } - - /** - * @dev Return the index of the last (most recent) checkpoint with key lower or equal than the search key, or `high` if there is none. - * `low` and `high` define a section where to do the search, with inclusive `low` and exclusive `high`. - * - * WARNING: `high` should not be greater than the array's length. - */ - function _upperBinaryLookup( - Checkpoint[] storage self, - uint32 key, - uint256 low, - uint256 high - ) private view returns (uint256) { - while (low < high) { - uint256 mid = Math.average(low, high); - if (_unsafeAccess(self, mid)._blockNumber > key) { - high = mid; - } else { - low = mid + 1; - } - } - return high; - } - - /** - * @dev Return the index of the first (oldest) checkpoint with key is greater or equal than the search key, or `high` if there is none. - * `low` and `high` define a section where to do the search, with inclusive `low` and exclusive `high`. - * - * WARNING: `high` should not be greater than the array's length. - */ - function _lowerBinaryLookup( - Checkpoint[] storage self, - uint32 key, - uint256 low, - uint256 high - ) private view returns (uint256) { - while (low < high) { - uint256 mid = Math.average(low, high); - if (_unsafeAccess(self, mid)._blockNumber < key) { - low = mid + 1; - } else { - high = mid; - } - } - return high; - } - - /** - * @dev Access an element of the array without performing bounds check. The position is assumed to be within bounds. - */ - function _unsafeAccess(Checkpoint[] storage self, uint256 pos) private pure returns (Checkpoint storage result) { - assembly { - mstore(0, self.slot) - result.slot := add(keccak256(0, 0x20), pos) - } - } - struct Trace224 { Checkpoint224[] _checkpoints; } diff --git a/contracts/utils/README.adoc b/contracts/utils/README.adoc index 994b4b956..07cee6427 100644 --- a/contracts/utils/README.adoc +++ b/contracts/utils/README.adoc @@ -31,10 +31,6 @@ Finally, {Create2} contains all necessary utilities to safely use the https://bl {{SafeCast}} -{{SafeMath}} - -{{SignedSafeMath}} - == Cryptography {{ECDSA}} @@ -45,39 +41,18 @@ Finally, {Create2} contains all necessary utilities to safely use the https://bl {{EIP712}} -== Escrow - -{{ConditionalEscrow}} - -{{Escrow}} - -{{RefundEscrow}} - == Introspection This set of interfaces and contracts deal with https://en.wikipedia.org/wiki/Type_introspection[type introspection] of contracts, that is, examining which functions can be called on them. This is usually referred to as a contract's _interface_. Ethereum contracts have no native concept of an interface, so applications must usually simply trust they are not making an incorrect call. For trusted setups this is a non-issue, but often unknown and untrusted third-party addresses need to be interacted with. There may even not be any direct calls to them! (e.g. `ERC20` tokens may be sent to a contract that lacks a way to transfer them out of it, locking them forever). In these cases, a contract _declaring_ its interface can be very helpful in preventing errors. -There are two main ways to approach this. - -* Locally, where a contract implements `IERC165` and declares an interface, and a second one queries it directly via `ERC165Checker`. -* Globally, where a global and unique registry (`IERC1820Registry`) is used to register implementers of a certain interface (`IERC1820Implementer`). It is then the registry that is queried, which allows for more complex setups, like contracts implementing interfaces for externally-owned accounts. - -Note that, in all cases, accounts simply _declare_ their interfaces, but they are not required to actually implement them. This mechanism can therefore be used to both prevent errors and allow for complex interactions (see `ERC777`), but it must not be relied on for security. - {{IERC165}} {{ERC165}} {{ERC165Checker}} -{{IERC1820Registry}} - -{{IERC1820Implementer}} - -{{ERC1820Implementer}} - == Data Structures {{BitMaps}} diff --git a/contracts/utils/Timers.sol b/contracts/utils/Timers.sol deleted file mode 100644 index 1c92b029b..000000000 --- a/contracts/utils/Timers.sol +++ /dev/null @@ -1,75 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts v4.4.1 (utils/Timers.sol) - -pragma solidity ^0.8.0; - -/** - * @dev Tooling for timepoints, timers and delays - * - * CAUTION: This file is deprecated as of 4.9 and will be removed in the next major release. - */ -library Timers { - struct Timestamp { - uint64 _deadline; - } - - function getDeadline(Timestamp memory timer) internal pure returns (uint64) { - return timer._deadline; - } - - function setDeadline(Timestamp storage timer, uint64 timestamp) internal { - timer._deadline = timestamp; - } - - function reset(Timestamp storage timer) internal { - timer._deadline = 0; - } - - function isUnset(Timestamp memory timer) internal pure returns (bool) { - return timer._deadline == 0; - } - - function isStarted(Timestamp memory timer) internal pure returns (bool) { - return timer._deadline > 0; - } - - function isPending(Timestamp memory timer) internal view returns (bool) { - return timer._deadline > block.timestamp; - } - - function isExpired(Timestamp memory timer) internal view returns (bool) { - return isStarted(timer) && timer._deadline <= block.timestamp; - } - - struct BlockNumber { - uint64 _deadline; - } - - function getDeadline(BlockNumber memory timer) internal pure returns (uint64) { - return timer._deadline; - } - - function setDeadline(BlockNumber storage timer, uint64 timestamp) internal { - timer._deadline = timestamp; - } - - function reset(BlockNumber storage timer) internal { - timer._deadline = 0; - } - - function isUnset(BlockNumber memory timer) internal pure returns (bool) { - return timer._deadline == 0; - } - - function isStarted(BlockNumber memory timer) internal pure returns (bool) { - return timer._deadline > 0; - } - - function isPending(BlockNumber memory timer) internal view returns (bool) { - return timer._deadline > block.number; - } - - function isExpired(BlockNumber memory timer) internal view returns (bool) { - return isStarted(timer) && timer._deadline <= block.number; - } -} diff --git a/contracts/utils/cryptography/ECDSA.sol b/contracts/utils/cryptography/ECDSA.sol index 77279eb4f..03d2b0eef 100644 --- a/contracts/utils/cryptography/ECDSA.sol +++ b/contracts/utils/cryptography/ECDSA.sol @@ -16,8 +16,7 @@ library ECDSA { NoError, InvalidSignature, InvalidSignatureLength, - InvalidSignatureS, - InvalidSignatureV // Deprecated in v4.8 + InvalidSignatureS } function _throwError(RecoverError error) private pure { diff --git a/contracts/utils/cryptography/draft-EIP712.sol b/contracts/utils/cryptography/draft-EIP712.sol deleted file mode 100644 index fdae3ba3e..000000000 --- a/contracts/utils/cryptography/draft-EIP712.sol +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.8.0) (utils/cryptography/draft-EIP712.sol) - -pragma solidity ^0.8.0; - -// EIP-712 is Final as of 2022-08-11. This file is deprecated. - -import "./EIP712.sol"; diff --git a/contracts/utils/escrow/ConditionalEscrow.sol b/contracts/utils/escrow/ConditionalEscrow.sol deleted file mode 100644 index 87f53815b..000000000 --- a/contracts/utils/escrow/ConditionalEscrow.sol +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts v4.4.1 (utils/escrow/ConditionalEscrow.sol) - -pragma solidity ^0.8.0; - -import "./Escrow.sol"; - -/** - * @title ConditionalEscrow - * @dev Base abstract escrow to only allow withdrawal if a condition is met. - * @dev Intended usage: See {Escrow}. Same usage guidelines apply here. - */ -abstract contract ConditionalEscrow is Escrow { - /** - * @dev Returns whether an address is allowed to withdraw their funds. To be - * implemented by derived contracts. - * @param payee The destination address of the funds. - */ - function withdrawalAllowed(address payee) public view virtual returns (bool); - - function withdraw(address payable payee) public virtual override { - require(withdrawalAllowed(payee), "ConditionalEscrow: payee is not allowed to withdraw"); - super.withdraw(payee); - } -} diff --git a/contracts/utils/escrow/Escrow.sol b/contracts/utils/escrow/Escrow.sol deleted file mode 100644 index 48dd51ab7..000000000 --- a/contracts/utils/escrow/Escrow.sol +++ /dev/null @@ -1,67 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.7.0) (utils/escrow/Escrow.sol) - -pragma solidity ^0.8.0; - -import "../../access/Ownable.sol"; -import "../Address.sol"; - -/** - * @title Escrow - * @dev Base escrow contract, holds funds designated for a payee until they - * withdraw them. - * - * Intended usage: This contract (and derived escrow contracts) should be a - * standalone contract, that only interacts with the contract that instantiated - * it. That way, it is guaranteed that all Ether will be handled according to - * the `Escrow` rules, and there is no need to check for payable functions or - * transfers in the inheritance tree. The contract that uses the escrow as its - * payment method should be its owner, and provide public methods redirecting - * to the escrow's deposit and withdraw. - */ -contract Escrow is Ownable { - using Address for address payable; - - event Deposited(address indexed payee, uint256 weiAmount); - event Withdrawn(address indexed payee, uint256 weiAmount); - - mapping(address => uint256) private _deposits; - - function depositsOf(address payee) public view returns (uint256) { - return _deposits[payee]; - } - - /** - * @dev Stores the sent amount as credit to be withdrawn. - * @param payee The destination address of the funds. - * - * Emits a {Deposited} event. - */ - function deposit(address payee) public payable virtual onlyOwner { - uint256 amount = msg.value; - _deposits[payee] += amount; - emit Deposited(payee, amount); - } - - /** - * @dev Withdraw accumulated balance for a payee, forwarding all gas to the - * recipient. - * - * WARNING: Forwarding all gas opens the door to reentrancy vulnerabilities. - * Make sure you trust the recipient, or are either following the - * checks-effects-interactions pattern or using {ReentrancyGuard}. - * - * @param payee The address whose funds will be withdrawn and transferred to. - * - * Emits a {Withdrawn} event. - */ - function withdraw(address payable payee) public virtual onlyOwner { - uint256 payment = _deposits[payee]; - - _deposits[payee] = 0; - - payee.sendValue(payment); - - emit Withdrawn(payee, payment); - } -} diff --git a/contracts/utils/escrow/RefundEscrow.sol b/contracts/utils/escrow/RefundEscrow.sol deleted file mode 100644 index 0e9621fee..000000000 --- a/contracts/utils/escrow/RefundEscrow.sol +++ /dev/null @@ -1,100 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts v4.4.1 (utils/escrow/RefundEscrow.sol) - -pragma solidity ^0.8.0; - -import "./ConditionalEscrow.sol"; - -/** - * @title RefundEscrow - * @dev Escrow that holds funds for a beneficiary, deposited from multiple - * parties. - * @dev Intended usage: See {Escrow}. Same usage guidelines apply here. - * @dev The owner account (that is, the contract that instantiates this - * contract) may deposit, close the deposit period, and allow for either - * withdrawal by the beneficiary, or refunds to the depositors. All interactions - * with `RefundEscrow` will be made through the owner contract. - */ -contract RefundEscrow is ConditionalEscrow { - using Address for address payable; - - enum State { - Active, - Refunding, - Closed - } - - event RefundsClosed(); - event RefundsEnabled(); - - State private _state; - address payable private immutable _beneficiary; - - /** - * @dev Constructor. - * @param beneficiary_ The beneficiary of the deposits. - */ - constructor(address payable beneficiary_) { - require(beneficiary_ != address(0), "RefundEscrow: beneficiary is the zero address"); - _beneficiary = beneficiary_; - _state = State.Active; - } - - /** - * @return The current state of the escrow. - */ - function state() public view virtual returns (State) { - return _state; - } - - /** - * @return The beneficiary of the escrow. - */ - function beneficiary() public view virtual returns (address payable) { - return _beneficiary; - } - - /** - * @dev Stores funds that may later be refunded. - * @param refundee The address funds will be sent to if a refund occurs. - */ - function deposit(address refundee) public payable virtual override { - require(state() == State.Active, "RefundEscrow: can only deposit while active"); - super.deposit(refundee); - } - - /** - * @dev Allows for the beneficiary to withdraw their funds, rejecting - * further deposits. - */ - function close() public virtual onlyOwner { - require(state() == State.Active, "RefundEscrow: can only close while active"); - _state = State.Closed; - emit RefundsClosed(); - } - - /** - * @dev Allows for refunds to take place, rejecting further deposits. - */ - function enableRefunds() public virtual onlyOwner { - require(state() == State.Active, "RefundEscrow: can only enable refunds while active"); - _state = State.Refunding; - emit RefundsEnabled(); - } - - /** - * @dev Withdraws the beneficiary's funds. - */ - function beneficiaryWithdraw() public virtual { - require(state() == State.Closed, "RefundEscrow: beneficiary can only withdraw while closed"); - beneficiary().sendValue(address(this).balance); - } - - /** - * @dev Returns whether refundees can withdraw their deposits (be refunded). The overridden function receives a - * 'payee' argument, but we ignore it here since the condition is global, not per-payee. - */ - function withdrawalAllowed(address) public view override returns (bool) { - return state() == State.Refunding; - } -} diff --git a/contracts/utils/introspection/ERC1820Implementer.sol b/contracts/utils/introspection/ERC1820Implementer.sol deleted file mode 100644 index cf4b50498..000000000 --- a/contracts/utils/introspection/ERC1820Implementer.sol +++ /dev/null @@ -1,43 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC1820Implementer.sol) - -pragma solidity ^0.8.0; - -import "./IERC1820Implementer.sol"; - -/** - * @dev Implementation of the {IERC1820Implementer} interface. - * - * Contracts may inherit from this and call {_registerInterfaceForAddress} to - * declare their willingness to be implementers. - * {IERC1820Registry-setInterfaceImplementer} should then be called for the - * registration to be complete. - * - * CAUTION: This file is deprecated as of v4.9 and will be removed in the next major release. - */ -contract ERC1820Implementer is IERC1820Implementer { - bytes32 private constant _ERC1820_ACCEPT_MAGIC = keccak256("ERC1820_ACCEPT_MAGIC"); - - mapping(bytes32 => mapping(address => bool)) private _supportedInterfaces; - - /** - * @dev See {IERC1820Implementer-canImplementInterfaceForAddress}. - */ - function canImplementInterfaceForAddress( - bytes32 interfaceHash, - address account - ) public view virtual override returns (bytes32) { - return _supportedInterfaces[interfaceHash][account] ? _ERC1820_ACCEPT_MAGIC : bytes32(0x00); - } - - /** - * @dev Declares the contract as willing to be an implementer of - * `interfaceHash` for `account`. - * - * See {IERC1820Registry-setInterfaceImplementer} and - * {IERC1820Registry-interfaceHash}. - */ - function _registerInterfaceForAddress(bytes32 interfaceHash, address account) internal virtual { - _supportedInterfaces[interfaceHash][account] = true; - } -} diff --git a/contracts/utils/introspection/IERC1820Implementer.sol b/contracts/utils/introspection/IERC1820Implementer.sol deleted file mode 100644 index c4d0b3028..000000000 --- a/contracts/utils/introspection/IERC1820Implementer.sol +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC1820Implementer.sol) - -pragma solidity ^0.8.0; - -/** - * @dev Interface for an ERC1820 implementer, as defined in the - * https://eips.ethereum.org/EIPS/eip-1820#interface-implementation-erc1820implementerinterface[EIP]. - * Used by contracts that will be registered as implementers in the - * {IERC1820Registry}. - */ -interface IERC1820Implementer { - /** - * @dev Returns a special value (`ERC1820_ACCEPT_MAGIC`) if this contract - * implements `interfaceHash` for `account`. - * - * See {IERC1820Registry-setInterfaceImplementer}. - */ - function canImplementInterfaceForAddress(bytes32 interfaceHash, address account) external view returns (bytes32); -} diff --git a/contracts/utils/introspection/IERC1820Registry.sol b/contracts/utils/introspection/IERC1820Registry.sol deleted file mode 100644 index a146bc2a6..000000000 --- a/contracts/utils/introspection/IERC1820Registry.sol +++ /dev/null @@ -1,112 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.8.0) (utils/introspection/IERC1820Registry.sol) - -pragma solidity ^0.8.0; - -/** - * @dev Interface of the global ERC1820 Registry, as defined in the - * https://eips.ethereum.org/EIPS/eip-1820[EIP]. Accounts may register - * implementers for interfaces in this registry, as well as query support. - * - * Implementers may be shared by multiple accounts, and can also implement more - * than a single interface for each account. Contracts can implement interfaces - * for themselves, but externally-owned accounts (EOA) must delegate this to a - * contract. - * - * {IERC165} interfaces can also be queried via the registry. - * - * For an in-depth explanation and source code analysis, see the EIP text. - */ -interface IERC1820Registry { - event InterfaceImplementerSet(address indexed account, bytes32 indexed interfaceHash, address indexed implementer); - - event ManagerChanged(address indexed account, address indexed newManager); - - /** - * @dev Sets `newManager` as the manager for `account`. A manager of an - * account is able to set interface implementers for it. - * - * By default, each account is its own manager. Passing a value of `0x0` in - * `newManager` will reset the manager to this initial state. - * - * Emits a {ManagerChanged} event. - * - * Requirements: - * - * - the caller must be the current manager for `account`. - */ - function setManager(address account, address newManager) external; - - /** - * @dev Returns the manager for `account`. - * - * See {setManager}. - */ - function getManager(address account) external view returns (address); - - /** - * @dev Sets the `implementer` contract as ``account``'s implementer for - * `interfaceHash`. - * - * `account` being the zero address is an alias for the caller's address. - * The zero address can also be used in `implementer` to remove an old one. - * - * See {interfaceHash} to learn how these are created. - * - * Emits an {InterfaceImplementerSet} event. - * - * Requirements: - * - * - the caller must be the current manager for `account`. - * - `interfaceHash` must not be an {IERC165} interface id (i.e. it must not - * end in 28 zeroes). - * - `implementer` must implement {IERC1820Implementer} and return true when - * queried for support, unless `implementer` is the caller. See - * {IERC1820Implementer-canImplementInterfaceForAddress}. - */ - function setInterfaceImplementer(address account, bytes32 _interfaceHash, address implementer) external; - - /** - * @dev Returns the implementer of `interfaceHash` for `account`. If no such - * implementer is registered, returns the zero address. - * - * If `interfaceHash` is an {IERC165} interface id (i.e. it ends with 28 - * zeroes), `account` will be queried for support of it. - * - * `account` being the zero address is an alias for the caller's address. - */ - function getInterfaceImplementer(address account, bytes32 _interfaceHash) external view returns (address); - - /** - * @dev Returns the interface hash for an `interfaceName`, as defined in the - * corresponding - * https://eips.ethereum.org/EIPS/eip-1820#interface-name[section of the EIP]. - */ - function interfaceHash(string calldata interfaceName) external pure returns (bytes32); - - /** - * @notice Updates the cache with whether the contract implements an ERC165 interface or not. - * @param account Address of the contract for which to update the cache. - * @param interfaceId ERC165 interface for which to update the cache. - */ - function updateERC165Cache(address account, bytes4 interfaceId) external; - - /** - * @notice Checks whether a contract implements an ERC165 interface or not. - * If the result is not cached a direct lookup on the contract address is performed. - * If the result is not cached or the cached value is out-of-date, the cache MUST be updated manually by calling - * {updateERC165Cache} with the contract address. - * @param account Address of the contract to check. - * @param interfaceId ERC165 interface to check. - * @return True if `account` implements `interfaceId`, false otherwise. - */ - function implementsERC165Interface(address account, bytes4 interfaceId) external view returns (bool); - - /** - * @notice Checks whether a contract implements an ERC165 interface or not without using or updating the cache. - * @param account Address of the contract to check. - * @param interfaceId ERC165 interface to check. - * @return True if `account` implements `interfaceId`, false otherwise. - */ - function implementsERC165InterfaceNoCache(address account, bytes4 interfaceId) external view returns (bool); -} diff --git a/contracts/utils/math/Math.sol b/contracts/utils/math/Math.sol index f8e7ca0a9..430d43a6d 100644 --- a/contracts/utils/math/Math.sol +++ b/contracts/utils/math/Math.sol @@ -13,6 +13,72 @@ library Math { Zero // Toward zero } + /** + * @dev Returns the addition of two unsigned integers, with an overflow flag. + * + * _Available since v5.0._ + */ + function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + uint256 c = a + b; + if (c < a) return (false, 0); + return (true, c); + } + } + + /** + * @dev Returns the subtraction of two unsigned integers, with an overflow flag. + * + * _Available since v5.0._ + */ + function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + if (b > a) return (false, 0); + return (true, a - b); + } + } + + /** + * @dev Returns the multiplication of two unsigned integers, with an overflow flag. + * + * _Available since v5.0._ + */ + function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + // Gas optimization: this is cheaper than requiring 'a' not being zero, but the + // benefit is lost if 'b' is also tested. + // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 + if (a == 0) return (true, 0); + uint256 c = a * b; + if (c / a != b) return (false, 0); + return (true, c); + } + } + + /** + * @dev Returns the division of two unsigned integers, with a division by zero flag. + * + * _Available since v5.0._ + */ + function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + if (b == 0) return (false, 0); + return (true, a / b); + } + } + + /** + * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag. + * + * _Available since v5.0._ + */ + function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + if (b == 0) return (false, 0); + return (true, a % b); + } + } + /** * @dev Returns the largest of two numbers. */ diff --git a/contracts/utils/math/SafeCast.sol b/contracts/utils/math/SafeCast.sol index 435a5f945..0744b3711 100644 --- a/contracts/utils/math/SafeCast.sol +++ b/contracts/utils/math/SafeCast.sol @@ -15,9 +15,6 @@ pragma solidity ^0.8.0; * * Using this library instead of the unchecked operations eliminates an entire * class of bugs, so it's recommended to use it always. - * - * Can be combined with {SafeMath} and {SignedSafeMath} to extend it to smaller types, by performing - * all math on `uint256` and `int256` and then downcasting. */ library SafeCast { /** diff --git a/contracts/utils/math/SafeMath.sol b/contracts/utils/math/SafeMath.sol deleted file mode 100644 index 2f48fb736..000000000 --- a/contracts/utils/math/SafeMath.sol +++ /dev/null @@ -1,215 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.6.0) (utils/math/SafeMath.sol) - -pragma solidity ^0.8.0; - -// CAUTION -// This version of SafeMath should only be used with Solidity 0.8 or later, -// because it relies on the compiler's built in overflow checks. - -/** - * @dev Wrappers over Solidity's arithmetic operations. - * - * NOTE: `SafeMath` is generally not needed starting with Solidity 0.8, since the compiler - * now has built in overflow checking. - */ -library SafeMath { - /** - * @dev Returns the addition of two unsigned integers, with an overflow flag. - * - * _Available since v3.4._ - */ - function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) { - unchecked { - uint256 c = a + b; - if (c < a) return (false, 0); - return (true, c); - } - } - - /** - * @dev Returns the subtraction of two unsigned integers, with an overflow flag. - * - * _Available since v3.4._ - */ - function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) { - unchecked { - if (b > a) return (false, 0); - return (true, a - b); - } - } - - /** - * @dev Returns the multiplication of two unsigned integers, with an overflow flag. - * - * _Available since v3.4._ - */ - function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) { - unchecked { - // Gas optimization: this is cheaper than requiring 'a' not being zero, but the - // benefit is lost if 'b' is also tested. - // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 - if (a == 0) return (true, 0); - uint256 c = a * b; - if (c / a != b) return (false, 0); - return (true, c); - } - } - - /** - * @dev Returns the division of two unsigned integers, with a division by zero flag. - * - * _Available since v3.4._ - */ - function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) { - unchecked { - if (b == 0) return (false, 0); - return (true, a / b); - } - } - - /** - * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag. - * - * _Available since v3.4._ - */ - function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) { - unchecked { - if (b == 0) return (false, 0); - return (true, a % b); - } - } - - /** - * @dev Returns the addition of two unsigned integers, reverting on - * overflow. - * - * Counterpart to Solidity's `+` operator. - * - * Requirements: - * - * - Addition cannot overflow. - */ - function add(uint256 a, uint256 b) internal pure returns (uint256) { - return a + b; - } - - /** - * @dev Returns the subtraction of two unsigned integers, reverting on - * overflow (when the result is negative). - * - * Counterpart to Solidity's `-` operator. - * - * Requirements: - * - * - Subtraction cannot overflow. - */ - function sub(uint256 a, uint256 b) internal pure returns (uint256) { - return a - b; - } - - /** - * @dev Returns the multiplication of two unsigned integers, reverting on - * overflow. - * - * Counterpart to Solidity's `*` operator. - * - * Requirements: - * - * - Multiplication cannot overflow. - */ - function mul(uint256 a, uint256 b) internal pure returns (uint256) { - return a * b; - } - - /** - * @dev Returns the integer division of two unsigned integers, reverting on - * division by zero. The result is rounded towards zero. - * - * Counterpart to Solidity's `/` operator. - * - * Requirements: - * - * - The divisor cannot be zero. - */ - function div(uint256 a, uint256 b) internal pure returns (uint256) { - return a / b; - } - - /** - * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), - * reverting when dividing by zero. - * - * Counterpart to Solidity's `%` operator. This function uses a `revert` - * opcode (which leaves remaining gas untouched) while Solidity uses an - * invalid opcode to revert (consuming all remaining gas). - * - * Requirements: - * - * - The divisor cannot be zero. - */ - function mod(uint256 a, uint256 b) internal pure returns (uint256) { - return a % b; - } - - /** - * @dev Returns the subtraction of two unsigned integers, reverting with custom message on - * overflow (when the result is negative). - * - * CAUTION: This function is deprecated because it requires allocating memory for the error - * message unnecessarily. For custom revert reasons use {trySub}. - * - * Counterpart to Solidity's `-` operator. - * - * Requirements: - * - * - Subtraction cannot overflow. - */ - function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { - unchecked { - require(b <= a, errorMessage); - return a - b; - } - } - - /** - * @dev Returns the integer division of two unsigned integers, reverting with custom message on - * division by zero. The result is rounded towards zero. - * - * Counterpart to Solidity's `/` operator. Note: this function uses a - * `revert` opcode (which leaves remaining gas untouched) while Solidity - * uses an invalid opcode to revert (consuming all remaining gas). - * - * Requirements: - * - * - The divisor cannot be zero. - */ - function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { - unchecked { - require(b > 0, errorMessage); - return a / b; - } - } - - /** - * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), - * reverting with custom message when dividing by zero. - * - * CAUTION: This function is deprecated because it requires allocating memory for the error - * message unnecessarily. For custom revert reasons use {tryMod}. - * - * Counterpart to Solidity's `%` operator. This function uses a `revert` - * opcode (which leaves remaining gas untouched) while Solidity uses an - * invalid opcode to revert (consuming all remaining gas). - * - * Requirements: - * - * - The divisor cannot be zero. - */ - function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { - unchecked { - require(b > 0, errorMessage); - return a % b; - } - } -} diff --git a/contracts/utils/math/SignedSafeMath.sol b/contracts/utils/math/SignedSafeMath.sol deleted file mode 100644 index 6704d4ce2..000000000 --- a/contracts/utils/math/SignedSafeMath.sol +++ /dev/null @@ -1,68 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts v4.4.1 (utils/math/SignedSafeMath.sol) - -pragma solidity ^0.8.0; - -/** - * @dev Wrappers over Solidity's arithmetic operations. - * - * NOTE: `SignedSafeMath` is no longer needed starting with Solidity 0.8. The compiler - * now has built in overflow checking. - */ -library SignedSafeMath { - /** - * @dev Returns the multiplication of two signed integers, reverting on - * overflow. - * - * Counterpart to Solidity's `*` operator. - * - * Requirements: - * - * - Multiplication cannot overflow. - */ - function mul(int256 a, int256 b) internal pure returns (int256) { - return a * b; - } - - /** - * @dev Returns the integer division of two signed integers. Reverts on - * division by zero. The result is rounded towards zero. - * - * Counterpart to Solidity's `/` operator. - * - * Requirements: - * - * - The divisor cannot be zero. - */ - function div(int256 a, int256 b) internal pure returns (int256) { - return a / b; - } - - /** - * @dev Returns the subtraction of two signed integers, reverting on - * overflow. - * - * Counterpart to Solidity's `-` operator. - * - * Requirements: - * - * - Subtraction cannot overflow. - */ - function sub(int256 a, int256 b) internal pure returns (int256) { - return a - b; - } - - /** - * @dev Returns the addition of two signed integers, reverting on - * overflow. - * - * Counterpart to Solidity's `+` operator. - * - * Requirements: - * - * - Addition cannot overflow. - */ - function add(int256 a, int256 b) internal pure returns (int256) { - return a + b; - } -} diff --git a/contracts/utils/structs/EnumerableMap.sol b/contracts/utils/structs/EnumerableMap.sol index fb21f02cf..0320ee9a0 100644 --- a/contracts/utils/structs/EnumerableMap.sol +++ b/contracts/utils/structs/EnumerableMap.sol @@ -140,22 +140,6 @@ library EnumerableMap { return value; } - /** - * @dev Same as {get}, with a custom error message when `key` is not in the map. - * - * CAUTION: This function is deprecated because it requires allocating memory for the error - * message unnecessarily. For custom revert reasons use {tryGet}. - */ - function get( - Bytes32ToBytes32Map storage map, - bytes32 key, - string memory errorMessage - ) internal view returns (bytes32) { - bytes32 value = map._values[key]; - require(value != 0 || contains(map, key), errorMessage); - return value; - } - /** * @dev Return the an array containing all the keys * @@ -242,16 +226,6 @@ library EnumerableMap { return uint256(get(map._inner, bytes32(key))); } - /** - * @dev Same as {get}, with a custom error message when `key` is not in the map. - * - * CAUTION: This function is deprecated because it requires allocating memory for the error - * message unnecessarily. For custom revert reasons use {tryGet}. - */ - function get(UintToUintMap storage map, uint256 key, string memory errorMessage) internal view returns (uint256) { - return uint256(get(map._inner, bytes32(key), errorMessage)); - } - /** * @dev Return the an array containing all the keys * @@ -346,20 +320,6 @@ library EnumerableMap { return address(uint160(uint256(get(map._inner, bytes32(key))))); } - /** - * @dev Same as {get}, with a custom error message when `key` is not in the map. - * - * CAUTION: This function is deprecated because it requires allocating memory for the error - * message unnecessarily. For custom revert reasons use {tryGet}. - */ - function get( - UintToAddressMap storage map, - uint256 key, - string memory errorMessage - ) internal view returns (address) { - return address(uint160(uint256(get(map._inner, bytes32(key), errorMessage)))); - } - /** * @dev Return the an array containing all the keys * @@ -454,20 +414,6 @@ library EnumerableMap { return uint256(get(map._inner, bytes32(uint256(uint160(key))))); } - /** - * @dev Same as {get}, with a custom error message when `key` is not in the map. - * - * CAUTION: This function is deprecated because it requires allocating memory for the error - * message unnecessarily. For custom revert reasons use {tryGet}. - */ - function get( - AddressToUintMap storage map, - address key, - string memory errorMessage - ) internal view returns (uint256) { - return uint256(get(map._inner, bytes32(uint256(uint160(key))), errorMessage)); - } - /** * @dev Return the an array containing all the keys * @@ -562,20 +508,6 @@ library EnumerableMap { return uint256(get(map._inner, key)); } - /** - * @dev Same as {get}, with a custom error message when `key` is not in the map. - * - * CAUTION: This function is deprecated because it requires allocating memory for the error - * message unnecessarily. For custom revert reasons use {tryGet}. - */ - function get( - Bytes32ToUintMap storage map, - bytes32 key, - string memory errorMessage - ) internal view returns (uint256) { - return uint256(get(map._inner, key, errorMessage)); - } - /** * @dev Return the an array containing all the keys * diff --git a/contracts/vendor/amb/IAMB.sol b/contracts/vendor/amb/IAMB.sol deleted file mode 100644 index 73a2bd24b..000000000 --- a/contracts/vendor/amb/IAMB.sol +++ /dev/null @@ -1,41 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.6.0) (vendor/amb/IAMB.sol) -pragma solidity ^0.8.0; - -interface IAMB { - event UserRequestForAffirmation(bytes32 indexed messageId, bytes encodedData); - event UserRequestForSignature(bytes32 indexed messageId, bytes encodedData); - event AffirmationCompleted( - address indexed sender, - address indexed executor, - bytes32 indexed messageId, - bool status - ); - event RelayedMessage(address indexed sender, address indexed executor, bytes32 indexed messageId, bool status); - - function messageSender() external view returns (address); - - function maxGasPerTx() external view returns (uint256); - - function transactionHash() external view returns (bytes32); - - function messageId() external view returns (bytes32); - - function messageSourceChainId() external view returns (bytes32); - - function messageCallStatus(bytes32 _messageId) external view returns (bool); - - function failedMessageDataHash(bytes32 _messageId) external view returns (bytes32); - - function failedMessageReceiver(bytes32 _messageId) external view returns (address); - - function failedMessageSender(bytes32 _messageId) external view returns (address); - - function requireToPassMessage(address _contract, bytes calldata _data, uint256 _gas) external returns (bytes32); - - function requireToConfirmMessage(address _contract, bytes calldata _data, uint256 _gas) external returns (bytes32); - - function sourceChainId() external view returns (uint256); - - function destinationChainId() external view returns (uint256); -} diff --git a/contracts/vendor/arbitrum/IArbSys.sol b/contracts/vendor/arbitrum/IArbSys.sol deleted file mode 100644 index 9b79d5c16..000000000 --- a/contracts/vendor/arbitrum/IArbSys.sol +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright 2021-2022, Offchain Labs, Inc. -// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE -// SPDX-License-Identifier: BUSL-1.1 -// OpenZeppelin Contracts (last updated v4.8.0) (vendor/arbitrum/IArbSys.sol) - -pragma solidity >=0.4.21 <0.9.0; - -/** - * @title System level functionality - * @notice For use by contracts to interact with core L2-specific functionality. - * Precompiled contract that exists in every Arbitrum chain at address(100), 0x0000000000000000000000000000000000000064. - */ -interface IArbSys { - /** - * @notice Get Arbitrum block number (distinct from L1 block number; Arbitrum genesis block has block number 0) - * @return block number as int - */ - function arbBlockNumber() external view returns (uint256); - - /** - * @notice Get Arbitrum block hash (reverts unless currentBlockNum-256 <= arbBlockNum < currentBlockNum) - * @return block hash - */ - function arbBlockHash(uint256 arbBlockNum) external view returns (bytes32); - - /** - * @notice Gets the rollup's unique chain identifier - * @return Chain identifier as int - */ - function arbChainID() external view returns (uint256); - - /** - * @notice Get internal version number identifying an ArbOS build - * @return version number as int - */ - function arbOSVersion() external view returns (uint256); - - /** - * @notice Returns 0 since Nitro has no concept of storage gas - * @return uint 0 - */ - function getStorageGasAvailable() external view returns (uint256); - - /** - * @notice (deprecated) check if current call is top level (meaning it was triggered by an EoA or a L1 contract) - * @dev this call has been deprecated and may be removed in a future release - * @return true if current execution frame is not a call by another L2 contract - */ - function isTopLevelCall() external view returns (bool); - - /** - * @notice map L1 sender contract address to its L2 alias - * @param sender sender address - * @param unused argument no longer used - * @return aliased sender address - */ - function mapL1SenderContractAddressToL2Alias(address sender, address unused) external pure returns (address); - - /** - * @notice check if the caller (of this caller of this) is an aliased L1 contract address - * @return true iff the caller's address is an alias for an L1 contract address - */ - function wasMyCallersAddressAliased() external view returns (bool); - - /** - * @notice return the address of the caller (of this caller of this), without applying L1 contract address aliasing - * @return address of the caller's caller, without applying L1 contract address aliasing - */ - function myCallersAddressWithoutAliasing() external view returns (address); - - /** - * @notice Send given amount of Eth to dest from sender. - * This is a convenience function, which is equivalent to calling sendTxToL1 with empty data. - * @param destination recipient address on L1 - * @return unique identifier for this L2-to-L1 transaction. - */ - function withdrawEth(address destination) external payable returns (uint256); - - /** - * @notice Send a transaction to L1 - * @dev it is not possible to execute on the L1 any L2-to-L1 transaction which contains data - * to a contract address without any code (as enforced by the Bridge contract). - * @param destination recipient address on L1 - * @param data (optional) calldata for L1 contract call - * @return a unique identifier for this L2-to-L1 transaction. - */ - function sendTxToL1(address destination, bytes calldata data) external payable returns (uint256); - - /** - * @notice Get send Merkle tree state - * @return size number of sends in the history - * @return root root hash of the send history - * @return partials hashes of partial subtrees in the send history tree - */ - function sendMerkleTreeState() external view returns (uint256 size, bytes32 root, bytes32[] memory partials); - - /** - * @notice creates a send txn from L2 to L1 - * @param position = (level << 192) + leaf = (0 << 192) + leaf = leaf - */ - event L2ToL1Tx( - address caller, - address indexed destination, - uint256 indexed hash, - uint256 indexed position, - uint256 arbBlockNum, - uint256 ethBlockNum, - uint256 timestamp, - uint256 callvalue, - bytes data - ); - - /// @dev DEPRECATED in favour of the new L2ToL1Tx event above after the nitro upgrade - event L2ToL1Transaction( - address caller, - address indexed destination, - uint256 indexed uniqueId, - uint256 indexed batchNumber, - uint256 indexInBatch, - uint256 arbBlockNum, - uint256 ethBlockNum, - uint256 timestamp, - uint256 callvalue, - bytes data - ); - - /** - * @notice logs a merkle branch for proof synthesis - * @param reserved an index meant only to align the 4th index with L2ToL1Transaction's 4th event - * @param hash the merkle hash - * @param position = (level << 192) + leaf - */ - event SendMerkleUpdate(uint256 indexed reserved, bytes32 indexed hash, uint256 indexed position); -} diff --git a/contracts/vendor/arbitrum/IBridge.sol b/contracts/vendor/arbitrum/IBridge.sol deleted file mode 100644 index e71bedce0..000000000 --- a/contracts/vendor/arbitrum/IBridge.sol +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright 2021-2022, Offchain Labs, Inc. -// For license information, see https://github.com/nitro/blob/master/LICENSE -// SPDX-License-Identifier: BUSL-1.1 -// OpenZeppelin Contracts (last updated v4.8.0) (vendor/arbitrum/IBridge.sol) - -// solhint-disable-next-line compiler-version -pragma solidity >=0.6.9 <0.9.0; - -interface IBridge { - event MessageDelivered( - uint256 indexed messageIndex, - bytes32 indexed beforeInboxAcc, - address inbox, - uint8 kind, - address sender, - bytes32 messageDataHash, - uint256 baseFeeL1, - uint64 timestamp - ); - - event BridgeCallTriggered(address indexed outbox, address indexed to, uint256 value, bytes data); - - event InboxToggle(address indexed inbox, bool enabled); - - event OutboxToggle(address indexed outbox, bool enabled); - - event SequencerInboxUpdated(address newSequencerInbox); - - function allowedDelayedInboxList(uint256) external returns (address); - - function allowedOutboxList(uint256) external returns (address); - - /// @dev Accumulator for delayed inbox messages; tail represents hash of the current state; each element represents the inclusion of a new message. - function delayedInboxAccs(uint256) external view returns (bytes32); - - /// @dev Accumulator for sequencer inbox messages; tail represents hash of the current state; each element represents the inclusion of a new message. - function sequencerInboxAccs(uint256) external view returns (bytes32); - - // OpenZeppelin: changed return type from IOwnable - function rollup() external view returns (address); - - function sequencerInbox() external view returns (address); - - function activeOutbox() external view returns (address); - - function allowedDelayedInboxes(address inbox) external view returns (bool); - - function allowedOutboxes(address outbox) external view returns (bool); - - function sequencerReportedSubMessageCount() external view returns (uint256); - - /** - * @dev Enqueue a message in the delayed inbox accumulator. - * These messages are later sequenced in the SequencerInbox, either - * by the sequencer as part of a normal batch, or by force inclusion. - */ - function enqueueDelayedMessage( - uint8 kind, - address sender, - bytes32 messageDataHash - ) external payable returns (uint256); - - function executeCall( - address to, - uint256 value, - bytes calldata data - ) external returns (bool success, bytes memory returnData); - - function delayedMessageCount() external view returns (uint256); - - function sequencerMessageCount() external view returns (uint256); - - // ---------- onlySequencerInbox functions ---------- - - function enqueueSequencerMessage( - bytes32 dataHash, - uint256 afterDelayedMessagesRead, - uint256 prevMessageCount, - uint256 newMessageCount - ) external returns (uint256 seqMessageIndex, bytes32 beforeAcc, bytes32 delayedAcc, bytes32 acc); - - /** - * @dev Allows the sequencer inbox to submit a delayed message of the batchPostingReport type - * This is done through a separate function entrypoint instead of allowing the sequencer inbox - * to call `enqueueDelayedMessage` to avoid the gas overhead of an extra SLOAD in either - * every delayed inbox or every sequencer inbox call. - */ - function submitBatchSpendingReport(address batchPoster, bytes32 dataHash) external returns (uint256 msgNum); - - // ---------- onlyRollupOrOwner functions ---------- - - function setSequencerInbox(address _sequencerInbox) external; - - function setDelayedInbox(address inbox, bool enabled) external; - - function setOutbox(address inbox, bool enabled) external; - - // ---------- initializer ---------- - - // OpenZeppelin: changed rollup_ type from IOwnable - function initialize(address rollup_) external; -} diff --git a/contracts/vendor/arbitrum/IDelayedMessageProvider.sol b/contracts/vendor/arbitrum/IDelayedMessageProvider.sol deleted file mode 100644 index 914c25fb7..000000000 --- a/contracts/vendor/arbitrum/IDelayedMessageProvider.sol +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2021-2022, Offchain Labs, Inc. -// For license information, see https://github.com/nitro/blob/master/LICENSE -// SPDX-License-Identifier: BUSL-1.1 -// OpenZeppelin Contracts (last updated v4.8.0) (vendor/arbitrum/IDelayedMessageProvider.sol) - -// solhint-disable-next-line compiler-version -pragma solidity >=0.6.9 <0.9.0; - -interface IDelayedMessageProvider { - /// @dev event emitted when a inbox message is added to the Bridge's delayed accumulator - event InboxMessageDelivered(uint256 indexed messageNum, bytes data); - - /// @dev event emitted when a inbox message is added to the Bridge's delayed accumulator - /// same as InboxMessageDelivered but the batch data is available in tx.input - event InboxMessageDeliveredFromOrigin(uint256 indexed messageNum); -} diff --git a/contracts/vendor/arbitrum/IInbox.sol b/contracts/vendor/arbitrum/IInbox.sol deleted file mode 100644 index a8b67511c..000000000 --- a/contracts/vendor/arbitrum/IInbox.sol +++ /dev/null @@ -1,152 +0,0 @@ -// Copyright 2021-2022, Offchain Labs, Inc. -// For license information, see https://github.com/nitro/blob/master/LICENSE -// SPDX-License-Identifier: BUSL-1.1 -// OpenZeppelin Contracts (last updated v4.8.0) (vendor/arbitrum/IInbox.sol) - -// solhint-disable-next-line compiler-version -pragma solidity >=0.6.9 <0.9.0; - -import "./IBridge.sol"; -import "./IDelayedMessageProvider.sol"; - -interface IInbox is IDelayedMessageProvider { - function bridge() external view returns (IBridge); - - // OpenZeppelin: changed return type from ISequencerInbox - function sequencerInbox() external view returns (address); - - /** - * @notice Send a generic L2 message to the chain - * @dev This method is an optimization to avoid having to emit the entirety of the messageData in a log. Instead validators are expected to be able to parse the data from the transaction's input - * @param messageData Data of the message being sent - */ - function sendL2MessageFromOrigin(bytes calldata messageData) external returns (uint256); - - /** - * @notice Send a generic L2 message to the chain - * @dev This method can be used to send any type of message that doesn't require L1 validation - * @param messageData Data of the message being sent - */ - function sendL2Message(bytes calldata messageData) external returns (uint256); - - function sendL1FundedUnsignedTransaction( - uint256 gasLimit, - uint256 maxFeePerGas, - uint256 nonce, - address to, - bytes calldata data - ) external payable returns (uint256); - - function sendL1FundedContractTransaction( - uint256 gasLimit, - uint256 maxFeePerGas, - address to, - bytes calldata data - ) external payable returns (uint256); - - function sendUnsignedTransaction( - uint256 gasLimit, - uint256 maxFeePerGas, - uint256 nonce, - address to, - uint256 value, - bytes calldata data - ) external returns (uint256); - - function sendContractTransaction( - uint256 gasLimit, - uint256 maxFeePerGas, - address to, - uint256 value, - bytes calldata data - ) external returns (uint256); - - /** - * @notice Get the L1 fee for submitting a retryable - * @dev This fee can be paid by funds already in the L2 aliased address or by the current message value - * @dev This formula may change in the future, to future proof your code query this method instead of inlining!! - * @param dataLength The length of the retryable's calldata, in bytes - * @param baseFee The block basefee when the retryable is included in the chain, if 0 current block.basefee will be used - */ - function calculateRetryableSubmissionFee(uint256 dataLength, uint256 baseFee) external view returns (uint256); - - /** - * @notice Deposit eth from L1 to L2 to address of the sender if sender is an EOA, and to its aliased address if the sender is a contract - * @dev This does not trigger the fallback function when receiving in the L2 side. - * Look into retryable tickets if you are interested in this functionality. - * @dev This function should not be called inside contract constructors - */ - function depositEth() external payable returns (uint256); - - /** - * @notice Put a message in the L2 inbox that can be reexecuted for some fixed amount of time if it reverts - * @dev all msg.value will deposited to callValueRefundAddress on L2 - * @dev Gas limit and maxFeePerGas should not be set to 1 as that is used to trigger the RetryableData error - * @param to destination L2 contract address - * @param l2CallValue call value for retryable L2 message - * @param maxSubmissionCost Max gas deducted from user's L2 balance to cover base submission fee - * @param excessFeeRefundAddress gasLimit x maxFeePerGas - execution cost gets credited here on L2 balance - * @param callValueRefundAddress l2Callvalue gets credited here on L2 if retryable txn times out or gets cancelled - * @param gasLimit Max gas deducted from user's L2 balance to cover L2 execution. Should not be set to 1 (magic value used to trigger the RetryableData error) - * @param maxFeePerGas price bid for L2 execution. Should not be set to 1 (magic value used to trigger the RetryableData error) - * @param data ABI encoded data of L2 message - * @return unique message number of the retryable transaction - */ - function createRetryableTicket( - address to, - uint256 l2CallValue, - uint256 maxSubmissionCost, - address excessFeeRefundAddress, - address callValueRefundAddress, - uint256 gasLimit, - uint256 maxFeePerGas, - bytes calldata data - ) external payable returns (uint256); - - /** - * @notice Put a message in the L2 inbox that can be reexecuted for some fixed amount of time if it reverts - * @dev Same as createRetryableTicket, but does not guarantee that submission will succeed by requiring the needed funds - * come from the deposit alone, rather than falling back on the user's L2 balance - * @dev Advanced usage only (does not rewrite aliases for excessFeeRefundAddress and callValueRefundAddress). - * createRetryableTicket method is the recommended standard. - * @dev Gas limit and maxFeePerGas should not be set to 1 as that is used to trigger the RetryableData error - * @param to destination L2 contract address - * @param l2CallValue call value for retryable L2 message - * @param maxSubmissionCost Max gas deducted from user's L2 balance to cover base submission fee - * @param excessFeeRefundAddress gasLimit x maxFeePerGas - execution cost gets credited here on L2 balance - * @param callValueRefundAddress l2Callvalue gets credited here on L2 if retryable txn times out or gets cancelled - * @param gasLimit Max gas deducted from user's L2 balance to cover L2 execution. Should not be set to 1 (magic value used to trigger the RetryableData error) - * @param maxFeePerGas price bid for L2 execution. Should not be set to 1 (magic value used to trigger the RetryableData error) - * @param data ABI encoded data of L2 message - * @return unique message number of the retryable transaction - */ - function unsafeCreateRetryableTicket( - address to, - uint256 l2CallValue, - uint256 maxSubmissionCost, - address excessFeeRefundAddress, - address callValueRefundAddress, - uint256 gasLimit, - uint256 maxFeePerGas, - bytes calldata data - ) external payable returns (uint256); - - // ---------- onlyRollupOrOwner functions ---------- - - /// @notice pauses all inbox functionality - function pause() external; - - /// @notice unpauses all inbox functionality - function unpause() external; - - // ---------- initializer ---------- - - /** - * @dev function to be called one time during the inbox upgrade process - * this is used to fix the storage slots - */ - function postUpgradeInit(IBridge _bridge) external; - - // OpenZeppelin: changed _sequencerInbox type from ISequencerInbox - function initialize(IBridge _bridge, address _sequencerInbox) external; -} diff --git a/contracts/vendor/arbitrum/IOutbox.sol b/contracts/vendor/arbitrum/IOutbox.sol deleted file mode 100644 index 22fa58f40..000000000 --- a/contracts/vendor/arbitrum/IOutbox.sol +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright 2021-2022, Offchain Labs, Inc. -// For license information, see https://github.com/nitro/blob/master/LICENSE -// SPDX-License-Identifier: BUSL-1.1 -// OpenZeppelin Contracts (last updated v4.8.0) (vendor/arbitrum/IOutbox.sol) - -// solhint-disable-next-line compiler-version -pragma solidity >=0.6.9 <0.9.0; - -import "./IBridge.sol"; - -interface IOutbox { - event SendRootUpdated(bytes32 indexed blockHash, bytes32 indexed outputRoot); - event OutBoxTransactionExecuted( - address indexed to, - address indexed l2Sender, - uint256 indexed zero, - uint256 transactionIndex - ); - - function rollup() external view returns (address); // the rollup contract - - function bridge() external view returns (IBridge); // the bridge contract - - function spent(uint256) external view returns (bytes32); // packed spent bitmap - - function roots(bytes32) external view returns (bytes32); // maps root hashes => L2 block hash - - // solhint-disable-next-line func-name-mixedcase - function OUTBOX_VERSION() external view returns (uint128); // the outbox version - - function updateSendRoot(bytes32 sendRoot, bytes32 l2BlockHash) external; - - /// @notice When l2ToL1Sender returns a nonzero address, the message was originated by an L2 account - /// When the return value is zero, that means this is a system message - /// @dev the l2ToL1Sender behaves as the tx.origin, the msg.sender should be validated to protect against reentrancies - function l2ToL1Sender() external view returns (address); - - /// @return l2Block return L2 block when the L2 tx was initiated or 0 if no L2 to L1 transaction is active - function l2ToL1Block() external view returns (uint256); - - /// @return l1Block return L1 block when the L2 tx was initiated or 0 if no L2 to L1 transaction is active - function l2ToL1EthBlock() external view returns (uint256); - - /// @return timestamp return L2 timestamp when the L2 tx was initiated or 0 if no L2 to L1 transaction is active - function l2ToL1Timestamp() external view returns (uint256); - - /// @return outputId returns the unique output identifier of the L2 to L1 tx or 0 if no L2 to L1 transaction is active - function l2ToL1OutputId() external view returns (bytes32); - - /** - * @notice Executes a messages in an Outbox entry. - * @dev Reverts if dispute period hasn't expired, since the outbox entry - * is only created once the rollup confirms the respective assertion. - * @dev it is not possible to execute any L2-to-L1 transaction which contains data - * to a contract address without any code (as enforced by the Bridge contract). - * @param proof Merkle proof of message inclusion in send root - * @param index Merkle path to message - * @param l2Sender sender if original message (i.e., caller of ArbSys.sendTxToL1) - * @param to destination address for L1 contract call - * @param l2Block l2 block number at which sendTxToL1 call was made - * @param l1Block l1 block number at which sendTxToL1 call was made - * @param l2Timestamp l2 Timestamp at which sendTxToL1 call was made - * @param value wei in L1 message - * @param data abi-encoded L1 message data - */ - function executeTransaction( - bytes32[] calldata proof, - uint256 index, - address l2Sender, - address to, - uint256 l2Block, - uint256 l1Block, - uint256 l2Timestamp, - uint256 value, - bytes calldata data - ) external; - - /** - * @dev function used to simulate the result of a particular function call from the outbox - * it is useful for things such as gas estimates. This function includes all costs except for - * proof validation (which can be considered offchain as a somewhat of a fixed cost - it's - * not really a fixed cost, but can be treated as so with a fixed overhead for gas estimation). - * We can't include the cost of proof validation since this is intended to be used to simulate txs - * that are included in yet-to-be confirmed merkle roots. The simulation entrypoint could instead pretend - * to confirm a pending merkle root, but that would be less practical for integrating with tooling. - * It is only possible to trigger it when the msg sender is address zero, which should be impossible - * unless under simulation in an eth_call or eth_estimateGas - */ - function executeTransactionSimulation( - uint256 index, - address l2Sender, - address to, - uint256 l2Block, - uint256 l1Block, - uint256 l2Timestamp, - uint256 value, - bytes calldata data - ) external; - - /** - * @param index Merkle path to message - * @return true if the message has been spent - */ - function isSpent(uint256 index) external view returns (bool); - - function calculateItemHash( - address l2Sender, - address to, - uint256 l2Block, - uint256 l1Block, - uint256 l2Timestamp, - uint256 value, - bytes calldata data - ) external pure returns (bytes32); - - function calculateMerkleRoot(bytes32[] memory proof, uint256 path, bytes32 item) external pure returns (bytes32); -} diff --git a/contracts/vendor/optimism/ICrossDomainMessenger.sol b/contracts/vendor/optimism/ICrossDomainMessenger.sol deleted file mode 100644 index cc01a48ab..000000000 --- a/contracts/vendor/optimism/ICrossDomainMessenger.sol +++ /dev/null @@ -1,34 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.6.0) (vendor/optimism/ICrossDomainMessenger.sol) -pragma solidity >0.5.0 <0.9.0; - -/** - * @title ICrossDomainMessenger - */ -interface ICrossDomainMessenger { - /********** - * Events * - **********/ - - event SentMessage(address indexed target, address sender, bytes message, uint256 messageNonce, uint256 gasLimit); - event RelayedMessage(bytes32 indexed msgHash); - event FailedRelayedMessage(bytes32 indexed msgHash); - - /************* - * Variables * - *************/ - - function xDomainMessageSender() external view returns (address); - - /******************** - * Public Functions * - ********************/ - - /** - * Sends a cross domain message to the target messenger. - * @param _target Target contract address. - * @param _message Message to send to the target. - * @param _gasLimit Gas limit for the provided message. - */ - function sendMessage(address _target, bytes calldata _message, uint32 _gasLimit) external; -} diff --git a/contracts/vendor/optimism/LICENSE b/contracts/vendor/optimism/LICENSE deleted file mode 100644 index 6a7da5218..000000000 --- a/contracts/vendor/optimism/LICENSE +++ /dev/null @@ -1,22 +0,0 @@ -(The MIT License) - -Copyright 2020-2021 Optimism - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/contracts/vendor/polygon/IFxMessageProcessor.sol b/contracts/vendor/polygon/IFxMessageProcessor.sol deleted file mode 100644 index be73e6f53..000000000 --- a/contracts/vendor/polygon/IFxMessageProcessor.sol +++ /dev/null @@ -1,7 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.6.0) (vendor/polygon/IFxMessageProcessor.sol) -pragma solidity ^0.8.0; - -interface IFxMessageProcessor { - function processMessageFromRoot(uint256 stateId, address rootMessageSender, bytes calldata data) external; -} diff --git a/docs/modules/ROOT/nav.adoc b/docs/modules/ROOT/nav.adoc index 071045e68..fc38e8953 100644 --- a/docs/modules/ROOT/nav.adoc +++ b/docs/modules/ROOT/nav.adoc @@ -11,14 +11,11 @@ ** xref:erc20.adoc[ERC20] *** xref:erc20-supply.adoc[Creating Supply] ** xref:erc721.adoc[ERC721] -** xref:erc777.adoc[ERC777] ** xref:erc1155.adoc[ERC1155] ** xref:erc4626.adoc[ERC4626] * xref:governance.adoc[Governance] -* xref:crosschain.adoc[Crosschain] - * xref:utilities.adoc[Utilities] * xref:subgraphs::index.adoc[Subgraphs] diff --git a/docs/modules/ROOT/pages/crosschain.adoc b/docs/modules/ROOT/pages/crosschain.adoc deleted file mode 100644 index cbe24df77..000000000 --- a/docs/modules/ROOT/pages/crosschain.adoc +++ /dev/null @@ -1,210 +0,0 @@ -= Adding cross-chain support to contracts - -If your contract is targeting to be used in the context of multichain operations, you may need specific tools to identify and process these cross-chain operations. - -OpenZeppelin provides the xref:api:crosschain.adoc#CrossChainEnabled[`CrossChainEnabled`] abstract contract, that includes dedicated internal functions. - -In this guide, we will go through an example use case: _how to build an upgradeable & mintable ERC20 token controlled by a governor present on a foreign chain_. - -== Starting point, our ERC20 contract - -Let's start with a small ERC20 contract, that we bootstrapped using the https://wizard.openzeppelin.com/[OpenZeppelin Contracts Wizard], and extended with an owner that has the ability to mint. Note that for demonstration purposes we have not used the built-in `Ownable` contract. - -[source,solidity] ----- -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; - -import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; -import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; -import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; - -contract MyToken is Initializable, ERC20Upgradeable, UUPSUpgradeable { - address public owner; - - modifier onlyOwner() { - require(owner == _msgSender(), "Not authorized"); - _; - } - - /// @custom:oz-upgrades-unsafe-allow constructor - constructor() initializer {} - - function initialize(address initialOwner) initializer public { - __ERC20_init("MyToken", "MTK"); - __UUPSUpgradeable_init(); - - owner = initialOwner; - } - - function mint(address to, uint256 amount) public onlyOwner { - _mint(to, amount); - } - - function _authorizeUpgrade(address newImplementation) internal override onlyOwner { - } -} ----- - -This token is mintable and upgradeable by the owner of the contract. - -== Preparing our contract for cross-chain operations. - -Let's now imagine that this contract is going to live on one chain, but we want the minting and the upgrading to be performed by a xref:governance.adoc[`governor`] contract on another chain. - -For example, we could have our token on xDai, with our governor on mainnet, or we could have our token on mainnet, with our governor on optimism - -In order to do that, we will start by adding xref:api:crosschain.adoc#CrossChainEnabled[`CrossChainEnabled`] to our contract. You will notice that the contract is now abstract. This is because `CrossChainEnabled` is an abstract contract: it is not tied to any particular chain and it deals with cross-chain interactions in an abstract way. This is what enables us to easily reuse the code for different chains. We will specialize it later by inheriting from a chain-specific implementation of the abstraction. - -```diff - import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; -+import "@openzeppelin/contracts-upgradeable/crosschain/CrossChainEnabled.sol"; - --contract MyToken is Initializable, ERC20Upgradeable, UUPSUpgradeable { -+abstract contract MyTokenCrossChain is Initializable, ERC20Upgradeable, UUPSUpgradeable, CrossChainEnabled { -``` - -Once that is done, we can use the `onlyCrossChainSender` modifier, provided by `CrossChainEnabled` in order to protect the minting and upgrading operations. - -```diff -- function mint(address to, uint256 amount) public onlyOwner { -+ function mint(address to, uint256 amount) public onlyCrossChainSender(owner) { - -- function _authorizeUpgrade(address newImplementation) internal override onlyOwner { -+ function _authorizeUpgrade(address newImplementation) internal override onlyCrossChainSender(owner) { -``` - -This change will effectively restrict the mint and upgrade operations to the `owner` on the remote chain. - -== Specializing for a specific chain - -Once the abstract cross-chain version of our token is ready we can easily specialize it for the chain we want, or more precisely for the bridge system that we want to rely on. - -This is done using one of the many `CrossChainEnabled` implementations. - -For example, if our token is on xDai, and our governor on mainnet, we can use the https://docs.tokenbridge.net/amb-bridge/about-amb-bridge[AMB] bridge available on xDai at https://blockscout.com/xdai/mainnet/address/0x75Df5AF045d91108662D8080fD1FEFAd6aA0bb59[0x75Df5AF045d91108662D8080fD1FEFAd6aA0bb59] - -[source,solidity] ----- -[...] - -import "@openzeppelin/contracts-upgradeable/crosschain/amb/CrossChainEnabledAMB.sol"; - -contract MyTokenXDAI is - MyTokenCrossChain, - CrossChainEnabledAMB(0x75Df5AF045d91108662D8080fD1FEFAd6aA0bb59) -{} ----- - -If the token is on Ethereum mainnet, and our governor on Optimism, we use the Optimism https://community.optimism.io/docs/protocol/protocol-2.0/#l1crossdomainmessenger[CrossDomainMessenger] available on mainnet at https://etherscan.io/address/0x25ace71c97B33Cc4729CF772ae268934F7ab5fA1[0x25ace71c97B33Cc4729CF772ae268934F7ab5fA1]. - -[source,solidity] ----- -[...] - -import "@openzeppelin/contracts-upgradeable/crosschain/optimismCrossChainEnabledOptimism.sol"; - -contract MyTokenOptimism is - MyTokenCrossChain, - CrossChainEnabledOptimism(0x25ace71c97B33Cc4729CF772ae268934F7ab5fA1) -{} ----- - -== Mixing cross domain addresses is dangerous - -When designing a contract with cross-chain support, it is essential to understand possible fallbacks and the security assumption that are being made. - -In this guide, we are particularly focusing on restricting access to a specific caller. This is usually done (as shown above) using `msg.sender` or `_msgSender()`. However, when going cross-chain, it is not just that simple. Even without considering possible bridge issues, it is important to keep in mind that the same address can correspond to very different entities when considering a multi-chain space. EOA wallets can only execute operations if the wallet's private-key signs the transaction. To our knowledge this is the case in all EVM chains, so a cross-chain message coming from such a wallet is arguably equivalent to a non-cross-chain message by the same wallet. The situation is however very different for smart contracts. - -Due to the way smart contract addresses are computed, and the fact that smart contracts on different chains live independent lives, you could have two very different contracts live at the same address on different chains. You could imagine two multisig wallets with different signers using the same address on different chains. You could also see a very basic smart wallet live on one chain at the same address as a full-fledged governor on another chain. Therefore, you should be careful that whenever you give permissions to a specific address, you control with chain this address can act from. - -== Going further with access control - -In the previous example, we have both an `onlyOwner()` modifier and the `onlyCrossChainSender(owner)` mechanism. We didn't use the xref:access-control.adoc#ownership-and-ownable[`Ownable`] pattern because the ownership transfer mechanism in includes is not designed to work with the owner being a cross-chain entity. Unlike xref:access-control.adoc#ownership-and-ownable[`Ownable`], xref:access-control.adoc#role-based-access-control[`AccessControl`] is more effective at capturing the nuances and can effectively be used to build cross-chain-aware contracts. - -Using xref:api:access.adoc#AccessControlCrossChain[`AccessControlCrossChain`] includes both the xref:api:access.adoc#AccessControl[`AccessControl`] core and the xref:api:crosschain.adoc#CrossChainEnabled[`CrossChainEnabled`] abstraction. It also includes some binding to make role management compatible with cross-chain operations. - -In the case of the `mint` function, the caller must have the `MINTER_ROLE` when the call originates from the same chain. If the caller is on a remote chain, then the caller should not have the `MINTER_ROLE`, but the "aliased" version (`MINTER_ROLE ^ CROSSCHAIN_ALIAS`). This mitigates the danger described in the previous section by strictly separating local accounts from remote accounts from a different chain. See the xref:api:access.adoc#AccessControlCrossChain[`AccessControlCrossChain`] documentation for more details. - - -```diff - import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; - import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; -+import "@openzeppelin/contracts-upgradeable/access/AccessControlCrossChainUpgradeable.sol"; - --abstract contract MyTokenCrossChain is Initializable, ERC20Upgradeable, UUPSUpgradeable, CrossChainEnabled { -+abstract contract MyTokenCrossChain is Initializable, ERC20Upgradeable, UUPSUpgradeable, AccessControlCrossChainUpgradeable { - -- address public owner; -- modifier onlyOwner() { -- require(owner == _msgSender(), "Not authorized"); -- _; -- } - -+ bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); -+ bytes32 public constant UPGRADER_ROLE = keccak256("UPGRADER_ROLE"); - - function initialize(address initialOwner) initializer public { - __ERC20_init("MyToken", "MTK"); - __UUPSUpgradeable_init(); -+ __AccessControl_init(); -+ _grantRole(_crossChainRoleAlias(DEFAULT_ADMIN_ROLE), initialOwner); // initialOwner is on a remote chain -- owner = initialOwner; - } - -- function mint(address to, uint256 amount) public onlyCrossChainSender(owner) { -+ function mint(address to, uint256 amount) public onlyRole(MINTER_ROLE) { - -- function _authorizeUpgrade(address newImplementation) internal override onlyCrossChainSender(owner) { -+ function _authorizeUpgrade(address newImplementation) internal override onlyRole(UPGRADER_ROLE) { -``` - -This results in the following, final, code: - -[source,solidity] ----- -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; - -import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; -import "@openzeppelin/contracts-upgradeable/access/AccessControlCrossChainUpgradeable.sol"; -import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; -import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; - -abstract contract MyTokenCrossChain is Initializable, ERC20Upgradeable, AccessControlCrossChainUpgradeable, UUPSUpgradeable { - bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); - bytes32 public constant UPGRADER_ROLE = keccak256("UPGRADER_ROLE"); - - /// @custom:oz-upgrades-unsafe-allow constructor - constructor() initializer {} - - function initialize(address initialOwner) initializer public { - __ERC20_init("MyToken", "MTK"); - __AccessControl_init(); - __UUPSUpgradeable_init(); - - _grantRole(_crossChainRoleAlias(DEFAULT_ADMIN_ROLE), initialOwner); // initialOwner is on a remote chain - } - - function mint(address to, uint256 amount) public onlyRole(MINTER_ROLE) { - _mint(to, amount); - } - - function _authorizeUpgrade(address newImplementation) internal onlyRole(UPGRADER_ROLE) override { - } -} - -import "@openzeppelin/contracts-upgradeable/crosschain/amb/CrossChainEnabledAMB.sol"; - -contract MyTokenXDAI is - MyTokenCrossChain, - CrossChainEnabledAMB(0x75Df5AF045d91108662D8080fD1FEFAd6aA0bb59) -{} - -import "@openzeppelin/contracts-upgradeable/crosschain/optimismCrossChainEnabledOptimism.sol"; - -contract MyTokenOptimism is - MyTokenCrossChain, - CrossChainEnabledOptimism(0x25ace71c97B33Cc4729CF772ae268934F7ab5fA1) -{} ----- diff --git a/docs/modules/ROOT/pages/erc1155.adoc b/docs/modules/ROOT/pages/erc1155.adoc index a01542c5a..d7976ca1f 100644 --- a/docs/modules/ROOT/pages/erc1155.adoc +++ b/docs/modules/ROOT/pages/erc1155.adoc @@ -2,7 +2,7 @@ ERC1155 is a novel token standard that aims to take the best from previous standards to create a xref:tokens.adoc#different-kinds-of-tokens[*fungibility-agnostic*] and *gas-efficient* xref:tokens.adoc#but_first_coffee_a_primer_on_token_contracts[token contract]. -TIP: ERC1155 draws ideas from all of xref:erc20.adoc[ERC20], xref:erc721.adoc[ERC721], and xref:erc777.adoc[ERC777]. If you're unfamiliar with those standards, head to their guides before moving on. +TIP: ERC1155 draws ideas from all of xref:erc20.adoc[ERC20], xref:erc721.adoc[ERC721], and https://eips.ethereum.org/EIPS/eip-777[ERC777]. If you're unfamiliar with those standards, head to their guides before moving on. [[multi-token-standard]] == Multi Token Standard @@ -22,9 +22,9 @@ In the spirit of the standard, we've also included batch operations in the non-s == Constructing an ERC1155 Token Contract -We'll use ERC1155 to track multiple items in our game, which will each have their own unique attributes. We mint all items to the deployer of the contract, which we can later transfer to players. Players are free to keep their tokens or trade them with other people as they see fit, as they would any other asset on the blockchain! +We'll use ERC1155 to track multiple items in our game, which will each have their own unique attributes. We mint all items to the deployer of the contract, which we can later transfer to players. Players are free to keep their tokens or trade them with other people as they see fit, as they would any other asset on the blockchain! -For simplicity, we will mint all items in the constructor, but you could add minting functionality to the contract to mint on demand to players. +For simplicity, we will mint all items in the constructor, but you could add minting functionality to the contract to mint on demand to players. TIP: For an overview of minting mechanisms, check out xref:erc20-supply.adoc[Creating ERC20 Supply]. @@ -112,7 +112,7 @@ The JSON document for token ID 2 might look something like: For more information about the metadata JSON Schema, check out the https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1155.md#erc-1155-metadata-uri-json-schema[ERC-1155 Metadata URI JSON Schema]. -NOTE: You'll notice that the item's information is included in the metadata, but that information isn't on-chain! So a game developer could change the underlying metadata, changing the rules of the game! +NOTE: You'll notice that the item's information is included in the metadata, but that information isn't on-chain! So a game developer could change the underlying metadata, changing the rules of the game! TIP: If you'd like to put all item information on-chain, you can extend ERC721 to do so (though it will be rather costly) by providing a xref:utilities.adoc#base64[`Base64`] Data URI with the JSON schema encoded. You could also leverage IPFS to store the URI information, but these techniques are out of the scope of this overview guide diff --git a/docs/modules/ROOT/pages/erc777.adoc b/docs/modules/ROOT/pages/erc777.adoc deleted file mode 100644 index 4a0af16c3..000000000 --- a/docs/modules/ROOT/pages/erc777.adoc +++ /dev/null @@ -1,75 +0,0 @@ -= ERC777 - -CAUTION: As of v4.9, OpenZeppelin's implementation of ERC-777 is deprecated and will be removed in the next major release. - -Like xref:erc20.adoc[ERC20], ERC777 is a standard for xref:tokens.adoc#different-kinds-of-tokens[_fungible_ tokens], and is focused around allowing more complex interactions when trading tokens. More generally, it brings tokens and Ether closer together by providing the equivalent of a `msg.value` field, but for tokens. - -The standard also brings multiple quality-of-life improvements, such as getting rid of the confusion around `decimals`, minting and burning with proper events, among others, but its killer feature is *receive hooks*. A hook is simply a function in a contract that is called when tokens are sent to it, meaning *accounts and contracts can react to receiving tokens*. - -This enables a lot of interesting use cases, including atomic purchases using tokens (no need to do `approve` and `transferFrom` in two separate transactions), rejecting reception of tokens (by reverting on the hook call), redirecting the received tokens to other addresses (similarly to how xref:api:payment#PaymentSplitter[`PaymentSplitter`] does it), among many others. - -Furthermore, since contracts are required to implement these hooks in order to receive tokens, _no tokens can get stuck in a contract that is unaware of the ERC777 protocol_, as has happened countless times when using ERC20s. - -== What If I Already Use ERC20? - -The standard has you covered! The ERC777 standard is *backwards compatible with ERC20*, meaning you can interact with these tokens as if they were ERC20, using the standard functions, while still getting all of the niceties, including send hooks. See the https://eips.ethereum.org/EIPS/eip-777#backward-compatibility[EIP's Backwards Compatibility section] to learn more. - -== Constructing an ERC777 Token Contract - -We will replicate the `GLD` example of the xref:erc20.adoc#constructing-an-erc20-token-contract[ERC20 guide], this time using ERC777. As always, check out the xref:api:token/ERC777.adoc[`API reference`] to learn more about the details of each function. - -[source,solidity] ----- -// contracts/GLDToken.sol -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/token/ERC777/ERC777.sol"; - -contract GLDToken is ERC777 { - constructor(uint256 initialSupply, address[] memory defaultOperators) - ERC777("Gold", "GLD", defaultOperators) - { - _mint(msg.sender, initialSupply, "", ""); - } -} ----- - -In this case, we'll be extending from the xref:api:token/ERC777.adoc#ERC777[`ERC777`] contract, which provides an implementation with compatibility support for ERC20. The API is quite similar to that of xref:api:token/ERC777.adoc#ERC777[`ERC777`], and we'll once again make use of xref:api:token/ERC777.adoc#ERC777-_mint-address-address-uint256-bytes-bytes-[`_mint`] to assign the `initialSupply` to the deployer account. Unlike xref:api:token/ERC20.adoc#ERC20-_mint-address-uint256-[ERC20's `_mint`], this one includes some extra parameters, but you can safely ignore those for now. - -You'll notice both xref:api:token/ERC777.adoc#IERC777-name--[`name`] and xref:api:token/ERC777.adoc#IERC777-symbol--[`symbol`] are assigned, but not xref:api:token/ERC777.adoc#ERC777-decimals--[`decimals`]. The ERC777 specification makes it mandatory to include support for these functions (unlike ERC20, where it is optional and we had to include xref:api:token/ERC20.adoc#ERC20Detailed[`ERC20Detailed`]), but also mandates that `decimals` always returns a fixed value of `18`, so there's no need to set it ourselves. For a review of ``decimals``'s role and importance, refer back to our xref:erc20.adoc#a-note-on-decimals[ERC20 guide]. - -Finally, we'll need to set the xref:api:token/ERC777.adoc#IERC777-defaultOperators--[`defaultOperators`]: special accounts (usually other smart contracts) that will be able to transfer tokens on behalf of their holders. If you're not planning on using operators in your token, you can simply pass an empty array. _Stay tuned for an upcoming in-depth guide on ERC777 operators!_ - -That's it for a basic token contract! We can now deploy it, and use the same xref:api:token/ERC777.adoc#IERC777-balanceOf-address-[`balanceOf`] method to query the deployer's balance: - -[source,javascript] ----- -> GLDToken.balanceOf(deployerAddress) -1000 ----- - -To move tokens from one account to another, we can use both xref:api:token/ERC777.adoc#ERC777-transfer-address-uint256-[``ERC20``'s `transfer`] method, or the new xref:api:token/ERC777.adoc#ERC777-send-address-uint256-bytes-[``ERC777``'s `send`], which fulfills a very similar role, but adds an optional `data` field: - -[source,javascript] ----- -> GLDToken.transfer(otherAddress, 300) -> GLDToken.send(otherAddress, 300, "") -> GLDToken.balanceOf(otherAddress) -600 -> GLDToken.balanceOf(deployerAddress) -400 ----- - -== Sending Tokens to Contracts - -A key difference when using xref:api:token/ERC777.adoc#ERC777-send-address-uint256-bytes-[`send`] is that token transfers to other contracts may revert with the following message: - -[source,text] ----- -ERC777: token recipient contract has no implementer for ERC777TokensRecipient ----- - -This is a good thing! It means that the recipient contract has not registered itself as aware of the ERC777 protocol, so transfers to it are disabled to *prevent tokens from being locked forever*. As an example, https://etherscan.io/token/0xa74476443119A942dE498590Fe1f2454d7D4aC0d?a=0xa74476443119A942dE498590Fe1f2454d7D4aC0d[the Golem contract currently holds over 350k `GNT` tokens], worth multiple tens of thousands of dollars, and lacks methods to get them out of there. This has happened to virtually every ERC20-backed project, usually due to user error. - -_An upcoming guide will cover how a contract can register itself as a recipient, send and receive hooks, and other advanced features of ERC777!_ diff --git a/docs/modules/ROOT/pages/tokens.adoc b/docs/modules/ROOT/pages/tokens.adoc index b168756df..10626f548 100644 --- a/docs/modules/ROOT/pages/tokens.adoc +++ b/docs/modules/ROOT/pages/tokens.adoc @@ -28,5 +28,4 @@ You've probably heard of the ERC20 or ERC721 token standards, and that's why you * xref:erc20.adoc[ERC20]: the most widespread token standard for fungible assets, albeit somewhat limited by its simplicity. * xref:erc721.adoc[ERC721]: the de-facto solution for non-fungible tokens, often used for collectibles and games. - * xref:erc777.adoc[ERC777]: a richer standard for fungible tokens, enabling new use cases and building on past learnings. Backwards compatible with ERC20. * xref:erc1155.adoc[ERC1155]: a novel standard for multi-tokens, allowing for a single contract to represent multiple fungible and non-fungible tokens, along with batched operations for increased gas efficiency. diff --git a/docs/modules/ROOT/pages/utilities.adoc b/docs/modules/ROOT/pages/utilities.adoc index 88207f0e1..eecb8293e 100644 --- a/docs/modules/ROOT/pages/utilities.adoc +++ b/docs/modules/ROOT/pages/utilities.adoc @@ -87,9 +87,7 @@ Easy! Want to split some payments between multiple people? Maybe you have an app that sends 30% of art purchases to the original creator and 70% of the profits to the current owner; you can build that with xref:api:finance.adoc#PaymentSplitter[`PaymentSplitter`]! -In Solidity, there are some security concerns with blindly sending money to accounts, since it allows them to execute arbitrary code. You can read up on these security concerns in the https://consensys.github.io/smart-contract-best-practices/[Ethereum Smart Contract Best Practices] website. One of the ways to fix reentrancy and stalling problems is, instead of immediately sending Ether to accounts that need it, you can use xref:api:security.adoc#PullPayment[`PullPayment`], which offers an xref:api:security.adoc#PullPayment-_asyncTransfer-address-uint256-[`_asyncTransfer`] function for sending money to something and requesting that they xref:api:security.adoc#PullPayment-withdrawPayments-address-payable-[`withdrawPayments()`] it later. - -If you want to Escrow some funds, check out xref:api:utils.adoc#Escrow[`Escrow`] and xref:api:utils.adoc#ConditionalEscrow[`ConditionalEscrow`] for governing the release of some escrowed Ether. +In Solidity, there are some security concerns with blindly sending money to accounts, since it allows them to execute arbitrary code. You can read up on these security concerns in the https://consensys.github.io/smart-contract-best-practices/[Ethereum Smart Contract Best Practices] website. [[collections]] == Collections @@ -103,7 +101,7 @@ Want to keep track of some numbers that increment by 1 every time you want anoth === Base64 -xref:api:utils.adoc#Base64[`Base64`] util allows you to transform `bytes32` data into its Base64 `string` representation. +xref:api:utils.adoc#Base64[`Base64`] util allows you to transform `bytes32` data into its Base64 `string` representation. This is especially useful for building URL-safe tokenURIs for both xref:api:token/ERC721.adoc#IERC721Metadata-tokenURI-uint256-[`ERC721`] or xref:api:token/ERC1155.adoc#IERC1155MetadataURI-uri-uint256-[`ERC1155`]. This library provides a clever way to serve URL-safe https://developer.mozilla.org/docs/Web/HTTP/Basics_of_HTTP/Data_URIs/[Data URI] compliant strings to serve on-chain data structures. @@ -122,7 +120,7 @@ contract My721Token is ERC721 { using Strings for uint256; constructor() ERC721("My721Token", "MTK") {} - + ... function tokenURI(uint256 tokenId) @@ -140,7 +138,7 @@ contract My721Token is ERC721 { return string( abi.encodePacked( - "data:application/json;base64,", + "data:application/json;base64,", Base64.encode(dataURI) ) ); diff --git a/hardhat.config.js b/hardhat.config.js index 5fd703d06..17d2c47b2 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -93,11 +93,7 @@ module.exports = { }, exposed: { initializers: true, - exclude: [ - 'vendor/**/*', - // Exclude Timers from hardhat-exposed because its overloaded functions are not transformed correctly - 'utils/Timers{,Upgradeable}.sol', - ], + exclude: ['vendor/**/*'], }, docgen: require('./docs/config'), }; diff --git a/scripts/generate/templates/Checkpoints.js b/scripts/generate/templates/Checkpoints.js index f64973b31..de58ad19e 100644 --- a/scripts/generate/templates/Checkpoints.js +++ b/scripts/generate/templates/Checkpoints.js @@ -1,5 +1,5 @@ const format = require('../format-lines'); -const { OPTS, LEGACY_OPTS } = require('./Checkpoints.opts.js'); +const { OPTS } = require('./Checkpoints.opts.js'); // TEMPLATE const header = `\ @@ -19,7 +19,7 @@ import "./math/SafeCast.sol"; */ `; -const types = opts => `\ +const template = opts => `\ struct ${opts.historyTypeName} { ${opts.checkpointTypeName}[] ${opts.checkpointFieldName}; } @@ -28,10 +28,7 @@ struct ${opts.checkpointTypeName} { ${opts.keyTypeName} ${opts.keyFieldName}; ${opts.valueTypeName} ${opts.valueFieldName}; } -`; -/* eslint-disable max-len */ -const operations = opts => `\ /** * @dev Pushes a (\`key\`, \`value\`) pair into a ${opts.historyTypeName} so that it is stored as the checkpoint. * @@ -87,77 +84,7 @@ function upperLookupRecent(${opts.historyTypeName} storage self, ${opts.keyTypeN return pos == 0 ? 0 : _unsafeAccess(self.${opts.checkpointFieldName}, pos - 1).${opts.valueFieldName}; } -`; - -const legacyOperations = opts => `\ -/** - * @dev Returns the value at a given block number. If a checkpoint is not available at that block, the closest one - * before it is returned, or zero otherwise. Because the number returned corresponds to that at the end of the - * block, the requested block number must be in the past, excluding the current block. - */ -function getAtBlock(${opts.historyTypeName} storage self, uint256 blockNumber) internal view returns (uint256) { - require(blockNumber < block.number, "Checkpoints: block not yet mined"); - uint32 key = SafeCast.toUint32(blockNumber); - - uint256 len = self.${opts.checkpointFieldName}.length; - uint256 pos = _upperBinaryLookup(self.${opts.checkpointFieldName}, key, 0, len); - return pos == 0 ? 0 : _unsafeAccess(self.${opts.checkpointFieldName}, pos - 1).${opts.valueFieldName}; -} - -/** - * @dev Returns the value at a given block number. If a checkpoint is not available at that block, the closest one - * before it is returned, or zero otherwise. Similar to {upperLookup} but optimized for the case when the searched - * checkpoint is probably "recent", defined as being among the last sqrt(N) checkpoints where N is the number of - * checkpoints. - */ -function getAtProbablyRecentBlock(${opts.historyTypeName} storage self, uint256 blockNumber) internal view returns (uint256) { - require(blockNumber < block.number, "Checkpoints: block not yet mined"); - uint32 key = SafeCast.toUint32(blockNumber); - - uint256 len = self.${opts.checkpointFieldName}.length; - - uint256 low = 0; - uint256 high = len; - - if (len > 5) { - uint256 mid = len - Math.sqrt(len); - if (key < _unsafeAccess(self.${opts.checkpointFieldName}, mid)._blockNumber) { - high = mid; - } else { - low = mid + 1; - } - } - - uint256 pos = _upperBinaryLookup(self.${opts.checkpointFieldName}, key, low, high); - - return pos == 0 ? 0 : _unsafeAccess(self.${opts.checkpointFieldName}, pos - 1).${opts.valueFieldName}; -} - -/** - * @dev Pushes a value onto a History so that it is stored as the checkpoint for the current block. - * - * Returns previous value and new value. - */ -function push(${opts.historyTypeName} storage self, uint256 value) internal returns (uint256, uint256) { - return _insert(self.${opts.checkpointFieldName}, SafeCast.toUint32(block.number), SafeCast.toUint224(value)); -} - -/** - * @dev Pushes a value onto a History, by updating the latest value using binary operation \`op\`. The new value will - * be set to \`op(latest, delta)\`. - * - * Returns previous value and new value. - */ -function push( - ${opts.historyTypeName} storage self, - function(uint256, uint256) view returns (uint256) op, - uint256 delta -) internal returns (uint256, uint256) { - return push(self, op(latest(self), delta)); -} -`; -const common = opts => `\ /** * @dev Returns the value in the most recent checkpoint, or zero if there are no checkpoints. */ @@ -299,13 +226,6 @@ function _unsafeAccess(${opts.checkpointTypeName}[] storage self, uint256 pos) module.exports = format( header.trimEnd(), 'library Checkpoints {', - [ - // Legacy types & functions - types(LEGACY_OPTS), - legacyOperations(LEGACY_OPTS), - common(LEGACY_OPTS), - // New flavors - ...OPTS.flatMap(opts => [types(opts), operations(opts), common(opts)]), - ], + OPTS.flatMap(opts => template(opts)), '}', ); diff --git a/scripts/generate/templates/Checkpoints.opts.js b/scripts/generate/templates/Checkpoints.opts.js index 03c5a9569..b8be23104 100644 --- a/scripts/generate/templates/Checkpoints.opts.js +++ b/scripts/generate/templates/Checkpoints.opts.js @@ -12,11 +12,6 @@ const defaultOpts = size => ({ }); module.exports = { + VALUE_SIZES, OPTS: VALUE_SIZES.map(size => defaultOpts(size)), - LEGACY_OPTS: { - ...defaultOpts(224), - historyTypeName: 'History', - checkpointTypeName: 'Checkpoint', - keyFieldName: '_blockNumber', - }, }; diff --git a/scripts/generate/templates/Checkpoints.t.js b/scripts/generate/templates/Checkpoints.t.js index b3da933a1..0451ecea4 100644 --- a/scripts/generate/templates/Checkpoints.t.js +++ b/scripts/generate/templates/Checkpoints.t.js @@ -1,6 +1,6 @@ const format = require('../format-lines'); const { capitalize } = require('../../helpers'); -const { OPTS, LEGACY_OPTS } = require('./Checkpoints.opts.js'); +const { OPTS } = require('./Checkpoints.opts.js'); // TEMPLATE const header = `\ @@ -12,7 +12,7 @@ import "../../contracts/utils/math/SafeCast.sol"; `; /* eslint-disable max-len */ -const common = opts => `\ +const template = opts => `\ using Checkpoints for Checkpoints.${opts.historyTypeName}; // Maximum gap between keys used during the fuzzing tests: the \`_prepareKeys\` function with make sure that @@ -52,9 +52,7 @@ function _assertLatestCheckpoint( assertEq(_key, key); assertEq(_value, value); } -`; -const testTrace = opts => `\ // tests function testPush( ${opts.keyTypeName}[] memory keys, @@ -88,7 +86,7 @@ function testPush( ${opts.keyTypeName} lastKey = keys[keys.length - 1]; if (lastKey > 0) { pastKey = _bound${capitalize(opts.keyTypeName)}(pastKey, 0, lastKey - 1); - + vm.expectRevert(); this.push(pastKey, values[keys.length % values.length]); } @@ -141,116 +139,8 @@ function testLookup( } `; -const testHistory = opts => `\ -// tests -function testPush( - ${opts.keyTypeName}[] memory keys, - ${opts.valueTypeName}[] memory values, - ${opts.keyTypeName} pastKey -) public { - vm.assume(values.length > 0 && values.length <= keys.length); - _prepareKeys(keys, _KEY_MAX_GAP); - - // initial state - assertEq(_ckpts.length(), 0); - assertEq(_ckpts.latest(), 0); - _assertLatestCheckpoint(false, 0, 0); - - uint256 duplicates = 0; - for (uint256 i = 0; i < keys.length; ++i) { - ${opts.keyTypeName} key = keys[i]; - ${opts.valueTypeName} value = values[i % values.length]; - if (i > 0 && key == keys[i - 1]) ++duplicates; - - // push - vm.roll(key); - _ckpts.push(value); - - // check length & latest - assertEq(_ckpts.length(), i + 1 - duplicates); - assertEq(_ckpts.latest(), value); - _assertLatestCheckpoint(true, key, value); - } - - // Can't push any key in the past - if (keys.length > 0) { - ${opts.keyTypeName} lastKey = keys[keys.length - 1]; - if (lastKey > 0) { - pastKey = _bound${capitalize(opts.keyTypeName)}(pastKey, 0, lastKey - 1); - - vm.roll(pastKey); - vm.expectRevert(); - this.push(values[keys.length % values.length]); - } - } -} - -// used to test reverts -function push(${opts.valueTypeName} value) external { - _ckpts.push(value); -} - -function testLookup( - ${opts.keyTypeName}[] memory keys, - ${opts.valueTypeName}[] memory values, - ${opts.keyTypeName} lookup -) public { - vm.assume(keys.length > 0); - vm.assume(values.length > 0 && values.length <= keys.length); - _prepareKeys(keys, _KEY_MAX_GAP); - - ${opts.keyTypeName} lastKey = keys[keys.length - 1]; - vm.assume(lastKey > 0); - lookup = _bound${capitalize(opts.keyTypeName)}(lookup, 0, lastKey - 1); - - ${opts.valueTypeName} upper = 0; - for (uint256 i = 0; i < keys.length; ++i) { - ${opts.keyTypeName} key = keys[i]; - ${opts.valueTypeName} value = values[i % values.length]; - - // push - vm.roll(key); - _ckpts.push(value); - - // track expected result of lookups - if (key <= lookup) { - upper = value; - } - } - - // check lookup - assertEq(_ckpts.getAtBlock(lookup), upper); - assertEq(_ckpts.getAtProbablyRecentBlock(lookup), upper); - - vm.expectRevert(); this.getAtBlock(lastKey); - vm.expectRevert(); this.getAtBlock(lastKey + 1); - vm.expectRevert(); this.getAtProbablyRecentBlock(lastKey); - vm.expectRevert(); this.getAtProbablyRecentBlock(lastKey + 1); -} - -// used to test reverts -function getAtBlock(${opts.keyTypeName} key) external view { - _ckpts.getAtBlock(key); -} - -// used to test reverts -function getAtProbablyRecentBlock(${opts.keyTypeName} key) external view { - _ckpts.getAtProbablyRecentBlock(key); -} -`; -/* eslint-enable max-len */ - // GENERATE module.exports = format( header, - // HISTORY - `contract Checkpoints${LEGACY_OPTS.historyTypeName}Test is Test {`, - [common(LEGACY_OPTS), testHistory(LEGACY_OPTS)], - '}', - // TRACEXXX - ...OPTS.flatMap(opts => [ - `contract Checkpoints${opts.historyTypeName}Test is Test {`, - [common(opts), testTrace(opts)], - '}', - ]), + ...OPTS.flatMap(opts => [`contract Checkpoints${opts.historyTypeName}Test is Test {`, [template(opts)], '}']), ); diff --git a/scripts/generate/templates/EnumerableMap.js b/scripts/generate/templates/EnumerableMap.js index 2bbb69e3e..61e23cfb4 100644 --- a/scripts/generate/templates/EnumerableMap.js +++ b/scripts/generate/templates/EnumerableMap.js @@ -153,22 +153,6 @@ function get(Bytes32ToBytes32Map storage map, bytes32 key) internal view returns return value; } -/** - * @dev Same as {get}, with a custom error message when \`key\` is not in the map. - * - * CAUTION: This function is deprecated because it requires allocating memory for the error - * message unnecessarily. For custom revert reasons use {tryGet}. - */ -function get( - Bytes32ToBytes32Map storage map, - bytes32 key, - string memory errorMessage -) internal view returns (bytes32) { - bytes32 value = map._values[key]; - require(value != 0 || contains(map, key), errorMessage); - return value; -} - /** * @dev Return the an array containing all the keys * @@ -261,20 +245,6 @@ function get(${name} storage map, ${keyType} key) internal view returns (${value return ${fromBytes32(valueType, `get(map._inner, ${toBytes32(keyType, 'key')})`)}; } -/** - * @dev Same as {get}, with a custom error message when \`key\` is not in the map. - * - * CAUTION: This function is deprecated because it requires allocating memory for the error - * message unnecessarily. For custom revert reasons use {tryGet}. - */ -function get( - ${name} storage map, - ${keyType} key, - string memory errorMessage -) internal view returns (${valueType}) { - return ${fromBytes32(valueType, `get(map._inner, ${toBytes32(keyType, 'key')}, errorMessage)`)}; -} - /** * @dev Return the an array containing all the keys * diff --git a/scripts/generate/templates/SafeCast.js b/scripts/generate/templates/SafeCast.js index 3e2b148fd..b47193be6 100644 --- a/scripts/generate/templates/SafeCast.js +++ b/scripts/generate/templates/SafeCast.js @@ -74,9 +74,6 @@ pragma solidity ^0.8.0; * * Using this library instead of the unchecked operations eliminates an entire * class of bugs, so it's recommended to use it always. - * - * Can be combined with {SafeMath} and {SignedSafeMath} to extend it to smaller types, by performing - * all math on \`uint256\` and \`int256\` and then downcasting. */ `; diff --git a/scripts/upgradeable/upgradeable.patch b/scripts/upgradeable/upgradeable.patch index 6f022d7b5..bc675ab8a 100644 --- a/scripts/upgradeable/upgradeable.patch +++ b/scripts/upgradeable/upgradeable.patch @@ -159,19 +159,6 @@ index 55e70b17..ceefb984 100644 }, "keywords": [ "solidity", -diff --git a/contracts/security/PullPayment.sol b/contracts/security/PullPayment.sol -index 65b4980f..f336592e 100644 ---- a/contracts/security/PullPayment.sol -+++ b/contracts/security/PullPayment.sol -@@ -22,6 +22,8 @@ import "../utils/escrow/Escrow.sol"; - * To use, derive from the `PullPayment` contract, and use {_asyncTransfer} - * instead of Solidity's `transfer` function. Payees can query their due - * payments with {payments}, and retrieve them with {withdrawPayments}. -+ * -+ * @custom:storage-size 51 - */ - abstract contract PullPayment { - Escrow private immutable _escrow; diff --git a/contracts/token/ERC20/extensions/ERC20Capped.sol b/contracts/token/ERC20/extensions/ERC20Capped.sol index 16f830d1..9ef98148 100644 --- a/contracts/token/ERC20/extensions/ERC20Capped.sol diff --git a/test/access/AccessControlCrossChain.test.js b/test/access/AccessControlCrossChain.test.js deleted file mode 100644 index d5a41076b..000000000 --- a/test/access/AccessControlCrossChain.test.js +++ /dev/null @@ -1,49 +0,0 @@ -const { expectRevert } = require('@openzeppelin/test-helpers'); -const { BridgeHelper } = require('../helpers/crosschain'); - -const { DEFAULT_ADMIN_ROLE, shouldBehaveLikeAccessControl } = require('./AccessControl.behavior.js'); - -const crossChainRoleAlias = role => - web3.utils.leftPad( - web3.utils.toHex(web3.utils.toBN(role).xor(web3.utils.toBN(web3.utils.soliditySha3('CROSSCHAIN_ALIAS')))), - 64, - ); - -const AccessControlCrossChainMock = artifacts.require('$AccessControlCrossChainMock'); - -const ROLE = web3.utils.soliditySha3('ROLE'); - -contract('AccessControl', function (accounts) { - before(async function () { - this.bridge = await BridgeHelper.deploy(); - }); - - beforeEach(async function () { - this.accessControl = await AccessControlCrossChainMock.new({ from: accounts[0] }); - await this.accessControl.$_grantRole(DEFAULT_ADMIN_ROLE, accounts[0]); - }); - - shouldBehaveLikeAccessControl('AccessControl', ...accounts); - - describe('CrossChain enabled', function () { - beforeEach(async function () { - await this.accessControl.grantRole(ROLE, accounts[0], { from: accounts[0] }); - await this.accessControl.grantRole(crossChainRoleAlias(ROLE), accounts[1], { from: accounts[0] }); - }); - - it('check alliassing', async function () { - expect(await this.accessControl.$_crossChainRoleAlias(ROLE)).to.be.bignumber.equal(crossChainRoleAlias(ROLE)); - }); - - it('Crosschain calls not authorized to non-aliased addresses', async function () { - await expectRevert( - this.bridge.call(accounts[0], this.accessControl, '$_checkRole(bytes32)', [ROLE]), - `AccessControl: account ${accounts[0].toLowerCase()} is missing role ${crossChainRoleAlias(ROLE)}`, - ); - }); - - it('Crosschain calls not authorized to non-aliased addresses', async function () { - await this.bridge.call(accounts[1], this.accessControl, '$_checkRole(bytes32)', [ROLE]); - }); - }); -}); diff --git a/test/crosschain/CrossChainEnabled.test.js b/test/crosschain/CrossChainEnabled.test.js deleted file mode 100644 index 9e7d26308..000000000 --- a/test/crosschain/CrossChainEnabled.test.js +++ /dev/null @@ -1,78 +0,0 @@ -const { BridgeHelper } = require('../helpers/crosschain'); -const { expectRevertCustomError } = require('../helpers/customError'); - -function randomAddress() { - return web3.utils.toChecksumAddress(web3.utils.randomHex(20)); -} - -const CrossChainEnabledAMBMock = artifacts.require('CrossChainEnabledAMBMock'); -const CrossChainEnabledArbitrumL1Mock = artifacts.require('CrossChainEnabledArbitrumL1Mock'); -const CrossChainEnabledArbitrumL2Mock = artifacts.require('CrossChainEnabledArbitrumL2Mock'); -const CrossChainEnabledOptimismMock = artifacts.require('CrossChainEnabledOptimismMock'); -const CrossChainEnabledPolygonChildMock = artifacts.require('CrossChainEnabledPolygonChildMock'); - -function shouldBehaveLikeReceiver(sender = randomAddress()) { - it('should reject same-chain calls', async function () { - await expectRevertCustomError(this.receiver.crossChainRestricted(), 'NotCrossChainCall()'); - - await expectRevertCustomError(this.receiver.crossChainOwnerRestricted(), 'NotCrossChainCall()'); - }); - - it('should restrict to cross-chain call from a invalid sender', async function () { - await expectRevertCustomError( - this.bridge.call(sender, this.receiver, 'crossChainOwnerRestricted()'), - `InvalidCrossChainSender("${sender}", "${await this.receiver.owner()}")`, - ); - }); - - it('should grant access to cross-chain call from the owner', async function () { - await this.bridge.call(await this.receiver.owner(), this.receiver, 'crossChainOwnerRestricted()'); - }); -} - -contract('CrossChainEnabled', function () { - describe('AMB', function () { - beforeEach(async function () { - this.bridge = await BridgeHelper.deploy('AMB'); - this.receiver = await CrossChainEnabledAMBMock.new(this.bridge.address); - }); - - shouldBehaveLikeReceiver(); - }); - - describe('Arbitrum-L1', function () { - beforeEach(async function () { - this.bridge = await BridgeHelper.deploy('Arbitrum-L1'); - this.receiver = await CrossChainEnabledArbitrumL1Mock.new(this.bridge.address); - }); - - shouldBehaveLikeReceiver(); - }); - - describe('Arbitrum-L2', function () { - beforeEach(async function () { - this.bridge = await BridgeHelper.deploy('Arbitrum-L2'); - this.receiver = await CrossChainEnabledArbitrumL2Mock.new(); - }); - - shouldBehaveLikeReceiver(); - }); - - describe('Optimism', function () { - beforeEach(async function () { - this.bridge = await BridgeHelper.deploy('Optimism'); - this.receiver = await CrossChainEnabledOptimismMock.new(this.bridge.address); - }); - - shouldBehaveLikeReceiver(); - }); - - describe('Polygon-Child', function () { - beforeEach(async function () { - this.bridge = await BridgeHelper.deploy('Polygon-Child'); - this.receiver = await CrossChainEnabledPolygonChildMock.new(this.bridge.address); - }); - - shouldBehaveLikeReceiver(); - }); -}); diff --git a/test/helpers/crosschain.js b/test/helpers/crosschain.js deleted file mode 100644 index 9e6ff9610..000000000 --- a/test/helpers/crosschain.js +++ /dev/null @@ -1,61 +0,0 @@ -const { promisify } = require('util'); - -const BridgeAMBMock = artifacts.require('BridgeAMBMock'); -const BridgeArbitrumL1Mock = artifacts.require('BridgeArbitrumL1Mock'); -const BridgeArbitrumL2Mock = artifacts.require('BridgeArbitrumL2Mock'); -const BridgeOptimismMock = artifacts.require('BridgeOptimismMock'); -const BridgePolygonChildMock = artifacts.require('BridgePolygonChildMock'); - -class BridgeHelper { - static async deploy(type) { - return new BridgeHelper(await deployBridge(type)); - } - - constructor(bridge) { - this.bridge = bridge; - this.address = bridge.address; - } - - call(from, target, selector = undefined, args = []) { - return this.bridge.relayAs( - target.address || target, - selector ? target.contract.methods[selector](...args).encodeABI() : '0x', - from, - ); - } -} - -async function deployBridge(type = 'Arbitrum-L2') { - switch (type) { - case 'AMB': - return BridgeAMBMock.new(); - - case 'Arbitrum-L1': - return BridgeArbitrumL1Mock.new(); - - case 'Arbitrum-L2': { - const instance = await BridgeArbitrumL2Mock.new(); - const code = await web3.eth.getCode(instance.address); - await promisify(web3.currentProvider.send.bind(web3.currentProvider))({ - jsonrpc: '2.0', - method: 'hardhat_setCode', - params: ['0x0000000000000000000000000000000000000064', code], - id: new Date().getTime(), - }); - return BridgeArbitrumL2Mock.at('0x0000000000000000000000000000000000000064'); - } - - case 'Optimism': - return BridgeOptimismMock.new(); - - case 'Polygon-Child': - return BridgePolygonChildMock.new(); - - default: - throw new Error(`CrossChain: ${type} is not supported`); - } -} - -module.exports = { - BridgeHelper, -}; diff --git a/test/security/PullPayment.test.js b/test/security/PullPayment.test.js deleted file mode 100644 index 5bf72bbe6..000000000 --- a/test/security/PullPayment.test.js +++ /dev/null @@ -1,51 +0,0 @@ -const { balance, ether } = require('@openzeppelin/test-helpers'); - -const { expect } = require('chai'); - -const PullPaymentMock = artifacts.require('PullPaymentMock'); - -contract('PullPayment', function (accounts) { - const [payer, payee1, payee2] = accounts; - - const amount = ether('17'); - - beforeEach(async function () { - this.contract = await PullPaymentMock.new({ value: amount }); - }); - - describe('payments', function () { - it('can record an async payment correctly', async function () { - await this.contract.callTransfer(payee1, 100, { from: payer }); - expect(await this.contract.payments(payee1)).to.be.bignumber.equal('100'); - }); - - it('can add multiple balances on one account', async function () { - await this.contract.callTransfer(payee1, 200, { from: payer }); - await this.contract.callTransfer(payee1, 300, { from: payer }); - expect(await this.contract.payments(payee1)).to.be.bignumber.equal('500'); - }); - - it('can add balances on multiple accounts', async function () { - await this.contract.callTransfer(payee1, 200, { from: payer }); - await this.contract.callTransfer(payee2, 300, { from: payer }); - - expect(await this.contract.payments(payee1)).to.be.bignumber.equal('200'); - - expect(await this.contract.payments(payee2)).to.be.bignumber.equal('300'); - }); - }); - - describe('withdrawPayments', function () { - it('can withdraw payment', async function () { - const balanceTracker = await balance.tracker(payee1); - - await this.contract.callTransfer(payee1, amount, { from: payer }); - expect(await this.contract.payments(payee1)).to.be.bignumber.equal(amount); - - await this.contract.withdrawPayments(payee1); - - expect(await balanceTracker.delta()).to.be.bignumber.equal(amount); - expect(await this.contract.payments(payee1)).to.be.bignumber.equal('0'); - }); - }); -}); diff --git a/test/token/ERC20/utils/SafeERC20.test.js b/test/token/ERC20/utils/SafeERC20.test.js index d4981dd30..04b3b5cb1 100644 --- a/test/token/ERC20/utils/SafeERC20.test.js +++ b/test/token/ERC20/utils/SafeERC20.test.js @@ -182,17 +182,6 @@ contract('SafeERC20', function (accounts) { await this.token.$_approve(this.mock.address, spender, 100); }); - it('safeApprove fails to update approval to non-zero', async function () { - await expectRevert( - this.mock.$safeApprove(this.token.address, spender, 200), - 'SafeERC20: approve from non-zero to non-zero allowance', - ); - }); - - it('safeApprove can update approval to zero', async function () { - await this.mock.$safeApprove(this.token.address, spender, 0); - }); - it('safeApprove can increase approval', async function () { await expectRevert(this.mock.$safeIncreaseAllowance(this.token.address, spender, 10), 'USDT approval failure'); }); @@ -217,10 +206,6 @@ function shouldRevertOnAllCalls([receiver, spender], reason) { await expectRevert(this.mock.$safeTransferFrom(this.token.address, this.mock.address, receiver, 0), reason); }); - it('reverts on approve', async function () { - await expectRevert(this.mock.$safeApprove(this.token.address, spender, 0), reason); - }); - it('reverts on increaseAllowance', async function () { // [TODO] make sure it's reverting for the right reason await expectRevert.unspecified(this.mock.$safeIncreaseAllowance(this.token.address, spender, 0)); @@ -269,16 +254,6 @@ function shouldOnlyRevertOnErrors([owner, receiver, spender]) { await this.token.$_approve(this.mock.address, spender, 0); }); - it("doesn't revert when approving a non-zero allowance", async function () { - await this.mock.$safeApprove(this.token.address, spender, 100); - expect(await this.token.allowance(this.mock.address, spender)).to.be.bignumber.equal('100'); - }); - - it("doesn't revert when approving a zero allowance", async function () { - await this.mock.$safeApprove(this.token.address, spender, 0); - expect(await this.token.allowance(this.mock.address, spender)).to.be.bignumber.equal('0'); - }); - it("doesn't revert when force approving a non-zero allowance", async function () { await this.mock.$forceApprove(this.token.address, spender, 100); expect(await this.token.allowance(this.mock.address, spender)).to.be.bignumber.equal('100'); @@ -307,18 +282,6 @@ function shouldOnlyRevertOnErrors([owner, receiver, spender]) { await this.token.$_approve(this.mock.address, spender, 100); }); - it('reverts when approving a non-zero allowance', async function () { - await expectRevert( - this.mock.$safeApprove(this.token.address, spender, 20), - 'SafeERC20: approve from non-zero to non-zero allowance', - ); - }); - - it("doesn't revert when approving a zero allowance", async function () { - await this.mock.$safeApprove(this.token.address, spender, 0); - expect(await this.token.allowance(this.mock.address, spender)).to.be.bignumber.equal('0'); - }); - it("doesn't revert when force approving a non-zero allowance", async function () { await this.mock.$forceApprove(this.token.address, spender, 20); expect(await this.token.allowance(this.mock.address, spender)).to.be.bignumber.equal('20'); diff --git a/test/token/ERC777/ERC777.behavior.js b/test/token/ERC777/ERC777.behavior.js deleted file mode 100644 index b1585bc91..000000000 --- a/test/token/ERC777/ERC777.behavior.js +++ /dev/null @@ -1,597 +0,0 @@ -const { BN, constants, expectEvent, expectRevert } = require('@openzeppelin/test-helpers'); -const { ZERO_ADDRESS } = constants; - -const { expect } = require('chai'); - -const ERC777SenderRecipientMock = artifacts.require('ERC777SenderRecipientMock'); - -function shouldBehaveLikeERC777DirectSendBurn(holder, recipient, data) { - shouldBehaveLikeERC777DirectSend(holder, recipient, data); - shouldBehaveLikeERC777DirectBurn(holder, data); -} - -function shouldBehaveLikeERC777OperatorSendBurn(holder, recipient, operator, data, operatorData) { - shouldBehaveLikeERC777OperatorSend(holder, recipient, operator, data, operatorData); - shouldBehaveLikeERC777OperatorBurn(holder, operator, data, operatorData); -} - -function shouldBehaveLikeERC777UnauthorizedOperatorSendBurn(holder, recipient, operator, data, operatorData) { - shouldBehaveLikeERC777UnauthorizedOperatorSend(holder, recipient, operator, data, operatorData); - shouldBehaveLikeERC777UnauthorizedOperatorBurn(holder, operator, data, operatorData); -} - -function shouldBehaveLikeERC777DirectSend(holder, recipient, data) { - describe('direct send', function () { - context('when the sender has tokens', function () { - shouldDirectSendTokens(holder, recipient, new BN('0'), data); - shouldDirectSendTokens(holder, recipient, new BN('1'), data); - - it('reverts when sending more than the balance', async function () { - const balance = await this.token.balanceOf(holder); - await expectRevert.unspecified(this.token.send(recipient, balance.addn(1), data, { from: holder })); - }); - - it('reverts when sending to the zero address', async function () { - await expectRevert.unspecified(this.token.send(ZERO_ADDRESS, new BN('1'), data, { from: holder })); - }); - }); - - context('when the sender has no tokens', function () { - removeBalance(holder); - - shouldDirectSendTokens(holder, recipient, new BN('0'), data); - - it('reverts when sending a non-zero amount', async function () { - await expectRevert.unspecified(this.token.send(recipient, new BN('1'), data, { from: holder })); - }); - }); - }); -} - -function shouldBehaveLikeERC777OperatorSend(holder, recipient, operator, data, operatorData) { - describe('operator send', function () { - context('when the sender has tokens', async function () { - shouldOperatorSendTokens(holder, operator, recipient, new BN('0'), data, operatorData); - shouldOperatorSendTokens(holder, operator, recipient, new BN('1'), data, operatorData); - - it('reverts when sending more than the balance', async function () { - const balance = await this.token.balanceOf(holder); - await expectRevert.unspecified( - this.token.operatorSend(holder, recipient, balance.addn(1), data, operatorData, { from: operator }), - ); - }); - - it('reverts when sending to the zero address', async function () { - await expectRevert.unspecified( - this.token.operatorSend(holder, ZERO_ADDRESS, new BN('1'), data, operatorData, { from: operator }), - ); - }); - }); - - context('when the sender has no tokens', function () { - removeBalance(holder); - - shouldOperatorSendTokens(holder, operator, recipient, new BN('0'), data, operatorData); - - it('reverts when sending a non-zero amount', async function () { - await expectRevert.unspecified( - this.token.operatorSend(holder, recipient, new BN('1'), data, operatorData, { from: operator }), - ); - }); - - it('reverts when sending from the zero address', async function () { - // This is not yet reflected in the spec - await expectRevert.unspecified( - this.token.operatorSend(ZERO_ADDRESS, recipient, new BN('0'), data, operatorData, { from: operator }), - ); - }); - }); - }); -} - -function shouldBehaveLikeERC777UnauthorizedOperatorSend(holder, recipient, operator, data, operatorData) { - describe('operator send', function () { - it('reverts', async function () { - await expectRevert.unspecified(this.token.operatorSend(holder, recipient, new BN('0'), data, operatorData)); - }); - }); -} - -function shouldBehaveLikeERC777DirectBurn(holder, data) { - describe('direct burn', function () { - context('when the sender has tokens', function () { - shouldDirectBurnTokens(holder, new BN('0'), data); - shouldDirectBurnTokens(holder, new BN('1'), data); - - it('reverts when burning more than the balance', async function () { - const balance = await this.token.balanceOf(holder); - await expectRevert.unspecified(this.token.burn(balance.addn(1), data, { from: holder })); - }); - }); - - context('when the sender has no tokens', function () { - removeBalance(holder); - - shouldDirectBurnTokens(holder, new BN('0'), data); - - it('reverts when burning a non-zero amount', async function () { - await expectRevert.unspecified(this.token.burn(new BN('1'), data, { from: holder })); - }); - }); - }); -} - -function shouldBehaveLikeERC777OperatorBurn(holder, operator, data, operatorData) { - describe('operator burn', function () { - context('when the sender has tokens', async function () { - shouldOperatorBurnTokens(holder, operator, new BN('0'), data, operatorData); - shouldOperatorBurnTokens(holder, operator, new BN('1'), data, operatorData); - - it('reverts when burning more than the balance', async function () { - const balance = await this.token.balanceOf(holder); - await expectRevert.unspecified( - this.token.operatorBurn(holder, balance.addn(1), data, operatorData, { from: operator }), - ); - }); - }); - - context('when the sender has no tokens', function () { - removeBalance(holder); - - shouldOperatorBurnTokens(holder, operator, new BN('0'), data, operatorData); - - it('reverts when burning a non-zero amount', async function () { - await expectRevert.unspecified( - this.token.operatorBurn(holder, new BN('1'), data, operatorData, { from: operator }), - ); - }); - - it('reverts when burning from the zero address', async function () { - // This is not yet reflected in the spec - await expectRevert.unspecified( - this.token.operatorBurn(ZERO_ADDRESS, new BN('0'), data, operatorData, { from: operator }), - ); - }); - }); - }); -} - -function shouldBehaveLikeERC777UnauthorizedOperatorBurn(holder, operator, data, operatorData) { - describe('operator burn', function () { - it('reverts', async function () { - await expectRevert.unspecified(this.token.operatorBurn(holder, new BN('0'), data, operatorData)); - }); - }); -} - -function shouldDirectSendTokens(from, to, amount, data) { - shouldSendTokens(from, null, to, amount, data, null); -} - -function shouldOperatorSendTokens(from, operator, to, amount, data, operatorData) { - shouldSendTokens(from, operator, to, amount, data, operatorData); -} - -function shouldSendTokens(from, operator, to, amount, data, operatorData) { - const operatorCall = operator !== null; - - it(`${operatorCall ? 'operator ' : ''}can send an amount of ${amount}`, async function () { - const initialTotalSupply = await this.token.totalSupply(); - const initialFromBalance = await this.token.balanceOf(from); - const initialToBalance = await this.token.balanceOf(to); - - let receipt; - if (!operatorCall) { - receipt = await this.token.send(to, amount, data, { from }); - expectEvent(receipt, 'Sent', { - operator: from, - from, - to, - amount, - data, - operatorData: null, - }); - } else { - receipt = await this.token.operatorSend(from, to, amount, data, operatorData, { from: operator }); - expectEvent(receipt, 'Sent', { - operator, - from, - to, - amount, - data, - operatorData, - }); - } - - expectEvent(receipt, 'Transfer', { - from, - to, - value: amount, - }); - - const finalTotalSupply = await this.token.totalSupply(); - const finalFromBalance = await this.token.balanceOf(from); - const finalToBalance = await this.token.balanceOf(to); - - expect(finalTotalSupply).to.be.bignumber.equal(initialTotalSupply); - expect(finalToBalance.sub(initialToBalance)).to.be.bignumber.equal(amount); - expect(finalFromBalance.sub(initialFromBalance)).to.be.bignumber.equal(amount.neg()); - }); -} - -function shouldDirectBurnTokens(from, amount, data) { - shouldBurnTokens(from, null, amount, data, null); -} - -function shouldOperatorBurnTokens(from, operator, amount, data, operatorData) { - shouldBurnTokens(from, operator, amount, data, operatorData); -} - -function shouldBurnTokens(from, operator, amount, data, operatorData) { - const operatorCall = operator !== null; - - it(`${operatorCall ? 'operator ' : ''}can burn an amount of ${amount}`, async function () { - const initialTotalSupply = await this.token.totalSupply(); - const initialFromBalance = await this.token.balanceOf(from); - - let receipt; - if (!operatorCall) { - receipt = await this.token.burn(amount, data, { from }); - expectEvent(receipt, 'Burned', { - operator: from, - from, - amount, - data, - operatorData: null, - }); - } else { - receipt = await this.token.operatorBurn(from, amount, data, operatorData, { from: operator }); - expectEvent(receipt, 'Burned', { - operator, - from, - amount, - data, - operatorData, - }); - } - - expectEvent(receipt, 'Transfer', { - from, - to: ZERO_ADDRESS, - value: amount, - }); - - const finalTotalSupply = await this.token.totalSupply(); - const finalFromBalance = await this.token.balanceOf(from); - - expect(finalTotalSupply.sub(initialTotalSupply)).to.be.bignumber.equal(amount.neg()); - expect(finalFromBalance.sub(initialFromBalance)).to.be.bignumber.equal(amount.neg()); - }); -} - -function shouldBehaveLikeERC777InternalMint(recipient, operator, amount, data, operatorData) { - shouldInternalMintTokens(operator, recipient, new BN('0'), data, operatorData); - shouldInternalMintTokens(operator, recipient, amount, data, operatorData); - - it('reverts when minting tokens for the zero address', async function () { - await expectRevert.unspecified( - this.token.$_mint(ZERO_ADDRESS, amount, data, operatorData, true, { from: operator }), - ); - }); -} - -function shouldInternalMintTokens(operator, to, amount, data, operatorData) { - it(`can (internal) mint an amount of ${amount}`, async function () { - const initialTotalSupply = await this.token.totalSupply(); - const initialToBalance = await this.token.balanceOf(to); - - const receipt = await this.token.$_mint(to, amount, data, operatorData, true, { from: operator }); - - expectEvent(receipt, 'Minted', { - operator, - to, - amount, - data, - operatorData, - }); - - expectEvent(receipt, 'Transfer', { - from: ZERO_ADDRESS, - to, - value: amount, - }); - - const finalTotalSupply = await this.token.totalSupply(); - const finalToBalance = await this.token.balanceOf(to); - - expect(finalTotalSupply.sub(initialTotalSupply)).to.be.bignumber.equal(amount); - expect(finalToBalance.sub(initialToBalance)).to.be.bignumber.equal(amount); - }); -} - -function shouldBehaveLikeERC777SendBurnMintInternalWithReceiveHook(operator, amount, data, operatorData) { - context('when TokensRecipient reverts', function () { - beforeEach(async function () { - await this.tokensRecipientImplementer.setShouldRevertReceive(true); - }); - - it('send reverts', async function () { - await expectRevert.unspecified(sendFromHolder(this.token, this.sender, this.recipient, amount, data)); - }); - - it('operatorSend reverts', async function () { - await expectRevert.unspecified( - this.token.operatorSend(this.sender, this.recipient, amount, data, operatorData, { from: operator }), - ); - }); - - it('mint (internal) reverts', async function () { - await expectRevert.unspecified( - this.token.$_mint(this.recipient, amount, data, operatorData, true, { from: operator }), - ); - }); - }); - - context('when TokensRecipient does not revert', function () { - beforeEach(async function () { - await this.tokensRecipientImplementer.setShouldRevertSend(false); - }); - - it('TokensRecipient receives send data and is called after state mutation', async function () { - const { tx } = await sendFromHolder(this.token, this.sender, this.recipient, amount, data); - - const postSenderBalance = await this.token.balanceOf(this.sender); - const postRecipientBalance = await this.token.balanceOf(this.recipient); - - await assertTokensReceivedCalled( - this.token, - tx, - this.sender, - this.sender, - this.recipient, - amount, - data, - null, - postSenderBalance, - postRecipientBalance, - ); - }); - - it('TokensRecipient receives operatorSend data and is called after state mutation', async function () { - const { tx } = await this.token.operatorSend(this.sender, this.recipient, amount, data, operatorData, { - from: operator, - }); - - const postSenderBalance = await this.token.balanceOf(this.sender); - const postRecipientBalance = await this.token.balanceOf(this.recipient); - - await assertTokensReceivedCalled( - this.token, - tx, - operator, - this.sender, - this.recipient, - amount, - data, - operatorData, - postSenderBalance, - postRecipientBalance, - ); - }); - - it('TokensRecipient receives mint (internal) data and is called after state mutation', async function () { - const { tx } = await this.token.$_mint(this.recipient, amount, data, operatorData, true, { from: operator }); - - const postRecipientBalance = await this.token.balanceOf(this.recipient); - - await assertTokensReceivedCalled( - this.token, - tx, - operator, - ZERO_ADDRESS, - this.recipient, - amount, - data, - operatorData, - new BN('0'), - postRecipientBalance, - ); - }); - }); -} - -function shouldBehaveLikeERC777SendBurnWithSendHook(operator, amount, data, operatorData) { - context('when TokensSender reverts', function () { - beforeEach(async function () { - await this.tokensSenderImplementer.setShouldRevertSend(true); - }); - - it('send reverts', async function () { - await expectRevert.unspecified(sendFromHolder(this.token, this.sender, this.recipient, amount, data)); - }); - - it('operatorSend reverts', async function () { - await expectRevert.unspecified( - this.token.operatorSend(this.sender, this.recipient, amount, data, operatorData, { from: operator }), - ); - }); - - it('burn reverts', async function () { - await expectRevert.unspecified(burnFromHolder(this.token, this.sender, amount, data)); - }); - - it('operatorBurn reverts', async function () { - await expectRevert.unspecified( - this.token.operatorBurn(this.sender, amount, data, operatorData, { from: operator }), - ); - }); - }); - - context('when TokensSender does not revert', function () { - beforeEach(async function () { - await this.tokensSenderImplementer.setShouldRevertSend(false); - }); - - it('TokensSender receives send data and is called before state mutation', async function () { - const preSenderBalance = await this.token.balanceOf(this.sender); - const preRecipientBalance = await this.token.balanceOf(this.recipient); - - const { tx } = await sendFromHolder(this.token, this.sender, this.recipient, amount, data); - - await assertTokensToSendCalled( - this.token, - tx, - this.sender, - this.sender, - this.recipient, - amount, - data, - null, - preSenderBalance, - preRecipientBalance, - ); - }); - - it('TokensSender receives operatorSend data and is called before state mutation', async function () { - const preSenderBalance = await this.token.balanceOf(this.sender); - const preRecipientBalance = await this.token.balanceOf(this.recipient); - - const { tx } = await this.token.operatorSend(this.sender, this.recipient, amount, data, operatorData, { - from: operator, - }); - - await assertTokensToSendCalled( - this.token, - tx, - operator, - this.sender, - this.recipient, - amount, - data, - operatorData, - preSenderBalance, - preRecipientBalance, - ); - }); - - it('TokensSender receives burn data and is called before state mutation', async function () { - const preSenderBalance = await this.token.balanceOf(this.sender); - - const { tx } = await burnFromHolder(this.token, this.sender, amount, data, { from: this.sender }); - - await assertTokensToSendCalled( - this.token, - tx, - this.sender, - this.sender, - ZERO_ADDRESS, - amount, - data, - null, - preSenderBalance, - ); - }); - - it('TokensSender receives operatorBurn data and is called before state mutation', async function () { - const preSenderBalance = await this.token.balanceOf(this.sender); - - const { tx } = await this.token.operatorBurn(this.sender, amount, data, operatorData, { from: operator }); - - await assertTokensToSendCalled( - this.token, - tx, - operator, - this.sender, - ZERO_ADDRESS, - amount, - data, - operatorData, - preSenderBalance, - ); - }); - }); -} - -function removeBalance(holder) { - beforeEach(async function () { - await this.token.burn(await this.token.balanceOf(holder), '0x', { from: holder }); - expect(await this.token.balanceOf(holder)).to.be.bignumber.equal('0'); - }); -} - -async function assertTokensReceivedCalled( - token, - txHash, - operator, - from, - to, - amount, - data, - operatorData, - fromBalance, - toBalance = '0', -) { - await expectEvent.inTransaction(txHash, ERC777SenderRecipientMock, 'TokensReceivedCalled', { - operator, - from, - to, - amount, - data, - operatorData, - token: token.address, - fromBalance, - toBalance, - }); -} - -async function assertTokensToSendCalled( - token, - txHash, - operator, - from, - to, - amount, - data, - operatorData, - fromBalance, - toBalance = '0', -) { - await expectEvent.inTransaction(txHash, ERC777SenderRecipientMock, 'TokensToSendCalled', { - operator, - from, - to, - amount, - data, - operatorData, - token: token.address, - fromBalance, - toBalance, - }); -} - -async function sendFromHolder(token, holder, to, amount, data) { - if ((await web3.eth.getCode(holder)).length <= '0x'.length) { - return token.send(to, amount, data, { from: holder }); - } else { - // assume holder is ERC777SenderRecipientMock contract - return (await ERC777SenderRecipientMock.at(holder)).send(token.address, to, amount, data); - } -} - -async function burnFromHolder(token, holder, amount, data) { - if ((await web3.eth.getCode(holder)).length <= '0x'.length) { - return token.burn(amount, data, { from: holder }); - } else { - // assume holder is ERC777SenderRecipientMock contract - return (await ERC777SenderRecipientMock.at(holder)).burn(token.address, amount, data); - } -} - -module.exports = { - shouldBehaveLikeERC777DirectSendBurn, - shouldBehaveLikeERC777OperatorSendBurn, - shouldBehaveLikeERC777UnauthorizedOperatorSendBurn, - shouldBehaveLikeERC777InternalMint, - shouldBehaveLikeERC777SendBurnMintInternalWithReceiveHook, - shouldBehaveLikeERC777SendBurnWithSendHook, -}; diff --git a/test/token/ERC777/ERC777.test.js b/test/token/ERC777/ERC777.test.js deleted file mode 100644 index 44bc25351..000000000 --- a/test/token/ERC777/ERC777.test.js +++ /dev/null @@ -1,556 +0,0 @@ -const { BN, constants, expectEvent, expectRevert, singletons } = require('@openzeppelin/test-helpers'); -const { ZERO_ADDRESS } = constants; - -const { expect } = require('chai'); - -const { - shouldBehaveLikeERC777DirectSendBurn, - shouldBehaveLikeERC777OperatorSendBurn, - shouldBehaveLikeERC777UnauthorizedOperatorSendBurn, - shouldBehaveLikeERC777InternalMint, - shouldBehaveLikeERC777SendBurnMintInternalWithReceiveHook, - shouldBehaveLikeERC777SendBurnWithSendHook, -} = require('./ERC777.behavior'); - -const { shouldBehaveLikeERC20, shouldBehaveLikeERC20Approve } = require('../ERC20/ERC20.behavior'); - -const ERC777 = artifacts.require('$ERC777Mock'); -const ERC777SenderRecipientMock = artifacts.require('$ERC777SenderRecipientMock'); - -contract('ERC777', function (accounts) { - const [registryFunder, holder, defaultOperatorA, defaultOperatorB, newOperator, anyone] = accounts; - - const initialSupply = new BN('10000'); - const name = 'ERC777Test'; - const symbol = '777T'; - const data = web3.utils.sha3('OZ777TestData'); - const operatorData = web3.utils.sha3('OZ777TestOperatorData'); - - const defaultOperators = [defaultOperatorA, defaultOperatorB]; - - beforeEach(async function () { - this.erc1820 = await singletons.ERC1820Registry(registryFunder); - }); - - context('with default operators', function () { - beforeEach(async function () { - this.token = await ERC777.new(name, symbol, defaultOperators); - await this.token.$_mint(holder, initialSupply, '0x', '0x'); - }); - - describe('as an ERC20 token', function () { - shouldBehaveLikeERC20('ERC777', initialSupply, holder, anyone, defaultOperatorA); - - describe('_approve', function () { - shouldBehaveLikeERC20Approve('ERC777', holder, anyone, initialSupply, function (owner, spender, amount) { - return this.token.$_approve(owner, spender, amount); - }); - - describe('when the owner is the zero address', function () { - it('reverts', async function () { - await expectRevert( - this.token.$_approve(ZERO_ADDRESS, anyone, initialSupply), - 'ERC777: approve from the zero address', - ); - }); - }); - }); - }); - - it('does not emit AuthorizedOperator events for default operators', async function () { - await expectEvent.notEmitted.inConstruction(this.token, 'AuthorizedOperator'); - }); - - describe('basic information', function () { - it('returns the name', async function () { - expect(await this.token.name()).to.equal(name); - }); - - it('returns the symbol', async function () { - expect(await this.token.symbol()).to.equal(symbol); - }); - - it('returns a granularity of 1', async function () { - expect(await this.token.granularity()).to.be.bignumber.equal('1'); - }); - - it('returns the default operators', async function () { - expect(await this.token.defaultOperators()).to.deep.equal(defaultOperators); - }); - - it('default operators are operators for all accounts', async function () { - for (const operator of defaultOperators) { - expect(await this.token.isOperatorFor(operator, anyone)).to.equal(true); - } - }); - - it('returns the total supply', async function () { - expect(await this.token.totalSupply()).to.be.bignumber.equal(initialSupply); - }); - - it('returns 18 when decimals is called', async function () { - expect(await this.token.decimals()).to.be.bignumber.equal('18'); - }); - - it('the ERC777Token interface is registered in the registry', async function () { - expect( - await this.erc1820.getInterfaceImplementer(this.token.address, web3.utils.soliditySha3('ERC777Token')), - ).to.equal(this.token.address); - }); - - it('the ERC20Token interface is registered in the registry', async function () { - expect( - await this.erc1820.getInterfaceImplementer(this.token.address, web3.utils.soliditySha3('ERC20Token')), - ).to.equal(this.token.address); - }); - }); - - describe('balanceOf', function () { - context('for an account with no tokens', function () { - it('returns zero', async function () { - expect(await this.token.balanceOf(anyone)).to.be.bignumber.equal('0'); - }); - }); - - context('for an account with tokens', function () { - it('returns their balance', async function () { - expect(await this.token.balanceOf(holder)).to.be.bignumber.equal(initialSupply); - }); - }); - }); - - context('with no ERC777TokensSender and no ERC777TokensRecipient implementers', function () { - describe('send/burn', function () { - shouldBehaveLikeERC777DirectSendBurn(holder, anyone, data); - - context('with self operator', function () { - shouldBehaveLikeERC777OperatorSendBurn(holder, anyone, holder, data, operatorData); - }); - - context('with first default operator', function () { - shouldBehaveLikeERC777OperatorSendBurn(holder, anyone, defaultOperatorA, data, operatorData); - }); - - context('with second default operator', function () { - shouldBehaveLikeERC777OperatorSendBurn(holder, anyone, defaultOperatorB, data, operatorData); - }); - - context('before authorizing a new operator', function () { - shouldBehaveLikeERC777UnauthorizedOperatorSendBurn(holder, anyone, newOperator, data, operatorData); - }); - - context('with new authorized operator', function () { - beforeEach(async function () { - await this.token.authorizeOperator(newOperator, { from: holder }); - }); - - shouldBehaveLikeERC777OperatorSendBurn(holder, anyone, newOperator, data, operatorData); - - context('with revoked operator', function () { - beforeEach(async function () { - await this.token.revokeOperator(newOperator, { from: holder }); - }); - - shouldBehaveLikeERC777UnauthorizedOperatorSendBurn(holder, anyone, newOperator, data, operatorData); - }); - }); - }); - - describe('mint (internal)', function () { - const to = anyone; - const amount = new BN('5'); - - context('with default operator', function () { - const operator = defaultOperatorA; - - shouldBehaveLikeERC777InternalMint(to, operator, amount, data, operatorData); - }); - - context('with non operator', function () { - const operator = newOperator; - - shouldBehaveLikeERC777InternalMint(to, operator, amount, data, operatorData); - }); - }); - - describe('mint (internal extended)', function () { - const amount = new BN('5'); - - context('to anyone', function () { - beforeEach(async function () { - this.recipient = anyone; - }); - - context('with default operator', function () { - const operator = defaultOperatorA; - - it('without requireReceptionAck', async function () { - await this.token.$_mint(this.recipient, amount, data, operatorData, false, { from: operator }); - }); - - it('with requireReceptionAck', async function () { - await this.token.$_mint(this.recipient, amount, data, operatorData, true, { from: operator }); - }); - }); - - context('with non operator', function () { - const operator = newOperator; - - it('without requireReceptionAck', async function () { - await this.token.$_mint(this.recipient, amount, data, operatorData, false, { from: operator }); - }); - - it('with requireReceptionAck', async function () { - await this.token.$_mint(this.recipient, amount, data, operatorData, true, { from: operator }); - }); - }); - }); - - context('to non ERC777TokensRecipient implementer', function () { - beforeEach(async function () { - this.tokensRecipientImplementer = await ERC777SenderRecipientMock.new(); - this.recipient = this.tokensRecipientImplementer.address; - }); - - context('with default operator', function () { - const operator = defaultOperatorA; - - it('without requireReceptionAck', async function () { - await this.token.$_mint(this.recipient, amount, data, operatorData, false, { from: operator }); - }); - - it('with requireReceptionAck', async function () { - await expectRevert( - this.token.$_mint(this.recipient, amount, data, operatorData, true, { from: operator }), - 'ERC777: token recipient contract has no implementer for ERC777TokensRecipient', - ); - }); - }); - - context('with non operator', function () { - const operator = newOperator; - - it('without requireReceptionAck', async function () { - await this.token.$_mint(this.recipient, amount, data, operatorData, false, { from: operator }); - }); - - it('with requireReceptionAck', async function () { - await expectRevert( - this.token.$_mint(this.recipient, amount, data, operatorData, true, { from: operator }), - 'ERC777: token recipient contract has no implementer for ERC777TokensRecipient', - ); - }); - }); - }); - }); - }); - - describe('operator management', function () { - it('accounts are their own operator', async function () { - expect(await this.token.isOperatorFor(holder, holder)).to.equal(true); - }); - - it('reverts when self-authorizing', async function () { - await expectRevert( - this.token.authorizeOperator(holder, { from: holder }), - 'ERC777: authorizing self as operator', - ); - }); - - it('reverts when self-revoking', async function () { - await expectRevert(this.token.revokeOperator(holder, { from: holder }), 'ERC777: revoking self as operator'); - }); - - it('non-operators can be revoked', async function () { - expect(await this.token.isOperatorFor(newOperator, holder)).to.equal(false); - - const receipt = await this.token.revokeOperator(newOperator, { from: holder }); - expectEvent(receipt, 'RevokedOperator', { operator: newOperator, tokenHolder: holder }); - - expect(await this.token.isOperatorFor(newOperator, holder)).to.equal(false); - }); - - it('non-operators can be authorized', async function () { - expect(await this.token.isOperatorFor(newOperator, holder)).to.equal(false); - - const receipt = await this.token.authorizeOperator(newOperator, { from: holder }); - expectEvent(receipt, 'AuthorizedOperator', { operator: newOperator, tokenHolder: holder }); - - expect(await this.token.isOperatorFor(newOperator, holder)).to.equal(true); - }); - - describe('new operators', function () { - beforeEach(async function () { - await this.token.authorizeOperator(newOperator, { from: holder }); - }); - - it('are not added to the default operators list', async function () { - expect(await this.token.defaultOperators()).to.deep.equal(defaultOperators); - }); - - it('can be re-authorized', async function () { - const receipt = await this.token.authorizeOperator(newOperator, { from: holder }); - expectEvent(receipt, 'AuthorizedOperator', { operator: newOperator, tokenHolder: holder }); - - expect(await this.token.isOperatorFor(newOperator, holder)).to.equal(true); - }); - - it('can be revoked', async function () { - const receipt = await this.token.revokeOperator(newOperator, { from: holder }); - expectEvent(receipt, 'RevokedOperator', { operator: newOperator, tokenHolder: holder }); - - expect(await this.token.isOperatorFor(newOperator, holder)).to.equal(false); - }); - }); - - describe('default operators', function () { - it('can be re-authorized', async function () { - const receipt = await this.token.authorizeOperator(defaultOperatorA, { from: holder }); - expectEvent(receipt, 'AuthorizedOperator', { operator: defaultOperatorA, tokenHolder: holder }); - - expect(await this.token.isOperatorFor(defaultOperatorA, holder)).to.equal(true); - }); - - it('can be revoked', async function () { - const receipt = await this.token.revokeOperator(defaultOperatorA, { from: holder }); - expectEvent(receipt, 'RevokedOperator', { operator: defaultOperatorA, tokenHolder: holder }); - - expect(await this.token.isOperatorFor(defaultOperatorA, holder)).to.equal(false); - }); - - it('cannot be revoked for themselves', async function () { - await expectRevert( - this.token.revokeOperator(defaultOperatorA, { from: defaultOperatorA }), - 'ERC777: revoking self as operator', - ); - }); - - context('with revoked default operator', function () { - beforeEach(async function () { - await this.token.revokeOperator(defaultOperatorA, { from: holder }); - }); - - it('default operator is not revoked for other holders', async function () { - expect(await this.token.isOperatorFor(defaultOperatorA, anyone)).to.equal(true); - }); - - it('other default operators are not revoked', async function () { - expect(await this.token.isOperatorFor(defaultOperatorB, holder)).to.equal(true); - }); - - it('default operators list is not modified', async function () { - expect(await this.token.defaultOperators()).to.deep.equal(defaultOperators); - }); - - it('revoked default operator can be re-authorized', async function () { - const receipt = await this.token.authorizeOperator(defaultOperatorA, { from: holder }); - expectEvent(receipt, 'AuthorizedOperator', { operator: defaultOperatorA, tokenHolder: holder }); - - expect(await this.token.isOperatorFor(defaultOperatorA, holder)).to.equal(true); - }); - }); - }); - }); - - describe('send and receive hooks', function () { - const amount = new BN('1'); - const operator = defaultOperatorA; - // sender and recipient are stored inside 'this', since in some tests their addresses are determined dynamically - - describe('tokensReceived', function () { - beforeEach(function () { - this.sender = holder; - }); - - context('with no ERC777TokensRecipient implementer', function () { - context('with contract recipient', function () { - beforeEach(async function () { - this.tokensRecipientImplementer = await ERC777SenderRecipientMock.new(); - this.recipient = this.tokensRecipientImplementer.address; - - // Note that tokensRecipientImplementer doesn't implement the recipient interface for the recipient - }); - - it('send reverts', async function () { - await expectRevert( - this.token.send(this.recipient, amount, data, { from: holder }), - 'ERC777: token recipient contract has no implementer for ERC777TokensRecipient', - ); - }); - - it('operatorSend reverts', async function () { - await expectRevert( - this.token.operatorSend(this.sender, this.recipient, amount, data, operatorData, { from: operator }), - 'ERC777: token recipient contract has no implementer for ERC777TokensRecipient', - ); - }); - - it('mint (internal) reverts', async function () { - await expectRevert( - this.token.$_mint(this.recipient, amount, data, operatorData, true, { from: operator }), - 'ERC777: token recipient contract has no implementer for ERC777TokensRecipient', - ); - }); - - it('(ERC20) transfer succeeds', async function () { - await this.token.transfer(this.recipient, amount, { from: holder }); - }); - - it('(ERC20) transferFrom succeeds', async function () { - const approved = anyone; - await this.token.approve(approved, amount, { from: this.sender }); - await this.token.transferFrom(this.sender, this.recipient, amount, { from: approved }); - }); - }); - }); - - context('with ERC777TokensRecipient implementer', function () { - context('with contract as implementer for an externally owned account', function () { - beforeEach(async function () { - this.tokensRecipientImplementer = await ERC777SenderRecipientMock.new(); - this.recipient = anyone; - - await this.tokensRecipientImplementer.recipientFor(this.recipient); - - await this.erc1820.setInterfaceImplementer( - this.recipient, - web3.utils.soliditySha3('ERC777TokensRecipient'), - this.tokensRecipientImplementer.address, - { from: this.recipient }, - ); - }); - - shouldBehaveLikeERC777SendBurnMintInternalWithReceiveHook(operator, amount, data, operatorData); - }); - - context('with contract as implementer for another contract', function () { - beforeEach(async function () { - this.recipientContract = await ERC777SenderRecipientMock.new(); - this.recipient = this.recipientContract.address; - - this.tokensRecipientImplementer = await ERC777SenderRecipientMock.new(); - await this.tokensRecipientImplementer.recipientFor(this.recipient); - await this.recipientContract.registerRecipient(this.tokensRecipientImplementer.address); - }); - - shouldBehaveLikeERC777SendBurnMintInternalWithReceiveHook(operator, amount, data, operatorData); - }); - - context('with contract as implementer for itself', function () { - beforeEach(async function () { - this.tokensRecipientImplementer = await ERC777SenderRecipientMock.new(); - this.recipient = this.tokensRecipientImplementer.address; - - await this.tokensRecipientImplementer.recipientFor(this.recipient); - }); - - shouldBehaveLikeERC777SendBurnMintInternalWithReceiveHook(operator, amount, data, operatorData); - }); - }); - }); - - describe('tokensToSend', function () { - beforeEach(function () { - this.recipient = anyone; - }); - - context('with a contract as implementer for an externally owned account', function () { - beforeEach(async function () { - this.tokensSenderImplementer = await ERC777SenderRecipientMock.new(); - this.sender = holder; - - await this.tokensSenderImplementer.senderFor(this.sender); - - await this.erc1820.setInterfaceImplementer( - this.sender, - web3.utils.soliditySha3('ERC777TokensSender'), - this.tokensSenderImplementer.address, - { from: this.sender }, - ); - }); - - shouldBehaveLikeERC777SendBurnWithSendHook(operator, amount, data, operatorData); - }); - - context('with contract as implementer for another contract', function () { - beforeEach(async function () { - this.senderContract = await ERC777SenderRecipientMock.new(); - this.sender = this.senderContract.address; - - this.tokensSenderImplementer = await ERC777SenderRecipientMock.new(); - await this.tokensSenderImplementer.senderFor(this.sender); - await this.senderContract.registerSender(this.tokensSenderImplementer.address); - - // For the contract to be able to receive tokens (that it can later send), it must also implement the - // recipient interface. - - await this.senderContract.recipientFor(this.sender); - await this.token.send(this.sender, amount, data, { from: holder }); - }); - - shouldBehaveLikeERC777SendBurnWithSendHook(operator, amount, data, operatorData); - }); - - context('with a contract as implementer for itself', function () { - beforeEach(async function () { - this.tokensSenderImplementer = await ERC777SenderRecipientMock.new(); - this.sender = this.tokensSenderImplementer.address; - - await this.tokensSenderImplementer.senderFor(this.sender); - - // For the contract to be able to receive tokens (that it can later send), it must also implement the - // recipient interface. - - await this.tokensSenderImplementer.recipientFor(this.sender); - await this.token.send(this.sender, amount, data, { from: holder }); - }); - - shouldBehaveLikeERC777SendBurnWithSendHook(operator, amount, data, operatorData); - }); - }); - }); - }); - - context('with no default operators', function () { - beforeEach(async function () { - this.token = await ERC777.new(name, symbol, []); - }); - - it('default operators list is empty', async function () { - expect(await this.token.defaultOperators()).to.deep.equal([]); - }); - }); - - describe('relative order of hooks', function () { - beforeEach(async function () { - await singletons.ERC1820Registry(registryFunder); - this.sender = await ERC777SenderRecipientMock.new(); - await this.sender.registerRecipient(this.sender.address); - await this.sender.registerSender(this.sender.address); - this.token = await ERC777.new(name, symbol, []); - await this.token.$_mint(this.sender.address, 1, '0x', '0x'); - }); - - it('send', async function () { - const { receipt } = await this.sender.send(this.token.address, anyone, 1, '0x'); - - const internalBeforeHook = receipt.logs.findIndex(l => l.event === 'BeforeTokenTransfer'); - expect(internalBeforeHook).to.be.gte(0); - const externalSendHook = receipt.logs.findIndex(l => l.event === 'TokensToSendCalled'); - expect(externalSendHook).to.be.gte(0); - - expect(externalSendHook).to.be.lt(internalBeforeHook); - }); - - it('burn', async function () { - const { receipt } = await this.sender.burn(this.token.address, 1, '0x'); - - const internalBeforeHook = receipt.logs.findIndex(l => l.event === 'BeforeTokenTransfer'); - expect(internalBeforeHook).to.be.gte(0); - const externalSendHook = receipt.logs.findIndex(l => l.event === 'TokensToSendCalled'); - expect(externalSendHook).to.be.gte(0); - - expect(externalSendHook).to.be.lt(internalBeforeHook); - }); - }); -}); diff --git a/test/utils/Checkpoints.t.sol b/test/utils/Checkpoints.t.sol index f2cb587d5..12598da46 100644 --- a/test/utils/Checkpoints.t.sol +++ b/test/utils/Checkpoints.t.sol @@ -7,129 +7,6 @@ import "forge-std/Test.sol"; import "../../contracts/utils/Checkpoints.sol"; import "../../contracts/utils/math/SafeCast.sol"; -contract CheckpointsHistoryTest is Test { - using Checkpoints for Checkpoints.History; - - // Maximum gap between keys used during the fuzzing tests: the `_prepareKeys` function with make sure that - // key#n+1 is in the [key#n, key#n + _KEY_MAX_GAP] range. - uint8 internal constant _KEY_MAX_GAP = 64; - - Checkpoints.History internal _ckpts; - - // helpers - function _boundUint32(uint32 x, uint32 min, uint32 max) internal view returns (uint32) { - return SafeCast.toUint32(bound(uint256(x), uint256(min), uint256(max))); - } - - function _prepareKeys(uint32[] memory keys, uint32 maxSpread) internal view { - uint32 lastKey = 0; - for (uint256 i = 0; i < keys.length; ++i) { - uint32 key = _boundUint32(keys[i], lastKey, lastKey + maxSpread); - keys[i] = key; - lastKey = key; - } - } - - function _assertLatestCheckpoint(bool exist, uint32 key, uint224 value) internal { - (bool _exist, uint32 _key, uint224 _value) = _ckpts.latestCheckpoint(); - assertEq(_exist, exist); - assertEq(_key, key); - assertEq(_value, value); - } - - // tests - function testPush(uint32[] memory keys, uint224[] memory values, uint32 pastKey) public { - vm.assume(values.length > 0 && values.length <= keys.length); - _prepareKeys(keys, _KEY_MAX_GAP); - - // initial state - assertEq(_ckpts.length(), 0); - assertEq(_ckpts.latest(), 0); - _assertLatestCheckpoint(false, 0, 0); - - uint256 duplicates = 0; - for (uint256 i = 0; i < keys.length; ++i) { - uint32 key = keys[i]; - uint224 value = values[i % values.length]; - if (i > 0 && key == keys[i - 1]) ++duplicates; - - // push - vm.roll(key); - _ckpts.push(value); - - // check length & latest - assertEq(_ckpts.length(), i + 1 - duplicates); - assertEq(_ckpts.latest(), value); - _assertLatestCheckpoint(true, key, value); - } - - // Can't push any key in the past - if (keys.length > 0) { - uint32 lastKey = keys[keys.length - 1]; - if (lastKey > 0) { - pastKey = _boundUint32(pastKey, 0, lastKey - 1); - - vm.roll(pastKey); - vm.expectRevert(); - this.push(values[keys.length % values.length]); - } - } - } - - // used to test reverts - function push(uint224 value) external { - _ckpts.push(value); - } - - function testLookup(uint32[] memory keys, uint224[] memory values, uint32 lookup) public { - vm.assume(keys.length > 0); - vm.assume(values.length > 0 && values.length <= keys.length); - _prepareKeys(keys, _KEY_MAX_GAP); - - uint32 lastKey = keys[keys.length - 1]; - vm.assume(lastKey > 0); - lookup = _boundUint32(lookup, 0, lastKey - 1); - - uint224 upper = 0; - for (uint256 i = 0; i < keys.length; ++i) { - uint32 key = keys[i]; - uint224 value = values[i % values.length]; - - // push - vm.roll(key); - _ckpts.push(value); - - // track expected result of lookups - if (key <= lookup) { - upper = value; - } - } - - // check lookup - assertEq(_ckpts.getAtBlock(lookup), upper); - assertEq(_ckpts.getAtProbablyRecentBlock(lookup), upper); - - vm.expectRevert(); - this.getAtBlock(lastKey); - vm.expectRevert(); - this.getAtBlock(lastKey + 1); - vm.expectRevert(); - this.getAtProbablyRecentBlock(lastKey); - vm.expectRevert(); - this.getAtProbablyRecentBlock(lastKey + 1); - } - - // used to test reverts - function getAtBlock(uint32 key) external view { - _ckpts.getAtBlock(key); - } - - // used to test reverts - function getAtProbablyRecentBlock(uint32 key) external view { - _ckpts.getAtProbablyRecentBlock(key); - } -} - contract CheckpointsTrace224Test is Test { using Checkpoints for Checkpoints.Trace224; diff --git a/test/utils/Checkpoints.test.js b/test/utils/Checkpoints.test.js index f395663be..d9fd2c8ea 100644 --- a/test/utils/Checkpoints.test.js +++ b/test/utils/Checkpoints.test.js @@ -1,166 +1,51 @@ -const { expectRevert, time } = require('@openzeppelin/test-helpers'); - +const { expectRevert } = require('@openzeppelin/test-helpers'); const { expect } = require('chai'); -const { batchInBlock } = require('../helpers/txpool'); +const { VALUE_SIZES } = require('../../scripts/generate/templates/Checkpoints.opts.js'); const $Checkpoints = artifacts.require('$Checkpoints'); // The library name may be 'Checkpoints' or 'CheckpointsUpgradeable' const libraryName = $Checkpoints._json.contractName.replace(/^\$/, ''); -const traceLengths = [160, 224]; - const first = array => (array.length ? array[0] : undefined); const last = array => (array.length ? array[array.length - 1] : undefined); contract('Checkpoints', function () { beforeEach(async function () { this.mock = await $Checkpoints.new(); - - this.methods = { trace: {} }; - this.methods.history = { - latest: (...args) => this.mock.methods[`$latest_${libraryName}_History(uint256)`](0, ...args), - latestCheckpoint: (...args) => this.mock.methods[`$latestCheckpoint_${libraryName}_History(uint256)`](0, ...args), - length: (...args) => this.mock.methods[`$length_${libraryName}_History(uint256)`](0, ...args), - push: (...args) => this.mock.methods['$push(uint256,uint256)'](0, ...args), - getAtBlock: (...args) => this.mock.$getAtBlock(0, ...args), - getAtRecentBlock: (...args) => this.mock.$getAtProbablyRecentBlock(0, ...args), - }; - for (const length of traceLengths) { - this.methods.trace[length] = { - latest: (...args) => this.mock.methods[`$latest_${libraryName}_Trace${length}(uint256)`](0, ...args), - latestCheckpoint: (...args) => - this.mock.methods[`$latestCheckpoint_${libraryName}_Trace${length}(uint256)`](0, ...args), - length: (...args) => this.mock.methods[`$length_${libraryName}_Trace${length}(uint256)`](0, ...args), - push: (...args) => this.mock.methods[`$push(uint256,uint${256 - length},uint${length})`](0, ...args), - lowerLookup: (...args) => this.mock.methods[`$lowerLookup(uint256,uint${256 - length})`](0, ...args), - upperLookup: (...args) => this.mock.methods[`$upperLookup(uint256,uint${256 - length})`](0, ...args), - upperLookupRecent: (...args) => - this.mock.methods[`$upperLookupRecent(uint256,uint${256 - length})`](0, ...args), - }; - } }); - describe('History checkpoints', function () { - describe('without checkpoints', function () { - it('returns zero as latest value', async function () { - expect(await this.methods.history.latest()).to.be.bignumber.equal('0'); - - const ckpt = await this.methods.history.latestCheckpoint(); - expect(ckpt[0]).to.be.equal(false); - expect(ckpt[1]).to.be.bignumber.equal('0'); - expect(ckpt[2]).to.be.bignumber.equal('0'); - }); - - it('returns zero as past value', async function () { - await time.advanceBlock(); - expect(await this.methods.history.getAtBlock((await web3.eth.getBlockNumber()) - 1)).to.be.bignumber.equal('0'); - expect( - await this.methods.history.getAtRecentBlock((await web3.eth.getBlockNumber()) - 1), - ).to.be.bignumber.equal('0'); - }); - }); - - describe('with checkpoints', function () { - beforeEach('pushing checkpoints', async function () { - this.tx1 = await this.methods.history.push(1); - this.tx2 = await this.methods.history.push(2); - await time.advanceBlock(); - this.tx3 = await this.methods.history.push(3); - await time.advanceBlock(); - await time.advanceBlock(); - }); - - it('returns latest value', async function () { - expect(await this.methods.history.latest()).to.be.bignumber.equal('3'); - - const ckpt = await this.methods.history.latestCheckpoint(); - expect(ckpt[0]).to.be.equal(true); - expect(ckpt[1]).to.be.bignumber.equal(web3.utils.toBN(this.tx3.receipt.blockNumber)); - expect(ckpt[2]).to.be.bignumber.equal(web3.utils.toBN('3')); - }); - - for (const getAtBlockVariant of ['getAtBlock', 'getAtRecentBlock']) { - describe(`lookup: ${getAtBlockVariant}`, function () { - it('returns past values', async function () { - expect( - await this.methods.history[getAtBlockVariant](this.tx1.receipt.blockNumber - 1), - ).to.be.bignumber.equal('0'); - expect(await this.methods.history[getAtBlockVariant](this.tx1.receipt.blockNumber)).to.be.bignumber.equal( - '1', - ); - expect(await this.methods.history[getAtBlockVariant](this.tx2.receipt.blockNumber)).to.be.bignumber.equal( - '2', - ); - // Block with no new checkpoints - expect( - await this.methods.history[getAtBlockVariant](this.tx2.receipt.blockNumber + 1), - ).to.be.bignumber.equal('2'); - expect(await this.methods.history[getAtBlockVariant](this.tx3.receipt.blockNumber)).to.be.bignumber.equal( - '3', - ); - expect( - await this.methods.history[getAtBlockVariant](this.tx3.receipt.blockNumber + 1), - ).to.be.bignumber.equal('3'); - }); - it('reverts if block number >= current block', async function () { - await expectRevert( - this.methods.history[getAtBlockVariant](await web3.eth.getBlockNumber()), - 'Checkpoints: block not yet mined', - ); - - await expectRevert( - this.methods.history[getAtBlockVariant]((await web3.eth.getBlockNumber()) + 1), - 'Checkpoints: block not yet mined', - ); - }); - }); - } - - it('multiple checkpoints in the same block', async function () { - const lengthBefore = await this.methods.history.length(); - - await batchInBlock([ - () => this.methods.history.push(8, { gas: 100000 }), - () => this.methods.history.push(9, { gas: 100000 }), - () => this.methods.history.push(10, { gas: 100000 }), - ]); - - expect(await this.methods.history.length()).to.be.bignumber.equal(lengthBefore.addn(1)); - expect(await this.methods.history.latest()).to.be.bignumber.equal('10'); - }); - - it('more than 5 checkpoints', async function () { - for (let i = 4; i <= 6; i++) { - await this.methods.history.push(i); - } - expect(await this.methods.history.length()).to.be.bignumber.equal('6'); - const block = await web3.eth.getBlockNumber(); - // recent - expect(await this.methods.history.getAtRecentBlock(block - 1)).to.be.bignumber.equal('5'); - // non-recent - expect(await this.methods.history.getAtRecentBlock(block - 9)).to.be.bignumber.equal('0'); + for (const length of VALUE_SIZES) { + describe(`Trace${length}`, function () { + beforeEach(async function () { + this.methods = { + latest: (...args) => this.mock.methods[`$latest_${libraryName}_Trace${length}(uint256)`](0, ...args), + latestCheckpoint: (...args) => + this.mock.methods[`$latestCheckpoint_${libraryName}_Trace${length}(uint256)`](0, ...args), + length: (...args) => this.mock.methods[`$length_${libraryName}_Trace${length}(uint256)`](0, ...args), + push: (...args) => this.mock.methods[`$push(uint256,uint${256 - length},uint${length})`](0, ...args), + lowerLookup: (...args) => this.mock.methods[`$lowerLookup(uint256,uint${256 - length})`](0, ...args), + upperLookup: (...args) => this.mock.methods[`$upperLookup(uint256,uint${256 - length})`](0, ...args), + upperLookupRecent: (...args) => + this.mock.methods[`$upperLookupRecent(uint256,uint${256 - length})`](0, ...args), + }; }); - }); - }); - for (const length of traceLengths) { - describe(`Trace${length}`, function () { describe('without checkpoints', function () { it('returns zero as latest value', async function () { - expect(await this.methods.trace[length].latest()).to.be.bignumber.equal('0'); + expect(await this.methods.latest()).to.be.bignumber.equal('0'); - const ckpt = await this.methods.trace[length].latestCheckpoint(); + const ckpt = await this.methods.latestCheckpoint(); expect(ckpt[0]).to.be.equal(false); expect(ckpt[1]).to.be.bignumber.equal('0'); expect(ckpt[2]).to.be.bignumber.equal('0'); }); it('lookup returns 0', async function () { - expect(await this.methods.trace[length].lowerLookup(0)).to.be.bignumber.equal('0'); - expect(await this.methods.trace[length].upperLookup(0)).to.be.bignumber.equal('0'); - expect(await this.methods.trace[length].upperLookupRecent(0)).to.be.bignumber.equal('0'); + expect(await this.methods.lowerLookup(0)).to.be.bignumber.equal('0'); + expect(await this.methods.upperLookup(0)).to.be.bignumber.equal('0'); + expect(await this.methods.upperLookupRecent(0)).to.be.bignumber.equal('0'); }); }); @@ -174,49 +59,46 @@ contract('Checkpoints', function () { { key: '11', value: '99' }, ]; for (const { key, value } of this.checkpoints) { - await this.methods.trace[length].push(key, value); + await this.methods.push(key, value); } }); it('length', async function () { - expect(await this.methods.trace[length].length()).to.be.bignumber.equal(this.checkpoints.length.toString()); + expect(await this.methods.length()).to.be.bignumber.equal(this.checkpoints.length.toString()); }); it('returns latest value', async function () { - expect(await this.methods.trace[length].latest()).to.be.bignumber.equal(last(this.checkpoints).value); + expect(await this.methods.latest()).to.be.bignumber.equal(last(this.checkpoints).value); - const ckpt = await this.methods.trace[length].latestCheckpoint(); + const ckpt = await this.methods.latestCheckpoint(); expect(ckpt[0]).to.be.equal(true); expect(ckpt[1]).to.be.bignumber.equal(last(this.checkpoints).key); expect(ckpt[2]).to.be.bignumber.equal(last(this.checkpoints).value); }); it('cannot push values in the past', async function () { - await expectRevert( - this.methods.trace[length].push(last(this.checkpoints).key - 1, '0'), - 'Checkpoint: decreasing keys', - ); + await expectRevert(this.methods.push(last(this.checkpoints).key - 1, '0'), 'Checkpoint: decreasing keys'); }); it('can update last value', async function () { const newValue = '42'; // check length before the update - expect(await this.methods.trace[length].length()).to.be.bignumber.equal(this.checkpoints.length.toString()); + expect(await this.methods.length()).to.be.bignumber.equal(this.checkpoints.length.toString()); // update last key - await this.methods.trace[length].push(last(this.checkpoints).key, newValue); - expect(await this.methods.trace[length].latest()).to.be.bignumber.equal(newValue); + await this.methods.push(last(this.checkpoints).key, newValue); + expect(await this.methods.latest()).to.be.bignumber.equal(newValue); // check that length did not change - expect(await this.methods.trace[length].length()).to.be.bignumber.equal(this.checkpoints.length.toString()); + expect(await this.methods.length()).to.be.bignumber.equal(this.checkpoints.length.toString()); }); it('lower lookup', async function () { for (let i = 0; i < 14; ++i) { const value = first(this.checkpoints.filter(x => i <= x.key))?.value || '0'; - expect(await this.methods.trace[length].lowerLookup(i)).to.be.bignumber.equal(value); + expect(await this.methods.lowerLookup(i)).to.be.bignumber.equal(value); } }); @@ -224,8 +106,8 @@ contract('Checkpoints', function () { for (let i = 0; i < 14; ++i) { const value = last(this.checkpoints.filter(x => i >= x.key))?.value || '0'; - expect(await this.methods.trace[length].upperLookup(i)).to.be.bignumber.equal(value); - expect(await this.methods.trace[length].upperLookupRecent(i)).to.be.bignumber.equal(value); + expect(await this.methods.upperLookup(i)).to.be.bignumber.equal(value); + expect(await this.methods.upperLookupRecent(i)).to.be.bignumber.equal(value); } }); @@ -240,13 +122,13 @@ contract('Checkpoints', function () { const allCheckpoints = [].concat(this.checkpoints, moreCheckpoints); for (const { key, value } of moreCheckpoints) { - await this.methods.trace[length].push(key, value); + await this.methods.push(key, value); } for (let i = 0; i < 25; ++i) { const value = last(allCheckpoints.filter(x => i >= x.key))?.value || '0'; - expect(await this.methods.trace[length].upperLookup(i)).to.be.bignumber.equal(value); - expect(await this.methods.trace[length].upperLookupRecent(i)).to.be.bignumber.equal(value); + expect(await this.methods.upperLookup(i)).to.be.bignumber.equal(value); + expect(await this.methods.upperLookupRecent(i)).to.be.bignumber.equal(value); } }); }); diff --git a/test/utils/Create2.test.js b/test/utils/Create2.test.js index 2fc27dc15..526602600 100644 --- a/test/utils/Create2.test.js +++ b/test/utils/Create2.test.js @@ -4,7 +4,10 @@ const { expect } = require('chai'); const Create2 = artifacts.require('$Create2'); const VestingWallet = artifacts.require('VestingWallet'); -const ERC1820Implementer = artifacts.require('$ERC1820Implementer'); +// This should be a contract that: +// - has no constructor arguments +// - has no immutable variable populated during construction +const ConstructorLessContract = Create2; contract('Create2', function (accounts) { const [deployerAccount, other] = accounts; @@ -38,14 +41,14 @@ contract('Create2', function (accounts) { }); describe('deploy', function () { - it('deploys a ERC1820Implementer from inline assembly code', async function () { - const offChainComputed = computeCreate2Address(saltHex, ERC1820Implementer.bytecode, this.factory.address); + it('deploys a contract without constructor', async function () { + const offChainComputed = computeCreate2Address(saltHex, ConstructorLessContract.bytecode, this.factory.address); - expectEvent(await this.factory.$deploy(0, saltHex, ERC1820Implementer.bytecode), 'return$deploy', { + expectEvent(await this.factory.$deploy(0, saltHex, ConstructorLessContract.bytecode), 'return$deploy', { addr: offChainComputed, }); - expect(ERC1820Implementer.bytecode).to.include((await web3.eth.getCode(offChainComputed)).slice(2)); + expect(ConstructorLessContract.bytecode).to.include((await web3.eth.getCode(offChainComputed)).slice(2)); }); it('deploys a contract with constructor arguments', async function () { diff --git a/test/utils/TimersBlockNumberImpl.test.js b/test/utils/TimersBlockNumberImpl.test.js deleted file mode 100644 index ee0d54bfb..000000000 --- a/test/utils/TimersBlockNumberImpl.test.js +++ /dev/null @@ -1,55 +0,0 @@ -const { BN, time } = require('@openzeppelin/test-helpers'); -const { expect } = require('chai'); - -const TimersBlockNumberImpl = artifacts.require('TimersBlockNumberImpl'); - -contract('TimersBlockNumber', function () { - beforeEach(async function () { - this.instance = await TimersBlockNumberImpl.new(); - this.now = await web3.eth.getBlock('latest').then(({ number }) => number); - }); - - it('unset', async function () { - expect(await this.instance.getDeadline()).to.be.bignumber.equal('0'); - expect(await this.instance.isUnset()).to.be.equal(true); - expect(await this.instance.isStarted()).to.be.equal(false); - expect(await this.instance.isPending()).to.be.equal(false); - expect(await this.instance.isExpired()).to.be.equal(false); - }); - - it('pending', async function () { - await this.instance.setDeadline(this.now + 3); - expect(await this.instance.getDeadline()).to.be.bignumber.equal(new BN(this.now + 3)); - expect(await this.instance.isUnset()).to.be.equal(false); - expect(await this.instance.isStarted()).to.be.equal(true); - expect(await this.instance.isPending()).to.be.equal(true); - expect(await this.instance.isExpired()).to.be.equal(false); - }); - - it('expired', async function () { - await this.instance.setDeadline(this.now - 3); - expect(await this.instance.getDeadline()).to.be.bignumber.equal(new BN(this.now - 3)); - expect(await this.instance.isUnset()).to.be.equal(false); - expect(await this.instance.isStarted()).to.be.equal(true); - expect(await this.instance.isPending()).to.be.equal(false); - expect(await this.instance.isExpired()).to.be.equal(true); - }); - - it('reset', async function () { - await this.instance.reset(); - expect(await this.instance.getDeadline()).to.be.bignumber.equal(new BN(0)); - expect(await this.instance.isUnset()).to.be.equal(true); - expect(await this.instance.isStarted()).to.be.equal(false); - expect(await this.instance.isPending()).to.be.equal(false); - expect(await this.instance.isExpired()).to.be.equal(false); - }); - - it('fast forward', async function () { - await this.instance.setDeadline(this.now + 3); - expect(await this.instance.isPending()).to.be.equal(true); - expect(await this.instance.isExpired()).to.be.equal(false); - await time.advanceBlockTo(this.now + 3); - expect(await this.instance.isPending()).to.be.equal(false); - expect(await this.instance.isExpired()).to.be.equal(true); - }); -}); diff --git a/test/utils/TimersTimestamp.test.js b/test/utils/TimersTimestamp.test.js deleted file mode 100644 index fc32de7d0..000000000 --- a/test/utils/TimersTimestamp.test.js +++ /dev/null @@ -1,55 +0,0 @@ -const { BN, time } = require('@openzeppelin/test-helpers'); -const { expect } = require('chai'); - -const TimersTimestampImpl = artifacts.require('TimersTimestampImpl'); - -contract('TimersTimestamp', function () { - beforeEach(async function () { - this.instance = await TimersTimestampImpl.new(); - this.now = await web3.eth.getBlock('latest').then(({ timestamp }) => timestamp); - }); - - it('unset', async function () { - expect(await this.instance.getDeadline()).to.be.bignumber.equal('0'); - expect(await this.instance.isUnset()).to.be.equal(true); - expect(await this.instance.isStarted()).to.be.equal(false); - expect(await this.instance.isPending()).to.be.equal(false); - expect(await this.instance.isExpired()).to.be.equal(false); - }); - - it('pending', async function () { - await this.instance.setDeadline(this.now + 100); - expect(await this.instance.getDeadline()).to.be.bignumber.equal(new BN(this.now + 100)); - expect(await this.instance.isUnset()).to.be.equal(false); - expect(await this.instance.isStarted()).to.be.equal(true); - expect(await this.instance.isPending()).to.be.equal(true); - expect(await this.instance.isExpired()).to.be.equal(false); - }); - - it('expired', async function () { - await this.instance.setDeadline(this.now - 100); - expect(await this.instance.getDeadline()).to.be.bignumber.equal(new BN(this.now - 100)); - expect(await this.instance.isUnset()).to.be.equal(false); - expect(await this.instance.isStarted()).to.be.equal(true); - expect(await this.instance.isPending()).to.be.equal(false); - expect(await this.instance.isExpired()).to.be.equal(true); - }); - - it('reset', async function () { - await this.instance.reset(); - expect(await this.instance.getDeadline()).to.be.bignumber.equal(new BN(0)); - expect(await this.instance.isUnset()).to.be.equal(true); - expect(await this.instance.isStarted()).to.be.equal(false); - expect(await this.instance.isPending()).to.be.equal(false); - expect(await this.instance.isExpired()).to.be.equal(false); - }); - - it('fast forward', async function () { - await this.instance.setDeadline(this.now + 100); - expect(await this.instance.isPending()).to.be.equal(true); - expect(await this.instance.isExpired()).to.be.equal(false); - await time.increaseTo(this.now + 100); - expect(await this.instance.isPending()).to.be.equal(false); - expect(await this.instance.isExpired()).to.be.equal(true); - }); -}); diff --git a/test/utils/escrow/ConditionalEscrow.test.js b/test/utils/escrow/ConditionalEscrow.test.js deleted file mode 100644 index 9e17a8bce..000000000 --- a/test/utils/escrow/ConditionalEscrow.test.js +++ /dev/null @@ -1,37 +0,0 @@ -const { ether, expectRevert } = require('@openzeppelin/test-helpers'); -const { shouldBehaveLikeEscrow } = require('./Escrow.behavior'); - -const ConditionalEscrowMock = artifacts.require('ConditionalEscrowMock'); - -contract('ConditionalEscrow', function (accounts) { - const [owner, payee, ...otherAccounts] = accounts; - - beforeEach(async function () { - this.escrow = await ConditionalEscrowMock.new({ from: owner }); - }); - - context('when withdrawal is allowed', function () { - beforeEach(async function () { - await Promise.all(otherAccounts.map(payee => this.escrow.setAllowed(payee, true))); - }); - - shouldBehaveLikeEscrow(owner, otherAccounts); - }); - - context('when withdrawal is disallowed', function () { - const amount = ether('23'); - - beforeEach(async function () { - await this.escrow.setAllowed(payee, false); - }); - - it('reverts on withdrawals', async function () { - await this.escrow.deposit(payee, { from: owner, value: amount }); - - await expectRevert( - this.escrow.withdraw(payee, { from: owner }), - 'ConditionalEscrow: payee is not allowed to withdraw', - ); - }); - }); -}); diff --git a/test/utils/escrow/Escrow.behavior.js b/test/utils/escrow/Escrow.behavior.js deleted file mode 100644 index f2059ed62..000000000 --- a/test/utils/escrow/Escrow.behavior.js +++ /dev/null @@ -1,90 +0,0 @@ -const { balance, ether, expectEvent, expectRevert } = require('@openzeppelin/test-helpers'); - -const { expect } = require('chai'); - -function shouldBehaveLikeEscrow(owner, [payee1, payee2]) { - const amount = ether('42'); - - describe('as an escrow', function () { - describe('deposits', function () { - it('can accept a single deposit', async function () { - await this.escrow.deposit(payee1, { from: owner, value: amount }); - - expect(await balance.current(this.escrow.address)).to.be.bignumber.equal(amount); - - expect(await this.escrow.depositsOf(payee1)).to.be.bignumber.equal(amount); - }); - - it('can accept an empty deposit', async function () { - await this.escrow.deposit(payee1, { from: owner, value: 0 }); - }); - - it('only the owner can deposit', async function () { - await expectRevert(this.escrow.deposit(payee1, { from: payee2 }), 'Ownable: caller is not the owner'); - }); - - it('emits a deposited event', async function () { - const receipt = await this.escrow.deposit(payee1, { from: owner, value: amount }); - expectEvent(receipt, 'Deposited', { - payee: payee1, - weiAmount: amount, - }); - }); - - it('can add multiple deposits on a single account', async function () { - await this.escrow.deposit(payee1, { from: owner, value: amount }); - await this.escrow.deposit(payee1, { from: owner, value: amount.muln(2) }); - - expect(await balance.current(this.escrow.address)).to.be.bignumber.equal(amount.muln(3)); - - expect(await this.escrow.depositsOf(payee1)).to.be.bignumber.equal(amount.muln(3)); - }); - - it('can track deposits to multiple accounts', async function () { - await this.escrow.deposit(payee1, { from: owner, value: amount }); - await this.escrow.deposit(payee2, { from: owner, value: amount.muln(2) }); - - expect(await balance.current(this.escrow.address)).to.be.bignumber.equal(amount.muln(3)); - - expect(await this.escrow.depositsOf(payee1)).to.be.bignumber.equal(amount); - - expect(await this.escrow.depositsOf(payee2)).to.be.bignumber.equal(amount.muln(2)); - }); - }); - - describe('withdrawals', async function () { - it('can withdraw payments', async function () { - const balanceTracker = await balance.tracker(payee1); - - await this.escrow.deposit(payee1, { from: owner, value: amount }); - await this.escrow.withdraw(payee1, { from: owner }); - - expect(await balanceTracker.delta()).to.be.bignumber.equal(amount); - - expect(await balance.current(this.escrow.address)).to.be.bignumber.equal('0'); - expect(await this.escrow.depositsOf(payee1)).to.be.bignumber.equal('0'); - }); - - it('can do an empty withdrawal', async function () { - await this.escrow.withdraw(payee1, { from: owner }); - }); - - it('only the owner can withdraw', async function () { - await expectRevert(this.escrow.withdraw(payee1, { from: payee1 }), 'Ownable: caller is not the owner'); - }); - - it('emits a withdrawn event', async function () { - await this.escrow.deposit(payee1, { from: owner, value: amount }); - const receipt = await this.escrow.withdraw(payee1, { from: owner }); - expectEvent(receipt, 'Withdrawn', { - payee: payee1, - weiAmount: amount, - }); - }); - }); - }); -} - -module.exports = { - shouldBehaveLikeEscrow, -}; diff --git a/test/utils/escrow/Escrow.test.js b/test/utils/escrow/Escrow.test.js deleted file mode 100644 index 47627904b..000000000 --- a/test/utils/escrow/Escrow.test.js +++ /dev/null @@ -1,14 +0,0 @@ -require('@openzeppelin/test-helpers'); -const { shouldBehaveLikeEscrow } = require('./Escrow.behavior'); - -const Escrow = artifacts.require('Escrow'); - -contract('Escrow', function (accounts) { - const [owner, ...otherAccounts] = accounts; - - beforeEach(async function () { - this.escrow = await Escrow.new({ from: owner }); - }); - - shouldBehaveLikeEscrow(owner, otherAccounts); -}); diff --git a/test/utils/escrow/RefundEscrow.test.js b/test/utils/escrow/RefundEscrow.test.js deleted file mode 100644 index c5344db16..000000000 --- a/test/utils/escrow/RefundEscrow.test.js +++ /dev/null @@ -1,143 +0,0 @@ -const { balance, constants, ether, expectEvent, expectRevert } = require('@openzeppelin/test-helpers'); -const { ZERO_ADDRESS } = constants; - -const { expect } = require('chai'); - -const RefundEscrow = artifacts.require('RefundEscrow'); - -contract('RefundEscrow', function (accounts) { - const [owner, beneficiary, refundee1, refundee2] = accounts; - - const amount = ether('54'); - const refundees = [refundee1, refundee2]; - - it('requires a non-null beneficiary', async function () { - await expectRevert( - RefundEscrow.new(ZERO_ADDRESS, { from: owner }), - 'RefundEscrow: beneficiary is the zero address', - ); - }); - - context('once deployed', function () { - beforeEach(async function () { - this.escrow = await RefundEscrow.new(beneficiary, { from: owner }); - }); - - context('active state', function () { - it('has beneficiary and state', async function () { - expect(await this.escrow.beneficiary()).to.equal(beneficiary); - expect(await this.escrow.state()).to.be.bignumber.equal('0'); - }); - - it('accepts deposits', async function () { - await this.escrow.deposit(refundee1, { from: owner, value: amount }); - - expect(await this.escrow.depositsOf(refundee1)).to.be.bignumber.equal(amount); - }); - - it('does not refund refundees', async function () { - await this.escrow.deposit(refundee1, { from: owner, value: amount }); - await expectRevert(this.escrow.withdraw(refundee1), 'ConditionalEscrow: payee is not allowed to withdraw'); - }); - - it('does not allow beneficiary withdrawal', async function () { - await this.escrow.deposit(refundee1, { from: owner, value: amount }); - await expectRevert( - this.escrow.beneficiaryWithdraw(), - 'RefundEscrow: beneficiary can only withdraw while closed', - ); - }); - }); - - it('only the owner can enter closed state', async function () { - await expectRevert(this.escrow.close({ from: beneficiary }), 'Ownable: caller is not the owner'); - - const receipt = await this.escrow.close({ from: owner }); - expectEvent(receipt, 'RefundsClosed'); - }); - - context('closed state', function () { - beforeEach(async function () { - await Promise.all(refundees.map(refundee => this.escrow.deposit(refundee, { from: owner, value: amount }))); - - await this.escrow.close({ from: owner }); - }); - - it('rejects deposits', async function () { - await expectRevert( - this.escrow.deposit(refundee1, { from: owner, value: amount }), - 'RefundEscrow: can only deposit while active', - ); - }); - - it('does not refund refundees', async function () { - await expectRevert(this.escrow.withdraw(refundee1), 'ConditionalEscrow: payee is not allowed to withdraw'); - }); - - it('allows beneficiary withdrawal', async function () { - const balanceTracker = await balance.tracker(beneficiary); - await this.escrow.beneficiaryWithdraw(); - expect(await balanceTracker.delta()).to.be.bignumber.equal(amount.muln(refundees.length)); - }); - - it('prevents entering the refund state', async function () { - await expectRevert( - this.escrow.enableRefunds({ from: owner }), - 'RefundEscrow: can only enable refunds while active', - ); - }); - - it('prevents re-entering the closed state', async function () { - await expectRevert(this.escrow.close({ from: owner }), 'RefundEscrow: can only close while active'); - }); - }); - - it('only the owner can enter refund state', async function () { - await expectRevert(this.escrow.enableRefunds({ from: beneficiary }), 'Ownable: caller is not the owner'); - - const receipt = await this.escrow.enableRefunds({ from: owner }); - expectEvent(receipt, 'RefundsEnabled'); - }); - - context('refund state', function () { - beforeEach(async function () { - await Promise.all(refundees.map(refundee => this.escrow.deposit(refundee, { from: owner, value: amount }))); - - await this.escrow.enableRefunds({ from: owner }); - }); - - it('rejects deposits', async function () { - await expectRevert( - this.escrow.deposit(refundee1, { from: owner, value: amount }), - 'RefundEscrow: can only deposit while active', - ); - }); - - it('refunds refundees', async function () { - for (const refundee of [refundee1, refundee2]) { - const balanceTracker = await balance.tracker(refundee); - await this.escrow.withdraw(refundee, { from: owner }); - expect(await balanceTracker.delta()).to.be.bignumber.equal(amount); - } - }); - - it('does not allow beneficiary withdrawal', async function () { - await expectRevert( - this.escrow.beneficiaryWithdraw(), - 'RefundEscrow: beneficiary can only withdraw while closed', - ); - }); - - it('prevents entering the closed state', async function () { - await expectRevert(this.escrow.close({ from: owner }), 'RefundEscrow: can only close while active'); - }); - - it('prevents re-entering the refund state', async function () { - await expectRevert( - this.escrow.enableRefunds({ from: owner }), - 'RefundEscrow: can only enable refunds while active', - ); - }); - }); - }); -}); diff --git a/test/utils/introspection/ERC1820Implementer.test.js b/test/utils/introspection/ERC1820Implementer.test.js deleted file mode 100644 index 83d0a6534..000000000 --- a/test/utils/introspection/ERC1820Implementer.test.js +++ /dev/null @@ -1,71 +0,0 @@ -const { expectRevert, singletons } = require('@openzeppelin/test-helpers'); -const { bufferToHex, keccakFromString } = require('ethereumjs-util'); - -const { expect } = require('chai'); - -const ERC1820Implementer = artifacts.require('$ERC1820Implementer'); - -contract('ERC1820Implementer', function (accounts) { - const [registryFunder, implementee, other] = accounts; - - const ERC1820_ACCEPT_MAGIC = bufferToHex(keccakFromString('ERC1820_ACCEPT_MAGIC')); - - beforeEach(async function () { - this.implementer = await ERC1820Implementer.new(); - this.registry = await singletons.ERC1820Registry(registryFunder); - - this.interfaceA = bufferToHex(keccakFromString('interfaceA')); - this.interfaceB = bufferToHex(keccakFromString('interfaceB')); - }); - - context('with no registered interfaces', function () { - it('returns false when interface implementation is queried', async function () { - expect(await this.implementer.canImplementInterfaceForAddress(this.interfaceA, implementee)).to.not.equal( - ERC1820_ACCEPT_MAGIC, - ); - }); - - it('reverts when attempting to set as implementer in the registry', async function () { - await expectRevert( - this.registry.setInterfaceImplementer(implementee, this.interfaceA, this.implementer.address, { - from: implementee, - }), - 'Does not implement the interface', - ); - }); - }); - - context('with registered interfaces', function () { - beforeEach(async function () { - await this.implementer.$_registerInterfaceForAddress(this.interfaceA, implementee); - }); - - it('returns true when interface implementation is queried', async function () { - expect(await this.implementer.canImplementInterfaceForAddress(this.interfaceA, implementee)).to.equal( - ERC1820_ACCEPT_MAGIC, - ); - }); - - it('returns false when interface implementation for non-supported interfaces is queried', async function () { - expect(await this.implementer.canImplementInterfaceForAddress(this.interfaceB, implementee)).to.not.equal( - ERC1820_ACCEPT_MAGIC, - ); - }); - - it('returns false when interface implementation for non-supported addresses is queried', async function () { - expect(await this.implementer.canImplementInterfaceForAddress(this.interfaceA, other)).to.not.equal( - ERC1820_ACCEPT_MAGIC, - ); - }); - - it('can be set as an implementer for supported interfaces in the registry', async function () { - await this.registry.setInterfaceImplementer(implementee, this.interfaceA, this.implementer.address, { - from: implementee, - }); - - expect(await this.registry.getInterfaceImplementer(implementee, this.interfaceA)).to.equal( - this.implementer.address, - ); - }); - }); -}); diff --git a/test/utils/math/Math.t.sol b/test/utils/math/Math.t.sol index 916136cdd..9e7e118df 100644 --- a/test/utils/math/Math.t.sol +++ b/test/utils/math/Math.t.sol @@ -5,7 +5,6 @@ pragma solidity ^0.8.0; import "forge-std/Test.sol"; import "../../../contracts/utils/math/Math.sol"; -import "../../../contracts/utils/math/SafeMath.sol"; contract MathTest is Test { // CEILDIV @@ -47,7 +46,7 @@ contract MathTest is Test { } function _squareBigger(uint256 value, uint256 ref) private pure returns (bool) { - (bool noOverflow, uint256 square) = SafeMath.tryMul(value, value); + (bool noOverflow, uint256 square) = Math.tryMul(value, value); return !noOverflow || square > ref; } diff --git a/test/utils/math/Math.test.js b/test/utils/math/Math.test.js index 6b97a646f..df459c5f8 100644 --- a/test/utils/math/Math.test.js +++ b/test/utils/math/Math.test.js @@ -5,6 +5,21 @@ const { Rounding } = require('../../helpers/enums.js'); const Math = artifacts.require('$Math'); +function expectStruct(value, expected) { + for (const key in expected) { + if (BN.isBN(value[key])) { + expect(value[key]).to.be.bignumber.equal(expected[key]); + } else { + expect(value[key]).to.be.equal(expected[key]); + } + } +} + +async function testCommutativeIterable(fn, lhs, rhs, expected, ...extra) { + expectStruct(await fn(lhs, rhs, ...extra), expected); + expectStruct(await fn(rhs, lhs, ...extra), expected); +} + contract('Math', function () { const min = new BN('1234'); const max = new BN('5678'); @@ -15,6 +30,130 @@ contract('Math', function () { this.math = await Math.new(); }); + describe('tryAdd', function () { + it('adds correctly', async function () { + const a = new BN('5678'); + const b = new BN('1234'); + + await testCommutativeIterable(this.math.$tryAdd, a, b, [true, a.add(b)]); + }); + + it('reverts on addition overflow', async function () { + const a = MAX_UINT256; + const b = new BN('1'); + + await testCommutativeIterable(this.math.$tryAdd, a, b, [false, '0']); + }); + }); + + describe('trySub', function () { + it('subtracts correctly', async function () { + const a = new BN('5678'); + const b = new BN('1234'); + + expectStruct(await this.math.$trySub(a, b), [true, a.sub(b)]); + }); + + it('reverts if subtraction result would be negative', async function () { + const a = new BN('1234'); + const b = new BN('5678'); + + expectStruct(await this.math.$trySub(a, b), [false, '0']); + }); + }); + + describe('tryMul', function () { + it('multiplies correctly', async function () { + const a = new BN('1234'); + const b = new BN('5678'); + + await testCommutativeIterable(this.math.$tryMul, a, b, [true, a.mul(b)]); + }); + + it('multiplies by zero correctly', async function () { + const a = new BN('0'); + const b = new BN('5678'); + + await testCommutativeIterable(this.math.$tryMul, a, b, [true, a.mul(b)]); + }); + + it('reverts on multiplication overflow', async function () { + const a = MAX_UINT256; + const b = new BN('2'); + + await testCommutativeIterable(this.math.$tryMul, a, b, [false, '0']); + }); + }); + + describe('tryDiv', function () { + it('divides correctly', async function () { + const a = new BN('5678'); + const b = new BN('5678'); + + expectStruct(await this.math.$tryDiv(a, b), [true, a.div(b)]); + }); + + it('divides zero correctly', async function () { + const a = new BN('0'); + const b = new BN('5678'); + + expectStruct(await this.math.$tryDiv(a, b), [true, a.div(b)]); + }); + + it('returns complete number result on non-even division', async function () { + const a = new BN('7000'); + const b = new BN('5678'); + + expectStruct(await this.math.$tryDiv(a, b), [true, a.div(b)]); + }); + + it('reverts on division by zero', async function () { + const a = new BN('5678'); + const b = new BN('0'); + + expectStruct(await this.math.$tryDiv(a, b), [false, '0']); + }); + }); + + describe('tryMod', function () { + describe('modulos correctly', async function () { + it('when the dividend is smaller than the divisor', async function () { + const a = new BN('284'); + const b = new BN('5678'); + + expectStruct(await this.math.$tryMod(a, b), [true, a.mod(b)]); + }); + + it('when the dividend is equal to the divisor', async function () { + const a = new BN('5678'); + const b = new BN('5678'); + + expectStruct(await this.math.$tryMod(a, b), [true, a.mod(b)]); + }); + + it('when the dividend is larger than the divisor', async function () { + const a = new BN('7000'); + const b = new BN('5678'); + + expectStruct(await this.math.$tryMod(a, b), [true, a.mod(b)]); + }); + + it('when the dividend is a multiple of the divisor', async function () { + const a = new BN('17034'); // 17034 == 5678 * 3 + const b = new BN('5678'); + + expectStruct(await this.math.$tryMod(a, b), [true, a.mod(b)]); + }); + }); + + it('reverts with a 0 divisor', async function () { + const a = new BN('5678'); + const b = new BN('0'); + + expectStruct(await this.math.$tryMod(a, b), [false, '0']); + }); + }); + describe('max', function () { it('is correctly detected in first argument position', async function () { expect(await this.math.$max(max, min)).to.be.bignumber.equal(max); diff --git a/test/utils/math/SafeMath.test.js b/test/utils/math/SafeMath.test.js deleted file mode 100644 index 9598ac57c..000000000 --- a/test/utils/math/SafeMath.test.js +++ /dev/null @@ -1,433 +0,0 @@ -const { BN, constants, expectRevert } = require('@openzeppelin/test-helpers'); -const { MAX_UINT256 } = constants; - -const { expect } = require('chai'); - -const SafeMath = artifacts.require('$SafeMath'); -const SafeMathMemoryCheck = artifacts.require('$SafeMathMemoryCheck'); - -function expectStruct(value, expected) { - for (const key in expected) { - if (BN.isBN(value[key])) { - expect(value[key]).to.be.bignumber.equal(expected[key]); - } else { - expect(value[key]).to.be.equal(expected[key]); - } - } -} - -contract('SafeMath', function () { - beforeEach(async function () { - this.safeMath = await SafeMath.new(); - }); - - async function testCommutative(fn, lhs, rhs, expected, ...extra) { - expect(await fn(lhs, rhs, ...extra)).to.be.bignumber.equal(expected); - expect(await fn(rhs, lhs, ...extra)).to.be.bignumber.equal(expected); - } - - async function testFailsCommutative(fn, lhs, rhs, reason, ...extra) { - if (reason === undefined) { - await expectRevert.unspecified(fn(lhs, rhs, ...extra)); - await expectRevert.unspecified(fn(rhs, lhs, ...extra)); - } else { - await expectRevert(fn(lhs, rhs, ...extra), reason); - await expectRevert(fn(rhs, lhs, ...extra), reason); - } - } - - async function testCommutativeIterable(fn, lhs, rhs, expected, ...extra) { - expectStruct(await fn(lhs, rhs, ...extra), expected); - expectStruct(await fn(rhs, lhs, ...extra), expected); - } - - describe('with flag', function () { - describe('add', function () { - it('adds correctly', async function () { - const a = new BN('5678'); - const b = new BN('1234'); - - testCommutativeIterable(this.safeMath.$tryAdd, a, b, [true, a.add(b)]); - }); - - it('reverts on addition overflow', async function () { - const a = MAX_UINT256; - const b = new BN('1'); - - testCommutativeIterable(this.safeMath.$tryAdd, a, b, [false, '0']); - }); - }); - - describe('sub', function () { - it('subtracts correctly', async function () { - const a = new BN('5678'); - const b = new BN('1234'); - - expectStruct(await this.safeMath.$trySub(a, b), [true, a.sub(b)]); - }); - - it('reverts if subtraction result would be negative', async function () { - const a = new BN('1234'); - const b = new BN('5678'); - - expectStruct(await this.safeMath.$trySub(a, b), [false, '0']); - }); - }); - - describe('mul', function () { - it('multiplies correctly', async function () { - const a = new BN('1234'); - const b = new BN('5678'); - - testCommutativeIterable(this.safeMath.$tryMul, a, b, [true, a.mul(b)]); - }); - - it('multiplies by zero correctly', async function () { - const a = new BN('0'); - const b = new BN('5678'); - - testCommutativeIterable(this.safeMath.$tryMul, a, b, [true, a.mul(b)]); - }); - - it('reverts on multiplication overflow', async function () { - const a = MAX_UINT256; - const b = new BN('2'); - - testCommutativeIterable(this.safeMath.$tryMul, a, b, [false, '0']); - }); - }); - - describe('div', function () { - it('divides correctly', async function () { - const a = new BN('5678'); - const b = new BN('5678'); - - expectStruct(await this.safeMath.$tryDiv(a, b), [true, a.div(b)]); - }); - - it('divides zero correctly', async function () { - const a = new BN('0'); - const b = new BN('5678'); - - expectStruct(await this.safeMath.$tryDiv(a, b), [true, a.div(b)]); - }); - - it('returns complete number result on non-even division', async function () { - const a = new BN('7000'); - const b = new BN('5678'); - - expectStruct(await this.safeMath.$tryDiv(a, b), [true, a.div(b)]); - }); - - it('reverts on division by zero', async function () { - const a = new BN('5678'); - const b = new BN('0'); - - expectStruct(await this.safeMath.$tryDiv(a, b), [false, '0']); - }); - }); - - describe('mod', function () { - describe('modulos correctly', async function () { - it('when the dividend is smaller than the divisor', async function () { - const a = new BN('284'); - const b = new BN('5678'); - - expectStruct(await this.safeMath.$tryMod(a, b), [true, a.mod(b)]); - }); - - it('when the dividend is equal to the divisor', async function () { - const a = new BN('5678'); - const b = new BN('5678'); - - expectStruct(await this.safeMath.$tryMod(a, b), [true, a.mod(b)]); - }); - - it('when the dividend is larger than the divisor', async function () { - const a = new BN('7000'); - const b = new BN('5678'); - - expectStruct(await this.safeMath.$tryMod(a, b), [true, a.mod(b)]); - }); - - it('when the dividend is a multiple of the divisor', async function () { - const a = new BN('17034'); // 17034 == 5678 * 3 - const b = new BN('5678'); - - expectStruct(await this.safeMath.$tryMod(a, b), [true, a.mod(b)]); - }); - }); - - it('reverts with a 0 divisor', async function () { - const a = new BN('5678'); - const b = new BN('0'); - - expectStruct(await this.safeMath.$tryMod(a, b), [false, '0']); - }); - }); - }); - - describe('with default revert message', function () { - describe('add', function () { - it('adds correctly', async function () { - const a = new BN('5678'); - const b = new BN('1234'); - - await testCommutative(this.safeMath.$add, a, b, a.add(b)); - }); - - it('reverts on addition overflow', async function () { - const a = MAX_UINT256; - const b = new BN('1'); - - await testFailsCommutative(this.safeMath.$add, a, b, undefined); - }); - }); - - describe('sub', function () { - it('subtracts correctly', async function () { - const a = new BN('5678'); - const b = new BN('1234'); - - expect(await this.safeMath.$sub(a, b)).to.be.bignumber.equal(a.sub(b)); - }); - - it('reverts if subtraction result would be negative', async function () { - const a = new BN('1234'); - const b = new BN('5678'); - - await expectRevert.unspecified(this.safeMath.$sub(a, b)); - }); - }); - - describe('mul', function () { - it('multiplies correctly', async function () { - const a = new BN('1234'); - const b = new BN('5678'); - - await testCommutative(this.safeMath.$mul, a, b, a.mul(b)); - }); - - it('multiplies by zero correctly', async function () { - const a = new BN('0'); - const b = new BN('5678'); - - await testCommutative(this.safeMath.$mul, a, b, '0'); - }); - - it('reverts on multiplication overflow', async function () { - const a = MAX_UINT256; - const b = new BN('2'); - - await testFailsCommutative(this.safeMath.$mul, a, b, undefined); - }); - }); - - describe('div', function () { - it('divides correctly', async function () { - const a = new BN('5678'); - const b = new BN('5678'); - - expect(await this.safeMath.$div(a, b)).to.be.bignumber.equal(a.div(b)); - }); - - it('divides zero correctly', async function () { - const a = new BN('0'); - const b = new BN('5678'); - - expect(await this.safeMath.$div(a, b)).to.be.bignumber.equal('0'); - }); - - it('returns complete number result on non-even division', async function () { - const a = new BN('7000'); - const b = new BN('5678'); - - expect(await this.safeMath.$div(a, b)).to.be.bignumber.equal('1'); - }); - - it('reverts on division by zero', async function () { - const a = new BN('5678'); - const b = new BN('0'); - - await expectRevert.unspecified(this.safeMath.$div(a, b)); - }); - }); - - describe('mod', function () { - describe('modulos correctly', async function () { - it('when the dividend is smaller than the divisor', async function () { - const a = new BN('284'); - const b = new BN('5678'); - - expect(await this.safeMath.$mod(a, b)).to.be.bignumber.equal(a.mod(b)); - }); - - it('when the dividend is equal to the divisor', async function () { - const a = new BN('5678'); - const b = new BN('5678'); - - expect(await this.safeMath.$mod(a, b)).to.be.bignumber.equal(a.mod(b)); - }); - - it('when the dividend is larger than the divisor', async function () { - const a = new BN('7000'); - const b = new BN('5678'); - - expect(await this.safeMath.$mod(a, b)).to.be.bignumber.equal(a.mod(b)); - }); - - it('when the dividend is a multiple of the divisor', async function () { - const a = new BN('17034'); // 17034 == 5678 * 3 - const b = new BN('5678'); - - expect(await this.safeMath.$mod(a, b)).to.be.bignumber.equal(a.mod(b)); - }); - }); - - it('reverts with a 0 divisor', async function () { - const a = new BN('5678'); - const b = new BN('0'); - - await expectRevert.unspecified(this.safeMath.$mod(a, b)); - }); - }); - }); - - describe('with custom revert message', function () { - describe('sub', function () { - it('subtracts correctly', async function () { - const a = new BN('5678'); - const b = new BN('1234'); - - expect( - await this.safeMath.methods['$sub(uint256,uint256,string)'](a, b, 'MyErrorMessage'), - ).to.be.bignumber.equal(a.sub(b)); - }); - - it('reverts if subtraction result would be negative', async function () { - const a = new BN('1234'); - const b = new BN('5678'); - - await expectRevert( - this.safeMath.methods['$sub(uint256,uint256,string)'](a, b, 'MyErrorMessage'), - 'MyErrorMessage', - ); - }); - }); - - describe('div', function () { - it('divides correctly', async function () { - const a = new BN('5678'); - const b = new BN('5678'); - - expect( - await this.safeMath.methods['$div(uint256,uint256,string)'](a, b, 'MyErrorMessage'), - ).to.be.bignumber.equal(a.div(b)); - }); - - it('divides zero correctly', async function () { - const a = new BN('0'); - const b = new BN('5678'); - - expect( - await this.safeMath.methods['$div(uint256,uint256,string)'](a, b, 'MyErrorMessage'), - ).to.be.bignumber.equal('0'); - }); - - it('returns complete number result on non-even division', async function () { - const a = new BN('7000'); - const b = new BN('5678'); - - expect( - await this.safeMath.methods['$div(uint256,uint256,string)'](a, b, 'MyErrorMessage'), - ).to.be.bignumber.equal('1'); - }); - - it('reverts on division by zero', async function () { - const a = new BN('5678'); - const b = new BN('0'); - - await expectRevert( - this.safeMath.methods['$div(uint256,uint256,string)'](a, b, 'MyErrorMessage'), - 'MyErrorMessage', - ); - }); - }); - - describe('mod', function () { - describe('modulos correctly', async function () { - it('when the dividend is smaller than the divisor', async function () { - const a = new BN('284'); - const b = new BN('5678'); - - expect( - await this.safeMath.methods['$mod(uint256,uint256,string)'](a, b, 'MyErrorMessage'), - ).to.be.bignumber.equal(a.mod(b)); - }); - - it('when the dividend is equal to the divisor', async function () { - const a = new BN('5678'); - const b = new BN('5678'); - - expect( - await this.safeMath.methods['$mod(uint256,uint256,string)'](a, b, 'MyErrorMessage'), - ).to.be.bignumber.equal(a.mod(b)); - }); - - it('when the dividend is larger than the divisor', async function () { - const a = new BN('7000'); - const b = new BN('5678'); - - expect( - await this.safeMath.methods['$mod(uint256,uint256,string)'](a, b, 'MyErrorMessage'), - ).to.be.bignumber.equal(a.mod(b)); - }); - - it('when the dividend is a multiple of the divisor', async function () { - const a = new BN('17034'); // 17034 == 5678 * 3 - const b = new BN('5678'); - - expect( - await this.safeMath.methods['$mod(uint256,uint256,string)'](a, b, 'MyErrorMessage'), - ).to.be.bignumber.equal(a.mod(b)); - }); - }); - - it('reverts with a 0 divisor', async function () { - const a = new BN('5678'); - const b = new BN('0'); - - await expectRevert( - this.safeMath.methods['$mod(uint256,uint256,string)'](a, b, 'MyErrorMessage'), - 'MyErrorMessage', - ); - }); - }); - }); - - describe('memory leakage', function () { - beforeEach(async function () { - this.safeMathMemoryCheck = await SafeMathMemoryCheck.new(); - }); - - it('add', async function () { - expect(await this.safeMathMemoryCheck.$addMemoryCheck()).to.be.bignumber.equal('0'); - }); - - it('sub', async function () { - expect(await this.safeMathMemoryCheck.$subMemoryCheck()).to.be.bignumber.equal('0'); - }); - - it('mul', async function () { - expect(await this.safeMathMemoryCheck.$mulMemoryCheck()).to.be.bignumber.equal('0'); - }); - - it('div', async function () { - expect(await this.safeMathMemoryCheck.$divMemoryCheck()).to.be.bignumber.equal('0'); - }); - - it('mod', async function () { - expect(await this.safeMathMemoryCheck.$modMemoryCheck()).to.be.bignumber.equal('0'); - }); - }); -}); diff --git a/test/utils/math/SignedSafeMath.test.js b/test/utils/math/SignedSafeMath.test.js deleted file mode 100644 index 3b6530cf5..000000000 --- a/test/utils/math/SignedSafeMath.test.js +++ /dev/null @@ -1,152 +0,0 @@ -const { BN, constants, expectRevert } = require('@openzeppelin/test-helpers'); -const { MAX_INT256, MIN_INT256 } = constants; - -const { expect } = require('chai'); - -const SignedSafeMath = artifacts.require('$SignedSafeMath'); - -contract('SignedSafeMath', function () { - beforeEach(async function () { - this.safeMath = await SignedSafeMath.new(); - }); - - async function testCommutative(fn, lhs, rhs, expected) { - expect(await fn(lhs, rhs)).to.be.bignumber.equal(expected); - expect(await fn(rhs, lhs)).to.be.bignumber.equal(expected); - } - - async function testFailsCommutative(fn, lhs, rhs) { - await expectRevert.unspecified(fn(lhs, rhs)); - await expectRevert.unspecified(fn(rhs, lhs)); - } - - describe('add', function () { - it('adds correctly if it does not overflow and the result is positive', async function () { - const a = new BN('1234'); - const b = new BN('5678'); - - await testCommutative(this.safeMath.$add, a, b, a.add(b)); - }); - - it('adds correctly if it does not overflow and the result is negative', async function () { - const a = MAX_INT256; - const b = MIN_INT256; - - await testCommutative(this.safeMath.$add, a, b, a.add(b)); - }); - - it('reverts on positive addition overflow', async function () { - const a = MAX_INT256; - const b = new BN('1'); - - await testFailsCommutative(this.safeMath.$add, a, b); - }); - - it('reverts on negative addition overflow', async function () { - const a = MIN_INT256; - const b = new BN('-1'); - - await testFailsCommutative(this.safeMath.$add, a, b); - }); - }); - - describe('sub', function () { - it('subtracts correctly if it does not overflow and the result is positive', async function () { - const a = new BN('5678'); - const b = new BN('1234'); - - const result = await this.safeMath.$sub(a, b); - expect(result).to.be.bignumber.equal(a.sub(b)); - }); - - it('subtracts correctly if it does not overflow and the result is negative', async function () { - const a = new BN('1234'); - const b = new BN('5678'); - - const result = await this.safeMath.$sub(a, b); - expect(result).to.be.bignumber.equal(a.sub(b)); - }); - - it('reverts on positive subtraction overflow', async function () { - const a = MAX_INT256; - const b = new BN('-1'); - - await expectRevert.unspecified(this.safeMath.$sub(a, b)); - }); - - it('reverts on negative subtraction overflow', async function () { - const a = MIN_INT256; - const b = new BN('1'); - - await expectRevert.unspecified(this.safeMath.$sub(a, b)); - }); - }); - - describe('mul', function () { - it('multiplies correctly', async function () { - const a = new BN('5678'); - const b = new BN('-1234'); - - await testCommutative(this.safeMath.$mul, a, b, a.mul(b)); - }); - - it('multiplies by zero correctly', async function () { - const a = new BN('0'); - const b = new BN('5678'); - - await testCommutative(this.safeMath.$mul, a, b, '0'); - }); - - it('reverts on multiplication overflow, positive operands', async function () { - const a = MAX_INT256; - const b = new BN('2'); - - await testFailsCommutative(this.safeMath.$mul, a, b); - }); - - it('reverts when minimum integer is multiplied by -1', async function () { - const a = MIN_INT256; - const b = new BN('-1'); - - await testFailsCommutative(this.safeMath.$mul, a, b); - }); - }); - - describe('div', function () { - it('divides correctly', async function () { - const a = new BN('-5678'); - const b = new BN('5678'); - - const result = await this.safeMath.$div(a, b); - expect(result).to.be.bignumber.equal(a.div(b)); - }); - - it('divides zero correctly', async function () { - const a = new BN('0'); - const b = new BN('5678'); - - expect(await this.safeMath.$div(a, b)).to.be.bignumber.equal('0'); - }); - - it('returns complete number result on non-even division', async function () { - const a = new BN('7000'); - const b = new BN('5678'); - - expect(await this.safeMath.$div(a, b)).to.be.bignumber.equal('1'); - }); - - it('reverts on division by zero', async function () { - const a = new BN('-5678'); - const b = new BN('0'); - - await expectRevert.unspecified(this.safeMath.$div(a, b)); - }); - - it('reverts on overflow, negative second', async function () { - const a = new BN(MIN_INT256); - const b = new BN('-1'); - - await expectRevert.unspecified(this.safeMath.$div(a, b)); - }); - }); -}); diff --git a/test/utils/structs/EnumerableMap.behavior.js b/test/utils/structs/EnumerableMap.behavior.js index b4b5ee372..3db45df6a 100644 --- a/test/utils/structs/EnumerableMap.behavior.js +++ b/test/utils/structs/EnumerableMap.behavior.js @@ -154,17 +154,6 @@ function shouldBehaveLikeMap(keys, values, zeroValue, methods, events) { }); }); - describe('get with message', function () { - it('existing value', async function () { - expect(await methods.getWithMessage(this.map, keyA, 'custom error string').then(r => r.toString())).to.be.equal( - valueA.toString(), - ); - }); - it('missing value', async function () { - await expectRevert(methods.getWithMessage(this.map, keyB, 'custom error string'), 'custom error string'); - }); - }); - describe('tryGet', function () { it('existing value', async function () { const result = await methods.tryGet(this.map, keyA); diff --git a/test/utils/structs/EnumerableMap.test.js b/test/utils/structs/EnumerableMap.test.js index f540ec99e..8861fa731 100644 --- a/test/utils/structs/EnumerableMap.test.js +++ b/test/utils/structs/EnumerableMap.test.js @@ -41,7 +41,6 @@ contract('EnumerableMap', function (accounts) { getMethods({ set: '$set(uint256,address,uint256)', get: '$get(uint256,address)', - getWithMessage: '$get(uint256,address,string)', tryGet: '$tryGet(uint256,address)', remove: '$remove(uint256,address)', length: `$length_${library}_AddressToUintMap(uint256)`, @@ -65,7 +64,6 @@ contract('EnumerableMap', function (accounts) { getMethods({ set: '$set(uint256,uint256,address)', get: `$get_${library}_UintToAddressMap(uint256,uint256)`, - getWithMessage: `$get_${library}_UintToAddressMap(uint256,uint256,string)`, tryGet: `$tryGet_${library}_UintToAddressMap(uint256,uint256)`, remove: `$remove_${library}_UintToAddressMap(uint256,uint256)`, length: `$length_${library}_UintToAddressMap(uint256)`, @@ -89,7 +87,6 @@ contract('EnumerableMap', function (accounts) { getMethods({ set: '$set(uint256,bytes32,bytes32)', get: `$get_${library}_Bytes32ToBytes32Map(uint256,bytes32)`, - getWithMessage: `$get_${library}_Bytes32ToBytes32Map(uint256,bytes32,string)`, tryGet: `$tryGet_${library}_Bytes32ToBytes32Map(uint256,bytes32)`, remove: `$remove_${library}_Bytes32ToBytes32Map(uint256,bytes32)`, length: `$length_${library}_Bytes32ToBytes32Map(uint256)`, @@ -113,7 +110,6 @@ contract('EnumerableMap', function (accounts) { getMethods({ set: '$set(uint256,uint256,uint256)', get: `$get_${library}_UintToUintMap(uint256,uint256)`, - getWithMessage: `$get_${library}_UintToUintMap(uint256,uint256,string)`, tryGet: `$tryGet_${library}_UintToUintMap(uint256,uint256)`, remove: `$remove_${library}_UintToUintMap(uint256,uint256)`, length: `$length_${library}_UintToUintMap(uint256)`, @@ -137,7 +133,6 @@ contract('EnumerableMap', function (accounts) { getMethods({ set: '$set(uint256,bytes32,uint256)', get: `$get_${library}_Bytes32ToUintMap(uint256,bytes32)`, - getWithMessage: `$get_${library}_Bytes32ToUintMap(uint256,bytes32,string)`, tryGet: `$tryGet_${library}_Bytes32ToUintMap(uint256,bytes32)`, remove: `$remove_${library}_Bytes32ToUintMap(uint256,bytes32)`, length: `$length_${library}_Bytes32ToUintMap(uint256)`, From dfef6a68ee18dbd2e1f5a099061a3b8a0e404485 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Fri, 19 May 2023 23:49:23 +0200 Subject: [PATCH 066/182] Fix error when running hardhat test with parameters (#4265) --- hardhat/task-test-get-files.js | 34 ++++++++++++---------------------- 1 file changed, 12 insertions(+), 22 deletions(-) diff --git a/hardhat/task-test-get-files.js b/hardhat/task-test-get-files.js index 896bf1274..108f40a42 100644 --- a/hardhat/task-test-get-files.js +++ b/hardhat/task-test-get-files.js @@ -3,33 +3,23 @@ const { TASK_TEST_GET_TEST_FILES } = require('hardhat/builtin-tasks/task-names') // Modifies `hardhat test` to skip the proxy tests after proxies are removed by the transpiler for upgradeability. -internalTask(TASK_TEST_GET_TEST_FILES).setAction(async ({ testFiles }, { config }) => { - if (testFiles.length !== 0) { - return testFiles; - } - - const globAsync = require('glob'); +internalTask(TASK_TEST_GET_TEST_FILES).setAction(async (args, hre, runSuper) => { const path = require('path'); const { promises: fs } = require('fs'); - const { promisify } = require('util'); - - const glob = promisify(globAsync); const hasProxies = await fs - .access(path.join(config.paths.sources, 'proxy/Proxy.sol')) + .access(path.join(hre.config.paths.sources, 'proxy/Proxy.sol')) .then(() => true) .catch(() => false); - return await glob(path.join(config.paths.tests, '**/*.js'), { - ignore: hasProxies - ? [] - : [ - 'proxy/beacon/BeaconProxy.test.js', - 'proxy/beacon/UpgradeableBeacon.test.js', - 'proxy/ERC1967/ERC1967Proxy.test.js', - 'proxy/transparent/ProxyAdmin.test.js', - 'proxy/transparent/TransparentUpgradeableProxy.test.js', - 'proxy/utils/UUPSUpgradeable.test.js', - ].map(p => path.join(config.paths.tests, p)), - }); + const ignoredIfProxy = [ + 'proxy/beacon/BeaconProxy.test.js', + 'proxy/beacon/UpgradeableBeacon.test.js', + 'proxy/ERC1967/ERC1967Proxy.test.js', + 'proxy/transparent/ProxyAdmin.test.js', + 'proxy/transparent/TransparentUpgradeableProxy.test.js', + 'proxy/utils/UUPSUpgradeable.test.js', + ].map(p => path.join(hre.config.paths.tests, p)); + + return (await runSuper(args)).filter(file => hasProxies || !ignoredIfProxy.includes(file)); }); From a1d57bac505eab080f7b5009cfd5f98dd2e779a2 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Tue, 23 May 2023 20:54:34 +0200 Subject: [PATCH 067/182] Improve FV specifications for AccessControlDefaultAdminRules (#4223) Co-authored-by: ernestognw Co-authored-by: Francisco --- .../specs/AccessControlDefaultAdminRules.spec | 244 ++++++++---------- certora/specs/helpers/helpers.spec | 9 + 2 files changed, 113 insertions(+), 140 deletions(-) diff --git a/certora/specs/AccessControlDefaultAdminRules.spec b/certora/specs/AccessControlDefaultAdminRules.spec index a4baa1871..58b9d1202 100644 --- a/certora/specs/AccessControlDefaultAdminRules.spec +++ b/certora/specs/AccessControlDefaultAdminRules.spec @@ -12,44 +12,23 @@ use rule onlyGrantCanGrant filtered { │ Helpers │ └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ */ +definition timeSanity(env e) returns bool = + e.block.timestamp > 0 && e.block.timestamp + defaultAdminDelay(e) < max_uint48(); -function max_uint48() returns mathint { - return (1 << 48) - 1; -} +definition delayChangeWaitSanity(env e, uint48 newDelay) returns bool = + e.block.timestamp + delayChangeWait_(e, newDelay) < max_uint48(); -function nonZeroAccount(address account) returns bool { - return account != 0; -} +definition isSet(uint48 schedule) returns bool = + schedule != 0; -function timeSanity(env e) returns bool { - return - e.block.timestamp > 0 && // Avoids 0 schedules - e.block.timestamp + defaultAdminDelay(e) < max_uint48(); -} +definition hasPassed(env e, uint48 schedule) returns bool = + schedule < e.block.timestamp; -function delayChangeWaitSanity(env e, uint48 newDelay) returns bool { - return e.block.timestamp + delayChangeWait_(e, newDelay) < max_uint48(); -} +definition increasingDelaySchedule(env e, uint48 newDelay) returns mathint = + e.block.timestamp + min(newDelay, defaultAdminDelayIncreaseWait()); -function isSet(uint48 schedule) returns bool { - return schedule != 0; -} - -function hasPassed(env e, uint48 schedule) returns bool { - return schedule < e.block.timestamp; -} - -function min(uint48 a, uint48 b) returns mathint { - return a < b ? a : b; -} - -function increasingDelaySchedule(env e, uint48 newDelay) returns mathint { - return e.block.timestamp + min(newDelay, defaultAdminDelayIncreaseWait()); -} - -function decreasingDelaySchedule(env e, uint48 newDelay) returns mathint { - return e.block.timestamp + defaultAdminDelay(e) - newDelay; -} +definition decreasingDelaySchedule(env e, uint48 newDelay) returns mathint = + e.block.timestamp + defaultAdminDelay(e) - newDelay; /* ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ @@ -57,11 +36,10 @@ function decreasingDelaySchedule(env e, uint48 newDelay) returns mathint { └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ */ invariant defaultAdminConsistency(address account) - defaultAdmin() == account <=> hasRole(DEFAULT_ADMIN_ROLE(), account) + (account == defaultAdmin() && account != 0) <=> hasRole(DEFAULT_ADMIN_ROLE(), account) { - preserved { - // defaultAdmin() returns the zero address when there's no default admin - require nonZeroAccount(account); + preserved with (env e) { + require nonzerosender(e); } } @@ -72,10 +50,12 @@ invariant defaultAdminConsistency(address account) */ invariant singleDefaultAdmin(address account, address another) hasRole(DEFAULT_ADMIN_ROLE(), account) && hasRole(DEFAULT_ADMIN_ROLE(), another) => another == account - // We filter here because we couldn't find a way to force Certora to have an initial state with - // only one DEFAULT_ADMIN_ROLE enforced, so a counter example is a different default admin since inception - // triggering the transfer, which is known to be impossible by definition. - filtered { f -> f.selector != acceptDefaultAdminTransfer().selector } + { + preserved { + requireInvariant defaultAdminConsistency(account); + requireInvariant defaultAdminConsistency(another); + } + } /* ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ @@ -118,7 +98,8 @@ rule revokeRoleEffect(env e, bytes32 role) { "roles can only be revoked by their owner except for the default admin role"; // effect - assert success => !hasRole(role, account), "role is revoked"; + assert success => !hasRole(role, account), + "role is revoked"; // no side effect assert hasOtherRoleBefore != hasOtherRoleAfter => (role == otherRole && account == otherAccount), @@ -137,35 +118,59 @@ rule renounceRoleEffect(env e, bytes32 role) { address account; address otherAccount; - bool hasOtherRoleBefore = hasRole(otherRole, otherAccount); - uint48 scheduleBefore = pendingDefaultAdminSchedule_(); + bool hasOtherRoleBefore = hasRole(otherRole, otherAccount); + address adminBefore = defaultAdmin(); address pendingAdminBefore = pendingDefaultAdmin_(); + uint48 scheduleBefore = pendingDefaultAdminSchedule_(); renounceRole@withrevert(e, role, account); bool success = !lastReverted; - bool hasOtherRoleAfter = hasRole(otherRole, otherAccount); + bool hasOtherRoleAfter = hasRole(otherRole, otherAccount); + address adminAfter = defaultAdmin(); + address pendingAdminAfter = pendingDefaultAdmin_(); + uint48 scheduleAfter = pendingDefaultAdminSchedule_(); // liveness assert success <=> ( account == e.msg.sender && ( + role != DEFAULT_ADMIN_ROLE() || + account != adminBefore || ( - role != DEFAULT_ADMIN_ROLE() - ) || ( - role == DEFAULT_ADMIN_ROLE() && pendingAdminBefore == 0 && isSet(scheduleBefore) && hasPassed(e, scheduleBefore) ) ) - ), "an account only can renounce by itself with a delay for the default admin role"; + ), + "an account only can renounce by itself with a delay for the default admin role"; // effect - assert success => !hasRole(role, account), "role is renounced"; + assert success => !hasRole(role, account), + "role is renounced"; + + assert success => ( + ( + role == DEFAULT_ADMIN_ROLE() && + account == adminBefore + ) ? ( + adminAfter == 0 && + pendingAdminAfter == 0 && + scheduleAfter == 0 + ) : ( + adminAfter == adminBefore && + pendingAdminAfter == pendingAdminBefore && + scheduleAfter == scheduleBefore + ) + ), + "renouncing default admin role cleans state iff called by previous admin"; // no side effect - assert hasOtherRoleBefore != hasOtherRoleAfter => (role == otherRole && account == otherAccount), + assert hasOtherRoleBefore != hasOtherRoleAfter => ( + role == otherRole && + account == otherAccount + ), "no other role is affected"; } @@ -175,10 +180,6 @@ rule renounceRoleEffect(env e, bytes32 role) { └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ */ rule noDefaultAdminChange(env e, method f, calldataarg args) { - require nonZeroAccount(e.msg.sender); - requireInvariant defaultAdminConsistency(defaultAdmin()); - requireInvariant singleDefaultAdmin(e.msg.sender, defaultAdmin()); - address adminBefore = defaultAdmin(); f(e, args); address adminAfter = defaultAdmin(); @@ -186,18 +187,17 @@ rule noDefaultAdminChange(env e, method f, calldataarg args) { assert adminBefore != adminAfter => ( f.selector == acceptDefaultAdminTransfer().selector || f.selector == renounceRole(bytes32,address).selector - ), "default admin is only affected by accepting an admin transfer or renoucing"; + ), + "default admin is only affected by accepting an admin transfer or renoucing"; } /* ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Rule: pendingDefaultAdmin is only affected by beginning, accepting or canceling an admin transfer │ +│ Rule: pendingDefaultAdmin is only affected by beginning, completing (accept or renounce), or canceling an admin │ +│ transfer │ └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ */ rule noPendingDefaultAdminChange(env e, method f, calldataarg args) { - requireInvariant defaultAdminConsistency(defaultAdmin()); - requireInvariant singleDefaultAdmin(e.msg.sender, defaultAdmin()); - address pendingAdminBefore = pendingDefaultAdmin_(); address scheduleBefore = pendingDefaultAdminSchedule_(); f(e, args); @@ -210,8 +210,10 @@ rule noPendingDefaultAdminChange(env e, method f, calldataarg args) { ) => ( f.selector == beginDefaultAdminTransfer(address).selector || f.selector == acceptDefaultAdminTransfer().selector || - f.selector == cancelDefaultAdminTransfer().selector - ), "pending admin and its schedule is only affected by beginning, accepting or cancelling an admin transfer"; + f.selector == cancelDefaultAdminTransfer().selector || + f.selector == renounceRole(bytes32,address).selector + ), + "pending admin and its schedule is only affected by beginning, completing, or cancelling an admin transfer"; } /* @@ -224,7 +226,8 @@ rule noDefaultAdminDelayChange(env e, method f, calldataarg args) { f(e, args); uint48 delayAfter = defaultAdminDelay(e); - assert delayBefore == delayAfter, "delay can't be changed atomically by any function"; + assert delayBefore == delayAfter, + "delay can't be changed atomically by any function"; } /* @@ -240,7 +243,8 @@ rule noPendingDefaultAdminDelayChange(env e, method f, calldataarg args) { assert pendingDelayBefore != pendingDelayAfter => ( f.selector == changeDefaultAdminDelay(uint48).selector || f.selector == rollbackDefaultAdminDelay().selector - ), "pending delay is only affected by changeDefaultAdminDelay or rollbackDefaultAdminDelay"; + ), + "pending delay is only affected by changeDefaultAdminDelay or rollbackDefaultAdminDelay"; } /* @@ -263,10 +267,10 @@ rule noDefaultAdminDelayIncreaseWaitChange(env e, method f, calldataarg args) { └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ */ rule beginDefaultAdminTransfer(env e, address newAdmin) { - require nonpayable(e); require timeSanity(e); - requireInvariant defaultAdminConsistency(defaultAdmin()); - requireInvariant singleDefaultAdmin(e.msg.sender, defaultAdmin()); + require nonpayable(e); + require nonzerosender(e); + requireInvariant defaultAdminConsistency(e.msg.sender); beginDefaultAdminTransfer@withrevert(e, newAdmin); bool success = !lastReverted; @@ -288,18 +292,24 @@ rule beginDefaultAdminTransfer(env e, address newAdmin) { └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ */ rule pendingDefaultAdminDelayEnforced(env e1, env e2, method f, calldataarg args, address newAdmin) { - require e1.block.timestamp < e2.block.timestamp; + require e1.block.timestamp <= e2.block.timestamp; uint48 delayBefore = defaultAdminDelay(e1); address adminBefore = defaultAdmin(); + // There might be a better way to generalize this without requiring `beginDefaultAdminTransfer`, but currently // it's the only way in which we can attest that only `delayBefore` has passed before a change. beginDefaultAdminTransfer(e1, newAdmin); f(e2, args); + address adminAfter = defaultAdmin(); - assert adminAfter == newAdmin => ((e2.block.timestamp >= e1.block.timestamp + delayBefore) || adminBefore == newAdmin), - "A delay can't change in less than applied schedule"; + // change can only happen towards the newAdmin, with the delay + assert adminAfter != adminBefore => ( + adminAfter == newAdmin && + e2.block.timestamp >= e1.block.timestamp + delayBefore + ), + "The admin can only change after the enforced delay and to the previously scheduled new admin"; } /* @@ -309,17 +319,19 @@ rule pendingDefaultAdminDelayEnforced(env e1, env e2, method f, calldataarg args */ rule acceptDefaultAdminTransfer(env e) { require nonpayable(e); - requireInvariant defaultAdminConsistency(defaultAdmin()); - requireInvariant singleDefaultAdmin(e.msg.sender, defaultAdmin()); address pendingAdminBefore = pendingDefaultAdmin_(); - uint48 scheduleAfter = pendingDefaultAdminSchedule_(); + uint48 scheduleBefore = pendingDefaultAdminSchedule_(); acceptDefaultAdminTransfer@withrevert(e); bool success = !lastReverted; // liveness - assert success <=> e.msg.sender == pendingAdminBefore && isSet(scheduleAfter) && hasPassed(e, scheduleAfter), + assert success <=> ( + e.msg.sender == pendingAdminBefore && + isSet(scheduleBefore) && + hasPassed(e, scheduleBefore) + ), "only the pending default admin can accept the role after the schedule has been set and passed"; // effect @@ -338,8 +350,8 @@ rule acceptDefaultAdminTransfer(env e) { */ rule cancelDefaultAdminTransfer(env e) { require nonpayable(e); - requireInvariant defaultAdminConsistency(defaultAdmin()); - requireInvariant singleDefaultAdmin(e.msg.sender, defaultAdmin()); + require nonzerosender(e); + requireInvariant defaultAdminConsistency(e.msg.sender); cancelDefaultAdminTransfer@withrevert(e); bool success = !lastReverted; @@ -361,11 +373,11 @@ rule cancelDefaultAdminTransfer(env e) { └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ */ rule changeDefaultAdminDelay(env e, uint48 newDelay) { - require nonpayable(e); require timeSanity(e); + require nonpayable(e); + require nonzerosender(e); require delayChangeWaitSanity(e, newDelay); - requireInvariant defaultAdminConsistency(defaultAdmin()); - requireInvariant singleDefaultAdmin(e.msg.sender, defaultAdmin()); + requireInvariant defaultAdminConsistency(e.msg.sender); uint48 delayBefore = defaultAdminDelay(e); @@ -377,7 +389,9 @@ rule changeDefaultAdminDelay(env e, uint48 newDelay) { "only the current default admin can begin a delay change"; // effect - assert success => pendingDelay_(e) == newDelay, "pending delay is set"; + assert success => pendingDelay_(e) == newDelay, + "pending delay is set"; + assert success => ( pendingDelaySchedule_(e) > e.block.timestamp || delayBefore == newDelay || // Interpreted as decreasing, x - x = 0 @@ -392,17 +406,22 @@ rule changeDefaultAdminDelay(env e, uint48 newDelay) { └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ */ rule pendingDelayWaitEnforced(env e1, env e2, method f, calldataarg args, uint48 newDelay) { - require e1.block.timestamp < e2.block.timestamp; + require e1.block.timestamp <= e2.block.timestamp; uint48 delayBefore = defaultAdminDelay(e1); + changeDefaultAdminDelay(e1, newDelay); f(e2, args); + uint48 delayAfter = defaultAdminDelay(e2); mathint delayWait = newDelay > delayBefore ? increasingDelaySchedule(e1, newDelay) : decreasingDelaySchedule(e1, newDelay); - assert delayAfter == newDelay => (e2.block.timestamp >= delayWait || delayBefore == newDelay), - "A delay can't change in less than applied schedule"; + assert delayAfter != delayBefore => ( + delayAfter == newDelay && + e2.block.timestamp >= delayWait + ), + "A delay can only change after the applied schedule"; } /* @@ -427,8 +446,8 @@ rule pendingDelayWait(env e, uint48 newDelay) { */ rule rollbackDefaultAdminDelay(env e) { require nonpayable(e); - requireInvariant defaultAdminConsistency(defaultAdmin()); - requireInvariant singleDefaultAdmin(e.msg.sender, defaultAdmin()); + require nonzerosender(e); + requireInvariant defaultAdminConsistency(e.msg.sender); rollbackDefaultAdminDelay@withrevert(e); bool success = !lastReverted; @@ -443,58 +462,3 @@ rule rollbackDefaultAdminDelay(env e) { assert success => pendingDelaySchedule_(e) == 0, "Pending default admin delay is reset"; } - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Rule: pending default admin and the delay can only change along with their corresponding schedules │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule pendingValueAndScheduleCoupling(env e, address newAdmin, uint48 newDelay) { - requireInvariant defaultAdminConsistency(defaultAdmin()); - requireInvariant singleDefaultAdmin(e.msg.sender, defaultAdmin()); - - // Pending admin - address pendingAdminBefore = pendingDefaultAdmin_(); - uint48 pendingAdminScheduleBefore = pendingDefaultAdminSchedule_(); - - beginDefaultAdminTransfer(e, newAdmin); - - address pendingAdminAfter = pendingDefaultAdmin_(); - uint48 pendingAdminScheduleAfter = pendingDefaultAdminSchedule_(); - - assert ( - pendingAdminScheduleBefore != pendingDefaultAdminSchedule_() && - pendingAdminBefore == pendingAdminAfter - ) => newAdmin == pendingAdminBefore, "pending admin stays the same if the new admin set is the same"; - - assert ( - pendingAdminBefore != pendingAdminAfter && - pendingAdminScheduleBefore == pendingDefaultAdminSchedule_() - ) => ( - // Schedule doesn't change if: - // - The defaultAdminDelay was reduced to a value such that added to the block.timestamp is equal to previous schedule - e.block.timestamp + defaultAdminDelay(e) == pendingAdminScheduleBefore - ), "pending admin stays the same if a default admin transfer is begun on accepted edge cases"; - - // Pending delay - address pendingDelayBefore = pendingDelay_(e); - uint48 pendingDelayScheduleBefore = pendingDelaySchedule_(e); - - changeDefaultAdminDelay(e, newDelay); - - address pendingDelayAfter = pendingDelay_(e); - uint48 pendingDelayScheduleAfter = pendingDelaySchedule_(e); - - assert ( - pendingDelayScheduleBefore != pendingDelayScheduleAfter && - pendingDelayBefore == pendingDelayAfter - ) => newDelay == pendingDelayBefore || pendingDelayBefore == 0, "pending delay stays the same if the new delay set is the same"; - - assert ( - pendingDelayBefore != pendingDelayAfter && - pendingDelayScheduleBefore == pendingDelayScheduleAfter - ) => ( - increasingDelaySchedule(e, newDelay) == pendingDelayScheduleBefore || - decreasingDelaySchedule(e, newDelay) == pendingDelayScheduleBefore - ), "pending delay stays the same if a default admin transfer is begun on accepted edge cases"; -} diff --git a/certora/specs/helpers/helpers.spec b/certora/specs/helpers/helpers.spec index 24842d62f..04e76df94 100644 --- a/certora/specs/helpers/helpers.spec +++ b/certora/specs/helpers/helpers.spec @@ -1 +1,10 @@ +// environment definition nonpayable(env e) returns bool = e.msg.value == 0; +definition nonzerosender(env e) returns bool = e.msg.sender != 0; + +// constants +definition max_uint48() returns mathint = (1 << 48) - 1; + +// math +definition min(mathint a, mathint b) returns mathint = a < b ? a : b; +definition max(mathint a, mathint b) returns mathint = a > b ? a : b; From 7e814a3074baa921db584c180ff6e300cdec8735 Mon Sep 17 00:00:00 2001 From: Francisco Date: Tue, 23 May 2023 22:21:17 +0100 Subject: [PATCH 068/182] Fix release merge script (#4273) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ernesto García --- scripts/release/workflow/prepare-release-merge.sh | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/scripts/release/workflow/prepare-release-merge.sh b/scripts/release/workflow/prepare-release-merge.sh index 8be96922c..4e6da5145 100644 --- a/scripts/release/workflow/prepare-release-merge.sh +++ b/scripts/release/workflow/prepare-release-merge.sh @@ -9,15 +9,18 @@ MERGE_BRANCH=merge/$GITHUB_REF_NAME git checkout -B "$MERGE_BRANCH" "$GITHUB_REF_NAME" # Get deleted changesets in this branch that might conflict with master -readarray -t DELETED_CHANGESETS < <(git diff origin/master --name-only -- '.changeset/*.md') +# --diff-filter=D - Only deleted files +readarray -t DELETED_CHANGESETS < <(git diff origin/master --diff-filter=D --name-only -- '.changeset/*.md') # Merge master, which will take those files cherry-picked. Auto-resolve conflicts favoring master. -git merge origin/master -m "Merge master to $GITHUB_REF_NAME" -X theirs +# Ignore conflicts that can't be resolved. +git merge origin/master -m "Merge master to $GITHUB_REF_NAME" -X theirs || true # Remove the originally deleted changesets to correctly sync with master rm -f "${DELETED_CHANGESETS[@]}" -git add .changeset/ +# Only git add deleted files +git ls-files --deleted .changeset/ | xargs git add # Allow empty here since there may be no changes if `rm -f` failed for all changesets git commit --allow-empty -m "Sync changesets with master" From 13d5e0466a9855e9305119ed383e54fc913fdc60 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Tue, 23 May 2023 23:26:43 +0200 Subject: [PATCH 069/182] Make Ownable's initial owner explicit (#4267) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ernesto García --- .changeset/clever-pumas-beg.md | 5 +++++ contracts/access/Ownable.sol | 4 ++-- contracts/mocks/ERC1271WalletMock.sol | 4 +--- contracts/proxy/beacon/UpgradeableBeacon.sol | 5 ++--- contracts/proxy/transparent/ProxyAdmin.sol | 5 +++++ test/access/Ownable.test.js | 2 +- test/access/Ownable2Step.test.js | 2 +- test/proxy/beacon/BeaconProxy.test.js | 12 ++++++------ test/proxy/beacon/UpgradeableBeacon.test.js | 7 +++++-- test/proxy/transparent/ProxyAdmin.test.js | 3 +-- 10 files changed, 29 insertions(+), 20 deletions(-) create mode 100644 .changeset/clever-pumas-beg.md diff --git a/.changeset/clever-pumas-beg.md b/.changeset/clever-pumas-beg.md new file mode 100644 index 000000000..5f1f4b13b --- /dev/null +++ b/.changeset/clever-pumas-beg.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': major +--- + +`Ownable`: Add an `initialOwner` parameter to the constructor, making the ownership initialization explicit. diff --git a/contracts/access/Ownable.sol b/contracts/access/Ownable.sol index 1378ffb43..1351dad03 100644 --- a/contracts/access/Ownable.sol +++ b/contracts/access/Ownable.sol @@ -25,8 +25,8 @@ abstract contract Ownable is Context { /** * @dev Initializes the contract setting the deployer as the initial owner. */ - constructor() { - _transferOwnership(_msgSender()); + constructor(address initialOwner) { + _transferOwnership(initialOwner); } /** diff --git a/contracts/mocks/ERC1271WalletMock.sol b/contracts/mocks/ERC1271WalletMock.sol index 015288998..4e6d3ce57 100644 --- a/contracts/mocks/ERC1271WalletMock.sol +++ b/contracts/mocks/ERC1271WalletMock.sol @@ -7,9 +7,7 @@ import "../interfaces/IERC1271.sol"; import "../utils/cryptography/ECDSA.sol"; contract ERC1271WalletMock is Ownable, IERC1271 { - constructor(address originalOwner) { - transferOwnership(originalOwner); - } + constructor(address originalOwner) Ownable(originalOwner) {} function isValidSignature(bytes32 hash, bytes memory signature) public view override returns (bytes4 magicValue) { return ECDSA.recover(hash, signature) == owner() ? this.isValidSignature.selector : bytes4(0); diff --git a/contracts/proxy/beacon/UpgradeableBeacon.sol b/contracts/proxy/beacon/UpgradeableBeacon.sol index 9eeb149a6..557c58f67 100644 --- a/contracts/proxy/beacon/UpgradeableBeacon.sol +++ b/contracts/proxy/beacon/UpgradeableBeacon.sol @@ -21,10 +21,9 @@ contract UpgradeableBeacon is IBeacon, Ownable { event Upgraded(address indexed implementation); /** - * @dev Sets the address of the initial implementation, and the deployer account as the owner who can upgrade the - * beacon. + * @dev Sets the address of the initial implementation, and the initial owner who can upgrade the beacon. */ - constructor(address implementation_) { + constructor(address implementation_, address initialOwner) Ownable(initialOwner) { _setImplementation(implementation_); } diff --git a/contracts/proxy/transparent/ProxyAdmin.sol b/contracts/proxy/transparent/ProxyAdmin.sol index 78a409e01..3739ad85f 100644 --- a/contracts/proxy/transparent/ProxyAdmin.sol +++ b/contracts/proxy/transparent/ProxyAdmin.sol @@ -11,6 +11,11 @@ import "../../access/Ownable.sol"; * explanation of why you would want to use this see the documentation for {TransparentUpgradeableProxy}. */ contract ProxyAdmin is Ownable { + /** + * @dev Sets the initial owner who can perform upgrades. + */ + constructor(address initialOwner) Ownable(initialOwner) {} + /** * @dev Changes the admin of `proxy` to `newAdmin`. * diff --git a/test/access/Ownable.test.js b/test/access/Ownable.test.js index 109150874..07b8764a5 100644 --- a/test/access/Ownable.test.js +++ b/test/access/Ownable.test.js @@ -9,7 +9,7 @@ contract('Ownable', function (accounts) { const [owner, other] = accounts; beforeEach(async function () { - this.ownable = await Ownable.new({ from: owner }); + this.ownable = await Ownable.new(owner); }); it('has an owner', async function () { diff --git a/test/access/Ownable2Step.test.js b/test/access/Ownable2Step.test.js index ce043057b..dfda6b708 100644 --- a/test/access/Ownable2Step.test.js +++ b/test/access/Ownable2Step.test.js @@ -8,7 +8,7 @@ contract('Ownable2Step', function (accounts) { const [owner, accountA, accountB] = accounts; beforeEach(async function () { - this.ownable2Step = await Ownable2Step.new({ from: owner }); + this.ownable2Step = await Ownable2Step.new(owner); }); describe('transfer ownership', function () { diff --git a/test/proxy/beacon/BeaconProxy.test.js b/test/proxy/beacon/BeaconProxy.test.js index 968f00be8..68db10ddc 100644 --- a/test/proxy/beacon/BeaconProxy.test.js +++ b/test/proxy/beacon/BeaconProxy.test.js @@ -11,7 +11,7 @@ const BadBeaconNoImpl = artifacts.require('BadBeaconNoImpl'); const BadBeaconNotContract = artifacts.require('BadBeaconNotContract'); contract('BeaconProxy', function (accounts) { - const [anotherAccount] = accounts; + const [upgradeableBeaconAdmin, anotherAccount] = accounts; describe('bad beacon is not accepted', async function () { it('non-contract beacon', async function () { @@ -49,7 +49,7 @@ contract('BeaconProxy', function (accounts) { }); beforeEach('deploy beacon', async function () { - this.beacon = await UpgradeableBeacon.new(this.implementationV0.address); + this.beacon = await UpgradeableBeacon.new(this.implementationV0.address, upgradeableBeaconAdmin); }); it('no initialization', async function () { @@ -81,7 +81,7 @@ contract('BeaconProxy', function (accounts) { }); it('upgrade a proxy by upgrading its beacon', async function () { - const beacon = await UpgradeableBeacon.new(this.implementationV0.address); + const beacon = await UpgradeableBeacon.new(this.implementationV0.address, upgradeableBeaconAdmin); const value = '10'; const data = this.implementationV0.contract.methods.initializeNonPayableWithValue(value).encodeABI(); @@ -96,7 +96,7 @@ contract('BeaconProxy', function (accounts) { expect(await dummy.version()).to.eq('V1'); // upgrade beacon - await beacon.upgradeTo(this.implementationV1.address); + await beacon.upgradeTo(this.implementationV1.address, { from: upgradeableBeaconAdmin }); // test upgraded version expect(await dummy.version()).to.eq('V2'); @@ -106,7 +106,7 @@ contract('BeaconProxy', function (accounts) { const value1 = '10'; const value2 = '42'; - const beacon = await UpgradeableBeacon.new(this.implementationV0.address); + const beacon = await UpgradeableBeacon.new(this.implementationV0.address, upgradeableBeaconAdmin); const proxy1InitializeData = this.implementationV0.contract.methods .initializeNonPayableWithValue(value1) @@ -130,7 +130,7 @@ contract('BeaconProxy', function (accounts) { expect(await dummy2.version()).to.eq('V1'); // upgrade beacon - await beacon.upgradeTo(this.implementationV1.address); + await beacon.upgradeTo(this.implementationV1.address, { from: upgradeableBeaconAdmin }); // test upgraded version expect(await dummy1.version()).to.eq('V2'); diff --git a/test/proxy/beacon/UpgradeableBeacon.test.js b/test/proxy/beacon/UpgradeableBeacon.test.js index d65f3e0a5..c19b250b9 100644 --- a/test/proxy/beacon/UpgradeableBeacon.test.js +++ b/test/proxy/beacon/UpgradeableBeacon.test.js @@ -9,13 +9,16 @@ contract('UpgradeableBeacon', function (accounts) { const [owner, other] = accounts; it('cannot be created with non-contract implementation', async function () { - await expectRevert(UpgradeableBeacon.new(accounts[0]), 'UpgradeableBeacon: implementation is not a contract'); + await expectRevert( + UpgradeableBeacon.new(accounts[0], owner), + 'UpgradeableBeacon: implementation is not a contract', + ); }); context('once deployed', async function () { beforeEach('deploying beacon', async function () { this.v1 = await Implementation1.new(); - this.beacon = await UpgradeableBeacon.new(this.v1.address, { from: owner }); + this.beacon = await UpgradeableBeacon.new(this.v1.address, owner); }); it('returns implementation', async function () { diff --git a/test/proxy/transparent/ProxyAdmin.test.js b/test/proxy/transparent/ProxyAdmin.test.js index efaaf94c2..85b6695df 100644 --- a/test/proxy/transparent/ProxyAdmin.test.js +++ b/test/proxy/transparent/ProxyAdmin.test.js @@ -17,12 +17,11 @@ contract('ProxyAdmin', function (accounts) { beforeEach(async function () { const initializeData = Buffer.from(''); - this.proxyAdmin = await ProxyAdmin.new({ from: proxyAdminOwner }); + this.proxyAdmin = await ProxyAdmin.new(proxyAdminOwner); const proxy = await TransparentUpgradeableProxy.new( this.implementationV1.address, this.proxyAdmin.address, initializeData, - { from: proxyAdminOwner }, ); this.proxy = await ITransparentUpgradeableProxy.at(proxy.address); }); From cbc6145f5f33744e91e40be153f151c2e0fb850f Mon Sep 17 00:00:00 2001 From: Balaji Shetty Pachai <32358081+balajipachai@users.noreply.github.com> Date: Wed, 24 May 2023 14:09:06 +0530 Subject: [PATCH 070/182] Removes zero address check from balanceOf in ERC1155 (#4263) Co-authored-by: bpachai Co-authored-by: Francisco Giordano --- .changeset/smooth-books-wink.md | 5 +++++ contracts/token/ERC1155/ERC1155.sol | 1 - test/token/ERC1155/ERC1155.behavior.js | 22 ++++++++++------------ 3 files changed, 15 insertions(+), 13 deletions(-) create mode 100644 .changeset/smooth-books-wink.md diff --git a/.changeset/smooth-books-wink.md b/.changeset/smooth-books-wink.md new file mode 100644 index 000000000..e5eb3fbeb --- /dev/null +++ b/.changeset/smooth-books-wink.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': major +--- + +`ERC1155`: Remove check for address zero in `balanceOf`. diff --git a/contracts/token/ERC1155/ERC1155.sol b/contracts/token/ERC1155/ERC1155.sol index b20b711d5..445c3179d 100644 --- a/contracts/token/ERC1155/ERC1155.sol +++ b/contracts/token/ERC1155/ERC1155.sol @@ -68,7 +68,6 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { * - `account` cannot be the zero address. */ function balanceOf(address account, uint256 id) public view virtual override returns (uint256) { - require(account != address(0), "ERC1155: address zero is not a valid owner"); return _balances[id][account]; } diff --git a/test/token/ERC1155/ERC1155.behavior.js b/test/token/ERC1155/ERC1155.behavior.js index 96d448a9e..c41c69c62 100644 --- a/test/token/ERC1155/ERC1155.behavior.js +++ b/test/token/ERC1155/ERC1155.behavior.js @@ -20,11 +20,8 @@ function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, m describe('like an ERC1155', function () { describe('balanceOf', function () { - it('reverts when queried about the zero address', async function () { - await expectRevert( - this.token.balanceOf(ZERO_ADDRESS, firstTokenId), - 'ERC1155: address zero is not a valid owner', - ); + it('should return 0 when queried about the zero address', async function () { + expect(await this.token.balanceOf(ZERO_ADDRESS, firstTokenId)).to.be.bignumber.equal('0'); }); context("when accounts don't own tokens", function () { @@ -76,14 +73,15 @@ function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, m ); }); - it('reverts when one of the addresses is the zero address', async function () { - await expectRevert( - this.token.balanceOfBatch( - [firstTokenHolder, secondTokenHolder, ZERO_ADDRESS], - [firstTokenId, secondTokenId, unknownTokenId], - ), - 'ERC1155: address zero is not a valid owner', + it('should return 0 as the balance when one of the addresses is the zero address', async function () { + const result = await this.token.balanceOfBatch( + [firstTokenHolder, secondTokenHolder, ZERO_ADDRESS], + [firstTokenId, secondTokenId, unknownTokenId], ); + expect(result).to.be.an('array'); + expect(result[0]).to.be.a.bignumber.equal('0'); + expect(result[1]).to.be.a.bignumber.equal('0'); + expect(result[2]).to.be.a.bignumber.equal('0'); }); context("when accounts don't own tokens", function () { From 09329f8a18f08df65863a5060f6e776bf7fccacf Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 24 May 2023 10:37:00 -0300 Subject: [PATCH 071/182] Merge release-v4.9 branch (#4274) Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Francisco Giordano Co-authored-by: github-actions[bot] Co-authored-by: Hadrien Croubois --- .changeset/beige-buses-drop.md | 7 ---- .changeset/curvy-shrimps-enjoy.md | 7 ---- .changeset/curvy-suns-sort.md | 7 ---- .changeset/early-oranges-raise.md | 5 --- .changeset/famous-rules-burn.md | 7 ---- .changeset/five-ducks-develop.md | 5 --- .changeset/five-poets-mix.md | 5 --- .changeset/flat-deers-end.md | 5 --- .changeset/four-bats-sniff.md | 5 --- .changeset/funny-rockets-compete.md | 7 ---- .changeset/gold-chicken-clean.md | 7 ---- .changeset/happy-socks-travel.md | 5 --- .changeset/healthy-squids-stare.md | 7 ---- .changeset/lemon-dogs-kiss.md | 7 ---- .changeset/little-kiwis-ring.md | 7 ---- .changeset/loud-wolves-promise.md | 5 --- .changeset/lovely-dragons-appear.md | 5 --- .changeset/modern-games-exist.md | 5 --- .changeset/new-ways-own.md | 5 --- .changeset/ninety-hornets-kick.md | 5 --- .changeset/perfect-insects-listen.md | 5 --- .changeset/pretty-hornets-play.md | 7 ---- .changeset/proud-comics-deliver.md | 5 --- .changeset/short-roses-judge.md | 5 --- .changeset/silent-dancers-type.md | 5 --- .changeset/slimy-knives-hug.md | 5 --- .changeset/small-cars-appear.md | 5 --- .changeset/small-terms-sleep.md | 5 --- .changeset/strong-bulldogs-buy.md | 5 --- .changeset/tame-ladybugs-sit.md | 7 ---- .changeset/tender-needles-dance.md | 7 ---- .changeset/thin-dragons-report.md | 5 --- .changeset/thirty-swans-exercise.md | 5 --- .changeset/violet-frogs-hide.md | 5 --- .changeset/warm-masks-obey.md | 5 --- .changeset/yellow-swans-cover.md | 5 --- CHANGELOG.md | 39 +++++++++++++++++++ contracts/access/AccessControl.sol | 2 +- .../access/AccessControlDefaultAdminRules.sol | 2 +- .../IAccessControlDefaultAdminRules.sol | 2 +- contracts/access/Ownable.sol | 2 +- contracts/access/Ownable2Step.sol | 2 +- .../polygon/CrossChainEnabledPolygonChild.sol | 2 +- contracts/finance/VestingWallet.sol | 2 +- contracts/governance/Governor.sol | 2 +- contracts/governance/IGovernor.sol | 2 +- contracts/governance/TimelockController.sol | 2 +- .../GovernorCompatibilityBravo.sol | 2 +- .../IGovernorCompatibilityBravo.sol | 2 +- .../extensions/GovernorCountingSimple.sol | 2 +- .../extensions/GovernorPreventLateQuorum.sol | 2 +- .../extensions/GovernorSettings.sol | 2 +- .../extensions/GovernorTimelockCompound.sol | 2 +- .../extensions/GovernorTimelockControl.sol | 2 +- .../governance/extensions/GovernorVotes.sol | 2 +- .../extensions/GovernorVotesComp.sol | 2 +- .../GovernorVotesQuorumFraction.sol | 2 +- contracts/governance/utils/IVotes.sol | 2 +- contracts/governance/utils/Votes.sol | 2 +- contracts/interfaces/IERC1363.sol | 2 +- contracts/interfaces/IERC1363Receiver.sol | 2 +- contracts/interfaces/IERC1363Spender.sol | 2 +- contracts/interfaces/IERC1967.sol | 1 + contracts/interfaces/IERC2612.sol | 2 +- contracts/interfaces/IERC2981.sol | 2 +- .../interfaces/IERC3156FlashBorrower.sol | 2 +- contracts/interfaces/IERC4626.sol | 2 +- contracts/interfaces/IERC4906.sol | 1 + contracts/interfaces/IERC5267.sol | 1 + contracts/interfaces/IERC5313.sol | 1 + contracts/interfaces/IERC5805.sol | 2 +- contracts/interfaces/IERC6372.sol | 2 +- contracts/interfaces/draft-IERC2612.sol | 1 + contracts/metatx/MinimalForwarder.sol | 2 +- contracts/package.json | 2 +- contracts/proxy/Clones.sol | 2 +- contracts/proxy/ERC1967/ERC1967Upgrade.sol | 2 +- .../TransparentUpgradeableProxy.sol | 2 +- contracts/proxy/utils/Initializable.sol | 2 +- contracts/proxy/utils/UUPSUpgradeable.sol | 2 +- contracts/security/ReentrancyGuard.sol | 2 +- contracts/token/ERC1155/ERC1155.sol | 2 +- contracts/token/ERC1155/IERC1155.sol | 2 +- .../ERC1155/extensions/ERC1155Burnable.sol | 2 +- .../presets/ERC1155PresetMinterPauser.sol | 2 +- contracts/token/ERC20/ERC20.sol | 2 +- contracts/token/ERC20/IERC20.sol | 2 +- .../token/ERC20/extensions/ERC20Pausable.sol | 2 +- .../token/ERC20/extensions/ERC20Permit.sol | 2 +- .../token/ERC20/extensions/ERC20Snapshot.sol | 2 +- .../token/ERC20/extensions/ERC20Votes.sol | 2 +- .../token/ERC20/extensions/ERC20Wrapper.sol | 2 +- contracts/token/ERC20/extensions/ERC4626.sol | 2 +- .../token/ERC20/extensions/IERC20Permit.sol | 2 +- .../ERC20/extensions/draft-ERC20Permit.sol | 2 +- .../ERC20/extensions/draft-IERC20Permit.sol | 1 + .../ERC20/presets/ERC20PresetFixedSupply.sol | 2 +- contracts/token/ERC20/utils/SafeERC20.sol | 2 +- contracts/token/ERC20/utils/TokenTimelock.sol | 2 +- contracts/token/ERC721/ERC721.sol | 2 +- contracts/token/ERC721/IERC721.sol | 2 +- .../ERC721/extensions/ERC721Consecutive.sol | 2 +- .../ERC721/extensions/ERC721URIStorage.sol | 2 +- .../token/ERC721/extensions/ERC721Votes.sol | 2 +- .../token/ERC721/extensions/ERC721Wrapper.sol | 1 + .../ERC721PresetMinterPauserAutoId.sol | 2 +- contracts/token/ERC721/utils/ERC721Holder.sol | 2 +- contracts/token/ERC777/ERC777.sol | 2 +- contracts/token/ERC777/IERC777.sol | 2 +- contracts/token/common/ERC2981.sol | 2 +- contracts/utils/Address.sol | 2 +- contracts/utils/Arrays.sol | 2 +- contracts/utils/Checkpoints.sol | 2 +- contracts/utils/Create2.sol | 2 +- contracts/utils/Multicall.sol | 2 +- contracts/utils/ShortStrings.sol | 1 + contracts/utils/StorageSlot.sol | 2 +- contracts/utils/Strings.sol | 2 +- contracts/utils/Timers.sol | 2 +- contracts/utils/cryptography/ECDSA.sol | 2 +- contracts/utils/cryptography/EIP712.sol | 2 +- contracts/utils/cryptography/MerkleProof.sol | 2 +- .../utils/cryptography/SignatureChecker.sol | 2 +- .../utils/introspection/ERC165Checker.sol | 2 +- .../introspection/ERC1820Implementer.sol | 2 +- .../utils/introspection/IERC1820Registry.sol | 2 +- contracts/utils/math/Math.sol | 2 +- contracts/utils/math/SafeMath.sol | 2 +- contracts/utils/structs/BitMaps.sol | 2 +- contracts/utils/structs/DoubleEndedQueue.sol | 2 +- contracts/utils/structs/EnumerableMap.sol | 2 +- contracts/utils/structs/EnumerableSet.sol | 2 +- contracts/vendor/amb/IAMB.sol | 2 +- contracts/vendor/arbitrum/IArbSys.sol | 2 +- contracts/vendor/arbitrum/IBridge.sol | 2 +- contracts/vendor/arbitrum/IOutbox.sol | 2 +- .../vendor/optimism/ICrossDomainMessenger.sol | 2 +- .../vendor/polygon/IFxMessageProcessor.sol | 2 +- package.json | 2 +- 139 files changed, 141 insertions(+), 298 deletions(-) delete mode 100644 .changeset/beige-buses-drop.md delete mode 100644 .changeset/curvy-shrimps-enjoy.md delete mode 100644 .changeset/curvy-suns-sort.md delete mode 100644 .changeset/early-oranges-raise.md delete mode 100644 .changeset/famous-rules-burn.md delete mode 100644 .changeset/five-ducks-develop.md delete mode 100644 .changeset/five-poets-mix.md delete mode 100644 .changeset/flat-deers-end.md delete mode 100644 .changeset/four-bats-sniff.md delete mode 100644 .changeset/funny-rockets-compete.md delete mode 100644 .changeset/gold-chicken-clean.md delete mode 100644 .changeset/happy-socks-travel.md delete mode 100644 .changeset/healthy-squids-stare.md delete mode 100644 .changeset/lemon-dogs-kiss.md delete mode 100644 .changeset/little-kiwis-ring.md delete mode 100644 .changeset/loud-wolves-promise.md delete mode 100644 .changeset/lovely-dragons-appear.md delete mode 100644 .changeset/modern-games-exist.md delete mode 100644 .changeset/new-ways-own.md delete mode 100644 .changeset/ninety-hornets-kick.md delete mode 100644 .changeset/perfect-insects-listen.md delete mode 100644 .changeset/pretty-hornets-play.md delete mode 100644 .changeset/proud-comics-deliver.md delete mode 100644 .changeset/short-roses-judge.md delete mode 100644 .changeset/silent-dancers-type.md delete mode 100644 .changeset/slimy-knives-hug.md delete mode 100644 .changeset/small-cars-appear.md delete mode 100644 .changeset/small-terms-sleep.md delete mode 100644 .changeset/strong-bulldogs-buy.md delete mode 100644 .changeset/tame-ladybugs-sit.md delete mode 100644 .changeset/tender-needles-dance.md delete mode 100644 .changeset/thin-dragons-report.md delete mode 100644 .changeset/thirty-swans-exercise.md delete mode 100644 .changeset/violet-frogs-hide.md delete mode 100644 .changeset/warm-masks-obey.md delete mode 100644 .changeset/yellow-swans-cover.md diff --git a/.changeset/beige-buses-drop.md b/.changeset/beige-buses-drop.md deleted file mode 100644 index ecfd08b35..000000000 --- a/.changeset/beige-buses-drop.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'openzeppelin-solidity': patch ---- - -`Initializable`: optimize `_disableInitializers` by using `!=` instead of `<`. - -pr: #3787 diff --git a/.changeset/curvy-shrimps-enjoy.md b/.changeset/curvy-shrimps-enjoy.md deleted file mode 100644 index 22c2bc54c..000000000 --- a/.changeset/curvy-shrimps-enjoy.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`ReentrancyGuard`: Add a `_reentrancyGuardEntered` function to expose the guard status. - -pr: #3714 diff --git a/.changeset/curvy-suns-sort.md b/.changeset/curvy-suns-sort.md deleted file mode 100644 index 201f45ca7..000000000 --- a/.changeset/curvy-suns-sort.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'openzeppelin-solidity': patch ---- - -`Ownable2Step`: make `acceptOwnership` public virtual to enable usecases that require overriding it. - -pr: #3960 diff --git a/.changeset/early-oranges-raise.md b/.changeset/early-oranges-raise.md deleted file mode 100644 index af60a4432..000000000 --- a/.changeset/early-oranges-raise.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`ERC721Wrapper`: add a new extension of the `ERC721` token which wraps an underlying token. Deposit and withdraw guarantee that the ownership of each token is backed by a corresponding underlying token with the same identifier. diff --git a/.changeset/famous-rules-burn.md b/.changeset/famous-rules-burn.md deleted file mode 100644 index a97aca0b3..000000000 --- a/.changeset/famous-rules-burn.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`EnumerableMap`: add a `keys()` function that returns an array containing all the keys. - -pr: #3920 diff --git a/.changeset/five-ducks-develop.md b/.changeset/five-ducks-develop.md deleted file mode 100644 index fe25db071..000000000 --- a/.changeset/five-ducks-develop.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': patch ---- - -`UUPSUpgradeable.sol`: Change visibility to the functions `upgradeTo ` and `upgradeToAndCall ` from `external` to `public`. diff --git a/.changeset/five-poets-mix.md b/.changeset/five-poets-mix.md deleted file mode 100644 index f5050b246..000000000 --- a/.changeset/five-poets-mix.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': patch ---- - -`TimelockController`: Add the `CallSalt` event to emit on operation schedule. diff --git a/.changeset/flat-deers-end.md b/.changeset/flat-deers-end.md deleted file mode 100644 index 61895f2cf..000000000 --- a/.changeset/flat-deers-end.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`Governor`: add a public `cancel(uint256)` function. diff --git a/.changeset/four-bats-sniff.md b/.changeset/four-bats-sniff.md deleted file mode 100644 index 137b5e515..000000000 --- a/.changeset/four-bats-sniff.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`Governor`: Enable timestamp operation for blockchains without a stable block time. This is achieved by connecting a Governor's internal clock to match a voting token's EIP-6372 interface. diff --git a/.changeset/funny-rockets-compete.md b/.changeset/funny-rockets-compete.md deleted file mode 100644 index 3f665bc9e..000000000 --- a/.changeset/funny-rockets-compete.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'openzeppelin-solidity': patch ---- - -Reformatted codebase with latest version of Prettier Solidity. - -pr: #3898 diff --git a/.changeset/gold-chicken-clean.md b/.changeset/gold-chicken-clean.md deleted file mode 100644 index 1353e9c9c..000000000 --- a/.changeset/gold-chicken-clean.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`Strings`: add `equal` method. - -pr: #3774 diff --git a/.changeset/happy-socks-travel.md b/.changeset/happy-socks-travel.md deleted file mode 100644 index b29d6bacd..000000000 --- a/.changeset/happy-socks-travel.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`IERC5313`: Add an interface for EIP-5313 that is now final. diff --git a/.changeset/healthy-squids-stare.md b/.changeset/healthy-squids-stare.md deleted file mode 100644 index 9e2c9f3dd..000000000 --- a/.changeset/healthy-squids-stare.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'openzeppelin-solidity': patch ---- - -`Math`: optimize `log256` rounding check. - -pr: #3745 diff --git a/.changeset/lemon-dogs-kiss.md b/.changeset/lemon-dogs-kiss.md deleted file mode 100644 index 5e2787cf1..000000000 --- a/.changeset/lemon-dogs-kiss.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'openzeppelin-solidity': patch ---- - -`ERC20Votes`: optimize by using unchecked arithmetic. - -pr: #3748 diff --git a/.changeset/little-kiwis-ring.md b/.changeset/little-kiwis-ring.md deleted file mode 100644 index 81909a513..000000000 --- a/.changeset/little-kiwis-ring.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'openzeppelin-solidity': patch ---- - -`Multicall`: annotate `multicall` function as upgrade safe to not raise a flag for its delegatecall. - -pr: #3961 diff --git a/.changeset/loud-wolves-promise.md b/.changeset/loud-wolves-promise.md deleted file mode 100644 index 544b52c5f..000000000 --- a/.changeset/loud-wolves-promise.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': patch ---- - -`AccessControlDefaultAdminRules`: Clean up pending admin schedule on renounce. diff --git a/.changeset/lovely-dragons-appear.md b/.changeset/lovely-dragons-appear.md deleted file mode 100644 index fe538634a..000000000 --- a/.changeset/lovely-dragons-appear.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`IERC4906`: Add an interface for ERC-4906 that is now Final. diff --git a/.changeset/modern-games-exist.md b/.changeset/modern-games-exist.md deleted file mode 100644 index bd89b4f16..000000000 --- a/.changeset/modern-games-exist.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`StorageSlot`: Add support for `string` and `bytes`. diff --git a/.changeset/new-ways-own.md b/.changeset/new-ways-own.md deleted file mode 100644 index f940bfeb7..000000000 --- a/.changeset/new-ways-own.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': patch ---- - -`ERC20Pausable`, `ERC721Pausable`, `ERC1155Pausable`: Add note regarding missing public pausing functionality diff --git a/.changeset/ninety-hornets-kick.md b/.changeset/ninety-hornets-kick.md deleted file mode 100644 index 16886c5c1..000000000 --- a/.changeset/ninety-hornets-kick.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`Votes`, `ERC20Votes`, `ERC721Votes`: support timestamp checkpointing using EIP-6372. diff --git a/.changeset/perfect-insects-listen.md b/.changeset/perfect-insects-listen.md deleted file mode 100644 index 9e60120ed..000000000 --- a/.changeset/perfect-insects-listen.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`ERC4626`: Add mitigation to the inflation attack through virtual shares and assets. diff --git a/.changeset/pretty-hornets-play.md b/.changeset/pretty-hornets-play.md deleted file mode 100644 index e7d15c24a..000000000 --- a/.changeset/pretty-hornets-play.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`Strings`: add `toString` method for signed integers. - -pr: #3773 diff --git a/.changeset/proud-comics-deliver.md b/.changeset/proud-comics-deliver.md deleted file mode 100644 index e9c1015f8..000000000 --- a/.changeset/proud-comics-deliver.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`ERC20Wrapper`: Make the `underlying` variable private and add a public accessor. diff --git a/.changeset/short-roses-judge.md b/.changeset/short-roses-judge.md deleted file mode 100644 index 002aebb11..000000000 --- a/.changeset/short-roses-judge.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`EIP712`: add EIP-5267 support for better domain discovery. diff --git a/.changeset/silent-dancers-type.md b/.changeset/silent-dancers-type.md deleted file mode 100644 index 74ecf500d..000000000 --- a/.changeset/silent-dancers-type.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`AccessControlDefaultAdminRules`: Add an extension of `AccessControl` with additional security rules for the `DEFAULT_ADMIN_ROLE`. diff --git a/.changeset/slimy-knives-hug.md b/.changeset/slimy-knives-hug.md deleted file mode 100644 index 94099eea7..000000000 --- a/.changeset/slimy-knives-hug.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`SignatureChecker`: Add `isValidERC1271SignatureNow` for checking a signature directly against a smart contract using ERC-1271. diff --git a/.changeset/small-cars-appear.md b/.changeset/small-cars-appear.md deleted file mode 100644 index 0263bcd18..000000000 --- a/.changeset/small-cars-appear.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': patch ---- - -`ECDSA`: Add a function `toDataWithIntendedValidatorHash` that encodes data with version 0x00 following EIP-191. diff --git a/.changeset/small-terms-sleep.md b/.changeset/small-terms-sleep.md deleted file mode 100644 index ed184a1c4..000000000 --- a/.changeset/small-terms-sleep.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`SafeERC20`: Add a `forceApprove` function to improve compatibility with tokens behaving like USDT. diff --git a/.changeset/strong-bulldogs-buy.md b/.changeset/strong-bulldogs-buy.md deleted file mode 100644 index 001b0f88f..000000000 --- a/.changeset/strong-bulldogs-buy.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`ERC1967Upgrade`: removed contract-wide `oz-upgrades-unsafe-allow delegatecall` annotation, replaced by granular annotation in `UUPSUpgradeable`. diff --git a/.changeset/tame-ladybugs-sit.md b/.changeset/tame-ladybugs-sit.md deleted file mode 100644 index 4cddc219e..000000000 --- a/.changeset/tame-ladybugs-sit.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'openzeppelin-solidity': patch ---- - -`MerkleProof`: optimize by using unchecked arithmetic. - -pr: #3745 diff --git a/.changeset/tender-needles-dance.md b/.changeset/tender-needles-dance.md deleted file mode 100644 index 75ce9fbf8..000000000 --- a/.changeset/tender-needles-dance.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`ERC20Wrapper`: self wrapping and deposit by the wrapper itself are now explicitly forbidden. - -commit: 3214f6c25 diff --git a/.changeset/thin-dragons-report.md b/.changeset/thin-dragons-report.md deleted file mode 100644 index b73730f7f..000000000 --- a/.changeset/thin-dragons-report.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`ECDSA`: optimize bytes32 computation by using assembly instead of `abi.encodePacked`. diff --git a/.changeset/thirty-swans-exercise.md b/.changeset/thirty-swans-exercise.md deleted file mode 100644 index a460271b0..000000000 --- a/.changeset/thirty-swans-exercise.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`ERC721URIStorage`: Emit ERC-4906 `MetadataUpdate` in `_setTokenURI`. diff --git a/.changeset/violet-frogs-hide.md b/.changeset/violet-frogs-hide.md deleted file mode 100644 index 21d2bf984..000000000 --- a/.changeset/violet-frogs-hide.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`ShortStrings`: Added a library for handling short strings in a gas efficient way, with fallback to storage for longer strings. diff --git a/.changeset/warm-masks-obey.md b/.changeset/warm-masks-obey.md deleted file mode 100644 index 3bcfa9bdd..000000000 --- a/.changeset/warm-masks-obey.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`SignatureChecker`: Allow return data length greater than 32 from EIP-1271 signers. diff --git a/.changeset/yellow-swans-cover.md b/.changeset/yellow-swans-cover.md deleted file mode 100644 index ee1680178..000000000 --- a/.changeset/yellow-swans-cover.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`UUPSUpgradeable`: added granular `oz-upgrades-unsafe-allow-reachable` annotation to improve upgrade safety checks on latest version of the Upgrades Plugins (starting with `@openzeppelin/upgrades-core@1.21.0`). diff --git a/CHANGELOG.md b/CHANGELOG.md index ffbd3acea..b3b25ac5b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,44 @@ # Changelog + +## 4.9.0 (2023-05-23) + +- `ReentrancyGuard`: Add a `_reentrancyGuardEntered` function to expose the guard status. ([#3714](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3714)) +- `ERC721Wrapper`: add a new extension of the `ERC721` token which wraps an underlying token. Deposit and withdraw guarantee that the ownership of each token is backed by a corresponding underlying token with the same identifier. ([#3863](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3863)) +- `EnumerableMap`: add a `keys()` function that returns an array containing all the keys. ([#3920](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3920)) +- `Governor`: add a public `cancel(uint256)` function. ([#3983](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3983)) +- `Governor`: Enable timestamp operation for blockchains without a stable block time. This is achieved by connecting a Governor's internal clock to match a voting token's EIP-6372 interface. ([#3934](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3934)) +- `Strings`: add `equal` method. ([#3774](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3774)) +- `IERC5313`: Add an interface for EIP-5313 that is now final. ([#4013](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4013)) +- `IERC4906`: Add an interface for ERC-4906 that is now Final. ([#4012](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4012)) +- `StorageSlot`: Add support for `string` and `bytes`. ([#4008](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4008)) +- `Votes`, `ERC20Votes`, `ERC721Votes`: support timestamp checkpointing using EIP-6372. ([#3934](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3934)) +- `ERC4626`: Add mitigation to the inflation attack through virtual shares and assets. ([#3979](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3979)) +- `Strings`: add `toString` method for signed integers. ([#3773](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3773)) +- `ERC20Wrapper`: Make the `underlying` variable private and add a public accessor. ([#4029](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4029)) +- `EIP712`: add EIP-5267 support for better domain discovery. ([#3969](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3969)) +- `AccessControlDefaultAdminRules`: Add an extension of `AccessControl` with additional security rules for the `DEFAULT_ADMIN_ROLE`. ([#4009](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4009)) +- `SignatureChecker`: Add `isValidERC1271SignatureNow` for checking a signature directly against a smart contract using ERC-1271. ([#3932](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3932)) +- `SafeERC20`: Add a `forceApprove` function to improve compatibility with tokens behaving like USDT. ([#4067](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4067)) +- `ERC1967Upgrade`: removed contract-wide `oz-upgrades-unsafe-allow delegatecall` annotation, replaced by granular annotation in `UUPSUpgradeable`. ([#3971](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3971)) +- `ERC20Wrapper`: self wrapping and deposit by the wrapper itself are now explicitly forbidden. ([#4100](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4100)) +- `ECDSA`: optimize bytes32 computation by using assembly instead of `abi.encodePacked`. ([#3853](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3853)) +- `ERC721URIStorage`: Emit ERC-4906 `MetadataUpdate` in `_setTokenURI`. ([#4012](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4012)) +- `ShortStrings`: Added a library for handling short strings in a gas efficient way, with fallback to storage for longer strings. ([#4023](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4023)) +- `SignatureChecker`: Allow return data length greater than 32 from EIP-1271 signers. ([#4038](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4038)) +- `UUPSUpgradeable`: added granular `oz-upgrades-unsafe-allow-reachable` annotation to improve upgrade safety checks on latest version of the Upgrades Plugins (starting with `@openzeppelin/upgrades-core@1.21.0`). ([#3971](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3971)) +- `Initializable`: optimize `_disableInitializers` by using `!=` instead of `<`. ([#3787](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3787)) +- `Ownable2Step`: make `acceptOwnership` public virtual to enable usecases that require overriding it. ([#3960](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3960)) +- `UUPSUpgradeable.sol`: Change visibility to the functions `upgradeTo ` and `upgradeToAndCall ` from `external` to `public`. ([#3959](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3959)) +- `TimelockController`: Add the `CallSalt` event to emit on operation schedule. ([#4001](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4001)) +- Reformatted codebase with latest version of Prettier Solidity. ([#3898](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3898)) +- `Math`: optimize `log256` rounding check. ([#3745](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3745)) +- `ERC20Votes`: optimize by using unchecked arithmetic. ([#3748](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3748)) +- `Multicall`: annotate `multicall` function as upgrade safe to not raise a flag for its delegatecall. ([#3961](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3961)) +- `ERC20Pausable`, `ERC721Pausable`, `ERC1155Pausable`: Add note regarding missing public pausing functionality ([#4007](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4007)) +- `ECDSA`: Add a function `toDataWithIntendedValidatorHash` that encodes data with version 0x00 following EIP-191. ([#4063](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4063)) +- `MerkleProof`: optimize by using unchecked arithmetic. ([#3745](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3745)) + ### Breaking changes - `EIP712`: Addition of ERC5267 support requires support for user defined value types, which was released in Solidity version 0.8.8. This requires a pragma change from `^0.8.0` to `^0.8.8`. diff --git a/contracts/access/AccessControl.sol b/contracts/access/AccessControl.sol index 3a73de78b..0ec6be39c 100644 --- a/contracts/access/AccessControl.sol +++ b/contracts/access/AccessControl.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.8.0) (access/AccessControl.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (access/AccessControl.sol) pragma solidity ^0.8.0; diff --git a/contracts/access/AccessControlDefaultAdminRules.sol b/contracts/access/AccessControlDefaultAdminRules.sol index 07a5b4f70..9de32002a 100644 --- a/contracts/access/AccessControlDefaultAdminRules.sol +++ b/contracts/access/AccessControlDefaultAdminRules.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.8.0) (access/AccessControlDefaultAdminRules.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (access/AccessControlDefaultAdminRules.sol) pragma solidity ^0.8.0; diff --git a/contracts/access/IAccessControlDefaultAdminRules.sol b/contracts/access/IAccessControlDefaultAdminRules.sol index d28c49d95..434324e50 100644 --- a/contracts/access/IAccessControlDefaultAdminRules.sol +++ b/contracts/access/IAccessControlDefaultAdminRules.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts v4.9.0 (access/IAccessControlDefaultAdminRules.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (access/IAccessControlDefaultAdminRules.sol) pragma solidity ^0.8.0; diff --git a/contracts/access/Ownable.sol b/contracts/access/Ownable.sol index 1378ffb43..c181ea1aa 100644 --- a/contracts/access/Ownable.sol +++ b/contracts/access/Ownable.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.7.0) (access/Ownable.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol) pragma solidity ^0.8.0; diff --git a/contracts/access/Ownable2Step.sol b/contracts/access/Ownable2Step.sol index f5a3d8047..e6f699881 100644 --- a/contracts/access/Ownable2Step.sol +++ b/contracts/access/Ownable2Step.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.8.0) (access/Ownable2Step.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable2Step.sol) pragma solidity ^0.8.0; diff --git a/contracts/crosschain/polygon/CrossChainEnabledPolygonChild.sol b/contracts/crosschain/polygon/CrossChainEnabledPolygonChild.sol index fa0994834..1a201fd32 100644 --- a/contracts/crosschain/polygon/CrossChainEnabledPolygonChild.sol +++ b/contracts/crosschain/polygon/CrossChainEnabledPolygonChild.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.7.0) (crosschain/polygon/CrossChainEnabledPolygonChild.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (crosschain/polygon/CrossChainEnabledPolygonChild.sol) pragma solidity ^0.8.4; diff --git a/contracts/finance/VestingWallet.sol b/contracts/finance/VestingWallet.sol index fe67eb54f..45cf9ce52 100644 --- a/contracts/finance/VestingWallet.sol +++ b/contracts/finance/VestingWallet.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.8.0) (finance/VestingWallet.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (finance/VestingWallet.sol) pragma solidity ^0.8.0; import "../token/ERC20/utils/SafeERC20.sol"; diff --git a/contracts/governance/Governor.sol b/contracts/governance/Governor.sol index 241d6139b..390d2b94a 100644 --- a/contracts/governance/Governor.sol +++ b/contracts/governance/Governor.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.8.0) (governance/Governor.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (governance/Governor.sol) pragma solidity ^0.8.0; diff --git a/contracts/governance/IGovernor.sol b/contracts/governance/IGovernor.sol index e4ad83e87..492d95b9a 100644 --- a/contracts/governance/IGovernor.sol +++ b/contracts/governance/IGovernor.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.8.0) (governance/IGovernor.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (governance/IGovernor.sol) pragma solidity ^0.8.0; diff --git a/contracts/governance/TimelockController.sol b/contracts/governance/TimelockController.sol index e330cca06..f70a7d11d 100644 --- a/contracts/governance/TimelockController.sol +++ b/contracts/governance/TimelockController.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.8.2) (governance/TimelockController.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (governance/TimelockController.sol) pragma solidity ^0.8.0; diff --git a/contracts/governance/compatibility/GovernorCompatibilityBravo.sol b/contracts/governance/compatibility/GovernorCompatibilityBravo.sol index 1332ac79d..ecc94fba0 100644 --- a/contracts/governance/compatibility/GovernorCompatibilityBravo.sol +++ b/contracts/governance/compatibility/GovernorCompatibilityBravo.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.8.3) (governance/compatibility/GovernorCompatibilityBravo.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (governance/compatibility/GovernorCompatibilityBravo.sol) pragma solidity ^0.8.0; diff --git a/contracts/governance/compatibility/IGovernorCompatibilityBravo.sol b/contracts/governance/compatibility/IGovernorCompatibilityBravo.sol index 7aa806a18..e64a66a66 100644 --- a/contracts/governance/compatibility/IGovernorCompatibilityBravo.sol +++ b/contracts/governance/compatibility/IGovernorCompatibilityBravo.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts v4.4.1 (governance/compatibility/IGovernorCompatibilityBravo.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (governance/compatibility/IGovernorCompatibilityBravo.sol) pragma solidity ^0.8.0; diff --git a/contracts/governance/extensions/GovernorCountingSimple.sol b/contracts/governance/extensions/GovernorCountingSimple.sol index f3eea9d7f..b9517445a 100644 --- a/contracts/governance/extensions/GovernorCountingSimple.sol +++ b/contracts/governance/extensions/GovernorCountingSimple.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.8.0) (governance/extensions/GovernorCountingSimple.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (governance/extensions/GovernorCountingSimple.sol) pragma solidity ^0.8.0; diff --git a/contracts/governance/extensions/GovernorPreventLateQuorum.sol b/contracts/governance/extensions/GovernorPreventLateQuorum.sol index 68496ca1e..752a92cf1 100644 --- a/contracts/governance/extensions/GovernorPreventLateQuorum.sol +++ b/contracts/governance/extensions/GovernorPreventLateQuorum.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.6.0) (governance/extensions/GovernorPreventLateQuorum.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (governance/extensions/GovernorPreventLateQuorum.sol) pragma solidity ^0.8.0; diff --git a/contracts/governance/extensions/GovernorSettings.sol b/contracts/governance/extensions/GovernorSettings.sol index 527f41cd8..ec6a98300 100644 --- a/contracts/governance/extensions/GovernorSettings.sol +++ b/contracts/governance/extensions/GovernorSettings.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts v4.4.1 (governance/extensions/GovernorSettings.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (governance/extensions/GovernorSettings.sol) pragma solidity ^0.8.0; diff --git a/contracts/governance/extensions/GovernorTimelockCompound.sol b/contracts/governance/extensions/GovernorTimelockCompound.sol index 912171cc3..a706da6a8 100644 --- a/contracts/governance/extensions/GovernorTimelockCompound.sol +++ b/contracts/governance/extensions/GovernorTimelockCompound.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.6.0) (governance/extensions/GovernorTimelockCompound.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (governance/extensions/GovernorTimelockCompound.sol) pragma solidity ^0.8.0; diff --git a/contracts/governance/extensions/GovernorTimelockControl.sol b/contracts/governance/extensions/GovernorTimelockControl.sol index 0cf2ea5f0..7cb60bab1 100644 --- a/contracts/governance/extensions/GovernorTimelockControl.sol +++ b/contracts/governance/extensions/GovernorTimelockControl.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.6.0) (governance/extensions/GovernorTimelockControl.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (governance/extensions/GovernorTimelockControl.sol) pragma solidity ^0.8.0; diff --git a/contracts/governance/extensions/GovernorVotes.sol b/contracts/governance/extensions/GovernorVotes.sol index 644317111..c2e65cba9 100644 --- a/contracts/governance/extensions/GovernorVotes.sol +++ b/contracts/governance/extensions/GovernorVotes.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.6.0) (governance/extensions/GovernorVotes.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (governance/extensions/GovernorVotes.sol) pragma solidity ^0.8.0; diff --git a/contracts/governance/extensions/GovernorVotesComp.sol b/contracts/governance/extensions/GovernorVotesComp.sol index 17250ad7b..e7d7c2c7c 100644 --- a/contracts/governance/extensions/GovernorVotesComp.sol +++ b/contracts/governance/extensions/GovernorVotesComp.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.6.0) (governance/extensions/GovernorVotesComp.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (governance/extensions/GovernorVotesComp.sol) pragma solidity ^0.8.0; diff --git a/contracts/governance/extensions/GovernorVotesQuorumFraction.sol b/contracts/governance/extensions/GovernorVotesQuorumFraction.sol index 403570260..097a79670 100644 --- a/contracts/governance/extensions/GovernorVotesQuorumFraction.sol +++ b/contracts/governance/extensions/GovernorVotesQuorumFraction.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.8.0) (governance/extensions/GovernorVotesQuorumFraction.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (governance/extensions/GovernorVotesQuorumFraction.sol) pragma solidity ^0.8.0; diff --git a/contracts/governance/utils/IVotes.sol b/contracts/governance/utils/IVotes.sol index 4f2b7eb85..647f79655 100644 --- a/contracts/governance/utils/IVotes.sol +++ b/contracts/governance/utils/IVotes.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.5.0) (governance/utils/IVotes.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (governance/utils/IVotes.sol) pragma solidity ^0.8.0; /** diff --git a/contracts/governance/utils/Votes.sol b/contracts/governance/utils/Votes.sol index b24ce824a..f0a53ba3f 100644 --- a/contracts/governance/utils/Votes.sol +++ b/contracts/governance/utils/Votes.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.8.0) (governance/utils/Votes.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (governance/utils/Votes.sol) pragma solidity ^0.8.0; import "../../interfaces/IERC5805.sol"; diff --git a/contracts/interfaces/IERC1363.sol b/contracts/interfaces/IERC1363.sol index 63d87b962..817f3dafc 100644 --- a/contracts/interfaces/IERC1363.sol +++ b/contracts/interfaces/IERC1363.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts v4.4.1 (interfaces/IERC1363.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC1363.sol) pragma solidity ^0.8.0; diff --git a/contracts/interfaces/IERC1363Receiver.sol b/contracts/interfaces/IERC1363Receiver.sol index f5e7a0c28..382d7f111 100644 --- a/contracts/interfaces/IERC1363Receiver.sol +++ b/contracts/interfaces/IERC1363Receiver.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts v4.4.1 (interfaces/IERC1363Receiver.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC1363Receiver.sol) pragma solidity ^0.8.0; diff --git a/contracts/interfaces/IERC1363Spender.sol b/contracts/interfaces/IERC1363Spender.sol index 16dd5e0fe..09a7bd278 100644 --- a/contracts/interfaces/IERC1363Spender.sol +++ b/contracts/interfaces/IERC1363Spender.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts v4.4.1 (interfaces/IERC1363Spender.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC1363Spender.sol) pragma solidity ^0.8.0; diff --git a/contracts/interfaces/IERC1967.sol b/contracts/interfaces/IERC1967.sol index ab4450eec..6fb112a2e 100644 --- a/contracts/interfaces/IERC1967.sol +++ b/contracts/interfaces/IERC1967.sol @@ -1,4 +1,5 @@ // SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC1967.sol) pragma solidity ^0.8.0; diff --git a/contracts/interfaces/IERC2612.sol b/contracts/interfaces/IERC2612.sol index 6dfdf6f63..cd5fca4cc 100644 --- a/contracts/interfaces/IERC2612.sol +++ b/contracts/interfaces/IERC2612.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts v4.4.1 (interfaces/IERC2612.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC2612.sol) pragma solidity ^0.8.0; diff --git a/contracts/interfaces/IERC2981.sol b/contracts/interfaces/IERC2981.sol index 1c9448a91..465b872ee 100644 --- a/contracts/interfaces/IERC2981.sol +++ b/contracts/interfaces/IERC2981.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.6.0) (interfaces/IERC2981.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC2981.sol) pragma solidity ^0.8.0; diff --git a/contracts/interfaces/IERC3156FlashBorrower.sol b/contracts/interfaces/IERC3156FlashBorrower.sol index 0428391fc..84bd72150 100644 --- a/contracts/interfaces/IERC3156FlashBorrower.sol +++ b/contracts/interfaces/IERC3156FlashBorrower.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.7.0) (interfaces/IERC3156FlashBorrower.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC3156FlashBorrower.sol) pragma solidity ^0.8.0; diff --git a/contracts/interfaces/IERC4626.sol b/contracts/interfaces/IERC4626.sol index 08e5de717..77dd96a05 100644 --- a/contracts/interfaces/IERC4626.sol +++ b/contracts/interfaces/IERC4626.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.8.0) (interfaces/IERC4626.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC4626.sol) pragma solidity ^0.8.0; diff --git a/contracts/interfaces/IERC4906.sol b/contracts/interfaces/IERC4906.sol index c9eaa1296..f5a6e00f9 100644 --- a/contracts/interfaces/IERC4906.sol +++ b/contracts/interfaces/IERC4906.sol @@ -1,4 +1,5 @@ // SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC4906.sol) pragma solidity ^0.8.0; diff --git a/contracts/interfaces/IERC5267.sol b/contracts/interfaces/IERC5267.sol index 3adc4a703..4d3a6b92a 100644 --- a/contracts/interfaces/IERC5267.sol +++ b/contracts/interfaces/IERC5267.sol @@ -1,4 +1,5 @@ // SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC5267.sol) pragma solidity ^0.8.0; diff --git a/contracts/interfaces/IERC5313.sol b/contracts/interfaces/IERC5313.sol index 2c9a47da9..e26094c06 100644 --- a/contracts/interfaces/IERC5313.sol +++ b/contracts/interfaces/IERC5313.sol @@ -1,4 +1,5 @@ // SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC5313.sol) pragma solidity ^0.8.0; diff --git a/contracts/interfaces/IERC5805.sol b/contracts/interfaces/IERC5805.sol index 2c2e5e345..a012ccb15 100644 --- a/contracts/interfaces/IERC5805.sol +++ b/contracts/interfaces/IERC5805.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (interfaces/IERC5805.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC5805.sol) pragma solidity ^0.8.0; diff --git a/contracts/interfaces/IERC6372.sol b/contracts/interfaces/IERC6372.sol index e1a0bf8b0..4c5fe039c 100644 --- a/contracts/interfaces/IERC6372.sol +++ b/contracts/interfaces/IERC6372.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (interfaces/IERC6372.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC6372.sol) pragma solidity ^0.8.0; diff --git a/contracts/interfaces/draft-IERC2612.sol b/contracts/interfaces/draft-IERC2612.sol index 1ea7bf1c0..024b753e2 100644 --- a/contracts/interfaces/draft-IERC2612.sol +++ b/contracts/interfaces/draft-IERC2612.sol @@ -1,4 +1,5 @@ // SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.9.0) (interfaces/draft-IERC2612.sol) pragma solidity ^0.8.0; diff --git a/contracts/metatx/MinimalForwarder.sol b/contracts/metatx/MinimalForwarder.sol index 9298ae675..de2646269 100644 --- a/contracts/metatx/MinimalForwarder.sol +++ b/contracts/metatx/MinimalForwarder.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.8.0) (metatx/MinimalForwarder.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (metatx/MinimalForwarder.sol) pragma solidity ^0.8.0; diff --git a/contracts/package.json b/contracts/package.json index 55e70b179..4d0f576bb 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -1,7 +1,7 @@ { "name": "@openzeppelin/contracts", "description": "Secure Smart Contract library for Solidity", - "version": "4.8.2", + "version": "4.9.0", "files": [ "**/*.sol", "/build/contracts/*.json", diff --git a/contracts/proxy/Clones.sol b/contracts/proxy/Clones.sol index 712519892..583e0223e 100644 --- a/contracts/proxy/Clones.sol +++ b/contracts/proxy/Clones.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.8.0) (proxy/Clones.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (proxy/Clones.sol) pragma solidity ^0.8.0; diff --git a/contracts/proxy/ERC1967/ERC1967Upgrade.sol b/contracts/proxy/ERC1967/ERC1967Upgrade.sol index 3942ca699..cc9e5695b 100644 --- a/contracts/proxy/ERC1967/ERC1967Upgrade.sol +++ b/contracts/proxy/ERC1967/ERC1967Upgrade.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.8.3) (proxy/ERC1967/ERC1967Upgrade.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (proxy/ERC1967/ERC1967Upgrade.sol) pragma solidity ^0.8.2; diff --git a/contracts/proxy/transparent/TransparentUpgradeableProxy.sol b/contracts/proxy/transparent/TransparentUpgradeableProxy.sol index e49768ab9..6f18b492f 100644 --- a/contracts/proxy/transparent/TransparentUpgradeableProxy.sol +++ b/contracts/proxy/transparent/TransparentUpgradeableProxy.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.8.3) (proxy/transparent/TransparentUpgradeableProxy.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (proxy/transparent/TransparentUpgradeableProxy.sol) pragma solidity ^0.8.0; diff --git a/contracts/proxy/utils/Initializable.sol b/contracts/proxy/utils/Initializable.sol index 3c898ec5a..33ec22791 100644 --- a/contracts/proxy/utils/Initializable.sol +++ b/contracts/proxy/utils/Initializable.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.8.1) (proxy/utils/Initializable.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (proxy/utils/Initializable.sol) pragma solidity ^0.8.2; diff --git a/contracts/proxy/utils/UUPSUpgradeable.sol b/contracts/proxy/utils/UUPSUpgradeable.sol index 4ff026638..86684cfc3 100644 --- a/contracts/proxy/utils/UUPSUpgradeable.sol +++ b/contracts/proxy/utils/UUPSUpgradeable.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.8.0) (proxy/utils/UUPSUpgradeable.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (proxy/utils/UUPSUpgradeable.sol) pragma solidity ^0.8.0; diff --git a/contracts/security/ReentrancyGuard.sol b/contracts/security/ReentrancyGuard.sol index f9281ec64..dac508b6a 100644 --- a/contracts/security/ReentrancyGuard.sol +++ b/contracts/security/ReentrancyGuard.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.8.0) (security/ReentrancyGuard.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (security/ReentrancyGuard.sol) pragma solidity ^0.8.0; diff --git a/contracts/token/ERC1155/ERC1155.sol b/contracts/token/ERC1155/ERC1155.sol index 445c3179d..2da521d55 100644 --- a/contracts/token/ERC1155/ERC1155.sol +++ b/contracts/token/ERC1155/ERC1155.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC1155/ERC1155.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC1155/ERC1155.sol) pragma solidity ^0.8.0; diff --git a/contracts/token/ERC1155/IERC1155.sol b/contracts/token/ERC1155/IERC1155.sol index eae0b7029..3446e604a 100644 --- a/contracts/token/ERC1155/IERC1155.sol +++ b/contracts/token/ERC1155/IERC1155.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.7.0) (token/ERC1155/IERC1155.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC1155/IERC1155.sol) pragma solidity ^0.8.0; diff --git a/contracts/token/ERC1155/extensions/ERC1155Burnable.sol b/contracts/token/ERC1155/extensions/ERC1155Burnable.sol index cc81957a7..9ee371986 100644 --- a/contracts/token/ERC1155/extensions/ERC1155Burnable.sol +++ b/contracts/token/ERC1155/extensions/ERC1155Burnable.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC1155/extensions/ERC1155Burnable.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC1155/extensions/ERC1155Burnable.sol) pragma solidity ^0.8.0; diff --git a/contracts/token/ERC1155/presets/ERC1155PresetMinterPauser.sol b/contracts/token/ERC1155/presets/ERC1155PresetMinterPauser.sol index fd7729aa7..2f68dcaf5 100644 --- a/contracts/token/ERC1155/presets/ERC1155PresetMinterPauser.sol +++ b/contracts/token/ERC1155/presets/ERC1155PresetMinterPauser.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC1155/presets/ERC1155PresetMinterPauser.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC1155/presets/ERC1155PresetMinterPauser.sol) pragma solidity ^0.8.0; diff --git a/contracts/token/ERC20/ERC20.sol b/contracts/token/ERC20/ERC20.sol index 7c53c6962..91b7f98f6 100644 --- a/contracts/token/ERC20/ERC20.sol +++ b/contracts/token/ERC20/ERC20.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC20/ERC20.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/ERC20.sol) pragma solidity ^0.8.0; diff --git a/contracts/token/ERC20/IERC20.sol b/contracts/token/ERC20/IERC20.sol index 66c4e4d88..6d5b4e9f1 100644 --- a/contracts/token/ERC20/IERC20.sol +++ b/contracts/token/ERC20/IERC20.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol) pragma solidity ^0.8.0; diff --git a/contracts/token/ERC20/extensions/ERC20Pausable.sol b/contracts/token/ERC20/extensions/ERC20Pausable.sol index 36cc30ce4..3b2b6329a 100644 --- a/contracts/token/ERC20/extensions/ERC20Pausable.sol +++ b/contracts/token/ERC20/extensions/ERC20Pausable.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.8.2) (token/ERC20/extensions/ERC20Pausable.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/ERC20Pausable.sol) pragma solidity ^0.8.0; diff --git a/contracts/token/ERC20/extensions/ERC20Permit.sol b/contracts/token/ERC20/extensions/ERC20Permit.sol index a357199b1..7ee7331f7 100644 --- a/contracts/token/ERC20/extensions/ERC20Permit.sol +++ b/contracts/token/ERC20/extensions/ERC20Permit.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/extensions/ERC20Permit.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/ERC20Permit.sol) pragma solidity ^0.8.0; diff --git a/contracts/token/ERC20/extensions/ERC20Snapshot.sol b/contracts/token/ERC20/extensions/ERC20Snapshot.sol index ee104b0ec..504e7a921 100644 --- a/contracts/token/ERC20/extensions/ERC20Snapshot.sol +++ b/contracts/token/ERC20/extensions/ERC20Snapshot.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.7.0) (token/ERC20/extensions/ERC20Snapshot.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/ERC20Snapshot.sol) pragma solidity ^0.8.0; diff --git a/contracts/token/ERC20/extensions/ERC20Votes.sol b/contracts/token/ERC20/extensions/ERC20Votes.sol index f44b89604..8b31fda6e 100644 --- a/contracts/token/ERC20/extensions/ERC20Votes.sol +++ b/contracts/token/ERC20/extensions/ERC20Votes.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.8.1) (token/ERC20/extensions/ERC20Votes.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/ERC20Votes.sol) pragma solidity ^0.8.0; diff --git a/contracts/token/ERC20/extensions/ERC20Wrapper.sol b/contracts/token/ERC20/extensions/ERC20Wrapper.sol index bfe782e43..bc85a0b37 100644 --- a/contracts/token/ERC20/extensions/ERC20Wrapper.sol +++ b/contracts/token/ERC20/extensions/ERC20Wrapper.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/extensions/ERC20Wrapper.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/ERC20Wrapper.sol) pragma solidity ^0.8.0; diff --git a/contracts/token/ERC20/extensions/ERC4626.sol b/contracts/token/ERC20/extensions/ERC4626.sol index 40e9cf2b3..4f6a2d64e 100644 --- a/contracts/token/ERC20/extensions/ERC4626.sol +++ b/contracts/token/ERC20/extensions/ERC4626.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.8.1) (token/ERC20/extensions/ERC4626.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/ERC4626.sol) pragma solidity ^0.8.0; diff --git a/contracts/token/ERC20/extensions/IERC20Permit.sol b/contracts/token/ERC20/extensions/IERC20Permit.sol index bb43e53b6..0deb54b14 100644 --- a/contracts/token/ERC20/extensions/IERC20Permit.sol +++ b/contracts/token/ERC20/extensions/IERC20Permit.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Permit.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/IERC20Permit.sol) pragma solidity ^0.8.0; diff --git a/contracts/token/ERC20/extensions/draft-ERC20Permit.sol b/contracts/token/ERC20/extensions/draft-ERC20Permit.sol index 6579ef33f..55b38b268 100644 --- a/contracts/token/ERC20/extensions/draft-ERC20Permit.sol +++ b/contracts/token/ERC20/extensions/draft-ERC20Permit.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC20/extensions/draft-ERC20Permit.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/draft-ERC20Permit.sol) pragma solidity ^0.8.0; diff --git a/contracts/token/ERC20/extensions/draft-IERC20Permit.sol b/contracts/token/ERC20/extensions/draft-IERC20Permit.sol index 1df6c537d..ff6ede3cc 100644 --- a/contracts/token/ERC20/extensions/draft-IERC20Permit.sol +++ b/contracts/token/ERC20/extensions/draft-IERC20Permit.sol @@ -1,4 +1,5 @@ // SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/draft-IERC20Permit.sol) pragma solidity ^0.8.0; diff --git a/contracts/token/ERC20/presets/ERC20PresetFixedSupply.sol b/contracts/token/ERC20/presets/ERC20PresetFixedSupply.sol index e8268145d..d293999cd 100644 --- a/contracts/token/ERC20/presets/ERC20PresetFixedSupply.sol +++ b/contracts/token/ERC20/presets/ERC20PresetFixedSupply.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC20/presets/ERC20PresetFixedSupply.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/presets/ERC20PresetFixedSupply.sol) pragma solidity ^0.8.0; import "../extensions/ERC20Burnable.sol"; diff --git a/contracts/token/ERC20/utils/SafeERC20.sol b/contracts/token/ERC20/utils/SafeERC20.sol index 2f76386d5..47d1684f1 100644 --- a/contracts/token/ERC20/utils/SafeERC20.sol +++ b/contracts/token/ERC20/utils/SafeERC20.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC20/utils/SafeERC20.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/utils/SafeERC20.sol) pragma solidity ^0.8.0; diff --git a/contracts/token/ERC20/utils/TokenTimelock.sol b/contracts/token/ERC20/utils/TokenTimelock.sol index ed855b7bc..cffdac5d4 100644 --- a/contracts/token/ERC20/utils/TokenTimelock.sol +++ b/contracts/token/ERC20/utils/TokenTimelock.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC20/utils/TokenTimelock.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/utils/TokenTimelock.sol) pragma solidity ^0.8.0; diff --git a/contracts/token/ERC721/ERC721.sol b/contracts/token/ERC721/ERC721.sol index 428338d90..7942e6fe4 100644 --- a/contracts/token/ERC721/ERC721.sol +++ b/contracts/token/ERC721/ERC721.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.8.2) (token/ERC721/ERC721.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC721/ERC721.sol) pragma solidity ^0.8.0; diff --git a/contracts/token/ERC721/IERC721.sol b/contracts/token/ERC721/IERC721.sol index 7b60a9f51..293f56683 100644 --- a/contracts/token/ERC721/IERC721.sol +++ b/contracts/token/ERC721/IERC721.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC721/IERC721.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC721/IERC721.sol) pragma solidity ^0.8.0; diff --git a/contracts/token/ERC721/extensions/ERC721Consecutive.sol b/contracts/token/ERC721/extensions/ERC721Consecutive.sol index 9451c8c59..4312d9849 100644 --- a/contracts/token/ERC721/extensions/ERC721Consecutive.sol +++ b/contracts/token/ERC721/extensions/ERC721Consecutive.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.8.2) (token/ERC721/extensions/ERC721Consecutive.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC721/extensions/ERC721Consecutive.sol) pragma solidity ^0.8.0; diff --git a/contracts/token/ERC721/extensions/ERC721URIStorage.sol b/contracts/token/ERC721/extensions/ERC721URIStorage.sol index 201b05a81..c9d79b6b2 100644 --- a/contracts/token/ERC721/extensions/ERC721URIStorage.sol +++ b/contracts/token/ERC721/extensions/ERC721URIStorage.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.7.0) (token/ERC721/extensions/ERC721URIStorage.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC721/extensions/ERC721URIStorage.sol) pragma solidity ^0.8.0; diff --git a/contracts/token/ERC721/extensions/ERC721Votes.sol b/contracts/token/ERC721/extensions/ERC721Votes.sol index 31397f107..55b82c1c1 100644 --- a/contracts/token/ERC721/extensions/ERC721Votes.sol +++ b/contracts/token/ERC721/extensions/ERC721Votes.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC721/extensions/ERC721Votes.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC721/extensions/ERC721Votes.sol) pragma solidity ^0.8.0; diff --git a/contracts/token/ERC721/extensions/ERC721Wrapper.sol b/contracts/token/ERC721/extensions/ERC721Wrapper.sol index 83e59ce88..c7ff06dec 100644 --- a/contracts/token/ERC721/extensions/ERC721Wrapper.sol +++ b/contracts/token/ERC721/extensions/ERC721Wrapper.sol @@ -1,4 +1,5 @@ // SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC721/extensions/ERC721Wrapper.sol) pragma solidity ^0.8.0; diff --git a/contracts/token/ERC721/presets/ERC721PresetMinterPauserAutoId.sol b/contracts/token/ERC721/presets/ERC721PresetMinterPauserAutoId.sol index 478e5808d..da53b88bd 100644 --- a/contracts/token/ERC721/presets/ERC721PresetMinterPauserAutoId.sol +++ b/contracts/token/ERC721/presets/ERC721PresetMinterPauserAutoId.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC721/presets/ERC721PresetMinterPauserAutoId.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC721/presets/ERC721PresetMinterPauserAutoId.sol) pragma solidity ^0.8.0; diff --git a/contracts/token/ERC721/utils/ERC721Holder.sol b/contracts/token/ERC721/utils/ERC721Holder.sol index cfa533a47..f18ad8a1d 100644 --- a/contracts/token/ERC721/utils/ERC721Holder.sol +++ b/contracts/token/ERC721/utils/ERC721Holder.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts v4.4.1 (token/ERC721/utils/ERC721Holder.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC721/utils/ERC721Holder.sol) pragma solidity ^0.8.0; diff --git a/contracts/token/ERC777/ERC777.sol b/contracts/token/ERC777/ERC777.sol index aa95b1843..c9a17d862 100644 --- a/contracts/token/ERC777/ERC777.sol +++ b/contracts/token/ERC777/ERC777.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC777/ERC777.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC777/ERC777.sol) pragma solidity ^0.8.0; diff --git a/contracts/token/ERC777/IERC777.sol b/contracts/token/ERC777/IERC777.sol index d3bede626..2d4f6df88 100644 --- a/contracts/token/ERC777/IERC777.sol +++ b/contracts/token/ERC777/IERC777.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC777/IERC777.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC777/IERC777.sol) pragma solidity ^0.8.0; diff --git a/contracts/token/common/ERC2981.sol b/contracts/token/common/ERC2981.sol index f49cc8d98..7d47b6c7e 100644 --- a/contracts/token/common/ERC2981.sol +++ b/contracts/token/common/ERC2981.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.7.0) (token/common/ERC2981.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (token/common/ERC2981.sol) pragma solidity ^0.8.0; diff --git a/contracts/utils/Address.sol b/contracts/utils/Address.sol index 5ff14140a..0791a66a7 100644 --- a/contracts/utils/Address.sol +++ b/contracts/utils/Address.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.8.0) (utils/Address.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol) pragma solidity ^0.8.1; diff --git a/contracts/utils/Arrays.sol b/contracts/utils/Arrays.sol index 24964759a..66fe322b5 100644 --- a/contracts/utils/Arrays.sol +++ b/contracts/utils/Arrays.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.8.0) (utils/Arrays.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (utils/Arrays.sol) pragma solidity ^0.8.0; diff --git a/contracts/utils/Checkpoints.sol b/contracts/utils/Checkpoints.sol index 1bb66dec9..9d4f0f96e 100644 --- a/contracts/utils/Checkpoints.sol +++ b/contracts/utils/Checkpoints.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.8.1) (utils/Checkpoints.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (utils/Checkpoints.sol) // This file was procedurally generated from scripts/generate/templates/Checkpoints.js. pragma solidity ^0.8.0; diff --git a/contracts/utils/Create2.sol b/contracts/utils/Create2.sol index 2255a4df8..29fa97329 100644 --- a/contracts/utils/Create2.sol +++ b/contracts/utils/Create2.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.8.0) (utils/Create2.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (utils/Create2.sol) pragma solidity ^0.8.0; diff --git a/contracts/utils/Multicall.sol b/contracts/utils/Multicall.sol index 7470c5595..5729f8452 100644 --- a/contracts/utils/Multicall.sol +++ b/contracts/utils/Multicall.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.5.0) (utils/Multicall.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (utils/Multicall.sol) pragma solidity ^0.8.0; diff --git a/contracts/utils/ShortStrings.sol b/contracts/utils/ShortStrings.sol index a8904777a..90a054999 100644 --- a/contracts/utils/ShortStrings.sol +++ b/contracts/utils/ShortStrings.sol @@ -1,4 +1,5 @@ // SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.9.0) (utils/ShortStrings.sol) pragma solidity ^0.8.8; diff --git a/contracts/utils/StorageSlot.sol b/contracts/utils/StorageSlot.sol index 44285c900..df62b8f7b 100644 --- a/contracts/utils/StorageSlot.sol +++ b/contracts/utils/StorageSlot.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.7.0) (utils/StorageSlot.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (utils/StorageSlot.sol) // This file was procedurally generated from scripts/generate/templates/StorageSlot.js. pragma solidity ^0.8.0; diff --git a/contracts/utils/Strings.sol b/contracts/utils/Strings.sol index 3a037f477..657ebd663 100644 --- a/contracts/utils/Strings.sol +++ b/contracts/utils/Strings.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.8.0) (utils/Strings.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (utils/Strings.sol) pragma solidity ^0.8.0; diff --git a/contracts/utils/Timers.sol b/contracts/utils/Timers.sol index 1c92b029b..04f866452 100644 --- a/contracts/utils/Timers.sol +++ b/contracts/utils/Timers.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts v4.4.1 (utils/Timers.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (utils/Timers.sol) pragma solidity ^0.8.0; diff --git a/contracts/utils/cryptography/ECDSA.sol b/contracts/utils/cryptography/ECDSA.sol index 77279eb4f..4326e5b66 100644 --- a/contracts/utils/cryptography/ECDSA.sol +++ b/contracts/utils/cryptography/ECDSA.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.8.0) (utils/cryptography/ECDSA.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (utils/cryptography/ECDSA.sol) pragma solidity ^0.8.0; diff --git a/contracts/utils/cryptography/EIP712.sol b/contracts/utils/cryptography/EIP712.sol index 6a4e1cad2..2a0e73486 100644 --- a/contracts/utils/cryptography/EIP712.sol +++ b/contracts/utils/cryptography/EIP712.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.8.0) (utils/cryptography/EIP712.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (utils/cryptography/EIP712.sol) pragma solidity ^0.8.8; diff --git a/contracts/utils/cryptography/MerkleProof.sol b/contracts/utils/cryptography/MerkleProof.sol index 3862fdbfe..cd79e51cf 100644 --- a/contracts/utils/cryptography/MerkleProof.sol +++ b/contracts/utils/cryptography/MerkleProof.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.8.0) (utils/cryptography/MerkleProof.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (utils/cryptography/MerkleProof.sol) pragma solidity ^0.8.0; diff --git a/contracts/utils/cryptography/SignatureChecker.sol b/contracts/utils/cryptography/SignatureChecker.sol index b81cf40be..1815d27fe 100644 --- a/contracts/utils/cryptography/SignatureChecker.sol +++ b/contracts/utils/cryptography/SignatureChecker.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.8.0) (utils/cryptography/SignatureChecker.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (utils/cryptography/SignatureChecker.sol) pragma solidity ^0.8.0; diff --git a/contracts/utils/introspection/ERC165Checker.sol b/contracts/utils/introspection/ERC165Checker.sol index fd51159cd..4831b6522 100644 --- a/contracts/utils/introspection/ERC165Checker.sol +++ b/contracts/utils/introspection/ERC165Checker.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.8.2) (utils/introspection/ERC165Checker.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (utils/introspection/ERC165Checker.sol) pragma solidity ^0.8.0; diff --git a/contracts/utils/introspection/ERC1820Implementer.sol b/contracts/utils/introspection/ERC1820Implementer.sol index cf4b50498..7a684eded 100644 --- a/contracts/utils/introspection/ERC1820Implementer.sol +++ b/contracts/utils/introspection/ERC1820Implementer.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC1820Implementer.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (utils/introspection/ERC1820Implementer.sol) pragma solidity ^0.8.0; diff --git a/contracts/utils/introspection/IERC1820Registry.sol b/contracts/utils/introspection/IERC1820Registry.sol index a146bc2a6..922df0408 100644 --- a/contracts/utils/introspection/IERC1820Registry.sol +++ b/contracts/utils/introspection/IERC1820Registry.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.8.0) (utils/introspection/IERC1820Registry.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (utils/introspection/IERC1820Registry.sol) pragma solidity ^0.8.0; diff --git a/contracts/utils/math/Math.sol b/contracts/utils/math/Math.sol index f8e7ca0a9..551b1a512 100644 --- a/contracts/utils/math/Math.sol +++ b/contracts/utils/math/Math.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.8.0) (utils/math/Math.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (utils/math/Math.sol) pragma solidity ^0.8.0; diff --git a/contracts/utils/math/SafeMath.sol b/contracts/utils/math/SafeMath.sol index 2f48fb736..f7315a61e 100644 --- a/contracts/utils/math/SafeMath.sol +++ b/contracts/utils/math/SafeMath.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.6.0) (utils/math/SafeMath.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (utils/math/SafeMath.sol) pragma solidity ^0.8.0; diff --git a/contracts/utils/structs/BitMaps.sol b/contracts/utils/structs/BitMaps.sol index eb67bfab0..2567fce47 100644 --- a/contracts/utils/structs/BitMaps.sol +++ b/contracts/utils/structs/BitMaps.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.8.0) (utils/structs/BitMaps.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (utils/structs/BitMaps.sol) pragma solidity ^0.8.0; /** diff --git a/contracts/utils/structs/DoubleEndedQueue.sol b/contracts/utils/structs/DoubleEndedQueue.sol index 6b3ea70e3..325918d2d 100644 --- a/contracts/utils/structs/DoubleEndedQueue.sol +++ b/contracts/utils/structs/DoubleEndedQueue.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.6.0) (utils/structs/DoubleEndedQueue.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (utils/structs/DoubleEndedQueue.sol) pragma solidity ^0.8.4; import "../math/SafeCast.sol"; diff --git a/contracts/utils/structs/EnumerableMap.sol b/contracts/utils/structs/EnumerableMap.sol index fb21f02cf..c49d2d675 100644 --- a/contracts/utils/structs/EnumerableMap.sol +++ b/contracts/utils/structs/EnumerableMap.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.8.0) (utils/structs/EnumerableMap.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (utils/structs/EnumerableMap.sol) // This file was procedurally generated from scripts/generate/templates/EnumerableMap.js. pragma solidity ^0.8.0; diff --git a/contracts/utils/structs/EnumerableSet.sol b/contracts/utils/structs/EnumerableSet.sol index a01f82d41..447f96302 100644 --- a/contracts/utils/structs/EnumerableSet.sol +++ b/contracts/utils/structs/EnumerableSet.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.8.0) (utils/structs/EnumerableSet.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (utils/structs/EnumerableSet.sol) // This file was procedurally generated from scripts/generate/templates/EnumerableSet.js. pragma solidity ^0.8.0; diff --git a/contracts/vendor/amb/IAMB.sol b/contracts/vendor/amb/IAMB.sol index 73a2bd24b..658f7260e 100644 --- a/contracts/vendor/amb/IAMB.sol +++ b/contracts/vendor/amb/IAMB.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.6.0) (vendor/amb/IAMB.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (vendor/amb/IAMB.sol) pragma solidity ^0.8.0; interface IAMB { diff --git a/contracts/vendor/arbitrum/IArbSys.sol b/contracts/vendor/arbitrum/IArbSys.sol index 9b79d5c16..ea4e7504f 100644 --- a/contracts/vendor/arbitrum/IArbSys.sol +++ b/contracts/vendor/arbitrum/IArbSys.sol @@ -1,7 +1,7 @@ // Copyright 2021-2022, Offchain Labs, Inc. // For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE // SPDX-License-Identifier: BUSL-1.1 -// OpenZeppelin Contracts (last updated v4.8.0) (vendor/arbitrum/IArbSys.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (vendor/arbitrum/IArbSys.sol) pragma solidity >=0.4.21 <0.9.0; diff --git a/contracts/vendor/arbitrum/IBridge.sol b/contracts/vendor/arbitrum/IBridge.sol index e71bedce0..398fb6130 100644 --- a/contracts/vendor/arbitrum/IBridge.sol +++ b/contracts/vendor/arbitrum/IBridge.sol @@ -1,7 +1,7 @@ // Copyright 2021-2022, Offchain Labs, Inc. // For license information, see https://github.com/nitro/blob/master/LICENSE // SPDX-License-Identifier: BUSL-1.1 -// OpenZeppelin Contracts (last updated v4.8.0) (vendor/arbitrum/IBridge.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (vendor/arbitrum/IBridge.sol) // solhint-disable-next-line compiler-version pragma solidity >=0.6.9 <0.9.0; diff --git a/contracts/vendor/arbitrum/IOutbox.sol b/contracts/vendor/arbitrum/IOutbox.sol index 22fa58f40..359ea6c1f 100644 --- a/contracts/vendor/arbitrum/IOutbox.sol +++ b/contracts/vendor/arbitrum/IOutbox.sol @@ -1,7 +1,7 @@ // Copyright 2021-2022, Offchain Labs, Inc. // For license information, see https://github.com/nitro/blob/master/LICENSE // SPDX-License-Identifier: BUSL-1.1 -// OpenZeppelin Contracts (last updated v4.8.0) (vendor/arbitrum/IOutbox.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (vendor/arbitrum/IOutbox.sol) // solhint-disable-next-line compiler-version pragma solidity >=0.6.9 <0.9.0; diff --git a/contracts/vendor/optimism/ICrossDomainMessenger.sol b/contracts/vendor/optimism/ICrossDomainMessenger.sol index cc01a48ab..461b88f6f 100644 --- a/contracts/vendor/optimism/ICrossDomainMessenger.sol +++ b/contracts/vendor/optimism/ICrossDomainMessenger.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.6.0) (vendor/optimism/ICrossDomainMessenger.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (vendor/optimism/ICrossDomainMessenger.sol) pragma solidity >0.5.0 <0.9.0; /** diff --git a/contracts/vendor/polygon/IFxMessageProcessor.sol b/contracts/vendor/polygon/IFxMessageProcessor.sol index be73e6f53..1c7b6f6f7 100644 --- a/contracts/vendor/polygon/IFxMessageProcessor.sol +++ b/contracts/vendor/polygon/IFxMessageProcessor.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.6.0) (vendor/polygon/IFxMessageProcessor.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (vendor/polygon/IFxMessageProcessor.sol) pragma solidity ^0.8.0; interface IFxMessageProcessor { diff --git a/package.json b/package.json index b198a0823..c96956a32 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "openzeppelin-solidity", "description": "Secure Smart Contract library for Solidity", - "version": "4.8.2", + "version": "4.9.0", "files": [ "/contracts/**/*.sol", "/build/contracts/*.json", From 25edd3cd628312baae4741544044ed4ed96767c6 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Thu, 25 May 2023 21:27:48 +0200 Subject: [PATCH 072/182] Use SafeERC20.forceApprove in safeIncreaseAllowance and safeDecreaseAllowance (#4260) Co-authored-by: Francisco --- .changeset/wild-windows-trade.md | 5 +++++ contracts/token/ERC20/utils/SafeERC20.sol | 4 ++-- test/token/ERC20/utils/SafeERC20.test.js | 11 +++++++---- 3 files changed, 14 insertions(+), 6 deletions(-) create mode 100644 .changeset/wild-windows-trade.md diff --git a/.changeset/wild-windows-trade.md b/.changeset/wild-windows-trade.md new file mode 100644 index 000000000..f599d0fcb --- /dev/null +++ b/.changeset/wild-windows-trade.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': major +--- + +`SafeERC20`: Refactor `safeDecreaseAllowance` and `safeIncreaseAllowance` to support USDT-like tokens. diff --git a/contracts/token/ERC20/utils/SafeERC20.sol b/contracts/token/ERC20/utils/SafeERC20.sol index f24447fb4..7248134f2 100644 --- a/contracts/token/ERC20/utils/SafeERC20.sol +++ b/contracts/token/ERC20/utils/SafeERC20.sol @@ -41,7 +41,7 @@ library SafeERC20 { */ function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal { uint256 oldAllowance = token.allowance(address(this), spender); - _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance + value)); + forceApprove(token, spender, oldAllowance + value); } /** @@ -52,7 +52,7 @@ library SafeERC20 { unchecked { uint256 oldAllowance = token.allowance(address(this), spender); require(oldAllowance >= value, "SafeERC20: decreased allowance below zero"); - _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance - value)); + forceApprove(token, spender, oldAllowance - value); } } diff --git a/test/token/ERC20/utils/SafeERC20.test.js b/test/token/ERC20/utils/SafeERC20.test.js index 04b3b5cb1..b0daf4384 100644 --- a/test/token/ERC20/utils/SafeERC20.test.js +++ b/test/token/ERC20/utils/SafeERC20.test.js @@ -182,16 +182,19 @@ contract('SafeERC20', function (accounts) { await this.token.$_approve(this.mock.address, spender, 100); }); - it('safeApprove can increase approval', async function () { - await expectRevert(this.mock.$safeIncreaseAllowance(this.token.address, spender, 10), 'USDT approval failure'); + it('safeIncreaseAllowance works', async function () { + await this.mock.$safeIncreaseAllowance(this.token.address, spender, 10); + expect(this.token.allowance(this.mock.address, spender, 90)); }); - it('safeApprove can decrease approval', async function () { - await expectRevert(this.mock.$safeDecreaseAllowance(this.token.address, spender, 10), 'USDT approval failure'); + it('safeDecreaseAllowance works', async function () { + await this.mock.$safeDecreaseAllowance(this.token.address, spender, 10); + expect(this.token.allowance(this.mock.address, spender, 110)); }); it('forceApprove works', async function () { await this.mock.$forceApprove(this.token.address, spender, 200); + expect(this.token.allowance(this.mock.address, spender, 200)); }); }); }); From 5420879d9b834a0579423d668fb60c5fc13b60cc Mon Sep 17 00:00:00 2001 From: kfishnchips <110192548+kfishnchips@users.noreply.github.com> Date: Fri, 26 May 2023 12:07:16 -0400 Subject: [PATCH 073/182] Ability to set starting token id for ERC721Consecutive (#4097) Co-authored-by: Hadrien Croubois Co-authored-by: ernestognw --- .changeset/spotty-hotels-type.md | 5 + .../mocks/token/ERC721ConsecutiveMock.sol | 9 + .../ERC721/extensions/ERC721Consecutive.sol | 43 ++-- .../ERC721/extensions/ERC721Consecutive.t.sol | 90 +++++-- .../extensions/ERC721Consecutive.test.js | 234 +++++++++--------- 5 files changed, 238 insertions(+), 143 deletions(-) create mode 100644 .changeset/spotty-hotels-type.md diff --git a/.changeset/spotty-hotels-type.md b/.changeset/spotty-hotels-type.md new file mode 100644 index 000000000..866d8fc02 --- /dev/null +++ b/.changeset/spotty-hotels-type.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': patch +--- + +`ERC721Consecutive`: Add a `_firstConsecutiveId` internal function that can be overridden to change the id of the first token minted through `_mintConsecutive`. diff --git a/contracts/mocks/token/ERC721ConsecutiveMock.sol b/contracts/mocks/token/ERC721ConsecutiveMock.sol index 427f44a19..65859f956 100644 --- a/contracts/mocks/token/ERC721ConsecutiveMock.sol +++ b/contracts/mocks/token/ERC721ConsecutiveMock.sol @@ -11,13 +11,18 @@ import "../../token/ERC721/extensions/draft-ERC721Votes.sol"; * @title ERC721ConsecutiveMock */ contract ERC721ConsecutiveMock is ERC721Consecutive, ERC721Pausable, ERC721Votes { + uint96 private immutable _offset; + constructor( string memory name, string memory symbol, + uint96 offset, address[] memory delegates, address[] memory receivers, uint96[] memory amounts ) ERC721(name, symbol) EIP712(name, "1") { + _offset = offset; + for (uint256 i = 0; i < delegates.length; ++i) { _delegate(delegates[i], delegates[i]); } @@ -27,6 +32,10 @@ contract ERC721ConsecutiveMock is ERC721Consecutive, ERC721Pausable, ERC721Votes } } + function _firstConsecutiveId() internal view virtual override returns (uint96) { + return _offset; + } + function _ownerOf(uint256 tokenId) internal view virtual override(ERC721, ERC721Consecutive) returns (address) { return super._ownerOf(tokenId); } diff --git a/contracts/token/ERC721/extensions/ERC721Consecutive.sol b/contracts/token/ERC721/extensions/ERC721Consecutive.sol index 4312d9849..abdba5b90 100644 --- a/contracts/token/ERC721/extensions/ERC721Consecutive.sol +++ b/contracts/token/ERC721/extensions/ERC721Consecutive.sol @@ -20,8 +20,8 @@ import "../../../utils/structs/BitMaps.sol"; * regained after construction. During construction, only batch minting is allowed. * * IMPORTANT: This extension bypasses the hooks {_beforeTokenTransfer} and {_afterTokenTransfer} for tokens minted in - * batch. When using this extension, you should consider the {_beforeConsecutiveTokenTransfer} and - * {_afterConsecutiveTokenTransfer} hooks in addition to {_beforeTokenTransfer} and {_afterTokenTransfer}. + * batch. The hooks will be only called once per batch, so you should take `batchSize` parameter into consideration + * when relying on hooks. * * IMPORTANT: When overriding {_afterTokenTransfer}, be careful about call ordering. {ownerOf} may return invalid * values during the {_afterTokenTransfer} execution if the super call is not called first. To be safe, execute the @@ -56,7 +56,7 @@ abstract contract ERC721Consecutive is IERC2309, ERC721 { address owner = super._ownerOf(tokenId); // If token is owned by the core, or beyond consecutive range, return base value - if (owner != address(0) || tokenId > type(uint96).max) { + if (owner != address(0) || tokenId > type(uint96).max || tokenId < _firstConsecutiveId()) { return owner; } @@ -82,7 +82,7 @@ abstract contract ERC721Consecutive is IERC2309, ERC721 { * Emits a {IERC2309-ConsecutiveTransfer} event. */ function _mintConsecutive(address to, uint96 batchSize) internal virtual returns (uint96) { - uint96 first = _totalConsecutiveSupply(); + uint96 next = _nextConsecutiveId(); // minting a batch of size 0 is a no-op if (batchSize > 0) { @@ -91,29 +91,29 @@ abstract contract ERC721Consecutive is IERC2309, ERC721 { require(batchSize <= _maxBatchSize(), "ERC721Consecutive: batch too large"); // hook before - _beforeTokenTransfer(address(0), to, first, batchSize); + _beforeTokenTransfer(address(0), to, next, batchSize); // push an ownership checkpoint & emit event - uint96 last = first + batchSize - 1; + uint96 last = next + batchSize - 1; _sequentialOwnership.push(last, uint160(to)); // The invariant required by this function is preserved because the new sequentialOwnership checkpoint // is attributing ownership of `batchSize` new tokens to account `to`. __unsafe_increaseBalance(to, batchSize); - emit ConsecutiveTransfer(first, last, address(0), to); + emit ConsecutiveTransfer(next, last, address(0), to); // hook after - _afterTokenTransfer(address(0), to, first, batchSize); + _afterTokenTransfer(address(0), to, next, batchSize); } - return first; + return next; } /** * @dev See {ERC721-_mint}. Override version that restricts normal minting to after construction. * - * Warning: Using {ERC721Consecutive} prevents using {_mint} during construction in favor of {_mintConsecutive}. + * WARNING: Using {ERC721Consecutive} prevents using {_mint} during construction in favor of {_mintConsecutive}. * After construction, {_mintConsecutive} is no longer available and {_mint} becomes available. */ function _mint(address to, uint256 tokenId) internal virtual override { @@ -132,17 +132,30 @@ abstract contract ERC721Consecutive is IERC2309, ERC721 { ) internal virtual override { if ( to == address(0) && // if we burn - firstTokenId < _totalConsecutiveSupply() && // and the tokenId was minted in a batch - !_sequentialBurn.get(firstTokenId) // and the token was never marked as burnt - ) { + firstTokenId >= _firstConsecutiveId() && + firstTokenId < _nextConsecutiveId() && + !_sequentialBurn.get(firstTokenId) + ) // and the token was never marked as burnt + { require(batchSize == 1, "ERC721Consecutive: batch burn not supported"); _sequentialBurn.set(firstTokenId); } super._afterTokenTransfer(from, to, firstTokenId, batchSize); } - function _totalConsecutiveSupply() private view returns (uint96) { + /** + * @dev Used to offset the first token id in {_nextConsecutiveId} + */ + function _firstConsecutiveId() internal view virtual returns (uint96) { + return 0; + } + + /** + * @dev Returns the next tokenId to mint using {_mintConsecutive}. It will return {_firstConsecutiveId} + * if no consecutive tokenId has been minted before. + */ + function _nextConsecutiveId() private view returns (uint96) { (bool exists, uint96 latestId, ) = _sequentialOwnership.latestCheckpoint(); - return exists ? latestId + 1 : 0; + return exists ? latestId + 1 : _firstConsecutiveId(); } } diff --git a/test/token/ERC721/extensions/ERC721Consecutive.t.sol b/test/token/ERC721/extensions/ERC721Consecutive.t.sol index 3cc868929..5bcbb024d 100644 --- a/test/token/ERC721/extensions/ERC721Consecutive.t.sol +++ b/test/token/ERC721/extensions/ERC721Consecutive.t.sol @@ -14,9 +14,11 @@ function toSingleton(address account) pure returns (address[] memory) { } contract ERC721ConsecutiveTarget is StdUtils, ERC721Consecutive { + uint96 private immutable _offset; uint256 public totalMinted = 0; - constructor(address[] memory receivers, uint256[] memory batches) ERC721("", "") { + constructor(address[] memory receivers, uint256[] memory batches, uint256 startingId) ERC721("", "") { + _offset = uint96(startingId); for (uint256 i = 0; i < batches.length; i++) { address receiver = receivers[i % receivers.length]; uint96 batchSize = uint96(bound(batches[i], 0, _maxBatchSize())); @@ -28,43 +30,71 @@ contract ERC721ConsecutiveTarget is StdUtils, ERC721Consecutive { function burn(uint256 tokenId) public { _burn(tokenId); } + + function _firstConsecutiveId() internal view virtual override returns (uint96) { + return _offset; + } } contract ERC721ConsecutiveTest is Test { - function test_balance(address receiver, uint256[] calldata batches) public { + function test_balance(address receiver, uint256[] calldata batches, uint96 startingId) public { vm.assume(receiver != address(0)); - ERC721ConsecutiveTarget token = new ERC721ConsecutiveTarget(toSingleton(receiver), batches); + uint256 startingTokenId = bound(startingId, 0, 5000); + + ERC721ConsecutiveTarget token = new ERC721ConsecutiveTarget(toSingleton(receiver), batches, startingTokenId); assertEq(token.balanceOf(receiver), token.totalMinted()); } - function test_ownership(address receiver, uint256[] calldata batches, uint256[2] calldata unboundedTokenId) public { + function test_ownership( + address receiver, + uint256[] calldata batches, + uint256[2] calldata unboundedTokenId, + uint96 startingId + ) public { vm.assume(receiver != address(0)); - ERC721ConsecutiveTarget token = new ERC721ConsecutiveTarget(toSingleton(receiver), batches); + uint256 startingTokenId = bound(startingId, 0, 5000); + + ERC721ConsecutiveTarget token = new ERC721ConsecutiveTarget(toSingleton(receiver), batches, startingTokenId); if (token.totalMinted() > 0) { - uint256 validTokenId = bound(unboundedTokenId[0], 0, token.totalMinted() - 1); + uint256 validTokenId = bound( + unboundedTokenId[0], + startingTokenId, + startingTokenId + token.totalMinted() - 1 + ); assertEq(token.ownerOf(validTokenId), receiver); } - uint256 invalidTokenId = bound(unboundedTokenId[1], token.totalMinted(), type(uint256).max); + uint256 invalidTokenId = bound( + unboundedTokenId[1], + startingTokenId + token.totalMinted(), + startingTokenId + token.totalMinted() + 1 + ); vm.expectRevert(); token.ownerOf(invalidTokenId); } - function test_burn(address receiver, uint256[] calldata batches, uint256 unboundedTokenId) public { + function test_burn( + address receiver, + uint256[] calldata batches, + uint256 unboundedTokenId, + uint96 startingId + ) public { vm.assume(receiver != address(0)); - ERC721ConsecutiveTarget token = new ERC721ConsecutiveTarget(toSingleton(receiver), batches); + uint256 startingTokenId = bound(startingId, 0, 5000); + + ERC721ConsecutiveTarget token = new ERC721ConsecutiveTarget(toSingleton(receiver), batches, startingTokenId); // only test if we minted at least one token uint256 supply = token.totalMinted(); vm.assume(supply > 0); // burn a token in [0; supply[ - uint256 tokenId = bound(unboundedTokenId, 0, supply - 1); + uint256 tokenId = bound(unboundedTokenId, startingTokenId, startingTokenId + supply - 1); token.burn(tokenId); // balance should have decreased @@ -78,25 +108,28 @@ contract ERC721ConsecutiveTest is Test { function test_transfer( address[2] calldata accounts, uint256[2] calldata unboundedBatches, - uint256[2] calldata unboundedTokenId + uint256[2] calldata unboundedTokenId, + uint96 startingId ) public { vm.assume(accounts[0] != address(0)); vm.assume(accounts[1] != address(0)); vm.assume(accounts[0] != accounts[1]); + uint256 startingTokenId = bound(startingId, 1, 5000); + address[] memory receivers = new address[](2); receivers[0] = accounts[0]; receivers[1] = accounts[1]; // We assume _maxBatchSize is 5000 (the default). This test will break otherwise. uint256[] memory batches = new uint256[](2); - batches[0] = bound(unboundedBatches[0], 1, 5000); - batches[1] = bound(unboundedBatches[1], 1, 5000); + batches[0] = bound(unboundedBatches[0], startingTokenId, 5000); + batches[1] = bound(unboundedBatches[1], startingTokenId, 5000); - ERC721ConsecutiveTarget token = new ERC721ConsecutiveTarget(receivers, batches); + ERC721ConsecutiveTarget token = new ERC721ConsecutiveTarget(receivers, batches, startingTokenId); - uint256 tokenId0 = bound(unboundedTokenId[0], 0, batches[0] - 1); - uint256 tokenId1 = bound(unboundedTokenId[1], 0, batches[1] - 1) + batches[0]; + uint256 tokenId0 = bound(unboundedTokenId[0], startingTokenId, batches[0]); + uint256 tokenId1 = bound(unboundedTokenId[1], startingTokenId, batches[1]) + batches[0]; assertEq(token.ownerOf(tokenId0), accounts[0]); assertEq(token.ownerOf(tokenId1), accounts[1]); @@ -119,4 +152,29 @@ contract ERC721ConsecutiveTest is Test { assertEq(token.balanceOf(accounts[0]), batches[0]); assertEq(token.balanceOf(accounts[1]), batches[1]); } + + function test_start_consecutive_id( + address receiver, + uint256[2] calldata unboundedBatches, + uint256[2] calldata unboundedTokenId, + uint96 startingId + ) public { + vm.assume(receiver != address(0)); + + uint256 startingTokenId = bound(startingId, 1, 5000); + + // We assume _maxBatchSize is 5000 (the default). This test will break otherwise. + uint256[] memory batches = new uint256[](2); + batches[0] = bound(unboundedBatches[0], startingTokenId, 5000); + batches[1] = bound(unboundedBatches[1], startingTokenId, 5000); + + ERC721ConsecutiveTarget token = new ERC721ConsecutiveTarget(toSingleton(receiver), batches, startingTokenId); + + uint256 tokenId0 = bound(unboundedTokenId[0], startingTokenId, batches[0]); + uint256 tokenId1 = bound(unboundedTokenId[1], startingTokenId, batches[1]); + + assertEq(token.ownerOf(tokenId0), receiver); + assertEq(token.ownerOf(tokenId1), receiver); + assertEq(token.balanceOf(receiver), batches[0] + batches[1]); + } } diff --git a/test/token/ERC721/extensions/ERC721Consecutive.test.js b/test/token/ERC721/extensions/ERC721Consecutive.test.js index a92aef480..4e2df4690 100644 --- a/test/token/ERC721/extensions/ERC721Consecutive.test.js +++ b/test/token/ERC721/extensions/ERC721Consecutive.test.js @@ -20,159 +20,169 @@ contract('ERC721Consecutive', function (accounts) { ]; const delegates = [user1, user3]; - describe('with valid batches', function () { - beforeEach(async function () { - this.token = await ERC721ConsecutiveMock.new( - name, - symbol, - delegates, - batches.map(({ receiver }) => receiver), - batches.map(({ amount }) => amount), - ); - }); + for (const offset of [0, 1, 42]) { + describe(`with offset ${offset}`, function () { + beforeEach(async function () { + this.token = await ERC721ConsecutiveMock.new( + name, + symbol, + offset, + delegates, + batches.map(({ receiver }) => receiver), + batches.map(({ amount }) => amount), + ); + }); - describe('minting during construction', function () { - it('events are emitted at construction', async function () { - let first = 0; - - for (const batch of batches) { - if (batch.amount > 0) { - await expectEvent.inConstruction(this.token, 'ConsecutiveTransfer', { - fromTokenId: web3.utils.toBN(first), - toTokenId: web3.utils.toBN(first + batch.amount - 1), - fromAddress: constants.ZERO_ADDRESS, - toAddress: batch.receiver, - }); - } else { - // expectEvent.notEmitted.inConstruction only looks at event name, and doesn't check the parameters + describe('minting during construction', function () { + it('events are emitted at construction', async function () { + let first = offset; + + for (const batch of batches) { + if (batch.amount > 0) { + await expectEvent.inConstruction(this.token, 'ConsecutiveTransfer', { + fromTokenId: web3.utils.toBN(first), + toTokenId: web3.utils.toBN(first + batch.amount - 1), + fromAddress: constants.ZERO_ADDRESS, + toAddress: batch.receiver, + }); + } else { + // expectEvent.notEmitted.inConstruction only looks at event name, and doesn't check the parameters + } + first += batch.amount; } - first += batch.amount; - } - }); + }); - it('ownership is set', async function () { - const owners = batches.flatMap(({ receiver, amount }) => Array(amount).fill(receiver)); + it('ownership is set', async function () { + const owners = [ + ...Array(offset).fill(constants.ZERO_ADDRESS), + ...batches.flatMap(({ receiver, amount }) => Array(amount).fill(receiver)), + ]; - for (const tokenId in owners) { - expect(await this.token.ownerOf(tokenId)).to.be.equal(owners[tokenId]); - } - }); + for (const tokenId in owners) { + if (owners[tokenId] != constants.ZERO_ADDRESS) { + expect(await this.token.ownerOf(tokenId)).to.be.equal(owners[tokenId]); + } + } + }); - it('balance & voting power are set', async function () { - for (const account of accounts) { - const balance = batches - .filter(({ receiver }) => receiver === account) - .map(({ amount }) => amount) - .reduce((a, b) => a + b, 0); + it('balance & voting power are set', async function () { + for (const account of accounts) { + const balance = batches + .filter(({ receiver }) => receiver === account) + .map(({ amount }) => amount) + .reduce((a, b) => a + b, 0); - expect(await this.token.balanceOf(account)).to.be.bignumber.equal(web3.utils.toBN(balance)); + expect(await this.token.balanceOf(account)).to.be.bignumber.equal(web3.utils.toBN(balance)); - // If not delegated at construction, check before + do delegation - if (!delegates.includes(account)) { - expect(await this.token.getVotes(account)).to.be.bignumber.equal(web3.utils.toBN(0)); + // If not delegated at construction, check before + do delegation + if (!delegates.includes(account)) { + expect(await this.token.getVotes(account)).to.be.bignumber.equal(web3.utils.toBN(0)); - await this.token.delegate(account, { from: account }); - } + await this.token.delegate(account, { from: account }); + } - // At this point all accounts should have delegated - expect(await this.token.getVotes(account)).to.be.bignumber.equal(web3.utils.toBN(balance)); - } + // At this point all accounts should have delegated + expect(await this.token.getVotes(account)).to.be.bignumber.equal(web3.utils.toBN(balance)); + } + }); }); - }); - describe('minting after construction', function () { - it('consecutive minting is not possible after construction', async function () { - await expectRevert( - this.token.$_mintConsecutive(user1, 10), - 'ERC721Consecutive: batch minting restricted to constructor', - ); - }); + describe('minting after construction', function () { + it('consecutive minting is not possible after construction', async function () { + await expectRevert( + this.token.$_mintConsecutive(user1, 10), + 'ERC721Consecutive: batch minting restricted to constructor', + ); + }); - it('simple minting is possible after construction', async function () { - const tokenId = batches.reduce((acc, { amount }) => acc + amount, 0); + it('simple minting is possible after construction', async function () { + const tokenId = batches.reduce((acc, { amount }) => acc + amount, offset); - expect(await this.token.$_exists(tokenId)).to.be.equal(false); + expect(await this.token.$_exists(tokenId)).to.be.equal(false); - expectEvent(await this.token.$_mint(user1, tokenId), 'Transfer', { - from: constants.ZERO_ADDRESS, - to: user1, - tokenId: tokenId.toString(), + expectEvent(await this.token.$_mint(user1, tokenId), 'Transfer', { + from: constants.ZERO_ADDRESS, + to: user1, + tokenId: tokenId.toString(), + }); }); - }); - it('cannot mint a token that has been batched minted', async function () { - const tokenId = batches.reduce((acc, { amount }) => acc + amount, 0) - 1; + it('cannot mint a token that has been batched minted', async function () { + const tokenId = batches.reduce((acc, { amount }) => acc + amount, offset) - 1; - expect(await this.token.$_exists(tokenId)).to.be.equal(true); + expect(await this.token.$_exists(tokenId)).to.be.equal(true); - await expectRevert(this.token.$_mint(user1, tokenId), 'ERC721: token already minted'); + await expectRevert(this.token.$_mint(user1, tokenId), 'ERC721: token already minted'); + }); }); - }); - describe('ERC721 behavior', function () { - it('core takes over ownership on transfer', async function () { - await this.token.transferFrom(user1, receiver, 1, { from: user1 }); + describe('ERC721 behavior', function () { + const tokenId = web3.utils.toBN(offset + 1); - expect(await this.token.ownerOf(1)).to.be.equal(receiver); - }); + it('core takes over ownership on transfer', async function () { + await this.token.transferFrom(user1, receiver, tokenId, { from: user1 }); - it('tokens can be burned and re-minted #1', async function () { - expectEvent(await this.token.$_burn(1, { from: user1 }), 'Transfer', { - from: user1, - to: constants.ZERO_ADDRESS, - tokenId: '1', + expect(await this.token.ownerOf(tokenId)).to.be.equal(receiver); }); - await expectRevert(this.token.ownerOf(1), 'ERC721: invalid token ID'); + it('tokens can be burned and re-minted #1', async function () { + expectEvent(await this.token.$_burn(tokenId, { from: user1 }), 'Transfer', { + from: user1, + to: constants.ZERO_ADDRESS, + tokenId, + }); + + await expectRevert(this.token.ownerOf(tokenId), 'ERC721: invalid token ID'); - expectEvent(await this.token.$_mint(user2, 1), 'Transfer', { - from: constants.ZERO_ADDRESS, - to: user2, - tokenId: '1', + expectEvent(await this.token.$_mint(user2, tokenId), 'Transfer', { + from: constants.ZERO_ADDRESS, + to: user2, + tokenId, + }); + + expect(await this.token.ownerOf(tokenId)).to.be.equal(user2); }); - expect(await this.token.ownerOf(1)).to.be.equal(user2); - }); + it('tokens can be burned and re-minted #2', async function () { + const tokenId = web3.utils.toBN(batches.reduce((acc, { amount }) => acc + amount, offset)); - it('tokens can be burned and re-minted #2', async function () { - const tokenId = batches.reduce((acc, { amount }) => acc.addn(amount), web3.utils.toBN(0)); + expect(await this.token.$_exists(tokenId)).to.be.equal(false); + await expectRevert(this.token.ownerOf(tokenId), 'ERC721: invalid token ID'); - expect(await this.token.$_exists(tokenId)).to.be.equal(false); - await expectRevert(this.token.ownerOf(tokenId), 'ERC721: invalid token ID'); + // mint + await this.token.$_mint(user1, tokenId); - // mint - await this.token.$_mint(user1, tokenId); + expect(await this.token.$_exists(tokenId)).to.be.equal(true); + expect(await this.token.ownerOf(tokenId), user1); - expect(await this.token.$_exists(tokenId)).to.be.equal(true); - expect(await this.token.ownerOf(tokenId), user1); + // burn + expectEvent(await this.token.$_burn(tokenId, { from: user1 }), 'Transfer', { + from: user1, + to: constants.ZERO_ADDRESS, + tokenId, + }); - // burn - expectEvent(await this.token.$_burn(tokenId, { from: user1 }), 'Transfer', { - from: user1, - to: constants.ZERO_ADDRESS, - tokenId, - }); + expect(await this.token.$_exists(tokenId)).to.be.equal(false); + await expectRevert(this.token.ownerOf(tokenId), 'ERC721: invalid token ID'); - expect(await this.token.$_exists(tokenId)).to.be.equal(false); - await expectRevert(this.token.ownerOf(tokenId), 'ERC721: invalid token ID'); + // re-mint + expectEvent(await this.token.$_mint(user2, tokenId), 'Transfer', { + from: constants.ZERO_ADDRESS, + to: user2, + tokenId, + }); - // re-mint - expectEvent(await this.token.$_mint(user2, tokenId), 'Transfer', { - from: constants.ZERO_ADDRESS, - to: user2, - tokenId, + expect(await this.token.$_exists(tokenId)).to.be.equal(true); + expect(await this.token.ownerOf(tokenId), user2); }); - - expect(await this.token.$_exists(tokenId)).to.be.equal(true); - expect(await this.token.ownerOf(tokenId), user2); }); }); - }); + } describe('invalid use', function () { it('cannot mint a batch larger than 5000', async function () { await expectRevert( - ERC721ConsecutiveMock.new(name, symbol, [], [user1], ['5001']), + ERC721ConsecutiveMock.new(name, symbol, 0, [], [user1], ['5001']), 'ERC721Consecutive: batch too large', ); }); From 4448c13c3cf5881c278179ad1dc8ddfdd74f47e6 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Fri, 26 May 2023 21:19:27 +0200 Subject: [PATCH 074/182] Move the Checkpoints library to utils/structs (#4275) --- .changeset/fresh-birds-kiss.md | 5 +++++ .../governance/extensions/GovernorVotesQuorumFraction.sol | 2 +- contracts/governance/utils/Votes.sol | 2 +- contracts/token/ERC721/extensions/ERC721Consecutive.sol | 2 +- contracts/utils/{ => structs}/Checkpoints.sol | 6 +++--- scripts/generate/run.js | 4 ++-- scripts/generate/templates/Checkpoints.js | 4 ++-- scripts/generate/templates/Checkpoints.t.js | 4 ++-- test/utils/{ => structs}/Checkpoints.t.sol | 4 ++-- test/utils/{ => structs}/Checkpoints.test.js | 2 +- 10 files changed, 20 insertions(+), 15 deletions(-) create mode 100644 .changeset/fresh-birds-kiss.md rename contracts/utils/{ => structs}/Checkpoints.sol (99%) rename test/utils/{ => structs}/Checkpoints.t.sol (98%) rename test/utils/{ => structs}/Checkpoints.test.js (98%) diff --git a/.changeset/fresh-birds-kiss.md b/.changeset/fresh-birds-kiss.md new file mode 100644 index 000000000..221f54cdf --- /dev/null +++ b/.changeset/fresh-birds-kiss.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': major +--- + +`Checkpoints`: library moved from `utils` to `utils/structs` diff --git a/contracts/governance/extensions/GovernorVotesQuorumFraction.sol b/contracts/governance/extensions/GovernorVotesQuorumFraction.sol index e26ffb260..d42bb2f47 100644 --- a/contracts/governance/extensions/GovernorVotesQuorumFraction.sol +++ b/contracts/governance/extensions/GovernorVotesQuorumFraction.sol @@ -4,8 +4,8 @@ pragma solidity ^0.8.0; import "./GovernorVotes.sol"; -import "../../utils/Checkpoints.sol"; import "../../utils/math/SafeCast.sol"; +import "../../utils/structs/Checkpoints.sol"; /** * @dev Extension of {Governor} for voting weight extraction from an {ERC20Votes} token and a quorum expressed as a diff --git a/contracts/governance/utils/Votes.sol b/contracts/governance/utils/Votes.sol index a579811e5..cc1143fe2 100644 --- a/contracts/governance/utils/Votes.sol +++ b/contracts/governance/utils/Votes.sol @@ -5,8 +5,8 @@ pragma solidity ^0.8.0; import "../../interfaces/IERC5805.sol"; import "../../utils/Context.sol"; import "../../utils/Nonces.sol"; -import "../../utils/Checkpoints.sol"; import "../../utils/cryptography/EIP712.sol"; +import "../../utils/structs/Checkpoints.sol"; /** * @dev This is a base abstract contract that tracks voting units, which are a measure of voting power that can be diff --git a/contracts/token/ERC721/extensions/ERC721Consecutive.sol b/contracts/token/ERC721/extensions/ERC721Consecutive.sol index a6ab6c76b..4191ad684 100644 --- a/contracts/token/ERC721/extensions/ERC721Consecutive.sol +++ b/contracts/token/ERC721/extensions/ERC721Consecutive.sol @@ -5,8 +5,8 @@ pragma solidity ^0.8.1; import "../ERC721.sol"; import "../../../interfaces/IERC2309.sol"; -import "../../../utils/Checkpoints.sol"; import "../../../utils/structs/BitMaps.sol"; +import "../../../utils/structs/Checkpoints.sol"; /** * @dev Implementation of the ERC2309 "Consecutive Transfer Extension" as defined in diff --git a/contracts/utils/Checkpoints.sol b/contracts/utils/structs/Checkpoints.sol similarity index 99% rename from contracts/utils/Checkpoints.sol rename to contracts/utils/structs/Checkpoints.sol index 90f03aef7..f2ee45811 100644 --- a/contracts/utils/Checkpoints.sol +++ b/contracts/utils/structs/Checkpoints.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.9.0) (utils/Checkpoints.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (utils/structs/Checkpoints.sol) // This file was procedurally generated from scripts/generate/templates/Checkpoints.js. pragma solidity ^0.8.0; -import "./math/Math.sol"; -import "./math/SafeCast.sol"; +import "../math/Math.sol"; +import "../math/SafeCast.sol"; /** * @dev This library defines the `History` struct, for checkpointing values as they change at different points in diff --git a/scripts/generate/run.js b/scripts/generate/run.js index 368b53ad1..53589455a 100755 --- a/scripts/generate/run.js +++ b/scripts/generate/run.js @@ -35,7 +35,7 @@ for (const [file, template] of Object.entries({ 'utils/math/SafeCast.sol': './templates/SafeCast.js', 'utils/structs/EnumerableSet.sol': './templates/EnumerableSet.js', 'utils/structs/EnumerableMap.sol': './templates/EnumerableMap.js', - 'utils/Checkpoints.sol': './templates/Checkpoints.js', + 'utils/structs/Checkpoints.sol': './templates/Checkpoints.js', 'utils/StorageSlot.sol': './templates/StorageSlot.js', })) { generateFromTemplate(file, template, './contracts/'); @@ -43,7 +43,7 @@ for (const [file, template] of Object.entries({ // Tests for (const [file, template] of Object.entries({ - 'utils/Checkpoints.t.sol': './templates/Checkpoints.t.js', + 'utils/structs/Checkpoints.t.sol': './templates/Checkpoints.t.js', })) { generateFromTemplate(file, template, './test/'); } diff --git a/scripts/generate/templates/Checkpoints.js b/scripts/generate/templates/Checkpoints.js index de58ad19e..f339fde22 100644 --- a/scripts/generate/templates/Checkpoints.js +++ b/scripts/generate/templates/Checkpoints.js @@ -5,8 +5,8 @@ const { OPTS } = require('./Checkpoints.opts.js'); const header = `\ pragma solidity ^0.8.0; -import "./math/Math.sol"; -import "./math/SafeCast.sol"; +import "../math/Math.sol"; +import "../math/SafeCast.sol"; /** * @dev This library defines the \`History\` struct, for checkpointing values as they change at different points in diff --git a/scripts/generate/templates/Checkpoints.t.js b/scripts/generate/templates/Checkpoints.t.js index 0451ecea4..2b8c7b67c 100644 --- a/scripts/generate/templates/Checkpoints.t.js +++ b/scripts/generate/templates/Checkpoints.t.js @@ -7,8 +7,8 @@ const header = `\ pragma solidity ^0.8.0; import "forge-std/Test.sol"; -import "../../contracts/utils/Checkpoints.sol"; -import "../../contracts/utils/math/SafeCast.sol"; +import "../../../contracts/utils/math/SafeCast.sol"; +import "../../../contracts/utils/structs/Checkpoints.sol"; `; /* eslint-disable max-len */ diff --git a/test/utils/Checkpoints.t.sol b/test/utils/structs/Checkpoints.t.sol similarity index 98% rename from test/utils/Checkpoints.t.sol rename to test/utils/structs/Checkpoints.t.sol index 12598da46..e3f4bd28b 100644 --- a/test/utils/Checkpoints.t.sol +++ b/test/utils/structs/Checkpoints.t.sol @@ -4,8 +4,8 @@ pragma solidity ^0.8.0; import "forge-std/Test.sol"; -import "../../contracts/utils/Checkpoints.sol"; -import "../../contracts/utils/math/SafeCast.sol"; +import "../../../contracts/utils/math/SafeCast.sol"; +import "../../../contracts/utils/structs/Checkpoints.sol"; contract CheckpointsTrace224Test is Test { using Checkpoints for Checkpoints.Trace224; diff --git a/test/utils/Checkpoints.test.js b/test/utils/structs/Checkpoints.test.js similarity index 98% rename from test/utils/Checkpoints.test.js rename to test/utils/structs/Checkpoints.test.js index d9fd2c8ea..ad95373a4 100644 --- a/test/utils/Checkpoints.test.js +++ b/test/utils/structs/Checkpoints.test.js @@ -1,7 +1,7 @@ const { expectRevert } = require('@openzeppelin/test-helpers'); const { expect } = require('chai'); -const { VALUE_SIZES } = require('../../scripts/generate/templates/Checkpoints.opts.js'); +const { VALUE_SIZES } = require('../../../scripts/generate/templates/Checkpoints.opts.js'); const $Checkpoints = artifacts.require('$Checkpoints'); From 15c5c7179555ee46baf839db003029f4b2bcb470 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Sat, 27 May 2023 00:30:00 +0200 Subject: [PATCH 075/182] Remove TokenTimelock, PaymentSplitter, ERC20Snapshot, ERC20VotesComp, GovernorVotesComp (#4276) --- CHANGELOG.md | 10 + contracts/finance/PaymentSplitter.sol | 214 ------- contracts/finance/README.adoc | 6 - contracts/finance/VestingWallet.sol | 12 +- contracts/governance/README.adoc | 4 - .../extensions/GovernorVotesComp.sol | 55 -- .../mocks/governance/GovernorCompMock.sol | 20 - .../GovernorCompatibilityBravoMock.sol | 4 +- contracts/mocks/token/VotesTimestamp.sol | 12 - contracts/token/ERC20/README.adoc | 15 +- .../token/ERC20/extensions/ERC20Snapshot.sol | 189 ------ .../token/ERC20/extensions/ERC20Votes.sol | 2 +- .../token/ERC20/extensions/ERC20VotesComp.sol | 46 -- contracts/token/ERC20/utils/TokenTimelock.sol | 72 --- scripts/upgradeable/upgradeable.patch | 36 +- test/finance/PaymentSplitter.test.js | 217 ------- test/finance/VestingWallet.test.js | 1 + .../GovernorCompatibilityBravo.test.js | 4 +- .../extensions/GovernorComp.test.js | 89 --- .../ERC20/extensions/ERC20Snapshot.test.js | 207 ------- .../ERC20/extensions/ERC20VotesComp.test.js | 579 ------------------ test/token/ERC20/utils/TokenTimelock.test.js | 71 --- 22 files changed, 37 insertions(+), 1828 deletions(-) delete mode 100644 contracts/finance/PaymentSplitter.sol delete mode 100644 contracts/governance/extensions/GovernorVotesComp.sol delete mode 100644 contracts/mocks/governance/GovernorCompMock.sol delete mode 100644 contracts/token/ERC20/extensions/ERC20Snapshot.sol delete mode 100644 contracts/token/ERC20/extensions/ERC20VotesComp.sol delete mode 100644 contracts/token/ERC20/utils/TokenTimelock.sol delete mode 100644 test/finance/PaymentSplitter.test.js delete mode 100644 test/governance/extensions/GovernorComp.test.js delete mode 100644 test/token/ERC20/extensions/ERC20Snapshot.test.js delete mode 100644 test/token/ERC20/extensions/ERC20VotesComp.test.js delete mode 100644 test/token/ERC20/utils/TokenTimelock.test.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 2298e2e51..8d2853cf8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +### Removals + +The following contracts were removed: + +- `ERC20Snapshot` +- `ERC20VotesComp` +- `GovernorVotesComp` +- `PaymentSplitter` +- `TokenTimelock` (removed in favor of `VestingWallet`) + ### How to upgrade from 4.x #### ERC20, ERC721, and ERC1155 diff --git a/contracts/finance/PaymentSplitter.sol b/contracts/finance/PaymentSplitter.sol deleted file mode 100644 index daa9090eb..000000000 --- a/contracts/finance/PaymentSplitter.sol +++ /dev/null @@ -1,214 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.8.0) (finance/PaymentSplitter.sol) - -pragma solidity ^0.8.0; - -import "../token/ERC20/utils/SafeERC20.sol"; -import "../utils/Address.sol"; -import "../utils/Context.sol"; - -/** - * @title PaymentSplitter - * @dev This contract allows to split Ether payments among a group of accounts. The sender does not need to be aware - * that the Ether will be split in this way, since it is handled transparently by the contract. - * - * The split can be in equal parts or in any other arbitrary proportion. The way this is specified is by assigning each - * account to a number of shares. Of all the Ether that this contract receives, each account will then be able to claim - * an amount proportional to the percentage of total shares they were assigned. The distribution of shares is set at the - * time of contract deployment and can't be updated thereafter. - * - * `PaymentSplitter` follows a _pull payment_ model. This means that payments are not automatically forwarded to the - * accounts but kept in this contract, and the actual transfer is triggered as a separate step by calling the {release} - * function. - * - * NOTE: This contract assumes that ERC20 tokens will behave similarly to native tokens (Ether). Rebasing tokens, and - * tokens that apply fees during transfers, are likely to not be supported as expected. If in doubt, we encourage you - * to run tests before sending real value to this contract. - */ -contract PaymentSplitter is Context { - event PayeeAdded(address account, uint256 shares); - event PaymentReleased(address to, uint256 amount); - event ERC20PaymentReleased(IERC20 indexed token, address to, uint256 amount); - event PaymentReceived(address from, uint256 amount); - - uint256 private _totalShares; - uint256 private _totalReleased; - - mapping(address => uint256) private _shares; - mapping(address => uint256) private _released; - address[] private _payees; - - mapping(IERC20 => uint256) private _erc20TotalReleased; - mapping(IERC20 => mapping(address => uint256)) private _erc20Released; - - /** - * @dev Creates an instance of `PaymentSplitter` where each account in `payees` is assigned the number of shares at - * the matching position in the `shares` array. - * - * All addresses in `payees` must be non-zero. Both arrays must have the same non-zero length, and there must be no - * duplicates in `payees`. - */ - constructor(address[] memory payees, uint256[] memory shares_) payable { - require(payees.length == shares_.length, "PaymentSplitter: payees and shares length mismatch"); - require(payees.length > 0, "PaymentSplitter: no payees"); - - for (uint256 i = 0; i < payees.length; i++) { - _addPayee(payees[i], shares_[i]); - } - } - - /** - * @dev The Ether received will be logged with {PaymentReceived} events. Note that these events are not fully - * reliable: it's possible for a contract to receive Ether without triggering this function. This only affects the - * reliability of the events, and not the actual splitting of Ether. - * - * To learn more about this see the Solidity documentation for - * https://solidity.readthedocs.io/en/latest/contracts.html#fallback-function[fallback - * functions]. - */ - receive() external payable virtual { - emit PaymentReceived(_msgSender(), msg.value); - } - - /** - * @dev Getter for the total shares held by payees. - */ - function totalShares() public view returns (uint256) { - return _totalShares; - } - - /** - * @dev Getter for the total amount of Ether already released. - */ - function totalReleased() public view returns (uint256) { - return _totalReleased; - } - - /** - * @dev Getter for the total amount of `token` already released. `token` should be the address of an IERC20 - * contract. - */ - function totalReleased(IERC20 token) public view returns (uint256) { - return _erc20TotalReleased[token]; - } - - /** - * @dev Getter for the amount of shares held by an account. - */ - function shares(address account) public view returns (uint256) { - return _shares[account]; - } - - /** - * @dev Getter for the amount of Ether already released to a payee. - */ - function released(address account) public view returns (uint256) { - return _released[account]; - } - - /** - * @dev Getter for the amount of `token` tokens already released to a payee. `token` should be the address of an - * IERC20 contract. - */ - function released(IERC20 token, address account) public view returns (uint256) { - return _erc20Released[token][account]; - } - - /** - * @dev Getter for the address of the payee number `index`. - */ - function payee(uint256 index) public view returns (address) { - return _payees[index]; - } - - /** - * @dev Getter for the amount of payee's releasable Ether. - */ - function releasable(address account) public view returns (uint256) { - uint256 totalReceived = address(this).balance + totalReleased(); - return _pendingPayment(account, totalReceived, released(account)); - } - - /** - * @dev Getter for the amount of payee's releasable `token` tokens. `token` should be the address of an - * IERC20 contract. - */ - function releasable(IERC20 token, address account) public view returns (uint256) { - uint256 totalReceived = token.balanceOf(address(this)) + totalReleased(token); - return _pendingPayment(account, totalReceived, released(token, account)); - } - - /** - * @dev Triggers a transfer to `account` of the amount of Ether they are owed, according to their percentage of the - * total shares and their previous withdrawals. - */ - function release(address payable account) public virtual { - require(_shares[account] > 0, "PaymentSplitter: account has no shares"); - - uint256 payment = releasable(account); - - require(payment != 0, "PaymentSplitter: account is not due payment"); - - // _totalReleased is the sum of all values in _released. - // If "_totalReleased += payment" does not overflow, then "_released[account] += payment" cannot overflow. - _totalReleased += payment; - unchecked { - _released[account] += payment; - } - - Address.sendValue(account, payment); - emit PaymentReleased(account, payment); - } - - /** - * @dev Triggers a transfer to `account` of the amount of `token` tokens they are owed, according to their - * percentage of the total shares and their previous withdrawals. `token` must be the address of an IERC20 - * contract. - */ - function release(IERC20 token, address account) public virtual { - require(_shares[account] > 0, "PaymentSplitter: account has no shares"); - - uint256 payment = releasable(token, account); - - require(payment != 0, "PaymentSplitter: account is not due payment"); - - // _erc20TotalReleased[token] is the sum of all values in _erc20Released[token]. - // If "_erc20TotalReleased[token] += payment" does not overflow, then "_erc20Released[token][account] += payment" - // cannot overflow. - _erc20TotalReleased[token] += payment; - unchecked { - _erc20Released[token][account] += payment; - } - - SafeERC20.safeTransfer(token, account, payment); - emit ERC20PaymentReleased(token, account, payment); - } - - /** - * @dev internal logic for computing the pending payment of an `account` given the token historical balances and - * already released amounts. - */ - function _pendingPayment( - address account, - uint256 totalReceived, - uint256 alreadyReleased - ) private view returns (uint256) { - return (totalReceived * _shares[account]) / _totalShares - alreadyReleased; - } - - /** - * @dev Add a new payee to the contract. - * @param account The address of the payee to add. - * @param shares_ The number of shares owned by the payee. - */ - function _addPayee(address account, uint256 shares_) private { - require(account != address(0), "PaymentSplitter: account is the zero address"); - require(shares_ > 0, "PaymentSplitter: shares are 0"); - require(_shares[account] == 0, "PaymentSplitter: account already has shares"); - - _payees.push(account); - _shares[account] = shares_; - _totalShares = _totalShares + shares_; - emit PayeeAdded(account, shares_); - } -} diff --git a/contracts/finance/README.adoc b/contracts/finance/README.adoc index b64af3125..ac7e4f015 100644 --- a/contracts/finance/README.adoc +++ b/contracts/finance/README.adoc @@ -5,16 +5,10 @@ NOTE: This document is better viewed at https://docs.openzeppelin.com/contracts/ This directory includes primitives for financial systems: -- {PaymentSplitter} allows to split Ether and ERC20 payments among a group of accounts. The sender does not need to be - aware that the assets will be split in this way, since it is handled transparently by the contract. The split can be - in equal parts or in any other arbitrary proportion. - - {VestingWallet} handles the vesting of Ether and ERC20 tokens for a given beneficiary. Custody of multiple tokens can be given to this contract, which will release the token to the beneficiary following a given, customizable, vesting schedule. == Contracts -{{PaymentSplitter}} - {{VestingWallet}} diff --git a/contracts/finance/VestingWallet.sol b/contracts/finance/VestingWallet.sol index 45cf9ce52..bb70d19fb 100644 --- a/contracts/finance/VestingWallet.sol +++ b/contracts/finance/VestingWallet.sol @@ -15,6 +15,9 @@ import "../utils/Context.sol"; * Any token transferred to this contract will follow the vesting schedule as if they were locked from the beginning. * Consequently, if the vesting has already started, any amount of tokens sent to this contract will (at least partly) * be immediately releasable. + * + * By setting the duration to 0, one can configure this contract to behave like an asset timelock that hold tokens for + * a beneficiary until a specified time. */ contract VestingWallet is Context { event EtherReleased(uint256 amount); @@ -62,6 +65,13 @@ contract VestingWallet is Context { return _duration; } + /** + * @dev Getter for the end timestamp. + */ + function end() public view virtual returns (uint256) { + return start() + duration(); + } + /** * @dev Amount of eth already released */ @@ -136,7 +146,7 @@ contract VestingWallet is Context { function _vestingSchedule(uint256 totalAllocation, uint64 timestamp) internal view virtual returns (uint256) { if (timestamp < start()) { return 0; - } else if (timestamp > start() + duration()) { + } else if (timestamp > end()) { return totalAllocation; } else { return (totalAllocation * (timestamp - start())) / duration(); diff --git a/contracts/governance/README.adoc b/contracts/governance/README.adoc index 171283662..00edfe23d 100644 --- a/contracts/governance/README.adoc +++ b/contracts/governance/README.adoc @@ -22,8 +22,6 @@ Votes modules determine the source of voting power, and sometimes quorum number. * {GovernorVotes}: Extracts voting weight from an {ERC20Votes}, or since v4.5 an {ERC721Votes} token. -* {GovernorVotesComp}: Extracts voting weight from a COMP-like or {ERC20VotesComp} token. - * {GovernorVotesQuorumFraction}: Combines with `GovernorVotes` to set the quorum as a fraction of the total token supply. Counting modules determine valid voting options. @@ -66,8 +64,6 @@ NOTE: Functions of the `Governor` contract do not include access control. If you {{GovernorVotesQuorumFraction}} -{{GovernorVotesComp}} - === Extensions {{GovernorTimelockControl}} diff --git a/contracts/governance/extensions/GovernorVotesComp.sol b/contracts/governance/extensions/GovernorVotesComp.sol deleted file mode 100644 index e7d7c2c7c..000000000 --- a/contracts/governance/extensions/GovernorVotesComp.sol +++ /dev/null @@ -1,55 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.9.0) (governance/extensions/GovernorVotesComp.sol) - -pragma solidity ^0.8.0; - -import "../Governor.sol"; -import "../../token/ERC20/extensions/ERC20VotesComp.sol"; - -/** - * @dev Extension of {Governor} for voting weight extraction from a Comp token. - * - * _Available since v4.3._ - */ -abstract contract GovernorVotesComp is Governor { - ERC20VotesComp public immutable token; - - constructor(ERC20VotesComp token_) { - token = token_; - } - - /** - * @dev Clock (as specified in EIP-6372) is set to match the token's clock. Fallback to block numbers if the token - * does not implement EIP-6372. - */ - function clock() public view virtual override returns (uint48) { - try token.clock() returns (uint48 timepoint) { - return timepoint; - } catch { - return SafeCast.toUint48(block.number); - } - } - - /** - * @dev Machine-readable description of the clock as specified in EIP-6372. - */ - // solhint-disable-next-line func-name-mixedcase - function CLOCK_MODE() public view virtual override returns (string memory) { - try token.CLOCK_MODE() returns (string memory clockmode) { - return clockmode; - } catch { - return "mode=blocknumber&from=default"; - } - } - - /** - * Read the voting weight from the token's built-in snapshot mechanism (see {Governor-_getVotes}). - */ - function _getVotes( - address account, - uint256 timepoint, - bytes memory /*params*/ - ) internal view virtual override returns (uint256) { - return token.getPriorVotes(account, timepoint); - } -} diff --git a/contracts/mocks/governance/GovernorCompMock.sol b/contracts/mocks/governance/GovernorCompMock.sol deleted file mode 100644 index cc368c42f..000000000 --- a/contracts/mocks/governance/GovernorCompMock.sol +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -import "../../governance/extensions/GovernorCountingSimple.sol"; -import "../../governance/extensions/GovernorVotesComp.sol"; - -abstract contract GovernorCompMock is GovernorVotesComp, GovernorCountingSimple { - function quorum(uint256) public pure override returns (uint256) { - return 0; - } - - function votingDelay() public pure override returns (uint256) { - return 4; - } - - function votingPeriod() public pure override returns (uint256) { - return 16; - } -} diff --git a/contracts/mocks/governance/GovernorCompatibilityBravoMock.sol b/contracts/mocks/governance/GovernorCompatibilityBravoMock.sol index 1b87d1433..4cdc0b888 100644 --- a/contracts/mocks/governance/GovernorCompatibilityBravoMock.sol +++ b/contracts/mocks/governance/GovernorCompatibilityBravoMock.sol @@ -5,13 +5,13 @@ pragma solidity ^0.8.0; import "../../governance/compatibility/GovernorCompatibilityBravo.sol"; import "../../governance/extensions/GovernorTimelockCompound.sol"; import "../../governance/extensions/GovernorSettings.sol"; -import "../../governance/extensions/GovernorVotesComp.sol"; +import "../../governance/extensions/GovernorVotes.sol"; abstract contract GovernorCompatibilityBravoMock is GovernorCompatibilityBravo, GovernorSettings, GovernorTimelockCompound, - GovernorVotesComp + GovernorVotes { function quorum(uint256) public pure override returns (uint256) { return 0; diff --git a/contracts/mocks/token/VotesTimestamp.sol b/contracts/mocks/token/VotesTimestamp.sol index 179c500f4..630e778f1 100644 --- a/contracts/mocks/token/VotesTimestamp.sol +++ b/contracts/mocks/token/VotesTimestamp.sol @@ -3,7 +3,6 @@ pragma solidity ^0.8.0; import "../../token/ERC20/extensions/ERC20Votes.sol"; -import "../../token/ERC20/extensions/ERC20VotesComp.sol"; import "../../token/ERC721/extensions/ERC721Votes.sol"; abstract contract ERC20VotesTimestampMock is ERC20Votes { @@ -17,17 +16,6 @@ abstract contract ERC20VotesTimestampMock is ERC20Votes { } } -abstract contract ERC20VotesCompTimestampMock is ERC20VotesComp { - function clock() public view virtual override returns (uint48) { - return SafeCast.toUint48(block.timestamp); - } - - // solhint-disable-next-line func-name-mixedcase - function CLOCK_MODE() public view virtual override returns (string memory) { - return "mode=timestamp"; - } -} - abstract contract ERC721VotesTimestampMock is ERC721Votes { function clock() public view virtual override returns (uint48) { return SafeCast.toUint48(block.timestamp); diff --git a/contracts/token/ERC20/README.adoc b/contracts/token/ERC20/README.adoc index 326aae684..9482b581b 100644 --- a/contracts/token/ERC20/README.adoc +++ b/contracts/token/ERC20/README.adoc @@ -18,18 +18,19 @@ Additionally there are multiple custom extensions, including: * {ERC20Burnable}: destruction of own tokens. * {ERC20Capped}: enforcement of a cap to the total supply when minting tokens. * {ERC20Pausable}: ability to pause token transfers. -* {ERC20Snapshot}: efficient storage of past token balances to be later queried at any point in time. * {ERC20Permit}: gasless approval of tokens (standardized as ERC2612). * {ERC20FlashMint}: token level support for flash loans through the minting and burning of ephemeral tokens (standardized as ERC3156). * {ERC20Votes}: support for voting and vote delegation. -* {ERC20VotesComp}: support for voting and vote delegation (compatible with Compound's token, with uint96 restrictions). * {ERC20Wrapper}: wrapper to create an ERC20 backed by another ERC20, with deposit and withdraw methods. Useful in conjunction with {ERC20Votes}. * {ERC4626}: tokenized vault that manages shares (represented as ERC20) that are backed by assets (another ERC20). -Finally, there are some utilities to interact with ERC20 contracts in various ways. +Finally, there are some utilities to interact with ERC20 contracts in various ways: * {SafeERC20}: a wrapper around the interface that eliminates the need to handle boolean return values. -* {TokenTimelock}: hold tokens for a beneficiary until a specified time. + +Other utilities that support ERC20 assets can be found in codebase: + +* ERC20 tokens can be timelocked (held tokens for a beneficiary until a specified time) or vested (released following a given schedule) using a {VestingWallet}. NOTE: This core set of contracts is designed to be unopinionated, allowing developers to access the internal functions in ERC20 (such as <>) and expose them as external functions in the way they prefer. @@ -51,12 +52,8 @@ NOTE: This core set of contracts is designed to be unopinionated, allowing devel {{ERC20Permit}} -{{ERC20Snapshot}} - {{ERC20Votes}} -{{ERC20VotesComp}} - {{ERC20Wrapper}} {{ERC20FlashMint}} @@ -66,5 +63,3 @@ NOTE: This core set of contracts is designed to be unopinionated, allowing devel == Utilities {{SafeERC20}} - -{{TokenTimelock}} diff --git a/contracts/token/ERC20/extensions/ERC20Snapshot.sol b/contracts/token/ERC20/extensions/ERC20Snapshot.sol deleted file mode 100644 index 70abc543a..000000000 --- a/contracts/token/ERC20/extensions/ERC20Snapshot.sol +++ /dev/null @@ -1,189 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/ERC20Snapshot.sol) - -pragma solidity ^0.8.0; - -import "../ERC20.sol"; -import "../../../utils/Arrays.sol"; -import "../../../utils/Counters.sol"; - -/** - * @dev This contract extends an ERC20 token with a snapshot mechanism. When a snapshot is created, the balances and - * total supply at the time are recorded for later access. - * - * This can be used to safely create mechanisms based on token balances such as trustless dividends or weighted voting. - * In naive implementations it's possible to perform a "double spend" attack by reusing the same balance from different - * accounts. By using snapshots to calculate dividends or voting power, those attacks no longer apply. It can also be - * used to create an efficient ERC20 forking mechanism. - * - * Snapshots are created by the internal {_snapshot} function, which will emit the {Snapshot} event and return a - * snapshot id. To get the total supply at the time of a snapshot, call the function {totalSupplyAt} with the snapshot - * id. To get the balance of an account at the time of a snapshot, call the {balanceOfAt} function with the snapshot id - * and the account address. - * - * NOTE: Snapshot policy can be customized by overriding the {_getCurrentSnapshotId} method. For example, having it - * return `block.number` will trigger the creation of snapshot at the beginning of each new block. When overriding this - * function, be careful about the monotonicity of its result. Non-monotonic snapshot ids will break the contract. - * - * Implementing snapshots for every block using this method will incur significant gas costs. For a gas-efficient - * alternative consider {ERC20Votes}. - * - * ==== Gas Costs - * - * Snapshots are efficient. Snapshot creation is _O(1)_. Retrieval of balances or total supply from a snapshot is _O(log - * n)_ in the number of snapshots that have been created, although _n_ for a specific account will generally be much - * smaller since identical balances in subsequent snapshots are stored as a single entry. - * - * There is a constant overhead for normal ERC20 transfers due to the additional snapshot bookkeeping. This overhead is - * only significant for the first transfer that immediately follows a snapshot for a particular account. Subsequent - * transfers will have normal cost until the next snapshot, and so on. - */ - -abstract contract ERC20Snapshot is ERC20 { - // Inspired by Jordi Baylina's MiniMeToken to record historical balances: - // https://github.com/Giveth/minime/blob/ea04d950eea153a04c51fa510b068b9dded390cb/contracts/MiniMeToken.sol - - using Arrays for uint256[]; - using Counters for Counters.Counter; - - // Snapshotted values have arrays of ids and the value corresponding to that id. These could be an array of a - // Snapshot struct, but that would impede usage of functions that work on an array. - struct Snapshots { - uint256[] ids; - uint256[] values; - } - - mapping(address => Snapshots) private _accountBalanceSnapshots; - Snapshots private _totalSupplySnapshots; - - // Snapshot ids increase monotonically, with the first value being 1. An id of 0 is invalid. - Counters.Counter private _currentSnapshotId; - - /** - * @dev Emitted by {_snapshot} when a snapshot identified by `id` is created. - */ - event Snapshot(uint256 id); - - /** - * @dev Creates a new snapshot and returns its snapshot id. - * - * Emits a {Snapshot} event that contains the same id. - * - * {_snapshot} is `internal` and you have to decide how to expose it externally. Its usage may be restricted to a - * set of accounts, for example using {AccessControl}, or it may be open to the public. - * - * [WARNING] - * ==== - * While an open way of calling {_snapshot} is required for certain trust minimization mechanisms such as forking, - * you must consider that it can potentially be used by attackers in two ways. - * - * First, it can be used to increase the cost of retrieval of values from snapshots, although it will grow - * logarithmically thus rendering this attack ineffective in the long term. Second, it can be used to target - * specific accounts and increase the cost of ERC20 transfers for them, in the ways specified in the Gas Costs - * section above. - * - * We haven't measured the actual numbers; if this is something you're interested in please reach out to us. - * ==== - */ - function _snapshot() internal virtual returns (uint256) { - _currentSnapshotId.increment(); - - uint256 currentId = _getCurrentSnapshotId(); - emit Snapshot(currentId); - return currentId; - } - - /** - * @dev Get the current snapshotId - */ - function _getCurrentSnapshotId() internal view virtual returns (uint256) { - return _currentSnapshotId.current(); - } - - /** - * @dev Retrieves the balance of `account` at the time `snapshotId` was created. - */ - function balanceOfAt(address account, uint256 snapshotId) public view virtual returns (uint256) { - (bool snapshotted, uint256 value) = _valueAt(snapshotId, _accountBalanceSnapshots[account]); - - return snapshotted ? value : balanceOf(account); - } - - /** - * @dev Retrieves the total supply at the time `snapshotId` was created. - */ - function totalSupplyAt(uint256 snapshotId) public view virtual returns (uint256) { - (bool snapshotted, uint256 value) = _valueAt(snapshotId, _totalSupplySnapshots); - - return snapshotted ? value : totalSupply(); - } - - // Update balance and/or total supply snapshots before the values are modified. This is executed - // for _mint, _burn, and _transfer operations. - function _update(address from, address to, uint256 amount) internal virtual override { - if (from == address(0)) { - _updateTotalSupplySnapshot(); - } else { - _updateAccountSnapshot(from); - } - - if (to == address(0)) { - _updateTotalSupplySnapshot(); - } else { - _updateAccountSnapshot(to); - } - - super._update(from, to, amount); - } - - function _valueAt(uint256 snapshotId, Snapshots storage snapshots) private view returns (bool, uint256) { - require(snapshotId > 0, "ERC20Snapshot: id is 0"); - require(snapshotId <= _getCurrentSnapshotId(), "ERC20Snapshot: nonexistent id"); - - // When a valid snapshot is queried, there are three possibilities: - // a) The queried value was not modified after the snapshot was taken. Therefore, a snapshot entry was never - // created for this id, and all stored snapshot ids are smaller than the requested one. The value that corresponds - // to this id is the current one. - // b) The queried value was modified after the snapshot was taken. Therefore, there will be an entry with the - // requested id, and its value is the one to return. - // c) More snapshots were created after the requested one, and the queried value was later modified. There will be - // no entry for the requested id: the value that corresponds to it is that of the smallest snapshot id that is - // larger than the requested one. - // - // In summary, we need to find an element in an array, returning the index of the smallest value that is larger if - // it is not found, unless said value doesn't exist (e.g. when all values are smaller). Arrays.findUpperBound does - // exactly this. - - uint256 index = snapshots.ids.findUpperBound(snapshotId); - - if (index == snapshots.ids.length) { - return (false, 0); - } else { - return (true, snapshots.values[index]); - } - } - - function _updateAccountSnapshot(address account) private { - _updateSnapshot(_accountBalanceSnapshots[account], balanceOf(account)); - } - - function _updateTotalSupplySnapshot() private { - _updateSnapshot(_totalSupplySnapshots, totalSupply()); - } - - function _updateSnapshot(Snapshots storage snapshots, uint256 currentValue) private { - uint256 currentId = _getCurrentSnapshotId(); - if (_lastSnapshotId(snapshots.ids) < currentId) { - snapshots.ids.push(currentId); - snapshots.values.push(currentValue); - } - } - - function _lastSnapshotId(uint256[] storage ids) private view returns (uint256) { - if (ids.length == 0) { - return 0; - } else { - return ids[ids.length - 1]; - } - } -} diff --git a/contracts/token/ERC20/extensions/ERC20Votes.sol b/contracts/token/ERC20/extensions/ERC20Votes.sol index 24fc7c2e5..e7cda4f35 100644 --- a/contracts/token/ERC20/extensions/ERC20Votes.sol +++ b/contracts/token/ERC20/extensions/ERC20Votes.sol @@ -11,7 +11,7 @@ import "../../../utils/math/SafeCast.sol"; * @dev Extension of ERC20 to support Compound-like voting and delegation. This version is more generic than Compound's, * and supports token supply up to 2^224^ - 1, while COMP is limited to 2^96^ - 1. * - * NOTE: If exact COMP compatibility is required, use the {ERC20VotesComp} variant of this module. + * NOTE: This contract does not provide interface compatibility with Compound's COMP token. * * This extension keeps a history (checkpoints) of each account's vote power. Vote power can be delegated either * by calling the {delegate} function directly, or by providing a signature to be used with {delegateBySig}. Voting diff --git a/contracts/token/ERC20/extensions/ERC20VotesComp.sol b/contracts/token/ERC20/extensions/ERC20VotesComp.sol deleted file mode 100644 index 0461310a4..000000000 --- a/contracts/token/ERC20/extensions/ERC20VotesComp.sol +++ /dev/null @@ -1,46 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC20/extensions/ERC20VotesComp.sol) - -pragma solidity ^0.8.0; - -import "./ERC20Votes.sol"; - -/** - * @dev Extension of ERC20 to support Compound's voting and delegation. This version exactly matches Compound's - * interface, with the drawback of only supporting supply up to (2^96^ - 1). - * - * NOTE: You should use this contract if you need exact compatibility with COMP (for example in order to use your token - * with Governor Alpha or Bravo) and if you are sure the supply cap of 2^96^ is enough for you. Otherwise, use the - * {ERC20Votes} variant of this module. - * - * This extension keeps a history (checkpoints) of each account's vote power. Vote power can be delegated either - * by calling the {delegate} function directly, or by providing a signature to be used with {delegateBySig}. Voting - * power can be queried through the public accessors {getCurrentVotes} and {getPriorVotes}. - * - * By default, token balance does not account for voting power. This makes transfers cheaper. The downside is that it - * requires users to delegate to themselves in order to activate checkpoints and have their voting power tracked. - * - * _Available since v4.2._ - */ -abstract contract ERC20VotesComp is ERC20Votes { - /** - * @dev Comp version of the {getVotes} accessor, with `uint96` return type. - */ - function getCurrentVotes(address account) external view virtual returns (uint96) { - return SafeCast.toUint96(getVotes(account)); - } - - /** - * @dev Comp version of the {getPastVotes} accessor, with `uint96` return type. - */ - function getPriorVotes(address account, uint256 blockNumber) external view virtual returns (uint96) { - return SafeCast.toUint96(getPastVotes(account, blockNumber)); - } - - /** - * @dev Maximum token supply. Reduced to `type(uint96).max` (2^96^ - 1) to fit COMP interface. - */ - function _maxSupply() internal view virtual override returns (uint224) { - return type(uint96).max; - } -} diff --git a/contracts/token/ERC20/utils/TokenTimelock.sol b/contracts/token/ERC20/utils/TokenTimelock.sol deleted file mode 100644 index cffdac5d4..000000000 --- a/contracts/token/ERC20/utils/TokenTimelock.sol +++ /dev/null @@ -1,72 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/utils/TokenTimelock.sol) - -pragma solidity ^0.8.0; - -import "./SafeERC20.sol"; - -/** - * @dev A token holder contract that will allow a beneficiary to extract the - * tokens after a given release time. - * - * Useful for simple vesting schedules like "advisors get all of their tokens - * after 1 year". - */ -contract TokenTimelock { - using SafeERC20 for IERC20; - - // ERC20 basic token contract being held - IERC20 private immutable _token; - - // beneficiary of tokens after they are released - address private immutable _beneficiary; - - // timestamp when token release is enabled - uint256 private immutable _releaseTime; - - /** - * @dev Deploys a timelock instance that is able to hold the token specified, and will only release it to - * `beneficiary_` when {release} is invoked after `releaseTime_`. The release time is specified as a Unix timestamp - * (in seconds). - */ - constructor(IERC20 token_, address beneficiary_, uint256 releaseTime_) { - require(releaseTime_ > block.timestamp, "TokenTimelock: release time is before current time"); - _token = token_; - _beneficiary = beneficiary_; - _releaseTime = releaseTime_; - } - - /** - * @dev Returns the token being held. - */ - function token() public view virtual returns (IERC20) { - return _token; - } - - /** - * @dev Returns the beneficiary that will receive the tokens. - */ - function beneficiary() public view virtual returns (address) { - return _beneficiary; - } - - /** - * @dev Returns the time when the tokens are released in seconds since Unix epoch (i.e. Unix timestamp). - */ - function releaseTime() public view virtual returns (uint256) { - return _releaseTime; - } - - /** - * @dev Transfers tokens held by the timelock to the beneficiary. Will only succeed if invoked after the release - * time. - */ - function release() public virtual { - require(block.timestamp >= releaseTime(), "TokenTimelock: current time is before release time"); - - uint256 amount = token().balanceOf(address(this)); - require(amount > 0, "TokenTimelock: no tokens to release"); - - token().safeTransfer(beneficiary(), amount); - } -} diff --git a/scripts/upgradeable/upgradeable.patch b/scripts/upgradeable/upgradeable.patch index bc675ab8a..efc89cab5 100644 --- a/scripts/upgradeable/upgradeable.patch +++ b/scripts/upgradeable/upgradeable.patch @@ -101,13 +101,13 @@ index 9fc95518..53130e3c 100644 } ``` diff --git a/contracts/finance/VestingWallet.sol b/contracts/finance/VestingWallet.sol -index fe67eb54..d26ea4e1 100644 +index bb70d19f..38513771 100644 --- a/contracts/finance/VestingWallet.sol +++ b/contracts/finance/VestingWallet.sol -@@ -15,6 +15,8 @@ import "../utils/Context.sol"; - * Any token transferred to this contract will follow the vesting schedule as if they were locked from the beginning. - * Consequently, if the vesting has already started, any amount of tokens sent to this contract will (at least partly) - * be immediately releasable. +@@ -18,6 +18,8 @@ import "../utils/Context.sol"; + * + * By setting the duration to 0, one can configure this contract to behave like an asset timelock that hold tokens for + * a beneficiary until a specified time. + * + * @custom:storage-size 52 */ @@ -126,19 +126,6 @@ index 64431711..885f0e42 100644 */ abstract contract GovernorVotes is Governor { IERC5805 public immutable token; -diff --git a/contracts/governance/extensions/GovernorVotesComp.sol b/contracts/governance/extensions/GovernorVotesComp.sol -index 17250ad7..1d26b72e 100644 ---- a/contracts/governance/extensions/GovernorVotesComp.sol -+++ b/contracts/governance/extensions/GovernorVotesComp.sol -@@ -10,6 +10,8 @@ import "../../token/ERC20/extensions/ERC20VotesComp.sol"; - * @dev Extension of {Governor} for voting weight extraction from a Comp token. - * - * _Available since v4.3._ -+ * -+ * @custom:storage-size 51 - */ - abstract contract GovernorVotesComp is Governor { - ERC20VotesComp public immutable token; diff --git a/contracts/package.json b/contracts/package.json index 55e70b17..ceefb984 100644 --- a/contracts/package.json @@ -198,19 +185,6 @@ index bfe782e4..7264fe32 100644 */ abstract contract ERC20Wrapper is ERC20 { IERC20 private immutable _underlying; -diff --git a/contracts/token/ERC20/utils/TokenTimelock.sol b/contracts/token/ERC20/utils/TokenTimelock.sol -index ed855b7b..3d30f59d 100644 ---- a/contracts/token/ERC20/utils/TokenTimelock.sol -+++ b/contracts/token/ERC20/utils/TokenTimelock.sol -@@ -11,6 +11,8 @@ import "./SafeERC20.sol"; - * - * Useful for simple vesting schedules like "advisors get all of their tokens - * after 1 year". -+ * -+ * @custom:storage-size 53 - */ - contract TokenTimelock { - using SafeERC20 for IERC20; diff --git a/contracts/utils/cryptography/EIP712.sol b/contracts/utils/cryptography/EIP712.sol index 6a4e1cad..55d8eced 100644 --- a/contracts/utils/cryptography/EIP712.sol diff --git a/test/finance/PaymentSplitter.test.js b/test/finance/PaymentSplitter.test.js deleted file mode 100644 index 1408c9f51..000000000 --- a/test/finance/PaymentSplitter.test.js +++ /dev/null @@ -1,217 +0,0 @@ -const { balance, constants, ether, expectEvent, send, expectRevert } = require('@openzeppelin/test-helpers'); -const { ZERO_ADDRESS } = constants; - -const { expect } = require('chai'); - -const PaymentSplitter = artifacts.require('PaymentSplitter'); -const ERC20 = artifacts.require('$ERC20'); - -contract('PaymentSplitter', function (accounts) { - const [owner, payee1, payee2, payee3, nonpayee1, payer1] = accounts; - - const amount = ether('1'); - - it('rejects an empty set of payees', async function () { - await expectRevert(PaymentSplitter.new([], []), 'PaymentSplitter: no payees'); - }); - - it('rejects more payees than shares', async function () { - await expectRevert( - PaymentSplitter.new([payee1, payee2, payee3], [20, 30]), - 'PaymentSplitter: payees and shares length mismatch', - ); - }); - - it('rejects more shares than payees', async function () { - await expectRevert( - PaymentSplitter.new([payee1, payee2], [20, 30, 40]), - 'PaymentSplitter: payees and shares length mismatch', - ); - }); - - it('rejects null payees', async function () { - await expectRevert( - PaymentSplitter.new([payee1, ZERO_ADDRESS], [20, 30]), - 'PaymentSplitter: account is the zero address', - ); - }); - - it('rejects zero-valued shares', async function () { - await expectRevert(PaymentSplitter.new([payee1, payee2], [20, 0]), 'PaymentSplitter: shares are 0'); - }); - - it('rejects repeated payees', async function () { - await expectRevert(PaymentSplitter.new([payee1, payee1], [20, 30]), 'PaymentSplitter: account already has shares'); - }); - - context('once deployed', function () { - beforeEach(async function () { - this.payees = [payee1, payee2, payee3]; - this.shares = [20, 10, 70]; - - this.contract = await PaymentSplitter.new(this.payees, this.shares); - this.token = await ERC20.new('MyToken', 'MT'); - await this.token.$_mint(owner, ether('1000')); - }); - - it('has total shares', async function () { - expect(await this.contract.totalShares()).to.be.bignumber.equal('100'); - }); - - it('has payees', async function () { - await Promise.all( - this.payees.map(async (payee, index) => { - expect(await this.contract.payee(index)).to.equal(payee); - expect(await this.contract.released(payee)).to.be.bignumber.equal('0'); - expect(await this.contract.releasable(payee)).to.be.bignumber.equal('0'); - }), - ); - }); - - describe('accepts payments', function () { - it('Ether', async function () { - await send.ether(owner, this.contract.address, amount); - - expect(await balance.current(this.contract.address)).to.be.bignumber.equal(amount); - }); - - it('Token', async function () { - await this.token.transfer(this.contract.address, amount, { from: owner }); - - expect(await this.token.balanceOf(this.contract.address)).to.be.bignumber.equal(amount); - }); - }); - - describe('shares', function () { - it('stores shares if address is payee', async function () { - expect(await this.contract.shares(payee1)).to.be.bignumber.not.equal('0'); - }); - - it('does not store shares if address is not payee', async function () { - expect(await this.contract.shares(nonpayee1)).to.be.bignumber.equal('0'); - }); - }); - - describe('release', function () { - describe('Ether', function () { - it('reverts if no funds to claim', async function () { - await expectRevert(this.contract.release(payee1), 'PaymentSplitter: account is not due payment'); - }); - it('reverts if non-payee want to claim', async function () { - await send.ether(payer1, this.contract.address, amount); - await expectRevert(this.contract.release(nonpayee1), 'PaymentSplitter: account has no shares'); - }); - }); - - describe('Token', function () { - it('reverts if no funds to claim', async function () { - await expectRevert( - this.contract.release(this.token.address, payee1), - 'PaymentSplitter: account is not due payment', - ); - }); - it('reverts if non-payee want to claim', async function () { - await this.token.transfer(this.contract.address, amount, { from: owner }); - await expectRevert( - this.contract.release(this.token.address, nonpayee1), - 'PaymentSplitter: account has no shares', - ); - }); - }); - }); - - describe('tracks releasable and released', function () { - it('Ether', async function () { - await send.ether(payer1, this.contract.address, amount); - const payment = amount.divn(10); - expect(await this.contract.releasable(payee2)).to.be.bignumber.equal(payment); - await this.contract.release(payee2); - expect(await this.contract.releasable(payee2)).to.be.bignumber.equal('0'); - expect(await this.contract.released(payee2)).to.be.bignumber.equal(payment); - }); - - it('Token', async function () { - await this.token.transfer(this.contract.address, amount, { from: owner }); - const payment = amount.divn(10); - expect(await this.contract.releasable(this.token.address, payee2, {})).to.be.bignumber.equal(payment); - await this.contract.release(this.token.address, payee2); - expect(await this.contract.releasable(this.token.address, payee2, {})).to.be.bignumber.equal('0'); - expect(await this.contract.released(this.token.address, payee2)).to.be.bignumber.equal(payment); - }); - }); - - describe('distributes funds to payees', function () { - it('Ether', async function () { - await send.ether(payer1, this.contract.address, amount); - - // receive funds - const initBalance = await balance.current(this.contract.address); - expect(initBalance).to.be.bignumber.equal(amount); - - // distribute to payees - - const tracker1 = await balance.tracker(payee1); - const receipt1 = await this.contract.release(payee1); - const profit1 = await tracker1.delta(); - expect(profit1).to.be.bignumber.equal(ether('0.20')); - expectEvent(receipt1, 'PaymentReleased', { to: payee1, amount: profit1 }); - - const tracker2 = await balance.tracker(payee2); - const receipt2 = await this.contract.release(payee2); - const profit2 = await tracker2.delta(); - expect(profit2).to.be.bignumber.equal(ether('0.10')); - expectEvent(receipt2, 'PaymentReleased', { to: payee2, amount: profit2 }); - - const tracker3 = await balance.tracker(payee3); - const receipt3 = await this.contract.release(payee3); - const profit3 = await tracker3.delta(); - expect(profit3).to.be.bignumber.equal(ether('0.70')); - expectEvent(receipt3, 'PaymentReleased', { to: payee3, amount: profit3 }); - - // end balance should be zero - expect(await balance.current(this.contract.address)).to.be.bignumber.equal('0'); - - // check correct funds released accounting - expect(await this.contract.totalReleased()).to.be.bignumber.equal(initBalance); - }); - - it('Token', async function () { - expect(await this.token.balanceOf(payee1)).to.be.bignumber.equal('0'); - expect(await this.token.balanceOf(payee2)).to.be.bignumber.equal('0'); - expect(await this.token.balanceOf(payee3)).to.be.bignumber.equal('0'); - - await this.token.transfer(this.contract.address, amount, { from: owner }); - - expectEvent(await this.contract.release(this.token.address, payee1), 'ERC20PaymentReleased', { - token: this.token.address, - to: payee1, - amount: ether('0.20'), - }); - - await this.token.transfer(this.contract.address, amount, { from: owner }); - - expectEvent(await this.contract.release(this.token.address, payee1), 'ERC20PaymentReleased', { - token: this.token.address, - to: payee1, - amount: ether('0.20'), - }); - - expectEvent(await this.contract.release(this.token.address, payee2), 'ERC20PaymentReleased', { - token: this.token.address, - to: payee2, - amount: ether('0.20'), - }); - - expectEvent(await this.contract.release(this.token.address, payee3), 'ERC20PaymentReleased', { - token: this.token.address, - to: payee3, - amount: ether('1.40'), - }); - - expect(await this.token.balanceOf(payee1)).to.be.bignumber.equal(ether('0.40')); - expect(await this.token.balanceOf(payee2)).to.be.bignumber.equal(ether('0.20')); - expect(await this.token.balanceOf(payee3)).to.be.bignumber.equal(ether('1.40')); - }); - }); - }); -}); diff --git a/test/finance/VestingWallet.test.js b/test/finance/VestingWallet.test.js index a110b2cd9..09205a99c 100644 --- a/test/finance/VestingWallet.test.js +++ b/test/finance/VestingWallet.test.js @@ -30,6 +30,7 @@ contract('VestingWallet', function (accounts) { expect(await this.mock.beneficiary()).to.be.equal(beneficiary); expect(await this.mock.start()).to.be.bignumber.equal(this.start); expect(await this.mock.duration()).to.be.bignumber.equal(duration); + expect(await this.mock.end()).to.be.bignumber.equal(this.start.add(duration)); }); describe('vesting schedule', function () { diff --git a/test/governance/compatibility/GovernorCompatibilityBravo.test.js b/test/governance/compatibility/GovernorCompatibilityBravo.test.js index d6d9f6a3c..9c45277d1 100644 --- a/test/governance/compatibility/GovernorCompatibilityBravo.test.js +++ b/test/governance/compatibility/GovernorCompatibilityBravo.test.js @@ -21,8 +21,8 @@ function makeContractAddress(creator, nonce) { } const TOKENS = [ - { Token: artifacts.require('$ERC20VotesComp'), mode: 'blocknumber' }, - { Token: artifacts.require('$ERC20VotesCompTimestampMock'), mode: 'timestamp' }, + { Token: artifacts.require('$ERC20Votes'), mode: 'blocknumber' }, + { Token: artifacts.require('$ERC20VotesTimestampMock'), mode: 'timestamp' }, ]; contract('GovernorCompatibilityBravo', function (accounts) { diff --git a/test/governance/extensions/GovernorComp.test.js b/test/governance/extensions/GovernorComp.test.js deleted file mode 100644 index 92a59d05f..000000000 --- a/test/governance/extensions/GovernorComp.test.js +++ /dev/null @@ -1,89 +0,0 @@ -const { expect } = require('chai'); - -const Enums = require('../../helpers/enums'); -const { GovernorHelper } = require('../../helpers/governance'); - -const Governor = artifacts.require('$GovernorCompMock'); -const CallReceiver = artifacts.require('CallReceiverMock'); - -const TOKENS = [ - { Token: artifacts.require('$ERC20VotesComp'), mode: 'blocknumber' }, - { Token: artifacts.require('$ERC20VotesCompTimestampMock'), mode: 'timestamp' }, -]; - -contract('GovernorComp', function (accounts) { - const [owner, voter1, voter2, voter3, voter4] = accounts; - - const name = 'OZ-Governor'; - const version = '1'; - const tokenName = 'MockToken'; - const tokenSymbol = 'MTKN'; - const tokenSupply = web3.utils.toWei('100'); - const votingDelay = web3.utils.toBN(4); - const votingPeriod = web3.utils.toBN(16); - const value = web3.utils.toWei('1'); - - for (const { mode, Token } of TOKENS) { - describe(`using ${Token._json.contractName}`, function () { - beforeEach(async function () { - this.owner = owner; - this.token = await Token.new(tokenName, tokenSymbol, tokenName, version); - this.mock = await Governor.new(name, this.token.address); - this.receiver = await CallReceiver.new(); - - this.helper = new GovernorHelper(this.mock, mode); - - await web3.eth.sendTransaction({ from: owner, to: this.mock.address, value }); - - await this.token.$_mint(owner, tokenSupply); - await this.helper.delegate({ token: this.token, to: voter1, value: web3.utils.toWei('10') }, { from: owner }); - await this.helper.delegate({ token: this.token, to: voter2, value: web3.utils.toWei('7') }, { from: owner }); - await this.helper.delegate({ token: this.token, to: voter3, value: web3.utils.toWei('5') }, { from: owner }); - await this.helper.delegate({ token: this.token, to: voter4, value: web3.utils.toWei('2') }, { from: owner }); - - // default proposal - this.proposal = this.helper.setProposal( - [ - { - target: this.receiver.address, - value, - data: this.receiver.contract.methods.mockFunction().encodeABI(), - }, - ], - '', - ); - }); - - it('deployment check', async function () { - expect(await this.mock.name()).to.be.equal(name); - expect(await this.mock.token()).to.be.equal(this.token.address); - expect(await this.mock.votingDelay()).to.be.bignumber.equal(votingDelay); - expect(await this.mock.votingPeriod()).to.be.bignumber.equal(votingPeriod); - expect(await this.mock.quorum(0)).to.be.bignumber.equal('0'); - }); - - it('voting with comp token', async function () { - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }); - await this.helper.vote({ support: Enums.VoteType.For }, { from: voter2 }); - await this.helper.vote({ support: Enums.VoteType.Against }, { from: voter3 }); - await this.helper.vote({ support: Enums.VoteType.Abstain }, { from: voter4 }); - await this.helper.waitForDeadline(); - await this.helper.execute(); - - expect(await this.mock.hasVoted(this.proposal.id, owner)).to.be.equal(false); - expect(await this.mock.hasVoted(this.proposal.id, voter1)).to.be.equal(true); - expect(await this.mock.hasVoted(this.proposal.id, voter2)).to.be.equal(true); - expect(await this.mock.hasVoted(this.proposal.id, voter3)).to.be.equal(true); - expect(await this.mock.hasVoted(this.proposal.id, voter4)).to.be.equal(true); - - await this.mock.proposalVotes(this.proposal.id).then(results => { - expect(results.forVotes).to.be.bignumber.equal(web3.utils.toWei('17')); - expect(results.againstVotes).to.be.bignumber.equal(web3.utils.toWei('5')); - expect(results.abstainVotes).to.be.bignumber.equal(web3.utils.toWei('2')); - }); - }); - }); - } -}); diff --git a/test/token/ERC20/extensions/ERC20Snapshot.test.js b/test/token/ERC20/extensions/ERC20Snapshot.test.js deleted file mode 100644 index fb0bb31d3..000000000 --- a/test/token/ERC20/extensions/ERC20Snapshot.test.js +++ /dev/null @@ -1,207 +0,0 @@ -const { BN, expectEvent, expectRevert } = require('@openzeppelin/test-helpers'); -const ERC20Snapshot = artifacts.require('$ERC20Snapshot'); - -const { expect } = require('chai'); - -contract('ERC20Snapshot', function (accounts) { - const [initialHolder, recipient, other] = accounts; - - const initialSupply = new BN(100); - - const name = 'My Token'; - const symbol = 'MTKN'; - - beforeEach(async function () { - this.token = await ERC20Snapshot.new(name, symbol); - await this.token.$_mint(initialHolder, initialSupply); - }); - - describe('snapshot', function () { - it('emits a snapshot event', async function () { - const receipt = await this.token.$_snapshot(); - expectEvent(receipt, 'Snapshot'); - }); - - it('creates increasing snapshots ids, starting from 1', async function () { - for (const id of ['1', '2', '3', '4', '5']) { - const receipt = await this.token.$_snapshot(); - expectEvent(receipt, 'Snapshot', { id }); - } - }); - }); - - describe('totalSupplyAt', function () { - it('reverts with a snapshot id of 0', async function () { - await expectRevert(this.token.totalSupplyAt(0), 'ERC20Snapshot: id is 0'); - }); - - it('reverts with a not-yet-created snapshot id', async function () { - await expectRevert(this.token.totalSupplyAt(1), 'ERC20Snapshot: nonexistent id'); - }); - - context('with initial snapshot', function () { - beforeEach(async function () { - this.initialSnapshotId = new BN('1'); - - const receipt = await this.token.$_snapshot(); - expectEvent(receipt, 'Snapshot', { id: this.initialSnapshotId }); - }); - - context('with no supply changes after the snapshot', function () { - it('returns the current total supply', async function () { - expect(await this.token.totalSupplyAt(this.initialSnapshotId)).to.be.bignumber.equal(initialSupply); - }); - }); - - context('with supply changes after the snapshot', function () { - beforeEach(async function () { - await this.token.$_mint(other, new BN('50')); - await this.token.$_burn(initialHolder, new BN('20')); - }); - - it('returns the total supply before the changes', async function () { - expect(await this.token.totalSupplyAt(this.initialSnapshotId)).to.be.bignumber.equal(initialSupply); - }); - - context('with a second snapshot after supply changes', function () { - beforeEach(async function () { - this.secondSnapshotId = new BN('2'); - - const receipt = await this.token.$_snapshot(); - expectEvent(receipt, 'Snapshot', { id: this.secondSnapshotId }); - }); - - it('snapshots return the supply before and after the changes', async function () { - expect(await this.token.totalSupplyAt(this.initialSnapshotId)).to.be.bignumber.equal(initialSupply); - - expect(await this.token.totalSupplyAt(this.secondSnapshotId)).to.be.bignumber.equal( - await this.token.totalSupply(), - ); - }); - }); - - context('with multiple snapshots after supply changes', function () { - beforeEach(async function () { - this.secondSnapshotIds = ['2', '3', '4']; - - for (const id of this.secondSnapshotIds) { - const receipt = await this.token.$_snapshot(); - expectEvent(receipt, 'Snapshot', { id }); - } - }); - - it('all posterior snapshots return the supply after the changes', async function () { - expect(await this.token.totalSupplyAt(this.initialSnapshotId)).to.be.bignumber.equal(initialSupply); - - const currentSupply = await this.token.totalSupply(); - - for (const id of this.secondSnapshotIds) { - expect(await this.token.totalSupplyAt(id)).to.be.bignumber.equal(currentSupply); - } - }); - }); - }); - }); - }); - - describe('balanceOfAt', function () { - it('reverts with a snapshot id of 0', async function () { - await expectRevert(this.token.balanceOfAt(other, 0), 'ERC20Snapshot: id is 0'); - }); - - it('reverts with a not-yet-created snapshot id', async function () { - await expectRevert(this.token.balanceOfAt(other, 1), 'ERC20Snapshot: nonexistent id'); - }); - - context('with initial snapshot', function () { - beforeEach(async function () { - this.initialSnapshotId = new BN('1'); - - const receipt = await this.token.$_snapshot(); - expectEvent(receipt, 'Snapshot', { id: this.initialSnapshotId }); - }); - - context('with no balance changes after the snapshot', function () { - it('returns the current balance for all accounts', async function () { - expect(await this.token.balanceOfAt(initialHolder, this.initialSnapshotId)).to.be.bignumber.equal( - initialSupply, - ); - expect(await this.token.balanceOfAt(recipient, this.initialSnapshotId)).to.be.bignumber.equal('0'); - expect(await this.token.balanceOfAt(other, this.initialSnapshotId)).to.be.bignumber.equal('0'); - }); - }); - - context('with balance changes after the snapshot', function () { - beforeEach(async function () { - await this.token.transfer(recipient, new BN('10'), { from: initialHolder }); - await this.token.$_mint(other, new BN('50')); - await this.token.$_burn(initialHolder, new BN('20')); - }); - - it('returns the balances before the changes', async function () { - expect(await this.token.balanceOfAt(initialHolder, this.initialSnapshotId)).to.be.bignumber.equal( - initialSupply, - ); - expect(await this.token.balanceOfAt(recipient, this.initialSnapshotId)).to.be.bignumber.equal('0'); - expect(await this.token.balanceOfAt(other, this.initialSnapshotId)).to.be.bignumber.equal('0'); - }); - - context('with a second snapshot after supply changes', function () { - beforeEach(async function () { - this.secondSnapshotId = new BN('2'); - - const receipt = await this.token.$_snapshot(); - expectEvent(receipt, 'Snapshot', { id: this.secondSnapshotId }); - }); - - it('snapshots return the balances before and after the changes', async function () { - expect(await this.token.balanceOfAt(initialHolder, this.initialSnapshotId)).to.be.bignumber.equal( - initialSupply, - ); - expect(await this.token.balanceOfAt(recipient, this.initialSnapshotId)).to.be.bignumber.equal('0'); - expect(await this.token.balanceOfAt(other, this.initialSnapshotId)).to.be.bignumber.equal('0'); - - expect(await this.token.balanceOfAt(initialHolder, this.secondSnapshotId)).to.be.bignumber.equal( - await this.token.balanceOf(initialHolder), - ); - expect(await this.token.balanceOfAt(recipient, this.secondSnapshotId)).to.be.bignumber.equal( - await this.token.balanceOf(recipient), - ); - expect(await this.token.balanceOfAt(other, this.secondSnapshotId)).to.be.bignumber.equal( - await this.token.balanceOf(other), - ); - }); - }); - - context('with multiple snapshots after supply changes', function () { - beforeEach(async function () { - this.secondSnapshotIds = ['2', '3', '4']; - - for (const id of this.secondSnapshotIds) { - const receipt = await this.token.$_snapshot(); - expectEvent(receipt, 'Snapshot', { id }); - } - }); - - it('all posterior snapshots return the supply after the changes', async function () { - expect(await this.token.balanceOfAt(initialHolder, this.initialSnapshotId)).to.be.bignumber.equal( - initialSupply, - ); - expect(await this.token.balanceOfAt(recipient, this.initialSnapshotId)).to.be.bignumber.equal('0'); - expect(await this.token.balanceOfAt(other, this.initialSnapshotId)).to.be.bignumber.equal('0'); - - for (const id of this.secondSnapshotIds) { - expect(await this.token.balanceOfAt(initialHolder, id)).to.be.bignumber.equal( - await this.token.balanceOf(initialHolder), - ); - expect(await this.token.balanceOfAt(recipient, id)).to.be.bignumber.equal( - await this.token.balanceOf(recipient), - ); - expect(await this.token.balanceOfAt(other, id)).to.be.bignumber.equal(await this.token.balanceOf(other)); - } - }); - }); - }); - }); - }); -}); diff --git a/test/token/ERC20/extensions/ERC20VotesComp.test.js b/test/token/ERC20/extensions/ERC20VotesComp.test.js deleted file mode 100644 index 815f22de0..000000000 --- a/test/token/ERC20/extensions/ERC20VotesComp.test.js +++ /dev/null @@ -1,579 +0,0 @@ -/* eslint-disable */ - -const { BN, constants, expectEvent, expectRevert, time } = require('@openzeppelin/test-helpers'); -const { expect } = require('chai'); -const { MAX_UINT256, ZERO_ADDRESS } = constants; - -const { batchInBlock } = require('../../../helpers/txpool'); -const { shouldBehaveLikeVotes } = require('../../../governance/utils/Votes.behavior'); -const { fromRpcSig } = require('ethereumjs-util'); -const ethSigUtil = require('eth-sig-util'); -const Wallet = require('ethereumjs-wallet').default; - -const { getDomain, domainType, domainSeparator } = require('../../../helpers/eip712'); -const { clock, clockFromReceipt } = require('../../../helpers/time'); - -const Delegation = [ - { name: 'delegatee', type: 'address' }, - { name: 'nonce', type: 'uint256' }, - { name: 'expiry', type: 'uint256' }, -]; - -const MODES = { - blocknumber: artifacts.require('$ERC20VotesComp'), - // no timestamp mode for ERC20VotesComp yet -}; - -contract('ERC20VotesComp', function (accounts) { - const [holder, recipient, holderDelegatee, other1, other2] = accounts; - - const name = 'My Token'; - const symbol = 'MTKN'; - const version = '1'; - const supply = new BN('10000000000000000000000000'); - - for (const [mode, artifact] of Object.entries(MODES)) { - describe(`vote with ${mode}`, function () { - beforeEach(async function () { - this.token = await artifact.new(name, symbol, name, version); - this.votes = this.token; - }); - - // includes EIP6372 behavior check - shouldBehaveLikeVotes(accounts, [1, 17, 42], { mode, fungible: true }); - - it('initial nonce is 0', async function () { - expect(await this.token.nonces(holder)).to.be.bignumber.equal('0'); - }); - - it('domain separator', async function () { - expect(await this.token.DOMAIN_SEPARATOR()).to.equal(await getDomain(this.token).then(domainSeparator)); - }); - - it('minting restriction', async function () { - const amount = new BN('2').pow(new BN('96')); - await expectRevert(this.token.$_mint(holder, amount), 'ERC20Votes: total supply risks overflowing votes'); - }); - - it('recent checkpoints', async function () { - await this.token.delegate(holder, { from: holder }); - for (let i = 0; i < 6; i++) { - await this.token.$_mint(holder, 1); - } - const timepoint = await clock[mode](); - expect(await this.token.numCheckpoints(holder)).to.be.bignumber.equal('6'); - // recent - expect(await this.token.getPastVotes(holder, timepoint - 1)).to.be.bignumber.equal('5'); - // non-recent - expect(await this.token.getPastVotes(holder, timepoint - 6)).to.be.bignumber.equal('0'); - }); - - describe('set delegation', function () { - describe('call', function () { - it('delegation with balance', async function () { - await this.token.$_mint(holder, supply); - expect(await this.token.delegates(holder)).to.be.equal(ZERO_ADDRESS); - - const { receipt } = await this.token.delegate(holder, { from: holder }); - const timepoint = await clockFromReceipt[mode](receipt); - - expectEvent(receipt, 'DelegateChanged', { - delegator: holder, - fromDelegate: ZERO_ADDRESS, - toDelegate: holder, - }); - expectEvent(receipt, 'DelegateVotesChanged', { - delegate: holder, - previousBalance: '0', - newBalance: supply, - }); - - expect(await this.token.delegates(holder)).to.be.equal(holder); - - expect(await this.token.getCurrentVotes(holder)).to.be.bignumber.equal(supply); - expect(await this.token.getPriorVotes(holder, timepoint - 1)).to.be.bignumber.equal('0'); - await time.advanceBlock(); - expect(await this.token.getPriorVotes(holder, timepoint)).to.be.bignumber.equal(supply); - }); - - it('delegation without balance', async function () { - expect(await this.token.delegates(holder)).to.be.equal(ZERO_ADDRESS); - - const { receipt } = await this.token.delegate(holder, { from: holder }); - expectEvent(receipt, 'DelegateChanged', { - delegator: holder, - fromDelegate: ZERO_ADDRESS, - toDelegate: holder, - }); - expectEvent.notEmitted(receipt, 'DelegateVotesChanged'); - - expect(await this.token.delegates(holder)).to.be.equal(holder); - }); - }); - - describe('with signature', function () { - const delegator = Wallet.generate(); - const delegatorAddress = web3.utils.toChecksumAddress(delegator.getAddressString()); - const nonce = 0; - - const buildData = (contract, message) => - getDomain(contract).then(domain => ({ - primaryType: 'Delegation', - types: { EIP712Domain: domainType(domain), Delegation }, - domain, - message, - })); - - beforeEach(async function () { - await this.token.$_mint(delegatorAddress, supply); - }); - - it('accept signed delegation', async function () { - const { v, r, s } = await buildData(this.token, { - delegatee: delegatorAddress, - nonce, - expiry: MAX_UINT256, - }).then(data => fromRpcSig(ethSigUtil.signTypedMessage(delegator.getPrivateKey(), { data }))); - - expect(await this.token.delegates(delegatorAddress)).to.be.equal(ZERO_ADDRESS); - - const { receipt } = await this.token.delegateBySig(delegatorAddress, nonce, MAX_UINT256, v, r, s); - const timepoint = await clockFromReceipt[mode](receipt); - - expectEvent(receipt, 'DelegateChanged', { - delegator: delegatorAddress, - fromDelegate: ZERO_ADDRESS, - toDelegate: delegatorAddress, - }); - expectEvent(receipt, 'DelegateVotesChanged', { - delegate: delegatorAddress, - previousBalance: '0', - newBalance: supply, - }); - - expect(await this.token.delegates(delegatorAddress)).to.be.equal(delegatorAddress); - - expect(await this.token.getCurrentVotes(delegatorAddress)).to.be.bignumber.equal(supply); - expect(await this.token.getPriorVotes(delegatorAddress, timepoint - 1)).to.be.bignumber.equal('0'); - await time.advanceBlock(); - expect(await this.token.getPriorVotes(delegatorAddress, timepoint)).to.be.bignumber.equal(supply); - }); - - it('rejects reused signature', async function () { - const { v, r, s } = await buildData(this.token, { - delegatee: delegatorAddress, - nonce, - expiry: MAX_UINT256, - }).then(data => fromRpcSig(ethSigUtil.signTypedMessage(delegator.getPrivateKey(), { data }))); - - await this.token.delegateBySig(delegatorAddress, nonce, MAX_UINT256, v, r, s); - - await expectRevert( - this.token.delegateBySig(delegatorAddress, nonce, MAX_UINT256, v, r, s), - 'Votes: invalid nonce', - ); - }); - - it('rejects bad delegatee', async function () { - const { v, r, s } = await buildData(this.token, { - delegatee: delegatorAddress, - nonce, - expiry: MAX_UINT256, - }).then(data => fromRpcSig(ethSigUtil.signTypedMessage(delegator.getPrivateKey(), { data }))); - - const receipt = await this.token.delegateBySig(holderDelegatee, nonce, MAX_UINT256, v, r, s); - const { args } = receipt.logs.find(({ event }) => event == 'DelegateChanged'); - expect(args.delegator).to.not.be.equal(delegatorAddress); - expect(args.fromDelegate).to.be.equal(ZERO_ADDRESS); - expect(args.toDelegate).to.be.equal(holderDelegatee); - }); - - it('rejects bad nonce', async function () { - const { v, r, s } = await buildData(this.token, { - delegatee: delegatorAddress, - nonce, - expiry: MAX_UINT256, - }).then(data => fromRpcSig(ethSigUtil.signTypedMessage(delegator.getPrivateKey(), { data }))); - - await expectRevert( - this.token.delegateBySig(delegatorAddress, nonce + 1, MAX_UINT256, v, r, s), - 'Votes: invalid nonce', - ); - }); - - it('rejects expired permit', async function () { - const expiry = (await time.latest()) - time.duration.weeks(1); - const { v, r, s } = await buildData(this.token, { - delegatee: delegatorAddress, - nonce, - expiry, - }).then(data => fromRpcSig(ethSigUtil.signTypedMessage(delegator.getPrivateKey(), { data }))); - - await expectRevert( - this.token.delegateBySig(delegatorAddress, nonce, expiry, v, r, s), - 'Votes: signature expired', - ); - }); - }); - }); - - describe('change delegation', function () { - beforeEach(async function () { - await this.token.$_mint(holder, supply); - await this.token.delegate(holder, { from: holder }); - }); - - it('call', async function () { - expect(await this.token.delegates(holder)).to.be.equal(holder); - - const { receipt } = await this.token.delegate(holderDelegatee, { from: holder }); - const timepoint = await clockFromReceipt[mode](receipt); - - expectEvent(receipt, 'DelegateChanged', { - delegator: holder, - fromDelegate: holder, - toDelegate: holderDelegatee, - }); - expectEvent(receipt, 'DelegateVotesChanged', { - delegate: holder, - previousBalance: supply, - newBalance: '0', - }); - expectEvent(receipt, 'DelegateVotesChanged', { - delegate: holderDelegatee, - previousBalance: '0', - newBalance: supply, - }); - - expect(await this.token.delegates(holder)).to.be.equal(holderDelegatee); - - expect(await this.token.getCurrentVotes(holder)).to.be.bignumber.equal('0'); - expect(await this.token.getCurrentVotes(holderDelegatee)).to.be.bignumber.equal(supply); - expect(await this.token.getPriorVotes(holder, timepoint - 1)).to.be.bignumber.equal(supply); - expect(await this.token.getPriorVotes(holderDelegatee, timepoint - 1)).to.be.bignumber.equal('0'); - await time.advanceBlock(); - expect(await this.token.getPriorVotes(holder, timepoint)).to.be.bignumber.equal('0'); - expect(await this.token.getPriorVotes(holderDelegatee, timepoint)).to.be.bignumber.equal(supply); - }); - }); - - describe('transfers', function () { - beforeEach(async function () { - await this.token.$_mint(holder, supply); - }); - - it('no delegation', async function () { - const { receipt } = await this.token.transfer(recipient, 1, { from: holder }); - expectEvent(receipt, 'Transfer', { from: holder, to: recipient, value: '1' }); - expectEvent.notEmitted(receipt, 'DelegateVotesChanged'); - - this.holderVotes = '0'; - this.recipientVotes = '0'; - }); - - it('sender delegation', async function () { - await this.token.delegate(holder, { from: holder }); - - const { receipt } = await this.token.transfer(recipient, 1, { from: holder }); - expectEvent(receipt, 'Transfer', { from: holder, to: recipient, value: '1' }); - expectEvent(receipt, 'DelegateVotesChanged', { - delegate: holder, - previousBalance: supply, - newBalance: supply.subn(1), - }); - - const { logIndex: transferLogIndex } = receipt.logs.find(({ event }) => event == 'Transfer'); - expect( - receipt.logs - .filter(({ event }) => event == 'DelegateVotesChanged') - .every(({ logIndex }) => transferLogIndex < logIndex), - ).to.be.equal(true); - - this.holderVotes = supply.subn(1); - this.recipientVotes = '0'; - }); - - it('receiver delegation', async function () { - await this.token.delegate(recipient, { from: recipient }); - - const { receipt } = await this.token.transfer(recipient, 1, { from: holder }); - expectEvent(receipt, 'Transfer', { from: holder, to: recipient, value: '1' }); - expectEvent(receipt, 'DelegateVotesChanged', { delegate: recipient, previousBalance: '0', newBalance: '1' }); - - const { logIndex: transferLogIndex } = receipt.logs.find(({ event }) => event == 'Transfer'); - expect( - receipt.logs - .filter(({ event }) => event == 'DelegateVotesChanged') - .every(({ logIndex }) => transferLogIndex < logIndex), - ).to.be.equal(true); - - this.holderVotes = '0'; - this.recipientVotes = '1'; - }); - - it('full delegation', async function () { - await this.token.delegate(holder, { from: holder }); - await this.token.delegate(recipient, { from: recipient }); - - const { receipt } = await this.token.transfer(recipient, 1, { from: holder }); - expectEvent(receipt, 'Transfer', { from: holder, to: recipient, value: '1' }); - expectEvent(receipt, 'DelegateVotesChanged', { - delegate: holder, - previousBalance: supply, - newBalance: supply.subn(1), - }); - expectEvent(receipt, 'DelegateVotesChanged', { delegate: recipient, previousBalance: '0', newBalance: '1' }); - - const { logIndex: transferLogIndex } = receipt.logs.find(({ event }) => event == 'Transfer'); - expect( - receipt.logs - .filter(({ event }) => event == 'DelegateVotesChanged') - .every(({ logIndex }) => transferLogIndex < logIndex), - ).to.be.equal(true); - - this.holderVotes = supply.subn(1); - this.recipientVotes = '1'; - }); - - afterEach(async function () { - expect(await this.token.getCurrentVotes(holder)).to.be.bignumber.equal(this.holderVotes); - expect(await this.token.getCurrentVotes(recipient)).to.be.bignumber.equal(this.recipientVotes); - - // need to advance 2 blocks to see the effect of a transfer on "getPriorVotes" - const timepoint = await clock[mode](); - await time.advanceBlock(); - expect(await this.token.getPriorVotes(holder, timepoint)).to.be.bignumber.equal(this.holderVotes); - expect(await this.token.getPriorVotes(recipient, timepoint)).to.be.bignumber.equal(this.recipientVotes); - }); - }); - - // The following tests are a adaptation of https://github.com/compound-finance/compound-protocol/blob/master/tests/Governance/CompTest.js. - describe('Compound test suite', function () { - beforeEach(async function () { - await this.token.$_mint(holder, supply); - }); - - describe('balanceOf', function () { - it('grants to initial account', async function () { - expect(await this.token.balanceOf(holder)).to.be.bignumber.equal('10000000000000000000000000'); - }); - }); - - describe('numCheckpoints', function () { - it('returns the number of checkpoints for a delegate', async function () { - await this.token.transfer(recipient, '100', { from: holder }); //give an account a few tokens for readability - expect(await this.token.numCheckpoints(other1)).to.be.bignumber.equal('0'); - - const t1 = await this.token.delegate(other1, { from: recipient }); - t1.timepoint = await clockFromReceipt[mode](t1.receipt); - expect(await this.token.numCheckpoints(other1)).to.be.bignumber.equal('1'); - - const t2 = await this.token.transfer(other2, 10, { from: recipient }); - t2.timepoint = await clockFromReceipt[mode](t2.receipt); - expect(await this.token.numCheckpoints(other1)).to.be.bignumber.equal('2'); - - const t3 = await this.token.transfer(other2, 10, { from: recipient }); - t3.timepoint = await clockFromReceipt[mode](t3.receipt); - expect(await this.token.numCheckpoints(other1)).to.be.bignumber.equal('3'); - - const t4 = await this.token.transfer(recipient, 20, { from: holder }); - t4.timepoint = await clockFromReceipt[mode](t4.receipt); - expect(await this.token.numCheckpoints(other1)).to.be.bignumber.equal('4'); - - expect(await this.token.checkpoints(other1, 0)).to.be.deep.equal([t1.timepoint.toString(), '100']); - expect(await this.token.checkpoints(other1, 1)).to.be.deep.equal([t2.timepoint.toString(), '90']); - expect(await this.token.checkpoints(other1, 2)).to.be.deep.equal([t3.timepoint.toString(), '80']); - expect(await this.token.checkpoints(other1, 3)).to.be.deep.equal([t4.timepoint.toString(), '100']); - - await time.advanceBlock(); - expect(await this.token.getPriorVotes(other1, t1.timepoint)).to.be.bignumber.equal('100'); - expect(await this.token.getPriorVotes(other1, t2.timepoint)).to.be.bignumber.equal('90'); - expect(await this.token.getPriorVotes(other1, t3.timepoint)).to.be.bignumber.equal('80'); - expect(await this.token.getPriorVotes(other1, t4.timepoint)).to.be.bignumber.equal('100'); - }); - - it('does not add more than one checkpoint in a block', async function () { - await this.token.transfer(recipient, '100', { from: holder }); - expect(await this.token.numCheckpoints(other1)).to.be.bignumber.equal('0'); - - const [t1, t2, t3] = await batchInBlock([ - () => this.token.delegate(other1, { from: recipient, gas: 200000 }), - () => this.token.transfer(other2, 10, { from: recipient, gas: 200000 }), - () => this.token.transfer(other2, 10, { from: recipient, gas: 200000 }), - ]); - t1.timepoint = await clockFromReceipt[mode](t1.receipt); - t2.timepoint = await clockFromReceipt[mode](t2.receipt); - t3.timepoint = await clockFromReceipt[mode](t3.receipt); - - expect(await this.token.numCheckpoints(other1)).to.be.bignumber.equal('1'); - expect(await this.token.checkpoints(other1, 0)).to.be.deep.equal([t1.timepoint.toString(), '80']); - - const t4 = await this.token.transfer(recipient, 20, { from: holder }); - t4.timepoint = await clockFromReceipt[mode](t4.receipt); - - expect(await this.token.numCheckpoints(other1)).to.be.bignumber.equal('2'); - expect(await this.token.checkpoints(other1, 1)).to.be.deep.equal([t4.timepoint.toString(), '100']); - }); - }); - - describe('getPriorVotes', function () { - it('reverts if block number >= current block', async function () { - await expectRevert(this.token.getPriorVotes(other1, 5e10), 'Votes: future lookup'); - }); - - it('returns 0 if there are no checkpoints', async function () { - expect(await this.token.getPriorVotes(other1, 0)).to.be.bignumber.equal('0'); - }); - - it('returns the latest block if >= last checkpoint block', async function () { - const { receipt } = await this.token.delegate(other1, { from: holder }); - const timepoint = await clockFromReceipt[mode](receipt); - await time.advanceBlock(); - await time.advanceBlock(); - - expect(await this.token.getPriorVotes(other1, timepoint)).to.be.bignumber.equal( - '10000000000000000000000000', - ); - expect(await this.token.getPriorVotes(other1, timepoint + 1)).to.be.bignumber.equal( - '10000000000000000000000000', - ); - }); - - it('returns zero if < first checkpoint block', async function () { - await time.advanceBlock(); - const { receipt } = await this.token.delegate(other1, { from: holder }); - const timepoint = await clockFromReceipt[mode](receipt); - await time.advanceBlock(); - await time.advanceBlock(); - - expect(await this.token.getPriorVotes(other1, timepoint - 1)).to.be.bignumber.equal('0'); - expect(await this.token.getPriorVotes(other1, timepoint + 1)).to.be.bignumber.equal( - '10000000000000000000000000', - ); - }); - - it('generally returns the voting balance at the appropriate checkpoint', async function () { - const t1 = await this.token.delegate(other1, { from: holder }); - await time.advanceBlock(); - await time.advanceBlock(); - const t2 = await this.token.transfer(other2, 10, { from: holder }); - await time.advanceBlock(); - await time.advanceBlock(); - const t3 = await this.token.transfer(other2, 10, { from: holder }); - await time.advanceBlock(); - await time.advanceBlock(); - const t4 = await this.token.transfer(holder, 20, { from: other2 }); - await time.advanceBlock(); - await time.advanceBlock(); - - t1.timepoint = await clockFromReceipt[mode](t1.receipt); - t2.timepoint = await clockFromReceipt[mode](t2.receipt); - t3.timepoint = await clockFromReceipt[mode](t3.receipt); - t4.timepoint = await clockFromReceipt[mode](t4.receipt); - - expect(await this.token.getPriorVotes(other1, t1.timepoint - 1)).to.be.bignumber.equal('0'); - expect(await this.token.getPriorVotes(other1, t1.timepoint)).to.be.bignumber.equal( - '10000000000000000000000000', - ); - expect(await this.token.getPriorVotes(other1, t1.timepoint + 1)).to.be.bignumber.equal( - '10000000000000000000000000', - ); - expect(await this.token.getPriorVotes(other1, t2.timepoint)).to.be.bignumber.equal( - '9999999999999999999999990', - ); - expect(await this.token.getPriorVotes(other1, t2.timepoint + 1)).to.be.bignumber.equal( - '9999999999999999999999990', - ); - expect(await this.token.getPriorVotes(other1, t3.timepoint)).to.be.bignumber.equal( - '9999999999999999999999980', - ); - expect(await this.token.getPriorVotes(other1, t3.timepoint + 1)).to.be.bignumber.equal( - '9999999999999999999999980', - ); - expect(await this.token.getPriorVotes(other1, t4.timepoint)).to.be.bignumber.equal( - '10000000000000000000000000', - ); - expect(await this.token.getPriorVotes(other1, t4.timepoint + 1)).to.be.bignumber.equal( - '10000000000000000000000000', - ); - }); - }); - }); - - describe('getPastTotalSupply', function () { - beforeEach(async function () { - await this.token.delegate(holder, { from: holder }); - }); - - it('reverts if block number >= current block', async function () { - await expectRevert(this.token.getPastTotalSupply(5e10), 'Votes: future lookup'); - }); - - it('returns 0 if there are no checkpoints', async function () { - expect(await this.token.getPastTotalSupply(0)).to.be.bignumber.equal('0'); - }); - - it('returns the latest block if >= last checkpoint block', async function () { - const { receipt } = await this.token.$_mint(holder, supply); - const timepoint = await clockFromReceipt[mode](receipt); - await time.advanceBlock(); - await time.advanceBlock(); - - expect(await this.token.getPastTotalSupply(timepoint)).to.be.bignumber.equal(supply); - expect(await this.token.getPastTotalSupply(timepoint + 1)).to.be.bignumber.equal(supply); - }); - - it('returns zero if < first checkpoint block', async function () { - await time.advanceBlock(); - const { receipt } = await this.token.$_mint(holder, supply); - const timepoint = await clockFromReceipt[mode](receipt); - await time.advanceBlock(); - await time.advanceBlock(); - - expect(await this.token.getPastTotalSupply(timepoint - 1)).to.be.bignumber.equal('0'); - expect(await this.token.getPastTotalSupply(timepoint + 1)).to.be.bignumber.equal( - '10000000000000000000000000', - ); - }); - - it('generally returns the voting balance at the appropriate checkpoint', async function () { - const t1 = await this.token.$_mint(holder, supply); - await time.advanceBlock(); - await time.advanceBlock(); - const t2 = await this.token.$_burn(holder, 10); - await time.advanceBlock(); - await time.advanceBlock(); - const t3 = await this.token.$_burn(holder, 10); - await time.advanceBlock(); - await time.advanceBlock(); - const t4 = await this.token.$_mint(holder, 20); - await time.advanceBlock(); - await time.advanceBlock(); - - t1.timepoint = await clockFromReceipt[mode](t1.receipt); - t2.timepoint = await clockFromReceipt[mode](t2.receipt); - t3.timepoint = await clockFromReceipt[mode](t3.receipt); - t4.timepoint = await clockFromReceipt[mode](t4.receipt); - - expect(await this.token.getPastTotalSupply(t1.timepoint - 1)).to.be.bignumber.equal('0'); - expect(await this.token.getPastTotalSupply(t1.timepoint)).to.be.bignumber.equal('10000000000000000000000000'); - expect(await this.token.getPastTotalSupply(t1.timepoint + 1)).to.be.bignumber.equal( - '10000000000000000000000000', - ); - expect(await this.token.getPastTotalSupply(t2.timepoint)).to.be.bignumber.equal('9999999999999999999999990'); - expect(await this.token.getPastTotalSupply(t2.timepoint + 1)).to.be.bignumber.equal( - '9999999999999999999999990', - ); - expect(await this.token.getPastTotalSupply(t3.timepoint)).to.be.bignumber.equal('9999999999999999999999980'); - expect(await this.token.getPastTotalSupply(t3.timepoint + 1)).to.be.bignumber.equal( - '9999999999999999999999980', - ); - expect(await this.token.getPastTotalSupply(t4.timepoint)).to.be.bignumber.equal('10000000000000000000000000'); - expect(await this.token.getPastTotalSupply(t4.timepoint + 1)).to.be.bignumber.equal( - '10000000000000000000000000', - ); - }); - }); - }); - } -}); diff --git a/test/token/ERC20/utils/TokenTimelock.test.js b/test/token/ERC20/utils/TokenTimelock.test.js deleted file mode 100644 index 22e8071eb..000000000 --- a/test/token/ERC20/utils/TokenTimelock.test.js +++ /dev/null @@ -1,71 +0,0 @@ -const { BN, expectRevert, time } = require('@openzeppelin/test-helpers'); - -const { expect } = require('chai'); - -const ERC20 = artifacts.require('$ERC20'); -const TokenTimelock = artifacts.require('TokenTimelock'); - -contract('TokenTimelock', function (accounts) { - const [beneficiary] = accounts; - - const name = 'My Token'; - const symbol = 'MTKN'; - - const amount = new BN(100); - - context('with token', function () { - beforeEach(async function () { - this.token = await ERC20.new(name, symbol); - }); - - it('rejects a release time in the past', async function () { - const pastReleaseTime = (await time.latest()).sub(time.duration.years(1)); - await expectRevert( - TokenTimelock.new(this.token.address, beneficiary, pastReleaseTime), - 'TokenTimelock: release time is before current time', - ); - }); - - context('once deployed', function () { - beforeEach(async function () { - this.releaseTime = (await time.latest()).add(time.duration.years(1)); - this.timelock = await TokenTimelock.new(this.token.address, beneficiary, this.releaseTime); - await this.token.$_mint(this.timelock.address, amount); - }); - - it('can get state', async function () { - expect(await this.timelock.token()).to.equal(this.token.address); - expect(await this.timelock.beneficiary()).to.equal(beneficiary); - expect(await this.timelock.releaseTime()).to.be.bignumber.equal(this.releaseTime); - }); - - it('cannot be released before time limit', async function () { - await expectRevert(this.timelock.release(), 'TokenTimelock: current time is before release time'); - }); - - it('cannot be released just before time limit', async function () { - await time.increaseTo(this.releaseTime.sub(time.duration.seconds(3))); - await expectRevert(this.timelock.release(), 'TokenTimelock: current time is before release time'); - }); - - it('can be released just after limit', async function () { - await time.increaseTo(this.releaseTime.add(time.duration.seconds(1))); - await this.timelock.release(); - expect(await this.token.balanceOf(beneficiary)).to.be.bignumber.equal(amount); - }); - - it('can be released after time limit', async function () { - await time.increaseTo(this.releaseTime.add(time.duration.years(1))); - await this.timelock.release(); - expect(await this.token.balanceOf(beneficiary)).to.be.bignumber.equal(amount); - }); - - it('cannot be released twice', async function () { - await time.increaseTo(this.releaseTime.add(time.duration.years(1))); - await this.timelock.release(); - await expectRevert(this.timelock.release(), 'TokenTimelock: no tokens to release'); - expect(await this.token.balanceOf(beneficiary)).to.be.bignumber.equal(amount); - }); - }); - }); -}); From 7bb5592ad5f7a1f28efbc358940f251ad0841366 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Mon, 29 May 2023 13:37:37 -0600 Subject: [PATCH 076/182] Bump minimum pragma version to `0.8.19` (#4288) --- .changeset/short-eels-enjoy.md | 5 +++++ README.md | 2 +- .../AccessControlDefaultAdminRulesHarness.sol | 2 +- certora/harnesses/AccessControlHarness.sol | 2 +- certora/harnesses/DoubleEndedQueueHarness.sol | 2 +- certora/harnesses/ERC20FlashMintHarness.sol | 2 +- certora/harnesses/ERC20PermitHarness.sol | 2 +- certora/harnesses/ERC20WrapperHarness.sol | 2 +- certora/harnesses/ERC3156FlashBorrowerHarness.sol | 2 +- certora/harnesses/ERC721Harness.sol | 2 +- certora/harnesses/ERC721ReceiverHarness.sol | 2 +- certora/harnesses/EnumerableMapHarness.sol | 2 +- certora/harnesses/EnumerableSetHarness.sol | 2 +- certora/harnesses/InitializableHarness.sol | 2 +- certora/harnesses/Ownable2StepHarness.sol | 2 +- certora/harnesses/OwnableHarness.sol | 2 +- certora/harnesses/PausableHarness.sol | 2 +- certora/harnesses/TimelockControllerHarness.sol | 2 +- contracts/access/AccessControl.sol | 2 +- contracts/access/AccessControlDefaultAdminRules.sol | 2 +- contracts/access/AccessControlEnumerable.sol | 2 +- contracts/access/IAccessControl.sol | 2 +- contracts/access/IAccessControlDefaultAdminRules.sol | 2 +- contracts/access/IAccessControlEnumerable.sol | 2 +- contracts/access/Ownable.sol | 2 +- contracts/access/Ownable2Step.sol | 2 +- contracts/finance/VestingWallet.sol | 2 +- contracts/governance/Governor.sol | 2 +- contracts/governance/IGovernor.sol | 2 +- contracts/governance/TimelockController.sol | 2 +- .../compatibility/GovernorCompatibilityBravo.sol | 2 +- .../compatibility/IGovernorCompatibilityBravo.sol | 2 +- .../governance/extensions/GovernorCountingSimple.sol | 2 +- .../extensions/GovernorPreventLateQuorum.sol | 2 +- contracts/governance/extensions/GovernorSettings.sol | 2 +- .../governance/extensions/GovernorTimelockCompound.sol | 2 +- .../governance/extensions/GovernorTimelockControl.sol | 2 +- contracts/governance/extensions/GovernorVotes.sol | 2 +- .../extensions/GovernorVotesQuorumFraction.sol | 2 +- contracts/governance/extensions/IGovernorTimelock.sol | 2 +- contracts/governance/utils/IVotes.sol | 2 +- contracts/governance/utils/Votes.sol | 2 +- contracts/interfaces/IERC1155.sol | 2 +- contracts/interfaces/IERC1155MetadataURI.sol | 2 +- contracts/interfaces/IERC1155Receiver.sol | 2 +- contracts/interfaces/IERC1271.sol | 2 +- contracts/interfaces/IERC1363.sol | 2 +- contracts/interfaces/IERC1363Receiver.sol | 2 +- contracts/interfaces/IERC1363Spender.sol | 2 +- contracts/interfaces/IERC165.sol | 2 +- contracts/interfaces/IERC1820Implementer.sol | 2 +- contracts/interfaces/IERC1820Registry.sol | 2 +- contracts/interfaces/IERC1967.sol | 2 +- contracts/interfaces/IERC20.sol | 2 +- contracts/interfaces/IERC20Metadata.sol | 2 +- contracts/interfaces/IERC2309.sol | 2 +- contracts/interfaces/IERC2612.sol | 2 +- contracts/interfaces/IERC2981.sol | 2 +- contracts/interfaces/IERC3156.sol | 2 +- contracts/interfaces/IERC3156FlashBorrower.sol | 2 +- contracts/interfaces/IERC3156FlashLender.sol | 2 +- contracts/interfaces/IERC4626.sol | 2 +- contracts/interfaces/IERC4906.sol | 2 +- contracts/interfaces/IERC5267.sol | 2 +- contracts/interfaces/IERC5313.sol | 2 +- contracts/interfaces/IERC5805.sol | 2 +- contracts/interfaces/IERC6372.sol | 2 +- contracts/interfaces/IERC721.sol | 2 +- contracts/interfaces/IERC721Enumerable.sol | 2 +- contracts/interfaces/IERC721Metadata.sol | 2 +- contracts/interfaces/IERC721Receiver.sol | 2 +- contracts/interfaces/IERC777.sol | 2 +- contracts/interfaces/IERC777Recipient.sol | 2 +- contracts/interfaces/IERC777Sender.sol | 2 +- contracts/interfaces/draft-IERC1822.sol | 2 +- contracts/metatx/ERC2771Context.sol | 2 +- contracts/metatx/MinimalForwarder.sol | 2 +- contracts/mocks/ArraysMock.sol | 2 +- contracts/mocks/CallReceiverMock.sol | 2 +- contracts/mocks/ContextMock.sol | 2 +- contracts/mocks/DummyImplementation.sol | 2 +- contracts/mocks/EIP712Verifier.sol | 2 +- contracts/mocks/ERC1271WalletMock.sol | 2 +- contracts/mocks/ERC165/ERC165InterfacesSupported.sol | 2 +- contracts/mocks/ERC165/ERC165MaliciousData.sol | 2 +- contracts/mocks/ERC165/ERC165MissingData.sol | 2 +- contracts/mocks/ERC165/ERC165NotSupported.sol | 2 +- contracts/mocks/ERC165/ERC165ReturnBomb.sol | 2 +- contracts/mocks/ERC2771ContextMock.sol | 2 +- contracts/mocks/ERC3156FlashBorrowerMock.sol | 2 +- contracts/mocks/EtherReceiverMock.sol | 2 +- contracts/mocks/InitializableMock.sol | 2 +- contracts/mocks/MulticallTest.sol | 2 +- .../mocks/MultipleInheritanceInitializableMocks.sol | 2 +- contracts/mocks/PausableMock.sol | 2 +- contracts/mocks/ReentrancyAttack.sol | 2 +- contracts/mocks/ReentrancyMock.sol | 2 +- contracts/mocks/RegressionImplementation.sol | 2 +- .../mocks/SingleInheritanceInitializableMocks.sol | 2 +- contracts/mocks/StorageSlotMock.sol | 2 +- contracts/mocks/TimelockReentrant.sol | 2 +- contracts/mocks/VotesMock.sol | 2 +- contracts/mocks/compound/CompTimelock.sol | 2 +- contracts/mocks/docs/ERC4626Fees.sol | 2 +- .../governance/GovernorCompatibilityBravoMock.sol | 2 +- contracts/mocks/governance/GovernorMock.sol | 2 +- .../mocks/governance/GovernorPreventLateQuorumMock.sol | 2 +- .../mocks/governance/GovernorTimelockCompoundMock.sol | 2 +- .../mocks/governance/GovernorTimelockControlMock.sol | 2 +- contracts/mocks/governance/GovernorVoteMock.sol | 2 +- contracts/mocks/governance/GovernorWithParamsMock.sol | 2 +- contracts/mocks/proxy/BadBeacon.sol | 2 +- contracts/mocks/proxy/ClashingImplementation.sol | 2 +- contracts/mocks/proxy/UUPSLegacy.sol | 2 +- contracts/mocks/proxy/UUPSUpgradeableMock.sol | 2 +- contracts/mocks/token/ERC1155ReceiverMock.sol | 2 +- contracts/mocks/token/ERC20DecimalsMock.sol | 2 +- contracts/mocks/token/ERC20ExcessDecimalsMock.sol | 2 +- contracts/mocks/token/ERC20FlashMintMock.sol | 2 +- contracts/mocks/token/ERC20ForceApproveMock.sol | 2 +- contracts/mocks/token/ERC20Mock.sol | 2 +- contracts/mocks/token/ERC20MulticallMock.sol | 2 +- contracts/mocks/token/ERC20NoReturnMock.sol | 2 +- contracts/mocks/token/ERC20PermitNoRevertMock.sol | 2 +- contracts/mocks/token/ERC20Reentrant.sol | 2 +- contracts/mocks/token/ERC20ReturnFalseMock.sol | 2 +- contracts/mocks/token/ERC20VotesLegacyMock.sol | 2 +- contracts/mocks/token/ERC4626Mock.sol | 2 +- contracts/mocks/token/ERC4626OffsetMock.sol | 2 +- contracts/mocks/token/ERC4646FeesMock.sol | 2 +- .../mocks/token/ERC721ConsecutiveEnumerableMock.sol | 2 +- contracts/mocks/token/ERC721ConsecutiveMock.sol | 2 +- contracts/mocks/token/ERC721ReceiverMock.sol | 2 +- contracts/mocks/token/ERC721URIStorageMock.sol | 2 +- contracts/mocks/token/VotesTimestamp.sol | 2 +- contracts/proxy/Clones.sol | 2 +- contracts/proxy/ERC1967/ERC1967Proxy.sol | 2 +- contracts/proxy/ERC1967/ERC1967Upgrade.sol | 2 +- contracts/proxy/Proxy.sol | 2 +- contracts/proxy/beacon/BeaconProxy.sol | 2 +- contracts/proxy/beacon/IBeacon.sol | 2 +- contracts/proxy/beacon/UpgradeableBeacon.sol | 2 +- contracts/proxy/transparent/ProxyAdmin.sol | 2 +- .../proxy/transparent/TransparentUpgradeableProxy.sol | 2 +- contracts/proxy/utils/Initializable.sol | 2 +- contracts/proxy/utils/UUPSUpgradeable.sol | 2 +- contracts/security/Pausable.sol | 2 +- contracts/security/ReentrancyGuard.sol | 2 +- contracts/token/ERC1155/ERC1155.sol | 2 +- contracts/token/ERC1155/IERC1155.sol | 2 +- contracts/token/ERC1155/IERC1155Receiver.sol | 2 +- contracts/token/ERC1155/extensions/ERC1155Burnable.sol | 2 +- contracts/token/ERC1155/extensions/ERC1155Pausable.sol | 2 +- contracts/token/ERC1155/extensions/ERC1155Supply.sol | 2 +- .../token/ERC1155/extensions/ERC1155URIStorage.sol | 2 +- .../token/ERC1155/extensions/IERC1155MetadataURI.sol | 2 +- contracts/token/ERC1155/utils/ERC1155Holder.sol | 2 +- contracts/token/ERC1155/utils/ERC1155Receiver.sol | 2 +- contracts/token/ERC20/ERC20.sol | 2 +- contracts/token/ERC20/IERC20.sol | 2 +- contracts/token/ERC20/extensions/ERC20Burnable.sol | 2 +- contracts/token/ERC20/extensions/ERC20Capped.sol | 2 +- contracts/token/ERC20/extensions/ERC20FlashMint.sol | 2 +- contracts/token/ERC20/extensions/ERC20Pausable.sol | 2 +- contracts/token/ERC20/extensions/ERC20Permit.sol | 2 +- contracts/token/ERC20/extensions/ERC20Votes.sol | 2 +- contracts/token/ERC20/extensions/ERC20Wrapper.sol | 2 +- contracts/token/ERC20/extensions/ERC4626.sol | 2 +- contracts/token/ERC20/extensions/IERC20Metadata.sol | 2 +- contracts/token/ERC20/extensions/IERC20Permit.sol | 2 +- contracts/token/ERC20/utils/SafeERC20.sol | 2 +- contracts/token/ERC721/ERC721.sol | 2 +- contracts/token/ERC721/IERC721.sol | 2 +- contracts/token/ERC721/IERC721Receiver.sol | 2 +- contracts/token/ERC721/extensions/ERC721Burnable.sol | 2 +- .../token/ERC721/extensions/ERC721Consecutive.sol | 2 +- contracts/token/ERC721/extensions/ERC721Enumerable.sol | 2 +- contracts/token/ERC721/extensions/ERC721Pausable.sol | 2 +- contracts/token/ERC721/extensions/ERC721Royalty.sol | 2 +- contracts/token/ERC721/extensions/ERC721URIStorage.sol | 2 +- contracts/token/ERC721/extensions/ERC721Votes.sol | 2 +- contracts/token/ERC721/extensions/ERC721Wrapper.sol | 2 +- .../token/ERC721/extensions/IERC721Enumerable.sol | 2 +- contracts/token/ERC721/extensions/IERC721Metadata.sol | 2 +- contracts/token/ERC721/utils/ERC721Holder.sol | 2 +- contracts/token/common/ERC2981.sol | 2 +- contracts/utils/Address.sol | 2 +- contracts/utils/Arrays.sol | 2 +- contracts/utils/Base64.sol | 2 +- contracts/utils/Context.sol | 2 +- contracts/utils/Counters.sol | 2 +- contracts/utils/Create2.sol | 2 +- contracts/utils/Multicall.sol | 2 +- contracts/utils/Nonces.sol | 2 +- contracts/utils/ShortStrings.sol | 2 +- contracts/utils/StorageSlot.sol | 2 +- contracts/utils/Strings.sol | 2 +- contracts/utils/cryptography/ECDSA.sol | 2 +- contracts/utils/cryptography/EIP712.sol | 2 +- contracts/utils/cryptography/MerkleProof.sol | 2 +- contracts/utils/cryptography/SignatureChecker.sol | 2 +- contracts/utils/introspection/ERC165.sol | 2 +- contracts/utils/introspection/ERC165Checker.sol | 2 +- contracts/utils/introspection/IERC165.sol | 2 +- contracts/utils/math/Math.sol | 2 +- contracts/utils/math/SafeCast.sol | 2 +- contracts/utils/math/SignedMath.sol | 2 +- contracts/utils/structs/BitMaps.sol | 2 +- contracts/utils/structs/Checkpoints.sol | 2 +- contracts/utils/structs/DoubleEndedQueue.sol | 2 +- contracts/utils/structs/EnumerableMap.sol | 2 +- contracts/utils/structs/EnumerableSet.sol | 2 +- contracts/vendor/compound/ICompoundTimelock.sol | 2 +- docs/modules/ROOT/pages/access-control.adoc | 8 ++++---- docs/modules/ROOT/pages/erc1155.adoc | 4 ++-- docs/modules/ROOT/pages/erc20.adoc | 2 +- docs/modules/ROOT/pages/erc721.adoc | 2 +- docs/modules/ROOT/pages/extending-contracts.adoc | 6 +++--- docs/modules/ROOT/pages/governance.adoc | 10 +++++----- docs/modules/ROOT/pages/index.adoc | 2 +- docs/modules/ROOT/pages/utilities.adoc | 2 +- hardhat.config.js | 2 +- scripts/generate/templates/Checkpoints.js | 2 +- scripts/generate/templates/Checkpoints.t.js | 2 +- scripts/generate/templates/EnumerableMap.js | 2 +- scripts/generate/templates/EnumerableSet.js | 2 +- scripts/generate/templates/SafeCast.js | 2 +- scripts/generate/templates/StorageSlot.js | 2 +- scripts/upgradeable/upgradeable.patch | 4 ++-- test/token/ERC20/extensions/ERC4626.t.sol | 2 +- test/token/ERC721/extensions/ERC721Consecutive.t.sol | 2 +- test/utils/ShortStrings.t.sol | 2 +- test/utils/math/Math.t.sol | 2 +- test/utils/structs/Checkpoints.t.sol | 2 +- 234 files changed, 249 insertions(+), 244 deletions(-) create mode 100644 .changeset/short-eels-enjoy.md diff --git a/.changeset/short-eels-enjoy.md b/.changeset/short-eels-enjoy.md new file mode 100644 index 000000000..b7d74c1a2 --- /dev/null +++ b/.changeset/short-eels-enjoy.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': major +--- + +Bump minimum compiler version required to 0.8.19 diff --git a/README.md b/README.md index 713ef6a68..aba991710 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ An alternative to npm is to use the GitHub repository (`openzeppelin/openzeppeli Once installed, you can use the contracts in the library by importing them: ```solidity -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; diff --git a/certora/harnesses/AccessControlDefaultAdminRulesHarness.sol b/certora/harnesses/AccessControlDefaultAdminRulesHarness.sol index 29fd3a709..3483c558f 100644 --- a/certora/harnesses/AccessControlDefaultAdminRulesHarness.sol +++ b/certora/harnesses/AccessControlDefaultAdminRulesHarness.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../patched/access/AccessControlDefaultAdminRules.sol"; diff --git a/certora/harnesses/AccessControlHarness.sol b/certora/harnesses/AccessControlHarness.sol index 0cb1e55d4..3363f3590 100644 --- a/certora/harnesses/AccessControlHarness.sol +++ b/certora/harnesses/AccessControlHarness.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../patched/access/AccessControl.sol"; diff --git a/certora/harnesses/DoubleEndedQueueHarness.sol b/certora/harnesses/DoubleEndedQueueHarness.sol index b4f0bc841..35dd4a54a 100644 --- a/certora/harnesses/DoubleEndedQueueHarness.sol +++ b/certora/harnesses/DoubleEndedQueueHarness.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../patched/utils/structs/DoubleEndedQueue.sol"; diff --git a/certora/harnesses/ERC20FlashMintHarness.sol b/certora/harnesses/ERC20FlashMintHarness.sol index 119eb4768..3599d8494 100644 --- a/certora/harnesses/ERC20FlashMintHarness.sol +++ b/certora/harnesses/ERC20FlashMintHarness.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../patched/token/ERC20/ERC20.sol"; import "../patched/token/ERC20/extensions/ERC20Permit.sol"; diff --git a/certora/harnesses/ERC20PermitHarness.sol b/certora/harnesses/ERC20PermitHarness.sol index dd0aacae2..dccc9cb91 100644 --- a/certora/harnesses/ERC20PermitHarness.sol +++ b/certora/harnesses/ERC20PermitHarness.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../patched/token/ERC20/extensions/ERC20Permit.sol"; diff --git a/certora/harnesses/ERC20WrapperHarness.sol b/certora/harnesses/ERC20WrapperHarness.sol index 50a96cc17..191c46cc4 100644 --- a/certora/harnesses/ERC20WrapperHarness.sol +++ b/certora/harnesses/ERC20WrapperHarness.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../patched/token/ERC20/extensions/ERC20Wrapper.sol"; diff --git a/certora/harnesses/ERC3156FlashBorrowerHarness.sol b/certora/harnesses/ERC3156FlashBorrowerHarness.sol index 0ad29a16e..d07d12e47 100644 --- a/certora/harnesses/ERC3156FlashBorrowerHarness.sol +++ b/certora/harnesses/ERC3156FlashBorrowerHarness.sol @@ -2,7 +2,7 @@ import "../patched/interfaces/IERC3156FlashBorrower.sol"; -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; contract ERC3156FlashBorrowerHarness is IERC3156FlashBorrower { bytes32 somethingToReturn; diff --git a/certora/harnesses/ERC721Harness.sol b/certora/harnesses/ERC721Harness.sol index 3307369a8..f22a6811d 100644 --- a/certora/harnesses/ERC721Harness.sol +++ b/certora/harnesses/ERC721Harness.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../patched/token/ERC721/ERC721.sol"; diff --git a/certora/harnesses/ERC721ReceiverHarness.sol b/certora/harnesses/ERC721ReceiverHarness.sol index 7e5739ee3..a0e9e247a 100644 --- a/certora/harnesses/ERC721ReceiverHarness.sol +++ b/certora/harnesses/ERC721ReceiverHarness.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../patched/interfaces/IERC721Receiver.sol"; diff --git a/certora/harnesses/EnumerableMapHarness.sol b/certora/harnesses/EnumerableMapHarness.sol index 3bcf1b50a..40b752487 100644 --- a/certora/harnesses/EnumerableMapHarness.sol +++ b/certora/harnesses/EnumerableMapHarness.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../patched/utils/structs/EnumerableMap.sol"; diff --git a/certora/harnesses/EnumerableSetHarness.sol b/certora/harnesses/EnumerableSetHarness.sol index 64383e6a4..21c7cc7c4 100644 --- a/certora/harnesses/EnumerableSetHarness.sol +++ b/certora/harnesses/EnumerableSetHarness.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../patched/utils/structs/EnumerableSet.sol"; diff --git a/certora/harnesses/InitializableHarness.sol b/certora/harnesses/InitializableHarness.sol index 19437e0fc..52b48b82d 100644 --- a/certora/harnesses/InitializableHarness.sol +++ b/certora/harnesses/InitializableHarness.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.2; +pragma solidity ^0.8.19; import "../patched/proxy/utils/Initializable.sol"; diff --git a/certora/harnesses/Ownable2StepHarness.sol b/certora/harnesses/Ownable2StepHarness.sol index 4d30e5041..1a9ed76e5 100644 --- a/certora/harnesses/Ownable2StepHarness.sol +++ b/certora/harnesses/Ownable2StepHarness.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../patched/access/Ownable2Step.sol"; diff --git a/certora/harnesses/OwnableHarness.sol b/certora/harnesses/OwnableHarness.sol index 93cbb4770..3113fa1bb 100644 --- a/certora/harnesses/OwnableHarness.sol +++ b/certora/harnesses/OwnableHarness.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../patched/access/Ownable.sol"; diff --git a/certora/harnesses/PausableHarness.sol b/certora/harnesses/PausableHarness.sol index 37c5d591d..34d6a82d8 100644 --- a/certora/harnesses/PausableHarness.sol +++ b/certora/harnesses/PausableHarness.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../patched/security/Pausable.sol"; diff --git a/certora/harnesses/TimelockControllerHarness.sol b/certora/harnesses/TimelockControllerHarness.sol index d1ea99065..f75dcfa45 100644 --- a/certora/harnesses/TimelockControllerHarness.sol +++ b/certora/harnesses/TimelockControllerHarness.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../patched/governance/TimelockController.sol"; diff --git a/contracts/access/AccessControl.sol b/contracts/access/AccessControl.sol index 94f9fbd26..82a43933d 100644 --- a/contracts/access/AccessControl.sol +++ b/contracts/access/AccessControl.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (access/AccessControl.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "./IAccessControl.sol"; import "../utils/Context.sol"; diff --git a/contracts/access/AccessControlDefaultAdminRules.sol b/contracts/access/AccessControlDefaultAdminRules.sol index 9de32002a..47df078c1 100644 --- a/contracts/access/AccessControlDefaultAdminRules.sol +++ b/contracts/access/AccessControlDefaultAdminRules.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (access/AccessControlDefaultAdminRules.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "./AccessControl.sol"; import "./IAccessControlDefaultAdminRules.sol"; diff --git a/contracts/access/AccessControlEnumerable.sol b/contracts/access/AccessControlEnumerable.sol index 354e1bed2..1c37a30a9 100644 --- a/contracts/access/AccessControlEnumerable.sol +++ b/contracts/access/AccessControlEnumerable.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.5.0) (access/AccessControlEnumerable.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "./IAccessControlEnumerable.sol"; import "./AccessControl.sol"; diff --git a/contracts/access/IAccessControl.sol b/contracts/access/IAccessControl.sol index f773ecc63..34708b78d 100644 --- a/contracts/access/IAccessControl.sol +++ b/contracts/access/IAccessControl.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; /** * @dev External interface of AccessControl declared to support ERC165 detection. diff --git a/contracts/access/IAccessControlDefaultAdminRules.sol b/contracts/access/IAccessControlDefaultAdminRules.sol index 434324e50..94cbe871d 100644 --- a/contracts/access/IAccessControlDefaultAdminRules.sol +++ b/contracts/access/IAccessControlDefaultAdminRules.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (access/IAccessControlDefaultAdminRules.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "./IAccessControl.sol"; diff --git a/contracts/access/IAccessControlEnumerable.sol b/contracts/access/IAccessControlEnumerable.sol index 61aaf57aa..240c61157 100644 --- a/contracts/access/IAccessControlEnumerable.sol +++ b/contracts/access/IAccessControlEnumerable.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (access/IAccessControlEnumerable.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "./IAccessControl.sol"; diff --git a/contracts/access/Ownable.sol b/contracts/access/Ownable.sol index 6ef38b89b..6c901b7a1 100644 --- a/contracts/access/Ownable.sol +++ b/contracts/access/Ownable.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../utils/Context.sol"; diff --git a/contracts/access/Ownable2Step.sol b/contracts/access/Ownable2Step.sol index e6f699881..59ffa3e0e 100644 --- a/contracts/access/Ownable2Step.sol +++ b/contracts/access/Ownable2Step.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable2Step.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "./Ownable.sol"; diff --git a/contracts/finance/VestingWallet.sol b/contracts/finance/VestingWallet.sol index bb70d19fb..5b7e1b150 100644 --- a/contracts/finance/VestingWallet.sol +++ b/contracts/finance/VestingWallet.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (finance/VestingWallet.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../token/ERC20/utils/SafeERC20.sol"; import "../utils/Address.sol"; diff --git a/contracts/governance/Governor.sol b/contracts/governance/Governor.sol index 390d2b94a..4ff8c3492 100644 --- a/contracts/governance/Governor.sol +++ b/contracts/governance/Governor.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (governance/Governor.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../token/ERC721/IERC721Receiver.sol"; import "../token/ERC1155/IERC1155Receiver.sol"; diff --git a/contracts/governance/IGovernor.sol b/contracts/governance/IGovernor.sol index 492d95b9a..245c5d97a 100644 --- a/contracts/governance/IGovernor.sol +++ b/contracts/governance/IGovernor.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (governance/IGovernor.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../interfaces/IERC165.sol"; import "../interfaces/IERC6372.sol"; diff --git a/contracts/governance/TimelockController.sol b/contracts/governance/TimelockController.sol index a80a674a1..5f09161df 100644 --- a/contracts/governance/TimelockController.sol +++ b/contracts/governance/TimelockController.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (governance/TimelockController.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../access/AccessControl.sol"; import "../token/ERC721/IERC721Receiver.sol"; diff --git a/contracts/governance/compatibility/GovernorCompatibilityBravo.sol b/contracts/governance/compatibility/GovernorCompatibilityBravo.sol index 1e18aeb6c..425ecad09 100644 --- a/contracts/governance/compatibility/GovernorCompatibilityBravo.sol +++ b/contracts/governance/compatibility/GovernorCompatibilityBravo.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (governance/compatibility/GovernorCompatibilityBravo.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../../utils/math/SafeCast.sol"; import "../extensions/IGovernorTimelock.sol"; diff --git a/contracts/governance/compatibility/IGovernorCompatibilityBravo.sol b/contracts/governance/compatibility/IGovernorCompatibilityBravo.sol index e64a66a66..d69bbf619 100644 --- a/contracts/governance/compatibility/IGovernorCompatibilityBravo.sol +++ b/contracts/governance/compatibility/IGovernorCompatibilityBravo.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (governance/compatibility/IGovernorCompatibilityBravo.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../IGovernor.sol"; diff --git a/contracts/governance/extensions/GovernorCountingSimple.sol b/contracts/governance/extensions/GovernorCountingSimple.sol index b9517445a..d5c99e593 100644 --- a/contracts/governance/extensions/GovernorCountingSimple.sol +++ b/contracts/governance/extensions/GovernorCountingSimple.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (governance/extensions/GovernorCountingSimple.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../Governor.sol"; diff --git a/contracts/governance/extensions/GovernorPreventLateQuorum.sol b/contracts/governance/extensions/GovernorPreventLateQuorum.sol index 752a92cf1..abb81128e 100644 --- a/contracts/governance/extensions/GovernorPreventLateQuorum.sol +++ b/contracts/governance/extensions/GovernorPreventLateQuorum.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (governance/extensions/GovernorPreventLateQuorum.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../Governor.sol"; import "../../utils/math/Math.sol"; diff --git a/contracts/governance/extensions/GovernorSettings.sol b/contracts/governance/extensions/GovernorSettings.sol index ec6a98300..570c88c54 100644 --- a/contracts/governance/extensions/GovernorSettings.sol +++ b/contracts/governance/extensions/GovernorSettings.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (governance/extensions/GovernorSettings.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../Governor.sol"; diff --git a/contracts/governance/extensions/GovernorTimelockCompound.sol b/contracts/governance/extensions/GovernorTimelockCompound.sol index a706da6a8..1efd3efff 100644 --- a/contracts/governance/extensions/GovernorTimelockCompound.sol +++ b/contracts/governance/extensions/GovernorTimelockCompound.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (governance/extensions/GovernorTimelockCompound.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "./IGovernorTimelock.sol"; import "../Governor.sol"; diff --git a/contracts/governance/extensions/GovernorTimelockControl.sol b/contracts/governance/extensions/GovernorTimelockControl.sol index 7cb60bab1..3fbce763a 100644 --- a/contracts/governance/extensions/GovernorTimelockControl.sol +++ b/contracts/governance/extensions/GovernorTimelockControl.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (governance/extensions/GovernorTimelockControl.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "./IGovernorTimelock.sol"; import "../Governor.sol"; diff --git a/contracts/governance/extensions/GovernorVotes.sol b/contracts/governance/extensions/GovernorVotes.sol index c2e65cba9..5d8318f46 100644 --- a/contracts/governance/extensions/GovernorVotes.sol +++ b/contracts/governance/extensions/GovernorVotes.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (governance/extensions/GovernorVotes.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../Governor.sol"; import "../../interfaces/IERC5805.sol"; diff --git a/contracts/governance/extensions/GovernorVotesQuorumFraction.sol b/contracts/governance/extensions/GovernorVotesQuorumFraction.sol index d42bb2f47..6c10240ce 100644 --- a/contracts/governance/extensions/GovernorVotesQuorumFraction.sol +++ b/contracts/governance/extensions/GovernorVotesQuorumFraction.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (governance/extensions/GovernorVotesQuorumFraction.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "./GovernorVotes.sol"; import "../../utils/math/SafeCast.sol"; diff --git a/contracts/governance/extensions/IGovernorTimelock.sol b/contracts/governance/extensions/IGovernorTimelock.sol index 40402f614..570092bc5 100644 --- a/contracts/governance/extensions/IGovernorTimelock.sol +++ b/contracts/governance/extensions/IGovernorTimelock.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (governance/extensions/IGovernorTimelock.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../IGovernor.sol"; diff --git a/contracts/governance/utils/IVotes.sol b/contracts/governance/utils/IVotes.sol index 647f79655..a1e4fe63a 100644 --- a/contracts/governance/utils/IVotes.sol +++ b/contracts/governance/utils/IVotes.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (governance/utils/IVotes.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; /** * @dev Common interface for {ERC20Votes}, {ERC721Votes}, and other {Votes}-enabled contracts. diff --git a/contracts/governance/utils/Votes.sol b/contracts/governance/utils/Votes.sol index cc1143fe2..5c855b5e4 100644 --- a/contracts/governance/utils/Votes.sol +++ b/contracts/governance/utils/Votes.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (governance/utils/Votes.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../../interfaces/IERC5805.sol"; import "../../utils/Context.sol"; diff --git a/contracts/interfaces/IERC1155.sol b/contracts/interfaces/IERC1155.sol index f89113212..8f7527f91 100644 --- a/contracts/interfaces/IERC1155.sol +++ b/contracts/interfaces/IERC1155.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (interfaces/IERC1155.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../token/ERC1155/IERC1155.sol"; diff --git a/contracts/interfaces/IERC1155MetadataURI.sol b/contracts/interfaces/IERC1155MetadataURI.sol index 2aa885feb..61b36c2e8 100644 --- a/contracts/interfaces/IERC1155MetadataURI.sol +++ b/contracts/interfaces/IERC1155MetadataURI.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (interfaces/IERC1155MetadataURI.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../token/ERC1155/extensions/IERC1155MetadataURI.sol"; diff --git a/contracts/interfaces/IERC1155Receiver.sol b/contracts/interfaces/IERC1155Receiver.sol index a6d4ead16..b5cd186b7 100644 --- a/contracts/interfaces/IERC1155Receiver.sol +++ b/contracts/interfaces/IERC1155Receiver.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (interfaces/IERC1155Receiver.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../token/ERC1155/IERC1155Receiver.sol"; diff --git a/contracts/interfaces/IERC1271.sol b/contracts/interfaces/IERC1271.sol index 5ec44c721..e7fca3079 100644 --- a/contracts/interfaces/IERC1271.sol +++ b/contracts/interfaces/IERC1271.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (interfaces/IERC1271.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; /** * @dev Interface of the ERC1271 standard signature validation method for diff --git a/contracts/interfaces/IERC1363.sol b/contracts/interfaces/IERC1363.sol index 817f3dafc..95c5b8992 100644 --- a/contracts/interfaces/IERC1363.sol +++ b/contracts/interfaces/IERC1363.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC1363.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "./IERC20.sol"; import "./IERC165.sol"; diff --git a/contracts/interfaces/IERC1363Receiver.sol b/contracts/interfaces/IERC1363Receiver.sol index 382d7f111..70ed857e9 100644 --- a/contracts/interfaces/IERC1363Receiver.sol +++ b/contracts/interfaces/IERC1363Receiver.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC1363Receiver.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; /** * @dev Interface for any contract that wants to support {IERC1363-transferAndCall} diff --git a/contracts/interfaces/IERC1363Spender.sol b/contracts/interfaces/IERC1363Spender.sol index 09a7bd278..0c89e58eb 100644 --- a/contracts/interfaces/IERC1363Spender.sol +++ b/contracts/interfaces/IERC1363Spender.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC1363Spender.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; /** * @dev Interface for any contract that wants to support {IERC1363-approveAndCall} diff --git a/contracts/interfaces/IERC165.sol b/contracts/interfaces/IERC165.sol index b97c4daa2..f4d90264c 100644 --- a/contracts/interfaces/IERC165.sol +++ b/contracts/interfaces/IERC165.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (interfaces/IERC165.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../utils/introspection/IERC165.sol"; diff --git a/contracts/interfaces/IERC1820Implementer.sol b/contracts/interfaces/IERC1820Implementer.sol index c4d0b3028..6f0ec661d 100644 --- a/contracts/interfaces/IERC1820Implementer.sol +++ b/contracts/interfaces/IERC1820Implementer.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC1820Implementer.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; /** * @dev Interface for an ERC1820 implementer, as defined in the diff --git a/contracts/interfaces/IERC1820Registry.sol b/contracts/interfaces/IERC1820Registry.sol index a146bc2a6..d9dd49355 100644 --- a/contracts/interfaces/IERC1820Registry.sol +++ b/contracts/interfaces/IERC1820Registry.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.8.0) (utils/introspection/IERC1820Registry.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; /** * @dev Interface of the global ERC1820 Registry, as defined in the diff --git a/contracts/interfaces/IERC1967.sol b/contracts/interfaces/IERC1967.sol index 6fb112a2e..190d86b2c 100644 --- a/contracts/interfaces/IERC1967.sol +++ b/contracts/interfaces/IERC1967.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC1967.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; /** * @dev ERC-1967: Proxy Storage Slots. This interface contains the events defined in the ERC. diff --git a/contracts/interfaces/IERC20.sol b/contracts/interfaces/IERC20.sol index a819316d1..dd559e980 100644 --- a/contracts/interfaces/IERC20.sol +++ b/contracts/interfaces/IERC20.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (interfaces/IERC20.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../token/ERC20/IERC20.sol"; diff --git a/contracts/interfaces/IERC20Metadata.sol b/contracts/interfaces/IERC20Metadata.sol index aa5c63910..061fd4a2f 100644 --- a/contracts/interfaces/IERC20Metadata.sol +++ b/contracts/interfaces/IERC20Metadata.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (interfaces/IERC20Metadata.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../token/ERC20/extensions/IERC20Metadata.sol"; diff --git a/contracts/interfaces/IERC2309.sol b/contracts/interfaces/IERC2309.sol index b3fec44e2..9d8886994 100644 --- a/contracts/interfaces/IERC2309.sol +++ b/contracts/interfaces/IERC2309.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.8.0) (interfaces/IERC2309.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; /** * @dev ERC-2309: ERC-721 Consecutive Transfer Extension. diff --git a/contracts/interfaces/IERC2612.sol b/contracts/interfaces/IERC2612.sol index cd5fca4cc..582eea81d 100644 --- a/contracts/interfaces/IERC2612.sol +++ b/contracts/interfaces/IERC2612.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC2612.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../token/ERC20/extensions/IERC20Permit.sol"; diff --git a/contracts/interfaces/IERC2981.sol b/contracts/interfaces/IERC2981.sol index 465b872ee..1b1476782 100644 --- a/contracts/interfaces/IERC2981.sol +++ b/contracts/interfaces/IERC2981.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC2981.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../utils/introspection/IERC165.sol"; diff --git a/contracts/interfaces/IERC3156.sol b/contracts/interfaces/IERC3156.sol index 12381906d..280b25be1 100644 --- a/contracts/interfaces/IERC3156.sol +++ b/contracts/interfaces/IERC3156.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (interfaces/IERC3156.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "./IERC3156FlashBorrower.sol"; import "./IERC3156FlashLender.sol"; diff --git a/contracts/interfaces/IERC3156FlashBorrower.sol b/contracts/interfaces/IERC3156FlashBorrower.sol index 84bd72150..3f216194b 100644 --- a/contracts/interfaces/IERC3156FlashBorrower.sol +++ b/contracts/interfaces/IERC3156FlashBorrower.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC3156FlashBorrower.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; /** * @dev Interface of the ERC3156 FlashBorrower, as defined in diff --git a/contracts/interfaces/IERC3156FlashLender.sol b/contracts/interfaces/IERC3156FlashLender.sol index 31012830f..89a486fdb 100644 --- a/contracts/interfaces/IERC3156FlashLender.sol +++ b/contracts/interfaces/IERC3156FlashLender.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (interfaces/IERC3156FlashLender.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "./IERC3156FlashBorrower.sol"; diff --git a/contracts/interfaces/IERC4626.sol b/contracts/interfaces/IERC4626.sol index 77dd96a05..946de9155 100644 --- a/contracts/interfaces/IERC4626.sol +++ b/contracts/interfaces/IERC4626.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC4626.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../token/ERC20/IERC20.sol"; import "../token/ERC20/extensions/IERC20Metadata.sol"; diff --git a/contracts/interfaces/IERC4906.sol b/contracts/interfaces/IERC4906.sol index f5a6e00f9..96e709228 100644 --- a/contracts/interfaces/IERC4906.sol +++ b/contracts/interfaces/IERC4906.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC4906.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "./IERC165.sol"; import "./IERC721.sol"; diff --git a/contracts/interfaces/IERC5267.sol b/contracts/interfaces/IERC5267.sol index 4d3a6b92a..c19b4a6da 100644 --- a/contracts/interfaces/IERC5267.sol +++ b/contracts/interfaces/IERC5267.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC5267.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; interface IERC5267 { /** diff --git a/contracts/interfaces/IERC5313.sol b/contracts/interfaces/IERC5313.sol index e26094c06..1d17080a1 100644 --- a/contracts/interfaces/IERC5313.sol +++ b/contracts/interfaces/IERC5313.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC5313.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; /** * @dev Interface for the Light Contract Ownership Standard. diff --git a/contracts/interfaces/IERC5805.sol b/contracts/interfaces/IERC5805.sol index a012ccb15..3c0a3a6c6 100644 --- a/contracts/interfaces/IERC5805.sol +++ b/contracts/interfaces/IERC5805.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC5805.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../governance/utils/IVotes.sol"; import "./IERC6372.sol"; diff --git a/contracts/interfaces/IERC6372.sol b/contracts/interfaces/IERC6372.sol index 4c5fe039c..c6c78d10a 100644 --- a/contracts/interfaces/IERC6372.sol +++ b/contracts/interfaces/IERC6372.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC6372.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; interface IERC6372 { /** diff --git a/contracts/interfaces/IERC721.sol b/contracts/interfaces/IERC721.sol index 822b311c5..e840f28c7 100644 --- a/contracts/interfaces/IERC721.sol +++ b/contracts/interfaces/IERC721.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (interfaces/IERC721.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../token/ERC721/IERC721.sol"; diff --git a/contracts/interfaces/IERC721Enumerable.sol b/contracts/interfaces/IERC721Enumerable.sol index e39a5a01b..fafda5998 100644 --- a/contracts/interfaces/IERC721Enumerable.sol +++ b/contracts/interfaces/IERC721Enumerable.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (interfaces/IERC721Enumerable.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../token/ERC721/extensions/IERC721Enumerable.sol"; diff --git a/contracts/interfaces/IERC721Metadata.sol b/contracts/interfaces/IERC721Metadata.sol index afe2707c9..f14433e8d 100644 --- a/contracts/interfaces/IERC721Metadata.sol +++ b/contracts/interfaces/IERC721Metadata.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (interfaces/IERC721Metadata.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../token/ERC721/extensions/IERC721Metadata.sol"; diff --git a/contracts/interfaces/IERC721Receiver.sol b/contracts/interfaces/IERC721Receiver.sol index c9c153a24..9e62fa734 100644 --- a/contracts/interfaces/IERC721Receiver.sol +++ b/contracts/interfaces/IERC721Receiver.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (interfaces/IERC721Receiver.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../token/ERC721/IERC721Receiver.sol"; diff --git a/contracts/interfaces/IERC777.sol b/contracts/interfaces/IERC777.sol index 4d36da52c..f868701ba 100644 --- a/contracts/interfaces/IERC777.sol +++ b/contracts/interfaces/IERC777.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; /** * @dev Interface of the ERC777Token standard as defined in the EIP. diff --git a/contracts/interfaces/IERC777Recipient.sol b/contracts/interfaces/IERC777Recipient.sol index 069904855..be2ab871d 100644 --- a/contracts/interfaces/IERC777Recipient.sol +++ b/contracts/interfaces/IERC777Recipient.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; /** * @dev Interface of the ERC777TokensRecipient standard as defined in the EIP. diff --git a/contracts/interfaces/IERC777Sender.sol b/contracts/interfaces/IERC777Sender.sol index c45477fcc..d4172b107 100644 --- a/contracts/interfaces/IERC777Sender.sol +++ b/contracts/interfaces/IERC777Sender.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; /** * @dev Interface of the ERC777TokensSender standard as defined in the EIP. diff --git a/contracts/interfaces/draft-IERC1822.sol b/contracts/interfaces/draft-IERC1822.sol index 3b73d744c..f452b5e34 100644 --- a/contracts/interfaces/draft-IERC1822.sol +++ b/contracts/interfaces/draft-IERC1822.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.5.0) (interfaces/draft-IERC1822.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; /** * @dev ERC1822: Universal Upgradeable Proxy Standard (UUPS) documents a method for upgradeability through a simplified diff --git a/contracts/metatx/ERC2771Context.sol b/contracts/metatx/ERC2771Context.sol index 8cc14b9f4..e02ffcc19 100644 --- a/contracts/metatx/ERC2771Context.sol +++ b/contracts/metatx/ERC2771Context.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.7.0) (metatx/ERC2771Context.sol) -pragma solidity ^0.8.9; +pragma solidity ^0.8.19; import "../utils/Context.sol"; diff --git a/contracts/metatx/MinimalForwarder.sol b/contracts/metatx/MinimalForwarder.sol index de2646269..8ea7a76e8 100644 --- a/contracts/metatx/MinimalForwarder.sol +++ b/contracts/metatx/MinimalForwarder.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (metatx/MinimalForwarder.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../utils/cryptography/ECDSA.sol"; import "../utils/cryptography/EIP712.sol"; diff --git a/contracts/mocks/ArraysMock.sol b/contracts/mocks/ArraysMock.sol index 2ea17a09f..b341edc62 100644 --- a/contracts/mocks/ArraysMock.sol +++ b/contracts/mocks/ArraysMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../utils/Arrays.sol"; diff --git a/contracts/mocks/CallReceiverMock.sol b/contracts/mocks/CallReceiverMock.sol index 492adbe92..b87f4e59e 100644 --- a/contracts/mocks/CallReceiverMock.sol +++ b/contracts/mocks/CallReceiverMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; contract CallReceiverMock { event MockFunctionCalled(); diff --git a/contracts/mocks/ContextMock.sol b/contracts/mocks/ContextMock.sol index 7759f3506..2e7751d0e 100644 --- a/contracts/mocks/ContextMock.sol +++ b/contracts/mocks/ContextMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../utils/Context.sol"; diff --git a/contracts/mocks/DummyImplementation.sol b/contracts/mocks/DummyImplementation.sol index ddcca6604..85503c36e 100644 --- a/contracts/mocks/DummyImplementation.sol +++ b/contracts/mocks/DummyImplementation.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; abstract contract Impl { function version() public pure virtual returns (string memory); diff --git a/contracts/mocks/EIP712Verifier.sol b/contracts/mocks/EIP712Verifier.sol index dcef9efbb..ea28162ba 100644 --- a/contracts/mocks/EIP712Verifier.sol +++ b/contracts/mocks/EIP712Verifier.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../utils/cryptography/ECDSA.sol"; import "../utils/cryptography/EIP712.sol"; diff --git a/contracts/mocks/ERC1271WalletMock.sol b/contracts/mocks/ERC1271WalletMock.sol index 4e6d3ce57..a28fd0fb9 100644 --- a/contracts/mocks/ERC1271WalletMock.sol +++ b/contracts/mocks/ERC1271WalletMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../access/Ownable.sol"; import "../interfaces/IERC1271.sol"; diff --git a/contracts/mocks/ERC165/ERC165InterfacesSupported.sol b/contracts/mocks/ERC165/ERC165InterfacesSupported.sol index 7a5e5bc67..d21d7c2d0 100644 --- a/contracts/mocks/ERC165/ERC165InterfacesSupported.sol +++ b/contracts/mocks/ERC165/ERC165InterfacesSupported.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../../utils/introspection/IERC165.sol"; diff --git a/contracts/mocks/ERC165/ERC165MaliciousData.sol b/contracts/mocks/ERC165/ERC165MaliciousData.sol index 2446f3df2..2f3b57d66 100644 --- a/contracts/mocks/ERC165/ERC165MaliciousData.sol +++ b/contracts/mocks/ERC165/ERC165MaliciousData.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; contract ERC165MaliciousData { function supportsInterface(bytes4) public pure returns (bool) { diff --git a/contracts/mocks/ERC165/ERC165MissingData.sol b/contracts/mocks/ERC165/ERC165MissingData.sol index 59cd51ae6..c58ca8fd0 100644 --- a/contracts/mocks/ERC165/ERC165MissingData.sol +++ b/contracts/mocks/ERC165/ERC165MissingData.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; contract ERC165MissingData { function supportsInterface(bytes4 interfaceId) public view {} // missing return diff --git a/contracts/mocks/ERC165/ERC165NotSupported.sol b/contracts/mocks/ERC165/ERC165NotSupported.sol index 486c7f0a4..9cd21bc2d 100644 --- a/contracts/mocks/ERC165/ERC165NotSupported.sol +++ b/contracts/mocks/ERC165/ERC165NotSupported.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; contract ERC165NotSupported {} diff --git a/contracts/mocks/ERC165/ERC165ReturnBomb.sol b/contracts/mocks/ERC165/ERC165ReturnBomb.sol index e53235d2c..d2a64151f 100644 --- a/contracts/mocks/ERC165/ERC165ReturnBomb.sol +++ b/contracts/mocks/ERC165/ERC165ReturnBomb.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../../utils/introspection/IERC165.sol"; diff --git a/contracts/mocks/ERC2771ContextMock.sol b/contracts/mocks/ERC2771ContextMock.sol index 387df785e..8c2cb43fd 100644 --- a/contracts/mocks/ERC2771ContextMock.sol +++ b/contracts/mocks/ERC2771ContextMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.9; +pragma solidity ^0.8.19; import "./ContextMock.sol"; import "../metatx/ERC2771Context.sol"; diff --git a/contracts/mocks/ERC3156FlashBorrowerMock.sol b/contracts/mocks/ERC3156FlashBorrowerMock.sol index 6a4410fcb..8d24af6c1 100644 --- a/contracts/mocks/ERC3156FlashBorrowerMock.sol +++ b/contracts/mocks/ERC3156FlashBorrowerMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../token/ERC20/IERC20.sol"; import "../interfaces/IERC3156.sol"; diff --git a/contracts/mocks/EtherReceiverMock.sol b/contracts/mocks/EtherReceiverMock.sol index a11e646fb..d06d35b21 100644 --- a/contracts/mocks/EtherReceiverMock.sol +++ b/contracts/mocks/EtherReceiverMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; contract EtherReceiverMock { bool private _acceptEther; diff --git a/contracts/mocks/InitializableMock.sol b/contracts/mocks/InitializableMock.sol index 34040b6e5..513aac052 100644 --- a/contracts/mocks/InitializableMock.sol +++ b/contracts/mocks/InitializableMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../proxy/utils/Initializable.sol"; diff --git a/contracts/mocks/MulticallTest.sol b/contracts/mocks/MulticallTest.sol index fcbec6ad8..090f73cb4 100644 --- a/contracts/mocks/MulticallTest.sol +++ b/contracts/mocks/MulticallTest.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "./token/ERC20MulticallMock.sol"; diff --git a/contracts/mocks/MultipleInheritanceInitializableMocks.sol b/contracts/mocks/MultipleInheritanceInitializableMocks.sol index e79cd92c8..cb62942ce 100644 --- a/contracts/mocks/MultipleInheritanceInitializableMocks.sol +++ b/contracts/mocks/MultipleInheritanceInitializableMocks.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../proxy/utils/Initializable.sol"; diff --git a/contracts/mocks/PausableMock.sol b/contracts/mocks/PausableMock.sol index 98bcfd593..85d45a3af 100644 --- a/contracts/mocks/PausableMock.sol +++ b/contracts/mocks/PausableMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../security/Pausable.sol"; diff --git a/contracts/mocks/ReentrancyAttack.sol b/contracts/mocks/ReentrancyAttack.sol index 4de181205..2da8b1f1a 100644 --- a/contracts/mocks/ReentrancyAttack.sol +++ b/contracts/mocks/ReentrancyAttack.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../utils/Context.sol"; diff --git a/contracts/mocks/ReentrancyMock.sol b/contracts/mocks/ReentrancyMock.sol index 161e1d3d8..104d4f42a 100644 --- a/contracts/mocks/ReentrancyMock.sol +++ b/contracts/mocks/ReentrancyMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../security/ReentrancyGuard.sol"; import "./ReentrancyAttack.sol"; diff --git a/contracts/mocks/RegressionImplementation.sol b/contracts/mocks/RegressionImplementation.sol index be6b501c1..f258bbc0e 100644 --- a/contracts/mocks/RegressionImplementation.sol +++ b/contracts/mocks/RegressionImplementation.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../proxy/utils/Initializable.sol"; diff --git a/contracts/mocks/SingleInheritanceInitializableMocks.sol b/contracts/mocks/SingleInheritanceInitializableMocks.sol index 6c82dd20c..2b5fad4eb 100644 --- a/contracts/mocks/SingleInheritanceInitializableMocks.sol +++ b/contracts/mocks/SingleInheritanceInitializableMocks.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../proxy/utils/Initializable.sol"; diff --git a/contracts/mocks/StorageSlotMock.sol b/contracts/mocks/StorageSlotMock.sol index 1da577c19..62dd23d6a 100644 --- a/contracts/mocks/StorageSlotMock.sol +++ b/contracts/mocks/StorageSlotMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../utils/StorageSlot.sol"; diff --git a/contracts/mocks/TimelockReentrant.sol b/contracts/mocks/TimelockReentrant.sol index a9344f50d..803a2b037 100644 --- a/contracts/mocks/TimelockReentrant.sol +++ b/contracts/mocks/TimelockReentrant.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../utils/Address.sol"; diff --git a/contracts/mocks/VotesMock.sol b/contracts/mocks/VotesMock.sol index c122f71e5..697d33448 100644 --- a/contracts/mocks/VotesMock.sol +++ b/contracts/mocks/VotesMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../governance/utils/Votes.sol"; diff --git a/contracts/mocks/compound/CompTimelock.sol b/contracts/mocks/compound/CompTimelock.sol index 49ffa4b77..9dc586ddb 100644 --- a/contracts/mocks/compound/CompTimelock.sol +++ b/contracts/mocks/compound/CompTimelock.sol @@ -24,7 +24,7 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; contract CompTimelock { event NewAdmin(address indexed newAdmin); diff --git a/contracts/mocks/docs/ERC4626Fees.sol b/contracts/mocks/docs/ERC4626Fees.sol index 8ff162953..703e4245a 100644 --- a/contracts/mocks/docs/ERC4626Fees.sol +++ b/contracts/mocks/docs/ERC4626Fees.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../../token/ERC20/extensions/ERC4626.sol"; diff --git a/contracts/mocks/governance/GovernorCompatibilityBravoMock.sol b/contracts/mocks/governance/GovernorCompatibilityBravoMock.sol index 4cdc0b888..6e14a357d 100644 --- a/contracts/mocks/governance/GovernorCompatibilityBravoMock.sol +++ b/contracts/mocks/governance/GovernorCompatibilityBravoMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../../governance/compatibility/GovernorCompatibilityBravo.sol"; import "../../governance/extensions/GovernorTimelockCompound.sol"; diff --git a/contracts/mocks/governance/GovernorMock.sol b/contracts/mocks/governance/GovernorMock.sol index 9284a24fe..9104c3f32 100644 --- a/contracts/mocks/governance/GovernorMock.sol +++ b/contracts/mocks/governance/GovernorMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../../governance/extensions/GovernorSettings.sol"; import "../../governance/extensions/GovernorCountingSimple.sol"; diff --git a/contracts/mocks/governance/GovernorPreventLateQuorumMock.sol b/contracts/mocks/governance/GovernorPreventLateQuorumMock.sol index 79d894896..4c1b408e6 100644 --- a/contracts/mocks/governance/GovernorPreventLateQuorumMock.sol +++ b/contracts/mocks/governance/GovernorPreventLateQuorumMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../../governance/extensions/GovernorPreventLateQuorum.sol"; import "../../governance/extensions/GovernorSettings.sol"; diff --git a/contracts/mocks/governance/GovernorTimelockCompoundMock.sol b/contracts/mocks/governance/GovernorTimelockCompoundMock.sol index b37462819..134d66133 100644 --- a/contracts/mocks/governance/GovernorTimelockCompoundMock.sol +++ b/contracts/mocks/governance/GovernorTimelockCompoundMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../../governance/extensions/GovernorTimelockCompound.sol"; import "../../governance/extensions/GovernorSettings.sol"; diff --git a/contracts/mocks/governance/GovernorTimelockControlMock.sol b/contracts/mocks/governance/GovernorTimelockControlMock.sol index 06309145a..28376835a 100644 --- a/contracts/mocks/governance/GovernorTimelockControlMock.sol +++ b/contracts/mocks/governance/GovernorTimelockControlMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../../governance/extensions/GovernorTimelockControl.sol"; import "../../governance/extensions/GovernorSettings.sol"; diff --git a/contracts/mocks/governance/GovernorVoteMock.sol b/contracts/mocks/governance/GovernorVoteMock.sol index 9b533bddf..1d0722ecf 100644 --- a/contracts/mocks/governance/GovernorVoteMock.sol +++ b/contracts/mocks/governance/GovernorVoteMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../../governance/extensions/GovernorCountingSimple.sol"; import "../../governance/extensions/GovernorVotes.sol"; diff --git a/contracts/mocks/governance/GovernorWithParamsMock.sol b/contracts/mocks/governance/GovernorWithParamsMock.sol index 361c2873e..f8de83710 100644 --- a/contracts/mocks/governance/GovernorWithParamsMock.sol +++ b/contracts/mocks/governance/GovernorWithParamsMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../../governance/extensions/GovernorCountingSimple.sol"; import "../../governance/extensions/GovernorVotes.sol"; diff --git a/contracts/mocks/proxy/BadBeacon.sol b/contracts/mocks/proxy/BadBeacon.sol index bedcfed84..2c8ffe291 100644 --- a/contracts/mocks/proxy/BadBeacon.sol +++ b/contracts/mocks/proxy/BadBeacon.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; contract BadBeaconNoImpl {} diff --git a/contracts/mocks/proxy/ClashingImplementation.sol b/contracts/mocks/proxy/ClashingImplementation.sol index 4e33160ff..957bc34be 100644 --- a/contracts/mocks/proxy/ClashingImplementation.sol +++ b/contracts/mocks/proxy/ClashingImplementation.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; /** * @dev Implementation contract with a payable changeAdmin(address) function made to clash with diff --git a/contracts/mocks/proxy/UUPSLegacy.sol b/contracts/mocks/proxy/UUPSLegacy.sol index ee0ed0bcc..ed243519e 100644 --- a/contracts/mocks/proxy/UUPSLegacy.sol +++ b/contracts/mocks/proxy/UUPSLegacy.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.1; +pragma solidity ^0.8.19; import "./UUPSUpgradeableMock.sol"; diff --git a/contracts/mocks/proxy/UUPSUpgradeableMock.sol b/contracts/mocks/proxy/UUPSUpgradeableMock.sol index f02271c49..83e6ef465 100644 --- a/contracts/mocks/proxy/UUPSUpgradeableMock.sol +++ b/contracts/mocks/proxy/UUPSUpgradeableMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../../proxy/utils/UUPSUpgradeable.sol"; import "../../utils/Counters.sol"; diff --git a/contracts/mocks/token/ERC1155ReceiverMock.sol b/contracts/mocks/token/ERC1155ReceiverMock.sol index 317d72425..9f75f002b 100644 --- a/contracts/mocks/token/ERC1155ReceiverMock.sol +++ b/contracts/mocks/token/ERC1155ReceiverMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../../token/ERC1155/IERC1155Receiver.sol"; import "../../utils/introspection/ERC165.sol"; diff --git a/contracts/mocks/token/ERC20DecimalsMock.sol b/contracts/mocks/token/ERC20DecimalsMock.sol index 32f287470..5699d31d2 100644 --- a/contracts/mocks/token/ERC20DecimalsMock.sol +++ b/contracts/mocks/token/ERC20DecimalsMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../../token/ERC20/ERC20.sol"; diff --git a/contracts/mocks/token/ERC20ExcessDecimalsMock.sol b/contracts/mocks/token/ERC20ExcessDecimalsMock.sol index 0fb35a607..bed8c5fcf 100644 --- a/contracts/mocks/token/ERC20ExcessDecimalsMock.sol +++ b/contracts/mocks/token/ERC20ExcessDecimalsMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; contract ERC20ExcessDecimalsMock { function decimals() public pure returns (uint256) { diff --git a/contracts/mocks/token/ERC20FlashMintMock.sol b/contracts/mocks/token/ERC20FlashMintMock.sol index b4de7b771..05c4db57d 100644 --- a/contracts/mocks/token/ERC20FlashMintMock.sol +++ b/contracts/mocks/token/ERC20FlashMintMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../../token/ERC20/extensions/ERC20FlashMint.sol"; diff --git a/contracts/mocks/token/ERC20ForceApproveMock.sol b/contracts/mocks/token/ERC20ForceApproveMock.sol index 955224bcf..42b417f41 100644 --- a/contracts/mocks/token/ERC20ForceApproveMock.sol +++ b/contracts/mocks/token/ERC20ForceApproveMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../../token/ERC20/ERC20.sol"; diff --git a/contracts/mocks/token/ERC20Mock.sol b/contracts/mocks/token/ERC20Mock.sol index fc6841d0e..5fb134f60 100644 --- a/contracts/mocks/token/ERC20Mock.sol +++ b/contracts/mocks/token/ERC20Mock.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../../token/ERC20/ERC20.sol"; diff --git a/contracts/mocks/token/ERC20MulticallMock.sol b/contracts/mocks/token/ERC20MulticallMock.sol index 145e97a62..b8259a75a 100644 --- a/contracts/mocks/token/ERC20MulticallMock.sol +++ b/contracts/mocks/token/ERC20MulticallMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../../token/ERC20/ERC20.sol"; import "../../utils/Multicall.sol"; diff --git a/contracts/mocks/token/ERC20NoReturnMock.sol b/contracts/mocks/token/ERC20NoReturnMock.sol index 348c0d6bb..7d597ad89 100644 --- a/contracts/mocks/token/ERC20NoReturnMock.sol +++ b/contracts/mocks/token/ERC20NoReturnMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../../token/ERC20/ERC20.sol"; diff --git a/contracts/mocks/token/ERC20PermitNoRevertMock.sol b/contracts/mocks/token/ERC20PermitNoRevertMock.sol index f2562d3cc..510176cf5 100644 --- a/contracts/mocks/token/ERC20PermitNoRevertMock.sol +++ b/contracts/mocks/token/ERC20PermitNoRevertMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../../token/ERC20/ERC20.sol"; import "../../token/ERC20/extensions/ERC20Permit.sol"; diff --git a/contracts/mocks/token/ERC20Reentrant.sol b/contracts/mocks/token/ERC20Reentrant.sol index bd67d940b..ee803b9e1 100644 --- a/contracts/mocks/token/ERC20Reentrant.sol +++ b/contracts/mocks/token/ERC20Reentrant.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../../token/ERC20/ERC20.sol"; import "../../token/ERC20/extensions/ERC4626.sol"; diff --git a/contracts/mocks/token/ERC20ReturnFalseMock.sol b/contracts/mocks/token/ERC20ReturnFalseMock.sol index c4dc6921f..763a120e9 100644 --- a/contracts/mocks/token/ERC20ReturnFalseMock.sol +++ b/contracts/mocks/token/ERC20ReturnFalseMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../../token/ERC20/ERC20.sol"; diff --git a/contracts/mocks/token/ERC20VotesLegacyMock.sol b/contracts/mocks/token/ERC20VotesLegacyMock.sol index 7ffa01022..cf8afa58a 100644 --- a/contracts/mocks/token/ERC20VotesLegacyMock.sol +++ b/contracts/mocks/token/ERC20VotesLegacyMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../../token/ERC20/extensions/ERC20Permit.sol"; import "../../utils/math/Math.sol"; diff --git a/contracts/mocks/token/ERC4626Mock.sol b/contracts/mocks/token/ERC4626Mock.sol index 40779d5c6..3713e0791 100644 --- a/contracts/mocks/token/ERC4626Mock.sol +++ b/contracts/mocks/token/ERC4626Mock.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../../token/ERC20/extensions/ERC4626.sol"; diff --git a/contracts/mocks/token/ERC4626OffsetMock.sol b/contracts/mocks/token/ERC4626OffsetMock.sol index 6e270ca73..b3c31cdd2 100644 --- a/contracts/mocks/token/ERC4626OffsetMock.sol +++ b/contracts/mocks/token/ERC4626OffsetMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../../token/ERC20/extensions/ERC4626.sol"; diff --git a/contracts/mocks/token/ERC4646FeesMock.sol b/contracts/mocks/token/ERC4646FeesMock.sol index cd8f41012..4c6c3c662 100644 --- a/contracts/mocks/token/ERC4646FeesMock.sol +++ b/contracts/mocks/token/ERC4646FeesMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../docs/ERC4626Fees.sol"; diff --git a/contracts/mocks/token/ERC721ConsecutiveEnumerableMock.sol b/contracts/mocks/token/ERC721ConsecutiveEnumerableMock.sol index 55c40ac17..a2ee8199a 100644 --- a/contracts/mocks/token/ERC721ConsecutiveEnumerableMock.sol +++ b/contracts/mocks/token/ERC721ConsecutiveEnumerableMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../../token/ERC721/extensions/ERC721Consecutive.sol"; import "../../token/ERC721/extensions/ERC721Enumerable.sol"; diff --git a/contracts/mocks/token/ERC721ConsecutiveMock.sol b/contracts/mocks/token/ERC721ConsecutiveMock.sol index 8bfa0cb9e..9fec7057a 100644 --- a/contracts/mocks/token/ERC721ConsecutiveMock.sol +++ b/contracts/mocks/token/ERC721ConsecutiveMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../../token/ERC721/extensions/ERC721Consecutive.sol"; import "../../token/ERC721/extensions/ERC721Enumerable.sol"; diff --git a/contracts/mocks/token/ERC721ReceiverMock.sol b/contracts/mocks/token/ERC721ReceiverMock.sol index dd25788d4..01526566a 100644 --- a/contracts/mocks/token/ERC721ReceiverMock.sol +++ b/contracts/mocks/token/ERC721ReceiverMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../../token/ERC721/IERC721Receiver.sol"; diff --git a/contracts/mocks/token/ERC721URIStorageMock.sol b/contracts/mocks/token/ERC721URIStorageMock.sol index 455c933c8..1808e47f7 100644 --- a/contracts/mocks/token/ERC721URIStorageMock.sol +++ b/contracts/mocks/token/ERC721URIStorageMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../../token/ERC721/extensions/ERC721URIStorage.sol"; diff --git a/contracts/mocks/token/VotesTimestamp.sol b/contracts/mocks/token/VotesTimestamp.sol index 630e778f1..7e3d8ca6d 100644 --- a/contracts/mocks/token/VotesTimestamp.sol +++ b/contracts/mocks/token/VotesTimestamp.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../../token/ERC20/extensions/ERC20Votes.sol"; import "../../token/ERC721/extensions/ERC721Votes.sol"; diff --git a/contracts/proxy/Clones.sol b/contracts/proxy/Clones.sol index 583e0223e..7cdab55f6 100644 --- a/contracts/proxy/Clones.sol +++ b/contracts/proxy/Clones.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (proxy/Clones.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; /** * @dev https://eips.ethereum.org/EIPS/eip-1167[EIP 1167] is a standard for diff --git a/contracts/proxy/ERC1967/ERC1967Proxy.sol b/contracts/proxy/ERC1967/ERC1967Proxy.sol index 642d811cc..320e026fa 100644 --- a/contracts/proxy/ERC1967/ERC1967Proxy.sol +++ b/contracts/proxy/ERC1967/ERC1967Proxy.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.7.0) (proxy/ERC1967/ERC1967Proxy.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../Proxy.sol"; import "./ERC1967Upgrade.sol"; diff --git a/contracts/proxy/ERC1967/ERC1967Upgrade.sol b/contracts/proxy/ERC1967/ERC1967Upgrade.sol index 95173d6e3..e42a06eb1 100644 --- a/contracts/proxy/ERC1967/ERC1967Upgrade.sol +++ b/contracts/proxy/ERC1967/ERC1967Upgrade.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (proxy/ERC1967/ERC1967Upgrade.sol) -pragma solidity ^0.8.2; +pragma solidity ^0.8.19; import "../beacon/IBeacon.sol"; import "../../interfaces/IERC1967.sol"; diff --git a/contracts/proxy/Proxy.sol b/contracts/proxy/Proxy.sol index 988cf72a0..c2875aeac 100644 --- a/contracts/proxy/Proxy.sol +++ b/contracts/proxy/Proxy.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.6.0) (proxy/Proxy.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; /** * @dev This abstract contract provides a fallback function that delegates all calls to another contract using the EVM diff --git a/contracts/proxy/beacon/BeaconProxy.sol b/contracts/proxy/beacon/BeaconProxy.sol index a278884f4..d603e38a7 100644 --- a/contracts/proxy/beacon/BeaconProxy.sol +++ b/contracts/proxy/beacon/BeaconProxy.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.7.0) (proxy/beacon/BeaconProxy.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "./IBeacon.sol"; import "../Proxy.sol"; diff --git a/contracts/proxy/beacon/IBeacon.sol b/contracts/proxy/beacon/IBeacon.sol index fba3ee2ab..fcd655d6c 100644 --- a/contracts/proxy/beacon/IBeacon.sol +++ b/contracts/proxy/beacon/IBeacon.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (proxy/beacon/IBeacon.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; /** * @dev This is the interface that {BeaconProxy} expects of its beacon. diff --git a/contracts/proxy/beacon/UpgradeableBeacon.sol b/contracts/proxy/beacon/UpgradeableBeacon.sol index 557c58f67..24d167f60 100644 --- a/contracts/proxy/beacon/UpgradeableBeacon.sol +++ b/contracts/proxy/beacon/UpgradeableBeacon.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (proxy/beacon/UpgradeableBeacon.sol) -pragma solidity ^0.8.1; +pragma solidity ^0.8.19; import "./IBeacon.sol"; import "../../access/Ownable.sol"; diff --git a/contracts/proxy/transparent/ProxyAdmin.sol b/contracts/proxy/transparent/ProxyAdmin.sol index 3739ad85f..490f552f8 100644 --- a/contracts/proxy/transparent/ProxyAdmin.sol +++ b/contracts/proxy/transparent/ProxyAdmin.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.8.3) (proxy/transparent/ProxyAdmin.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "./TransparentUpgradeableProxy.sol"; import "../../access/Ownable.sol"; diff --git a/contracts/proxy/transparent/TransparentUpgradeableProxy.sol b/contracts/proxy/transparent/TransparentUpgradeableProxy.sol index 722abc8a3..01f55e99b 100644 --- a/contracts/proxy/transparent/TransparentUpgradeableProxy.sol +++ b/contracts/proxy/transparent/TransparentUpgradeableProxy.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (proxy/transparent/TransparentUpgradeableProxy.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../ERC1967/ERC1967Proxy.sol"; diff --git a/contracts/proxy/utils/Initializable.sol b/contracts/proxy/utils/Initializable.sol index 94406a7a5..a42887918 100644 --- a/contracts/proxy/utils/Initializable.sol +++ b/contracts/proxy/utils/Initializable.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (proxy/utils/Initializable.sol) -pragma solidity ^0.8.2; +pragma solidity ^0.8.19; import "../../utils/Address.sol"; diff --git a/contracts/proxy/utils/UUPSUpgradeable.sol b/contracts/proxy/utils/UUPSUpgradeable.sol index 86684cfc3..c281c9091 100644 --- a/contracts/proxy/utils/UUPSUpgradeable.sol +++ b/contracts/proxy/utils/UUPSUpgradeable.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (proxy/utils/UUPSUpgradeable.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../../interfaces/draft-IERC1822.sol"; import "../ERC1967/ERC1967Upgrade.sol"; diff --git a/contracts/security/Pausable.sol b/contracts/security/Pausable.sol index bdd118432..cdf3ee2cd 100644 --- a/contracts/security/Pausable.sol +++ b/contracts/security/Pausable.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.7.0) (security/Pausable.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../utils/Context.sol"; diff --git a/contracts/security/ReentrancyGuard.sol b/contracts/security/ReentrancyGuard.sol index dac508b6a..88a86ae7e 100644 --- a/contracts/security/ReentrancyGuard.sol +++ b/contracts/security/ReentrancyGuard.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (security/ReentrancyGuard.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; /** * @dev Contract module that helps prevent reentrant calls to a function. diff --git a/contracts/token/ERC1155/ERC1155.sol b/contracts/token/ERC1155/ERC1155.sol index 31ddb6679..a2d7404bf 100644 --- a/contracts/token/ERC1155/ERC1155.sol +++ b/contracts/token/ERC1155/ERC1155.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC1155/ERC1155.sol) -pragma solidity ^0.8.1; +pragma solidity ^0.8.19; import "./IERC1155.sol"; import "./IERC1155Receiver.sol"; diff --git a/contracts/token/ERC1155/IERC1155.sol b/contracts/token/ERC1155/IERC1155.sol index 3446e604a..d7e25a5b1 100644 --- a/contracts/token/ERC1155/IERC1155.sol +++ b/contracts/token/ERC1155/IERC1155.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC1155/IERC1155.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../../utils/introspection/IERC165.sol"; diff --git a/contracts/token/ERC1155/IERC1155Receiver.sol b/contracts/token/ERC1155/IERC1155Receiver.sol index 0dd271d04..a272f61b0 100644 --- a/contracts/token/ERC1155/IERC1155Receiver.sol +++ b/contracts/token/ERC1155/IERC1155Receiver.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.5.0) (token/ERC1155/IERC1155Receiver.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../../utils/introspection/IERC165.sol"; diff --git a/contracts/token/ERC1155/extensions/ERC1155Burnable.sol b/contracts/token/ERC1155/extensions/ERC1155Burnable.sol index 9ee371986..c079f07e1 100644 --- a/contracts/token/ERC1155/extensions/ERC1155Burnable.sol +++ b/contracts/token/ERC1155/extensions/ERC1155Burnable.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC1155/extensions/ERC1155Burnable.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../ERC1155.sol"; diff --git a/contracts/token/ERC1155/extensions/ERC1155Pausable.sol b/contracts/token/ERC1155/extensions/ERC1155Pausable.sol index cd12c2b4a..95f006e6f 100644 --- a/contracts/token/ERC1155/extensions/ERC1155Pausable.sol +++ b/contracts/token/ERC1155/extensions/ERC1155Pausable.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.8.2) (token/ERC1155/extensions/ERC1155Pausable.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../ERC1155.sol"; import "../../../security/Pausable.sol"; diff --git a/contracts/token/ERC1155/extensions/ERC1155Supply.sol b/contracts/token/ERC1155/extensions/ERC1155Supply.sol index fadc8f9ff..599df00f3 100644 --- a/contracts/token/ERC1155/extensions/ERC1155Supply.sol +++ b/contracts/token/ERC1155/extensions/ERC1155Supply.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.6.0) (token/ERC1155/extensions/ERC1155Supply.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../ERC1155.sol"; diff --git a/contracts/token/ERC1155/extensions/ERC1155URIStorage.sol b/contracts/token/ERC1155/extensions/ERC1155URIStorage.sol index 623504f3d..5b0da2b33 100644 --- a/contracts/token/ERC1155/extensions/ERC1155URIStorage.sol +++ b/contracts/token/ERC1155/extensions/ERC1155URIStorage.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.6.0) (token/ERC1155/extensions/ERC1155URIStorage.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../../../utils/Strings.sol"; import "../ERC1155.sol"; diff --git a/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol b/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol index 520a29715..2c998995e 100644 --- a/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol +++ b/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (token/ERC1155/extensions/IERC1155MetadataURI.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../IERC1155.sol"; diff --git a/contracts/token/ERC1155/utils/ERC1155Holder.sol b/contracts/token/ERC1155/utils/ERC1155Holder.sol index 7249de841..b06a37f18 100644 --- a/contracts/token/ERC1155/utils/ERC1155Holder.sol +++ b/contracts/token/ERC1155/utils/ERC1155Holder.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.5.0) (token/ERC1155/utils/ERC1155Holder.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "./ERC1155Receiver.sol"; diff --git a/contracts/token/ERC1155/utils/ERC1155Receiver.sol b/contracts/token/ERC1155/utils/ERC1155Receiver.sol index 2e6804a2d..6fb21491f 100644 --- a/contracts/token/ERC1155/utils/ERC1155Receiver.sol +++ b/contracts/token/ERC1155/utils/ERC1155Receiver.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (token/ERC1155/utils/ERC1155Receiver.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../IERC1155Receiver.sol"; import "../../../utils/introspection/ERC165.sol"; diff --git a/contracts/token/ERC20/ERC20.sol b/contracts/token/ERC20/ERC20.sol index 6e8da2801..2646a0ddc 100644 --- a/contracts/token/ERC20/ERC20.sol +++ b/contracts/token/ERC20/ERC20.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/ERC20.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "./IERC20.sol"; import "./extensions/IERC20Metadata.sol"; diff --git a/contracts/token/ERC20/IERC20.sol b/contracts/token/ERC20/IERC20.sol index 6d5b4e9f1..a19535a30 100644 --- a/contracts/token/ERC20/IERC20.sol +++ b/contracts/token/ERC20/IERC20.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; /** * @dev Interface of the ERC20 standard as defined in the EIP. diff --git a/contracts/token/ERC20/extensions/ERC20Burnable.sol b/contracts/token/ERC20/extensions/ERC20Burnable.sol index 1cd08ee81..cae186b64 100644 --- a/contracts/token/ERC20/extensions/ERC20Burnable.sol +++ b/contracts/token/ERC20/extensions/ERC20Burnable.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.5.0) (token/ERC20/extensions/ERC20Burnable.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../ERC20.sol"; import "../../../utils/Context.sol"; diff --git a/contracts/token/ERC20/extensions/ERC20Capped.sol b/contracts/token/ERC20/extensions/ERC20Capped.sol index 3e2a9623e..cda072651 100644 --- a/contracts/token/ERC20/extensions/ERC20Capped.sol +++ b/contracts/token/ERC20/extensions/ERC20Capped.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/ERC20Capped.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../ERC20.sol"; diff --git a/contracts/token/ERC20/extensions/ERC20FlashMint.sol b/contracts/token/ERC20/extensions/ERC20FlashMint.sol index 063fe99fb..ce68793b5 100644 --- a/contracts/token/ERC20/extensions/ERC20FlashMint.sol +++ b/contracts/token/ERC20/extensions/ERC20FlashMint.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.8.0) (token/ERC20/extensions/ERC20FlashMint.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../../../interfaces/IERC3156FlashBorrower.sol"; import "../../../interfaces/IERC3156FlashLender.sol"; diff --git a/contracts/token/ERC20/extensions/ERC20Pausable.sol b/contracts/token/ERC20/extensions/ERC20Pausable.sol index 71302f978..b31cac7de 100644 --- a/contracts/token/ERC20/extensions/ERC20Pausable.sol +++ b/contracts/token/ERC20/extensions/ERC20Pausable.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/ERC20Pausable.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../ERC20.sol"; import "../../../security/Pausable.sol"; diff --git a/contracts/token/ERC20/extensions/ERC20Permit.sol b/contracts/token/ERC20/extensions/ERC20Permit.sol index 0f816d7bf..dbdc37053 100644 --- a/contracts/token/ERC20/extensions/ERC20Permit.sol +++ b/contracts/token/ERC20/extensions/ERC20Permit.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/ERC20Permit.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "./IERC20Permit.sol"; import "../ERC20.sol"; diff --git a/contracts/token/ERC20/extensions/ERC20Votes.sol b/contracts/token/ERC20/extensions/ERC20Votes.sol index e7cda4f35..c078878ec 100644 --- a/contracts/token/ERC20/extensions/ERC20Votes.sol +++ b/contracts/token/ERC20/extensions/ERC20Votes.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/ERC20Votes.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../ERC20.sol"; import "../../../governance/utils/Votes.sol"; diff --git a/contracts/token/ERC20/extensions/ERC20Wrapper.sol b/contracts/token/ERC20/extensions/ERC20Wrapper.sol index bc85a0b37..bf2b225cf 100644 --- a/contracts/token/ERC20/extensions/ERC20Wrapper.sol +++ b/contracts/token/ERC20/extensions/ERC20Wrapper.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/ERC20Wrapper.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../ERC20.sol"; import "../utils/SafeERC20.sol"; diff --git a/contracts/token/ERC20/extensions/ERC4626.sol b/contracts/token/ERC20/extensions/ERC4626.sol index 4f6a2d64e..a706b5457 100644 --- a/contracts/token/ERC20/extensions/ERC4626.sol +++ b/contracts/token/ERC20/extensions/ERC4626.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/ERC4626.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../ERC20.sol"; import "../utils/SafeERC20.sol"; diff --git a/contracts/token/ERC20/extensions/IERC20Metadata.sol b/contracts/token/ERC20/extensions/IERC20Metadata.sol index 83ba6ac5e..1cf7e0b2e 100644 --- a/contracts/token/ERC20/extensions/IERC20Metadata.sol +++ b/contracts/token/ERC20/extensions/IERC20Metadata.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../IERC20.sol"; diff --git a/contracts/token/ERC20/extensions/IERC20Permit.sol b/contracts/token/ERC20/extensions/IERC20Permit.sol index 0deb54b14..eb3e3f005 100644 --- a/contracts/token/ERC20/extensions/IERC20Permit.sol +++ b/contracts/token/ERC20/extensions/IERC20Permit.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/IERC20Permit.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; /** * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in diff --git a/contracts/token/ERC20/utils/SafeERC20.sol b/contracts/token/ERC20/utils/SafeERC20.sol index 7248134f2..751b82735 100644 --- a/contracts/token/ERC20/utils/SafeERC20.sol +++ b/contracts/token/ERC20/utils/SafeERC20.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/utils/SafeERC20.sol) -pragma solidity ^0.8.1; +pragma solidity ^0.8.19; import "../IERC20.sol"; import "../extensions/IERC20Permit.sol"; diff --git a/contracts/token/ERC721/ERC721.sol b/contracts/token/ERC721/ERC721.sol index d1d4a20dd..58a626cda 100644 --- a/contracts/token/ERC721/ERC721.sol +++ b/contracts/token/ERC721/ERC721.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC721/ERC721.sol) -pragma solidity ^0.8.1; +pragma solidity ^0.8.19; import "./IERC721.sol"; import "./IERC721Receiver.sol"; diff --git a/contracts/token/ERC721/IERC721.sol b/contracts/token/ERC721/IERC721.sol index 293f56683..ba4c108ef 100644 --- a/contracts/token/ERC721/IERC721.sol +++ b/contracts/token/ERC721/IERC721.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC721/IERC721.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../../utils/introspection/IERC165.sol"; diff --git a/contracts/token/ERC721/IERC721Receiver.sol b/contracts/token/ERC721/IERC721Receiver.sol index de672099e..3839b6f6c 100644 --- a/contracts/token/ERC721/IERC721Receiver.sol +++ b/contracts/token/ERC721/IERC721Receiver.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.6.0) (token/ERC721/IERC721Receiver.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; /** * @title ERC721 token receiver interface diff --git a/contracts/token/ERC721/extensions/ERC721Burnable.sol b/contracts/token/ERC721/extensions/ERC721Burnable.sol index 0dc7dae2c..5489169e8 100644 --- a/contracts/token/ERC721/extensions/ERC721Burnable.sol +++ b/contracts/token/ERC721/extensions/ERC721Burnable.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.8.0) (token/ERC721/extensions/ERC721Burnable.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../ERC721.sol"; import "../../../utils/Context.sol"; diff --git a/contracts/token/ERC721/extensions/ERC721Consecutive.sol b/contracts/token/ERC721/extensions/ERC721Consecutive.sol index 4191ad684..869d78d06 100644 --- a/contracts/token/ERC721/extensions/ERC721Consecutive.sol +++ b/contracts/token/ERC721/extensions/ERC721Consecutive.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC721/extensions/ERC721Consecutive.sol) -pragma solidity ^0.8.1; +pragma solidity ^0.8.19; import "../ERC721.sol"; import "../../../interfaces/IERC2309.sol"; diff --git a/contracts/token/ERC721/extensions/ERC721Enumerable.sol b/contracts/token/ERC721/extensions/ERC721Enumerable.sol index aab81a9f3..6b702d53b 100644 --- a/contracts/token/ERC721/extensions/ERC721Enumerable.sol +++ b/contracts/token/ERC721/extensions/ERC721Enumerable.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.8.0) (token/ERC721/extensions/ERC721Enumerable.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../ERC721.sol"; import "./IERC721Enumerable.sol"; diff --git a/contracts/token/ERC721/extensions/ERC721Pausable.sol b/contracts/token/ERC721/extensions/ERC721Pausable.sol index e8eb4b103..0cadaa7c7 100644 --- a/contracts/token/ERC721/extensions/ERC721Pausable.sol +++ b/contracts/token/ERC721/extensions/ERC721Pausable.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.8.2) (token/ERC721/extensions/ERC721Pausable.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../ERC721.sol"; import "../../../security/Pausable.sol"; diff --git a/contracts/token/ERC721/extensions/ERC721Royalty.sol b/contracts/token/ERC721/extensions/ERC721Royalty.sol index 298e34205..7d6ef6c04 100644 --- a/contracts/token/ERC721/extensions/ERC721Royalty.sol +++ b/contracts/token/ERC721/extensions/ERC721Royalty.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.8.0) (token/ERC721/extensions/ERC721Royalty.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../ERC721.sol"; import "../../common/ERC2981.sol"; diff --git a/contracts/token/ERC721/extensions/ERC721URIStorage.sol b/contracts/token/ERC721/extensions/ERC721URIStorage.sol index c9d79b6b2..6350a0952 100644 --- a/contracts/token/ERC721/extensions/ERC721URIStorage.sol +++ b/contracts/token/ERC721/extensions/ERC721URIStorage.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC721/extensions/ERC721URIStorage.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../ERC721.sol"; import "../../../interfaces/IERC4906.sol"; diff --git a/contracts/token/ERC721/extensions/ERC721Votes.sol b/contracts/token/ERC721/extensions/ERC721Votes.sol index 55b82c1c1..89b2e073c 100644 --- a/contracts/token/ERC721/extensions/ERC721Votes.sol +++ b/contracts/token/ERC721/extensions/ERC721Votes.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC721/extensions/ERC721Votes.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../ERC721.sol"; import "../../../governance/utils/Votes.sol"; diff --git a/contracts/token/ERC721/extensions/ERC721Wrapper.sol b/contracts/token/ERC721/extensions/ERC721Wrapper.sol index c7ff06dec..58346182b 100644 --- a/contracts/token/ERC721/extensions/ERC721Wrapper.sol +++ b/contracts/token/ERC721/extensions/ERC721Wrapper.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC721/extensions/ERC721Wrapper.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../ERC721.sol"; diff --git a/contracts/token/ERC721/extensions/IERC721Enumerable.sol b/contracts/token/ERC721/extensions/IERC721Enumerable.sol index dfea427ba..d5fe6330c 100644 --- a/contracts/token/ERC721/extensions/IERC721Enumerable.sol +++ b/contracts/token/ERC721/extensions/IERC721Enumerable.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.5.0) (token/ERC721/extensions/IERC721Enumerable.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../IERC721.sol"; diff --git a/contracts/token/ERC721/extensions/IERC721Metadata.sol b/contracts/token/ERC721/extensions/IERC721Metadata.sol index dca77ba5b..0b085812c 100644 --- a/contracts/token/ERC721/extensions/IERC721Metadata.sol +++ b/contracts/token/ERC721/extensions/IERC721Metadata.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (token/ERC721/extensions/IERC721Metadata.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../IERC721.sol"; diff --git a/contracts/token/ERC721/utils/ERC721Holder.sol b/contracts/token/ERC721/utils/ERC721Holder.sol index f18ad8a1d..41bee820c 100644 --- a/contracts/token/ERC721/utils/ERC721Holder.sol +++ b/contracts/token/ERC721/utils/ERC721Holder.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC721/utils/ERC721Holder.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../IERC721Receiver.sol"; diff --git a/contracts/token/common/ERC2981.sol b/contracts/token/common/ERC2981.sol index 7d47b6c7e..5d40388aa 100644 --- a/contracts/token/common/ERC2981.sol +++ b/contracts/token/common/ERC2981.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (token/common/ERC2981.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../../interfaces/IERC2981.sol"; import "../../utils/introspection/ERC165.sol"; diff --git a/contracts/utils/Address.sol b/contracts/utils/Address.sol index 8aada4b04..02f475620 100644 --- a/contracts/utils/Address.sol +++ b/contracts/utils/Address.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol) -pragma solidity ^0.8.1; +pragma solidity ^0.8.19; /** * @dev Collection of functions related to the address type diff --git a/contracts/utils/Arrays.sol b/contracts/utils/Arrays.sol index 66fe322b5..383a0820a 100644 --- a/contracts/utils/Arrays.sol +++ b/contracts/utils/Arrays.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (utils/Arrays.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "./StorageSlot.sol"; import "./math/Math.sol"; diff --git a/contracts/utils/Base64.sol b/contracts/utils/Base64.sol index 4e08cd563..9ba6defe0 100644 --- a/contracts/utils/Base64.sol +++ b/contracts/utils/Base64.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.7.0) (utils/Base64.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; /** * @dev Provides a set of functions to operate with Base64 strings. diff --git a/contracts/utils/Context.sol b/contracts/utils/Context.sol index f304065b4..2d517987d 100644 --- a/contracts/utils/Context.sol +++ b/contracts/utils/Context.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (utils/Context.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; /** * @dev Provides information about the current execution context, including the diff --git a/contracts/utils/Counters.sol b/contracts/utils/Counters.sol index 8a4f2a2e7..2c6951840 100644 --- a/contracts/utils/Counters.sol +++ b/contracts/utils/Counters.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (utils/Counters.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; /** * @title Counters diff --git a/contracts/utils/Create2.sol b/contracts/utils/Create2.sol index 29fa97329..d5776885f 100644 --- a/contracts/utils/Create2.sol +++ b/contracts/utils/Create2.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (utils/Create2.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; /** * @dev Helper to make usage of the `CREATE2` EVM opcode easier and safer. diff --git a/contracts/utils/Multicall.sol b/contracts/utils/Multicall.sol index 5729f8452..8e0ef8195 100644 --- a/contracts/utils/Multicall.sol +++ b/contracts/utils/Multicall.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (utils/Multicall.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "./Address.sol"; diff --git a/contracts/utils/Nonces.sol b/contracts/utils/Nonces.sol index ce65909ff..a908234ee 100644 --- a/contracts/utils/Nonces.sol +++ b/contracts/utils/Nonces.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "./Counters.sol"; diff --git a/contracts/utils/ShortStrings.sol b/contracts/utils/ShortStrings.sol index 90a054999..a6cb1e620 100644 --- a/contracts/utils/ShortStrings.sol +++ b/contracts/utils/ShortStrings.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (utils/ShortStrings.sol) -pragma solidity ^0.8.8; +pragma solidity ^0.8.19; import "./StorageSlot.sol"; diff --git a/contracts/utils/StorageSlot.sol b/contracts/utils/StorageSlot.sol index 5bba0b0f8..f12640e7b 100644 --- a/contracts/utils/StorageSlot.sol +++ b/contracts/utils/StorageSlot.sol @@ -2,7 +2,7 @@ // OpenZeppelin Contracts (last updated v4.9.0) (utils/StorageSlot.sol) // This file was procedurally generated from scripts/generate/templates/StorageSlot.js. -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; /** * @dev Library for reading and writing primitive types to specific storage slots. diff --git a/contracts/utils/Strings.sol b/contracts/utils/Strings.sol index 657ebd663..33d7bbf59 100644 --- a/contracts/utils/Strings.sol +++ b/contracts/utils/Strings.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (utils/Strings.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "./math/Math.sol"; import "./math/SignedMath.sol"; diff --git a/contracts/utils/cryptography/ECDSA.sol b/contracts/utils/cryptography/ECDSA.sol index f97922419..f12815d9d 100644 --- a/contracts/utils/cryptography/ECDSA.sol +++ b/contracts/utils/cryptography/ECDSA.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (utils/cryptography/ECDSA.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../Strings.sol"; diff --git a/contracts/utils/cryptography/EIP712.sol b/contracts/utils/cryptography/EIP712.sol index 2a0e73486..526b13580 100644 --- a/contracts/utils/cryptography/EIP712.sol +++ b/contracts/utils/cryptography/EIP712.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (utils/cryptography/EIP712.sol) -pragma solidity ^0.8.8; +pragma solidity ^0.8.19; import "./ECDSA.sol"; import "../ShortStrings.sol"; diff --git a/contracts/utils/cryptography/MerkleProof.sol b/contracts/utils/cryptography/MerkleProof.sol index cd79e51cf..0bcdda2cd 100644 --- a/contracts/utils/cryptography/MerkleProof.sol +++ b/contracts/utils/cryptography/MerkleProof.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (utils/cryptography/MerkleProof.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; /** * @dev These functions deal with verification of Merkle Tree proofs. diff --git a/contracts/utils/cryptography/SignatureChecker.sol b/contracts/utils/cryptography/SignatureChecker.sol index 1815d27fe..ba8f7cf36 100644 --- a/contracts/utils/cryptography/SignatureChecker.sol +++ b/contracts/utils/cryptography/SignatureChecker.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (utils/cryptography/SignatureChecker.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "./ECDSA.sol"; import "../../interfaces/IERC1271.sol"; diff --git a/contracts/utils/introspection/ERC165.sol b/contracts/utils/introspection/ERC165.sol index 49395f8a1..6849637e8 100644 --- a/contracts/utils/introspection/ERC165.sol +++ b/contracts/utils/introspection/ERC165.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "./IERC165.sol"; diff --git a/contracts/utils/introspection/ERC165Checker.sol b/contracts/utils/introspection/ERC165Checker.sol index 4831b6522..4dc84c163 100644 --- a/contracts/utils/introspection/ERC165Checker.sol +++ b/contracts/utils/introspection/ERC165Checker.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (utils/introspection/ERC165Checker.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "./IERC165.sol"; diff --git a/contracts/utils/introspection/IERC165.sol b/contracts/utils/introspection/IERC165.sol index e8cdbdbf6..da6f186d4 100644 --- a/contracts/utils/introspection/IERC165.sol +++ b/contracts/utils/introspection/IERC165.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; /** * @dev Interface of the ERC165 standard, as defined in the diff --git a/contracts/utils/math/Math.sol b/contracts/utils/math/Math.sol index d62ad97d9..ff3dc8d9f 100644 --- a/contracts/utils/math/Math.sol +++ b/contracts/utils/math/Math.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (utils/math/Math.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; /** * @dev Standard math utilities missing in the Solidity language. diff --git a/contracts/utils/math/SafeCast.sol b/contracts/utils/math/SafeCast.sol index 0744b3711..d9e21bb17 100644 --- a/contracts/utils/math/SafeCast.sol +++ b/contracts/utils/math/SafeCast.sol @@ -2,7 +2,7 @@ // OpenZeppelin Contracts (last updated v4.8.0) (utils/math/SafeCast.sol) // This file was procedurally generated from scripts/generate/templates/SafeCast.js. -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; /** * @dev Wrappers over Solidity's uintXX/intXX casting operators with added overflow diff --git a/contracts/utils/math/SignedMath.sol b/contracts/utils/math/SignedMath.sol index 3ea9f8bf0..80413531d 100644 --- a/contracts/utils/math/SignedMath.sol +++ b/contracts/utils/math/SignedMath.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.8.0) (utils/math/SignedMath.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; /** * @dev Standard signed math utilities missing in the Solidity language. diff --git a/contracts/utils/structs/BitMaps.sol b/contracts/utils/structs/BitMaps.sol index 2567fce47..9786c7f8e 100644 --- a/contracts/utils/structs/BitMaps.sol +++ b/contracts/utils/structs/BitMaps.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (utils/structs/BitMaps.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; /** * @dev Library for managing uint256 to bool mapping in a compact and efficient way, providing the keys are sequential. diff --git a/contracts/utils/structs/Checkpoints.sol b/contracts/utils/structs/Checkpoints.sol index f2ee45811..a0c45f659 100644 --- a/contracts/utils/structs/Checkpoints.sol +++ b/contracts/utils/structs/Checkpoints.sol @@ -2,7 +2,7 @@ // OpenZeppelin Contracts (last updated v4.9.0) (utils/structs/Checkpoints.sol) // This file was procedurally generated from scripts/generate/templates/Checkpoints.js. -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../math/Math.sol"; import "../math/SafeCast.sol"; diff --git a/contracts/utils/structs/DoubleEndedQueue.sol b/contracts/utils/structs/DoubleEndedQueue.sol index 325918d2d..be6e3898f 100644 --- a/contracts/utils/structs/DoubleEndedQueue.sol +++ b/contracts/utils/structs/DoubleEndedQueue.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (utils/structs/DoubleEndedQueue.sol) -pragma solidity ^0.8.4; +pragma solidity ^0.8.19; import "../math/SafeCast.sol"; diff --git a/contracts/utils/structs/EnumerableMap.sol b/contracts/utils/structs/EnumerableMap.sol index f34481426..4bd18055d 100644 --- a/contracts/utils/structs/EnumerableMap.sol +++ b/contracts/utils/structs/EnumerableMap.sol @@ -2,7 +2,7 @@ // OpenZeppelin Contracts (last updated v4.9.0) (utils/structs/EnumerableMap.sol) // This file was procedurally generated from scripts/generate/templates/EnumerableMap.js. -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "./EnumerableSet.sol"; diff --git a/contracts/utils/structs/EnumerableSet.sol b/contracts/utils/structs/EnumerableSet.sol index 447f96302..b10b3cdcd 100644 --- a/contracts/utils/structs/EnumerableSet.sol +++ b/contracts/utils/structs/EnumerableSet.sol @@ -2,7 +2,7 @@ // OpenZeppelin Contracts (last updated v4.9.0) (utils/structs/EnumerableSet.sol) // This file was procedurally generated from scripts/generate/templates/EnumerableSet.js. -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; /** * @dev Library for managing diff --git a/contracts/vendor/compound/ICompoundTimelock.sol b/contracts/vendor/compound/ICompoundTimelock.sol index fb33a6805..1b04290b4 100644 --- a/contracts/vendor/compound/ICompoundTimelock.sol +++ b/contracts/vendor/compound/ICompoundTimelock.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.6.0) (vendor/compound/ICompoundTimelock.sol) -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; /** * https://github.com/compound-finance/compound-protocol/blob/master/contracts/Timelock.sol[Compound's timelock] interface diff --git a/docs/modules/ROOT/pages/access-control.adoc b/docs/modules/ROOT/pages/access-control.adoc index e52c10401..11a5fab0c 100644 --- a/docs/modules/ROOT/pages/access-control.adoc +++ b/docs/modules/ROOT/pages/access-control.adoc @@ -13,7 +13,7 @@ OpenZeppelin Contracts provides xref:api:access.adoc#Ownable[`Ownable`] for impl ---- // contracts/MyContract.sol // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "@openzeppelin/contracts/access/Ownable.sol"; @@ -62,7 +62,7 @@ Here's a simple example of using `AccessControl` in an xref:tokens.adoc#ERC20[`E ---- // contracts/MyToken.sol // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "@openzeppelin/contracts/access/AccessControl.sol"; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; @@ -94,7 +94,7 @@ Let's augment our ERC20 token example by also defining a 'burner' role, which le ---- // contracts/MyToken.sol // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "@openzeppelin/contracts/access/AccessControl.sol"; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; @@ -139,7 +139,7 @@ Let's take a look at the ERC20 token example, this time taking advantage of the ---- // contracts/MyToken.sol // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "@openzeppelin/contracts/access/AccessControl.sol"; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; diff --git a/docs/modules/ROOT/pages/erc1155.adoc b/docs/modules/ROOT/pages/erc1155.adoc index d7976ca1f..067bde89e 100644 --- a/docs/modules/ROOT/pages/erc1155.adoc +++ b/docs/modules/ROOT/pages/erc1155.adoc @@ -34,7 +34,7 @@ Here's what a contract for tokenized items might look like: ---- // contracts/GameItems.sol // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; @@ -134,7 +134,7 @@ In order for our contract to receive ERC1155 tokens we can inherit from the conv ---- // contracts/MyContract.sol // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol"; diff --git a/docs/modules/ROOT/pages/erc20.adoc b/docs/modules/ROOT/pages/erc20.adoc index e72491ab6..817d364b1 100644 --- a/docs/modules/ROOT/pages/erc20.adoc +++ b/docs/modules/ROOT/pages/erc20.adoc @@ -15,7 +15,7 @@ Here's what our GLD token might look like. ---- // contracts/GLDToken.sol // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; diff --git a/docs/modules/ROOT/pages/erc721.adoc b/docs/modules/ROOT/pages/erc721.adoc index 812630ab3..695f44355 100644 --- a/docs/modules/ROOT/pages/erc721.adoc +++ b/docs/modules/ROOT/pages/erc721.adoc @@ -14,7 +14,7 @@ Here's what a contract for tokenized items might look like: ---- // contracts/GameItem.sol // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; import "@openzeppelin/contracts/utils/Counters.sol"; diff --git a/docs/modules/ROOT/pages/extending-contracts.adoc b/docs/modules/ROOT/pages/extending-contracts.adoc index d12ef7585..df93f2133 100644 --- a/docs/modules/ROOT/pages/extending-contracts.adoc +++ b/docs/modules/ROOT/pages/extending-contracts.adoc @@ -20,7 +20,7 @@ For example, imagine you want to change xref:api:access.adoc#AccessControl[`Acce ```solidity // contracts/ModifiedAccessControl.sol // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "@openzeppelin/contracts/access/AccessControl.sol"; @@ -48,7 +48,7 @@ Here is a modified version of xref:api:access.adoc#AccessControl[`AccessControl` ```solidity // contracts/ModifiedAccessControl.sol // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "@openzeppelin/contracts/access/AccessControl.sol"; @@ -80,7 +80,7 @@ Hooks are simply functions that are called before or after some action takes pla Here's how you would implement the `IERC721Receiver` pattern in `ERC20`, using the xref:api:token/ERC20.adoc#ERC20-_beforeTokenTransfer-address-address-uint256-[`_beforeTokenTransfer`] hook: ```solidity -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; diff --git a/docs/modules/ROOT/pages/governance.adoc b/docs/modules/ROOT/pages/governance.adoc index c5bcf58cd..a40275b07 100644 --- a/docs/modules/ROOT/pages/governance.adoc +++ b/docs/modules/ROOT/pages/governance.adoc @@ -44,7 +44,7 @@ The voting power of each account in our governance setup will be determined by a ```solidity // SPDX-License-Identifier: MIT -pragma solidity ^0.8.2; +pragma solidity ^0.8.19; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol"; @@ -82,7 +82,7 @@ If your project already has a live token that does not include ERC20Votes and is ```solidity // SPDX-License-Identifier: MIT -pragma solidity ^0.8.2; +pragma solidity ^0.8.19; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol"; @@ -147,7 +147,7 @@ We can optionally set a proposal threshold as well. This restricts proposal crea ```solidity // SPDX-License-Identifier: MIT -pragma solidity ^0.8.2; +pragma solidity ^0.8.19; import "@openzeppelin/contracts/governance/Governor.sol"; import "@openzeppelin/contracts/governance/compatibility/GovernorCompatibilityBravo.sol"; @@ -339,7 +339,7 @@ Since v4.9, all voting contracts (including xref:api:token/ERC20.adoc#ERC20Votes ```solidity // SPDX-License-Identifier: MIT -pragma solidity ^0.8.2; +pragma solidity ^0.8.19; import "github.com/openzeppelin/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; import "github.com/openzeppelin/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Permit.sol"; @@ -389,7 +389,7 @@ The Governor will automatically detect the clock mode used by the token and adap ```solidity // SPDX-License-Identifier: MIT -pragma solidity ^0.8.2; +pragma solidity ^0.8.19; import "@openzeppelin/contracts/governance/Governor.sol"; import "@openzeppelin/contracts/governance/compatibility/GovernorCompatibilityBravo.sol"; diff --git a/docs/modules/ROOT/pages/index.adoc b/docs/modules/ROOT/pages/index.adoc index 5b64f0508..b3b51abfc 100644 --- a/docs/modules/ROOT/pages/index.adoc +++ b/docs/modules/ROOT/pages/index.adoc @@ -26,7 +26,7 @@ Once installed, you can use the contracts in the library by importing them: ---- // contracts/MyNFT.sol // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; diff --git a/docs/modules/ROOT/pages/utilities.adoc b/docs/modules/ROOT/pages/utilities.adoc index eecb8293e..efe59efcd 100644 --- a/docs/modules/ROOT/pages/utilities.adoc +++ b/docs/modules/ROOT/pages/utilities.adoc @@ -156,7 +156,7 @@ Consider this dummy contract: ---- // contracts/Box.sol // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "@openzeppelin/contracts/utils/Multicall.sol"; diff --git a/hardhat.config.js b/hardhat.config.js index 17d2c47b2..66367e96e 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -40,7 +40,7 @@ const argv = require('yargs/yargs')() compiler: { alias: 'compileVersion', type: 'string', - default: '0.8.13', + default: '0.8.19', }, coinmarketcap: { alias: 'coinmarketcapApiKey', diff --git a/scripts/generate/templates/Checkpoints.js b/scripts/generate/templates/Checkpoints.js index f339fde22..c85cfcbb3 100644 --- a/scripts/generate/templates/Checkpoints.js +++ b/scripts/generate/templates/Checkpoints.js @@ -3,7 +3,7 @@ const { OPTS } = require('./Checkpoints.opts.js'); // TEMPLATE const header = `\ -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "../math/Math.sol"; import "../math/SafeCast.sol"; diff --git a/scripts/generate/templates/Checkpoints.t.js b/scripts/generate/templates/Checkpoints.t.js index 2b8c7b67c..c5b225f38 100644 --- a/scripts/generate/templates/Checkpoints.t.js +++ b/scripts/generate/templates/Checkpoints.t.js @@ -4,7 +4,7 @@ const { OPTS } = require('./Checkpoints.opts.js'); // TEMPLATE const header = `\ -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "forge-std/Test.sol"; import "../../../contracts/utils/math/SafeCast.sol"; diff --git a/scripts/generate/templates/EnumerableMap.js b/scripts/generate/templates/EnumerableMap.js index 61e23cfb4..13d3d8686 100644 --- a/scripts/generate/templates/EnumerableMap.js +++ b/scripts/generate/templates/EnumerableMap.js @@ -10,7 +10,7 @@ const TYPES = [ /* eslint-disable max-len */ const header = `\ -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "./EnumerableSet.sol"; diff --git a/scripts/generate/templates/EnumerableSet.js b/scripts/generate/templates/EnumerableSet.js index 2b6a9c64d..1edab9162 100644 --- a/scripts/generate/templates/EnumerableSet.js +++ b/scripts/generate/templates/EnumerableSet.js @@ -9,7 +9,7 @@ const TYPES = [ /* eslint-disable max-len */ const header = `\ -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; /** * @dev Library for managing diff --git a/scripts/generate/templates/SafeCast.js b/scripts/generate/templates/SafeCast.js index b47193be6..0d78a2ca3 100644 --- a/scripts/generate/templates/SafeCast.js +++ b/scripts/generate/templates/SafeCast.js @@ -61,7 +61,7 @@ const version = (selector, length) => { }; const header = `\ -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; /** * @dev Wrappers over Solidity's uintXX/intXX casting operators with added overflow diff --git a/scripts/generate/templates/StorageSlot.js b/scripts/generate/templates/StorageSlot.js index f985f2242..b51affc22 100644 --- a/scripts/generate/templates/StorageSlot.js +++ b/scripts/generate/templates/StorageSlot.js @@ -18,7 +18,7 @@ const VERSIONS = unique(TYPES.map(t => t.version)).map( ); const header = `\ -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; /** * @dev Library for reading and writing primitive types to specific storage slots. diff --git a/scripts/upgradeable/upgradeable.patch b/scripts/upgradeable/upgradeable.patch index efc89cab5..b29e7d12b 100644 --- a/scripts/upgradeable/upgradeable.patch +++ b/scripts/upgradeable/upgradeable.patch @@ -87,7 +87,7 @@ index 9fc95518..53130e3c 100644 @@ -35,10 +38,11 @@ Once installed, you can use the contracts in the library by importing them: ```solidity - pragma solidity ^0.8.0; + pragma solidity ^0.8.19; -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; +import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol"; @@ -190,7 +190,7 @@ index 6a4e1cad..55d8eced 100644 --- a/contracts/utils/cryptography/EIP712.sol +++ b/contracts/utils/cryptography/EIP712.sol @@ -4,7 +4,6 @@ - pragma solidity ^0.8.8; + pragma solidity ^0.8.19; import "./ECDSA.sol"; -import "../ShortStrings.sol"; diff --git a/test/token/ERC20/extensions/ERC4626.t.sol b/test/token/ERC20/extensions/ERC4626.t.sol index 956dd2ce0..da01c7d81 100644 --- a/test/token/ERC20/extensions/ERC4626.t.sol +++ b/test/token/ERC20/extensions/ERC4626.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import {ERC4626Test} from "erc4626-tests/ERC4626.test.sol"; diff --git a/test/token/ERC721/extensions/ERC721Consecutive.t.sol b/test/token/ERC721/extensions/ERC721Consecutive.t.sol index 3cc868929..6b70ab028 100644 --- a/test/token/ERC721/extensions/ERC721Consecutive.t.sol +++ b/test/token/ERC721/extensions/ERC721Consecutive.t.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; // solhint-disable func-name-mixedcase diff --git a/test/utils/ShortStrings.t.sol b/test/utils/ShortStrings.t.sol index 7c4faa89d..b70793bd7 100644 --- a/test/utils/ShortStrings.t.sol +++ b/test/utils/ShortStrings.t.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "forge-std/Test.sol"; diff --git a/test/utils/math/Math.t.sol b/test/utils/math/Math.t.sol index 9e7e118df..61f558e0e 100644 --- a/test/utils/math/Math.t.sol +++ b/test/utils/math/Math.t.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "forge-std/Test.sol"; diff --git a/test/utils/structs/Checkpoints.t.sol b/test/utils/structs/Checkpoints.t.sol index e3f4bd28b..1fb6dcb4e 100644 --- a/test/utils/structs/Checkpoints.t.sol +++ b/test/utils/structs/Checkpoints.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // This file was procedurally generated from scripts/generate/templates/Checkpoints.t.js. -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import "forge-std/Test.sol"; import "../../../contracts/utils/math/SafeCast.sol"; From 2ee1da12c4498fc130b7770905d0b4c4f91f6a9d Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Wed, 31 May 2023 16:40:28 +0200 Subject: [PATCH 077/182] Remove utils/Counters.sol (#4289) Co-authored-by: Francisco Giordano --- CHANGELOG.md | 3 +- contracts/mocks/proxy/UUPSUpgradeableMock.sol | 7 +- contracts/utils/Counters.sol | 43 ---------- contracts/utils/Nonces.sol | 19 ++--- contracts/utils/README.adoc | 3 - docs/modules/ROOT/pages/erc721.adoc | 13 ++- docs/modules/ROOT/pages/utilities.adoc | 2 - test/utils/Counters.test.js | 84 ------------------- test/utils/Nonces.test.js | 2 +- 9 files changed, 20 insertions(+), 156 deletions(-) delete mode 100644 contracts/utils/Counters.sol delete mode 100644 test/utils/Counters.test.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d2853cf8..2fc4cc5e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,9 @@ ### Removals -The following contracts were removed: +The following contracts and libraries were removed: +- `Counters` - `ERC20Snapshot` - `ERC20VotesComp` - `GovernorVotesComp` diff --git a/contracts/mocks/proxy/UUPSUpgradeableMock.sol b/contracts/mocks/proxy/UUPSUpgradeableMock.sol index 83e6ef465..567f3b536 100644 --- a/contracts/mocks/proxy/UUPSUpgradeableMock.sol +++ b/contracts/mocks/proxy/UUPSUpgradeableMock.sol @@ -3,17 +3,16 @@ pragma solidity ^0.8.19; import "../../proxy/utils/UUPSUpgradeable.sol"; -import "../../utils/Counters.sol"; contract NonUpgradeableMock { - Counters.Counter internal _counter; + uint256 internal _counter; function current() external view returns (uint256) { - return Counters.current(_counter); + return _counter; } function increment() external { - return Counters.increment(_counter); + ++_counter; } } diff --git a/contracts/utils/Counters.sol b/contracts/utils/Counters.sol deleted file mode 100644 index 2c6951840..000000000 --- a/contracts/utils/Counters.sol +++ /dev/null @@ -1,43 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts v4.4.1 (utils/Counters.sol) - -pragma solidity ^0.8.19; - -/** - * @title Counters - * @author Matt Condon (@shrugs) - * @dev Provides counters that can only be incremented, decremented or reset. This can be used e.g. to track the number - * of elements in a mapping, issuing ERC721 ids, or counting request ids. - * - * Include with `using Counters for Counters.Counter;` - */ -library Counters { - struct Counter { - // This variable should never be directly accessed by users of the library: interactions must be restricted to - // the library's function. As of Solidity v0.5.2, this cannot be enforced, though there is a proposal to add - // this feature: see https://github.com/ethereum/solidity/issues/4637 - uint256 _value; // default: 0 - } - - function current(Counter storage counter) internal view returns (uint256) { - return counter._value; - } - - function increment(Counter storage counter) internal { - unchecked { - counter._value += 1; - } - } - - function decrement(Counter storage counter) internal { - uint256 value = counter._value; - require(value > 0, "Counter: decrement overflow"); - unchecked { - counter._value = value - 1; - } - } - - function reset(Counter storage counter) internal { - counter._value = 0; - } -} diff --git a/contracts/utils/Nonces.sol b/contracts/utils/Nonces.sol index a908234ee..04b884797 100644 --- a/contracts/utils/Nonces.sol +++ b/contracts/utils/Nonces.sol @@ -1,21 +1,17 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; -import "./Counters.sol"; - /** * @dev Provides tracking nonces for addresses. Nonces will only increment. */ abstract contract Nonces { - using Counters for Counters.Counter; - - mapping(address => Counters.Counter) private _nonces; + mapping(address => uint256) private _nonces; /** * @dev Returns an address nonce. */ function nonces(address owner) public view virtual returns (uint256) { - return _nonces[owner].current(); + return _nonces[owner]; } /** @@ -23,9 +19,12 @@ abstract contract Nonces { * * Returns the current value and increments nonce. */ - function _useNonce(address owner) internal virtual returns (uint256 current) { - Counters.Counter storage nonce = _nonces[owner]; - current = nonce.current(); - nonce.increment(); + function _useNonce(address owner) internal virtual returns (uint256) { + // For each account, the nonce has an initial value of 0, can only be incremented by one, and cannot be + // decremented or reset. This guarantees that the nonce never overflows. + unchecked { + // It is important to do x++ and not ++x here. + return _nonces[owner]++; + } } } diff --git a/contracts/utils/README.adoc b/contracts/utils/README.adoc index 07cee6427..aa5182bd1 100644 --- a/contracts/utils/README.adoc +++ b/contracts/utils/README.adoc @@ -10,7 +10,6 @@ The {Address}, {Arrays}, {Base64} and {Strings} libraries provide more operation For new data types: - * {Counters}: a simple way to get a counter that can only be incremented, decremented or reset. Very useful for ID generation, counting contract activity, among others. * {EnumerableMap}: like Solidity's https://solidity.readthedocs.io/en/latest/types.html#mapping-types[`mapping`] type, but with key-value _enumeration_: this will let you know how many entries a mapping has, and iterate over them (which is not possible with `mapping`). * {EnumerableSet}: like {EnumerableMap}, but for https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets]. Can be used to store privileged accounts, issued IDs, etc. @@ -75,8 +74,6 @@ Ethereum contracts have no native concept of an interface, so applications must {{Base64}} -{{Counters}} - {{Strings}} {{ShortStrings}} diff --git a/docs/modules/ROOT/pages/erc721.adoc b/docs/modules/ROOT/pages/erc721.adoc index 695f44355..6d839c81e 100644 --- a/docs/modules/ROOT/pages/erc721.adoc +++ b/docs/modules/ROOT/pages/erc721.adoc @@ -17,11 +17,9 @@ Here's what a contract for tokenized items might look like: pragma solidity ^0.8.19; import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; -import "@openzeppelin/contracts/utils/Counters.sol"; contract GameItem is ERC721URIStorage { - using Counters for Counters.Counter; - Counters.Counter private _tokenIds; + uint256 private _nextTokenId; constructor() ERC721("GameItem", "ITM") {} @@ -29,12 +27,11 @@ contract GameItem is ERC721URIStorage { public returns (uint256) { - uint256 newItemId = _tokenIds.current(); - _mint(player, newItemId); - _setTokenURI(newItemId, tokenURI); + uint256 tokenId = _nextTokenId++; + _mint(player, tokenId); + _setTokenURI(tokenId, tokenURI); - _tokenIds.increment(); - return newItemId; + return tokenId; } } ---- diff --git a/docs/modules/ROOT/pages/utilities.adoc b/docs/modules/ROOT/pages/utilities.adoc index efe59efcd..0b178d9fe 100644 --- a/docs/modules/ROOT/pages/utilities.adoc +++ b/docs/modules/ROOT/pages/utilities.adoc @@ -97,8 +97,6 @@ If you need support for more powerful collections than Solidity's native arrays [[misc]] == Misc -Want to keep track of some numbers that increment by 1 every time you want another one? Check out xref:api:utils.adoc#Counters[`Counters`]. This is useful for lots of things, like creating incremental identifiers, as shown on the xref:erc721.adoc[ERC721 guide]. - === Base64 xref:api:utils.adoc#Base64[`Base64`] util allows you to transform `bytes32` data into its Base64 `string` representation. diff --git a/test/utils/Counters.test.js b/test/utils/Counters.test.js deleted file mode 100644 index 6fb89922d..000000000 --- a/test/utils/Counters.test.js +++ /dev/null @@ -1,84 +0,0 @@ -const { expectRevert } = require('@openzeppelin/test-helpers'); - -const { expect } = require('chai'); - -const Counters = artifacts.require('$Counters'); - -contract('Counters', function () { - beforeEach(async function () { - this.counter = await Counters.new(); - }); - - it('starts at zero', async function () { - expect(await this.counter.$current(0)).to.be.bignumber.equal('0'); - }); - - describe('increment', function () { - context('starting from 0', function () { - it('increments the current value by one', async function () { - await this.counter.$increment(0); - expect(await this.counter.$current(0)).to.be.bignumber.equal('1'); - }); - - it('can be called multiple times', async function () { - await this.counter.$increment(0); - await this.counter.$increment(0); - await this.counter.$increment(0); - - expect(await this.counter.$current(0)).to.be.bignumber.equal('3'); - }); - }); - }); - - describe('decrement', function () { - beforeEach(async function () { - await this.counter.$increment(0); - expect(await this.counter.$current(0)).to.be.bignumber.equal('1'); - }); - context('starting from 1', function () { - it('decrements the current value by one', async function () { - await this.counter.$decrement(0); - expect(await this.counter.$current(0)).to.be.bignumber.equal('0'); - }); - - it('reverts if the current value is 0', async function () { - await this.counter.$decrement(0); - await expectRevert(this.counter.$decrement(0), 'Counter: decrement overflow'); - }); - }); - context('after incremented to 3', function () { - it('can be called multiple times', async function () { - await this.counter.$increment(0); - await this.counter.$increment(0); - - expect(await this.counter.$current(0)).to.be.bignumber.equal('3'); - - await this.counter.$decrement(0); - await this.counter.$decrement(0); - await this.counter.$decrement(0); - - expect(await this.counter.$current(0)).to.be.bignumber.equal('0'); - }); - }); - }); - - describe('reset', function () { - context('null counter', function () { - it('does not throw', async function () { - await this.counter.$reset(0); - expect(await this.counter.$current(0)).to.be.bignumber.equal('0'); - }); - }); - - context('non null counter', function () { - beforeEach(async function () { - await this.counter.$increment(0); - expect(await this.counter.$current(0)).to.be.bignumber.equal('1'); - }); - it('reset to 0', async function () { - await this.counter.$reset(0); - expect(await this.counter.$current(0)).to.be.bignumber.equal('0'); - }); - }); - }); -}); diff --git a/test/utils/Nonces.test.js b/test/utils/Nonces.test.js index a09cddb1f..4a01bb1bc 100644 --- a/test/utils/Nonces.test.js +++ b/test/utils/Nonces.test.js @@ -19,7 +19,7 @@ contract('Nonces', function (accounts) { expect(await this.nonces.nonces(sender)).to.be.bignumber.equal('0'); const { receipt } = await this.nonces.$_useNonce(sender); - expectEvent(receipt, 'return$_useNonce', { current: '0' }); + expectEvent(receipt, 'return$_useNonce', ['0']); expect(await this.nonces.nonces(sender)).to.be.bignumber.equal('1'); }); From 30256fa8384cd8e95de7413454bb74c913ed9f3f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 1 Jun 2023 20:15:51 -0300 Subject: [PATCH 078/182] Update lockfile (#4294) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package-lock.json | 2543 ++++++++++----------------------------------- 1 file changed, 540 insertions(+), 2003 deletions(-) diff --git a/package-lock.json b/package-lock.json index ddfe359ef..edf1e9284 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "openzeppelin-solidity", - "version": "4.8.2", + "version": "4.9.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "openzeppelin-solidity", - "version": "4.8.2", + "version": "4.9.0", "license": "MIT", "devDependencies": { "@changesets/changelog-github": "^0.4.8", @@ -87,9 +87,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.21.5", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.5.tgz", - "integrity": "sha512-8jI69toZqqcsnqGGqwGS4Qb1VwLOEp4hz+CXPywcvjs60u3B4Pom/U/7rm4W8tMOYEB+E9wgD0mW1l3r8qlI9Q==", + "version": "7.22.3", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.3.tgz", + "integrity": "sha512-XsDuspWKLUsxwCp6r7EhsExHtYfbe5oAGQ19kqngTdCPUoPQzOPdUbD/pB9PJiwb2ptYKQDjSJT3R6dC+EPqfQ==", "dev": true, "dependencies": { "regenerator-runtime": "^0.13.11" @@ -549,14 +549,14 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.2.tgz", - "integrity": "sha512-3W4f5tDUra+pA+FzgugqL2pRimUTDJWKr7BINqOpkZrC0uYI0NIc0/JFgBROCU07HR6GieA5m3/rsPIhDmCXTQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.3.tgz", + "integrity": "sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ==", "dev": true, "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.5.1", + "espree": "^9.5.2", "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", @@ -590,9 +590,9 @@ } }, "node_modules/@eslint/js": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.39.0.tgz", - "integrity": "sha512-kf9RB0Fg7NZfap83B3QOqOGg9QmD9yBudqQXzzOtn3i4y7ZUXe5ONeW34Gwi+TxhH4mvj72R1Zc300KUMa9Bng==", + "version": "8.41.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.41.0.tgz", + "integrity": "sha512-LxcyMGxwmTh2lY9FwHPGWOHmYFCZvbrFCBZL4FzSSsxsRPuhrYUg/49/0KDfW8tnIEaEHtfmn6+NPN+1DqaNmA==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -2257,9 +2257,9 @@ } }, "node_modules/@openzeppelin/upgrades-core": { - "version": "1.25.0", - "resolved": "https://registry.npmjs.org/@openzeppelin/upgrades-core/-/upgrades-core-1.25.0.tgz", - "integrity": "sha512-vSxOSm1k+P156nNm15ydhOmSPGC37mnl092FMVOH+eGaoqLjr2Za6ULVjDMFzvMnG+sGE1UoDOqBNPfTm0ch8w==", + "version": "1.26.2", + "resolved": "https://registry.npmjs.org/@openzeppelin/upgrades-core/-/upgrades-core-1.26.2.tgz", + "integrity": "sha512-TJORrgyun5qflPos/47P3j61gDw+7W+tEirSBOYRxfVL1WGjX1n8iaLrijPIqzyeS1MKguN1nckAMspQ4SKrxw==", "dev": true, "dependencies": { "cbor": "^8.0.0", @@ -2522,38 +2522,14 @@ } }, "node_modules/@truffle/abi-utils": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@truffle/abi-utils/-/abi-utils-0.3.9.tgz", - "integrity": "sha512-G5dqgwRHx5zwlXjz3QT8OJVfB2cOqWwD6DwKso0KttUt/zejhCjnkKq72rSgyeLMkz7wBB9ERLOsupLBILM8MA==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@truffle/abi-utils/-/abi-utils-1.0.0.tgz", + "integrity": "sha512-h1wGFB28YfByAWm/uBeMCwqDlGsrcMYTumLC/sB/qYhHisi1LK6tV47FEF7zKyf6Al2CtsO28v02+wfLXbUVRg==", "dev": true, "dependencies": { "change-case": "3.0.2", "fast-check": "3.1.1", - "web3-utils": "1.8.2" - } - }, - "node_modules/@truffle/abi-utils/node_modules/bn.js": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", - "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", - "dev": true - }, - "node_modules/@truffle/abi-utils/node_modules/web3-utils": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.8.2.tgz", - "integrity": "sha512-v7j6xhfLQfY7xQDrUP0BKbaNrmZ2/+egbqP9q3KYmOiPpnvAfol+32slgL0WX/5n8VPvKCK5EZ1HGrAVICSToA==", - "dev": true, - "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" - }, - "engines": { - "node": ">=8.0.0" + "web3-utils": "1.10.0" } }, "node_modules/@truffle/blockchain-utils": { @@ -2563,13 +2539,13 @@ "dev": true }, "node_modules/@truffle/codec": { - "version": "0.14.17", - "resolved": "https://registry.npmjs.org/@truffle/codec/-/codec-0.14.17.tgz", - "integrity": "sha512-kD4dD86huLeaBEq5R8D1zleJEu6NsXbyYLdXl1V1TKdiO8odw5CBC6Y/+wdu5d3t1dyEYrTbhn1dqknZa52pmw==", + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/@truffle/codec/-/codec-0.15.1.tgz", + "integrity": "sha512-OBANcmefxEXLApWl/uU1SOHQJixO8pDaRTybP0YMvxPhgyj7G7+wC+fUvnZdmrTJD2WJFLuoYvZbxILmycEvPg==", "dev": true, "dependencies": { - "@truffle/abi-utils": "^0.3.9", - "@truffle/compile-common": "^0.9.4", + "@truffle/abi-utils": "^1.0.0", + "@truffle/compile-common": "^0.9.5", "big.js": "^6.0.3", "bn.js": "^5.1.3", "cbor": "^5.2.0", @@ -2577,7 +2553,7 @@ "lodash": "^4.17.21", "semver": "7.3.7", "utf8": "^3.0.0", - "web3-utils": "1.8.2" + "web3-utils": "1.10.0" } }, "node_modules/@truffle/codec/node_modules/bignumber.js": { @@ -2644,24 +2620,6 @@ "node": ">=10" } }, - "node_modules/@truffle/codec/node_modules/web3-utils": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.8.2.tgz", - "integrity": "sha512-v7j6xhfLQfY7xQDrUP0BKbaNrmZ2/+egbqP9q3KYmOiPpnvAfol+32slgL0WX/5n8VPvKCK5EZ1HGrAVICSToA==", - "dev": true, - "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" - }, - "engines": { - "node": ">=8.0.0" - } - }, "node_modules/@truffle/codec/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", @@ -2669,9 +2627,9 @@ "dev": true }, "node_modules/@truffle/compile-common": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/@truffle/compile-common/-/compile-common-0.9.4.tgz", - "integrity": "sha512-mnqJB/hLiPHNf+WKwt/2MH6lv34xSG/SFCib7+ckAklutUqVLeFo8EwQxinuHNkU7LY0C+YgZXhK1WTCO5YRJQ==", + "version": "0.9.5", + "resolved": "https://registry.npmjs.org/@truffle/compile-common/-/compile-common-0.9.5.tgz", + "integrity": "sha512-qOIT7nYzQsrnpjk8LurKE6EYYvvJIk3rCHfn+xed88aG6F1l4WYtkUKl+Dcwgxgv3LH0khcQOpjqkXK5kUIJ8A==", "dev": true, "dependencies": { "@truffle/error": "^0.2.0", @@ -2685,31 +2643,31 @@ "dev": true }, "node_modules/@truffle/contract": { - "version": "4.6.20", - "resolved": "https://registry.npmjs.org/@truffle/contract/-/contract-4.6.20.tgz", - "integrity": "sha512-s7Mbc37L/CF5Apy/cjPnalkgACmG9tTAmcIW28cIZLRLOUAze18pqhtdHryxAQhEOtKGaDAho6TriqL7/74uHw==", + "version": "4.6.22", + "resolved": "https://registry.npmjs.org/@truffle/contract/-/contract-4.6.22.tgz", + "integrity": "sha512-081tM5CBBLgTQX0Fhzp0nlZHnfgojRXweV7/d6v7LHe6QGrGBmgvUy3EIbO+R3P1uaxeGVijMvB4Ok8md9IpYQ==", "dev": true, "dependencies": { "@ensdomains/ensjs": "^2.1.0", "@truffle/blockchain-utils": "^0.1.7", - "@truffle/contract-schema": "^3.4.13", - "@truffle/debug-utils": "^6.0.48", + "@truffle/contract-schema": "^3.4.14", + "@truffle/debug-utils": "^6.0.50", "@truffle/error": "^0.2.0", - "@truffle/interface-adapter": "^0.5.32", + "@truffle/interface-adapter": "^0.5.33", "bignumber.js": "^7.2.1", "debug": "^4.3.1", "ethers": "^4.0.32", - "web3": "1.8.2", - "web3-core-helpers": "1.8.2", - "web3-core-promievent": "1.8.2", - "web3-eth-abi": "1.8.2", - "web3-utils": "1.8.2" + "web3": "1.10.0", + "web3-core-helpers": "1.10.0", + "web3-core-promievent": "1.10.0", + "web3-eth-abi": "1.10.0", + "web3-utils": "1.10.0" } }, "node_modules/@truffle/contract-schema": { - "version": "3.4.13", - "resolved": "https://registry.npmjs.org/@truffle/contract-schema/-/contract-schema-3.4.13.tgz", - "integrity": "sha512-emG7upuryYFrsPDbHqeASPWXL824M1tinhQwSPG0phSoa3g+RX9fUNNN/VPmF3tSkXLWUMhRnb7ehxnaCuRbZg==", + "version": "3.4.14", + "resolved": "https://registry.npmjs.org/@truffle/contract-schema/-/contract-schema-3.4.14.tgz", + "integrity": "sha512-IwVQZG9RVNwTdn321+jbFIcky3/kZLkCtq8tqil4jZwivvmZQg8rIVC8GJ7Lkrmixl9/yTyQNL6GtIUUvkZxyA==", "dev": true, "dependencies": { "ajv": "^6.10.0", @@ -2722,391 +2680,13 @@ "integrity": "sha512-Fe0/z4WWb7IP2gBnv3l6zqP87Y0kSMs7oiSLakKJq17q3GUunrHSdioKuNspdggxkXIBhEQLhi8C+LJdwmHKWQ==", "dev": true }, - "node_modules/@truffle/contract/node_modules/eth-lib": { - "version": "0.2.8", - "resolved": "https://registry.npmjs.org/eth-lib/-/eth-lib-0.2.8.tgz", - "integrity": "sha512-ArJ7x1WcWOlSpzdoTBX8vkwlkSQ85CjjifSZtV4co64vWxSV8geWfPI9x4SVYu3DSxnX4yWFVTtGL+j9DUFLNw==", - "dev": true, - "dependencies": { - "bn.js": "^4.11.6", - "elliptic": "^6.4.0", - "xhr-request-promise": "^0.1.2" - } - }, - "node_modules/@truffle/contract/node_modules/uuid": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", - "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", - "dev": true, - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/@truffle/contract/node_modules/web3": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3/-/web3-1.8.2.tgz", - "integrity": "sha512-92h0GdEHW9wqDICQQKyG4foZBYi0OQkyg4CRml2F7XBl/NG+fu9o6J19kzfFXzSBoA4DnJXbyRgj/RHZv5LRiw==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "web3-bzz": "1.8.2", - "web3-core": "1.8.2", - "web3-eth": "1.8.2", - "web3-eth-personal": "1.8.2", - "web3-net": "1.8.2", - "web3-shh": "1.8.2", - "web3-utils": "1.8.2" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@truffle/contract/node_modules/web3-bzz": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-bzz/-/web3-bzz-1.8.2.tgz", - "integrity": "sha512-1EEnxjPnFnvNWw3XeeKuTR8PBxYd0+XWzvaLK7OJC/Go9O8llLGxrxICbKV+8cgIE0sDRBxiYx02X+6OhoAQ9w==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "@types/node": "^12.12.6", - "got": "12.1.0", - "swarm-js": "^0.1.40" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@truffle/contract/node_modules/web3-core": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-core/-/web3-core-1.8.2.tgz", - "integrity": "sha512-DJTVEAYcNqxkqruJE+Rxp3CIv0y5AZMwPHQmOkz/cz+MM75SIzMTc0AUdXzGyTS8xMF8h3YWMQGgGEy8SBf1PQ==", - "dev": true, - "dependencies": { - "@types/bn.js": "^5.1.0", - "@types/node": "^12.12.6", - "bignumber.js": "^9.0.0", - "web3-core-helpers": "1.8.2", - "web3-core-method": "1.8.2", - "web3-core-requestmanager": "1.8.2", - "web3-utils": "1.8.2" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@truffle/contract/node_modules/web3-core-helpers": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-core-helpers/-/web3-core-helpers-1.8.2.tgz", - "integrity": "sha512-6B1eLlq9JFrfealZBomd1fmlq1o4A09vrCVQSa51ANoib/jllT3atZrRDr0zt1rfI7TSZTZBXdN/aTdeN99DWw==", - "dev": true, - "dependencies": { - "web3-eth-iban": "1.8.2", - "web3-utils": "1.8.2" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@truffle/contract/node_modules/web3-core-method": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-core-method/-/web3-core-method-1.8.2.tgz", - "integrity": "sha512-1qnr5mw5wVyULzLOrk4B+ryO3gfGjGd/fx8NR+J2xCGLf1e6OSjxT9vbfuQ3fErk/NjSTWWreieYWLMhaogcRA==", - "dev": true, - "dependencies": { - "@ethersproject/transactions": "^5.6.2", - "web3-core-helpers": "1.8.2", - "web3-core-promievent": "1.8.2", - "web3-core-subscriptions": "1.8.2", - "web3-utils": "1.8.2" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@truffle/contract/node_modules/web3-core-promievent": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-core-promievent/-/web3-core-promievent-1.8.2.tgz", - "integrity": "sha512-nvkJWDVgoOSsolJldN33tKW6bKKRJX3MCPDYMwP5SUFOA/mCzDEoI88N0JFofDTXkh1k7gOqp1pvwi9heuaxGg==", - "dev": true, - "dependencies": { - "eventemitter3": "4.0.4" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@truffle/contract/node_modules/web3-core-requestmanager": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-core-requestmanager/-/web3-core-requestmanager-1.8.2.tgz", - "integrity": "sha512-p1d090RYs5Mu7DK1yyc3GCBVZB/03rBtFhYFoS2EruGzOWs/5Q0grgtpwS/DScdRAm8wB8mYEBhY/RKJWF6B2g==", - "dev": true, - "dependencies": { - "util": "^0.12.5", - "web3-core-helpers": "1.8.2", - "web3-providers-http": "1.8.2", - "web3-providers-ipc": "1.8.2", - "web3-providers-ws": "1.8.2" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@truffle/contract/node_modules/web3-core-subscriptions": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-core-subscriptions/-/web3-core-subscriptions-1.8.2.tgz", - "integrity": "sha512-vXQogHDmAIQcKpXvGiMddBUeP9lnKgYF64+yQJhPNE5PnWr1sAibXuIPV7mIPihpFr/n/DORRj6Wh1pUv9zaTw==", - "dev": true, - "dependencies": { - "eventemitter3": "4.0.4", - "web3-core-helpers": "1.8.2" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@truffle/contract/node_modules/web3-core/node_modules/bignumber.js": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.1.tgz", - "integrity": "sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/@truffle/contract/node_modules/web3-eth": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-eth/-/web3-eth-1.8.2.tgz", - "integrity": "sha512-JoTiWWc4F4TInpbvDUGb0WgDYJsFhuIjJlinc5ByjWD88Gvh+GKLsRjjFdbqe5YtwIGT4NymwoC5LQd1K6u/QQ==", - "dev": true, - "dependencies": { - "web3-core": "1.8.2", - "web3-core-helpers": "1.8.2", - "web3-core-method": "1.8.2", - "web3-core-subscriptions": "1.8.2", - "web3-eth-abi": "1.8.2", - "web3-eth-accounts": "1.8.2", - "web3-eth-contract": "1.8.2", - "web3-eth-ens": "1.8.2", - "web3-eth-iban": "1.8.2", - "web3-eth-personal": "1.8.2", - "web3-net": "1.8.2", - "web3-utils": "1.8.2" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@truffle/contract/node_modules/web3-eth-abi": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-eth-abi/-/web3-eth-abi-1.8.2.tgz", - "integrity": "sha512-Om9g3kaRNjqiNPAgKwGT16y+ZwtBzRe4ZJFGjLiSs6v5I7TPNF+rRMWuKnR6jq0azQZDj6rblvKFMA49/k48Og==", - "dev": true, - "dependencies": { - "@ethersproject/abi": "^5.6.3", - "web3-utils": "1.8.2" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@truffle/contract/node_modules/web3-eth-accounts": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-eth-accounts/-/web3-eth-accounts-1.8.2.tgz", - "integrity": "sha512-c367Ij63VCz9YdyjiHHWLFtN85l6QghgwMQH2B1eM/p9Y5lTlTX7t/Eg/8+f1yoIStXbk2w/PYM2lk+IkbqdLA==", - "dev": true, - "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.8.2", - "web3-core-helpers": "1.8.2", - "web3-core-method": "1.8.2", - "web3-utils": "1.8.2" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@truffle/contract/node_modules/web3-eth-contract": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-eth-contract/-/web3-eth-contract-1.8.2.tgz", - "integrity": "sha512-ID5A25tHTSBNwOPjiXSVzxruz006ULRIDbzWTYIFTp7NJ7vXu/kynKK2ag/ObuTqBpMbobP8nXcA9b5EDkIdQA==", - "dev": true, - "dependencies": { - "@types/bn.js": "^5.1.0", - "web3-core": "1.8.2", - "web3-core-helpers": "1.8.2", - "web3-core-method": "1.8.2", - "web3-core-promievent": "1.8.2", - "web3-core-subscriptions": "1.8.2", - "web3-eth-abi": "1.8.2", - "web3-utils": "1.8.2" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@truffle/contract/node_modules/web3-eth-ens": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-eth-ens/-/web3-eth-ens-1.8.2.tgz", - "integrity": "sha512-PWph7C/CnqdWuu1+SH4U4zdrK4t2HNt0I4XzPYFdv9ugE8EuojselioPQXsVGvjql+Nt3jDLvQvggPqlMbvwRw==", - "dev": true, - "dependencies": { - "content-hash": "^2.5.2", - "eth-ens-namehash": "2.0.8", - "web3-core": "1.8.2", - "web3-core-helpers": "1.8.2", - "web3-core-promievent": "1.8.2", - "web3-eth-abi": "1.8.2", - "web3-eth-contract": "1.8.2", - "web3-utils": "1.8.2" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@truffle/contract/node_modules/web3-eth-iban": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-eth-iban/-/web3-eth-iban-1.8.2.tgz", - "integrity": "sha512-h3vNblDWkWMuYx93Q27TAJz6lhzpP93EiC3+45D6xoz983p6si773vntoQ+H+5aZhwglBtoiBzdh7PSSOnP/xQ==", - "dev": true, - "dependencies": { - "bn.js": "^5.2.1", - "web3-utils": "1.8.2" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@truffle/contract/node_modules/web3-eth-iban/node_modules/bn.js": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", - "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", - "dev": true - }, - "node_modules/@truffle/contract/node_modules/web3-eth-personal": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-eth-personal/-/web3-eth-personal-1.8.2.tgz", - "integrity": "sha512-Vg4HfwCr7doiUF/RC+Jz0wT4+cYaXcOWMAW2AHIjHX6Z7Xwa8nrURIeQgeEE62qcEHAzajyAdB1u6bJyTfuCXw==", - "dev": true, - "dependencies": { - "@types/node": "^12.12.6", - "web3-core": "1.8.2", - "web3-core-helpers": "1.8.2", - "web3-core-method": "1.8.2", - "web3-net": "1.8.2", - "web3-utils": "1.8.2" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@truffle/contract/node_modules/web3-net": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-net/-/web3-net-1.8.2.tgz", - "integrity": "sha512-1itkDMGmbgb83Dg9nporFes9/fxsU7smJ3oRXlFkg4ZHn8YJyP1MSQFPJWWwSc+GrcCFt4O5IrUTvEkHqE3xag==", - "dev": true, - "dependencies": { - "web3-core": "1.8.2", - "web3-core-method": "1.8.2", - "web3-utils": "1.8.2" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@truffle/contract/node_modules/web3-providers-http": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-providers-http/-/web3-providers-http-1.8.2.tgz", - "integrity": "sha512-2xY94IIEQd16+b+vIBF4IC1p7GVaz9q4EUFscvMUjtEq4ru4Atdzjs9GP+jmcoo49p70II0UV3bqQcz0TQfVyQ==", - "dev": true, - "dependencies": { - "abortcontroller-polyfill": "^1.7.3", - "cross-fetch": "^3.1.4", - "es6-promise": "^4.2.8", - "web3-core-helpers": "1.8.2" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@truffle/contract/node_modules/web3-providers-ipc": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-providers-ipc/-/web3-providers-ipc-1.8.2.tgz", - "integrity": "sha512-p6fqKVGFg+WiXGHWnB1hu43PbvPkDHTz4RgoEzbXugv5rtv5zfYLqm8Ba6lrJOS5ks9kGKR21a0y3NzE3u7V4w==", - "dev": true, - "dependencies": { - "oboe": "2.1.5", - "web3-core-helpers": "1.8.2" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@truffle/contract/node_modules/web3-providers-ws": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-providers-ws/-/web3-providers-ws-1.8.2.tgz", - "integrity": "sha512-3s/4K+wHgbiN+Zrp9YjMq2eqAF6QGABw7wFftPdx+m5hWImV27/MoIx57c6HffNRqZXmCHnfWWFCNHHsi7wXnA==", - "dev": true, - "dependencies": { - "eventemitter3": "4.0.4", - "web3-core-helpers": "1.8.2", - "websocket": "^1.0.32" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@truffle/contract/node_modules/web3-shh": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-shh/-/web3-shh-1.8.2.tgz", - "integrity": "sha512-uZ+3MAoNcaJsXXNCDnizKJ5viBNeHOFYsCbFhV755Uu52FswzTOw6DtE7yK9nYXMtIhiSgi7nwl1RYzP8pystw==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "web3-core": "1.8.2", - "web3-core-method": "1.8.2", - "web3-core-subscriptions": "1.8.2", - "web3-net": "1.8.2" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@truffle/contract/node_modules/web3-utils": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.8.2.tgz", - "integrity": "sha512-v7j6xhfLQfY7xQDrUP0BKbaNrmZ2/+egbqP9q3KYmOiPpnvAfol+32slgL0WX/5n8VPvKCK5EZ1HGrAVICSToA==", - "dev": true, - "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" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@truffle/contract/node_modules/web3-utils/node_modules/bn.js": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", - "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", - "dev": true - }, "node_modules/@truffle/debug-utils": { - "version": "6.0.48", - "resolved": "https://registry.npmjs.org/@truffle/debug-utils/-/debug-utils-6.0.48.tgz", - "integrity": "sha512-HdK/7eH5EFrcTPeZVEgKaKkkzuZ4xsrH8yw+EoLEsScLsOEuQeKynY61NctjuU93voATWrYmV99Sfb/MRq2i2g==", + "version": "6.0.50", + "resolved": "https://registry.npmjs.org/@truffle/debug-utils/-/debug-utils-6.0.50.tgz", + "integrity": "sha512-OWdSoOsPW7/jvcO7ASBRzXDzXQNb7dg8UqwoBAI7j7UpdQoCAhz7JQsusSNiFN6g1qrxEpGzeh5iIMDq9WxO3w==", "dev": true, "dependencies": { - "@truffle/codec": "^0.14.17", + "@truffle/codec": "^0.15.1", "@trufflesuite/chromafi": "^3.0.0", "bn.js": "^5.1.3", "chalk": "^2.4.2", @@ -3127,23 +2707,14 @@ "dev": true }, "node_modules/@truffle/interface-adapter": { - "version": "0.5.32", - "resolved": "https://registry.npmjs.org/@truffle/interface-adapter/-/interface-adapter-0.5.32.tgz", - "integrity": "sha512-7Hgmdb4nJUcWtq4vvgWY7Mr2RLOTOp5FZaWyMiFmjkcEEhDlezm2QstALWAXgT0W6q7tDmDBpw3vTIFenRhHBA==", + "version": "0.5.33", + "resolved": "https://registry.npmjs.org/@truffle/interface-adapter/-/interface-adapter-0.5.33.tgz", + "integrity": "sha512-vbVcH2I8hX+wM0Xj9uAjpgxMHqfT+y6m26zSkOVvZ2wo9Ez1slaOJkK1/TZK+7nJitGZSXeJeB4purMDuADvGA==", "dev": true, "dependencies": { "bn.js": "^5.1.3", "ethers": "^4.0.32", - "web3": "1.8.2" - } - }, - "node_modules/@truffle/interface-adapter/node_modules/bignumber.js": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.1.tgz", - "integrity": "sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig==", - "dev": true, - "engines": { - "node": "*" + "web3": "1.10.0" } }, "node_modules/@truffle/interface-adapter/node_modules/bn.js": { @@ -3152,369 +2723,6 @@ "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", "dev": true }, - "node_modules/@truffle/interface-adapter/node_modules/eth-lib": { - "version": "0.2.8", - "resolved": "https://registry.npmjs.org/eth-lib/-/eth-lib-0.2.8.tgz", - "integrity": "sha512-ArJ7x1WcWOlSpzdoTBX8vkwlkSQ85CjjifSZtV4co64vWxSV8geWfPI9x4SVYu3DSxnX4yWFVTtGL+j9DUFLNw==", - "dev": true, - "dependencies": { - "bn.js": "^4.11.6", - "elliptic": "^6.4.0", - "xhr-request-promise": "^0.1.2" - } - }, - "node_modules/@truffle/interface-adapter/node_modules/eth-lib/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - }, - "node_modules/@truffle/interface-adapter/node_modules/uuid": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", - "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", - "dev": true, - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/@truffle/interface-adapter/node_modules/web3": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3/-/web3-1.8.2.tgz", - "integrity": "sha512-92h0GdEHW9wqDICQQKyG4foZBYi0OQkyg4CRml2F7XBl/NG+fu9o6J19kzfFXzSBoA4DnJXbyRgj/RHZv5LRiw==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "web3-bzz": "1.8.2", - "web3-core": "1.8.2", - "web3-eth": "1.8.2", - "web3-eth-personal": "1.8.2", - "web3-net": "1.8.2", - "web3-shh": "1.8.2", - "web3-utils": "1.8.2" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@truffle/interface-adapter/node_modules/web3-bzz": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-bzz/-/web3-bzz-1.8.2.tgz", - "integrity": "sha512-1EEnxjPnFnvNWw3XeeKuTR8PBxYd0+XWzvaLK7OJC/Go9O8llLGxrxICbKV+8cgIE0sDRBxiYx02X+6OhoAQ9w==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "@types/node": "^12.12.6", - "got": "12.1.0", - "swarm-js": "^0.1.40" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@truffle/interface-adapter/node_modules/web3-core": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-core/-/web3-core-1.8.2.tgz", - "integrity": "sha512-DJTVEAYcNqxkqruJE+Rxp3CIv0y5AZMwPHQmOkz/cz+MM75SIzMTc0AUdXzGyTS8xMF8h3YWMQGgGEy8SBf1PQ==", - "dev": true, - "dependencies": { - "@types/bn.js": "^5.1.0", - "@types/node": "^12.12.6", - "bignumber.js": "^9.0.0", - "web3-core-helpers": "1.8.2", - "web3-core-method": "1.8.2", - "web3-core-requestmanager": "1.8.2", - "web3-utils": "1.8.2" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@truffle/interface-adapter/node_modules/web3-core-helpers": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-core-helpers/-/web3-core-helpers-1.8.2.tgz", - "integrity": "sha512-6B1eLlq9JFrfealZBomd1fmlq1o4A09vrCVQSa51ANoib/jllT3atZrRDr0zt1rfI7TSZTZBXdN/aTdeN99DWw==", - "dev": true, - "dependencies": { - "web3-eth-iban": "1.8.2", - "web3-utils": "1.8.2" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@truffle/interface-adapter/node_modules/web3-core-method": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-core-method/-/web3-core-method-1.8.2.tgz", - "integrity": "sha512-1qnr5mw5wVyULzLOrk4B+ryO3gfGjGd/fx8NR+J2xCGLf1e6OSjxT9vbfuQ3fErk/NjSTWWreieYWLMhaogcRA==", - "dev": true, - "dependencies": { - "@ethersproject/transactions": "^5.6.2", - "web3-core-helpers": "1.8.2", - "web3-core-promievent": "1.8.2", - "web3-core-subscriptions": "1.8.2", - "web3-utils": "1.8.2" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@truffle/interface-adapter/node_modules/web3-core-promievent": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-core-promievent/-/web3-core-promievent-1.8.2.tgz", - "integrity": "sha512-nvkJWDVgoOSsolJldN33tKW6bKKRJX3MCPDYMwP5SUFOA/mCzDEoI88N0JFofDTXkh1k7gOqp1pvwi9heuaxGg==", - "dev": true, - "dependencies": { - "eventemitter3": "4.0.4" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@truffle/interface-adapter/node_modules/web3-core-requestmanager": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-core-requestmanager/-/web3-core-requestmanager-1.8.2.tgz", - "integrity": "sha512-p1d090RYs5Mu7DK1yyc3GCBVZB/03rBtFhYFoS2EruGzOWs/5Q0grgtpwS/DScdRAm8wB8mYEBhY/RKJWF6B2g==", - "dev": true, - "dependencies": { - "util": "^0.12.5", - "web3-core-helpers": "1.8.2", - "web3-providers-http": "1.8.2", - "web3-providers-ipc": "1.8.2", - "web3-providers-ws": "1.8.2" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@truffle/interface-adapter/node_modules/web3-core-subscriptions": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-core-subscriptions/-/web3-core-subscriptions-1.8.2.tgz", - "integrity": "sha512-vXQogHDmAIQcKpXvGiMddBUeP9lnKgYF64+yQJhPNE5PnWr1sAibXuIPV7mIPihpFr/n/DORRj6Wh1pUv9zaTw==", - "dev": true, - "dependencies": { - "eventemitter3": "4.0.4", - "web3-core-helpers": "1.8.2" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@truffle/interface-adapter/node_modules/web3-eth": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-eth/-/web3-eth-1.8.2.tgz", - "integrity": "sha512-JoTiWWc4F4TInpbvDUGb0WgDYJsFhuIjJlinc5ByjWD88Gvh+GKLsRjjFdbqe5YtwIGT4NymwoC5LQd1K6u/QQ==", - "dev": true, - "dependencies": { - "web3-core": "1.8.2", - "web3-core-helpers": "1.8.2", - "web3-core-method": "1.8.2", - "web3-core-subscriptions": "1.8.2", - "web3-eth-abi": "1.8.2", - "web3-eth-accounts": "1.8.2", - "web3-eth-contract": "1.8.2", - "web3-eth-ens": "1.8.2", - "web3-eth-iban": "1.8.2", - "web3-eth-personal": "1.8.2", - "web3-net": "1.8.2", - "web3-utils": "1.8.2" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@truffle/interface-adapter/node_modules/web3-eth-abi": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-eth-abi/-/web3-eth-abi-1.8.2.tgz", - "integrity": "sha512-Om9g3kaRNjqiNPAgKwGT16y+ZwtBzRe4ZJFGjLiSs6v5I7TPNF+rRMWuKnR6jq0azQZDj6rblvKFMA49/k48Og==", - "dev": true, - "dependencies": { - "@ethersproject/abi": "^5.6.3", - "web3-utils": "1.8.2" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@truffle/interface-adapter/node_modules/web3-eth-accounts": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-eth-accounts/-/web3-eth-accounts-1.8.2.tgz", - "integrity": "sha512-c367Ij63VCz9YdyjiHHWLFtN85l6QghgwMQH2B1eM/p9Y5lTlTX7t/Eg/8+f1yoIStXbk2w/PYM2lk+IkbqdLA==", - "dev": true, - "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.8.2", - "web3-core-helpers": "1.8.2", - "web3-core-method": "1.8.2", - "web3-utils": "1.8.2" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@truffle/interface-adapter/node_modules/web3-eth-contract": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-eth-contract/-/web3-eth-contract-1.8.2.tgz", - "integrity": "sha512-ID5A25tHTSBNwOPjiXSVzxruz006ULRIDbzWTYIFTp7NJ7vXu/kynKK2ag/ObuTqBpMbobP8nXcA9b5EDkIdQA==", - "dev": true, - "dependencies": { - "@types/bn.js": "^5.1.0", - "web3-core": "1.8.2", - "web3-core-helpers": "1.8.2", - "web3-core-method": "1.8.2", - "web3-core-promievent": "1.8.2", - "web3-core-subscriptions": "1.8.2", - "web3-eth-abi": "1.8.2", - "web3-utils": "1.8.2" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@truffle/interface-adapter/node_modules/web3-eth-ens": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-eth-ens/-/web3-eth-ens-1.8.2.tgz", - "integrity": "sha512-PWph7C/CnqdWuu1+SH4U4zdrK4t2HNt0I4XzPYFdv9ugE8EuojselioPQXsVGvjql+Nt3jDLvQvggPqlMbvwRw==", - "dev": true, - "dependencies": { - "content-hash": "^2.5.2", - "eth-ens-namehash": "2.0.8", - "web3-core": "1.8.2", - "web3-core-helpers": "1.8.2", - "web3-core-promievent": "1.8.2", - "web3-eth-abi": "1.8.2", - "web3-eth-contract": "1.8.2", - "web3-utils": "1.8.2" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@truffle/interface-adapter/node_modules/web3-eth-iban": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-eth-iban/-/web3-eth-iban-1.8.2.tgz", - "integrity": "sha512-h3vNblDWkWMuYx93Q27TAJz6lhzpP93EiC3+45D6xoz983p6si773vntoQ+H+5aZhwglBtoiBzdh7PSSOnP/xQ==", - "dev": true, - "dependencies": { - "bn.js": "^5.2.1", - "web3-utils": "1.8.2" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@truffle/interface-adapter/node_modules/web3-eth-personal": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-eth-personal/-/web3-eth-personal-1.8.2.tgz", - "integrity": "sha512-Vg4HfwCr7doiUF/RC+Jz0wT4+cYaXcOWMAW2AHIjHX6Z7Xwa8nrURIeQgeEE62qcEHAzajyAdB1u6bJyTfuCXw==", - "dev": true, - "dependencies": { - "@types/node": "^12.12.6", - "web3-core": "1.8.2", - "web3-core-helpers": "1.8.2", - "web3-core-method": "1.8.2", - "web3-net": "1.8.2", - "web3-utils": "1.8.2" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@truffle/interface-adapter/node_modules/web3-net": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-net/-/web3-net-1.8.2.tgz", - "integrity": "sha512-1itkDMGmbgb83Dg9nporFes9/fxsU7smJ3oRXlFkg4ZHn8YJyP1MSQFPJWWwSc+GrcCFt4O5IrUTvEkHqE3xag==", - "dev": true, - "dependencies": { - "web3-core": "1.8.2", - "web3-core-method": "1.8.2", - "web3-utils": "1.8.2" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@truffle/interface-adapter/node_modules/web3-providers-http": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-providers-http/-/web3-providers-http-1.8.2.tgz", - "integrity": "sha512-2xY94IIEQd16+b+vIBF4IC1p7GVaz9q4EUFscvMUjtEq4ru4Atdzjs9GP+jmcoo49p70II0UV3bqQcz0TQfVyQ==", - "dev": true, - "dependencies": { - "abortcontroller-polyfill": "^1.7.3", - "cross-fetch": "^3.1.4", - "es6-promise": "^4.2.8", - "web3-core-helpers": "1.8.2" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@truffle/interface-adapter/node_modules/web3-providers-ipc": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-providers-ipc/-/web3-providers-ipc-1.8.2.tgz", - "integrity": "sha512-p6fqKVGFg+WiXGHWnB1hu43PbvPkDHTz4RgoEzbXugv5rtv5zfYLqm8Ba6lrJOS5ks9kGKR21a0y3NzE3u7V4w==", - "dev": true, - "dependencies": { - "oboe": "2.1.5", - "web3-core-helpers": "1.8.2" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@truffle/interface-adapter/node_modules/web3-providers-ws": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-providers-ws/-/web3-providers-ws-1.8.2.tgz", - "integrity": "sha512-3s/4K+wHgbiN+Zrp9YjMq2eqAF6QGABw7wFftPdx+m5hWImV27/MoIx57c6HffNRqZXmCHnfWWFCNHHsi7wXnA==", - "dev": true, - "dependencies": { - "eventemitter3": "4.0.4", - "web3-core-helpers": "1.8.2", - "websocket": "^1.0.32" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@truffle/interface-adapter/node_modules/web3-shh": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-shh/-/web3-shh-1.8.2.tgz", - "integrity": "sha512-uZ+3MAoNcaJsXXNCDnizKJ5viBNeHOFYsCbFhV755Uu52FswzTOw6DtE7yK9nYXMtIhiSgi7nwl1RYzP8pystw==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "web3-core": "1.8.2", - "web3-core-method": "1.8.2", - "web3-core-subscriptions": "1.8.2", - "web3-net": "1.8.2" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@truffle/interface-adapter/node_modules/web3-utils": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.8.2.tgz", - "integrity": "sha512-v7j6xhfLQfY7xQDrUP0BKbaNrmZ2/+egbqP9q3KYmOiPpnvAfol+32slgL0WX/5n8VPvKCK5EZ1HGrAVICSToA==", - "dev": true, - "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" - }, - "engines": { - "node": ">=8.0.0" - } - }, "node_modules/@trufflesuite/chromafi": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@trufflesuite/chromafi/-/chromafi-3.0.0.tgz", @@ -3947,9 +3155,9 @@ } }, "node_modules/antlr4": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/antlr4/-/antlr4-4.12.0.tgz", - "integrity": "sha512-23iB5IzXJZRZeK9TigzUyrNc9pSmNqAerJRBcNq1ETrmttMWRgaYZzC561IgEO3ygKsDJTYDTozABXa4b/fTQQ==", + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/antlr4/-/antlr4-4.13.0.tgz", + "integrity": "sha512-zooUbt+UscjnWyOrsuY/tVFL4rwrAGwOivpQmvmUDE22hy/lUA467Rc1rcixyRwcRUIXFYBwv7+dClDSHdmmew==", "dev": true, "engines": { "node": ">=16" @@ -4392,9 +3600,9 @@ } }, "node_modules/breakword": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/breakword/-/breakword-1.0.5.tgz", - "integrity": "sha512-ex5W9DoOQ/LUEU3PMdLs9ua/CYZl1678NUkKOdUSi8Aw5F1idieaiRURCBFJCwVcrD1J8Iy3vfWSloaMwO2qFg==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/breakword/-/breakword-1.0.6.tgz", + "integrity": "sha512-yjxDAYyK/pBvws9H4xKYpLDpYKEH6CzrBPAuXq3x18I+c/2MkVtT3qAr7Oloi6Dss9qNhPVueAAVU1CSeNDIXw==", "dev": true, "dependencies": { "wcwidth": "^1.0.1" @@ -5306,33 +4514,13 @@ "sha.js": "^2.4.8" } }, - "node_modules/cross-fetch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", - "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", - "dev": true, - "dependencies": { - "node-fetch": "2.6.7" - } - }, - "node_modules/cross-fetch/node_modules/node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "node_modules/cross-fetch": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz", + "integrity": "sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==", "dev": true, "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } + "node-fetch": "^2.6.11" } }, "node_modules/cross-spawn": { @@ -6144,15 +5332,15 @@ } }, "node_modules/eslint": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.39.0.tgz", - "integrity": "sha512-mwiok6cy7KTW7rBpo05k6+p4YVZByLNjAZ/ACB9DRCu4YDRwjXI01tWHp6KAUWelsBetTxKK/2sHB0vdS8Z2Og==", + "version": "8.41.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.41.0.tgz", + "integrity": "sha512-WQDQpzGBOP5IrXPo4Hc0814r4/v2rrIsB0rhT7jtunIalgg6gYXWhRMOejVO8yH21T/FGaxjmFjBMNqcIlmH1Q==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.4.0", - "@eslint/eslintrc": "^2.0.2", - "@eslint/js": "8.39.0", + "@eslint/eslintrc": "^2.0.3", + "@eslint/js": "8.41.0", "@humanwhocodes/config-array": "^0.11.8", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", @@ -6163,8 +5351,8 @@ "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", "eslint-scope": "^7.2.0", - "eslint-visitor-keys": "^3.4.0", - "espree": "^9.5.1", + "eslint-visitor-keys": "^3.4.1", + "espree": "^9.5.2", "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -6172,13 +5360,12 @@ "find-up": "^5.0.0", "glob-parent": "^6.0.2", "globals": "^13.19.0", - "grapheme-splitter": "^1.0.4", + "graphemer": "^1.4.0", "ignore": "^5.2.0", "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", - "js-sdsl": "^4.1.4", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", @@ -6229,9 +5416,9 @@ } }, "node_modules/eslint-visitor-keys": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz", - "integrity": "sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", + "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -6420,14 +5607,14 @@ } }, "node_modules/espree": { - "version": "9.5.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.1.tgz", - "integrity": "sha512-5yxtHSZXRSW5pvv3hAlXM5+/Oswi1AUFqBmbibKb5s6bp3rGIDkyXU6xCoyuuLhijr4SFwPrXRoZjz0AZDN9tg==", + "version": "9.5.2", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.2.tgz", + "integrity": "sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==", "dev": true, "dependencies": { "acorn": "^8.8.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.0" + "eslint-visitor-keys": "^3.4.1" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -7514,9 +6701,9 @@ "dev": true }, "node_modules/fast-diff": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", - "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", "dev": true }, "node_modules/fast-glob": { @@ -7853,13 +7040,14 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", - "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", + "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", "dev": true, "dependencies": { "function-bind": "^1.1.1", "has": "^1.0.3", + "has-proto": "^1.0.1", "has-symbols": "^1.0.3" }, "funding": { @@ -8126,6 +7314,12 @@ "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", "dev": true }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, "node_modules/graphlib": { "version": "2.1.8", "resolved": "https://registry.npmjs.org/graphlib/-/graphlib-2.1.8.tgz", @@ -8207,9 +7401,9 @@ } }, "node_modules/hardhat": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.14.0.tgz", - "integrity": "sha512-73jsInY4zZahMSVFurSK+5TNCJTXMv+vemvGia0Ac34Mm19fYp6vEPVGF3sucbumszsYxiTT2TbS8Ii2dsDSoQ==", + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.14.1.tgz", + "integrity": "sha512-H3Qp/UKyQGmPDDBSfMoSyH18rRnac90rsb0LNer+sKe6at6rxLe4D5j+M+1icqZQF02iLPjNRwc/PA8OPf757A==", "dev": true, "dependencies": { "@ethersproject/abi": "^5.1.2", @@ -8283,9 +7477,9 @@ } }, "node_modules/hardhat-exposed": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/hardhat-exposed/-/hardhat-exposed-0.3.3.tgz", - "integrity": "sha512-AFBM3IUlSU9wkt3886kYbCSzZteB4OQt0ciehPUrCgY/Tazn6g2cxsmoIZT8O8va1R2PtXd9PRC4KZ6WiSVXOw==", + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/hardhat-exposed/-/hardhat-exposed-0.3.6.tgz", + "integrity": "sha512-jb03M+GolhEfjDYJyPkGJzcAs1/eZrVufMADXQDwng2iuUJD1zgzM3k1oq2YQihRxguQDsLE1nuHIn6N8o5GmQ==", "dev": true, "dependencies": { "micromatch": "^4.0.4", @@ -10784,9 +9978,9 @@ } }, "node_modules/node-fetch": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", - "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==", + "version": "2.6.11", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.11.tgz", + "integrity": "sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==", "dev": true, "dependencies": { "whatwg-url": "^5.0.0" @@ -11638,9 +10832,9 @@ ] }, "node_modules/qs": { - "version": "6.11.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.1.tgz", - "integrity": "sha512-0wsrzgTz/kAVIeuxSjnpGC56rzYtr6JT/2BwEvMaPhFIoYa1aGO8LbzuU1R0uUYQkLpWBTOj0l/CLAJB64J6nQ==", + "version": "6.11.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", + "integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==", "dev": true, "dependencies": { "side-channel": "^1.0.4" @@ -12402,9 +11596,9 @@ } }, "node_modules/semver": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", - "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", + "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -13409,9 +12603,9 @@ } }, "node_modules/solidity-ast": { - "version": "0.4.46", - "resolved": "https://registry.npmjs.org/solidity-ast/-/solidity-ast-0.4.46.tgz", - "integrity": "sha512-MlPZQfPhjWXqh7YxWcBGDXaPZIfMYCOHYoLEhGDWulNwEPIQQZuB7mA9eP17CU0jY/bGR4avCEUVVpvHtT2gbA==", + "version": "0.4.49", + "resolved": "https://registry.npmjs.org/solidity-ast/-/solidity-ast-0.4.49.tgz", + "integrity": "sha512-Pr5sCAj1SFqzwFZw1HPKSq0PehlQNdM8GwKyAVYh2DOn7/cCK8LUKD1HeHnKtTgBW7hi9h4nnnan7hpAg5RhWQ==", "dev": true }, "node_modules/solidity-comments": { @@ -15353,28 +14547,28 @@ } }, "node_modules/web3": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/web3/-/web3-1.9.0.tgz", - "integrity": "sha512-E9IvVy/d2ozfQQsCiV+zh/LmlZGv9fQxI0UedDVjm87yOKf4AYbBNEn1iWtHveiGzAk2CEMZMUzAZzaQNSSYog==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/web3/-/web3-1.10.0.tgz", + "integrity": "sha512-YfKY9wSkGcM8seO+daR89oVTcbu18NsVfvOngzqMYGUU0pPSQmE57qQDvQzUeoIOHAnXEBNzrhjQJmm8ER0rng==", "dev": true, "hasInstallScript": true, "dependencies": { - "web3-bzz": "1.9.0", - "web3-core": "1.9.0", - "web3-eth": "1.9.0", - "web3-eth-personal": "1.9.0", - "web3-net": "1.9.0", - "web3-shh": "1.9.0", - "web3-utils": "1.9.0" + "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" }, "engines": { "node": ">=8.0.0" } }, "node_modules/web3-bzz": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/web3-bzz/-/web3-bzz-1.9.0.tgz", - "integrity": "sha512-9Zli9dikX8GdHwBb5/WPzpSVuy3EWMKY3P4EokCQra31fD7DLizqAAaTUsFwnK7xYkw5ogpHgelw9uKHHzNajg==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/web3-bzz/-/web3-bzz-1.10.0.tgz", + "integrity": "sha512-o9IR59io3pDUsXTsps5pO5hW1D5zBmg46iNc2t4j2DkaYHNdDLwk2IP9ukoM2wg47QILfPEJYzhTfkS/CcX0KA==", "dev": true, "hasInstallScript": true, "dependencies": { @@ -15387,56 +14581,56 @@ } }, "node_modules/web3-core": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/web3-core/-/web3-core-1.9.0.tgz", - "integrity": "sha512-DZ+TPmq/ZLlx4LSVzFgrHCP/QFpKDbGWO4HoquZSdu24cjk5SZ+FEU1SZB2OaK3/bgBh+25mRbmv8y56ysUu1w==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/web3-core/-/web3-core-1.10.0.tgz", + "integrity": "sha512-fWySwqy2hn3TL89w5TM8wXF1Z2Q6frQTKHWmP0ppRQorEK8NcHJRfeMiv/mQlSKoTS1F6n/nv2uyZsixFycjYQ==", "dev": true, "dependencies": { "@types/bn.js": "^5.1.1", "@types/node": "^12.12.6", "bignumber.js": "^9.0.0", - "web3-core-helpers": "1.9.0", - "web3-core-method": "1.9.0", - "web3-core-requestmanager": "1.9.0", - "web3-utils": "1.9.0" + "web3-core-helpers": "1.10.0", + "web3-core-method": "1.10.0", + "web3-core-requestmanager": "1.10.0", + "web3-utils": "1.10.0" }, "engines": { "node": ">=8.0.0" } }, "node_modules/web3-core-helpers": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/web3-core-helpers/-/web3-core-helpers-1.9.0.tgz", - "integrity": "sha512-NeJzylAp9Yj9xAt2uTT+kyug3X0DLnfBdnAcGZuY6HhoNPDIfQRA9CkJjLngVRlGTLZGjNp9x9eR+RyZQgUlXg==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/web3-core-helpers/-/web3-core-helpers-1.10.0.tgz", + "integrity": "sha512-pIxAzFDS5vnbXvfvLSpaA1tfRykAe9adw43YCKsEYQwH0gCLL0kMLkaCX3q+Q8EVmAh+e1jWL/nl9U0de1+++g==", "dev": true, "dependencies": { - "web3-eth-iban": "1.9.0", - "web3-utils": "1.9.0" + "web3-eth-iban": "1.10.0", + "web3-utils": "1.10.0" }, "engines": { "node": ">=8.0.0" } }, "node_modules/web3-core-method": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/web3-core-method/-/web3-core-method-1.9.0.tgz", - "integrity": "sha512-sswbNsY2xRBBhGeaLt9c/eDc+0yDDhi6keUBAkgIRa9ueSx/VKzUY9HMqiV6bXDcGT2fJyejq74FfEB4lc/+/w==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/web3-core-method/-/web3-core-method-1.10.0.tgz", + "integrity": "sha512-4R700jTLAMKDMhQ+nsVfIXvH6IGJlJzGisIfMKWAIswH31h5AZz7uDUW2YctI+HrYd+5uOAlS4OJeeT9bIpvkA==", "dev": true, "dependencies": { "@ethersproject/transactions": "^5.6.2", - "web3-core-helpers": "1.9.0", - "web3-core-promievent": "1.9.0", - "web3-core-subscriptions": "1.9.0", - "web3-utils": "1.9.0" + "web3-core-helpers": "1.10.0", + "web3-core-promievent": "1.10.0", + "web3-core-subscriptions": "1.10.0", + "web3-utils": "1.10.0" }, "engines": { "node": ">=8.0.0" } }, "node_modules/web3-core-promievent": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/web3-core-promievent/-/web3-core-promievent-1.9.0.tgz", - "integrity": "sha512-PHG1Mn23IGwMZhnPDN8dETKypqsFbHfiyRqP+XsVMPmTHkVfzDQTCBU/c2r6hUktBDoGKut5xZQpGfhFk71KbQ==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/web3-core-promievent/-/web3-core-promievent-1.10.0.tgz", + "integrity": "sha512-68N7k5LWL5R38xRaKFrTFT2pm2jBNFaM4GioS00YjAKXRQ3KjmhijOMG3TICz6Aa5+6GDWYelDNx21YAeZ4YTg==", "dev": true, "dependencies": { "eventemitter3": "4.0.4" @@ -15446,29 +14640,29 @@ } }, "node_modules/web3-core-requestmanager": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/web3-core-requestmanager/-/web3-core-requestmanager-1.9.0.tgz", - "integrity": "sha512-hcJ5PCtTIJpj+8qWxoseqlCovDo94JJjTX7dZOLXgwp8ah7E3WRYozhGyZocerx+KebKyg1mCQIhkDpMwjfo9Q==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/web3-core-requestmanager/-/web3-core-requestmanager-1.10.0.tgz", + "integrity": "sha512-3z/JKE++Os62APml4dvBM+GAuId4h3L9ckUrj7ebEtS2AR0ixyQPbrBodgL91Sv7j7cQ3Y+hllaluqjguxvSaQ==", "dev": true, "dependencies": { "util": "^0.12.5", - "web3-core-helpers": "1.9.0", - "web3-providers-http": "1.9.0", - "web3-providers-ipc": "1.9.0", - "web3-providers-ws": "1.9.0" + "web3-core-helpers": "1.10.0", + "web3-providers-http": "1.10.0", + "web3-providers-ipc": "1.10.0", + "web3-providers-ws": "1.10.0" }, "engines": { "node": ">=8.0.0" } }, "node_modules/web3-core-subscriptions": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/web3-core-subscriptions/-/web3-core-subscriptions-1.9.0.tgz", - "integrity": "sha512-MaIo29yz7hTV8X8bioclPDbHFOVuHmnbMv+D3PDH12ceJFJAXGyW8GL5KU1DYyWIj4TD1HM4WknyVA/YWBiiLA==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/web3-core-subscriptions/-/web3-core-subscriptions-1.10.0.tgz", + "integrity": "sha512-HGm1PbDqsxejI075gxBc5OSkwymilRWZufIy9zEpnWKNmfbuv5FfHgW1/chtJP6aP3Uq2vHkvTDl3smQBb8l+g==", "dev": true, "dependencies": { "eventemitter3": "4.0.4", - "web3-core-helpers": "1.9.0" + "web3-core-helpers": "1.10.0" }, "engines": { "node": ">=8.0.0" @@ -15484,45 +14678,45 @@ } }, "node_modules/web3-eth": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/web3-eth/-/web3-eth-1.9.0.tgz", - "integrity": "sha512-c5gSWk9bLNr6VPATHmZ1n7LTIefIZQnJMzfnvkoBcIFGKJbGmsuRhv6lEXsKdAO/FlqYnSbaw3fOq1fVFiIOFQ==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/web3-eth/-/web3-eth-1.10.0.tgz", + "integrity": "sha512-Z5vT6slNMLPKuwRyKGbqeGYC87OAy8bOblaqRTgg94CXcn/mmqU7iPIlG4506YdcdK3x6cfEDG7B6w+jRxypKA==", "dev": true, "dependencies": { - "web3-core": "1.9.0", - "web3-core-helpers": "1.9.0", - "web3-core-method": "1.9.0", - "web3-core-subscriptions": "1.9.0", - "web3-eth-abi": "1.9.0", - "web3-eth-accounts": "1.9.0", - "web3-eth-contract": "1.9.0", - "web3-eth-ens": "1.9.0", - "web3-eth-iban": "1.9.0", - "web3-eth-personal": "1.9.0", - "web3-net": "1.9.0", - "web3-utils": "1.9.0" + "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" }, "engines": { "node": ">=8.0.0" } }, "node_modules/web3-eth-abi": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/web3-eth-abi/-/web3-eth-abi-1.9.0.tgz", - "integrity": "sha512-0BLQ3FKMrzJkA930jOX3fMaybAyubk06HChclLpiR0NWmgWXm1tmBrJdkyRy2ZTZpmfuZc9xTFRfl0yZID1voA==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/web3-eth-abi/-/web3-eth-abi-1.10.0.tgz", + "integrity": "sha512-cwS+qRBWpJ43aI9L3JS88QYPfFcSJJ3XapxOQ4j40v6mk7ATpA8CVK1vGTzpihNlOfMVRBkR95oAj7oL6aiDOg==", "dev": true, "dependencies": { "@ethersproject/abi": "^5.6.3", - "web3-utils": "1.9.0" + "web3-utils": "1.10.0" }, "engines": { "node": ">=8.0.0" } }, "node_modules/web3-eth-accounts": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/web3-eth-accounts/-/web3-eth-accounts-1.9.0.tgz", - "integrity": "sha512-VeIZVevmnSll0AC1k5F/y398ZE89d1SRuYk8IewLUhL/tVAsFEsjl2SGgm0+aDcHmgPrkW+qsCJ+C7rWg/N4ZA==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/web3-eth-accounts/-/web3-eth-accounts-1.10.0.tgz", + "integrity": "sha512-wiq39Uc3mOI8rw24wE2n15hboLE0E9BsQLdlmsL4Zua9diDS6B5abXG0XhFcoNsXIGMWXVZz4TOq3u4EdpXF/Q==", "dev": true, "dependencies": { "@ethereumjs/common": "2.5.0", @@ -15531,10 +14725,10 @@ "ethereumjs-util": "^7.1.5", "scrypt-js": "^3.0.1", "uuid": "^9.0.0", - "web3-core": "1.9.0", - "web3-core-helpers": "1.9.0", - "web3-core-method": "1.9.0", - "web3-utils": "1.9.0" + "web3-core": "1.10.0", + "web3-core-helpers": "1.10.0", + "web3-core-method": "1.10.0", + "web3-utils": "1.10.0" }, "engines": { "node": ">=8.0.0" @@ -15561,51 +14755,51 @@ } }, "node_modules/web3-eth-contract": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/web3-eth-contract/-/web3-eth-contract-1.9.0.tgz", - "integrity": "sha512-+j26hpSaEtAdUed0TN5rnc+YZOcjPxMjFX4ZBKatvFkImdbVv/tzTvcHlltubSpgb2ZLyZ89lSL6phKYwd2zNQ==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/web3-eth-contract/-/web3-eth-contract-1.10.0.tgz", + "integrity": "sha512-MIC5FOzP/+2evDksQQ/dpcXhSqa/2hFNytdl/x61IeWxhh6vlFeSjq0YVTAyIzdjwnL7nEmZpjfI6y6/Ufhy7w==", "dev": true, "dependencies": { "@types/bn.js": "^5.1.1", - "web3-core": "1.9.0", - "web3-core-helpers": "1.9.0", - "web3-core-method": "1.9.0", - "web3-core-promievent": "1.9.0", - "web3-core-subscriptions": "1.9.0", - "web3-eth-abi": "1.9.0", - "web3-utils": "1.9.0" + "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" }, "engines": { "node": ">=8.0.0" } }, "node_modules/web3-eth-ens": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/web3-eth-ens/-/web3-eth-ens-1.9.0.tgz", - "integrity": "sha512-LOJZeN+AGe9arhuExnrPPFYQr4WSxXEkpvYIlst/joOEUNLDwfndHnJIK6PI5mXaYSROBtTx6erv+HupzGo7vA==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/web3-eth-ens/-/web3-eth-ens-1.10.0.tgz", + "integrity": "sha512-3hpGgzX3qjgxNAmqdrC2YUQMTfnZbs4GeLEmy8aCWziVwogbuqQZ+Gzdfrym45eOZodk+lmXyLuAdqkNlvkc1g==", "dev": true, "dependencies": { "content-hash": "^2.5.2", "eth-ens-namehash": "2.0.8", - "web3-core": "1.9.0", - "web3-core-helpers": "1.9.0", - "web3-core-promievent": "1.9.0", - "web3-eth-abi": "1.9.0", - "web3-eth-contract": "1.9.0", - "web3-utils": "1.9.0" + "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" }, "engines": { "node": ">=8.0.0" } }, "node_modules/web3-eth-iban": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/web3-eth-iban/-/web3-eth-iban-1.9.0.tgz", - "integrity": "sha512-jPAm77PuEs1kE/UrrBFJdPD2PN42pwfXA0gFuuw35bZezhskYML9W4QCxcqnUtceyEA4FUn7K2qTMuCk+23fog==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/web3-eth-iban/-/web3-eth-iban-1.10.0.tgz", + "integrity": "sha512-0l+SP3IGhInw7Q20LY3IVafYEuufo4Dn75jAHT7c2aDJsIolvf2Lc6ugHkBajlwUneGfbRQs/ccYPQ9JeMUbrg==", "dev": true, "dependencies": { "bn.js": "^5.2.1", - "web3-utils": "1.9.0" + "web3-utils": "1.10.0" }, "engines": { "node": ">=8.0.0" @@ -15618,72 +14812,72 @@ "dev": true }, "node_modules/web3-eth-personal": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/web3-eth-personal/-/web3-eth-personal-1.9.0.tgz", - "integrity": "sha512-r9Ldo/luBqJlv1vCUEQnUS+C3a3ZdbYxVHyfDkj6RWMyCqqo8JE41HWE+pfa0RmB1xnGL2g8TbYcHcqItck/qg==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/web3-eth-personal/-/web3-eth-personal-1.10.0.tgz", + "integrity": "sha512-anseKn98w/d703eWq52uNuZi7GhQeVjTC5/svrBWEKob0WZ5kPdo+EZoFN0sp5a5ubbrk/E0xSl1/M5yORMtpg==", "dev": true, "dependencies": { "@types/node": "^12.12.6", - "web3-core": "1.9.0", - "web3-core-helpers": "1.9.0", - "web3-core-method": "1.9.0", - "web3-net": "1.9.0", - "web3-utils": "1.9.0" + "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" }, "engines": { "node": ">=8.0.0" } }, "node_modules/web3-net": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/web3-net/-/web3-net-1.9.0.tgz", - "integrity": "sha512-L+fDZFgrLM5Y15aonl2q6L+RvfaImAngmC0Jv45hV2FJ5IfRT0/2ob9etxZmvEBWvOpbqSvghfOhJIT3XZ37Pg==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/web3-net/-/web3-net-1.10.0.tgz", + "integrity": "sha512-NLH/N3IshYWASpxk4/18Ge6n60GEvWBVeM8inx2dmZJVmRI6SJIlUxbL8jySgiTn3MMZlhbdvrGo8fpUW7a1GA==", "dev": true, "dependencies": { - "web3-core": "1.9.0", - "web3-core-method": "1.9.0", - "web3-utils": "1.9.0" + "web3-core": "1.10.0", + "web3-core-method": "1.10.0", + "web3-utils": "1.10.0" }, "engines": { "node": ">=8.0.0" } }, "node_modules/web3-providers-http": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/web3-providers-http/-/web3-providers-http-1.9.0.tgz", - "integrity": "sha512-5+dMNDAE0rRFz6SJpfnBqlVi2J5bB/Ivr2SanMt2YUrkxW5t8betZbzVwRkTbwtUvkqgj3xeUQzqpOttiv+IqQ==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/web3-providers-http/-/web3-providers-http-1.10.0.tgz", + "integrity": "sha512-eNr965YB8a9mLiNrkjAWNAPXgmQWfpBfkkn7tpEFlghfww0u3I0tktMZiaToJVcL2+Xq+81cxbkpeWJ5XQDwOA==", "dev": true, "dependencies": { "abortcontroller-polyfill": "^1.7.3", "cross-fetch": "^3.1.4", "es6-promise": "^4.2.8", - "web3-core-helpers": "1.9.0" + "web3-core-helpers": "1.10.0" }, "engines": { "node": ">=8.0.0" } }, "node_modules/web3-providers-ipc": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/web3-providers-ipc/-/web3-providers-ipc-1.9.0.tgz", - "integrity": "sha512-cPXU93Du40HCylvjaa5x62DbnGqH+86HpK/+kMcFIzF6sDUBhKpag2tSbYhGbj7GMpfkmDTUiiMLdWnFV6+uBA==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/web3-providers-ipc/-/web3-providers-ipc-1.10.0.tgz", + "integrity": "sha512-OfXG1aWN8L1OUqppshzq8YISkWrYHaATW9H8eh0p89TlWMc1KZOL9vttBuaBEi96D/n0eYDn2trzt22bqHWfXA==", "dev": true, "dependencies": { "oboe": "2.1.5", - "web3-core-helpers": "1.9.0" + "web3-core-helpers": "1.10.0" }, "engines": { "node": ">=8.0.0" } }, "node_modules/web3-providers-ws": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/web3-providers-ws/-/web3-providers-ws-1.9.0.tgz", - "integrity": "sha512-JRVsnQZ7j2k1a2yzBNHe39xqk1ijOv01dfIBFw52VeEkSRzvrOcsPIM/ttSyBuJqt70ntMxXY0ekCrqfleKH/w==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/web3-providers-ws/-/web3-providers-ws-1.10.0.tgz", + "integrity": "sha512-sK0fNcglW36yD5xjnjtSGBnEtf59cbw4vZzJ+CmOWIKGIR96mP5l684g0WD0Eo+f4NQc2anWWXG74lRc9OVMCQ==", "dev": true, "dependencies": { "eventemitter3": "4.0.4", - "web3-core-helpers": "1.9.0", + "web3-core-helpers": "1.10.0", "websocket": "^1.0.32" }, "engines": { @@ -15691,25 +14885,25 @@ } }, "node_modules/web3-shh": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/web3-shh/-/web3-shh-1.9.0.tgz", - "integrity": "sha512-bIBZlralgz4ICCrwkefB2nPPJWfx28NuHIpjB7d9ADKynElubQuqudYhKtSEkKXACuME/BJm0pIFJcJs/gDnMg==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/web3-shh/-/web3-shh-1.10.0.tgz", + "integrity": "sha512-uNUUuNsO2AjX41GJARV9zJibs11eq6HtOe6Wr0FtRUcj8SN6nHeYIzwstAvJ4fXA53gRqFMTxdntHEt9aXVjpg==", "dev": true, "hasInstallScript": true, "dependencies": { - "web3-core": "1.9.0", - "web3-core-method": "1.9.0", - "web3-core-subscriptions": "1.9.0", - "web3-net": "1.9.0" + "web3-core": "1.10.0", + "web3-core-method": "1.10.0", + "web3-core-subscriptions": "1.10.0", + "web3-net": "1.10.0" }, "engines": { "node": ">=8.0.0" } }, "node_modules/web3-utils": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.9.0.tgz", - "integrity": "sha512-p++69rCNNfu2jM9n5+VD/g26l+qkEOQ1m6cfRQCbH8ZRrtquTmrirJMgTmyOoax5a5XRYOuws14aypCOs51pdQ==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.10.0.tgz", + "integrity": "sha512-kSaCM0uMcZTNUSmn5vMEhlo02RObGNRRCkdX0V9UTAU0+lrvn0HSaudyCo6CQzuXUsnuY2ERJGCGPfeWmv19Rg==", "dev": true, "dependencies": { "bn.js": "^5.2.1", @@ -16271,9 +15465,9 @@ } }, "@babel/runtime": { - "version": "7.21.5", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.5.tgz", - "integrity": "sha512-8jI69toZqqcsnqGGqwGS4Qb1VwLOEp4hz+CXPywcvjs60u3B4Pom/U/7rm4W8tMOYEB+E9wgD0mW1l3r8qlI9Q==", + "version": "7.22.3", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.3.tgz", + "integrity": "sha512-XsDuspWKLUsxwCp6r7EhsExHtYfbe5oAGQ19kqngTdCPUoPQzOPdUbD/pB9PJiwb2ptYKQDjSJT3R6dC+EPqfQ==", "dev": true, "requires": { "regenerator-runtime": "^0.13.11" @@ -16698,14 +15892,14 @@ "dev": true }, "@eslint/eslintrc": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.2.tgz", - "integrity": "sha512-3W4f5tDUra+pA+FzgugqL2pRimUTDJWKr7BINqOpkZrC0uYI0NIc0/JFgBROCU07HR6GieA5m3/rsPIhDmCXTQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.3.tgz", + "integrity": "sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ==", "dev": true, "requires": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.5.1", + "espree": "^9.5.2", "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", @@ -16732,9 +15926,9 @@ } }, "@eslint/js": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.39.0.tgz", - "integrity": "sha512-kf9RB0Fg7NZfap83B3QOqOGg9QmD9yBudqQXzzOtn3i4y7ZUXe5ONeW34Gwi+TxhH4mvj72R1Zc300KUMa9Bng==", + "version": "8.41.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.41.0.tgz", + "integrity": "sha512-LxcyMGxwmTh2lY9FwHPGWOHmYFCZvbrFCBZL4FzSSsxsRPuhrYUg/49/0KDfW8tnIEaEHtfmn6+NPN+1DqaNmA==", "dev": true }, "@ethereumjs/common": { @@ -17872,9 +17066,9 @@ } }, "@openzeppelin/upgrades-core": { - "version": "1.25.0", - "resolved": "https://registry.npmjs.org/@openzeppelin/upgrades-core/-/upgrades-core-1.25.0.tgz", - "integrity": "sha512-vSxOSm1k+P156nNm15ydhOmSPGC37mnl092FMVOH+eGaoqLjr2Za6ULVjDMFzvMnG+sGE1UoDOqBNPfTm0ch8w==", + "version": "1.26.2", + "resolved": "https://registry.npmjs.org/@openzeppelin/upgrades-core/-/upgrades-core-1.26.2.tgz", + "integrity": "sha512-TJORrgyun5qflPos/47P3j61gDw+7W+tEirSBOYRxfVL1WGjX1n8iaLrijPIqzyeS1MKguN1nckAMspQ4SKrxw==", "dev": true, "requires": { "cbor": "^8.0.0", @@ -18070,37 +17264,14 @@ } }, "@truffle/abi-utils": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@truffle/abi-utils/-/abi-utils-0.3.9.tgz", - "integrity": "sha512-G5dqgwRHx5zwlXjz3QT8OJVfB2cOqWwD6DwKso0KttUt/zejhCjnkKq72rSgyeLMkz7wBB9ERLOsupLBILM8MA==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@truffle/abi-utils/-/abi-utils-1.0.0.tgz", + "integrity": "sha512-h1wGFB28YfByAWm/uBeMCwqDlGsrcMYTumLC/sB/qYhHisi1LK6tV47FEF7zKyf6Al2CtsO28v02+wfLXbUVRg==", "dev": true, "requires": { "change-case": "3.0.2", "fast-check": "3.1.1", - "web3-utils": "1.8.2" - }, - "dependencies": { - "bn.js": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", - "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", - "dev": true - }, - "web3-utils": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.8.2.tgz", - "integrity": "sha512-v7j6xhfLQfY7xQDrUP0BKbaNrmZ2/+egbqP9q3KYmOiPpnvAfol+32slgL0WX/5n8VPvKCK5EZ1HGrAVICSToA==", - "dev": true, - "requires": { - "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.0" } }, "@truffle/blockchain-utils": { @@ -18110,13 +17281,13 @@ "dev": true }, "@truffle/codec": { - "version": "0.14.17", - "resolved": "https://registry.npmjs.org/@truffle/codec/-/codec-0.14.17.tgz", - "integrity": "sha512-kD4dD86huLeaBEq5R8D1zleJEu6NsXbyYLdXl1V1TKdiO8odw5CBC6Y/+wdu5d3t1dyEYrTbhn1dqknZa52pmw==", + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/@truffle/codec/-/codec-0.15.1.tgz", + "integrity": "sha512-OBANcmefxEXLApWl/uU1SOHQJixO8pDaRTybP0YMvxPhgyj7G7+wC+fUvnZdmrTJD2WJFLuoYvZbxILmycEvPg==", "dev": true, "requires": { - "@truffle/abi-utils": "^0.3.9", - "@truffle/compile-common": "^0.9.4", + "@truffle/abi-utils": "^1.0.0", + "@truffle/compile-common": "^0.9.5", "big.js": "^6.0.3", "bn.js": "^5.1.3", "cbor": "^5.2.0", @@ -18124,442 +17295,115 @@ "lodash": "^4.17.21", "semver": "7.3.7", "utf8": "^3.0.0", - "web3-utils": "1.8.2" + "web3-utils": "1.10.0" }, "dependencies": { "bignumber.js": { "version": "9.1.1", "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.1.tgz", - "integrity": "sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig==", - "dev": true - }, - "bn.js": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", - "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", - "dev": true - }, - "cbor": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/cbor/-/cbor-5.2.0.tgz", - "integrity": "sha512-5IMhi9e1QU76ppa5/ajP1BmMWZ2FHkhAhjeVKQ/EFCgYSEaeVaoGtL7cxJskf9oCCk+XjzaIdc3IuU/dbA/o2A==", - "dev": true, - "requires": { - "bignumber.js": "^9.0.1", - "nofilter": "^1.0.4" - } - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "nofilter": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/nofilter/-/nofilter-1.0.4.tgz", - "integrity": "sha512-N8lidFp+fCz+TD51+haYdbDGrcBWwuHX40F5+z0qkUjMJ5Tp+rdSuAkMJ9N9eoolDlEVTf6u5icM+cNKkKW2mA==", - "dev": true - }, - "semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "web3-utils": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.8.2.tgz", - "integrity": "sha512-v7j6xhfLQfY7xQDrUP0BKbaNrmZ2/+egbqP9q3KYmOiPpnvAfol+32slgL0WX/5n8VPvKCK5EZ1HGrAVICSToA==", - "dev": true, - "requires": { - "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" - } - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - } - } - }, - "@truffle/compile-common": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/@truffle/compile-common/-/compile-common-0.9.4.tgz", - "integrity": "sha512-mnqJB/hLiPHNf+WKwt/2MH6lv34xSG/SFCib7+ckAklutUqVLeFo8EwQxinuHNkU7LY0C+YgZXhK1WTCO5YRJQ==", - "dev": true, - "requires": { - "@truffle/error": "^0.2.0", - "colors": "1.4.0" - }, - "dependencies": { - "@truffle/error": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@truffle/error/-/error-0.2.0.tgz", - "integrity": "sha512-Fe0/z4WWb7IP2gBnv3l6zqP87Y0kSMs7oiSLakKJq17q3GUunrHSdioKuNspdggxkXIBhEQLhi8C+LJdwmHKWQ==", - "dev": true - } - } - }, - "@truffle/contract": { - "version": "4.6.20", - "resolved": "https://registry.npmjs.org/@truffle/contract/-/contract-4.6.20.tgz", - "integrity": "sha512-s7Mbc37L/CF5Apy/cjPnalkgACmG9tTAmcIW28cIZLRLOUAze18pqhtdHryxAQhEOtKGaDAho6TriqL7/74uHw==", - "dev": true, - "requires": { - "@ensdomains/ensjs": "^2.1.0", - "@truffle/blockchain-utils": "^0.1.7", - "@truffle/contract-schema": "^3.4.13", - "@truffle/debug-utils": "^6.0.48", - "@truffle/error": "^0.2.0", - "@truffle/interface-adapter": "^0.5.32", - "bignumber.js": "^7.2.1", - "debug": "^4.3.1", - "ethers": "^4.0.32", - "web3": "1.8.2", - "web3-core-helpers": "1.8.2", - "web3-core-promievent": "1.8.2", - "web3-eth-abi": "1.8.2", - "web3-utils": "1.8.2" - }, - "dependencies": { - "@truffle/error": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@truffle/error/-/error-0.2.0.tgz", - "integrity": "sha512-Fe0/z4WWb7IP2gBnv3l6zqP87Y0kSMs7oiSLakKJq17q3GUunrHSdioKuNspdggxkXIBhEQLhi8C+LJdwmHKWQ==", - "dev": true - }, - "eth-lib": { - "version": "0.2.8", - "resolved": "https://registry.npmjs.org/eth-lib/-/eth-lib-0.2.8.tgz", - "integrity": "sha512-ArJ7x1WcWOlSpzdoTBX8vkwlkSQ85CjjifSZtV4co64vWxSV8geWfPI9x4SVYu3DSxnX4yWFVTtGL+j9DUFLNw==", - "dev": true, - "requires": { - "bn.js": "^4.11.6", - "elliptic": "^6.4.0", - "xhr-request-promise": "^0.1.2" - } - }, - "uuid": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", - "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", - "dev": true - }, - "web3": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3/-/web3-1.8.2.tgz", - "integrity": "sha512-92h0GdEHW9wqDICQQKyG4foZBYi0OQkyg4CRml2F7XBl/NG+fu9o6J19kzfFXzSBoA4DnJXbyRgj/RHZv5LRiw==", - "dev": true, - "requires": { - "web3-bzz": "1.8.2", - "web3-core": "1.8.2", - "web3-eth": "1.8.2", - "web3-eth-personal": "1.8.2", - "web3-net": "1.8.2", - "web3-shh": "1.8.2", - "web3-utils": "1.8.2" - } - }, - "web3-bzz": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-bzz/-/web3-bzz-1.8.2.tgz", - "integrity": "sha512-1EEnxjPnFnvNWw3XeeKuTR8PBxYd0+XWzvaLK7OJC/Go9O8llLGxrxICbKV+8cgIE0sDRBxiYx02X+6OhoAQ9w==", - "dev": true, - "requires": { - "@types/node": "^12.12.6", - "got": "12.1.0", - "swarm-js": "^0.1.40" - } - }, - "web3-core": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-core/-/web3-core-1.8.2.tgz", - "integrity": "sha512-DJTVEAYcNqxkqruJE+Rxp3CIv0y5AZMwPHQmOkz/cz+MM75SIzMTc0AUdXzGyTS8xMF8h3YWMQGgGEy8SBf1PQ==", - "dev": true, - "requires": { - "@types/bn.js": "^5.1.0", - "@types/node": "^12.12.6", - "bignumber.js": "^9.0.0", - "web3-core-helpers": "1.8.2", - "web3-core-method": "1.8.2", - "web3-core-requestmanager": "1.8.2", - "web3-utils": "1.8.2" - }, - "dependencies": { - "bignumber.js": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.1.tgz", - "integrity": "sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig==", - "dev": true - } - } - }, - "web3-core-helpers": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-core-helpers/-/web3-core-helpers-1.8.2.tgz", - "integrity": "sha512-6B1eLlq9JFrfealZBomd1fmlq1o4A09vrCVQSa51ANoib/jllT3atZrRDr0zt1rfI7TSZTZBXdN/aTdeN99DWw==", - "dev": true, - "requires": { - "web3-eth-iban": "1.8.2", - "web3-utils": "1.8.2" - } - }, - "web3-core-method": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-core-method/-/web3-core-method-1.8.2.tgz", - "integrity": "sha512-1qnr5mw5wVyULzLOrk4B+ryO3gfGjGd/fx8NR+J2xCGLf1e6OSjxT9vbfuQ3fErk/NjSTWWreieYWLMhaogcRA==", - "dev": true, - "requires": { - "@ethersproject/transactions": "^5.6.2", - "web3-core-helpers": "1.8.2", - "web3-core-promievent": "1.8.2", - "web3-core-subscriptions": "1.8.2", - "web3-utils": "1.8.2" - } - }, - "web3-core-promievent": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-core-promievent/-/web3-core-promievent-1.8.2.tgz", - "integrity": "sha512-nvkJWDVgoOSsolJldN33tKW6bKKRJX3MCPDYMwP5SUFOA/mCzDEoI88N0JFofDTXkh1k7gOqp1pvwi9heuaxGg==", - "dev": true, - "requires": { - "eventemitter3": "4.0.4" - } - }, - "web3-core-requestmanager": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-core-requestmanager/-/web3-core-requestmanager-1.8.2.tgz", - "integrity": "sha512-p1d090RYs5Mu7DK1yyc3GCBVZB/03rBtFhYFoS2EruGzOWs/5Q0grgtpwS/DScdRAm8wB8mYEBhY/RKJWF6B2g==", - "dev": true, - "requires": { - "util": "^0.12.5", - "web3-core-helpers": "1.8.2", - "web3-providers-http": "1.8.2", - "web3-providers-ipc": "1.8.2", - "web3-providers-ws": "1.8.2" - } - }, - "web3-core-subscriptions": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-core-subscriptions/-/web3-core-subscriptions-1.8.2.tgz", - "integrity": "sha512-vXQogHDmAIQcKpXvGiMddBUeP9lnKgYF64+yQJhPNE5PnWr1sAibXuIPV7mIPihpFr/n/DORRj6Wh1pUv9zaTw==", - "dev": true, - "requires": { - "eventemitter3": "4.0.4", - "web3-core-helpers": "1.8.2" - } - }, - "web3-eth": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-eth/-/web3-eth-1.8.2.tgz", - "integrity": "sha512-JoTiWWc4F4TInpbvDUGb0WgDYJsFhuIjJlinc5ByjWD88Gvh+GKLsRjjFdbqe5YtwIGT4NymwoC5LQd1K6u/QQ==", - "dev": true, - "requires": { - "web3-core": "1.8.2", - "web3-core-helpers": "1.8.2", - "web3-core-method": "1.8.2", - "web3-core-subscriptions": "1.8.2", - "web3-eth-abi": "1.8.2", - "web3-eth-accounts": "1.8.2", - "web3-eth-contract": "1.8.2", - "web3-eth-ens": "1.8.2", - "web3-eth-iban": "1.8.2", - "web3-eth-personal": "1.8.2", - "web3-net": "1.8.2", - "web3-utils": "1.8.2" - } - }, - "web3-eth-abi": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-eth-abi/-/web3-eth-abi-1.8.2.tgz", - "integrity": "sha512-Om9g3kaRNjqiNPAgKwGT16y+ZwtBzRe4ZJFGjLiSs6v5I7TPNF+rRMWuKnR6jq0azQZDj6rblvKFMA49/k48Og==", - "dev": true, - "requires": { - "@ethersproject/abi": "^5.6.3", - "web3-utils": "1.8.2" - } - }, - "web3-eth-accounts": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-eth-accounts/-/web3-eth-accounts-1.8.2.tgz", - "integrity": "sha512-c367Ij63VCz9YdyjiHHWLFtN85l6QghgwMQH2B1eM/p9Y5lTlTX7t/Eg/8+f1yoIStXbk2w/PYM2lk+IkbqdLA==", - "dev": true, - "requires": { - "@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.8.2", - "web3-core-helpers": "1.8.2", - "web3-core-method": "1.8.2", - "web3-utils": "1.8.2" - } - }, - "web3-eth-contract": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-eth-contract/-/web3-eth-contract-1.8.2.tgz", - "integrity": "sha512-ID5A25tHTSBNwOPjiXSVzxruz006ULRIDbzWTYIFTp7NJ7vXu/kynKK2ag/ObuTqBpMbobP8nXcA9b5EDkIdQA==", - "dev": true, - "requires": { - "@types/bn.js": "^5.1.0", - "web3-core": "1.8.2", - "web3-core-helpers": "1.8.2", - "web3-core-method": "1.8.2", - "web3-core-promievent": "1.8.2", - "web3-core-subscriptions": "1.8.2", - "web3-eth-abi": "1.8.2", - "web3-utils": "1.8.2" - } - }, - "web3-eth-ens": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-eth-ens/-/web3-eth-ens-1.8.2.tgz", - "integrity": "sha512-PWph7C/CnqdWuu1+SH4U4zdrK4t2HNt0I4XzPYFdv9ugE8EuojselioPQXsVGvjql+Nt3jDLvQvggPqlMbvwRw==", - "dev": true, - "requires": { - "content-hash": "^2.5.2", - "eth-ens-namehash": "2.0.8", - "web3-core": "1.8.2", - "web3-core-helpers": "1.8.2", - "web3-core-promievent": "1.8.2", - "web3-eth-abi": "1.8.2", - "web3-eth-contract": "1.8.2", - "web3-utils": "1.8.2" - } - }, - "web3-eth-iban": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-eth-iban/-/web3-eth-iban-1.8.2.tgz", - "integrity": "sha512-h3vNblDWkWMuYx93Q27TAJz6lhzpP93EiC3+45D6xoz983p6si773vntoQ+H+5aZhwglBtoiBzdh7PSSOnP/xQ==", - "dev": true, - "requires": { - "bn.js": "^5.2.1", - "web3-utils": "1.8.2" - }, - "dependencies": { - "bn.js": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", - "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", - "dev": true - } - } - }, - "web3-eth-personal": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-eth-personal/-/web3-eth-personal-1.8.2.tgz", - "integrity": "sha512-Vg4HfwCr7doiUF/RC+Jz0wT4+cYaXcOWMAW2AHIjHX6Z7Xwa8nrURIeQgeEE62qcEHAzajyAdB1u6bJyTfuCXw==", - "dev": true, - "requires": { - "@types/node": "^12.12.6", - "web3-core": "1.8.2", - "web3-core-helpers": "1.8.2", - "web3-core-method": "1.8.2", - "web3-net": "1.8.2", - "web3-utils": "1.8.2" - } + "integrity": "sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig==", + "dev": true }, - "web3-net": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-net/-/web3-net-1.8.2.tgz", - "integrity": "sha512-1itkDMGmbgb83Dg9nporFes9/fxsU7smJ3oRXlFkg4ZHn8YJyP1MSQFPJWWwSc+GrcCFt4O5IrUTvEkHqE3xag==", - "dev": true, - "requires": { - "web3-core": "1.8.2", - "web3-core-method": "1.8.2", - "web3-utils": "1.8.2" - } + "bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", + "dev": true }, - "web3-providers-http": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-providers-http/-/web3-providers-http-1.8.2.tgz", - "integrity": "sha512-2xY94IIEQd16+b+vIBF4IC1p7GVaz9q4EUFscvMUjtEq4ru4Atdzjs9GP+jmcoo49p70II0UV3bqQcz0TQfVyQ==", + "cbor": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/cbor/-/cbor-5.2.0.tgz", + "integrity": "sha512-5IMhi9e1QU76ppa5/ajP1BmMWZ2FHkhAhjeVKQ/EFCgYSEaeVaoGtL7cxJskf9oCCk+XjzaIdc3IuU/dbA/o2A==", "dev": true, "requires": { - "abortcontroller-polyfill": "^1.7.3", - "cross-fetch": "^3.1.4", - "es6-promise": "^4.2.8", - "web3-core-helpers": "1.8.2" + "bignumber.js": "^9.0.1", + "nofilter": "^1.0.4" } }, - "web3-providers-ipc": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-providers-ipc/-/web3-providers-ipc-1.8.2.tgz", - "integrity": "sha512-p6fqKVGFg+WiXGHWnB1hu43PbvPkDHTz4RgoEzbXugv5rtv5zfYLqm8Ba6lrJOS5ks9kGKR21a0y3NzE3u7V4w==", + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "dev": true, "requires": { - "oboe": "2.1.5", - "web3-core-helpers": "1.8.2" + "yallist": "^4.0.0" } }, - "web3-providers-ws": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-providers-ws/-/web3-providers-ws-1.8.2.tgz", - "integrity": "sha512-3s/4K+wHgbiN+Zrp9YjMq2eqAF6QGABw7wFftPdx+m5hWImV27/MoIx57c6HffNRqZXmCHnfWWFCNHHsi7wXnA==", - "dev": true, - "requires": { - "eventemitter3": "4.0.4", - "web3-core-helpers": "1.8.2", - "websocket": "^1.0.32" - } + "nofilter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/nofilter/-/nofilter-1.0.4.tgz", + "integrity": "sha512-N8lidFp+fCz+TD51+haYdbDGrcBWwuHX40F5+z0qkUjMJ5Tp+rdSuAkMJ9N9eoolDlEVTf6u5icM+cNKkKW2mA==", + "dev": true }, - "web3-shh": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-shh/-/web3-shh-1.8.2.tgz", - "integrity": "sha512-uZ+3MAoNcaJsXXNCDnizKJ5viBNeHOFYsCbFhV755Uu52FswzTOw6DtE7yK9nYXMtIhiSgi7nwl1RYzP8pystw==", + "semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", "dev": true, "requires": { - "web3-core": "1.8.2", - "web3-core-method": "1.8.2", - "web3-core-subscriptions": "1.8.2", - "web3-net": "1.8.2" + "lru-cache": "^6.0.0" } }, - "web3-utils": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.8.2.tgz", - "integrity": "sha512-v7j6xhfLQfY7xQDrUP0BKbaNrmZ2/+egbqP9q3KYmOiPpnvAfol+32slgL0WX/5n8VPvKCK5EZ1HGrAVICSToA==", - "dev": true, - "requires": { - "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" - }, - "dependencies": { - "bn.js": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", - "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", - "dev": true - } - } + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "@truffle/compile-common": { + "version": "0.9.5", + "resolved": "https://registry.npmjs.org/@truffle/compile-common/-/compile-common-0.9.5.tgz", + "integrity": "sha512-qOIT7nYzQsrnpjk8LurKE6EYYvvJIk3rCHfn+xed88aG6F1l4WYtkUKl+Dcwgxgv3LH0khcQOpjqkXK5kUIJ8A==", + "dev": true, + "requires": { + "@truffle/error": "^0.2.0", + "colors": "1.4.0" + }, + "dependencies": { + "@truffle/error": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@truffle/error/-/error-0.2.0.tgz", + "integrity": "sha512-Fe0/z4WWb7IP2gBnv3l6zqP87Y0kSMs7oiSLakKJq17q3GUunrHSdioKuNspdggxkXIBhEQLhi8C+LJdwmHKWQ==", + "dev": true + } + } + }, + "@truffle/contract": { + "version": "4.6.22", + "resolved": "https://registry.npmjs.org/@truffle/contract/-/contract-4.6.22.tgz", + "integrity": "sha512-081tM5CBBLgTQX0Fhzp0nlZHnfgojRXweV7/d6v7LHe6QGrGBmgvUy3EIbO+R3P1uaxeGVijMvB4Ok8md9IpYQ==", + "dev": true, + "requires": { + "@ensdomains/ensjs": "^2.1.0", + "@truffle/blockchain-utils": "^0.1.7", + "@truffle/contract-schema": "^3.4.14", + "@truffle/debug-utils": "^6.0.50", + "@truffle/error": "^0.2.0", + "@truffle/interface-adapter": "^0.5.33", + "bignumber.js": "^7.2.1", + "debug": "^4.3.1", + "ethers": "^4.0.32", + "web3": "1.10.0", + "web3-core-helpers": "1.10.0", + "web3-core-promievent": "1.10.0", + "web3-eth-abi": "1.10.0", + "web3-utils": "1.10.0" + }, + "dependencies": { + "@truffle/error": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@truffle/error/-/error-0.2.0.tgz", + "integrity": "sha512-Fe0/z4WWb7IP2gBnv3l6zqP87Y0kSMs7oiSLakKJq17q3GUunrHSdioKuNspdggxkXIBhEQLhi8C+LJdwmHKWQ==", + "dev": true } } }, "@truffle/contract-schema": { - "version": "3.4.13", - "resolved": "https://registry.npmjs.org/@truffle/contract-schema/-/contract-schema-3.4.13.tgz", - "integrity": "sha512-emG7upuryYFrsPDbHqeASPWXL824M1tinhQwSPG0phSoa3g+RX9fUNNN/VPmF3tSkXLWUMhRnb7ehxnaCuRbZg==", + "version": "3.4.14", + "resolved": "https://registry.npmjs.org/@truffle/contract-schema/-/contract-schema-3.4.14.tgz", + "integrity": "sha512-IwVQZG9RVNwTdn321+jbFIcky3/kZLkCtq8tqil4jZwivvmZQg8rIVC8GJ7Lkrmixl9/yTyQNL6GtIUUvkZxyA==", "dev": true, "requires": { "ajv": "^6.10.0", @@ -18567,12 +17411,12 @@ } }, "@truffle/debug-utils": { - "version": "6.0.48", - "resolved": "https://registry.npmjs.org/@truffle/debug-utils/-/debug-utils-6.0.48.tgz", - "integrity": "sha512-HdK/7eH5EFrcTPeZVEgKaKkkzuZ4xsrH8yw+EoLEsScLsOEuQeKynY61NctjuU93voATWrYmV99Sfb/MRq2i2g==", + "version": "6.0.50", + "resolved": "https://registry.npmjs.org/@truffle/debug-utils/-/debug-utils-6.0.50.tgz", + "integrity": "sha512-OWdSoOsPW7/jvcO7ASBRzXDzXQNb7dg8UqwoBAI7j7UpdQoCAhz7JQsusSNiFN6g1qrxEpGzeh5iIMDq9WxO3w==", "dev": true, "requires": { - "@truffle/codec": "^0.14.17", + "@truffle/codec": "^0.15.1", "@trufflesuite/chromafi": "^3.0.0", "bn.js": "^5.1.3", "chalk": "^2.4.2", @@ -18595,323 +17439,21 @@ "dev": true }, "@truffle/interface-adapter": { - "version": "0.5.32", - "resolved": "https://registry.npmjs.org/@truffle/interface-adapter/-/interface-adapter-0.5.32.tgz", - "integrity": "sha512-7Hgmdb4nJUcWtq4vvgWY7Mr2RLOTOp5FZaWyMiFmjkcEEhDlezm2QstALWAXgT0W6q7tDmDBpw3vTIFenRhHBA==", + "version": "0.5.33", + "resolved": "https://registry.npmjs.org/@truffle/interface-adapter/-/interface-adapter-0.5.33.tgz", + "integrity": "sha512-vbVcH2I8hX+wM0Xj9uAjpgxMHqfT+y6m26zSkOVvZ2wo9Ez1slaOJkK1/TZK+7nJitGZSXeJeB4purMDuADvGA==", "dev": true, "requires": { "bn.js": "^5.1.3", "ethers": "^4.0.32", - "web3": "1.8.2" + "web3": "1.10.0" }, "dependencies": { - "bignumber.js": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.1.tgz", - "integrity": "sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig==", - "dev": true - }, "bn.js": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", "dev": true - }, - "eth-lib": { - "version": "0.2.8", - "resolved": "https://registry.npmjs.org/eth-lib/-/eth-lib-0.2.8.tgz", - "integrity": "sha512-ArJ7x1WcWOlSpzdoTBX8vkwlkSQ85CjjifSZtV4co64vWxSV8geWfPI9x4SVYu3DSxnX4yWFVTtGL+j9DUFLNw==", - "dev": true, - "requires": { - "bn.js": "^4.11.6", - "elliptic": "^6.4.0", - "xhr-request-promise": "^0.1.2" - }, - "dependencies": { - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - } - } - }, - "uuid": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", - "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", - "dev": true - }, - "web3": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3/-/web3-1.8.2.tgz", - "integrity": "sha512-92h0GdEHW9wqDICQQKyG4foZBYi0OQkyg4CRml2F7XBl/NG+fu9o6J19kzfFXzSBoA4DnJXbyRgj/RHZv5LRiw==", - "dev": true, - "requires": { - "web3-bzz": "1.8.2", - "web3-core": "1.8.2", - "web3-eth": "1.8.2", - "web3-eth-personal": "1.8.2", - "web3-net": "1.8.2", - "web3-shh": "1.8.2", - "web3-utils": "1.8.2" - } - }, - "web3-bzz": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-bzz/-/web3-bzz-1.8.2.tgz", - "integrity": "sha512-1EEnxjPnFnvNWw3XeeKuTR8PBxYd0+XWzvaLK7OJC/Go9O8llLGxrxICbKV+8cgIE0sDRBxiYx02X+6OhoAQ9w==", - "dev": true, - "requires": { - "@types/node": "^12.12.6", - "got": "12.1.0", - "swarm-js": "^0.1.40" - } - }, - "web3-core": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-core/-/web3-core-1.8.2.tgz", - "integrity": "sha512-DJTVEAYcNqxkqruJE+Rxp3CIv0y5AZMwPHQmOkz/cz+MM75SIzMTc0AUdXzGyTS8xMF8h3YWMQGgGEy8SBf1PQ==", - "dev": true, - "requires": { - "@types/bn.js": "^5.1.0", - "@types/node": "^12.12.6", - "bignumber.js": "^9.0.0", - "web3-core-helpers": "1.8.2", - "web3-core-method": "1.8.2", - "web3-core-requestmanager": "1.8.2", - "web3-utils": "1.8.2" - } - }, - "web3-core-helpers": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-core-helpers/-/web3-core-helpers-1.8.2.tgz", - "integrity": "sha512-6B1eLlq9JFrfealZBomd1fmlq1o4A09vrCVQSa51ANoib/jllT3atZrRDr0zt1rfI7TSZTZBXdN/aTdeN99DWw==", - "dev": true, - "requires": { - "web3-eth-iban": "1.8.2", - "web3-utils": "1.8.2" - } - }, - "web3-core-method": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-core-method/-/web3-core-method-1.8.2.tgz", - "integrity": "sha512-1qnr5mw5wVyULzLOrk4B+ryO3gfGjGd/fx8NR+J2xCGLf1e6OSjxT9vbfuQ3fErk/NjSTWWreieYWLMhaogcRA==", - "dev": true, - "requires": { - "@ethersproject/transactions": "^5.6.2", - "web3-core-helpers": "1.8.2", - "web3-core-promievent": "1.8.2", - "web3-core-subscriptions": "1.8.2", - "web3-utils": "1.8.2" - } - }, - "web3-core-promievent": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-core-promievent/-/web3-core-promievent-1.8.2.tgz", - "integrity": "sha512-nvkJWDVgoOSsolJldN33tKW6bKKRJX3MCPDYMwP5SUFOA/mCzDEoI88N0JFofDTXkh1k7gOqp1pvwi9heuaxGg==", - "dev": true, - "requires": { - "eventemitter3": "4.0.4" - } - }, - "web3-core-requestmanager": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-core-requestmanager/-/web3-core-requestmanager-1.8.2.tgz", - "integrity": "sha512-p1d090RYs5Mu7DK1yyc3GCBVZB/03rBtFhYFoS2EruGzOWs/5Q0grgtpwS/DScdRAm8wB8mYEBhY/RKJWF6B2g==", - "dev": true, - "requires": { - "util": "^0.12.5", - "web3-core-helpers": "1.8.2", - "web3-providers-http": "1.8.2", - "web3-providers-ipc": "1.8.2", - "web3-providers-ws": "1.8.2" - } - }, - "web3-core-subscriptions": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-core-subscriptions/-/web3-core-subscriptions-1.8.2.tgz", - "integrity": "sha512-vXQogHDmAIQcKpXvGiMddBUeP9lnKgYF64+yQJhPNE5PnWr1sAibXuIPV7mIPihpFr/n/DORRj6Wh1pUv9zaTw==", - "dev": true, - "requires": { - "eventemitter3": "4.0.4", - "web3-core-helpers": "1.8.2" - } - }, - "web3-eth": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-eth/-/web3-eth-1.8.2.tgz", - "integrity": "sha512-JoTiWWc4F4TInpbvDUGb0WgDYJsFhuIjJlinc5ByjWD88Gvh+GKLsRjjFdbqe5YtwIGT4NymwoC5LQd1K6u/QQ==", - "dev": true, - "requires": { - "web3-core": "1.8.2", - "web3-core-helpers": "1.8.2", - "web3-core-method": "1.8.2", - "web3-core-subscriptions": "1.8.2", - "web3-eth-abi": "1.8.2", - "web3-eth-accounts": "1.8.2", - "web3-eth-contract": "1.8.2", - "web3-eth-ens": "1.8.2", - "web3-eth-iban": "1.8.2", - "web3-eth-personal": "1.8.2", - "web3-net": "1.8.2", - "web3-utils": "1.8.2" - } - }, - "web3-eth-abi": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-eth-abi/-/web3-eth-abi-1.8.2.tgz", - "integrity": "sha512-Om9g3kaRNjqiNPAgKwGT16y+ZwtBzRe4ZJFGjLiSs6v5I7TPNF+rRMWuKnR6jq0azQZDj6rblvKFMA49/k48Og==", - "dev": true, - "requires": { - "@ethersproject/abi": "^5.6.3", - "web3-utils": "1.8.2" - } - }, - "web3-eth-accounts": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-eth-accounts/-/web3-eth-accounts-1.8.2.tgz", - "integrity": "sha512-c367Ij63VCz9YdyjiHHWLFtN85l6QghgwMQH2B1eM/p9Y5lTlTX7t/Eg/8+f1yoIStXbk2w/PYM2lk+IkbqdLA==", - "dev": true, - "requires": { - "@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.8.2", - "web3-core-helpers": "1.8.2", - "web3-core-method": "1.8.2", - "web3-utils": "1.8.2" - } - }, - "web3-eth-contract": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-eth-contract/-/web3-eth-contract-1.8.2.tgz", - "integrity": "sha512-ID5A25tHTSBNwOPjiXSVzxruz006ULRIDbzWTYIFTp7NJ7vXu/kynKK2ag/ObuTqBpMbobP8nXcA9b5EDkIdQA==", - "dev": true, - "requires": { - "@types/bn.js": "^5.1.0", - "web3-core": "1.8.2", - "web3-core-helpers": "1.8.2", - "web3-core-method": "1.8.2", - "web3-core-promievent": "1.8.2", - "web3-core-subscriptions": "1.8.2", - "web3-eth-abi": "1.8.2", - "web3-utils": "1.8.2" - } - }, - "web3-eth-ens": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-eth-ens/-/web3-eth-ens-1.8.2.tgz", - "integrity": "sha512-PWph7C/CnqdWuu1+SH4U4zdrK4t2HNt0I4XzPYFdv9ugE8EuojselioPQXsVGvjql+Nt3jDLvQvggPqlMbvwRw==", - "dev": true, - "requires": { - "content-hash": "^2.5.2", - "eth-ens-namehash": "2.0.8", - "web3-core": "1.8.2", - "web3-core-helpers": "1.8.2", - "web3-core-promievent": "1.8.2", - "web3-eth-abi": "1.8.2", - "web3-eth-contract": "1.8.2", - "web3-utils": "1.8.2" - } - }, - "web3-eth-iban": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-eth-iban/-/web3-eth-iban-1.8.2.tgz", - "integrity": "sha512-h3vNblDWkWMuYx93Q27TAJz6lhzpP93EiC3+45D6xoz983p6si773vntoQ+H+5aZhwglBtoiBzdh7PSSOnP/xQ==", - "dev": true, - "requires": { - "bn.js": "^5.2.1", - "web3-utils": "1.8.2" - } - }, - "web3-eth-personal": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-eth-personal/-/web3-eth-personal-1.8.2.tgz", - "integrity": "sha512-Vg4HfwCr7doiUF/RC+Jz0wT4+cYaXcOWMAW2AHIjHX6Z7Xwa8nrURIeQgeEE62qcEHAzajyAdB1u6bJyTfuCXw==", - "dev": true, - "requires": { - "@types/node": "^12.12.6", - "web3-core": "1.8.2", - "web3-core-helpers": "1.8.2", - "web3-core-method": "1.8.2", - "web3-net": "1.8.2", - "web3-utils": "1.8.2" - } - }, - "web3-net": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-net/-/web3-net-1.8.2.tgz", - "integrity": "sha512-1itkDMGmbgb83Dg9nporFes9/fxsU7smJ3oRXlFkg4ZHn8YJyP1MSQFPJWWwSc+GrcCFt4O5IrUTvEkHqE3xag==", - "dev": true, - "requires": { - "web3-core": "1.8.2", - "web3-core-method": "1.8.2", - "web3-utils": "1.8.2" - } - }, - "web3-providers-http": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-providers-http/-/web3-providers-http-1.8.2.tgz", - "integrity": "sha512-2xY94IIEQd16+b+vIBF4IC1p7GVaz9q4EUFscvMUjtEq4ru4Atdzjs9GP+jmcoo49p70II0UV3bqQcz0TQfVyQ==", - "dev": true, - "requires": { - "abortcontroller-polyfill": "^1.7.3", - "cross-fetch": "^3.1.4", - "es6-promise": "^4.2.8", - "web3-core-helpers": "1.8.2" - } - }, - "web3-providers-ipc": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-providers-ipc/-/web3-providers-ipc-1.8.2.tgz", - "integrity": "sha512-p6fqKVGFg+WiXGHWnB1hu43PbvPkDHTz4RgoEzbXugv5rtv5zfYLqm8Ba6lrJOS5ks9kGKR21a0y3NzE3u7V4w==", - "dev": true, - "requires": { - "oboe": "2.1.5", - "web3-core-helpers": "1.8.2" - } - }, - "web3-providers-ws": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-providers-ws/-/web3-providers-ws-1.8.2.tgz", - "integrity": "sha512-3s/4K+wHgbiN+Zrp9YjMq2eqAF6QGABw7wFftPdx+m5hWImV27/MoIx57c6HffNRqZXmCHnfWWFCNHHsi7wXnA==", - "dev": true, - "requires": { - "eventemitter3": "4.0.4", - "web3-core-helpers": "1.8.2", - "websocket": "^1.0.32" - } - }, - "web3-shh": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-shh/-/web3-shh-1.8.2.tgz", - "integrity": "sha512-uZ+3MAoNcaJsXXNCDnizKJ5viBNeHOFYsCbFhV755Uu52FswzTOw6DtE7yK9nYXMtIhiSgi7nwl1RYzP8pystw==", - "dev": true, - "requires": { - "web3-core": "1.8.2", - "web3-core-method": "1.8.2", - "web3-core-subscriptions": "1.8.2", - "web3-net": "1.8.2" - } - }, - "web3-utils": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.8.2.tgz", - "integrity": "sha512-v7j6xhfLQfY7xQDrUP0BKbaNrmZ2/+egbqP9q3KYmOiPpnvAfol+32slgL0WX/5n8VPvKCK5EZ1HGrAVICSToA==", - "dev": true, - "requires": { - "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" - } } } }, @@ -19280,9 +17822,9 @@ } }, "antlr4": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/antlr4/-/antlr4-4.12.0.tgz", - "integrity": "sha512-23iB5IzXJZRZeK9TigzUyrNc9pSmNqAerJRBcNq1ETrmttMWRgaYZzC561IgEO3ygKsDJTYDTozABXa4b/fTQQ==", + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/antlr4/-/antlr4-4.13.0.tgz", + "integrity": "sha512-zooUbt+UscjnWyOrsuY/tVFL4rwrAGwOivpQmvmUDE22hy/lUA467Rc1rcixyRwcRUIXFYBwv7+dClDSHdmmew==", "dev": true }, "antlr4ts": { @@ -19629,9 +18171,9 @@ } }, "breakword": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/breakword/-/breakword-1.0.5.tgz", - "integrity": "sha512-ex5W9DoOQ/LUEU3PMdLs9ua/CYZl1678NUkKOdUSi8Aw5F1idieaiRURCBFJCwVcrD1J8Iy3vfWSloaMwO2qFg==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/breakword/-/breakword-1.0.6.tgz", + "integrity": "sha512-yjxDAYyK/pBvws9H4xKYpLDpYKEH6CzrBPAuXq3x18I+c/2MkVtT3qAr7Oloi6Dss9qNhPVueAAVU1CSeNDIXw==", "dev": true, "requires": { "wcwidth": "^1.0.1" @@ -20365,23 +18907,12 @@ } }, "cross-fetch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", - "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz", + "integrity": "sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==", "dev": true, "requires": { - "node-fetch": "2.6.7" - }, - "dependencies": { - "node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "dev": true, - "requires": { - "whatwg-url": "^5.0.0" - } - } + "node-fetch": "^2.6.11" } }, "cross-spawn": { @@ -21007,15 +19538,15 @@ } }, "eslint": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.39.0.tgz", - "integrity": "sha512-mwiok6cy7KTW7rBpo05k6+p4YVZByLNjAZ/ACB9DRCu4YDRwjXI01tWHp6KAUWelsBetTxKK/2sHB0vdS8Z2Og==", + "version": "8.41.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.41.0.tgz", + "integrity": "sha512-WQDQpzGBOP5IrXPo4Hc0814r4/v2rrIsB0rhT7jtunIalgg6gYXWhRMOejVO8yH21T/FGaxjmFjBMNqcIlmH1Q==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.4.0", - "@eslint/eslintrc": "^2.0.2", - "@eslint/js": "8.39.0", + "@eslint/eslintrc": "^2.0.3", + "@eslint/js": "8.41.0", "@humanwhocodes/config-array": "^0.11.8", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", @@ -21026,8 +19557,8 @@ "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", "eslint-scope": "^7.2.0", - "eslint-visitor-keys": "^3.4.0", - "espree": "^9.5.1", + "eslint-visitor-keys": "^3.4.1", + "espree": "^9.5.2", "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -21035,13 +19566,12 @@ "find-up": "^5.0.0", "glob-parent": "^6.0.2", "globals": "^13.19.0", - "grapheme-splitter": "^1.0.4", + "graphemer": "^1.4.0", "ignore": "^5.2.0", "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", - "js-sdsl": "^4.1.4", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", @@ -21196,20 +19726,20 @@ } }, "eslint-visitor-keys": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz", - "integrity": "sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", + "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", "dev": true }, "espree": { - "version": "9.5.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.1.tgz", - "integrity": "sha512-5yxtHSZXRSW5pvv3hAlXM5+/Oswi1AUFqBmbibKb5s6bp3rGIDkyXU6xCoyuuLhijr4SFwPrXRoZjz0AZDN9tg==", + "version": "9.5.2", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.2.tgz", + "integrity": "sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==", "dev": true, "requires": { "acorn": "^8.8.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.0" + "eslint-visitor-keys": "^3.4.1" } }, "esprima": { @@ -22132,9 +20662,9 @@ "dev": true }, "fast-diff": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", - "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", "dev": true }, "fast-glob": { @@ -22401,13 +20931,14 @@ "dev": true }, "get-intrinsic": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", - "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", + "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", "dev": true, "requires": { "function-bind": "^1.1.1", "has": "^1.0.3", + "has-proto": "^1.0.1", "has-symbols": "^1.0.3" } }, @@ -22609,6 +21140,12 @@ "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", "dev": true }, + "graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, "graphlib": { "version": "2.1.8", "resolved": "https://registry.npmjs.org/graphlib/-/graphlib-2.1.8.tgz", @@ -22668,9 +21205,9 @@ "dev": true }, "hardhat": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.14.0.tgz", - "integrity": "sha512-73jsInY4zZahMSVFurSK+5TNCJTXMv+vemvGia0Ac34Mm19fYp6vEPVGF3sucbumszsYxiTT2TbS8Ii2dsDSoQ==", + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.14.1.tgz", + "integrity": "sha512-H3Qp/UKyQGmPDDBSfMoSyH18rRnac90rsb0LNer+sKe6at6rxLe4D5j+M+1icqZQF02iLPjNRwc/PA8OPf757A==", "dev": true, "requires": { "@ethersproject/abi": "^5.1.2", @@ -22883,9 +21420,9 @@ } }, "hardhat-exposed": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/hardhat-exposed/-/hardhat-exposed-0.3.3.tgz", - "integrity": "sha512-AFBM3IUlSU9wkt3886kYbCSzZteB4OQt0ciehPUrCgY/Tazn6g2cxsmoIZT8O8va1R2PtXd9PRC4KZ6WiSVXOw==", + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/hardhat-exposed/-/hardhat-exposed-0.3.6.tgz", + "integrity": "sha512-jb03M+GolhEfjDYJyPkGJzcAs1/eZrVufMADXQDwng2iuUJD1zgzM3k1oq2YQihRxguQDsLE1nuHIn6N8o5GmQ==", "dev": true, "requires": { "micromatch": "^4.0.4", @@ -24653,9 +23190,9 @@ } }, "node-fetch": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", - "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==", + "version": "2.6.11", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.11.tgz", + "integrity": "sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==", "dev": true, "requires": { "whatwg-url": "^5.0.0" @@ -25288,9 +23825,9 @@ "dev": true }, "qs": { - "version": "6.11.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.1.tgz", - "integrity": "sha512-0wsrzgTz/kAVIeuxSjnpGC56rzYtr6JT/2BwEvMaPhFIoYa1aGO8LbzuU1R0uUYQkLpWBTOj0l/CLAJB64J6nQ==", + "version": "6.11.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", + "integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==", "dev": true, "requires": { "side-channel": "^1.0.4" @@ -25845,9 +24382,9 @@ } }, "semver": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", - "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", + "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -26653,9 +25190,9 @@ } }, "solidity-ast": { - "version": "0.4.46", - "resolved": "https://registry.npmjs.org/solidity-ast/-/solidity-ast-0.4.46.tgz", - "integrity": "sha512-MlPZQfPhjWXqh7YxWcBGDXaPZIfMYCOHYoLEhGDWulNwEPIQQZuB7mA9eP17CU0jY/bGR4avCEUVVpvHtT2gbA==", + "version": "0.4.49", + "resolved": "https://registry.npmjs.org/solidity-ast/-/solidity-ast-0.4.49.tgz", + "integrity": "sha512-Pr5sCAj1SFqzwFZw1HPKSq0PehlQNdM8GwKyAVYh2DOn7/cCK8LUKD1HeHnKtTgBW7hi9h4nnnan7hpAg5RhWQ==", "dev": true }, "solidity-comments": { @@ -28150,24 +26687,24 @@ } }, "web3": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/web3/-/web3-1.9.0.tgz", - "integrity": "sha512-E9IvVy/d2ozfQQsCiV+zh/LmlZGv9fQxI0UedDVjm87yOKf4AYbBNEn1iWtHveiGzAk2CEMZMUzAZzaQNSSYog==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/web3/-/web3-1.10.0.tgz", + "integrity": "sha512-YfKY9wSkGcM8seO+daR89oVTcbu18NsVfvOngzqMYGUU0pPSQmE57qQDvQzUeoIOHAnXEBNzrhjQJmm8ER0rng==", "dev": true, "requires": { - "web3-bzz": "1.9.0", - "web3-core": "1.9.0", - "web3-eth": "1.9.0", - "web3-eth-personal": "1.9.0", - "web3-net": "1.9.0", - "web3-shh": "1.9.0", - "web3-utils": "1.9.0" + "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-bzz": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/web3-bzz/-/web3-bzz-1.9.0.tgz", - "integrity": "sha512-9Zli9dikX8GdHwBb5/WPzpSVuy3EWMKY3P4EokCQra31fD7DLizqAAaTUsFwnK7xYkw5ogpHgelw9uKHHzNajg==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/web3-bzz/-/web3-bzz-1.10.0.tgz", + "integrity": "sha512-o9IR59io3pDUsXTsps5pO5hW1D5zBmg46iNc2t4j2DkaYHNdDLwk2IP9ukoM2wg47QILfPEJYzhTfkS/CcX0KA==", "dev": true, "requires": { "@types/node": "^12.12.6", @@ -28176,18 +26713,18 @@ } }, "web3-core": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/web3-core/-/web3-core-1.9.0.tgz", - "integrity": "sha512-DZ+TPmq/ZLlx4LSVzFgrHCP/QFpKDbGWO4HoquZSdu24cjk5SZ+FEU1SZB2OaK3/bgBh+25mRbmv8y56ysUu1w==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/web3-core/-/web3-core-1.10.0.tgz", + "integrity": "sha512-fWySwqy2hn3TL89w5TM8wXF1Z2Q6frQTKHWmP0ppRQorEK8NcHJRfeMiv/mQlSKoTS1F6n/nv2uyZsixFycjYQ==", "dev": true, "requires": { "@types/bn.js": "^5.1.1", "@types/node": "^12.12.6", "bignumber.js": "^9.0.0", - "web3-core-helpers": "1.9.0", - "web3-core-method": "1.9.0", - "web3-core-requestmanager": "1.9.0", - "web3-utils": "1.9.0" + "web3-core-helpers": "1.10.0", + "web3-core-method": "1.10.0", + "web3-core-requestmanager": "1.10.0", + "web3-utils": "1.10.0" }, "dependencies": { "bignumber.js": { @@ -28199,94 +26736,94 @@ } }, "web3-core-helpers": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/web3-core-helpers/-/web3-core-helpers-1.9.0.tgz", - "integrity": "sha512-NeJzylAp9Yj9xAt2uTT+kyug3X0DLnfBdnAcGZuY6HhoNPDIfQRA9CkJjLngVRlGTLZGjNp9x9eR+RyZQgUlXg==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/web3-core-helpers/-/web3-core-helpers-1.10.0.tgz", + "integrity": "sha512-pIxAzFDS5vnbXvfvLSpaA1tfRykAe9adw43YCKsEYQwH0gCLL0kMLkaCX3q+Q8EVmAh+e1jWL/nl9U0de1+++g==", "dev": true, "requires": { - "web3-eth-iban": "1.9.0", - "web3-utils": "1.9.0" + "web3-eth-iban": "1.10.0", + "web3-utils": "1.10.0" } }, "web3-core-method": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/web3-core-method/-/web3-core-method-1.9.0.tgz", - "integrity": "sha512-sswbNsY2xRBBhGeaLt9c/eDc+0yDDhi6keUBAkgIRa9ueSx/VKzUY9HMqiV6bXDcGT2fJyejq74FfEB4lc/+/w==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/web3-core-method/-/web3-core-method-1.10.0.tgz", + "integrity": "sha512-4R700jTLAMKDMhQ+nsVfIXvH6IGJlJzGisIfMKWAIswH31h5AZz7uDUW2YctI+HrYd+5uOAlS4OJeeT9bIpvkA==", "dev": true, "requires": { "@ethersproject/transactions": "^5.6.2", - "web3-core-helpers": "1.9.0", - "web3-core-promievent": "1.9.0", - "web3-core-subscriptions": "1.9.0", - "web3-utils": "1.9.0" + "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-promievent": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/web3-core-promievent/-/web3-core-promievent-1.9.0.tgz", - "integrity": "sha512-PHG1Mn23IGwMZhnPDN8dETKypqsFbHfiyRqP+XsVMPmTHkVfzDQTCBU/c2r6hUktBDoGKut5xZQpGfhFk71KbQ==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/web3-core-promievent/-/web3-core-promievent-1.10.0.tgz", + "integrity": "sha512-68N7k5LWL5R38xRaKFrTFT2pm2jBNFaM4GioS00YjAKXRQ3KjmhijOMG3TICz6Aa5+6GDWYelDNx21YAeZ4YTg==", "dev": true, "requires": { "eventemitter3": "4.0.4" } }, "web3-core-requestmanager": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/web3-core-requestmanager/-/web3-core-requestmanager-1.9.0.tgz", - "integrity": "sha512-hcJ5PCtTIJpj+8qWxoseqlCovDo94JJjTX7dZOLXgwp8ah7E3WRYozhGyZocerx+KebKyg1mCQIhkDpMwjfo9Q==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/web3-core-requestmanager/-/web3-core-requestmanager-1.10.0.tgz", + "integrity": "sha512-3z/JKE++Os62APml4dvBM+GAuId4h3L9ckUrj7ebEtS2AR0ixyQPbrBodgL91Sv7j7cQ3Y+hllaluqjguxvSaQ==", "dev": true, "requires": { "util": "^0.12.5", - "web3-core-helpers": "1.9.0", - "web3-providers-http": "1.9.0", - "web3-providers-ipc": "1.9.0", - "web3-providers-ws": "1.9.0" + "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-subscriptions": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/web3-core-subscriptions/-/web3-core-subscriptions-1.9.0.tgz", - "integrity": "sha512-MaIo29yz7hTV8X8bioclPDbHFOVuHmnbMv+D3PDH12ceJFJAXGyW8GL5KU1DYyWIj4TD1HM4WknyVA/YWBiiLA==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/web3-core-subscriptions/-/web3-core-subscriptions-1.10.0.tgz", + "integrity": "sha512-HGm1PbDqsxejI075gxBc5OSkwymilRWZufIy9zEpnWKNmfbuv5FfHgW1/chtJP6aP3Uq2vHkvTDl3smQBb8l+g==", "dev": true, "requires": { "eventemitter3": "4.0.4", - "web3-core-helpers": "1.9.0" + "web3-core-helpers": "1.10.0" } }, "web3-eth": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/web3-eth/-/web3-eth-1.9.0.tgz", - "integrity": "sha512-c5gSWk9bLNr6VPATHmZ1n7LTIefIZQnJMzfnvkoBcIFGKJbGmsuRhv6lEXsKdAO/FlqYnSbaw3fOq1fVFiIOFQ==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/web3-eth/-/web3-eth-1.10.0.tgz", + "integrity": "sha512-Z5vT6slNMLPKuwRyKGbqeGYC87OAy8bOblaqRTgg94CXcn/mmqU7iPIlG4506YdcdK3x6cfEDG7B6w+jRxypKA==", "dev": true, "requires": { - "web3-core": "1.9.0", - "web3-core-helpers": "1.9.0", - "web3-core-method": "1.9.0", - "web3-core-subscriptions": "1.9.0", - "web3-eth-abi": "1.9.0", - "web3-eth-accounts": "1.9.0", - "web3-eth-contract": "1.9.0", - "web3-eth-ens": "1.9.0", - "web3-eth-iban": "1.9.0", - "web3-eth-personal": "1.9.0", - "web3-net": "1.9.0", - "web3-utils": "1.9.0" + "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-abi": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/web3-eth-abi/-/web3-eth-abi-1.9.0.tgz", - "integrity": "sha512-0BLQ3FKMrzJkA930jOX3fMaybAyubk06HChclLpiR0NWmgWXm1tmBrJdkyRy2ZTZpmfuZc9xTFRfl0yZID1voA==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/web3-eth-abi/-/web3-eth-abi-1.10.0.tgz", + "integrity": "sha512-cwS+qRBWpJ43aI9L3JS88QYPfFcSJJ3XapxOQ4j40v6mk7ATpA8CVK1vGTzpihNlOfMVRBkR95oAj7oL6aiDOg==", "dev": true, "requires": { "@ethersproject/abi": "^5.6.3", - "web3-utils": "1.9.0" + "web3-utils": "1.10.0" } }, "web3-eth-accounts": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/web3-eth-accounts/-/web3-eth-accounts-1.9.0.tgz", - "integrity": "sha512-VeIZVevmnSll0AC1k5F/y398ZE89d1SRuYk8IewLUhL/tVAsFEsjl2SGgm0+aDcHmgPrkW+qsCJ+C7rWg/N4ZA==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/web3-eth-accounts/-/web3-eth-accounts-1.10.0.tgz", + "integrity": "sha512-wiq39Uc3mOI8rw24wE2n15hboLE0E9BsQLdlmsL4Zua9diDS6B5abXG0XhFcoNsXIGMWXVZz4TOq3u4EdpXF/Q==", "dev": true, "requires": { "@ethereumjs/common": "2.5.0", @@ -28295,10 +26832,10 @@ "ethereumjs-util": "^7.1.5", "scrypt-js": "^3.0.1", "uuid": "^9.0.0", - "web3-core": "1.9.0", - "web3-core-helpers": "1.9.0", - "web3-core-method": "1.9.0", - "web3-utils": "1.9.0" + "web3-core": "1.10.0", + "web3-core-helpers": "1.10.0", + "web3-core-method": "1.10.0", + "web3-utils": "1.10.0" }, "dependencies": { "eth-lib": { @@ -28321,45 +26858,45 @@ } }, "web3-eth-contract": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/web3-eth-contract/-/web3-eth-contract-1.9.0.tgz", - "integrity": "sha512-+j26hpSaEtAdUed0TN5rnc+YZOcjPxMjFX4ZBKatvFkImdbVv/tzTvcHlltubSpgb2ZLyZ89lSL6phKYwd2zNQ==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/web3-eth-contract/-/web3-eth-contract-1.10.0.tgz", + "integrity": "sha512-MIC5FOzP/+2evDksQQ/dpcXhSqa/2hFNytdl/x61IeWxhh6vlFeSjq0YVTAyIzdjwnL7nEmZpjfI6y6/Ufhy7w==", "dev": true, "requires": { "@types/bn.js": "^5.1.1", - "web3-core": "1.9.0", - "web3-core-helpers": "1.9.0", - "web3-core-method": "1.9.0", - "web3-core-promievent": "1.9.0", - "web3-core-subscriptions": "1.9.0", - "web3-eth-abi": "1.9.0", - "web3-utils": "1.9.0" + "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-ens": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/web3-eth-ens/-/web3-eth-ens-1.9.0.tgz", - "integrity": "sha512-LOJZeN+AGe9arhuExnrPPFYQr4WSxXEkpvYIlst/joOEUNLDwfndHnJIK6PI5mXaYSROBtTx6erv+HupzGo7vA==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/web3-eth-ens/-/web3-eth-ens-1.10.0.tgz", + "integrity": "sha512-3hpGgzX3qjgxNAmqdrC2YUQMTfnZbs4GeLEmy8aCWziVwogbuqQZ+Gzdfrym45eOZodk+lmXyLuAdqkNlvkc1g==", "dev": true, "requires": { "content-hash": "^2.5.2", "eth-ens-namehash": "2.0.8", - "web3-core": "1.9.0", - "web3-core-helpers": "1.9.0", - "web3-core-promievent": "1.9.0", - "web3-eth-abi": "1.9.0", - "web3-eth-contract": "1.9.0", - "web3-utils": "1.9.0" + "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-iban": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/web3-eth-iban/-/web3-eth-iban-1.9.0.tgz", - "integrity": "sha512-jPAm77PuEs1kE/UrrBFJdPD2PN42pwfXA0gFuuw35bZezhskYML9W4QCxcqnUtceyEA4FUn7K2qTMuCk+23fog==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/web3-eth-iban/-/web3-eth-iban-1.10.0.tgz", + "integrity": "sha512-0l+SP3IGhInw7Q20LY3IVafYEuufo4Dn75jAHT7c2aDJsIolvf2Lc6ugHkBajlwUneGfbRQs/ccYPQ9JeMUbrg==", "dev": true, "requires": { "bn.js": "^5.2.1", - "web3-utils": "1.9.0" + "web3-utils": "1.10.0" }, "dependencies": { "bn.js": { @@ -28371,79 +26908,79 @@ } }, "web3-eth-personal": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/web3-eth-personal/-/web3-eth-personal-1.9.0.tgz", - "integrity": "sha512-r9Ldo/luBqJlv1vCUEQnUS+C3a3ZdbYxVHyfDkj6RWMyCqqo8JE41HWE+pfa0RmB1xnGL2g8TbYcHcqItck/qg==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/web3-eth-personal/-/web3-eth-personal-1.10.0.tgz", + "integrity": "sha512-anseKn98w/d703eWq52uNuZi7GhQeVjTC5/svrBWEKob0WZ5kPdo+EZoFN0sp5a5ubbrk/E0xSl1/M5yORMtpg==", "dev": true, "requires": { "@types/node": "^12.12.6", - "web3-core": "1.9.0", - "web3-core-helpers": "1.9.0", - "web3-core-method": "1.9.0", - "web3-net": "1.9.0", - "web3-utils": "1.9.0" + "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-net": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/web3-net/-/web3-net-1.9.0.tgz", - "integrity": "sha512-L+fDZFgrLM5Y15aonl2q6L+RvfaImAngmC0Jv45hV2FJ5IfRT0/2ob9etxZmvEBWvOpbqSvghfOhJIT3XZ37Pg==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/web3-net/-/web3-net-1.10.0.tgz", + "integrity": "sha512-NLH/N3IshYWASpxk4/18Ge6n60GEvWBVeM8inx2dmZJVmRI6SJIlUxbL8jySgiTn3MMZlhbdvrGo8fpUW7a1GA==", "dev": true, "requires": { - "web3-core": "1.9.0", - "web3-core-method": "1.9.0", - "web3-utils": "1.9.0" + "web3-core": "1.10.0", + "web3-core-method": "1.10.0", + "web3-utils": "1.10.0" } }, "web3-providers-http": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/web3-providers-http/-/web3-providers-http-1.9.0.tgz", - "integrity": "sha512-5+dMNDAE0rRFz6SJpfnBqlVi2J5bB/Ivr2SanMt2YUrkxW5t8betZbzVwRkTbwtUvkqgj3xeUQzqpOttiv+IqQ==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/web3-providers-http/-/web3-providers-http-1.10.0.tgz", + "integrity": "sha512-eNr965YB8a9mLiNrkjAWNAPXgmQWfpBfkkn7tpEFlghfww0u3I0tktMZiaToJVcL2+Xq+81cxbkpeWJ5XQDwOA==", "dev": true, "requires": { "abortcontroller-polyfill": "^1.7.3", "cross-fetch": "^3.1.4", "es6-promise": "^4.2.8", - "web3-core-helpers": "1.9.0" + "web3-core-helpers": "1.10.0" } }, "web3-providers-ipc": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/web3-providers-ipc/-/web3-providers-ipc-1.9.0.tgz", - "integrity": "sha512-cPXU93Du40HCylvjaa5x62DbnGqH+86HpK/+kMcFIzF6sDUBhKpag2tSbYhGbj7GMpfkmDTUiiMLdWnFV6+uBA==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/web3-providers-ipc/-/web3-providers-ipc-1.10.0.tgz", + "integrity": "sha512-OfXG1aWN8L1OUqppshzq8YISkWrYHaATW9H8eh0p89TlWMc1KZOL9vttBuaBEi96D/n0eYDn2trzt22bqHWfXA==", "dev": true, "requires": { "oboe": "2.1.5", - "web3-core-helpers": "1.9.0" + "web3-core-helpers": "1.10.0" } }, "web3-providers-ws": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/web3-providers-ws/-/web3-providers-ws-1.9.0.tgz", - "integrity": "sha512-JRVsnQZ7j2k1a2yzBNHe39xqk1ijOv01dfIBFw52VeEkSRzvrOcsPIM/ttSyBuJqt70ntMxXY0ekCrqfleKH/w==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/web3-providers-ws/-/web3-providers-ws-1.10.0.tgz", + "integrity": "sha512-sK0fNcglW36yD5xjnjtSGBnEtf59cbw4vZzJ+CmOWIKGIR96mP5l684g0WD0Eo+f4NQc2anWWXG74lRc9OVMCQ==", "dev": true, "requires": { "eventemitter3": "4.0.4", - "web3-core-helpers": "1.9.0", + "web3-core-helpers": "1.10.0", "websocket": "^1.0.32" } }, "web3-shh": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/web3-shh/-/web3-shh-1.9.0.tgz", - "integrity": "sha512-bIBZlralgz4ICCrwkefB2nPPJWfx28NuHIpjB7d9ADKynElubQuqudYhKtSEkKXACuME/BJm0pIFJcJs/gDnMg==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/web3-shh/-/web3-shh-1.10.0.tgz", + "integrity": "sha512-uNUUuNsO2AjX41GJARV9zJibs11eq6HtOe6Wr0FtRUcj8SN6nHeYIzwstAvJ4fXA53gRqFMTxdntHEt9aXVjpg==", "dev": true, "requires": { - "web3-core": "1.9.0", - "web3-core-method": "1.9.0", - "web3-core-subscriptions": "1.9.0", - "web3-net": "1.9.0" + "web3-core": "1.10.0", + "web3-core-method": "1.10.0", + "web3-core-subscriptions": "1.10.0", + "web3-net": "1.10.0" } }, "web3-utils": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.9.0.tgz", - "integrity": "sha512-p++69rCNNfu2jM9n5+VD/g26l+qkEOQ1m6cfRQCbH8ZRrtquTmrirJMgTmyOoax5a5XRYOuws14aypCOs51pdQ==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.10.0.tgz", + "integrity": "sha512-kSaCM0uMcZTNUSmn5vMEhlo02RObGNRRCkdX0V9UTAU0+lrvn0HSaudyCo6CQzuXUsnuY2ERJGCGPfeWmv19Rg==", "dev": true, "requires": { "bn.js": "^5.2.1", From 5cef83d2c7730fbd523045b551d0efd020ceb17b Mon Sep 17 00:00:00 2001 From: Renan Souza Date: Fri, 2 Jun 2023 11:37:59 -0300 Subject: [PATCH 079/182] Optimize array allocation in ERC1155 (#4196) Co-authored-by: Francisco --- .changeset/serious-books-lie.md | 5 +++++ contracts/token/ERC1155/ERC1155.sol | 30 ++++++++++++++++++----------- 2 files changed, 24 insertions(+), 11 deletions(-) create mode 100644 .changeset/serious-books-lie.md diff --git a/.changeset/serious-books-lie.md b/.changeset/serious-books-lie.md new file mode 100644 index 000000000..6f0a0a732 --- /dev/null +++ b/.changeset/serious-books-lie.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': patch +--- + +`ERC1155`: Optimize array allocation. diff --git a/contracts/token/ERC1155/ERC1155.sol b/contracts/token/ERC1155/ERC1155.sol index a2d7404bf..6761edd24 100644 --- a/contracts/token/ERC1155/ERC1155.sol +++ b/contracts/token/ERC1155/ERC1155.sol @@ -206,8 +206,7 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { function _safeTransferFrom(address from, address to, uint256 id, uint256 amount, bytes memory data) internal { require(to != address(0), "ERC1155: transfer to the zero address"); require(from != address(0), "ERC1155: transfer from the zero address"); - uint256[] memory ids = _asSingletonArray(id); - uint256[] memory amounts = _asSingletonArray(amount); + (uint256[] memory ids, uint256[] memory amounts) = _asSingletonArrays(id, amount); _update(from, to, ids, amounts, data); } @@ -269,8 +268,7 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { */ function _mint(address to, uint256 id, uint256 amount, bytes memory data) internal { require(to != address(0), "ERC1155: mint to the zero address"); - uint256[] memory ids = _asSingletonArray(id); - uint256[] memory amounts = _asSingletonArray(amount); + (uint256[] memory ids, uint256[] memory amounts) = _asSingletonArrays(id, amount); _update(address(0), to, ids, amounts, data); } @@ -302,8 +300,7 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { */ function _burn(address from, uint256 id, uint256 amount) internal { require(from != address(0), "ERC1155: burn from the zero address"); - uint256[] memory ids = _asSingletonArray(id); - uint256[] memory amounts = _asSingletonArray(amount); + (uint256[] memory ids, uint256[] memory amounts) = _asSingletonArrays(id, amount); _update(from, address(0), ids, amounts, ""); } @@ -376,10 +373,21 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { } } - function _asSingletonArray(uint256 element) private pure returns (uint256[] memory) { - uint256[] memory array = new uint256[](1); - array[0] = element; - - return array; + function _asSingletonArrays( + uint256 element1, + uint256 element2 + ) private pure returns (uint256[] memory array1, uint256[] memory array2) { + /// @solidity memory-safe-assembly + assembly { + array1 := mload(0x40) + mstore(array1, 1) + mstore(add(array1, 0x20), element1) + + array2 := add(array1, 0x40) + mstore(array2, 1) + mstore(add(array2, 0x20), element2) + + mstore(0x40, add(array2, 0x40)) + } } } From 3902a410f15a579c91d17a8d7e1e5b24351b6c65 Mon Sep 17 00:00:00 2001 From: Francisco Date: Fri, 2 Jun 2023 16:02:57 +0100 Subject: [PATCH 080/182] Remove DOMAIN_SEPARATOR from Votes and update docs examples (#4297) Co-authored-by: Qiwei Yang Co-authored-by: Hadrien Croubois --- .changeset/silly-bees-beam.md | 4 +- .github/workflows/checks.yml | 8 +- contracts/governance/utils/Votes.sol | 8 - .../mocks/docs/governance/MyGovernor.sol | 88 ++++++++ contracts/mocks/docs/governance/MyToken.sol | 20 ++ .../docs/governance/MyTokenTimestampBased.sol | 31 +++ .../mocks/docs/governance/MyTokenWrapped.sol | 27 +++ .../token/ERC20/extensions/ERC20Permit.sol | 2 +- docs/modules/ROOT/pages/governance.adoc | 199 +----------------- scripts/prepare-docs.sh | 13 +- test/governance/utils/Votes.behavior.js | 6 +- .../token/ERC20/extensions/ERC20Votes.test.js | 4 - 12 files changed, 186 insertions(+), 224 deletions(-) create mode 100644 contracts/mocks/docs/governance/MyGovernor.sol create mode 100644 contracts/mocks/docs/governance/MyToken.sol create mode 100644 contracts/mocks/docs/governance/MyTokenTimestampBased.sol create mode 100644 contracts/mocks/docs/governance/MyTokenWrapped.sol diff --git a/.changeset/silly-bees-beam.md b/.changeset/silly-bees-beam.md index 6be23768c..0f4f40507 100644 --- a/.changeset/silly-bees-beam.md +++ b/.changeset/silly-bees-beam.md @@ -2,4 +2,6 @@ 'openzeppelin-solidity': major --- -`ERC20Votes`: Changed internal vote accounting to reusable `Votes` module previously used by `ERC721Votes`. Removed implicit `ERC20Permit` inheritance. [#3816](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3816) +`ERC20Votes`: Changed internal vote accounting to reusable `Votes` module previously used by `ERC721Votes`. Removed implicit `ERC20Permit` inheritance. Note that the `DOMAIN_SEPARATOR` getter was previously guaranteed to be available for `ERC20Votes` contracts, but is no longer available unless `ERC20Permit` is explicitly used; ERC-5267 support is included in `ERC20Votes` with `EIP712` and is recommended as an alternative. + +pr: #3816 diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 7cf7e6917..122d39564 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -13,6 +13,9 @@ concurrency: group: checks-${{ github.ref }} cancel-in-progress: true +env: + NODE_OPTIONS: --max_old_space_size=5120 + jobs: lint: runs-on: ubuntu-latest @@ -26,7 +29,6 @@ jobs: runs-on: ubuntu-latest env: FORCE_COLOR: 1 - NODE_OPTIONS: --max_old_space_size=4096 GAS: true steps: - uses: actions/checkout@v3 @@ -57,8 +59,6 @@ jobs: run: bash scripts/upgradeable/transpile.sh - name: Run tests run: npm run test - env: - NODE_OPTIONS: --max_old_space_size=4096 - name: Check linearisation of the inheritance graph run: npm run test:inheritance - name: Check storage layout @@ -86,8 +86,6 @@ jobs: - name: Set up environment uses: ./.github/actions/setup - run: npm run coverage - env: - NODE_OPTIONS: --max_old_space_size=4096 - uses: codecov/codecov-action@v3 with: token: ${{ secrets.CODECOV_TOKEN }} diff --git a/contracts/governance/utils/Votes.sol b/contracts/governance/utils/Votes.sol index 5c855b5e4..94f6d4fb9 100644 --- a/contracts/governance/utils/Votes.sol +++ b/contracts/governance/utils/Votes.sol @@ -225,14 +225,6 @@ abstract contract Votes is Context, EIP712, Nonces, IERC5805 { return a - b; } - /** - * @dev Returns the contract's {EIP712} domain separator. - */ - // solhint-disable-next-line func-name-mixedcase - function DOMAIN_SEPARATOR() external view returns (bytes32) { - return _domainSeparatorV4(); - } - /** * @dev Must return the voting units held by an account. */ diff --git a/contracts/mocks/docs/governance/MyGovernor.sol b/contracts/mocks/docs/governance/MyGovernor.sol new file mode 100644 index 000000000..095523b3d --- /dev/null +++ b/contracts/mocks/docs/governance/MyGovernor.sol @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import "../../../governance/Governor.sol"; +import "../../../governance/compatibility/GovernorCompatibilityBravo.sol"; +import "../../../governance/extensions/GovernorVotes.sol"; +import "../../../governance/extensions/GovernorVotesQuorumFraction.sol"; +import "../../../governance/extensions/GovernorTimelockControl.sol"; + +contract MyGovernor is + Governor, + GovernorCompatibilityBravo, + GovernorVotes, + GovernorVotesQuorumFraction, + GovernorTimelockControl +{ + constructor( + IVotes _token, + TimelockController _timelock + ) Governor("MyGovernor") GovernorVotes(_token) GovernorVotesQuorumFraction(4) GovernorTimelockControl(_timelock) {} + + function votingDelay() public pure override returns (uint256) { + return 7200; // 1 day + } + + function votingPeriod() public pure override returns (uint256) { + return 50400; // 1 week + } + + function proposalThreshold() public pure override returns (uint256) { + return 0; + } + + // The functions below are overrides required by Solidity. + + function state( + uint256 proposalId + ) public view override(Governor, IGovernor, GovernorTimelockControl) returns (ProposalState) { + return super.state(proposalId); + } + + function propose( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + string memory description + ) public override(Governor, GovernorCompatibilityBravo, IGovernor) returns (uint256) { + return super.propose(targets, values, calldatas, description); + } + + function cancel( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 descriptionHash + ) public override(Governor, GovernorCompatibilityBravo, IGovernor) returns (uint256) { + return super.cancel(targets, values, calldatas, descriptionHash); + } + + function _execute( + uint256 proposalId, + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 descriptionHash + ) internal override(Governor, GovernorTimelockControl) { + super._execute(proposalId, targets, values, calldatas, descriptionHash); + } + + function _cancel( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 descriptionHash + ) internal override(Governor, GovernorTimelockControl) returns (uint256) { + return super._cancel(targets, values, calldatas, descriptionHash); + } + + function _executor() internal view override(Governor, GovernorTimelockControl) returns (address) { + return super._executor(); + } + + function supportsInterface( + bytes4 interfaceId + ) public view override(Governor, IERC165, GovernorTimelockControl) returns (bool) { + return super.supportsInterface(interfaceId); + } +} diff --git a/contracts/mocks/docs/governance/MyToken.sol b/contracts/mocks/docs/governance/MyToken.sol new file mode 100644 index 000000000..f7707ff9d --- /dev/null +++ b/contracts/mocks/docs/governance/MyToken.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import "../../../token/ERC20/ERC20.sol"; +import "../../../token/ERC20/extensions/ERC20Permit.sol"; +import "../../../token/ERC20/extensions/ERC20Votes.sol"; + +contract MyToken is ERC20, ERC20Permit, ERC20Votes { + constructor() ERC20("MyToken", "MTK") ERC20Permit("MyToken") {} + + // The functions below are overrides required by Solidity. + + function _update(address from, address to, uint256 amount) internal override(ERC20, ERC20Votes) { + super._update(from, to, amount); + } + + function nonces(address owner) public view virtual override(ERC20Permit, Nonces) returns (uint256) { + return super.nonces(owner); + } +} diff --git a/contracts/mocks/docs/governance/MyTokenTimestampBased.sol b/contracts/mocks/docs/governance/MyTokenTimestampBased.sol new file mode 100644 index 000000000..1c688c250 --- /dev/null +++ b/contracts/mocks/docs/governance/MyTokenTimestampBased.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import "../../../token/ERC20/ERC20.sol"; +import "../../../token/ERC20/extensions/ERC20Permit.sol"; +import "../../../token/ERC20/extensions/ERC20Votes.sol"; + +contract MyTokenTimestampBased is ERC20, ERC20Permit, ERC20Votes { + constructor() ERC20("MyTokenTimestampBased", "MTK") ERC20Permit("MyTokenTimestampBased") {} + + // Overrides IERC6372 functions to make the token & governor timestamp-based + + function clock() public view override returns (uint48) { + return uint48(block.timestamp); + } + + // solhint-disable-next-line func-name-mixedcase + function CLOCK_MODE() public pure override returns (string memory) { + return "mode=timestamp"; + } + + // The functions below are overrides required by Solidity. + + function _update(address from, address to, uint256 amount) internal override(ERC20, ERC20Votes) { + super._update(from, to, amount); + } + + function nonces(address owner) public view virtual override(ERC20Permit, Nonces) returns (uint256) { + return super.nonces(owner); + } +} diff --git a/contracts/mocks/docs/governance/MyTokenWrapped.sol b/contracts/mocks/docs/governance/MyTokenWrapped.sol new file mode 100644 index 000000000..6637dccc3 --- /dev/null +++ b/contracts/mocks/docs/governance/MyTokenWrapped.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import "../../../token/ERC20/ERC20.sol"; +import "../../../token/ERC20/extensions/ERC20Permit.sol"; +import "../../../token/ERC20/extensions/ERC20Votes.sol"; +import "../../../token/ERC20/extensions/ERC20Wrapper.sol"; + +contract MyTokenWrapped is ERC20, ERC20Permit, ERC20Votes, ERC20Wrapper { + constructor( + IERC20 wrappedToken + ) ERC20("MyTokenWrapped", "MTK") ERC20Permit("MyTokenWrapped") ERC20Wrapper(wrappedToken) {} + + // The functions below are overrides required by Solidity. + + function decimals() public view override(ERC20, ERC20Wrapper) returns (uint8) { + return super.decimals(); + } + + function _update(address from, address to, uint256 amount) internal override(ERC20, ERC20Votes) { + super._update(from, to, amount); + } + + function nonces(address owner) public view virtual override(ERC20Permit, Nonces) returns (uint256) { + return super.nonces(owner); + } +} diff --git a/contracts/token/ERC20/extensions/ERC20Permit.sol b/contracts/token/ERC20/extensions/ERC20Permit.sol index dbdc37053..e7d43c2dd 100644 --- a/contracts/token/ERC20/extensions/ERC20Permit.sol +++ b/contracts/token/ERC20/extensions/ERC20Permit.sol @@ -66,7 +66,7 @@ abstract contract ERC20Permit is ERC20, IERC20Permit, EIP712, Nonces { * @dev See {IERC20Permit-DOMAIN_SEPARATOR}. */ // solhint-disable-next-line func-name-mixedcase - function DOMAIN_SEPARATOR() external view override returns (bytes32) { + function DOMAIN_SEPARATOR() external view virtual override returns (bytes32) { return _domainSeparatorV4(); } } diff --git a/docs/modules/ROOT/pages/governance.adoc b/docs/modules/ROOT/pages/governance.adoc index a40275b07..3d2bc05f3 100644 --- a/docs/modules/ROOT/pages/governance.adoc +++ b/docs/modules/ROOT/pages/governance.adoc @@ -43,82 +43,13 @@ In the rest of this guide, we will focus on a fresh deploy of the vanilla OpenZe The voting power of each account in our governance setup will be determined by an ERC20 token. The token has to implement the ERC20Votes extension. This extension will keep track of historical balances so that voting power is retrieved from past snapshots rather than current balance, which is an important protection that prevents double voting. ```solidity -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; - -import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol"; -import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol"; - -contract MyToken is ERC20, ERC20Permit, ERC20Votes { - constructor() ERC20("MyToken", "MTK") ERC20Permit("MyToken") {} - - // The functions below are overrides required by Solidity. - - function _afterTokenTransfer(address from, address to, uint256 amount) - internal - override(ERC20, ERC20Votes) - { - super._afterTokenTransfer(from, to, amount); - } - - function _mint(address to, uint256 amount) - internal - override(ERC20, ERC20Votes) - { - super._mint(to, amount); - } - - function _burn(address account, uint256 amount) - internal - override(ERC20, ERC20Votes) - { - super._burn(account, amount); - } -} +include::api:example$governance/MyToken.sol[] ``` If your project already has a live token that does not include ERC20Votes and is not upgradeable, you can wrap it in a governance token by using ERC20Wrapper. This will allow token holders to participate in governance by wrapping their tokens 1-to-1. ```solidity -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; - -import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol"; -import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol"; -import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Wrapper.sol"; - -contract MyToken is ERC20, ERC20Permit, ERC20Votes, ERC20Wrapper { - constructor(IERC20 wrappedToken) - ERC20("MyToken", "MTK") - ERC20Permit("MyToken") - ERC20Wrapper(wrappedToken) - {} - - // The functions below are overrides required by Solidity. - - function _afterTokenTransfer(address from, address to, uint256 amount) - internal - override(ERC20, ERC20Votes) - { - super._afterTokenTransfer(from, to, amount); - } - - function _mint(address to, uint256 amount) - internal - override(ERC20, ERC20Votes) - { - super._mint(to, amount); - } - - function _burn(address account, uint256 amount) - internal - override(ERC20, ERC20Votes) - { - super._burn(account, amount); - } -} +include::api:example$governance/MyTokenWrapped.sol[] ``` NOTE: The only other source of voting power available in OpenZeppelin Contracts currently is xref:api:token/ERC721.adoc#ERC721Votes[`ERC721Votes`]. ERC721 tokens that don't provide this functionality can be wrapped into a voting tokens using a combination of xref:api:token/ERC721.adoc#ERC721Votes[`ERC721Votes`] and xref:api:token/ERC721Wrapper.adoc#ERC721Wrapper[`ERC721Wrapper`]. @@ -146,87 +77,7 @@ These parameters are specified in the unit defined in the token's clock. Assumin We can optionally set a proposal threshold as well. This restricts proposal creation to accounts who have enough voting power. ```solidity -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; - -import "@openzeppelin/contracts/governance/Governor.sol"; -import "@openzeppelin/contracts/governance/compatibility/GovernorCompatibilityBravo.sol"; -import "@openzeppelin/contracts/governance/extensions/GovernorVotes.sol"; -import "@openzeppelin/contracts/governance/extensions/GovernorVotesQuorumFraction.sol"; -import "@openzeppelin/contracts/governance/extensions/GovernorTimelockControl.sol"; - -contract MyGovernor is Governor, GovernorCompatibilityBravo, GovernorVotes, GovernorVotesQuorumFraction, GovernorTimelockControl { - constructor(IVotes _token, TimelockController _timelock) - Governor("MyGovernor") - GovernorVotes(_token) - GovernorVotesQuorumFraction(4) - GovernorTimelockControl(_timelock) - {} - - function votingDelay() public pure override returns (uint256) { - return 7200; // 1 day - } - - function votingPeriod() public pure override returns (uint256) { - return 50400; // 1 week - } - - function proposalThreshold() public pure override returns (uint256) { - return 0; - } - - // The functions below are overrides required by Solidity. - - function state(uint256 proposalId) - public - view - override(Governor, IGovernor, GovernorTimelockControl) - returns (ProposalState) - { - return super.state(proposalId); - } - - function propose(address[] memory targets, uint256[] memory values, bytes[] memory calldatas, string memory description) - public - override(Governor, GovernorCompatibilityBravo, IGovernor) - returns (uint256) - { - return super.propose(targets, values, calldatas, description); - } - - function _execute(uint256 proposalId, address[] memory targets, uint256[] memory values, bytes[] memory calldatas, bytes32 descriptionHash) - internal - override(Governor, GovernorTimelockControl) - { - super._execute(proposalId, targets, values, calldatas, descriptionHash); - } - - function _cancel(address[] memory targets, uint256[] memory values, bytes[] memory calldatas, bytes32 descriptionHash) - internal - override(Governor, GovernorTimelockControl) - returns (uint256) - { - return super._cancel(targets, values, calldatas, descriptionHash); - } - - function _executor() - internal - view - override(Governor, GovernorTimelockControl) - returns (address) - { - return super._executor(); - } - - function supportsInterface(bytes4 interfaceId) - public - view - override(Governor, IERC165, GovernorTimelockControl) - returns (bool) - { - return super.supportsInterface(interfaceId); - } -} +include::api:example$governance/MyGovernor.sol[] ``` === Timelock @@ -338,49 +189,7 @@ Therefore, designing a timestamp based voting system starts with the token. Since v4.9, all voting contracts (including xref:api:token/ERC20.adoc#ERC20Votes[`ERC20Votes`] and xref:api:token/ERC721.adoc#ERC721Votes[`ERC721Votes`]) rely on xref:api:interfaces.adoc#IERC6372[IERC6372] for clock management. In order to change from operating with block numbers to operating with timestamps, all that is required is to override the `clock()` and `CLOCK_MODE()` functions. ```solidity -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; - -import "github.com/openzeppelin/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; -import "github.com/openzeppelin/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Permit.sol"; -import "github.com/openzeppelin/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Votes.sol"; - -contract MyToken is ERC20, ERC20Permit, ERC20Votes { - constructor() ERC20("MyToken", "MTK") ERC20Permit("MyToken") {} - - // Overrides IERC6372 functions to make the token & governor timestamp-based - - function clock() public view override returns (uint48) { - return uint48(block.timestamp); - } - - function CLOCK_MODE() public pure override returns (string memory) { - return "mode=timestamp"; - } - - // The functions below are overrides required by Solidity. - - function _afterTokenTransfer(address from, address to, uint256 amount) - internal - override(ERC20, ERC20Votes) - { - super._afterTokenTransfer(from, to, amount); - } - - function _mint(address to, uint256 amount) - internal - override(ERC20, ERC20Votes) - { - super._mint(to, amount); - } - - function _burn(address account, uint256 amount) - internal - override(ERC20, ERC20Votes) - { - super._burn(account, amount); - } -} +include::api:example$governance/MyTokenTimestampBased.sol[] ``` === Governor diff --git a/scripts/prepare-docs.sh b/scripts/prepare-docs.sh index bb9c5d0ad..d1317b092 100755 --- a/scripts/prepare-docs.sh +++ b/scripts/prepare-docs.sh @@ -1,6 +1,7 @@ #!/usr/bin/env bash set -euo pipefail +shopt -s globstar OUTDIR="$(node -p 'require("./docs/config.js").outputDir')" @@ -13,11 +14,13 @@ rm -rf "$OUTDIR" hardhat docgen # copy examples and adjust imports -examples_dir="docs/modules/api/examples" -mkdir -p "$examples_dir" -for f in contracts/mocks/docs/*.sol; do - name="$(basename "$f")" - sed -e '/^import/s|\.\./\.\./|@openzeppelin/contracts/|' "$f" > "docs/modules/api/examples/$name" +examples_source_dir="contracts/mocks/docs" +examples_target_dir="docs/modules/api/examples" + +for f in "$examples_source_dir"/**/*.sol; do + name="${f/#"$examples_source_dir/"/}" + mkdir -p "$examples_target_dir/$(dirname "$name")" + sed -Ee '/^import/s|"(\.\./)+|"@openzeppelin/contracts/|' "$f" > "$examples_target_dir/$name" done node scripts/gen-nav.js "$OUTDIR" > "$OUTDIR/../nav.adoc" diff --git a/test/governance/utils/Votes.behavior.js b/test/governance/utils/Votes.behavior.js index 02f2c2544..37062e19c 100644 --- a/test/governance/utils/Votes.behavior.js +++ b/test/governance/utils/Votes.behavior.js @@ -7,7 +7,7 @@ const ethSigUtil = require('eth-sig-util'); const Wallet = require('ethereumjs-wallet').default; const { shouldBehaveLikeEIP6372 } = require('./EIP6372.behavior'); -const { getDomain, domainType, domainSeparator } = require('../../helpers/eip712'); +const { getDomain, domainType } = require('../../helpers/eip712'); const { clockFromReceipt } = require('../../helpers/time'); const Delegation = [ @@ -36,10 +36,6 @@ function shouldBehaveLikeVotes(accounts, tokens, { mode = 'blocknumber', fungibl expect(await this.votes.nonces(accounts[0])).to.be.bignumber.equal('0'); }); - it('domain separator', async function () { - expect(await this.votes.DOMAIN_SEPARATOR()).to.equal(domainSeparator(await getDomain(this.votes))); - }); - describe('delegation with signature', function () { const token = tokens[0]; diff --git a/test/token/ERC20/extensions/ERC20Votes.test.js b/test/token/ERC20/extensions/ERC20Votes.test.js index 621f58b7d..e4ff58cd9 100644 --- a/test/token/ERC20/extensions/ERC20Votes.test.js +++ b/test/token/ERC20/extensions/ERC20Votes.test.js @@ -46,10 +46,6 @@ contract('ERC20Votes', function (accounts) { expect(await this.token.nonces(holder)).to.be.bignumber.equal('0'); }); - it('domain separator', async function () { - expect(await this.token.DOMAIN_SEPARATOR()).to.equal(await getDomain(this.token).then(domainSeparator)); - }); - it('minting restriction', async function () { const amount = new BN('2').pow(new BN('224')); await expectRevert(this.token.$_mint(holder, amount), 'ERC20Votes: total supply risks overflowing votes'); From 2d1da295e6e28b3cd86eccab47c40a2bcbf0f4d1 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Fri, 2 Jun 2023 17:14:41 +0200 Subject: [PATCH 081/182] Move some changeset to the "Removals" section of CHANGELOG (#4290) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ernesto García Co-authored-by: Francisco --- .changeset/beige-ducks-flow.md | 5 ----- .changeset/dirty-mangos-sort.md | 5 ----- .changeset/fluffy-gifts-build.md | 5 ----- .changeset/friendly-suits-camp.md | 5 ----- .changeset/hungry-impalas-perform.md | 5 ----- .changeset/selfish-queens-rest.md | 5 ----- .changeset/spicy-ducks-cough.md | 5 ----- .changeset/swift-berries-sort.md | 5 ----- .changeset/tame-geckos-search.md | 5 ----- .changeset/three-weeks-double.md | 5 ----- .changeset/unlucky-snakes-drive.md | 5 ----- CHANGELOG.md | 26 ++++++++++++++++++++++++-- 12 files changed, 24 insertions(+), 57 deletions(-) delete mode 100644 .changeset/beige-ducks-flow.md delete mode 100644 .changeset/dirty-mangos-sort.md delete mode 100644 .changeset/fluffy-gifts-build.md delete mode 100644 .changeset/friendly-suits-camp.md delete mode 100644 .changeset/hungry-impalas-perform.md delete mode 100644 .changeset/selfish-queens-rest.md delete mode 100644 .changeset/spicy-ducks-cough.md delete mode 100644 .changeset/swift-berries-sort.md delete mode 100644 .changeset/tame-geckos-search.md delete mode 100644 .changeset/three-weeks-double.md delete mode 100644 .changeset/unlucky-snakes-drive.md diff --git a/.changeset/beige-ducks-flow.md b/.changeset/beige-ducks-flow.md deleted file mode 100644 index 4edc870b5..000000000 --- a/.changeset/beige-ducks-flow.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': major ---- - -Remove deprecated GovernorProposalThreshold module. diff --git a/.changeset/dirty-mangos-sort.md b/.changeset/dirty-mangos-sort.md deleted file mode 100644 index 6981399c0..000000000 --- a/.changeset/dirty-mangos-sort.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': major ---- - -Removed presets in favor of [OpenZeppelin Contracts Wizard](https://wizard.openzeppelin.com/). diff --git a/.changeset/fluffy-gifts-build.md b/.changeset/fluffy-gifts-build.md deleted file mode 100644 index a1ffe8be1..000000000 --- a/.changeset/fluffy-gifts-build.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': major ---- - -Remove ERC1820Implementer. diff --git a/.changeset/friendly-suits-camp.md b/.changeset/friendly-suits-camp.md deleted file mode 100644 index bcf1d7ee4..000000000 --- a/.changeset/friendly-suits-camp.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': major ---- - -Remove Checkpoints.History. diff --git a/.changeset/hungry-impalas-perform.md b/.changeset/hungry-impalas-perform.md deleted file mode 100644 index 24901ec86..000000000 --- a/.changeset/hungry-impalas-perform.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': major ---- - -`ERC165Storage`: Removed this contract in favor of inheritance based approach. ([#3880](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3880)) diff --git a/.changeset/selfish-queens-rest.md b/.changeset/selfish-queens-rest.md deleted file mode 100644 index 739fa1356..000000000 --- a/.changeset/selfish-queens-rest.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': major ---- - -Remove PullPayment and Escrow contracts (Escrow, ConditionalEscrow, RefundEscrow). diff --git a/.changeset/spicy-ducks-cough.md b/.changeset/spicy-ducks-cough.md deleted file mode 100644 index bf7296e4e..000000000 --- a/.changeset/spicy-ducks-cough.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': major ---- - -Remove the Timers library. diff --git a/.changeset/swift-berries-sort.md b/.changeset/swift-berries-sort.md deleted file mode 100644 index 9af6cba64..000000000 --- a/.changeset/swift-berries-sort.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': major ---- - -Remove ERC777 implementation. diff --git a/.changeset/tame-geckos-search.md b/.changeset/tame-geckos-search.md deleted file mode 100644 index 6b6ae86d0..000000000 --- a/.changeset/tame-geckos-search.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': major ---- - -Remove SafeMath and SignedSafeMath libraries. diff --git a/.changeset/three-weeks-double.md b/.changeset/three-weeks-double.md deleted file mode 100644 index d267e72c4..000000000 --- a/.changeset/three-weeks-double.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': major ---- - -Remove CrossChain contracts, including AccessControlCrossChain and all the vendored bridge interfaces. diff --git a/.changeset/unlucky-snakes-drive.md b/.changeset/unlucky-snakes-drive.md deleted file mode 100644 index d4c78dd50..000000000 --- a/.changeset/unlucky-snakes-drive.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': patch ---- - -`Address`: Removed `isContract` because of its ambiguous nature and potential for misuse. diff --git a/CHANGELOG.md b/CHANGELOG.md index 2fc4cc5e3..c9fac4d1f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,14 +2,36 @@ ### Removals -The following contracts and libraries were removed: +The following contracts, libraries and functions were removed: +- `Address.isContract` (because of its ambiguous nature and potential for misuse) +- `Checkpoints.History` - `Counters` - `ERC20Snapshot` - `ERC20VotesComp` +- `ERC165Storage` (in favor of inheritance based approach) +- `ERC777` +- `ERC1820Implementer` - `GovernorVotesComp` +- `GovernorProposalThreshold` (deprecated since 4.4) - `PaymentSplitter` -- `TokenTimelock` (removed in favor of `VestingWallet`) +- `PullPayment` +- `SafeMath` +- `SignedSafeMath` +- `Timers` +- `TokenTimelock` (in favor of `VestingWallet`) +- All escrow contracts (`Escrow`, `ConditionalEscrow` and `RefundEscrow`) +- All cross-chain contracts, including `AccessControlCrossChain` and all the vendored bridge interfaces +- All presets in favor of [OpenZeppelin Contracts Wizard](https://wizard.openzeppelin.com/) + +These removals were implemented in the following PRs: + +- [3637](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3637) +- [3880](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3880) +- [3945](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3945) +- [4258](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4258) +- [4276](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4276) +- [4289](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4289) ### How to upgrade from 4.x From eecd5e15c7157ad8dd8a65e9d1462853876f1d00 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Fri, 2 Jun 2023 17:42:02 +0200 Subject: [PATCH 082/182] Make CHANGELOG more compact for improved readability (#4306) --- CHANGELOG.md | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c9fac4d1f..9d4e13046 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,14 +24,7 @@ The following contracts, libraries and functions were removed: - All cross-chain contracts, including `AccessControlCrossChain` and all the vendored bridge interfaces - All presets in favor of [OpenZeppelin Contracts Wizard](https://wizard.openzeppelin.com/) -These removals were implemented in the following PRs: - -- [3637](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3637) -- [3880](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3880) -- [3945](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3945) -- [4258](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4258) -- [4276](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4276) -- [4289](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4289) +These removals were implemented in the following PRs: [#3637](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3637), [#3880](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3880), [#3945](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3945), [#4258](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4258), [#4276](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4276), [#4289](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4289) ### How to upgrade from 4.x From ffceb3cd988874369806139ae9e59d2e2a93daec Mon Sep 17 00:00:00 2001 From: Francisco Date: Fri, 2 Jun 2023 18:20:58 +0100 Subject: [PATCH 083/182] Remove hardcoded function resolution (#4299) --- .changeset/red-dots-fold.md | 5 +++ .../ERC1155/extensions/ERC1155Supply.sol | 2 +- .../token/ERC20/extensions/ERC20FlashMint.sol | 2 +- contracts/token/ERC721/ERC721.sol | 32 +++++++++---------- .../ERC721/extensions/ERC721Enumerable.sol | 4 +-- 5 files changed, 25 insertions(+), 20 deletions(-) create mode 100644 .changeset/red-dots-fold.md diff --git a/.changeset/red-dots-fold.md b/.changeset/red-dots-fold.md new file mode 100644 index 000000000..6df4c8f0c --- /dev/null +++ b/.changeset/red-dots-fold.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': major +--- + +Overrides are now used internally for a number of functions that were previously hardcoded to their default implementation in certain locations: `ERC1155Supply.totalSupply`, `ERC721.ownerOf`, `ERC721.balanceOf` in `ERC721Enumerable`, and `ERC20.totalSupply` in `ERC20FlashMint`. diff --git a/contracts/token/ERC1155/extensions/ERC1155Supply.sol b/contracts/token/ERC1155/extensions/ERC1155Supply.sol index 599df00f3..4ad83ea02 100644 --- a/contracts/token/ERC1155/extensions/ERC1155Supply.sol +++ b/contracts/token/ERC1155/extensions/ERC1155Supply.sol @@ -38,7 +38,7 @@ abstract contract ERC1155Supply is ERC1155 { * @dev Indicates whether any token exist with a given id, or not. */ function exists(uint256 id) public view virtual returns (bool) { - return ERC1155Supply.totalSupply(id) > 0; + return totalSupply(id) > 0; } /** diff --git a/contracts/token/ERC20/extensions/ERC20FlashMint.sol b/contracts/token/ERC20/extensions/ERC20FlashMint.sol index ce68793b5..18b0a81c6 100644 --- a/contracts/token/ERC20/extensions/ERC20FlashMint.sol +++ b/contracts/token/ERC20/extensions/ERC20FlashMint.sol @@ -25,7 +25,7 @@ abstract contract ERC20FlashMint is ERC20, IERC3156FlashLender { * @return The amount of token that can be loaned. */ function maxFlashLoan(address token) public view virtual override returns (uint256) { - return token == address(this) ? type(uint256).max - ERC20.totalSupply() : 0; + return token == address(this) ? type(uint256).max - totalSupply() : 0; } /** diff --git a/contracts/token/ERC721/ERC721.sol b/contracts/token/ERC721/ERC721.sol index 58a626cda..af00ecaf4 100644 --- a/contracts/token/ERC721/ERC721.sol +++ b/contracts/token/ERC721/ERC721.sol @@ -108,7 +108,7 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata { * @dev See {IERC721-approve}. */ function approve(address to, uint256 tokenId) public virtual override { - address owner = ERC721.ownerOf(tokenId); + address owner = ownerOf(tokenId); require(to != owner, "ERC721: approval to current owner"); require( @@ -217,7 +217,7 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata { * - `tokenId` must exist. */ function _isApprovedOrOwner(address spender, uint256 tokenId) internal view virtual returns (bool) { - address owner = ERC721.ownerOf(tokenId); + address owner = ownerOf(tokenId); return (spender == owner || isApprovedForAll(owner, spender) || getApproved(tokenId) == spender); } @@ -295,21 +295,20 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata { * Emits a {Transfer} event. */ function _burn(uint256 tokenId) internal virtual { - address owner = ERC721.ownerOf(tokenId); + address owner = ownerOf(tokenId); _beforeTokenTransfer(owner, address(0), tokenId, 1); // Update ownership in case tokenId was transferred by `_beforeTokenTransfer` hook - owner = ERC721.ownerOf(tokenId); + owner = ownerOf(tokenId); // Clear approvals delete _tokenApprovals[tokenId]; - unchecked { - // Cannot overflow, as that would require more tokens to be burned/transferred - // out than the owner initially received through minting and transferring in. - _balances[owner] -= 1; - } + // Decrease balance with checked arithmetic, because an `ownerOf` override may + // invalidate the assumption that `_balances[from] >= 1`. + _balances[owner] -= 1; + delete _owners[tokenId]; emit Transfer(owner, address(0), tokenId); @@ -329,26 +328,27 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata { * Emits a {Transfer} event. */ function _transfer(address from, address to, uint256 tokenId) internal virtual { - require(ERC721.ownerOf(tokenId) == from, "ERC721: transfer from incorrect owner"); + require(ownerOf(tokenId) == from, "ERC721: transfer from incorrect owner"); require(to != address(0), "ERC721: transfer to the zero address"); _beforeTokenTransfer(from, to, tokenId, 1); // Check that tokenId was not transferred by `_beforeTokenTransfer` hook - require(ERC721.ownerOf(tokenId) == from, "ERC721: transfer from incorrect owner"); + require(ownerOf(tokenId) == from, "ERC721: transfer from incorrect owner"); // Clear approvals from the previous owner delete _tokenApprovals[tokenId]; + // Decrease balance with checked arithmetic, because an `ownerOf` override may + // invalidate the assumption that `_balances[from] >= 1`. + _balances[from] -= 1; + unchecked { - // `_balances[from]` cannot overflow for the same reason as described in `_burn`: - // `from`'s balance is the number of token held, which is at least one before the current - // transfer. // `_balances[to]` could overflow in the conditions described in `_mint`. That would require // all 2**256 token ids to be minted, which in practice is impossible. - _balances[from] -= 1; _balances[to] += 1; } + _owners[tokenId] = to; emit Transfer(from, to, tokenId); @@ -363,7 +363,7 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata { */ function _approve(address to, uint256 tokenId) internal virtual { _tokenApprovals[tokenId] = to; - emit Approval(ERC721.ownerOf(tokenId), to, tokenId); + emit Approval(ownerOf(tokenId), to, tokenId); } /** diff --git a/contracts/token/ERC721/extensions/ERC721Enumerable.sol b/contracts/token/ERC721/extensions/ERC721Enumerable.sol index 6b702d53b..a9513b21d 100644 --- a/contracts/token/ERC721/extensions/ERC721Enumerable.sol +++ b/contracts/token/ERC721/extensions/ERC721Enumerable.sol @@ -35,7 +35,7 @@ abstract contract ERC721Enumerable is ERC721, IERC721Enumerable { * @dev See {IERC721Enumerable-tokenOfOwnerByIndex}. */ function tokenOfOwnerByIndex(address owner, uint256 index) public view virtual override returns (uint256) { - require(index < ERC721.balanceOf(owner), "ERC721Enumerable: owner index out of bounds"); + require(index < balanceOf(owner), "ERC721Enumerable: owner index out of bounds"); return _ownedTokens[owner][index]; } @@ -90,7 +90,7 @@ abstract contract ERC721Enumerable is ERC721, IERC721Enumerable { * @param tokenId uint256 ID of the token to be added to the tokens list of the given address */ function _addTokenToOwnerEnumeration(address to, uint256 tokenId) private { - uint256 length = ERC721.balanceOf(to); + uint256 length = balanceOf(to); _ownedTokens[to][length] = tokenId; _ownedTokensIndex[tokenId] = length; } From 253bfa68c27785536d58afcc95ebeb4af6f9aa7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=BBuk?= Date: Tue, 6 Jun 2023 02:37:12 +0200 Subject: [PATCH 084/182] Optimize Strings.equal (#4262) Co-authored-by: Hadrien Croubois --- .changeset/eighty-crabs-listen.md | 5 +++++ contracts/utils/Strings.sol | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .changeset/eighty-crabs-listen.md diff --git a/.changeset/eighty-crabs-listen.md b/.changeset/eighty-crabs-listen.md new file mode 100644 index 000000000..7de904db8 --- /dev/null +++ b/.changeset/eighty-crabs-listen.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': patch +--- + +Optimize `Strings.equal` diff --git a/contracts/utils/Strings.sol b/contracts/utils/Strings.sol index 33d7bbf59..050f6f9ca 100644 --- a/contracts/utils/Strings.sol +++ b/contracts/utils/Strings.sol @@ -80,6 +80,6 @@ library Strings { * @dev Returns true if the two strings are equal. */ function equal(string memory a, string memory b) internal pure returns (bool) { - return keccak256(bytes(a)) == keccak256(bytes(b)); + return bytes(a).length == bytes(b).length && keccak256(bytes(a)) == keccak256(bytes(b)); } } From 6c14de4f0c4e5b86762360f96348b095551d936e Mon Sep 17 00:00:00 2001 From: Pascal Marco Caversaccio Date: Tue, 6 Jun 2023 19:00:01 +0300 Subject: [PATCH 085/182] `ECDSA`: Use hexadecimal literals (#4317) --- contracts/utils/cryptography/ECDSA.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/utils/cryptography/ECDSA.sol b/contracts/utils/cryptography/ECDSA.sol index f12815d9d..2ddbd9ba0 100644 --- a/contracts/utils/cryptography/ECDSA.sol +++ b/contracts/utils/cryptography/ECDSA.sol @@ -197,7 +197,7 @@ library ECDSA { /// @solidity memory-safe-assembly assembly { let ptr := mload(0x40) - mstore(ptr, "\x19\x01") + mstore(ptr, hex"19_01") mstore(add(ptr, 0x02), domainSeparator) mstore(add(ptr, 0x22), structHash) data := keccak256(ptr, 0x42) @@ -211,6 +211,6 @@ library ECDSA { * See {recover}. */ function toDataWithIntendedValidatorHash(address validator, bytes memory data) internal pure returns (bytes32) { - return keccak256(abi.encodePacked("\x19\x00", validator, data)); + return keccak256(abi.encodePacked(hex"19_00", validator, data)); } } From 85696d80ad50246e9f7ec983fa325a3c40190033 Mon Sep 17 00:00:00 2001 From: Francisco Date: Tue, 6 Jun 2023 18:42:50 +0100 Subject: [PATCH 086/182] Remove further hardcoded function resolution (#4309) --- .changeset/red-dots-fold.md | 2 +- contracts/mocks/proxy/UUPSUpgradeableMock.sol | 4 ++-- contracts/proxy/ERC1967/ERC1967Proxy.sol | 2 +- .../token/ERC721/extensions/ERC721Enumerable.sol | 12 +++++++----- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/.changeset/red-dots-fold.md b/.changeset/red-dots-fold.md index 6df4c8f0c..08cc77843 100644 --- a/.changeset/red-dots-fold.md +++ b/.changeset/red-dots-fold.md @@ -2,4 +2,4 @@ 'openzeppelin-solidity': major --- -Overrides are now used internally for a number of functions that were previously hardcoded to their default implementation in certain locations: `ERC1155Supply.totalSupply`, `ERC721.ownerOf`, `ERC721.balanceOf` in `ERC721Enumerable`, and `ERC20.totalSupply` in `ERC20FlashMint`. +Overrides are now used internally for a number of functions that were previously hardcoded to their default implementation in certain locations: `ERC1155Supply.totalSupply`, `ERC721.ownerOf`, `ERC721.balanceOf` and `ERC721.totalSupply` in `ERC721Enumerable`, `ERC20.totalSupply` in `ERC20FlashMint`, and `ERC1967._getImplementation` in `ERC1967Proxy`. diff --git a/contracts/mocks/proxy/UUPSUpgradeableMock.sol b/contracts/mocks/proxy/UUPSUpgradeableMock.sol index 567f3b536..60eed4c93 100644 --- a/contracts/mocks/proxy/UUPSUpgradeableMock.sol +++ b/contracts/mocks/proxy/UUPSUpgradeableMock.sol @@ -23,10 +23,10 @@ contract UUPSUpgradeableMock is NonUpgradeableMock, UUPSUpgradeable { contract UUPSUpgradeableUnsafeMock is UUPSUpgradeableMock { function upgradeTo(address newImplementation) public override { - ERC1967Upgrade._upgradeToAndCall(newImplementation, bytes(""), false); + _upgradeToAndCall(newImplementation, bytes(""), false); } function upgradeToAndCall(address newImplementation, bytes memory data) public payable override { - ERC1967Upgrade._upgradeToAndCall(newImplementation, data, false); + _upgradeToAndCall(newImplementation, data, false); } } diff --git a/contracts/proxy/ERC1967/ERC1967Proxy.sol b/contracts/proxy/ERC1967/ERC1967Proxy.sol index 320e026fa..ea5c204f8 100644 --- a/contracts/proxy/ERC1967/ERC1967Proxy.sol +++ b/contracts/proxy/ERC1967/ERC1967Proxy.sol @@ -31,6 +31,6 @@ contract ERC1967Proxy is Proxy, ERC1967Upgrade { * `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc` */ function _implementation() internal view virtual override returns (address impl) { - return ERC1967Upgrade._getImplementation(); + return _getImplementation(); } } diff --git a/contracts/token/ERC721/extensions/ERC721Enumerable.sol b/contracts/token/ERC721/extensions/ERC721Enumerable.sol index a9513b21d..2168e100b 100644 --- a/contracts/token/ERC721/extensions/ERC721Enumerable.sol +++ b/contracts/token/ERC721/extensions/ERC721Enumerable.sol @@ -7,9 +7,11 @@ import "../ERC721.sol"; import "./IERC721Enumerable.sol"; /** - * @dev This implements an optional extension of {ERC721} defined in the EIP that adds - * enumerability of all the token ids in the contract as well as all token ids owned by each - * account. + * @dev This implements an optional extension of {ERC721} defined in the EIP that adds enumerability + * of all the token ids in the contract as well as all token ids owned by each account. + * + * CAUTION: `ERC721` extensions that implement custom `balanceOf` logic, such as `ERC721Consecutive`, + * interfere with enumerability and should not be used together with `ERC721Enumerable`. */ abstract contract ERC721Enumerable is ERC721, IERC721Enumerable { // Mapping from owner to list of owned token IDs @@ -50,7 +52,7 @@ abstract contract ERC721Enumerable is ERC721, IERC721Enumerable { * @dev See {IERC721Enumerable-tokenByIndex}. */ function tokenByIndex(uint256 index) public view virtual override returns (uint256) { - require(index < ERC721Enumerable.totalSupply(), "ERC721Enumerable: global index out of bounds"); + require(index < totalSupply(), "ERC721Enumerable: global index out of bounds"); return _allTokens[index]; } @@ -116,7 +118,7 @@ abstract contract ERC721Enumerable is ERC721, IERC721Enumerable { // To prevent a gap in from's tokens array, we store the last token in the index of the token to delete, and // then delete the last slot (swap and pop). - uint256 lastTokenIndex = ERC721.balanceOf(from) - 1; + uint256 lastTokenIndex = balanceOf(from) - 1; uint256 tokenIndex = _ownedTokensIndex[tokenId]; // When the token to delete is the last token, the swap operation is unnecessary From 4fd2f8be339e850c32206342c3f9a1a7bedbb204 Mon Sep 17 00:00:00 2001 From: Balaji Shetty Pachai <32358081+balajipachai@users.noreply.github.com> Date: Wed, 7 Jun 2023 02:02:55 +0530 Subject: [PATCH 087/182] Replace abi.encodeWithSelector & abi.encodeWithSignature with abi.encodeCall (#4293) Co-authored-by: Hadrien Croubois --- .changeset/big-plums-cover.md | 4 ++++ contracts/mocks/MulticallTest.sol | 2 +- contracts/mocks/ReentrancyMock.sol | 2 +- contracts/mocks/proxy/UUPSLegacy.sol | 5 +---- contracts/token/ERC20/extensions/ERC4626.sol | 2 +- contracts/token/ERC20/utils/SafeERC20.sol | 8 ++++---- contracts/utils/cryptography/SignatureChecker.sol | 2 +- contracts/utils/introspection/ERC165Checker.sol | 2 +- 8 files changed, 14 insertions(+), 13 deletions(-) create mode 100644 .changeset/big-plums-cover.md diff --git a/.changeset/big-plums-cover.md b/.changeset/big-plums-cover.md new file mode 100644 index 000000000..411156253 --- /dev/null +++ b/.changeset/big-plums-cover.md @@ -0,0 +1,4 @@ +--- +'openzeppelin-solidity': major +--- +Use `abi.encodeCall` in place of `abi.encodeWithSelector` and `abi.encodeWithSignature` for improved type-checking of parameters diff --git a/contracts/mocks/MulticallTest.sol b/contracts/mocks/MulticallTest.sol index 090f73cb4..cf89c58df 100644 --- a/contracts/mocks/MulticallTest.sol +++ b/contracts/mocks/MulticallTest.sol @@ -12,7 +12,7 @@ contract MulticallTest { ) external { bytes[] memory calls = new bytes[](recipients.length); for (uint256 i = 0; i < recipients.length; i++) { - calls[i] = abi.encodeWithSignature("transfer(address,uint256)", recipients[i], amounts[i]); + calls[i] = abi.encodeCall(multicallToken.transfer, (recipients[i], amounts[i])); } bytes[] memory results = multicallToken.multicall(calls); diff --git a/contracts/mocks/ReentrancyMock.sol b/contracts/mocks/ReentrancyMock.sol index 104d4f42a..b4819dd59 100644 --- a/contracts/mocks/ReentrancyMock.sol +++ b/contracts/mocks/ReentrancyMock.sol @@ -26,7 +26,7 @@ contract ReentrancyMock is ReentrancyGuard { function countThisRecursive(uint256 n) public nonReentrant { if (n > 0) { _count(); - (bool success, ) = address(this).call(abi.encodeWithSignature("countThisRecursive(uint256)", n - 1)); + (bool success, ) = address(this).call(abi.encodeCall(this.countThisRecursive, (n - 1))); require(success, "ReentrancyMock: failed call"); } } diff --git a/contracts/mocks/proxy/UUPSLegacy.sol b/contracts/mocks/proxy/UUPSLegacy.sol index ed243519e..f8ea7214b 100644 --- a/contracts/mocks/proxy/UUPSLegacy.sol +++ b/contracts/mocks/proxy/UUPSLegacy.sol @@ -31,10 +31,7 @@ contract UUPSUpgradeableLegacyMock is UUPSUpgradeableMock { if (!rollbackTesting.value) { // Trigger rollback using upgradeTo from the new implementation rollbackTesting.value = true; - Address.functionDelegateCall( - newImplementation, - abi.encodeWithSignature("upgradeTo(address)", oldImplementation) - ); + Address.functionDelegateCall(newImplementation, abi.encodeCall(this.upgradeTo, (oldImplementation))); rollbackTesting.value = false; // Check rollback was effective require(oldImplementation == _getImplementation(), "ERC1967Upgrade: upgrade breaks further upgrades"); diff --git a/contracts/token/ERC20/extensions/ERC4626.sol b/contracts/token/ERC20/extensions/ERC4626.sol index a706b5457..54f5ae24b 100644 --- a/contracts/token/ERC20/extensions/ERC4626.sol +++ b/contracts/token/ERC20/extensions/ERC4626.sol @@ -67,7 +67,7 @@ abstract contract ERC4626 is ERC20, IERC4626 { */ function _tryGetAssetDecimals(IERC20 asset_) private view returns (bool, uint8) { (bool success, bytes memory encodedDecimals) = address(asset_).staticcall( - abi.encodeWithSelector(IERC20Metadata.decimals.selector) + abi.encodeCall(IERC20Metadata.decimals, ()) ); if (success && encodedDecimals.length >= 32) { uint256 returnedDecimals = abi.decode(encodedDecimals, (uint256)); diff --git a/contracts/token/ERC20/utils/SafeERC20.sol b/contracts/token/ERC20/utils/SafeERC20.sol index 751b82735..b1532d1cb 100644 --- a/contracts/token/ERC20/utils/SafeERC20.sol +++ b/contracts/token/ERC20/utils/SafeERC20.sol @@ -24,7 +24,7 @@ library SafeERC20 { * non-reverting calls are assumed to be successful. */ function safeTransfer(IERC20 token, address to, uint256 value) internal { - _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); + _callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value))); } /** @@ -32,7 +32,7 @@ library SafeERC20 { * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful. */ function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal { - _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value)); + _callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value))); } /** @@ -62,10 +62,10 @@ library SafeERC20 { * 0 before setting it to a non-zero value. */ function forceApprove(IERC20 token, address spender, uint256 value) internal { - bytes memory approvalCall = abi.encodeWithSelector(token.approve.selector, spender, value); + bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value)); if (!_callOptionalReturnBool(token, approvalCall)) { - _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, 0)); + _callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0))); _callOptionalReturn(token, approvalCall); } } diff --git a/contracts/utils/cryptography/SignatureChecker.sol b/contracts/utils/cryptography/SignatureChecker.sol index ba8f7cf36..941f7538f 100644 --- a/contracts/utils/cryptography/SignatureChecker.sol +++ b/contracts/utils/cryptography/SignatureChecker.sol @@ -41,7 +41,7 @@ library SignatureChecker { bytes memory signature ) internal view returns (bool) { (bool success, bytes memory result) = signer.staticcall( - abi.encodeWithSelector(IERC1271.isValidSignature.selector, hash, signature) + abi.encodeCall(IERC1271.isValidSignature, (hash, signature)) ); return (success && result.length >= 32 && diff --git a/contracts/utils/introspection/ERC165Checker.sol b/contracts/utils/introspection/ERC165Checker.sol index 4dc84c163..c27b2f17d 100644 --- a/contracts/utils/introspection/ERC165Checker.sol +++ b/contracts/utils/introspection/ERC165Checker.sol @@ -109,7 +109,7 @@ library ERC165Checker { */ function supportsERC165InterfaceUnchecked(address account, bytes4 interfaceId) internal view returns (bool) { // prepare call - bytes memory encodedParams = abi.encodeWithSelector(IERC165.supportsInterface.selector, interfaceId); + bytes memory encodedParams = abi.encodeCall(IERC165.supportsInterface, (interfaceId)); // perform static call bool success; From df2778f38ecf196d6ecab6df9b958fb5169d3463 Mon Sep 17 00:00:00 2001 From: Renan Souza Date: Tue, 6 Jun 2023 21:13:08 -0300 Subject: [PATCH 088/182] Remove override interface implementations (#4315) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ernesto García --- .changeset/violet-dancers-cough.md | 5 +++ contracts/access/AccessControl.sol | 10 +++--- contracts/access/AccessControlEnumerable.sol | 4 +-- contracts/governance/Governor.sol | 12 ++----- contracts/governance/IGovernor.sol | 4 +-- contracts/governance/TimelockController.sol | 12 ++----- contracts/governance/utils/Votes.sol | 16 +++++----- contracts/mocks/ERC1271WalletMock.sol | 4 +-- contracts/mocks/ERC3156FlashBorrowerMock.sol | 2 +- contracts/mocks/token/ERC1155ReceiverMock.sol | 4 +-- .../mocks/token/ERC20VotesLegacyMock.sol | 12 +++---- contracts/mocks/token/ERC721ReceiverMock.sol | 2 +- contracts/proxy/beacon/UpgradeableBeacon.sol | 2 +- contracts/proxy/utils/UUPSUpgradeable.sol | 4 +-- contracts/token/ERC1155/ERC1155.sol | 20 ++++-------- contracts/token/ERC20/ERC20.sol | 18 +++++------ .../token/ERC20/extensions/ERC20FlashMint.sol | 6 ++-- .../token/ERC20/extensions/ERC20Permit.sol | 4 +-- contracts/token/ERC20/extensions/ERC4626.sol | 32 +++++++++---------- contracts/token/ERC721/ERC721.sol | 24 +++++++------- .../ERC721/extensions/ERC721Enumerable.sol | 6 ++-- .../token/ERC721/extensions/ERC721Wrapper.sol | 7 +--- contracts/token/ERC721/utils/ERC721Holder.sol | 2 +- contracts/token/common/ERC2981.sol | 2 +- contracts/utils/cryptography/EIP712.sol | 1 - contracts/utils/introspection/ERC165.sol | 2 +- 26 files changed, 99 insertions(+), 118 deletions(-) create mode 100644 .changeset/violet-dancers-cough.md diff --git a/.changeset/violet-dancers-cough.md b/.changeset/violet-dancers-cough.md new file mode 100644 index 000000000..c6160d287 --- /dev/null +++ b/.changeset/violet-dancers-cough.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': minor +--- + +Remove the `override` specifier from functions that only override a single interface function. diff --git a/contracts/access/AccessControl.sol b/contracts/access/AccessControl.sol index 82a43933d..df16dbdab 100644 --- a/contracts/access/AccessControl.sol +++ b/contracts/access/AccessControl.sol @@ -82,7 +82,7 @@ abstract contract AccessControl is Context, IAccessControl, ERC165 { /** * @dev Returns `true` if `account` has been granted `role`. */ - function hasRole(bytes32 role, address account) public view virtual override returns (bool) { + function hasRole(bytes32 role, address account) public view virtual returns (bool) { return _roles[role].members[account]; } @@ -126,7 +126,7 @@ abstract contract AccessControl is Context, IAccessControl, ERC165 { * * To change a role's admin, use {_setRoleAdmin}. */ - function getRoleAdmin(bytes32 role) public view virtual override returns (bytes32) { + function getRoleAdmin(bytes32 role) public view virtual returns (bytes32) { return _roles[role].adminRole; } @@ -142,7 +142,7 @@ abstract contract AccessControl is Context, IAccessControl, ERC165 { * * May emit a {RoleGranted} event. */ - function grantRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) { + function grantRole(bytes32 role, address account) public virtual onlyRole(getRoleAdmin(role)) { _grantRole(role, account); } @@ -157,7 +157,7 @@ abstract contract AccessControl is Context, IAccessControl, ERC165 { * * May emit a {RoleRevoked} event. */ - function revokeRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) { + function revokeRole(bytes32 role, address account) public virtual onlyRole(getRoleAdmin(role)) { _revokeRole(role, account); } @@ -177,7 +177,7 @@ abstract contract AccessControl is Context, IAccessControl, ERC165 { * * May emit a {RoleRevoked} event. */ - function renounceRole(bytes32 role, address account) public virtual override { + function renounceRole(bytes32 role, address account) public virtual { require(account == _msgSender(), "AccessControl: can only renounce roles for self"); _revokeRole(role, account); diff --git a/contracts/access/AccessControlEnumerable.sol b/contracts/access/AccessControlEnumerable.sol index 1c37a30a9..297d34536 100644 --- a/contracts/access/AccessControlEnumerable.sol +++ b/contracts/access/AccessControlEnumerable.sol @@ -34,7 +34,7 @@ abstract contract AccessControlEnumerable is IAccessControlEnumerable, AccessCon * https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post] * for more information. */ - function getRoleMember(bytes32 role, uint256 index) public view virtual override returns (address) { + function getRoleMember(bytes32 role, uint256 index) public view virtual returns (address) { return _roleMembers[role].at(index); } @@ -42,7 +42,7 @@ abstract contract AccessControlEnumerable is IAccessControlEnumerable, AccessCon * @dev Returns the number of accounts that have `role`. Can be used * together with {getRoleMember} to enumerate all bearers of a role. */ - function getRoleMemberCount(bytes32 role) public view virtual override returns (uint256) { + function getRoleMemberCount(bytes32 role) public view virtual returns (uint256) { return _roleMembers[role].length(); } diff --git a/contracts/governance/Governor.sol b/contracts/governance/Governor.sol index 4ff8c3492..74d60c742 100644 --- a/contracts/governance/Governor.sol +++ b/contracts/governance/Governor.sol @@ -605,20 +605,14 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor, IERC721Receive /** * @dev See {IERC721Receiver-onERC721Received}. */ - function onERC721Received(address, address, uint256, bytes memory) public virtual override returns (bytes4) { + function onERC721Received(address, address, uint256, bytes memory) public virtual returns (bytes4) { return this.onERC721Received.selector; } /** * @dev See {IERC1155Receiver-onERC1155Received}. */ - function onERC1155Received( - address, - address, - uint256, - uint256, - bytes memory - ) public virtual override returns (bytes4) { + function onERC1155Received(address, address, uint256, uint256, bytes memory) public virtual returns (bytes4) { return this.onERC1155Received.selector; } @@ -631,7 +625,7 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor, IERC721Receive uint256[] memory, uint256[] memory, bytes memory - ) public virtual override returns (bytes4) { + ) public virtual returns (bytes4) { return this.onERC1155BatchReceived.selector; } } diff --git a/contracts/governance/IGovernor.sol b/contracts/governance/IGovernor.sol index 245c5d97a..992b5ca10 100644 --- a/contracts/governance/IGovernor.sol +++ b/contracts/governance/IGovernor.sol @@ -86,14 +86,14 @@ abstract contract IGovernor is IERC165, IERC6372 { * @notice module:core * @dev See {IERC6372} */ - function clock() public view virtual override returns (uint48); + function clock() public view virtual returns (uint48); /** * @notice module:core * @dev See EIP-6372. */ // solhint-disable-next-line func-name-mixedcase - function CLOCK_MODE() public view virtual override returns (string memory); + function CLOCK_MODE() public view virtual returns (string memory); /** * @notice module:voting diff --git a/contracts/governance/TimelockController.sol b/contracts/governance/TimelockController.sol index 5f09161df..9930d6a49 100644 --- a/contracts/governance/TimelockController.sol +++ b/contracts/governance/TimelockController.sol @@ -384,20 +384,14 @@ contract TimelockController is AccessControl, IERC721Receiver, IERC1155Receiver /** * @dev See {IERC721Receiver-onERC721Received}. */ - function onERC721Received(address, address, uint256, bytes memory) public virtual override returns (bytes4) { + function onERC721Received(address, address, uint256, bytes memory) public virtual returns (bytes4) { return this.onERC721Received.selector; } /** * @dev See {IERC1155Receiver-onERC1155Received}. */ - function onERC1155Received( - address, - address, - uint256, - uint256, - bytes memory - ) public virtual override returns (bytes4) { + function onERC1155Received(address, address, uint256, uint256, bytes memory) public virtual returns (bytes4) { return this.onERC1155Received.selector; } @@ -410,7 +404,7 @@ contract TimelockController is AccessControl, IERC721Receiver, IERC1155Receiver uint256[] memory, uint256[] memory, bytes memory - ) public virtual override returns (bytes4) { + ) public virtual returns (bytes4) { return this.onERC1155BatchReceived.selector; } } diff --git a/contracts/governance/utils/Votes.sol b/contracts/governance/utils/Votes.sol index 94f6d4fb9..5fc15da29 100644 --- a/contracts/governance/utils/Votes.sol +++ b/contracts/governance/utils/Votes.sol @@ -46,7 +46,7 @@ abstract contract Votes is Context, EIP712, Nonces, IERC5805 { * @dev Clock used for flagging checkpoints. Can be overridden to implement timestamp based * checkpoints (and voting), in which case {CLOCK_MODE} should be overridden as well to match. */ - function clock() public view virtual override returns (uint48) { + function clock() public view virtual returns (uint48) { return SafeCast.toUint48(block.number); } @@ -54,7 +54,7 @@ abstract contract Votes is Context, EIP712, Nonces, IERC5805 { * @dev Machine-readable description of the clock as specified in EIP-6372. */ // solhint-disable-next-line func-name-mixedcase - function CLOCK_MODE() public view virtual override returns (string memory) { + function CLOCK_MODE() public view virtual returns (string memory) { // Check that the clock was not modified require(clock() == block.number, "Votes: broken clock mode"); return "mode=blocknumber&from=default"; @@ -63,7 +63,7 @@ abstract contract Votes is Context, EIP712, Nonces, IERC5805 { /** * @dev Returns the current amount of votes that `account` has. */ - function getVotes(address account) public view virtual override returns (uint256) { + function getVotes(address account) public view virtual returns (uint256) { return _delegateCheckpoints[account].latest(); } @@ -75,7 +75,7 @@ abstract contract Votes is Context, EIP712, Nonces, IERC5805 { * * - `timepoint` must be in the past. If operating using block numbers, the block must be already mined. */ - function getPastVotes(address account, uint256 timepoint) public view virtual override returns (uint256) { + function getPastVotes(address account, uint256 timepoint) public view virtual returns (uint256) { require(timepoint < clock(), "Votes: future lookup"); return _delegateCheckpoints[account].upperLookupRecent(SafeCast.toUint32(timepoint)); } @@ -92,7 +92,7 @@ abstract contract Votes is Context, EIP712, Nonces, IERC5805 { * * - `timepoint` must be in the past. If operating using block numbers, the block must be already mined. */ - function getPastTotalSupply(uint256 timepoint) public view virtual override returns (uint256) { + function getPastTotalSupply(uint256 timepoint) public view virtual returns (uint256) { require(timepoint < clock(), "Votes: future lookup"); return _totalCheckpoints.upperLookupRecent(SafeCast.toUint32(timepoint)); } @@ -107,14 +107,14 @@ abstract contract Votes is Context, EIP712, Nonces, IERC5805 { /** * @dev Returns the delegate that `account` has chosen. */ - function delegates(address account) public view virtual override returns (address) { + function delegates(address account) public view virtual returns (address) { return _delegation[account]; } /** * @dev Delegates votes from the sender to `delegatee`. */ - function delegate(address delegatee) public virtual override { + function delegate(address delegatee) public virtual { address account = _msgSender(); _delegate(account, delegatee); } @@ -129,7 +129,7 @@ abstract contract Votes is Context, EIP712, Nonces, IERC5805 { uint8 v, bytes32 r, bytes32 s - ) public virtual override { + ) public virtual { require(block.timestamp <= expiry, "Votes: signature expired"); address signer = ECDSA.recover( _hashTypedDataV4(keccak256(abi.encode(_DELEGATION_TYPEHASH, delegatee, nonce, expiry))), diff --git a/contracts/mocks/ERC1271WalletMock.sol b/contracts/mocks/ERC1271WalletMock.sol index a28fd0fb9..2bc390636 100644 --- a/contracts/mocks/ERC1271WalletMock.sol +++ b/contracts/mocks/ERC1271WalletMock.sol @@ -9,13 +9,13 @@ import "../utils/cryptography/ECDSA.sol"; contract ERC1271WalletMock is Ownable, IERC1271 { constructor(address originalOwner) Ownable(originalOwner) {} - function isValidSignature(bytes32 hash, bytes memory signature) public view override returns (bytes4 magicValue) { + function isValidSignature(bytes32 hash, bytes memory signature) public view returns (bytes4 magicValue) { return ECDSA.recover(hash, signature) == owner() ? this.isValidSignature.selector : bytes4(0); } } contract ERC1271MaliciousMock is IERC1271 { - function isValidSignature(bytes32, bytes memory) public pure override returns (bytes4) { + function isValidSignature(bytes32, bytes memory) public pure returns (bytes4) { assembly { mstore(0, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff) return(0, 32) diff --git a/contracts/mocks/ERC3156FlashBorrowerMock.sol b/contracts/mocks/ERC3156FlashBorrowerMock.sol index 8d24af6c1..abf836606 100644 --- a/contracts/mocks/ERC3156FlashBorrowerMock.sol +++ b/contracts/mocks/ERC3156FlashBorrowerMock.sol @@ -33,7 +33,7 @@ contract ERC3156FlashBorrowerMock is IERC3156FlashBorrower { uint256 amount, uint256 fee, bytes calldata data - ) public override returns (bytes32) { + ) public returns (bytes32) { require(msg.sender == token); emit BalanceOf(token, address(this), IERC20(token).balanceOf(address(this))); diff --git a/contracts/mocks/token/ERC1155ReceiverMock.sol b/contracts/mocks/token/ERC1155ReceiverMock.sol index 9f75f002b..2b591c058 100644 --- a/contracts/mocks/token/ERC1155ReceiverMock.sol +++ b/contracts/mocks/token/ERC1155ReceiverMock.sol @@ -27,7 +27,7 @@ contract ERC1155ReceiverMock is ERC165, IERC1155Receiver { uint256 id, uint256 value, bytes calldata data - ) external override returns (bytes4) { + ) external returns (bytes4) { require(!_recReverts, "ERC1155ReceiverMock: reverting on receive"); emit Received(operator, from, id, value, data, gasleft()); return _recRetval; @@ -39,7 +39,7 @@ contract ERC1155ReceiverMock is ERC165, IERC1155Receiver { uint256[] calldata ids, uint256[] calldata values, bytes calldata data - ) external override returns (bytes4) { + ) external returns (bytes4) { require(!_batReverts, "ERC1155ReceiverMock: reverting on batch receive"); emit BatchReceived(operator, from, ids, values, data, gasleft()); return _batRetval; diff --git a/contracts/mocks/token/ERC20VotesLegacyMock.sol b/contracts/mocks/token/ERC20VotesLegacyMock.sol index cf8afa58a..88ba84236 100644 --- a/contracts/mocks/token/ERC20VotesLegacyMock.sol +++ b/contracts/mocks/token/ERC20VotesLegacyMock.sol @@ -41,14 +41,14 @@ abstract contract ERC20VotesLegacyMock is IVotes, ERC20Permit { /** * @dev Get the address `account` is currently delegating to. */ - function delegates(address account) public view virtual override returns (address) { + function delegates(address account) public view virtual returns (address) { return _delegates[account]; } /** * @dev Gets the current votes balance for `account` */ - function getVotes(address account) public view virtual override returns (uint256) { + function getVotes(address account) public view virtual returns (uint256) { uint256 pos = _checkpoints[account].length; unchecked { return pos == 0 ? 0 : _checkpoints[account][pos - 1].votes; @@ -62,7 +62,7 @@ abstract contract ERC20VotesLegacyMock is IVotes, ERC20Permit { * * - `blockNumber` must have been already mined */ - function getPastVotes(address account, uint256 blockNumber) public view virtual override returns (uint256) { + function getPastVotes(address account, uint256 blockNumber) public view virtual returns (uint256) { require(blockNumber < block.number, "ERC20Votes: block not yet mined"); return _checkpointsLookup(_checkpoints[account], blockNumber); } @@ -75,7 +75,7 @@ abstract contract ERC20VotesLegacyMock is IVotes, ERC20Permit { * * - `blockNumber` must have been already mined */ - function getPastTotalSupply(uint256 blockNumber) public view virtual override returns (uint256) { + function getPastTotalSupply(uint256 blockNumber) public view virtual returns (uint256) { require(blockNumber < block.number, "ERC20Votes: block not yet mined"); return _checkpointsLookup(_totalSupplyCheckpoints, blockNumber); } @@ -127,7 +127,7 @@ abstract contract ERC20VotesLegacyMock is IVotes, ERC20Permit { /** * @dev Delegate votes from the sender to `delegatee`. */ - function delegate(address delegatee) public virtual override { + function delegate(address delegatee) public virtual { _delegate(_msgSender(), delegatee); } @@ -141,7 +141,7 @@ abstract contract ERC20VotesLegacyMock is IVotes, ERC20Permit { uint8 v, bytes32 r, bytes32 s - ) public virtual override { + ) public virtual { require(block.timestamp <= expiry, "ERC20Votes: signature expired"); address signer = ECDSA.recover( _hashTypedDataV4(keccak256(abi.encode(_DELEGATION_TYPEHASH, delegatee, nonce, expiry))), diff --git a/contracts/mocks/token/ERC721ReceiverMock.sol b/contracts/mocks/token/ERC721ReceiverMock.sol index 01526566a..e97ceacf3 100644 --- a/contracts/mocks/token/ERC721ReceiverMock.sol +++ b/contracts/mocks/token/ERC721ReceiverMock.sol @@ -27,7 +27,7 @@ contract ERC721ReceiverMock is IERC721Receiver { address from, uint256 tokenId, bytes memory data - ) public override returns (bytes4) { + ) public returns (bytes4) { if (_error == Error.RevertWithMessage) { revert("ERC721ReceiverMock: reverting"); } else if (_error == Error.RevertWithoutMessage) { diff --git a/contracts/proxy/beacon/UpgradeableBeacon.sol b/contracts/proxy/beacon/UpgradeableBeacon.sol index 24d167f60..37d27f67a 100644 --- a/contracts/proxy/beacon/UpgradeableBeacon.sol +++ b/contracts/proxy/beacon/UpgradeableBeacon.sol @@ -30,7 +30,7 @@ contract UpgradeableBeacon is IBeacon, Ownable { /** * @dev Returns the current implementation address. */ - function implementation() public view virtual override returns (address) { + function implementation() public view virtual returns (address) { return _implementation; } diff --git a/contracts/proxy/utils/UUPSUpgradeable.sol b/contracts/proxy/utils/UUPSUpgradeable.sol index c281c9091..1fd73247c 100644 --- a/contracts/proxy/utils/UUPSUpgradeable.sol +++ b/contracts/proxy/utils/UUPSUpgradeable.sol @@ -52,7 +52,7 @@ abstract contract UUPSUpgradeable is IERC1822Proxiable, ERC1967Upgrade { * bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this * function revert if invoked through a proxy. This is guaranteed by the `notDelegated` modifier. */ - function proxiableUUID() external view virtual override notDelegated returns (bytes32) { + function proxiableUUID() external view virtual notDelegated returns (bytes32) { return _IMPLEMENTATION_SLOT; } @@ -92,7 +92,7 @@ abstract contract UUPSUpgradeable is IERC1822Proxiable, ERC1967Upgrade { * Normally, this function will use an xref:access.adoc[access control] modifier such as {Ownable-onlyOwner}. * * ```solidity - * function _authorizeUpgrade(address) internal override onlyOwner {} + * function _authorizeUpgrade(address) internal onlyOwner {} * ``` */ function _authorizeUpgrade(address newImplementation) internal virtual; diff --git a/contracts/token/ERC1155/ERC1155.sol b/contracts/token/ERC1155/ERC1155.sol index 6761edd24..c2f217d7e 100644 --- a/contracts/token/ERC1155/ERC1155.sol +++ b/contracts/token/ERC1155/ERC1155.sol @@ -53,7 +53,7 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { * Clients calling this function must replace the `\{id\}` substring with the * actual token type ID. */ - function uri(uint256) public view virtual override returns (string memory) { + function uri(uint256) public view virtual returns (string memory) { return _uri; } @@ -64,7 +64,7 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { * * - `account` cannot be the zero address. */ - function balanceOf(address account, uint256 id) public view virtual override returns (uint256) { + function balanceOf(address account, uint256 id) public view virtual returns (uint256) { return _balances[id][account]; } @@ -78,7 +78,7 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { function balanceOfBatch( address[] memory accounts, uint256[] memory ids - ) public view virtual override returns (uint256[] memory) { + ) public view virtual returns (uint256[] memory) { require(accounts.length == ids.length, "ERC1155: accounts and ids length mismatch"); uint256[] memory batchBalances = new uint256[](accounts.length); @@ -93,27 +93,21 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { /** * @dev See {IERC1155-setApprovalForAll}. */ - function setApprovalForAll(address operator, bool approved) public virtual override { + function setApprovalForAll(address operator, bool approved) public virtual { _setApprovalForAll(_msgSender(), operator, approved); } /** * @dev See {IERC1155-isApprovedForAll}. */ - function isApprovedForAll(address account, address operator) public view virtual override returns (bool) { + function isApprovedForAll(address account, address operator) public view virtual returns (bool) { return _operatorApprovals[account][operator]; } /** * @dev See {IERC1155-safeTransferFrom}. */ - function safeTransferFrom( - address from, - address to, - uint256 id, - uint256 amount, - bytes memory data - ) public virtual override { + function safeTransferFrom(address from, address to, uint256 id, uint256 amount, bytes memory data) public virtual { require( from == _msgSender() || isApprovedForAll(from, _msgSender()), "ERC1155: caller is not token owner or approved" @@ -130,7 +124,7 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { uint256[] memory ids, uint256[] memory amounts, bytes memory data - ) public virtual override { + ) public virtual { require( from == _msgSender() || isApprovedForAll(from, _msgSender()), "ERC1155: caller is not token owner or approved" diff --git a/contracts/token/ERC20/ERC20.sol b/contracts/token/ERC20/ERC20.sol index 2646a0ddc..4db525a7a 100644 --- a/contracts/token/ERC20/ERC20.sol +++ b/contracts/token/ERC20/ERC20.sol @@ -58,7 +58,7 @@ contract ERC20 is Context, IERC20, IERC20Metadata { /** * @dev Returns the name of the token. */ - function name() public view virtual override returns (string memory) { + function name() public view virtual returns (string memory) { return _name; } @@ -66,7 +66,7 @@ contract ERC20 is Context, IERC20, IERC20Metadata { * @dev Returns the symbol of the token, usually a shorter version of the * name. */ - function symbol() public view virtual override returns (string memory) { + function symbol() public view virtual returns (string memory) { return _symbol; } @@ -83,21 +83,21 @@ contract ERC20 is Context, IERC20, IERC20Metadata { * no way affects any of the arithmetic of the contract, including * {IERC20-balanceOf} and {IERC20-transfer}. */ - function decimals() public view virtual override returns (uint8) { + function decimals() public view virtual returns (uint8) { return 18; } /** * @dev See {IERC20-totalSupply}. */ - function totalSupply() public view virtual override returns (uint256) { + function totalSupply() public view virtual returns (uint256) { return _totalSupply; } /** * @dev See {IERC20-balanceOf}. */ - function balanceOf(address account) public view virtual override returns (uint256) { + function balanceOf(address account) public view virtual returns (uint256) { return _balances[account]; } @@ -109,7 +109,7 @@ contract ERC20 is Context, IERC20, IERC20Metadata { * - `to` cannot be the zero address. * - the caller must have a balance of at least `amount`. */ - function transfer(address to, uint256 amount) public virtual override returns (bool) { + function transfer(address to, uint256 amount) public virtual returns (bool) { address owner = _msgSender(); _transfer(owner, to, amount); return true; @@ -118,7 +118,7 @@ contract ERC20 is Context, IERC20, IERC20Metadata { /** * @dev See {IERC20-allowance}. */ - function allowance(address owner, address spender) public view virtual override returns (uint256) { + function allowance(address owner, address spender) public view virtual returns (uint256) { return _allowances[owner][spender]; } @@ -132,7 +132,7 @@ contract ERC20 is Context, IERC20, IERC20Metadata { * * - `spender` cannot be the zero address. */ - function approve(address spender, uint256 amount) public virtual override returns (bool) { + function approve(address spender, uint256 amount) public virtual returns (bool) { address owner = _msgSender(); _approve(owner, spender, amount); return true; @@ -154,7 +154,7 @@ contract ERC20 is Context, IERC20, IERC20Metadata { * - the caller must have allowance for ``from``'s tokens of at least * `amount`. */ - function transferFrom(address from, address to, uint256 amount) public virtual override returns (bool) { + function transferFrom(address from, address to, uint256 amount) public virtual returns (bool) { address spender = _msgSender(); _spendAllowance(from, spender, amount); _transfer(from, to, amount); diff --git a/contracts/token/ERC20/extensions/ERC20FlashMint.sol b/contracts/token/ERC20/extensions/ERC20FlashMint.sol index 18b0a81c6..7a4076678 100644 --- a/contracts/token/ERC20/extensions/ERC20FlashMint.sol +++ b/contracts/token/ERC20/extensions/ERC20FlashMint.sol @@ -24,7 +24,7 @@ abstract contract ERC20FlashMint is ERC20, IERC3156FlashLender { * @param token The address of the token that is requested. * @return The amount of token that can be loaned. */ - function maxFlashLoan(address token) public view virtual override returns (uint256) { + function maxFlashLoan(address token) public view virtual returns (uint256) { return token == address(this) ? type(uint256).max - totalSupply() : 0; } @@ -36,7 +36,7 @@ abstract contract ERC20FlashMint is ERC20, IERC3156FlashLender { * @param amount The amount of tokens to be loaned. * @return The fees applied to the corresponding flash loan. */ - function flashFee(address token, uint256 amount) public view virtual override returns (uint256) { + function flashFee(address token, uint256 amount) public view virtual returns (uint256) { require(token == address(this), "ERC20FlashMint: wrong token"); return _flashFee(token, amount); } @@ -88,7 +88,7 @@ abstract contract ERC20FlashMint is ERC20, IERC3156FlashLender { address token, uint256 amount, bytes calldata data - ) public virtual override returns (bool) { + ) public virtual returns (bool) { require(amount <= maxFlashLoan(token), "ERC20FlashMint: amount exceeds maxFlashLoan"); uint256 fee = flashFee(token, amount); _mint(address(receiver), amount); diff --git a/contracts/token/ERC20/extensions/ERC20Permit.sol b/contracts/token/ERC20/extensions/ERC20Permit.sol index e7d43c2dd..9379e4451 100644 --- a/contracts/token/ERC20/extensions/ERC20Permit.sol +++ b/contracts/token/ERC20/extensions/ERC20Permit.sol @@ -42,7 +42,7 @@ abstract contract ERC20Permit is ERC20, IERC20Permit, EIP712, Nonces { uint8 v, bytes32 r, bytes32 s - ) public virtual override { + ) public virtual { require(block.timestamp <= deadline, "ERC20Permit: expired deadline"); bytes32 structHash = keccak256(abi.encode(_PERMIT_TYPEHASH, owner, spender, value, _useNonce(owner), deadline)); @@ -66,7 +66,7 @@ abstract contract ERC20Permit is ERC20, IERC20Permit, EIP712, Nonces { * @dev See {IERC20Permit-DOMAIN_SEPARATOR}. */ // solhint-disable-next-line func-name-mixedcase - function DOMAIN_SEPARATOR() external view virtual override returns (bytes32) { + function DOMAIN_SEPARATOR() external view virtual returns (bytes32) { return _domainSeparatorV4(); } } diff --git a/contracts/token/ERC20/extensions/ERC4626.sol b/contracts/token/ERC20/extensions/ERC4626.sol index 54f5ae24b..665c95a02 100644 --- a/contracts/token/ERC20/extensions/ERC4626.sol +++ b/contracts/token/ERC20/extensions/ERC4626.sol @@ -90,67 +90,67 @@ abstract contract ERC4626 is ERC20, IERC4626 { } /** @dev See {IERC4626-asset}. */ - function asset() public view virtual override returns (address) { + function asset() public view virtual returns (address) { return address(_asset); } /** @dev See {IERC4626-totalAssets}. */ - function totalAssets() public view virtual override returns (uint256) { + function totalAssets() public view virtual returns (uint256) { return _asset.balanceOf(address(this)); } /** @dev See {IERC4626-convertToShares}. */ - function convertToShares(uint256 assets) public view virtual override returns (uint256) { + function convertToShares(uint256 assets) public view virtual returns (uint256) { return _convertToShares(assets, Math.Rounding.Down); } /** @dev See {IERC4626-convertToAssets}. */ - function convertToAssets(uint256 shares) public view virtual override returns (uint256) { + function convertToAssets(uint256 shares) public view virtual returns (uint256) { return _convertToAssets(shares, Math.Rounding.Down); } /** @dev See {IERC4626-maxDeposit}. */ - function maxDeposit(address) public view virtual override returns (uint256) { + function maxDeposit(address) public view virtual returns (uint256) { return type(uint256).max; } /** @dev See {IERC4626-maxMint}. */ - function maxMint(address) public view virtual override returns (uint256) { + function maxMint(address) public view virtual returns (uint256) { return type(uint256).max; } /** @dev See {IERC4626-maxWithdraw}. */ - function maxWithdraw(address owner) public view virtual override returns (uint256) { + function maxWithdraw(address owner) public view virtual returns (uint256) { return _convertToAssets(balanceOf(owner), Math.Rounding.Down); } /** @dev See {IERC4626-maxRedeem}. */ - function maxRedeem(address owner) public view virtual override returns (uint256) { + function maxRedeem(address owner) public view virtual returns (uint256) { return balanceOf(owner); } /** @dev See {IERC4626-previewDeposit}. */ - function previewDeposit(uint256 assets) public view virtual override returns (uint256) { + function previewDeposit(uint256 assets) public view virtual returns (uint256) { return _convertToShares(assets, Math.Rounding.Down); } /** @dev See {IERC4626-previewMint}. */ - function previewMint(uint256 shares) public view virtual override returns (uint256) { + function previewMint(uint256 shares) public view virtual returns (uint256) { return _convertToAssets(shares, Math.Rounding.Up); } /** @dev See {IERC4626-previewWithdraw}. */ - function previewWithdraw(uint256 assets) public view virtual override returns (uint256) { + function previewWithdraw(uint256 assets) public view virtual returns (uint256) { return _convertToShares(assets, Math.Rounding.Up); } /** @dev See {IERC4626-previewRedeem}. */ - function previewRedeem(uint256 shares) public view virtual override returns (uint256) { + function previewRedeem(uint256 shares) public view virtual returns (uint256) { return _convertToAssets(shares, Math.Rounding.Down); } /** @dev See {IERC4626-deposit}. */ - function deposit(uint256 assets, address receiver) public virtual override returns (uint256) { + function deposit(uint256 assets, address receiver) public virtual returns (uint256) { require(assets <= maxDeposit(receiver), "ERC4626: deposit more than max"); uint256 shares = previewDeposit(assets); @@ -164,7 +164,7 @@ abstract contract ERC4626 is ERC20, IERC4626 { * As opposed to {deposit}, minting is allowed even if the vault is in a state where the price of a share is zero. * In this case, the shares will be minted without requiring any assets to be deposited. */ - function mint(uint256 shares, address receiver) public virtual override returns (uint256) { + function mint(uint256 shares, address receiver) public virtual returns (uint256) { require(shares <= maxMint(receiver), "ERC4626: mint more than max"); uint256 assets = previewMint(shares); @@ -174,7 +174,7 @@ abstract contract ERC4626 is ERC20, IERC4626 { } /** @dev See {IERC4626-withdraw}. */ - function withdraw(uint256 assets, address receiver, address owner) public virtual override returns (uint256) { + function withdraw(uint256 assets, address receiver, address owner) public virtual returns (uint256) { require(assets <= maxWithdraw(owner), "ERC4626: withdraw more than max"); uint256 shares = previewWithdraw(assets); @@ -184,7 +184,7 @@ abstract contract ERC4626 is ERC20, IERC4626 { } /** @dev See {IERC4626-redeem}. */ - function redeem(uint256 shares, address receiver, address owner) public virtual override returns (uint256) { + function redeem(uint256 shares, address receiver, address owner) public virtual returns (uint256) { require(shares <= maxRedeem(owner), "ERC4626: redeem more than max"); uint256 assets = previewRedeem(shares); diff --git a/contracts/token/ERC721/ERC721.sol b/contracts/token/ERC721/ERC721.sol index af00ecaf4..3eb3f74cf 100644 --- a/contracts/token/ERC721/ERC721.sol +++ b/contracts/token/ERC721/ERC721.sol @@ -57,7 +57,7 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata { /** * @dev See {IERC721-balanceOf}. */ - function balanceOf(address owner) public view virtual override returns (uint256) { + function balanceOf(address owner) public view virtual returns (uint256) { require(owner != address(0), "ERC721: address zero is not a valid owner"); return _balances[owner]; } @@ -65,7 +65,7 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata { /** * @dev See {IERC721-ownerOf}. */ - function ownerOf(uint256 tokenId) public view virtual override returns (address) { + function ownerOf(uint256 tokenId) public view virtual returns (address) { address owner = _ownerOf(tokenId); require(owner != address(0), "ERC721: invalid token ID"); return owner; @@ -74,21 +74,21 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata { /** * @dev See {IERC721Metadata-name}. */ - function name() public view virtual override returns (string memory) { + function name() public view virtual returns (string memory) { return _name; } /** * @dev See {IERC721Metadata-symbol}. */ - function symbol() public view virtual override returns (string memory) { + function symbol() public view virtual returns (string memory) { return _symbol; } /** * @dev See {IERC721Metadata-tokenURI}. */ - function tokenURI(uint256 tokenId) public view virtual override returns (string memory) { + function tokenURI(uint256 tokenId) public view virtual returns (string memory) { _requireMinted(tokenId); string memory baseURI = _baseURI(); @@ -107,7 +107,7 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata { /** * @dev See {IERC721-approve}. */ - function approve(address to, uint256 tokenId) public virtual override { + function approve(address to, uint256 tokenId) public virtual { address owner = ownerOf(tokenId); require(to != owner, "ERC721: approval to current owner"); @@ -122,7 +122,7 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata { /** * @dev See {IERC721-getApproved}. */ - function getApproved(uint256 tokenId) public view virtual override returns (address) { + function getApproved(uint256 tokenId) public view virtual returns (address) { _requireMinted(tokenId); return _tokenApprovals[tokenId]; @@ -131,21 +131,21 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata { /** * @dev See {IERC721-setApprovalForAll}. */ - function setApprovalForAll(address operator, bool approved) public virtual override { + function setApprovalForAll(address operator, bool approved) public virtual { _setApprovalForAll(_msgSender(), operator, approved); } /** * @dev See {IERC721-isApprovedForAll}. */ - function isApprovedForAll(address owner, address operator) public view virtual override returns (bool) { + function isApprovedForAll(address owner, address operator) public view virtual returns (bool) { return _operatorApprovals[owner][operator]; } /** * @dev See {IERC721-transferFrom}. */ - function transferFrom(address from, address to, uint256 tokenId) public virtual override { + function transferFrom(address from, address to, uint256 tokenId) public virtual { //solhint-disable-next-line max-line-length require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner or approved"); @@ -155,14 +155,14 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata { /** * @dev See {IERC721-safeTransferFrom}. */ - function safeTransferFrom(address from, address to, uint256 tokenId) public virtual override { + function safeTransferFrom(address from, address to, uint256 tokenId) public virtual { safeTransferFrom(from, to, tokenId, ""); } /** * @dev See {IERC721-safeTransferFrom}. */ - function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) public virtual override { + function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) public virtual { require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner or approved"); _safeTransfer(from, to, tokenId, data); } diff --git a/contracts/token/ERC721/extensions/ERC721Enumerable.sol b/contracts/token/ERC721/extensions/ERC721Enumerable.sol index 2168e100b..8cea9e19a 100644 --- a/contracts/token/ERC721/extensions/ERC721Enumerable.sol +++ b/contracts/token/ERC721/extensions/ERC721Enumerable.sol @@ -36,7 +36,7 @@ abstract contract ERC721Enumerable is ERC721, IERC721Enumerable { /** * @dev See {IERC721Enumerable-tokenOfOwnerByIndex}. */ - function tokenOfOwnerByIndex(address owner, uint256 index) public view virtual override returns (uint256) { + function tokenOfOwnerByIndex(address owner, uint256 index) public view virtual returns (uint256) { require(index < balanceOf(owner), "ERC721Enumerable: owner index out of bounds"); return _ownedTokens[owner][index]; } @@ -44,14 +44,14 @@ abstract contract ERC721Enumerable is ERC721, IERC721Enumerable { /** * @dev See {IERC721Enumerable-totalSupply}. */ - function totalSupply() public view virtual override returns (uint256) { + function totalSupply() public view virtual returns (uint256) { return _allTokens.length; } /** * @dev See {IERC721Enumerable-tokenByIndex}. */ - function tokenByIndex(uint256 index) public view virtual override returns (uint256) { + function tokenByIndex(uint256 index) public view virtual returns (uint256) { require(index < totalSupply(), "ERC721Enumerable: global index out of bounds"); return _allTokens[index]; } diff --git a/contracts/token/ERC721/extensions/ERC721Wrapper.sol b/contracts/token/ERC721/extensions/ERC721Wrapper.sol index 58346182b..9226349f4 100644 --- a/contracts/token/ERC721/extensions/ERC721Wrapper.sol +++ b/contracts/token/ERC721/extensions/ERC721Wrapper.sol @@ -67,12 +67,7 @@ abstract contract ERC721Wrapper is ERC721, IERC721Receiver { * WARNING: Doesn't work with unsafe transfers (eg. {IERC721-transferFrom}). Use {ERC721Wrapper-_recover} * for recovering in that scenario. */ - function onERC721Received( - address, - address from, - uint256 tokenId, - bytes memory - ) public virtual override returns (bytes4) { + function onERC721Received(address, address from, uint256 tokenId, bytes memory) public virtual returns (bytes4) { require(address(underlying()) == _msgSender(), "ERC721Wrapper: caller is not underlying"); _safeMint(from, tokenId); return IERC721Receiver.onERC721Received.selector; diff --git a/contracts/token/ERC721/utils/ERC721Holder.sol b/contracts/token/ERC721/utils/ERC721Holder.sol index 41bee820c..9f5b2e9f9 100644 --- a/contracts/token/ERC721/utils/ERC721Holder.sol +++ b/contracts/token/ERC721/utils/ERC721Holder.sol @@ -17,7 +17,7 @@ contract ERC721Holder is IERC721Receiver { * * Always returns `IERC721Receiver.onERC721Received.selector`. */ - function onERC721Received(address, address, uint256, bytes memory) public virtual override returns (bytes4) { + function onERC721Received(address, address, uint256, bytes memory) public virtual returns (bytes4) { return this.onERC721Received.selector; } } diff --git a/contracts/token/common/ERC2981.sol b/contracts/token/common/ERC2981.sol index 5d40388aa..85e902733 100644 --- a/contracts/token/common/ERC2981.sol +++ b/contracts/token/common/ERC2981.sol @@ -40,7 +40,7 @@ abstract contract ERC2981 is IERC2981, ERC165 { /** * @inheritdoc IERC2981 */ - function royaltyInfo(uint256 tokenId, uint256 salePrice) public view virtual override returns (address, uint256) { + function royaltyInfo(uint256 tokenId, uint256 salePrice) public view virtual returns (address, uint256) { RoyaltyInfo memory royalty = _tokenRoyaltyInfo[tokenId]; if (royalty.receiver == address(0)) { diff --git a/contracts/utils/cryptography/EIP712.sol b/contracts/utils/cryptography/EIP712.sol index 526b13580..b9c9c9d19 100644 --- a/contracts/utils/cryptography/EIP712.sol +++ b/contracts/utils/cryptography/EIP712.sol @@ -118,7 +118,6 @@ abstract contract EIP712 is IERC5267 { public view virtual - override returns ( bytes1 fields, string memory name, diff --git a/contracts/utils/introspection/ERC165.sol b/contracts/utils/introspection/ERC165.sol index 6849637e8..111913007 100644 --- a/contracts/utils/introspection/ERC165.sol +++ b/contracts/utils/introspection/ERC165.sol @@ -21,7 +21,7 @@ abstract contract ERC165 is IERC165 { /** * @dev See {IERC165-supportsInterface}. */ - function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { + function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) { return interfaceId == type(IERC165).interfaceId; } } From d9474327a492f9f310f31bc53f38dbea56ed9a57 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Wed, 7 Jun 2023 02:32:14 +0200 Subject: [PATCH 089/182] Merge pull request from GHSA-5h3x-9wvq-w4m2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Francisco Co-authored-by: Ernesto García --- .changeset/swift-bags-divide.md | 5 ++ contracts/governance/Governor.sol | 90 ++++++++++++++++++++++++++++++- test/governance/Governor.t.sol | 55 +++++++++++++++++++ test/governance/Governor.test.js | 83 ++++++++++++++++++++++++++++ 4 files changed, 231 insertions(+), 2 deletions(-) create mode 100644 .changeset/swift-bags-divide.md create mode 100644 test/governance/Governor.t.sol diff --git a/.changeset/swift-bags-divide.md b/.changeset/swift-bags-divide.md new file mode 100644 index 000000000..9af63e98e --- /dev/null +++ b/.changeset/swift-bags-divide.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': patch +--- + +`Governor`: Add a mechanism to restrict the address of the proposer using a suffix in the description. diff --git a/contracts/governance/Governor.sol b/contracts/governance/Governor.sol index 74d60c742..2e2289c6a 100644 --- a/contracts/governance/Governor.sol +++ b/contracts/governance/Governor.sol @@ -263,7 +263,7 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor, IERC721Receive } /** - * @dev See {IGovernor-propose}. + * @dev See {IGovernor-propose}. This function has opt-in frontrunning protection, described in {_isValidDescriptionForProposer}. */ function propose( address[] memory targets, @@ -272,8 +272,9 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor, IERC721Receive string memory description ) public virtual override returns (uint256) { address proposer = _msgSender(); - uint256 currentTimepoint = clock(); + require(_isValidDescriptionForProposer(proposer, description), "Governor: proposer restricted"); + uint256 currentTimepoint = clock(); require( getVotes(proposer, currentTimepoint - 1) >= proposalThreshold(), "Governor: proposer votes below proposal threshold" @@ -628,4 +629,89 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor, IERC721Receive ) public virtual returns (bytes4) { return this.onERC1155BatchReceived.selector; } + + /** + * @dev Check if the proposer is authorized to submit a proposal with the given description. + * + * If the proposal description ends with `#proposer=0x???`, where `0x???` is an address written as a hex string + * (case insensitive), then the submission of this proposal will only be authorized to said address. + * + * This is used for frontrunning protection. By adding this pattern at the end of their proposal, one can ensure + * that no other address can submit the same proposal. An attacker would have to either remove or change that part, + * which would result in a different proposal id. + * + * If the description does not match this pattern, it is unrestricted and anyone can submit it. This includes: + * - If the `0x???` part is not a valid hex string. + * - If the `0x???` part is a valid hex string, but does not contain exactly 40 hex digits. + * - If it ends with the expected suffix followed by newlines or other whitespace. + * - If it ends with some other similar suffix, e.g. `#other=abc`. + * - If it does not end with any such suffix. + */ + function _isValidDescriptionForProposer( + address proposer, + string memory description + ) internal view virtual returns (bool) { + uint256 len = bytes(description).length; + + // Length is too short to contain a valid proposer suffix + if (len < 52) { + return true; + } + + // Extract what would be the `#proposer=0x` marker beginning the suffix + bytes12 marker; + assembly { + // - Start of the string contents in memory = description + 32 + // - First character of the marker = len - 52 + // - Length of "#proposer=0x0000000000000000000000000000000000000000" = 52 + // - We read the memory word starting at the first character of the marker: + // - (description + 32) + (len - 52) = description + (len - 20) + // - Note: Solidity will ignore anything past the first 12 bytes + marker := mload(add(description, sub(len, 20))) + } + + // If the marker is not found, there is no proposer suffix to check + if (marker != bytes12("#proposer=0x")) { + return true; + } + + // Parse the 40 characters following the marker as uint160 + uint160 recovered = 0; + for (uint256 i = len - 40; i < len; ++i) { + (bool isHex, uint8 value) = _tryHexToUint(bytes(description)[i]); + // If any of the characters is not a hex digit, ignore the suffix entirely + if (!isHex) { + return true; + } + recovered = (recovered << 4) | value; + } + + return recovered == uint160(proposer); + } + + /** + * @dev Try to parse a character from a string as a hex value. Returns `(true, value)` if the char is in + * `[0-9a-fA-F]` and `(false, 0)` otherwise. Value is guaranteed to be in the range `0 <= value < 16` + */ + function _tryHexToUint(bytes1 char) private pure returns (bool, uint8) { + uint8 c = uint8(char); + unchecked { + // Case 0-9 + if (47 < c && c < 58) { + return (true, c - 48); + } + // Case A-F + else if (64 < c && c < 71) { + return (true, c - 55); + } + // Case a-f + else if (96 < c && c < 103) { + return (true, c - 87); + } + // Else: not a hex char + else { + return (false, 0); + } + } + } } diff --git a/test/governance/Governor.t.sol b/test/governance/Governor.t.sol new file mode 100644 index 000000000..43c4c5ddd --- /dev/null +++ b/test/governance/Governor.t.sol @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.19; + +import "forge-std/Test.sol"; +import "../../contracts/utils/Strings.sol"; +import "../../contracts/governance/Governor.sol"; + +contract GovernorInternalTest is Test, Governor { + constructor() Governor("") {} + + function testValidDescriptionForProposer(string memory description, address proposer, bool includeProposer) public { + if (includeProposer) { + description = string.concat(description, "#proposer=", Strings.toHexString(proposer)); + } + assertTrue(_isValidDescriptionForProposer(proposer, description)); + } + + function testInvalidDescriptionForProposer( + string memory description, + address commitProposer, + address actualProposer + ) public { + vm.assume(commitProposer != actualProposer); + description = string.concat(description, "#proposer=", Strings.toHexString(commitProposer)); + assertFalse(_isValidDescriptionForProposer(actualProposer, description)); + } + + // We don't need to truly implement implement the missing functions because we are just testing + // internal helpers. + + function clock() public pure override returns (uint48) {} + + // solhint-disable-next-line func-name-mixedcase + function CLOCK_MODE() public pure override returns (string memory) {} + + // solhint-disable-next-line func-name-mixedcase + function COUNTING_MODE() public pure virtual override returns (string memory) {} + + function votingDelay() public pure virtual override returns (uint256) {} + + function votingPeriod() public pure virtual override returns (uint256) {} + + function quorum(uint256) public pure virtual override returns (uint256) {} + + function hasVoted(uint256, address) public pure virtual override returns (bool) {} + + function _quorumReached(uint256) internal pure virtual override returns (bool) {} + + function _voteSucceeded(uint256) internal pure virtual override returns (bool) {} + + function _getVotes(address, uint256, bytes memory) internal pure virtual override returns (uint256) {} + + function _countVote(uint256, address, uint8, uint256, bytes memory) internal virtual override {} +} diff --git a/test/governance/Governor.test.js b/test/governance/Governor.test.js index d34482212..96feaf3a7 100644 --- a/test/governance/Governor.test.js +++ b/test/governance/Governor.test.js @@ -551,6 +551,89 @@ contract('Governor', function (accounts) { }); }); + describe('frontrun protection using description suffix', function () { + describe('without protection', function () { + describe('without suffix', function () { + it('proposer can propose', async function () { + expectEvent(await this.helper.propose({ from: proposer }), 'ProposalCreated'); + }); + + it('someone else can propose', async function () { + expectEvent(await this.helper.propose({ from: voter1 }), 'ProposalCreated'); + }); + }); + + describe('with different suffix', function () { + beforeEach(async function () { + this.proposal = this.helper.setProposal( + [ + { + target: this.receiver.address, + data: this.receiver.contract.methods.mockFunction().encodeABI(), + value, + }, + ], + `#wrong-suffix=${proposer}`, + ); + }); + + it('proposer can propose', async function () { + expectEvent(await this.helper.propose({ from: proposer }), 'ProposalCreated'); + }); + + it('someone else can propose', async function () { + expectEvent(await this.helper.propose({ from: voter1 }), 'ProposalCreated'); + }); + }); + + describe('with proposer suffix but bad address part', function () { + beforeEach(async function () { + this.proposal = this.helper.setProposal( + [ + { + target: this.receiver.address, + data: this.receiver.contract.methods.mockFunction().encodeABI(), + value, + }, + ], + `#proposer=0x3C44CdDdB6a900fa2b585dd299e03d12FA429XYZ`, // XYZ are not a valid hex char + ); + }); + + it('propose can propose', async function () { + expectEvent(await this.helper.propose({ from: proposer }), 'ProposalCreated'); + }); + + it('someone else can propose', async function () { + expectEvent(await this.helper.propose({ from: voter1 }), 'ProposalCreated'); + }); + }); + }); + + describe('with protection via proposer suffix', function () { + beforeEach(async function () { + this.proposal = this.helper.setProposal( + [ + { + target: this.receiver.address, + data: this.receiver.contract.methods.mockFunction().encodeABI(), + value, + }, + ], + `#proposer=${proposer}`, + ); + }); + + it('proposer can propose', async function () { + expectEvent(await this.helper.propose({ from: proposer }), 'ProposalCreated'); + }); + + it('someone else cannot propose', async function () { + await expectRevert(this.helper.propose({ from: voter1 }), 'Governor: proposer restricted'); + }); + }); + }); + describe('onlyGovernance updates', function () { it('setVotingDelay is protected', async function () { await expectRevert(this.mock.setVotingDelay('0'), 'Governor: onlyGovernance'); From 1d5bcd04e77041fa7b971bc7386cc161a5ae3bf8 Mon Sep 17 00:00:00 2001 From: Pascal Marco Caversaccio Date: Thu, 8 Jun 2023 04:10:43 +0300 Subject: [PATCH 090/182] `ECDSA`: Use unchecked arithmetic for the `tryRecover` function (#4301) Signed-off-by: Pascal Marco Caversaccio --- .changeset/four-adults-knock.md | 5 +++++ contracts/utils/cryptography/ECDSA.sol | 9 ++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) create mode 100644 .changeset/four-adults-knock.md diff --git a/.changeset/four-adults-knock.md b/.changeset/four-adults-knock.md new file mode 100644 index 000000000..f6f566d7a --- /dev/null +++ b/.changeset/four-adults-knock.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': patch +--- + +`ECDSA`: Use unchecked arithmetic for the `tryRecover` function that receives the `r` and `vs` short-signature fields separately. diff --git a/contracts/utils/cryptography/ECDSA.sol b/contracts/utils/cryptography/ECDSA.sol index 2ddbd9ba0..efff6063c 100644 --- a/contracts/utils/cryptography/ECDSA.sol +++ b/contracts/utils/cryptography/ECDSA.sol @@ -98,9 +98,12 @@ library ECDSA { * _Available since v4.3._ */ function tryRecover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address, RecoverError) { - bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff); - uint8 v = uint8((uint256(vs) >> 255) + 27); - return tryRecover(hash, v, r, s); + unchecked { + bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff); + // We do not check for an overflow here since the shift operation results in 0 or 1. + uint8 v = uint8((uint256(vs) >> 255) + 27); + return tryRecover(hash, v, r, s); + } } /** From cc0426317052b1850b686f2fbbcf481520399735 Mon Sep 17 00:00:00 2001 From: Robb Walters Date: Fri, 9 Jun 2023 09:00:16 -0700 Subject: [PATCH 091/182] Highlight Reentrancy Risk in IERC1155 SafeTransferFrom Function (#4283) Co-authored-by: Francisco --- contracts/token/ERC1155/IERC1155.sol | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/contracts/token/ERC1155/IERC1155.sol b/contracts/token/ERC1155/IERC1155.sol index d7e25a5b1..3d2585d7e 100644 --- a/contracts/token/ERC1155/IERC1155.sol +++ b/contracts/token/ERC1155/IERC1155.sol @@ -86,6 +86,11 @@ interface IERC1155 is IERC165 { /** * @dev Transfers `amount` tokens of token type `id` from `from` to `to`. * + * WARNING: This function can potentially allow a reentrancy attack when transferring tokens + * to an untrusted contract, when invoking {onERC1155Received} on the receiver. + * Ensure to follow the checks-effects-interactions pattern and consider employing + * reentrancy guards when interacting with untrusted contracts. + * * Emits a {TransferSingle} event. * * Requirements: @@ -101,6 +106,12 @@ interface IERC1155 is IERC165 { /** * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {safeTransferFrom}. * + * + * WARNING: This function can potentially allow a reentrancy attack when transferring tokens + * to an untrusted contract, when invoking {onERC1155Received} on the receiver. + * Ensure to follow the checks-effects-interactions pattern and consider employing + * reentrancy guards when interacting with untrusted contracts. + * * Emits a {TransferBatch} event. * * Requirements: From e73f90fa9d42489b5e7df327738dad9cdad31b0b Mon Sep 17 00:00:00 2001 From: Pascal Marco Caversaccio Date: Sun, 11 Jun 2023 21:38:31 +0200 Subject: [PATCH 092/182] Fix `IERC1155.safeBatchTransferFrom` documentation (#4340) --- contracts/token/ERC1155/IERC1155.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/token/ERC1155/IERC1155.sol b/contracts/token/ERC1155/IERC1155.sol index 3d2585d7e..16b2b71a3 100644 --- a/contracts/token/ERC1155/IERC1155.sol +++ b/contracts/token/ERC1155/IERC1155.sol @@ -108,7 +108,7 @@ interface IERC1155 is IERC165 { * * * WARNING: This function can potentially allow a reentrancy attack when transferring tokens - * to an untrusted contract, when invoking {onERC1155Received} on the receiver. + * to an untrusted contract, when invoking {onERC1155BatchReceived} on the receiver. * Ensure to follow the checks-effects-interactions pattern and consider employing * reentrancy guards when interacting with untrusted contracts. * From 08fd777f6d66369f7a6cbd2d64effda937ff9ce9 Mon Sep 17 00:00:00 2001 From: Pascal Marco Caversaccio Date: Mon, 12 Jun 2023 11:00:35 +0200 Subject: [PATCH 093/182] Rename "`ecrecover` opcode" to "`ecrecover` precompile" in `ECDSA` (#4339) --- contracts/utils/cryptography/ECDSA.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/utils/cryptography/ECDSA.sol b/contracts/utils/cryptography/ECDSA.sol index efff6063c..b907abfc0 100644 --- a/contracts/utils/cryptography/ECDSA.sol +++ b/contracts/utils/cryptography/ECDSA.sol @@ -35,7 +35,7 @@ library ECDSA { * @dev Returns the address that signed a hashed message (`hash`) with * `signature` or error string. This address can then be used for verification purposes. * - * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures: + * The `ecrecover` EVM precompile allows for malleable (non-unique) signatures: * this function rejects them by requiring the `s` value to be in the lower * half order, and the `v` value to be either 27 or 28. * @@ -74,7 +74,7 @@ library ECDSA { * @dev Returns the address that signed a hashed message (`hash`) with * `signature`. This address can then be used for verification purposes. * - * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures: + * The `ecrecover` EVM precompile allows for malleable (non-unique) signatures: * this function rejects them by requiring the `s` value to be in the lower * half order, and the `v` value to be either 27 or 28. * From b425a722409fdf4f3e0ec8f42d686f3c0522af19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Mon, 12 Jun 2023 17:41:52 -0600 Subject: [PATCH 094/182] Replace revert strings with custom errors (#4261) Co-authored-by: Hadrien Croubois Co-authored-by: Francisco --- .changeset/lovely-geckos-hide.md | 5 + GUIDELINES.md | 14 + contracts/access/AccessControl.sol | 21 +- .../access/AccessControlDefaultAdminRules.sol | 36 ++- contracts/access/IAccessControl.sol | 16 +- .../IAccessControlDefaultAdminRules.sol | 22 ++ contracts/access/Ownable.sol | 18 +- contracts/access/Ownable2Step.sol | 4 +- contracts/finance/VestingWallet.sol | 9 +- contracts/governance/Governor.sol | 96 ++++-- contracts/governance/IGovernor.sol | 57 ++++ contracts/governance/TimelockController.sol | 76 ++++- .../GovernorCompatibilityBravo.sol | 19 +- .../IGovernorCompatibilityBravo.sol | 5 + .../extensions/GovernorCountingSimple.sol | 6 +- .../extensions/GovernorSettings.sol | 4 +- .../extensions/GovernorTimelockCompound.sol | 20 +- .../extensions/GovernorTimelockControl.sol | 9 +- .../GovernorVotesQuorumFraction.sol | 13 +- .../extensions/IGovernorTimelock.sol | 10 + contracts/governance/utils/IVotes.sol | 5 + contracts/governance/utils/Votes.sol | 30 +- contracts/interfaces/draft-IERC6093.sol | 162 ++++++++++ contracts/metatx/MinimalForwarder.sol | 42 ++- contracts/mocks/AddressFnPointersMock.sol | 50 ++++ .../mocks/token/ERC721ConsecutiveMock.sol | 1 - contracts/proxy/Clones.sol | 13 +- contracts/proxy/ERC1967/ERC1967Upgrade.sol | 49 +++- contracts/proxy/beacon/UpgradeableBeacon.sol | 9 +- .../TransparentUpgradeableProxy.sol | 16 +- contracts/proxy/utils/Initializable.sol | 29 +- contracts/proxy/utils/UUPSUpgradeable.sol | 20 +- contracts/security/Pausable.sol | 20 +- contracts/security/ReentrancyGuard.sol | 9 +- contracts/token/ERC1155/ERC1155.sol | 77 +++-- .../ERC1155/extensions/ERC1155Burnable.sol | 14 +- .../ERC1155/extensions/ERC1155Pausable.sol | 3 +- .../ERC1155/extensions/ERC1155Supply.sol | 5 +- contracts/token/ERC20/ERC20.sol | 51 +++- .../token/ERC20/extensions/ERC20Capped.sol | 24 +- .../token/ERC20/extensions/ERC20FlashMint.sol | 31 +- .../token/ERC20/extensions/ERC20Pausable.sol | 3 +- .../token/ERC20/extensions/ERC20Permit.sol | 18 +- .../token/ERC20/extensions/ERC20Votes.sol | 11 +- .../token/ERC20/extensions/ERC20Wrapper.sol | 13 +- contracts/token/ERC20/extensions/ERC4626.sol | 40 ++- contracts/token/ERC20/utils/SafeERC20.sol | 32 +- contracts/token/ERC721/ERC721.sol | 78 +++-- .../ERC721/extensions/ERC721Burnable.sol | 5 +- .../ERC721/extensions/ERC721Consecutive.sol | 44 ++- .../ERC721/extensions/ERC721Enumerable.sol | 22 +- .../ERC721/extensions/ERC721Pausable.sol | 2 +- .../ERC721/extensions/ERC721URIStorage.sol | 4 +- .../token/ERC721/extensions/ERC721Wrapper.sol | 18 +- contracts/token/common/ERC2981.sol | 40 ++- contracts/utils/Address.sol | 117 ++++++-- contracts/utils/Create2.sol | 27 +- contracts/utils/Nonces.sol | 16 + contracts/utils/StorageSlot.sol | 2 +- contracts/utils/Strings.sol | 14 +- contracts/utils/cryptography/ECDSA.sol | 54 ++-- contracts/utils/cryptography/MerkleProof.sol | 13 +- .../utils/cryptography/SignatureChecker.sol | 2 +- contracts/utils/math/Math.sol | 9 +- contracts/utils/math/SafeCast.sol | 276 ++++++++++++++---- contracts/utils/structs/Checkpoints.sol | 13 +- contracts/utils/structs/DoubleEndedQueue.sol | 24 +- contracts/utils/structs/EnumerableMap.sol | 9 +- scripts/generate/templates/Checkpoints.js | 12 +- scripts/generate/templates/EnumerableMap.js | 9 +- scripts/generate/templates/SafeCast.js | 39 ++- scripts/generate/templates/StorageSlot.js | 2 +- test/access/AccessControl.behavior.js | 126 ++++---- test/access/AccessControl.test.js | 2 +- .../AccessControlDefaultAdminRules.test.js | 7 +- test/access/AccessControlEnumerable.test.js | 4 +- test/access/Ownable.test.js | 19 +- test/access/Ownable2Step.test.js | 21 +- test/finance/VestingWallet.test.js | 8 +- test/governance/Governor.test.js | 160 +++++++--- test/governance/TimelockController.test.js | 157 ++++++---- .../GovernorCompatibilityBravo.test.js | 61 ++-- .../GovernorPreventLateQuorum.test.js | 9 +- .../GovernorTimelockCompound.test.js | 59 ++-- .../GovernorTimelockControl.test.js | 89 ++++-- .../GovernorVotesQuorumFraction.test.js | 30 +- test/governance/utils/Votes.behavior.js | 39 ++- test/governance/utils/Votes.test.js | 9 +- test/helpers/customError.js | 43 ++- test/helpers/enums.js | 1 + test/helpers/governance.js | 39 +++ test/metatx/MinimalForwarder.test.js | 132 ++++----- test/proxy/Clones.test.js | 6 +- test/proxy/beacon/BeaconProxy.test.js | 9 +- test/proxy/beacon/UpgradeableBeacon.test.js | 20 +- test/proxy/transparent/ProxyAdmin.test.js | 19 +- .../TransparentUpgradeableProxy.behaviour.js | 18 +- test/proxy/utils/Initializable.test.js | 25 +- test/proxy/utils/UUPSUpgradeable.test.js | 15 +- test/security/Pausable.test.js | 15 +- test/security/ReentrancyGuard.test.js | 9 +- test/token/ERC1155/ERC1155.behavior.js | 109 +++---- test/token/ERC1155/ERC1155.test.js | 58 ++-- .../extensions/ERC1155Burnable.test.js | 14 +- .../extensions/ERC1155Pausable.test.js | 43 +-- test/token/ERC20/ERC20.behavior.js | 70 +++-- test/token/ERC20/ERC20.test.js | 47 +-- .../extensions/ERC20Burnable.behavior.js | 19 +- .../ERC20/extensions/ERC20Capped.behavior.js | 7 +- .../ERC20/extensions/ERC20Capped.test.js | 5 +- .../ERC20/extensions/ERC20FlashMint.test.js | 20 +- .../ERC20/extensions/ERC20Pausable.test.js | 17 +- ...RC20Permit.test.js => ERC20Permit.test.js} | 35 ++- .../token/ERC20/extensions/ERC20Votes.test.js | 42 ++- .../ERC20/extensions/ERC20Wrapper.test.js | 22 +- test/token/ERC20/extensions/ERC4626.test.js | 12 +- test/token/ERC20/utils/SafeERC20.test.js | 117 +++++--- test/token/ERC721/ERC721.behavior.js | 129 ++++---- test/token/ERC721/ERC721.test.js | 4 +- test/token/ERC721/ERC721Enumerable.test.js | 6 +- .../ERC721/extensions/ERC721Burnable.test.js | 11 +- .../extensions/ERC721Consecutive.test.js | 37 +-- .../ERC721/extensions/ERC721Pausable.test.js | 22 +- .../extensions/ERC721URIStorage.test.js | 18 +- .../ERC721/extensions/ERC721Wrapper.test.js | 30 +- test/token/common/ERC2981.behavior.js | 32 +- test/utils/Address.test.js | 115 +++++--- test/utils/Create2.test.js | 14 +- test/utils/Multicall.test.js | 7 +- test/utils/Nonces.test.js | 58 +++- test/utils/ShortStrings.test.js | 4 +- test/utils/Strings.test.js | 11 +- test/utils/cryptography/ECDSA.test.js | 46 +-- test/utils/cryptography/MerkleProof.test.js | 14 +- test/utils/math/SafeCast.test.js | 43 +-- test/utils/structs/Checkpoints.test.js | 10 +- test/utils/structs/DoubleEndedQueue.test.js | 10 +- test/utils/structs/EnumerableMap.behavior.js | 8 +- 138 files changed, 3219 insertions(+), 1286 deletions(-) create mode 100644 .changeset/lovely-geckos-hide.md create mode 100644 contracts/interfaces/draft-IERC6093.sol create mode 100644 contracts/mocks/AddressFnPointersMock.sol rename test/token/ERC20/extensions/{draft-ERC20Permit.test.js => ERC20Permit.test.js} (73%) diff --git a/.changeset/lovely-geckos-hide.md b/.changeset/lovely-geckos-hide.md new file mode 100644 index 000000000..1fbcb2077 --- /dev/null +++ b/.changeset/lovely-geckos-hide.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': major +--- + +Replace revert strings and require statements with custom errors. diff --git a/GUIDELINES.md b/GUIDELINES.md index 0f4c9829e..1dd606ddd 100644 --- a/GUIDELINES.md +++ b/GUIDELINES.md @@ -115,3 +115,17 @@ In addition to the official Solidity Style Guide we have a number of other conve ``` * Unchecked arithmetic blocks should contain comments explaining why overflow is guaranteed not to happen. If the reason is immediately apparent from the line above the unchecked block, the comment may be omitted. + +* Custom errors should be declared following the [EIP-6093](https://eips.ethereum.org/EIPS/eip-6093) rationale whenever reasonable. Also, consider the following: + + * The domain prefix should be picked in the following order: + 1. Use `ERC` if the error is a violation of an ERC specification. + 2. Use the name of the underlying component where it belongs (eg. `Governor`, `ECDSA`, or `Timelock`). + + * The location of custom errors should be decided in the following order: + 1. Take the errors from their underlying ERCs if they're already defined. + 2. Declare the errors in the underlying interface/library if the error makes sense in its context. + 3. Declare the error in the implementation if the underlying interface/library is not suitable to do so (eg. interface/library already specified in an ERC). + 4. Declare the error in an extension if the error only happens in such extension or child contracts. + + * Custom error names should not be declared twice along the library to avoid duplicated identifier declarations when inheriting from multiple contracts. diff --git a/contracts/access/AccessControl.sol b/contracts/access/AccessControl.sol index df16dbdab..12dc770b3 100644 --- a/contracts/access/AccessControl.sol +++ b/contracts/access/AccessControl.sol @@ -107,16 +107,7 @@ abstract contract AccessControl is Context, IAccessControl, ERC165 { */ function _checkRole(bytes32 role, address account) internal view virtual { if (!hasRole(role, account)) { - revert( - string( - abi.encodePacked( - "AccessControl: account ", - Strings.toHexString(account), - " is missing role ", - Strings.toHexString(uint256(role), 32) - ) - ) - ); + revert AccessControlUnauthorizedAccount(account, role); } } @@ -173,14 +164,16 @@ abstract contract AccessControl is Context, IAccessControl, ERC165 { * * Requirements: * - * - the caller must be `account`. + * - the caller must be `callerConfirmation`. * * May emit a {RoleRevoked} event. */ - function renounceRole(bytes32 role, address account) public virtual { - require(account == _msgSender(), "AccessControl: can only renounce roles for self"); + function renounceRole(bytes32 role, address callerConfirmation) public virtual { + if (callerConfirmation != _msgSender()) { + revert AccessControlBadConfirmation(); + } - _revokeRole(role, account); + _revokeRole(role, callerConfirmation); } /** diff --git a/contracts/access/AccessControlDefaultAdminRules.sol b/contracts/access/AccessControlDefaultAdminRules.sol index 47df078c1..e27eaf3db 100644 --- a/contracts/access/AccessControlDefaultAdminRules.sol +++ b/contracts/access/AccessControlDefaultAdminRules.sol @@ -53,7 +53,9 @@ abstract contract AccessControlDefaultAdminRules is IAccessControlDefaultAdminRu * @dev Sets the initial values for {defaultAdminDelay} and {defaultAdmin} address. */ constructor(uint48 initialDelay, address initialDefaultAdmin) { - require(initialDefaultAdmin != address(0), "AccessControl: 0 default admin"); + if (initialDefaultAdmin == address(0)) { + revert AccessControlInvalidDefaultAdmin(address(0)); + } _currentDelay = initialDelay; _grantRole(DEFAULT_ADMIN_ROLE, initialDefaultAdmin); } @@ -80,7 +82,9 @@ abstract contract AccessControlDefaultAdminRules is IAccessControlDefaultAdminRu * @dev See {AccessControl-grantRole}. Reverts for `DEFAULT_ADMIN_ROLE`. */ function grantRole(bytes32 role, address account) public virtual override(AccessControl, IAccessControl) { - require(role != DEFAULT_ADMIN_ROLE, "AccessControl: can't directly grant default admin role"); + if (role == DEFAULT_ADMIN_ROLE) { + revert AccessControlEnforcedDefaultAdminRules(); + } super.grantRole(role, account); } @@ -88,7 +92,9 @@ abstract contract AccessControlDefaultAdminRules is IAccessControlDefaultAdminRu * @dev See {AccessControl-revokeRole}. Reverts for `DEFAULT_ADMIN_ROLE`. */ function revokeRole(bytes32 role, address account) public virtual override(AccessControl, IAccessControl) { - require(role != DEFAULT_ADMIN_ROLE, "AccessControl: can't directly revoke default admin role"); + if (role == DEFAULT_ADMIN_ROLE) { + revert AccessControlEnforcedDefaultAdminRules(); + } super.revokeRole(role, account); } @@ -108,10 +114,9 @@ abstract contract AccessControlDefaultAdminRules is IAccessControlDefaultAdminRu function renounceRole(bytes32 role, address account) public virtual override(AccessControl, IAccessControl) { if (role == DEFAULT_ADMIN_ROLE && account == defaultAdmin()) { (address newDefaultAdmin, uint48 schedule) = pendingDefaultAdmin(); - require( - newDefaultAdmin == address(0) && _isScheduleSet(schedule) && _hasSchedulePassed(schedule), - "AccessControl: only can renounce in two delayed steps" - ); + if (newDefaultAdmin != address(0) || !_isScheduleSet(schedule) || !_hasSchedulePassed(schedule)) { + revert AccessControlEnforcedDefaultAdminDelay(schedule); + } delete _pendingDefaultAdminSchedule; } super.renounceRole(role, account); @@ -128,7 +133,9 @@ abstract contract AccessControlDefaultAdminRules is IAccessControlDefaultAdminRu */ function _grantRole(bytes32 role, address account) internal virtual override { if (role == DEFAULT_ADMIN_ROLE) { - require(defaultAdmin() == address(0), "AccessControl: default admin already granted"); + if (defaultAdmin() != address(0)) { + revert AccessControlEnforcedDefaultAdminRules(); + } _currentDefaultAdmin = account; } super._grantRole(role, account); @@ -148,7 +155,9 @@ abstract contract AccessControlDefaultAdminRules is IAccessControlDefaultAdminRu * @dev See {AccessControl-_setRoleAdmin}. Reverts for `DEFAULT_ADMIN_ROLE`. */ function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual override { - require(role != DEFAULT_ADMIN_ROLE, "AccessControl: can't violate default admin rules"); + if (role == DEFAULT_ADMIN_ROLE) { + revert AccessControlEnforcedDefaultAdminRules(); + } super._setRoleAdmin(role, adminRole); } @@ -236,7 +245,10 @@ abstract contract AccessControlDefaultAdminRules is IAccessControlDefaultAdminRu */ function acceptDefaultAdminTransfer() public virtual { (address newDefaultAdmin, ) = pendingDefaultAdmin(); - require(_msgSender() == newDefaultAdmin, "AccessControl: pending admin must accept"); + if (_msgSender() != newDefaultAdmin) { + // Enforce newDefaultAdmin explicit acceptance. + revert AccessControlInvalidDefaultAdmin(_msgSender()); + } _acceptDefaultAdminTransfer(); } @@ -247,7 +259,9 @@ abstract contract AccessControlDefaultAdminRules is IAccessControlDefaultAdminRu */ function _acceptDefaultAdminTransfer() internal virtual { (address newAdmin, uint48 schedule) = pendingDefaultAdmin(); - require(_isScheduleSet(schedule) && _hasSchedulePassed(schedule), "AccessControl: transfer delay not passed"); + if (!_isScheduleSet(schedule) || !_hasSchedulePassed(schedule)) { + revert AccessControlEnforcedDefaultAdminDelay(schedule); + } _revokeRole(DEFAULT_ADMIN_ROLE, defaultAdmin()); _grantRole(DEFAULT_ADMIN_ROLE, newAdmin); delete _pendingDefaultAdmin; diff --git a/contracts/access/IAccessControl.sol b/contracts/access/IAccessControl.sol index 34708b78d..9abc2b735 100644 --- a/contracts/access/IAccessControl.sol +++ b/contracts/access/IAccessControl.sol @@ -7,6 +7,18 @@ pragma solidity ^0.8.19; * @dev External interface of AccessControl declared to support ERC165 detection. */ interface IAccessControl { + /** + * @dev The `account` is missing a role. + */ + error AccessControlUnauthorizedAccount(address account, bytes32 neededRole); + + /** + * @dev The caller of a function is not the expected one. + * + * NOTE: Don't confuse with {AccessControlUnauthorizedAccount}. + */ + error AccessControlBadConfirmation(); + /** * @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole` * @@ -82,7 +94,7 @@ interface IAccessControl { * * Requirements: * - * - the caller must be `account`. + * - the caller must be `callerConfirmation`. */ - function renounceRole(bytes32 role, address account) external; + function renounceRole(bytes32 role, address callerConfirmation) external; } diff --git a/contracts/access/IAccessControlDefaultAdminRules.sol b/contracts/access/IAccessControlDefaultAdminRules.sol index 94cbe871d..fbecfe120 100644 --- a/contracts/access/IAccessControlDefaultAdminRules.sol +++ b/contracts/access/IAccessControlDefaultAdminRules.sol @@ -11,6 +11,28 @@ import "./IAccessControl.sol"; * _Available since v4.9._ */ interface IAccessControlDefaultAdminRules is IAccessControl { + /** + * @dev The new default admin is not a valid default admin. + */ + error AccessControlInvalidDefaultAdmin(address defaultAdmin); + + /** + * @dev At least one of the following rules was violated: + * + * - The `DEFAULT_ADMIN_ROLE` must only be managed by itself. + * - The `DEFAULT_ADMIN_ROLE` must only be held by one account at the time. + * - Any `DEFAULT_ADMIN_ROLE` transfer must be in two delayed steps. + */ + error AccessControlEnforcedDefaultAdminRules(); + + /** + * @dev The delay for transferring the default admin delay is enforced and + * the operation must wait until `schedule`. + * + * NOTE: `schedule` can be 0 indicating there's no transfer scheduled. + */ + error AccessControlEnforcedDefaultAdminDelay(uint48 schedule); + /** * @dev Emitted when a {defaultAdmin} transfer is started, setting `newAdmin` as the next * address to become the {defaultAdmin} by calling {acceptDefaultAdminTransfer} only after `acceptSchedule` diff --git a/contracts/access/Ownable.sol b/contracts/access/Ownable.sol index 6c901b7a1..f4394378b 100644 --- a/contracts/access/Ownable.sol +++ b/contracts/access/Ownable.sol @@ -20,6 +20,16 @@ import "../utils/Context.sol"; abstract contract Ownable is Context { address private _owner; + /** + * @dev The caller account is not authorized to perform an operation. + */ + error OwnableUnauthorizedAccount(address account); + + /** + * @dev The owner is not a valid owner account. (eg. `address(0)`) + */ + error OwnableInvalidOwner(address owner); + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); /** @@ -48,7 +58,9 @@ abstract contract Ownable is Context { * @dev Throws if the sender is not the owner. */ function _checkOwner() internal view virtual { - require(owner() == _msgSender(), "Ownable: caller is not the owner"); + if (owner() != _msgSender()) { + revert OwnableUnauthorizedAccount(_msgSender()); + } } /** @@ -67,7 +79,9 @@ abstract contract Ownable is Context { * Can only be called by the current owner. */ function transferOwnership(address newOwner) public virtual onlyOwner { - require(newOwner != address(0), "Ownable: new owner is the zero address"); + if (newOwner == address(0)) { + revert OwnableInvalidOwner(address(0)); + } _transferOwnership(newOwner); } diff --git a/contracts/access/Ownable2Step.sol b/contracts/access/Ownable2Step.sol index 59ffa3e0e..61005b7e3 100644 --- a/contracts/access/Ownable2Step.sol +++ b/contracts/access/Ownable2Step.sol @@ -51,7 +51,9 @@ abstract contract Ownable2Step is Ownable { */ function acceptOwnership() public virtual { address sender = _msgSender(); - require(pendingOwner() == sender, "Ownable2Step: caller is not the new owner"); + if (pendingOwner() != sender) { + revert OwnableUnauthorizedAccount(sender); + } _transferOwnership(sender); } } diff --git a/contracts/finance/VestingWallet.sol b/contracts/finance/VestingWallet.sol index 5b7e1b150..ebdf0a330 100644 --- a/contracts/finance/VestingWallet.sol +++ b/contracts/finance/VestingWallet.sol @@ -23,6 +23,11 @@ contract VestingWallet is Context { event EtherReleased(uint256 amount); event ERC20Released(address indexed token, uint256 amount); + /** + * @dev The `beneficiary` is not a valid account. + */ + error VestingWalletInvalidBeneficiary(address beneficiary); + uint256 private _released; mapping(address => uint256) private _erc20Released; address private immutable _beneficiary; @@ -33,7 +38,9 @@ contract VestingWallet is Context { * @dev Set the beneficiary, start timestamp and vesting duration of the vesting wallet. */ constructor(address beneficiaryAddress, uint64 startTimestamp, uint64 durationSeconds) payable { - require(beneficiaryAddress != address(0), "VestingWallet: beneficiary is zero address"); + if (beneficiaryAddress == address(0)) { + revert VestingWalletInvalidBeneficiary(address(0)); + } _beneficiary = beneficiaryAddress; _start = startTimestamp; _duration = durationSeconds; diff --git a/contracts/governance/Governor.sol b/contracts/governance/Governor.sol index 2e2289c6a..b42fe1034 100644 --- a/contracts/governance/Governor.sol +++ b/contracts/governance/Governor.sol @@ -47,6 +47,7 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor, IERC721Receive } // solhint-enable var-name-mixedcase + bytes32 private constant _ALL_PROPOSAL_STATES_BITMAP = bytes32((2 ** (uint8(type(ProposalState).max) + 1)) - 1); string private _name; /// @custom:oz-retyped-from mapping(uint256 => Governor.ProposalCore) @@ -69,7 +70,9 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor, IERC721Receive * governance protocol (since v4.6). */ modifier onlyGovernance() { - require(_msgSender() == _executor(), "Governor: onlyGovernance"); + if (_msgSender() != _executor()) { + revert GovernorOnlyExecutor(_msgSender()); + } if (_executor() != address(this)) { bytes32 msgDataHash = keccak256(_msgData()); // loop until popping the expected operation - throw if deque is empty (operation not authorized) @@ -89,7 +92,9 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor, IERC721Receive * @dev Function to receive ETH that will be handled by the governor (disabled if executor is a third party contract) */ receive() external payable virtual { - require(_executor() == address(this), "Governor: must send to executor"); + if (_executor() != address(this)) { + revert GovernorDisabledDeposit(); + } } /** @@ -174,7 +179,7 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor, IERC721Receive uint256 snapshot = proposalSnapshot(proposalId); if (snapshot == 0) { - revert("Governor: unknown proposal id"); + revert GovernorNonexistentProposal(proposalId); } uint256 currentTimepoint = clock(); @@ -275,17 +280,24 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor, IERC721Receive require(_isValidDescriptionForProposer(proposer, description), "Governor: proposer restricted"); uint256 currentTimepoint = clock(); - require( - getVotes(proposer, currentTimepoint - 1) >= proposalThreshold(), - "Governor: proposer votes below proposal threshold" - ); + + // Avoid stack too deep + { + uint256 proposerVotes = getVotes(proposer, currentTimepoint - 1); + uint256 votesThreshold = proposalThreshold(); + if (proposerVotes < votesThreshold) { + revert GovernorInsufficientProposerVotes(proposer, proposerVotes, votesThreshold); + } + } uint256 proposalId = hashProposal(targets, values, calldatas, keccak256(bytes(description))); - require(targets.length == values.length, "Governor: invalid proposal length"); - require(targets.length == calldatas.length, "Governor: invalid proposal length"); - require(targets.length > 0, "Governor: empty proposal"); - require(_proposals[proposalId].voteStart == 0, "Governor: proposal already exists"); + if (targets.length != values.length || targets.length != calldatas.length || targets.length == 0) { + revert GovernorInvalidProposalLength(targets.length, calldatas.length, values.length); + } + if (_proposals[proposalId].voteStart != 0) { + revert GovernorUnexpectedProposalState(proposalId, state(proposalId), bytes32(0)); + } uint256 snapshot = currentTimepoint + votingDelay(); uint256 deadline = snapshot + votingPeriod(); @@ -327,10 +339,13 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor, IERC721Receive uint256 proposalId = hashProposal(targets, values, calldatas, descriptionHash); ProposalState currentState = state(proposalId); - require( - currentState == ProposalState.Succeeded || currentState == ProposalState.Queued, - "Governor: proposal not successful" - ); + if (currentState != ProposalState.Succeeded && currentState != ProposalState.Queued) { + revert GovernorUnexpectedProposalState( + proposalId, + currentState, + _encodeStateBitmap(ProposalState.Succeeded) | _encodeStateBitmap(ProposalState.Queued) + ); + } _proposals[proposalId].executed = true; emit ProposalExecuted(proposalId); @@ -352,8 +367,13 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor, IERC721Receive bytes32 descriptionHash ) public virtual override returns (uint256) { uint256 proposalId = hashProposal(targets, values, calldatas, descriptionHash); - require(state(proposalId) == ProposalState.Pending, "Governor: too late to cancel"); - require(_msgSender() == _proposals[proposalId].proposer, "Governor: only proposer can cancel"); + ProposalState currentState = state(proposalId); + if (currentState != ProposalState.Pending) { + revert GovernorUnexpectedProposalState(proposalId, currentState, _encodeStateBitmap(ProposalState.Pending)); + } + if (_msgSender() != proposalProposer(proposalId)) { + revert GovernorOnlyProposer(_msgSender()); + } return _cancel(targets, values, calldatas, descriptionHash); } @@ -367,10 +387,9 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor, IERC721Receive bytes[] memory calldatas, bytes32 /*descriptionHash*/ ) internal virtual { - string memory errorMessage = "Governor: call reverted without message"; for (uint256 i = 0; i < targets.length; ++i) { (bool success, bytes memory returndata) = targets[i].call{value: values[i]}(calldatas[i]); - Address.verifyCallResult(success, returndata, errorMessage); + Address.verifyCallResult(success, returndata); } } @@ -426,12 +445,16 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor, IERC721Receive ProposalState currentState = state(proposalId); - require( - currentState != ProposalState.Canceled && - currentState != ProposalState.Expired && - currentState != ProposalState.Executed, - "Governor: proposal not active" - ); + bytes32 forbiddenStates = _encodeStateBitmap(ProposalState.Canceled) | + _encodeStateBitmap(ProposalState.Expired) | + _encodeStateBitmap(ProposalState.Executed); + if (forbiddenStates & _encodeStateBitmap(currentState) != 0) { + revert GovernorUnexpectedProposalState( + proposalId, + currentState, + _ALL_PROPOSAL_STATES_BITMAP ^ forbiddenStates + ); + } _proposals[proposalId].canceled = true; emit ProposalCanceled(proposalId); @@ -570,7 +593,10 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor, IERC721Receive bytes memory params ) internal virtual returns (uint256) { ProposalCore storage proposal = _proposals[proposalId]; - require(state(proposalId) == ProposalState.Active, "Governor: vote not currently active"); + ProposalState currentState = state(proposalId); + if (currentState != ProposalState.Active) { + revert GovernorUnexpectedProposalState(proposalId, currentState, _encodeStateBitmap(ProposalState.Active)); + } uint256 weight = _getVotes(account, proposal.voteStart, params); _countVote(proposalId, account, support, weight, params); @@ -592,7 +618,7 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor, IERC721Receive */ function relay(address target, uint256 value, bytes calldata data) external payable virtual onlyGovernance { (bool success, bytes memory returndata) = target.call{value: value}(data); - Address.verifyCallResult(success, returndata, "Governor: relay reverted without message"); + Address.verifyCallResult(success, returndata); } /** @@ -631,6 +657,22 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor, IERC721Receive } /** + * @dev Encodes a `ProposalState` into a `bytes32` representation where each bit enabled corresponds to + * the underlying position in the `ProposalState` enum. For example: + * + * 0x000...10000 + * ^^^^^^------ ... + * ^----- Succeeded + * ^---- Defeated + * ^--- Canceled + * ^-- Active + * ^- Pending + */ + function _encodeStateBitmap(ProposalState proposalState) internal pure returns (bytes32) { + return bytes32(1 << uint8(proposalState)); + } + + /* * @dev Check if the proposer is authorized to submit a proposal with the given description. * * If the proposal description ends with `#proposer=0x???`, where `0x???` is an address written as a hex string diff --git a/contracts/governance/IGovernor.sol b/contracts/governance/IGovernor.sol index 992b5ca10..5b51f088a 100644 --- a/contracts/governance/IGovernor.sol +++ b/contracts/governance/IGovernor.sol @@ -23,6 +23,63 @@ abstract contract IGovernor is IERC165, IERC6372 { Executed } + /** + * @dev Empty proposal or a mismatch between the parameters length for a proposal call. + */ + error GovernorInvalidProposalLength(uint256 targets, uint256 calldatas, uint256 values); + + /** + * @dev The vote was already cast. + */ + error GovernorAlreadyCastVote(address voter); + + /** + * @dev Token deposits are disabled in this contract. + */ + error GovernorDisabledDeposit(); + + /** + * @dev The `account` is not a proposer. + */ + error GovernorOnlyProposer(address account); + + /** + * @dev The `account` is not the governance executor. + */ + error GovernorOnlyExecutor(address account); + + /** + * @dev The `proposalId` doesn't exist. + */ + error GovernorNonexistentProposal(uint256 proposalId); + + /** + * @dev The current state of a proposal is not the required for performing an operation. + * The `expectedStates` is a bitmap with the bits enabled for each ProposalState enum position + * counting from right to left. + * + * NOTE: If `expectedState` is `bytes32(0)`, the proposal is expected to not be in any state (i.e. not exist). + * This is the case when a proposal that is expected to be unset is already initiated (the proposal is duplicated). + * + * See {Governor-_encodeStateBitmap}. + */ + error GovernorUnexpectedProposalState(uint256 proposalId, ProposalState current, bytes32 expectedStates); + + /** + * @dev The voting period set is not a valid period. + */ + error GovernorInvalidVotingPeriod(uint256 votingPeriod); + + /** + * @dev The `proposer` does not have the required votes to operate on a proposal. + */ + error GovernorInsufficientProposerVotes(address proposer, uint256 votes, uint256 threshold); + + /** + * @dev The vote type used is not valid for the corresponding counting module. + */ + error GovernorInvalidVoteType(); + /** * @dev Emitted when a proposal is created. */ diff --git a/contracts/governance/TimelockController.sol b/contracts/governance/TimelockController.sol index 9930d6a49..4772a252f 100644 --- a/contracts/governance/TimelockController.sol +++ b/contracts/governance/TimelockController.sol @@ -6,6 +6,7 @@ pragma solidity ^0.8.19; import "../access/AccessControl.sol"; import "../token/ERC721/IERC721Receiver.sol"; import "../token/ERC1155/IERC1155Receiver.sol"; +import "../utils/Address.sol"; /** * @dev Contract module which acts as a timelocked controller. When set as the @@ -31,6 +32,38 @@ contract TimelockController is AccessControl, IERC721Receiver, IERC1155Receiver mapping(bytes32 => uint256) private _timestamps; uint256 private _minDelay; + enum OperationState { + Unset, + Pending, + Ready, + Done + } + + /** + * @dev Mismatch between the parameters length for an operation call. + */ + error TimelockInvalidOperationLength(uint256 targets, uint256 payloads, uint256 values); + + /** + * @dev The schedule operation doesn't meet the minimum delay. + */ + error TimelockInsufficientDelay(uint256 delay, uint256 minDelay); + + /** + * @dev The current state of an operation is not as required. + */ + error TimelockUnexpectedOperationState(bytes32 operationId, OperationState expected); + + /** + * @dev The predecessor to an operation not yet done. + */ + error TimelockUnexecutedPredecessor(bytes32 predecessorId); + + /** + * @dev The caller account is not authorized. + */ + error TimelockUnauthorizedCaller(address caller); + /** * @dev Emitted when a call is scheduled as part of operation `id`. */ @@ -243,8 +276,9 @@ contract TimelockController is AccessControl, IERC721Receiver, IERC1155Receiver bytes32 salt, uint256 delay ) public virtual onlyRole(PROPOSER_ROLE) { - require(targets.length == values.length, "TimelockController: length mismatch"); - require(targets.length == payloads.length, "TimelockController: length mismatch"); + if (targets.length != values.length || targets.length != payloads.length) { + revert TimelockInvalidOperationLength(targets.length, payloads.length, values.length); + } bytes32 id = hashOperationBatch(targets, values, payloads, predecessor, salt); _schedule(id, delay); @@ -260,8 +294,13 @@ contract TimelockController is AccessControl, IERC721Receiver, IERC1155Receiver * @dev Schedule an operation that is to become valid after a given delay. */ function _schedule(bytes32 id, uint256 delay) private { - require(!isOperation(id), "TimelockController: operation already scheduled"); - require(delay >= getMinDelay(), "TimelockController: insufficient delay"); + if (isOperation(id)) { + revert TimelockUnexpectedOperationState(id, OperationState.Unset); + } + uint256 minDelay = getMinDelay(); + if (delay < minDelay) { + revert TimelockInsufficientDelay(delay, minDelay); + } _timestamps[id] = block.timestamp + delay; } @@ -273,7 +312,9 @@ contract TimelockController is AccessControl, IERC721Receiver, IERC1155Receiver * - the caller must have the 'canceller' role. */ function cancel(bytes32 id) public virtual onlyRole(CANCELLER_ROLE) { - require(isOperationPending(id), "TimelockController: operation cannot be cancelled"); + if (!isOperationPending(id)) { + revert TimelockUnexpectedOperationState(id, OperationState.Pending); + } delete _timestamps[id]; emit Cancelled(id); @@ -325,8 +366,9 @@ contract TimelockController is AccessControl, IERC721Receiver, IERC1155Receiver bytes32 predecessor, bytes32 salt ) public payable virtual onlyRoleOrOpenRole(EXECUTOR_ROLE) { - require(targets.length == values.length, "TimelockController: length mismatch"); - require(targets.length == payloads.length, "TimelockController: length mismatch"); + if (targets.length != values.length || targets.length != payloads.length) { + revert TimelockInvalidOperationLength(targets.length, payloads.length, values.length); + } bytes32 id = hashOperationBatch(targets, values, payloads, predecessor, salt); @@ -345,23 +387,29 @@ contract TimelockController is AccessControl, IERC721Receiver, IERC1155Receiver * @dev Execute an operation's call. */ function _execute(address target, uint256 value, bytes calldata data) internal virtual { - (bool success, ) = target.call{value: value}(data); - require(success, "TimelockController: underlying transaction reverted"); + (bool success, bytes memory returndata) = target.call{value: value}(data); + Address.verifyCallResult(success, returndata); } /** * @dev Checks before execution of an operation's calls. */ function _beforeCall(bytes32 id, bytes32 predecessor) private view { - require(isOperationReady(id), "TimelockController: operation is not ready"); - require(predecessor == bytes32(0) || isOperationDone(predecessor), "TimelockController: missing dependency"); + if (!isOperationReady(id)) { + revert TimelockUnexpectedOperationState(id, OperationState.Ready); + } + if (predecessor != bytes32(0) && !isOperationDone(predecessor)) { + revert TimelockUnexecutedPredecessor(predecessor); + } } /** * @dev Checks after execution of an operation's calls. */ function _afterCall(bytes32 id) private { - require(isOperationReady(id), "TimelockController: operation is not ready"); + if (!isOperationReady(id)) { + revert TimelockUnexpectedOperationState(id, OperationState.Ready); + } _timestamps[id] = _DONE_TIMESTAMP; } @@ -376,7 +424,9 @@ contract TimelockController is AccessControl, IERC721Receiver, IERC1155Receiver * an operation where the timelock is the target and the data is the ABI-encoded call to this function. */ function updateDelay(uint256 newDelay) external virtual { - require(msg.sender == address(this), "TimelockController: caller must be timelock"); + if (msg.sender != address(this)) { + revert TimelockUnauthorizedCaller(msg.sender); + } emit MinDelayChange(_minDelay, newDelay); _minDelay = newDelay; } diff --git a/contracts/governance/compatibility/GovernorCompatibilityBravo.sol b/contracts/governance/compatibility/GovernorCompatibilityBravo.sol index 425ecad09..670be9591 100644 --- a/contracts/governance/compatibility/GovernorCompatibilityBravo.sol +++ b/contracts/governance/compatibility/GovernorCompatibilityBravo.sol @@ -69,7 +69,9 @@ abstract contract GovernorCompatibilityBravo is IGovernorTimelock, IGovernorComp bytes[] memory calldatas, string memory description ) public virtual override returns (uint256) { - require(signatures.length == calldatas.length, "GovernorBravo: invalid signatures length"); + if (signatures.length != calldatas.length) { + revert GovernorInvalidSignaturesLength(signatures.length, calldatas.length); + } // Stores the full proposal and fallback to the public (possibly overridden) propose. The fallback is done // after the full proposal is stored, so the store operation included in the fallback will be skipped. Here we // call `propose` and not `super.propose` to make sure if a child contract override `propose`, whatever code @@ -133,10 +135,11 @@ abstract contract GovernorCompatibilityBravo is IGovernorTimelock, IGovernorComp uint256 proposalId = hashProposal(targets, values, calldatas, descriptionHash); address proposer = proposalProposer(proposalId); - require( - _msgSender() == proposer || getVotes(proposer, clock() - 1) < proposalThreshold(), - "GovernorBravo: proposer above threshold" - ); + uint256 proposerVotes = getVotes(proposer, clock() - 1); + uint256 votesThreshold = proposalThreshold(); + if (_msgSender() != proposer && proposerVotes >= votesThreshold) { + revert GovernorInsufficientProposerVotes(proposer, proposerVotes, votesThreshold); + } return _cancel(targets, values, calldatas, descriptionHash); } @@ -312,7 +315,9 @@ abstract contract GovernorCompatibilityBravo is IGovernorTimelock, IGovernorComp ProposalDetails storage details = _proposalDetails[proposalId]; Receipt storage receipt = details.receipts[account]; - require(!receipt.hasVoted, "GovernorCompatibilityBravo: vote already cast"); + if (receipt.hasVoted) { + revert GovernorAlreadyCastVote(account); + } receipt.hasVoted = true; receipt.support = support; receipt.votes = SafeCast.toUint96(weight); @@ -324,7 +329,7 @@ abstract contract GovernorCompatibilityBravo is IGovernorTimelock, IGovernorComp } else if (support == uint8(VoteType.Abstain)) { details.abstainVotes += weight; } else { - revert("GovernorCompatibilityBravo: invalid vote type"); + revert GovernorInvalidVoteType(); } } } diff --git a/contracts/governance/compatibility/IGovernorCompatibilityBravo.sol b/contracts/governance/compatibility/IGovernorCompatibilityBravo.sol index d69bbf619..197936ab9 100644 --- a/contracts/governance/compatibility/IGovernorCompatibilityBravo.sol +++ b/contracts/governance/compatibility/IGovernorCompatibilityBravo.sol @@ -11,6 +11,11 @@ import "../IGovernor.sol"; * _Available since v4.3._ */ abstract contract IGovernorCompatibilityBravo is IGovernor { + /** + * @dev Mismatch between the parameters length for a proposal call. + */ + error GovernorInvalidSignaturesLength(uint256 signatures, uint256 calldatas); + /** * @dev Proposal structure from Compound Governor Bravo. Not actually used by the compatibility layer, as * {{proposal}} returns a very different structure. diff --git a/contracts/governance/extensions/GovernorCountingSimple.sol b/contracts/governance/extensions/GovernorCountingSimple.sol index d5c99e593..315f4ad45 100644 --- a/contracts/governance/extensions/GovernorCountingSimple.sol +++ b/contracts/governance/extensions/GovernorCountingSimple.sol @@ -84,7 +84,9 @@ abstract contract GovernorCountingSimple is Governor { ) internal virtual override { ProposalVote storage proposalVote = _proposalVotes[proposalId]; - require(!proposalVote.hasVoted[account], "GovernorVotingSimple: vote already cast"); + if (proposalVote.hasVoted[account]) { + revert GovernorAlreadyCastVote(account); + } proposalVote.hasVoted[account] = true; if (support == uint8(VoteType.Against)) { @@ -94,7 +96,7 @@ abstract contract GovernorCountingSimple is Governor { } else if (support == uint8(VoteType.Abstain)) { proposalVote.abstainVotes += weight; } else { - revert("GovernorVotingSimple: invalid value for enum VoteType"); + revert GovernorInvalidVoteType(); } } } diff --git a/contracts/governance/extensions/GovernorSettings.sol b/contracts/governance/extensions/GovernorSettings.sol index 570c88c54..64e081cc5 100644 --- a/contracts/governance/extensions/GovernorSettings.sol +++ b/contracts/governance/extensions/GovernorSettings.sol @@ -93,7 +93,9 @@ abstract contract GovernorSettings is Governor { */ function _setVotingPeriod(uint256 newVotingPeriod) internal virtual { // voting period must be at least one block long - require(newVotingPeriod > 0, "GovernorSettings: voting period too low"); + if (newVotingPeriod == 0) { + revert GovernorInvalidVotingPeriod(0); + } emit VotingPeriodSet(_votingPeriod, newVotingPeriod); _votingPeriod = newVotingPeriod; } diff --git a/contracts/governance/extensions/GovernorTimelockCompound.sol b/contracts/governance/extensions/GovernorTimelockCompound.sol index 1efd3efff..21439b4db 100644 --- a/contracts/governance/extensions/GovernorTimelockCompound.sol +++ b/contracts/governance/extensions/GovernorTimelockCompound.sol @@ -90,16 +90,22 @@ abstract contract GovernorTimelockCompound is IGovernorTimelock, Governor { ) public virtual override returns (uint256) { uint256 proposalId = hashProposal(targets, values, calldatas, descriptionHash); - require(state(proposalId) == ProposalState.Succeeded, "Governor: proposal not successful"); + ProposalState currentState = state(proposalId); + if (currentState != ProposalState.Succeeded) { + revert GovernorUnexpectedProposalState( + proposalId, + currentState, + _encodeStateBitmap(ProposalState.Succeeded) + ); + } uint256 eta = block.timestamp + _timelock.delay(); _proposalTimelocks[proposalId] = SafeCast.toUint64(eta); for (uint256 i = 0; i < targets.length; ++i) { - require( - !_timelock.queuedTransactions(keccak256(abi.encode(targets[i], values[i], "", calldatas[i], eta))), - "GovernorTimelockCompound: identical proposal action already queued" - ); + if (_timelock.queuedTransactions(keccak256(abi.encode(targets[i], values[i], "", calldatas[i], eta)))) { + revert GovernorAlreadyQueuedProposal(proposalId); + } _timelock.queueTransaction(targets[i], values[i], "", calldatas[i], eta); } @@ -119,7 +125,9 @@ abstract contract GovernorTimelockCompound is IGovernorTimelock, Governor { bytes32 /*descriptionHash*/ ) internal virtual override { uint256 eta = proposalEta(proposalId); - require(eta > 0, "GovernorTimelockCompound: proposal not yet queued"); + if (eta == 0) { + revert GovernorNotQueuedProposal(proposalId); + } Address.sendValue(payable(_timelock), msg.value); for (uint256 i = 0; i < targets.length; ++i) { _timelock.executeTransaction(targets[i], values[i], "", calldatas[i], eta); diff --git a/contracts/governance/extensions/GovernorTimelockControl.sol b/contracts/governance/extensions/GovernorTimelockControl.sol index 3fbce763a..888406ba7 100644 --- a/contracts/governance/extensions/GovernorTimelockControl.sol +++ b/contracts/governance/extensions/GovernorTimelockControl.sol @@ -95,7 +95,14 @@ abstract contract GovernorTimelockControl is IGovernorTimelock, Governor { ) public virtual override returns (uint256) { uint256 proposalId = hashProposal(targets, values, calldatas, descriptionHash); - require(state(proposalId) == ProposalState.Succeeded, "Governor: proposal not successful"); + ProposalState currentState = state(proposalId); + if (currentState != ProposalState.Succeeded) { + revert GovernorUnexpectedProposalState( + proposalId, + currentState, + _encodeStateBitmap(ProposalState.Succeeded) + ); + } uint256 delay = _timelock.getMinDelay(); _timelockIds[proposalId] = _timelock.hashOperationBatch(targets, values, calldatas, 0, descriptionHash); diff --git a/contracts/governance/extensions/GovernorVotesQuorumFraction.sol b/contracts/governance/extensions/GovernorVotesQuorumFraction.sol index 6c10240ce..0094fecd6 100644 --- a/contracts/governance/extensions/GovernorVotesQuorumFraction.sol +++ b/contracts/governance/extensions/GovernorVotesQuorumFraction.sol @@ -21,6 +21,11 @@ abstract contract GovernorVotesQuorumFraction is GovernorVotes { event QuorumNumeratorUpdated(uint256 oldQuorumNumerator, uint256 newQuorumNumerator); + /** + * @dev The quorum set is not a valid fraction. + */ + error GovernorInvalidQuorumFraction(uint256 quorumNumerator, uint256 quorumDenominator); + /** * @dev Initialize quorum as a fraction of the token's total supply. * @@ -94,10 +99,10 @@ abstract contract GovernorVotesQuorumFraction is GovernorVotes { * - New numerator must be smaller or equal to the denominator. */ function _updateQuorumNumerator(uint256 newQuorumNumerator) internal virtual { - require( - newQuorumNumerator <= quorumDenominator(), - "GovernorVotesQuorumFraction: quorumNumerator over quorumDenominator" - ); + uint256 denominator = quorumDenominator(); + if (newQuorumNumerator > denominator) { + revert GovernorInvalidQuorumFraction(newQuorumNumerator, denominator); + } uint256 oldQuorumNumerator = quorumNumerator(); _quorumNumeratorHistory.push(SafeCast.toUint32(clock()), SafeCast.toUint224(newQuorumNumerator)); diff --git a/contracts/governance/extensions/IGovernorTimelock.sol b/contracts/governance/extensions/IGovernorTimelock.sol index 570092bc5..c51429481 100644 --- a/contracts/governance/extensions/IGovernorTimelock.sol +++ b/contracts/governance/extensions/IGovernorTimelock.sol @@ -11,6 +11,16 @@ import "../IGovernor.sol"; * _Available since v4.3._ */ abstract contract IGovernorTimelock is IGovernor { + /** + * @dev The proposal hasn't been queued yet. + */ + error GovernorNotQueuedProposal(uint256 proposalId); + + /** + * @dev The proposal has already been queued. + */ + error GovernorAlreadyQueuedProposal(uint256 proposalId); + event ProposalQueued(uint256 proposalId, uint256 eta); function timelock() public view virtual returns (address); diff --git a/contracts/governance/utils/IVotes.sol b/contracts/governance/utils/IVotes.sol index a1e4fe63a..a8a20856f 100644 --- a/contracts/governance/utils/IVotes.sol +++ b/contracts/governance/utils/IVotes.sol @@ -8,6 +8,11 @@ pragma solidity ^0.8.19; * _Available since v4.5._ */ interface IVotes { + /** + * @dev The signature used has expired. + */ + error VotesExpiredSignature(uint256 expiry); + /** * @dev Emitted when an account changes their delegate. */ diff --git a/contracts/governance/utils/Votes.sol b/contracts/governance/utils/Votes.sol index 5fc15da29..09eb4e22c 100644 --- a/contracts/governance/utils/Votes.sol +++ b/contracts/governance/utils/Votes.sol @@ -42,6 +42,16 @@ abstract contract Votes is Context, EIP712, Nonces, IERC5805 { /// @custom:oz-retyped-from Checkpoints.History Checkpoints.Trace224 private _totalCheckpoints; + /** + * @dev The clock was incorrectly modified. + */ + error ERC6372InconsistentClock(); + + /** + * @dev Lookup to future votes is not available. + */ + error ERC5805FutureLookup(uint256 timepoint, uint48 clock); + /** * @dev Clock used for flagging checkpoints. Can be overridden to implement timestamp based * checkpoints (and voting), in which case {CLOCK_MODE} should be overridden as well to match. @@ -56,7 +66,9 @@ abstract contract Votes is Context, EIP712, Nonces, IERC5805 { // solhint-disable-next-line func-name-mixedcase function CLOCK_MODE() public view virtual returns (string memory) { // Check that the clock was not modified - require(clock() == block.number, "Votes: broken clock mode"); + if (clock() != block.number) { + revert ERC6372InconsistentClock(); + } return "mode=blocknumber&from=default"; } @@ -76,7 +88,10 @@ abstract contract Votes is Context, EIP712, Nonces, IERC5805 { * - `timepoint` must be in the past. If operating using block numbers, the block must be already mined. */ function getPastVotes(address account, uint256 timepoint) public view virtual returns (uint256) { - require(timepoint < clock(), "Votes: future lookup"); + uint48 currentTimepoint = clock(); + if (timepoint >= currentTimepoint) { + revert ERC5805FutureLookup(timepoint, currentTimepoint); + } return _delegateCheckpoints[account].upperLookupRecent(SafeCast.toUint32(timepoint)); } @@ -93,7 +108,10 @@ abstract contract Votes is Context, EIP712, Nonces, IERC5805 { * - `timepoint` must be in the past. If operating using block numbers, the block must be already mined. */ function getPastTotalSupply(uint256 timepoint) public view virtual returns (uint256) { - require(timepoint < clock(), "Votes: future lookup"); + uint48 currentTimepoint = clock(); + if (timepoint >= currentTimepoint) { + revert ERC5805FutureLookup(timepoint, currentTimepoint); + } return _totalCheckpoints.upperLookupRecent(SafeCast.toUint32(timepoint)); } @@ -130,14 +148,16 @@ abstract contract Votes is Context, EIP712, Nonces, IERC5805 { bytes32 r, bytes32 s ) public virtual { - require(block.timestamp <= expiry, "Votes: signature expired"); + if (block.timestamp > expiry) { + revert VotesExpiredSignature(expiry); + } address signer = ECDSA.recover( _hashTypedDataV4(keccak256(abi.encode(_DELEGATION_TYPEHASH, delegatee, nonce, expiry))), v, r, s ); - require(nonce == _useNonce(signer), "Votes: invalid nonce"); + _useCheckedNonce(signer, nonce); _delegate(signer, delegatee); } diff --git a/contracts/interfaces/draft-IERC6093.sol b/contracts/interfaces/draft-IERC6093.sol new file mode 100644 index 000000000..cebda56a3 --- /dev/null +++ b/contracts/interfaces/draft-IERC6093.sol @@ -0,0 +1,162 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +/** + * @dev Standard ERC20 Errors + * Interface of the ERC6093 custom errors for ERC20 tokens + * as defined in https://eips.ethereum.org/EIPS/eip-6093 + */ +interface IERC20Errors { + /** + * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers. + * @param sender Address whose tokens are being transferred. + * @param balance Current balance for the interacting account. + * @param needed Minimum amount required to perform a transfer. + */ + error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed); + + /** + * @dev Indicates a failure with the token `sender`. Used in transfers. + * @param sender Address whose tokens are being transferred. + */ + error ERC20InvalidSender(address sender); + + /** + * @dev Indicates a failure with the token `receiver`. Used in transfers. + * @param receiver Address to which tokens are being transferred. + */ + error ERC20InvalidReceiver(address receiver); + + /** + * @dev Indicates a failure with the `spender`’s `allowance`. Used in transfers. + * @param spender Address that may be allowed to operate on tokens without being their owner. + * @param allowance Amount of tokens a `spender` is allowed to operate with. + * @param needed Minimum amount required to perform a transfer. + */ + error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed); + + /** + * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals. + * @param approver Address initiating an approval operation. + */ + error ERC20InvalidApprover(address approver); + + /** + * @dev Indicates a failure with the `spender` to be approved. Used in approvals. + * @param spender Address that may be allowed to operate on tokens without being their owner. + */ + error ERC20InvalidSpender(address spender); +} + +/** + * @dev Standard ERC721 Errors + * Interface of the ERC6093 custom errors for ERC721 tokens + * as defined in https://eips.ethereum.org/EIPS/eip-6093 + */ +interface IERC721Errors { + /** + * @dev Indicates that an address can't be an owner. For example, `address(0)` is a forbidden owner in EIP-20. + * Used in balance queries. + * @param owner Address of the current owner of a token. + */ + error ERC721InvalidOwner(address owner); + + /** + * @dev Indicates a `tokenId` whose `owner` is the zero address. + * @param tokenId Identifier number of a token. + */ + error ERC721NonexistentToken(uint256 tokenId); + + /** + * @dev Indicates an error related to the ownership over a particular token. Used in transfers. + * @param sender Address whose tokens are being transferred. + * @param tokenId Identifier number of a token. + * @param owner Address of the current owner of a token. + */ + error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner); + + /** + * @dev Indicates a failure with the token `sender`. Used in transfers. + * @param sender Address whose tokens are being transferred. + */ + error ERC721InvalidSender(address sender); + + /** + * @dev Indicates a failure with the token `receiver`. Used in transfers. + * @param receiver Address to which tokens are being transferred. + */ + error ERC721InvalidReceiver(address receiver); + + /** + * @dev Indicates a failure with the `operator`’s approval. Used in transfers. + * @param operator Address that may be allowed to operate on tokens without being their owner. + * @param tokenId Identifier number of a token. + */ + error ERC721InsufficientApproval(address operator, uint256 tokenId); + + /** + * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals. + * @param approver Address initiating an approval operation. + */ + error ERC721InvalidApprover(address approver); + + /** + * @dev Indicates a failure with the `operator` to be approved. Used in approvals. + * @param operator Address that may be allowed to operate on tokens without being their owner. + */ + error ERC721InvalidOperator(address operator); +} + +/** + * @dev Standard ERC1155 Errors + * Interface of the ERC6093 custom errors for ERC1155 tokens + * as defined in https://eips.ethereum.org/EIPS/eip-6093 + */ +interface IERC1155Errors { + /** + * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers. + * @param sender Address whose tokens are being transferred. + * @param balance Current balance for the interacting account. + * @param needed Minimum amount required to perform a transfer. + */ + error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId); + + /** + * @dev Indicates a failure with the token `sender`. Used in transfers. + * @param sender Address whose tokens are being transferred. + */ + error ERC1155InvalidSender(address sender); + + /** + * @dev Indicates a failure with the token `receiver`. Used in transfers. + * @param receiver Address to which tokens are being transferred. + */ + error ERC1155InvalidReceiver(address receiver); + + /** + * @dev Indicates a failure with the `operator`’s approval. Used in transfers. + * @param operator Address that may be allowed to operate on tokens without being their owner. + * @param owner Address of the current owner of a token. + */ + error ERC1155InsufficientApprovalForAll(address operator, address owner); + + /** + * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals. + * @param approver Address initiating an approval operation. + */ + error ERC1155InvalidApprover(address approver); + + /** + * @dev Indicates a failure with the `operator` to be approved. Used in approvals. + * @param operator Address that may be allowed to operate on tokens without being their owner. + */ + error ERC1155InvalidOperator(address operator); + + /** + * @dev Indicates an array length mismatch between ids and values in a safeBatchTransferFrom operation. + * Used in batch transfers. + * @param idsLength Length of the array of token identifiers + * @param valuesLength Length of the array of token amounts + */ + error ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength); +} diff --git a/contracts/metatx/MinimalForwarder.sol b/contracts/metatx/MinimalForwarder.sol index 8ea7a76e8..b5267aa10 100644 --- a/contracts/metatx/MinimalForwarder.sol +++ b/contracts/metatx/MinimalForwarder.sol @@ -31,6 +31,16 @@ contract MinimalForwarder is EIP712 { mapping(address => uint256) private _nonces; + /** + * @dev The request `from` doesn't match with the recovered `signer`. + */ + error MinimalForwarderInvalidSigner(address signer, address from); + + /** + * @dev The request nonce doesn't match with the `current` nonce for the request signer. + */ + error MinimalForwarderInvalidNonce(address signer, uint256 current); + constructor() EIP712("MinimalForwarder", "0.0.1") {} function getNonce(address from) public view returns (uint256) { @@ -38,17 +48,25 @@ contract MinimalForwarder is EIP712 { } function verify(ForwardRequest calldata req, bytes calldata signature) public view returns (bool) { - address signer = _hashTypedDataV4( - keccak256(abi.encode(_TYPEHASH, req.from, req.to, req.value, req.gas, req.nonce, keccak256(req.data))) - ).recover(signature); - return _nonces[req.from] == req.nonce && signer == req.from; + address signer = _recover(req, signature); + (bool correctFrom, bool correctNonce) = _validateReq(req, signer); + return correctFrom && correctNonce; } function execute( ForwardRequest calldata req, bytes calldata signature ) public payable returns (bool, bytes memory) { - require(verify(req, signature), "MinimalForwarder: signature does not match request"); + address signer = _recover(req, signature); + (bool correctFrom, bool correctNonce) = _validateReq(req, signer); + + if (!correctFrom) { + revert MinimalForwarderInvalidSigner(signer, req.from); + } + if (!correctNonce) { + revert MinimalForwarderInvalidNonce(signer, _nonces[req.from]); + } + _nonces[req.from] = req.nonce + 1; (bool success, bytes memory returndata) = req.to.call{gas: req.gas, value: req.value}( @@ -69,4 +87,18 @@ contract MinimalForwarder is EIP712 { return (success, returndata); } + + function _recover(ForwardRequest calldata req, bytes calldata signature) internal view returns (address) { + return + _hashTypedDataV4( + keccak256(abi.encode(_TYPEHASH, req.from, req.to, req.value, req.gas, req.nonce, keccak256(req.data))) + ).recover(signature); + } + + function _validateReq( + ForwardRequest calldata req, + address signer + ) internal view returns (bool correctFrom, bool correctNonce) { + return (signer == req.from, _nonces[req.from] == req.nonce); + } } diff --git a/contracts/mocks/AddressFnPointersMock.sol b/contracts/mocks/AddressFnPointersMock.sol new file mode 100644 index 000000000..c696b3ec1 --- /dev/null +++ b/contracts/mocks/AddressFnPointersMock.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import "../utils/Address.sol"; + +/** + * @dev A mock to expose `Address`'s functions with function pointers. + */ +contract AddressFnPointerMock { + error CustomRevert(); + + function functionCall(address target, bytes memory data) external returns (bytes memory) { + return Address.functionCall(target, data, _customRevert); + } + + function functionCallWithValue(address target, bytes memory data, uint256 value) external returns (bytes memory) { + return Address.functionCallWithValue(target, data, value, _customRevert); + } + + function functionStaticCall(address target, bytes memory data) external view returns (bytes memory) { + return Address.functionStaticCall(target, data, _customRevert); + } + + function functionDelegateCall(address target, bytes memory data) external returns (bytes memory) { + return Address.functionDelegateCall(target, data, _customRevert); + } + + function verifyCallResultFromTarget( + address target, + bool success, + bytes memory returndata + ) external view returns (bytes memory) { + return Address.verifyCallResultFromTarget(target, success, returndata, _customRevert); + } + + function verifyCallResult(bool success, bytes memory returndata) external view returns (bytes memory) { + return Address.verifyCallResult(success, returndata, _customRevert); + } + + function verifyCallResultVoid(bool success, bytes memory returndata) external view returns (bytes memory) { + return Address.verifyCallResult(success, returndata, _customRevertVoid); + } + + function _customRevert() internal pure { + revert CustomRevert(); + } + + function _customRevertVoid() internal pure {} +} diff --git a/contracts/mocks/token/ERC721ConsecutiveMock.sol b/contracts/mocks/token/ERC721ConsecutiveMock.sol index 851e45ceb..4aae388e0 100644 --- a/contracts/mocks/token/ERC721ConsecutiveMock.sol +++ b/contracts/mocks/token/ERC721ConsecutiveMock.sol @@ -3,7 +3,6 @@ pragma solidity ^0.8.19; import "../../token/ERC721/extensions/ERC721Consecutive.sol"; -import "../../token/ERC721/extensions/ERC721Enumerable.sol"; import "../../token/ERC721/extensions/ERC721Pausable.sol"; import "../../token/ERC721/extensions/ERC721Votes.sol"; diff --git a/contracts/proxy/Clones.sol b/contracts/proxy/Clones.sol index 7cdab55f6..d859d5645 100644 --- a/contracts/proxy/Clones.sol +++ b/contracts/proxy/Clones.sol @@ -17,6 +17,11 @@ pragma solidity ^0.8.19; * _Available since v3.4._ */ library Clones { + /** + * @dev A clone instance deployment failed. + */ + error ERC1167FailedCreateClone(); + /** * @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`. * @@ -32,7 +37,9 @@ library Clones { mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3)) instance := create(0, 0x09, 0x37) } - require(instance != address(0), "ERC1167: create failed"); + if (instance == address(0)) { + revert ERC1167FailedCreateClone(); + } } /** @@ -52,7 +59,9 @@ library Clones { mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3)) instance := create2(0, 0x09, 0x37, salt) } - require(instance != address(0), "ERC1167: create2 failed"); + if (instance == address(0)) { + revert ERC1167FailedCreateClone(); + } } /** diff --git a/contracts/proxy/ERC1967/ERC1967Upgrade.sol b/contracts/proxy/ERC1967/ERC1967Upgrade.sol index e42a06eb1..ca6b92580 100644 --- a/contracts/proxy/ERC1967/ERC1967Upgrade.sol +++ b/contracts/proxy/ERC1967/ERC1967Upgrade.sol @@ -26,6 +26,26 @@ abstract contract ERC1967Upgrade is IERC1967 { */ bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; + /** + * @dev The `implementation` of the proxy is invalid. + */ + error ERC1967InvalidImplementation(address implementation); + + /** + * @dev The `admin` of the proxy is invalid. + */ + error ERC1967InvalidAdmin(address admin); + + /** + * @dev The `beacon` of the proxy is invalid. + */ + error ERC1967InvalidBeacon(address beacon); + + /** + * @dev The storage `slot` is unsupported as a UUID. + */ + error ERC1967UnsupportedProxiableUUID(bytes32 slot); + /** * @dev Returns the current implementation address. */ @@ -37,7 +57,9 @@ abstract contract ERC1967Upgrade is IERC1967 { * @dev Stores a new address in the EIP1967 implementation slot. */ function _setImplementation(address newImplementation) private { - require(newImplementation.code.length > 0, "ERC1967: new implementation is not a contract"); + if (newImplementation.code.length == 0) { + revert ERC1967InvalidImplementation(newImplementation); + } StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation; } @@ -76,9 +98,12 @@ abstract contract ERC1967Upgrade is IERC1967 { _setImplementation(newImplementation); } else { try IERC1822Proxiable(newImplementation).proxiableUUID() returns (bytes32 slot) { - require(slot == _IMPLEMENTATION_SLOT, "ERC1967Upgrade: unsupported proxiableUUID"); + if (slot != _IMPLEMENTATION_SLOT) { + revert ERC1967UnsupportedProxiableUUID(slot); + } } catch { - revert("ERC1967Upgrade: new implementation is not UUPS"); + // The implementation is not UUPS + revert ERC1967InvalidImplementation(newImplementation); } _upgradeToAndCall(newImplementation, data, forceCall); } @@ -106,7 +131,9 @@ abstract contract ERC1967Upgrade is IERC1967 { * @dev Stores a new address in the EIP1967 admin slot. */ function _setAdmin(address newAdmin) private { - require(newAdmin != address(0), "ERC1967: new admin is the zero address"); + if (newAdmin == address(0)) { + revert ERC1967InvalidAdmin(address(0)); + } StorageSlot.getAddressSlot(_ADMIN_SLOT).value = newAdmin; } @@ -137,11 +164,15 @@ abstract contract ERC1967Upgrade is IERC1967 { * @dev Stores a new beacon in the EIP1967 beacon slot. */ function _setBeacon(address newBeacon) private { - require(newBeacon.code.length > 0, "ERC1967: new beacon is not a contract"); - require( - IBeacon(newBeacon).implementation().code.length > 0, - "ERC1967: beacon implementation is not a contract" - ); + if (newBeacon.code.length == 0) { + revert ERC1967InvalidBeacon(newBeacon); + } + + address beaconImplementation = IBeacon(newBeacon).implementation(); + if (beaconImplementation.code.length == 0) { + revert ERC1967InvalidImplementation(beaconImplementation); + } + StorageSlot.getAddressSlot(_BEACON_SLOT).value = newBeacon; } diff --git a/contracts/proxy/beacon/UpgradeableBeacon.sol b/contracts/proxy/beacon/UpgradeableBeacon.sol index 37d27f67a..c5e64ea59 100644 --- a/contracts/proxy/beacon/UpgradeableBeacon.sol +++ b/contracts/proxy/beacon/UpgradeableBeacon.sol @@ -15,6 +15,11 @@ import "../../access/Ownable.sol"; contract UpgradeableBeacon is IBeacon, Ownable { address private _implementation; + /** + * @dev The `implementation` of the beacon is invalid. + */ + error BeaconInvalidImplementation(address implementation); + /** * @dev Emitted when the implementation returned by the beacon is changed. */ @@ -57,7 +62,9 @@ contract UpgradeableBeacon is IBeacon, Ownable { * - `newImplementation` must be a contract. */ function _setImplementation(address newImplementation) private { - require(newImplementation.code.length > 0, "UpgradeableBeacon: implementation is not a contract"); + if (newImplementation.code.length == 0) { + revert BeaconInvalidImplementation(newImplementation); + } _implementation = newImplementation; } } diff --git a/contracts/proxy/transparent/TransparentUpgradeableProxy.sol b/contracts/proxy/transparent/TransparentUpgradeableProxy.sol index 01f55e99b..4536e28c8 100644 --- a/contracts/proxy/transparent/TransparentUpgradeableProxy.sol +++ b/contracts/proxy/transparent/TransparentUpgradeableProxy.sol @@ -52,6 +52,16 @@ interface ITransparentUpgradeableProxy is IERC1967 { * render the admin operations inaccessible, which could prevent upgradeability. Transparency may also be compromised. */ contract TransparentUpgradeableProxy is ERC1967Proxy { + /** + * @dev The proxy caller is the current admin, and can't fallback to the proxy target. + */ + error ProxyDeniedAdminAccess(); + + /** + * @dev msg.value is not 0. + */ + error ProxyNonPayableFunction(); + /** * @dev Initializes an upgradeable proxy managed by `_admin`, backed by the implementation at `_logic`, and * optionally initialized with `_data` as explained in {ERC1967Proxy-constructor}. @@ -74,7 +84,7 @@ contract TransparentUpgradeableProxy is ERC1967Proxy { } else if (selector == ITransparentUpgradeableProxy.changeAdmin.selector) { ret = _dispatchChangeAdmin(); } else { - revert("TransparentUpgradeableProxy: admin cannot fallback to proxy target"); + revert ProxyDeniedAdminAccess(); } assembly { return(add(ret, 0x20), mload(ret)) @@ -127,6 +137,8 @@ contract TransparentUpgradeableProxy is ERC1967Proxy { * non-payability of function implemented through dispatchers while still allowing value to pass through. */ function _requireZeroValue() private { - require(msg.value == 0); + if (msg.value != 0) { + revert ProxyNonPayableFunction(); + } } } diff --git a/contracts/proxy/utils/Initializable.sol b/contracts/proxy/utils/Initializable.sol index a42887918..3ae5e4a65 100644 --- a/contracts/proxy/utils/Initializable.sol +++ b/contracts/proxy/utils/Initializable.sol @@ -67,6 +67,16 @@ abstract contract Initializable { */ bool private _initializing; + /** + * @dev The contract is already initialized. + */ + error AlreadyInitialized(); + + /** + * @dev The contract is not initializing. + */ + error NotInitializing(); + /** * @dev Triggered when the contract has been initialized or reinitialized. */ @@ -83,10 +93,9 @@ abstract contract Initializable { */ modifier initializer() { bool isTopLevelCall = !_initializing; - require( - (isTopLevelCall && _initialized < 1) || (address(this).code.length == 0 && _initialized == 1), - "Initializable: contract is already initialized" - ); + if (!(isTopLevelCall && _initialized < 1) && !(address(this).code.length == 0 && _initialized == 1)) { + revert AlreadyInitialized(); + } _initialized = 1; if (isTopLevelCall) { _initializing = true; @@ -117,7 +126,9 @@ abstract contract Initializable { * Emits an {Initialized} event. */ modifier reinitializer(uint8 version) { - require(!_initializing && _initialized < version, "Initializable: contract is already initialized"); + if (_initializing || _initialized >= version) { + revert AlreadyInitialized(); + } _initialized = version; _initializing = true; _; @@ -130,7 +141,9 @@ abstract contract Initializable { * {initializer} and {reinitializer} modifiers, directly or indirectly. */ modifier onlyInitializing() { - require(_initializing, "Initializable: contract is not initializing"); + if (!_initializing) { + revert NotInitializing(); + } _; } @@ -143,7 +156,9 @@ abstract contract Initializable { * Emits an {Initialized} event the first time it is successfully executed. */ function _disableInitializers() internal virtual { - require(!_initializing, "Initializable: contract is initializing"); + if (_initializing) { + revert AlreadyInitialized(); + } if (_initialized != type(uint8).max) { _initialized = type(uint8).max; emit Initialized(type(uint8).max); diff --git a/contracts/proxy/utils/UUPSUpgradeable.sol b/contracts/proxy/utils/UUPSUpgradeable.sol index 1fd73247c..41a72ef9c 100644 --- a/contracts/proxy/utils/UUPSUpgradeable.sol +++ b/contracts/proxy/utils/UUPSUpgradeable.sol @@ -22,6 +22,11 @@ abstract contract UUPSUpgradeable is IERC1822Proxiable, ERC1967Upgrade { /// @custom:oz-upgrades-unsafe-allow state-variable-immutable state-variable-assignment address private immutable __self = address(this); + /** + * @dev The call is from an unauthorized context. + */ + error UUPSUnauthorizedCallContext(); + /** * @dev Check that the execution is being performed through a delegatecall call and that the execution context is * a proxy contract with an implementation (as defined in ERC1967) pointing to self. This should only be the case @@ -30,8 +35,14 @@ abstract contract UUPSUpgradeable is IERC1822Proxiable, ERC1967Upgrade { * fail. */ modifier onlyProxy() { - require(address(this) != __self, "Function must be called through delegatecall"); - require(_getImplementation() == __self, "Function must be called through active proxy"); + if (address(this) == __self) { + // Must be called through delegatecall + revert UUPSUnauthorizedCallContext(); + } + if (_getImplementation() != __self) { + // Must be called through an active proxy + revert UUPSUnauthorizedCallContext(); + } _; } @@ -40,7 +51,10 @@ abstract contract UUPSUpgradeable is IERC1822Proxiable, ERC1967Upgrade { * callable on the implementing contract but not through proxies. */ modifier notDelegated() { - require(address(this) == __self, "UUPSUpgradeable: must not be called through delegatecall"); + if (address(this) != __self) { + // Must not be called through delegatecall + revert UUPSUnauthorizedCallContext(); + } _; } diff --git a/contracts/security/Pausable.sol b/contracts/security/Pausable.sol index cdf3ee2cd..dc0afa663 100644 --- a/contracts/security/Pausable.sol +++ b/contracts/security/Pausable.sol @@ -15,6 +15,8 @@ import "../utils/Context.sol"; * simply including this module, only once the modifiers are put in place. */ abstract contract Pausable is Context { + bool private _paused; + /** * @dev Emitted when the pause is triggered by `account`. */ @@ -25,7 +27,15 @@ abstract contract Pausable is Context { */ event Unpaused(address account); - bool private _paused; + /** + * @dev The operation failed because the contract is paused. + */ + error EnforcedPause(); + + /** + * @dev The operation failed because the contract is not paused. + */ + error ExpectedPause(); /** * @dev Initializes the contract in unpaused state. @@ -69,14 +79,18 @@ abstract contract Pausable is Context { * @dev Throws if the contract is paused. */ function _requireNotPaused() internal view virtual { - require(!paused(), "Pausable: paused"); + if (paused()) { + revert EnforcedPause(); + } } /** * @dev Throws if the contract is not paused. */ function _requirePaused() internal view virtual { - require(paused(), "Pausable: not paused"); + if (!paused()) { + revert ExpectedPause(); + } } /** diff --git a/contracts/security/ReentrancyGuard.sol b/contracts/security/ReentrancyGuard.sol index 88a86ae7e..40ae5b050 100644 --- a/contracts/security/ReentrancyGuard.sol +++ b/contracts/security/ReentrancyGuard.sol @@ -36,6 +36,11 @@ abstract contract ReentrancyGuard { uint256 private _status; + /** + * @dev Unauthorized reentrant call. + */ + error ReentrancyGuardReentrantCall(); + constructor() { _status = _NOT_ENTERED; } @@ -55,7 +60,9 @@ abstract contract ReentrancyGuard { function _nonReentrantBefore() private { // On the first call to nonReentrant, _status will be _NOT_ENTERED - require(_status != _ENTERED, "ReentrancyGuard: reentrant call"); + if (_status == _ENTERED) { + revert ReentrancyGuardReentrantCall(); + } // Any calls to nonReentrant after this point will fail _status = _ENTERED; diff --git a/contracts/token/ERC1155/ERC1155.sol b/contracts/token/ERC1155/ERC1155.sol index c2f217d7e..05bcc3fa1 100644 --- a/contracts/token/ERC1155/ERC1155.sol +++ b/contracts/token/ERC1155/ERC1155.sol @@ -8,6 +8,7 @@ import "./IERC1155Receiver.sol"; import "./extensions/IERC1155MetadataURI.sol"; import "../../utils/Context.sol"; import "../../utils/introspection/ERC165.sol"; +import "../../interfaces/draft-IERC6093.sol"; /** * @dev Implementation of the basic standard multi-token. @@ -16,7 +17,7 @@ import "../../utils/introspection/ERC165.sol"; * * _Available since v3.1._ */ -contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { +contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI, IERC1155Errors { // Mapping from token ID to account balances mapping(uint256 => mapping(address => uint256)) private _balances; @@ -79,7 +80,9 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { address[] memory accounts, uint256[] memory ids ) public view virtual returns (uint256[] memory) { - require(accounts.length == ids.length, "ERC1155: accounts and ids length mismatch"); + if (accounts.length != ids.length) { + revert ERC1155InvalidArrayLength(ids.length, accounts.length); + } uint256[] memory batchBalances = new uint256[](accounts.length); @@ -108,10 +111,9 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { * @dev See {IERC1155-safeTransferFrom}. */ function safeTransferFrom(address from, address to, uint256 id, uint256 amount, bytes memory data) public virtual { - require( - from == _msgSender() || isApprovedForAll(from, _msgSender()), - "ERC1155: caller is not token owner or approved" - ); + if (from != _msgSender() && !isApprovedForAll(from, _msgSender())) { + revert ERC1155InsufficientApprovalForAll(_msgSender(), from); + } _safeTransferFrom(from, to, id, amount, data); } @@ -125,10 +127,9 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { uint256[] memory amounts, bytes memory data ) public virtual { - require( - from == _msgSender() || isApprovedForAll(from, _msgSender()), - "ERC1155: caller is not token owner or approved" - ); + if (from != _msgSender() && !isApprovedForAll(from, _msgSender())) { + revert ERC1155InsufficientApprovalForAll(_msgSender(), from); + } _safeBatchTransferFrom(from, to, ids, amounts, data); } @@ -149,7 +150,9 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { uint256[] memory amounts, bytes memory data ) internal virtual { - require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch"); + if (ids.length != amounts.length) { + revert ERC1155InvalidArrayLength(ids.length, amounts.length); + } address operator = _msgSender(); @@ -159,7 +162,9 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { if (from != address(0)) { uint256 fromBalance = _balances[id][from]; - require(fromBalance >= amount, "ERC1155: insufficient balance for transfer"); + if (fromBalance < amount) { + revert ERC1155InsufficientBalance(from, fromBalance, amount, id); + } unchecked { _balances[id][from] = fromBalance - amount; } @@ -198,8 +203,12 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { * acceptance magic value. */ function _safeTransferFrom(address from, address to, uint256 id, uint256 amount, bytes memory data) internal { - require(to != address(0), "ERC1155: transfer to the zero address"); - require(from != address(0), "ERC1155: transfer from the zero address"); + if (to == address(0)) { + revert ERC1155InvalidReceiver(address(0)); + } + if (from == address(0)) { + revert ERC1155InvalidSender(address(0)); + } (uint256[] memory ids, uint256[] memory amounts) = _asSingletonArrays(id, amount); _update(from, to, ids, amounts, data); } @@ -221,8 +230,12 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { uint256[] memory amounts, bytes memory data ) internal { - require(to != address(0), "ERC1155: transfer to the zero address"); - require(from != address(0), "ERC1155: transfer from the zero address"); + if (to == address(0)) { + revert ERC1155InvalidReceiver(address(0)); + } + if (from == address(0)) { + revert ERC1155InvalidSender(address(0)); + } _update(from, to, ids, amounts, data); } @@ -261,7 +274,9 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { * acceptance magic value. */ function _mint(address to, uint256 id, uint256 amount, bytes memory data) internal { - require(to != address(0), "ERC1155: mint to the zero address"); + if (to == address(0)) { + revert ERC1155InvalidReceiver(address(0)); + } (uint256[] memory ids, uint256[] memory amounts) = _asSingletonArrays(id, amount); _update(address(0), to, ids, amounts, data); } @@ -278,7 +293,9 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { * acceptance magic value. */ function _mintBatch(address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data) internal { - require(to != address(0), "ERC1155: mint to the zero address"); + if (to == address(0)) { + revert ERC1155InvalidReceiver(address(0)); + } _update(address(0), to, ids, amounts, data); } @@ -293,7 +310,9 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { * - `from` must have at least `amount` tokens of token type `id`. */ function _burn(address from, uint256 id, uint256 amount) internal { - require(from != address(0), "ERC1155: burn from the zero address"); + if (from == address(0)) { + revert ERC1155InvalidSender(address(0)); + } (uint256[] memory ids, uint256[] memory amounts) = _asSingletonArrays(id, amount); _update(from, address(0), ids, amounts, ""); } @@ -308,7 +327,9 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { * - `ids` and `amounts` must have the same length. */ function _burnBatch(address from, uint256[] memory ids, uint256[] memory amounts) internal { - require(from != address(0), "ERC1155: burn from the zero address"); + if (from == address(0)) { + revert ERC1155InvalidSender(address(0)); + } _update(from, address(0), ids, amounts, ""); } @@ -318,7 +339,9 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { * Emits an {ApprovalForAll} event. */ function _setApprovalForAll(address owner, address operator, bool approved) internal virtual { - require(owner != operator, "ERC1155: setting approval status for self"); + if (owner == operator) { + revert ERC1155InvalidOperator(operator); + } _operatorApprovals[owner][operator] = approved; emit ApprovalForAll(owner, operator, approved); } @@ -334,12 +357,14 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { if (to.code.length > 0) { try IERC1155Receiver(to).onERC1155Received(operator, from, id, amount, data) returns (bytes4 response) { if (response != IERC1155Receiver.onERC1155Received.selector) { - revert("ERC1155: ERC1155Receiver rejected tokens"); + // Tokens rejected + revert ERC1155InvalidReceiver(to); } } catch Error(string memory reason) { revert(reason); } catch { - revert("ERC1155: transfer to non-ERC1155Receiver implementer"); + // non-ERC1155Receiver implementer + revert ERC1155InvalidReceiver(to); } } } @@ -357,12 +382,14 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { bytes4 response ) { if (response != IERC1155Receiver.onERC1155BatchReceived.selector) { - revert("ERC1155: ERC1155Receiver rejected tokens"); + // Tokens rejected + revert ERC1155InvalidReceiver(to); } } catch Error(string memory reason) { revert(reason); } catch { - revert("ERC1155: transfer to non-ERC1155Receiver implementer"); + // non-ERC1155Receiver implementer + revert ERC1155InvalidReceiver(to); } } } diff --git a/contracts/token/ERC1155/extensions/ERC1155Burnable.sol b/contracts/token/ERC1155/extensions/ERC1155Burnable.sol index c079f07e1..ff099cb58 100644 --- a/contracts/token/ERC1155/extensions/ERC1155Burnable.sol +++ b/contracts/token/ERC1155/extensions/ERC1155Burnable.sol @@ -13,19 +13,17 @@ import "../ERC1155.sol"; */ abstract contract ERC1155Burnable is ERC1155 { function burn(address account, uint256 id, uint256 value) public virtual { - require( - account == _msgSender() || isApprovedForAll(account, _msgSender()), - "ERC1155: caller is not token owner or approved" - ); + if (account != _msgSender() && !isApprovedForAll(account, _msgSender())) { + revert ERC1155InsufficientApprovalForAll(_msgSender(), account); + } _burn(account, id, value); } function burnBatch(address account, uint256[] memory ids, uint256[] memory values) public virtual { - require( - account == _msgSender() || isApprovedForAll(account, _msgSender()), - "ERC1155: caller is not token owner or approved" - ); + if (account != _msgSender() && !isApprovedForAll(account, _msgSender())) { + revert ERC1155InsufficientApprovalForAll(_msgSender(), account); + } _burnBatch(account, ids, values); } diff --git a/contracts/token/ERC1155/extensions/ERC1155Pausable.sol b/contracts/token/ERC1155/extensions/ERC1155Pausable.sol index 95f006e6f..f8357062c 100644 --- a/contracts/token/ERC1155/extensions/ERC1155Pausable.sol +++ b/contracts/token/ERC1155/extensions/ERC1155Pausable.sol @@ -35,8 +35,7 @@ abstract contract ERC1155Pausable is ERC1155, Pausable { uint256[] memory ids, uint256[] memory amounts, bytes memory data - ) internal virtual override { - require(!paused(), "ERC1155Pausable: token transfer while paused"); + ) internal virtual override whenNotPaused { super._update(from, to, ids, amounts, data); } } diff --git a/contracts/token/ERC1155/extensions/ERC1155Supply.sol b/contracts/token/ERC1155/extensions/ERC1155Supply.sol index 4ad83ea02..f32fbb74b 100644 --- a/contracts/token/ERC1155/extensions/ERC1155Supply.sol +++ b/contracts/token/ERC1155/extensions/ERC1155Supply.sol @@ -66,11 +66,8 @@ abstract contract ERC1155Supply is ERC1155 { for (uint256 i = 0; i < ids.length; ++i) { uint256 id = ids[i]; uint256 amount = amounts[i]; - uint256 supply = _totalSupply[id]; - require(supply >= amount, "ERC1155: burn amount exceeds totalSupply"); + _totalSupply[id] -= amount; unchecked { - // Overflow not possible: amounts[i] <= totalSupply(i) - _totalSupply[id] = supply - amount; // Overflow not possible: sum(amounts[i]) <= sum(totalSupply(i)) <= totalSupplyAll totalBurnAmount += amount; } diff --git a/contracts/token/ERC20/ERC20.sol b/contracts/token/ERC20/ERC20.sol index 4db525a7a..dd46c15dd 100644 --- a/contracts/token/ERC20/ERC20.sol +++ b/contracts/token/ERC20/ERC20.sol @@ -6,6 +6,7 @@ pragma solidity ^0.8.19; import "./IERC20.sol"; import "./extensions/IERC20Metadata.sol"; import "../../utils/Context.sol"; +import "../../interfaces/draft-IERC6093.sol"; /** * @dev Implementation of the {IERC20} interface. @@ -34,7 +35,7 @@ import "../../utils/Context.sol"; * functions have been added to mitigate the well-known issues around setting * allowances. See {IERC20-approve}. */ -contract ERC20 is Context, IERC20, IERC20Metadata { +contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors { mapping(address => uint256) private _balances; mapping(address => mapping(address => uint256)) private _allowances; @@ -44,6 +45,11 @@ contract ERC20 is Context, IERC20, IERC20Metadata { string private _name; string private _symbol; + /** + * @dev Indicates a failed `decreaseAllowance` request. + */ + error ERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease); + /** * @dev Sets the values for {name} and {symbol}. * @@ -191,14 +197,16 @@ contract ERC20 is Context, IERC20, IERC20Metadata { * * - `spender` cannot be the zero address. * - `spender` must have allowance for the caller of at least - * `subtractedValue`. + * `requestedDecrease`. */ - function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) { + function decreaseAllowance(address spender, uint256 requestedDecrease) public virtual returns (bool) { address owner = _msgSender(); uint256 currentAllowance = allowance(owner, spender); - require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero"); + if (currentAllowance < requestedDecrease) { + revert ERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease); + } unchecked { - _approve(owner, spender, currentAllowance - subtractedValue); + _approve(owner, spender, currentAllowance - requestedDecrease); } return true; @@ -215,8 +223,12 @@ contract ERC20 is Context, IERC20, IERC20Metadata { * NOTE: This function is not virtual, {_update} should be overridden instead. */ function _transfer(address from, address to, uint256 amount) internal { - require(from != address(0), "ERC20: transfer from the zero address"); - require(to != address(0), "ERC20: transfer to the zero address"); + if (from == address(0)) { + revert ERC20InvalidSender(address(0)); + } + if (to == address(0)) { + revert ERC20InvalidReceiver(address(0)); + } _update(from, to, amount); } @@ -231,7 +243,9 @@ contract ERC20 is Context, IERC20, IERC20Metadata { _totalSupply += amount; } else { uint256 fromBalance = _balances[from]; - require(fromBalance >= amount, "ERC20: transfer amount exceeds balance"); + if (fromBalance < amount) { + revert ERC20InsufficientBalance(from, fromBalance, amount); + } unchecked { // Overflow not possible: amount <= fromBalance <= totalSupply. _balances[from] = fromBalance - amount; @@ -262,7 +276,9 @@ contract ERC20 is Context, IERC20, IERC20Metadata { * NOTE: This function is not virtual, {_update} should be overridden instead. */ function _mint(address account, uint256 amount) internal { - require(account != address(0), "ERC20: mint to the zero address"); + if (account == address(0)) { + revert ERC20InvalidReceiver(address(0)); + } _update(address(0), account, amount); } @@ -275,7 +291,9 @@ contract ERC20 is Context, IERC20, IERC20Metadata { * NOTE: This function is not virtual, {_update} should be overridden instead */ function _burn(address account, uint256 amount) internal { - require(account != address(0), "ERC20: burn from the zero address"); + if (account == address(0)) { + revert ERC20InvalidSender(address(0)); + } _update(account, address(0), amount); } @@ -293,9 +311,12 @@ contract ERC20 is Context, IERC20, IERC20Metadata { * - `spender` cannot be the zero address. */ function _approve(address owner, address spender, uint256 amount) internal virtual { - require(owner != address(0), "ERC20: approve from the zero address"); - require(spender != address(0), "ERC20: approve to the zero address"); - + if (owner == address(0)) { + revert ERC20InvalidApprover(address(0)); + } + if (spender == address(0)) { + revert ERC20InvalidSpender(address(0)); + } _allowances[owner][spender] = amount; emit Approval(owner, spender, amount); } @@ -311,7 +332,9 @@ contract ERC20 is Context, IERC20, IERC20Metadata { function _spendAllowance(address owner, address spender, uint256 amount) internal virtual { uint256 currentAllowance = allowance(owner, spender); if (currentAllowance != type(uint256).max) { - require(currentAllowance >= amount, "ERC20: insufficient allowance"); + if (currentAllowance < amount) { + revert ERC20InsufficientAllowance(spender, currentAllowance, amount); + } unchecked { _approve(owner, spender, currentAllowance - amount); } diff --git a/contracts/token/ERC20/extensions/ERC20Capped.sol b/contracts/token/ERC20/extensions/ERC20Capped.sol index cda072651..41e9ce5cf 100644 --- a/contracts/token/ERC20/extensions/ERC20Capped.sol +++ b/contracts/token/ERC20/extensions/ERC20Capped.sol @@ -11,12 +11,24 @@ import "../ERC20.sol"; abstract contract ERC20Capped is ERC20 { uint256 private immutable _cap; + /** + * @dev Total supply cap has been exceeded. + */ + error ERC20ExceededCap(uint256 increasedSupply, uint256 cap); + + /** + * @dev The supplied cap is not a valid cap. + */ + error ERC20InvalidCap(uint256 cap); + /** * @dev Sets the value of the `cap`. This value is immutable, it can only be * set once during construction. */ constructor(uint256 cap_) { - require(cap_ > 0, "ERC20Capped: cap is 0"); + if (cap_ == 0) { + revert ERC20InvalidCap(0); + } _cap = cap_; } @@ -31,10 +43,14 @@ abstract contract ERC20Capped is ERC20 { * @dev See {ERC20-_update}. */ function _update(address from, address to, uint256 amount) internal virtual override { + super._update(from, to, amount); + if (from == address(0)) { - require(totalSupply() + amount <= cap(), "ERC20Capped: cap exceeded"); + uint256 maxSupply = cap(); + uint256 supply = totalSupply(); + if (supply > maxSupply) { + revert ERC20ExceededCap(supply, maxSupply); + } } - - super._update(from, to, amount); } } diff --git a/contracts/token/ERC20/extensions/ERC20FlashMint.sol b/contracts/token/ERC20/extensions/ERC20FlashMint.sol index 7a4076678..09c20bacc 100644 --- a/contracts/token/ERC20/extensions/ERC20FlashMint.sol +++ b/contracts/token/ERC20/extensions/ERC20FlashMint.sol @@ -19,6 +19,21 @@ import "../ERC20.sol"; abstract contract ERC20FlashMint is ERC20, IERC3156FlashLender { bytes32 private constant _RETURN_VALUE = keccak256("ERC3156FlashBorrower.onFlashLoan"); + /** + * @dev The loan token is not valid. + */ + error ERC3156UnsupportedToken(address token); + + /** + * @dev The requested loan exceeds the max loan amount for `token`. + */ + error ERC3156ExceededMaxLoan(uint256 maxLoan); + + /** + * @dev The receiver of a flashloan is not a valid {onFlashLoan} implementer. + */ + error ERC3156InvalidReceiver(address receiver); + /** * @dev Returns the maximum amount of tokens available for loan. * @param token The address of the token that is requested. @@ -37,7 +52,9 @@ abstract contract ERC20FlashMint is ERC20, IERC3156FlashLender { * @return The fees applied to the corresponding flash loan. */ function flashFee(address token, uint256 amount) public view virtual returns (uint256) { - require(token == address(this), "ERC20FlashMint: wrong token"); + if (token != address(this)) { + revert ERC3156UnsupportedToken(token); + } return _flashFee(token, amount); } @@ -89,13 +106,15 @@ abstract contract ERC20FlashMint is ERC20, IERC3156FlashLender { uint256 amount, bytes calldata data ) public virtual returns (bool) { - require(amount <= maxFlashLoan(token), "ERC20FlashMint: amount exceeds maxFlashLoan"); + uint256 maxLoan = maxFlashLoan(token); + if (amount > maxLoan) { + revert ERC3156ExceededMaxLoan(maxLoan); + } uint256 fee = flashFee(token, amount); _mint(address(receiver), amount); - require( - receiver.onFlashLoan(msg.sender, token, amount, fee, data) == _RETURN_VALUE, - "ERC20FlashMint: invalid return value" - ); + if (receiver.onFlashLoan(msg.sender, token, amount, fee, data) != _RETURN_VALUE) { + revert ERC3156InvalidReceiver(address(receiver)); + } address flashFeeReceiver = _flashFeeReceiver(); _spendAllowance(address(receiver), address(this), amount + fee); if (fee == 0 || flashFeeReceiver == address(0)) { diff --git a/contracts/token/ERC20/extensions/ERC20Pausable.sol b/contracts/token/ERC20/extensions/ERC20Pausable.sol index b31cac7de..5ef50f9c6 100644 --- a/contracts/token/ERC20/extensions/ERC20Pausable.sol +++ b/contracts/token/ERC20/extensions/ERC20Pausable.sol @@ -27,8 +27,7 @@ abstract contract ERC20Pausable is ERC20, Pausable { * * - the contract must not be paused. */ - function _update(address from, address to, uint256 amount) internal virtual override { - require(!paused(), "ERC20Pausable: token transfer while paused"); + function _update(address from, address to, uint256 amount) internal virtual override whenNotPaused { super._update(from, to, amount); } } diff --git a/contracts/token/ERC20/extensions/ERC20Permit.sol b/contracts/token/ERC20/extensions/ERC20Permit.sol index 9379e4451..4378eb7c1 100644 --- a/contracts/token/ERC20/extensions/ERC20Permit.sol +++ b/contracts/token/ERC20/extensions/ERC20Permit.sol @@ -24,6 +24,16 @@ abstract contract ERC20Permit is ERC20, IERC20Permit, EIP712, Nonces { bytes32 private constant _PERMIT_TYPEHASH = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"); + /** + * @dev Permit deadline has expired. + */ + error ERC2612ExpiredSignature(uint256 deadline); + + /** + * @dev Mismatched signature. + */ + error ERC2612InvalidSigner(address signer, address owner); + /** * @dev Initializes the {EIP712} domain separator using the `name` parameter, and setting `version` to `"1"`. * @@ -43,14 +53,18 @@ abstract contract ERC20Permit is ERC20, IERC20Permit, EIP712, Nonces { bytes32 r, bytes32 s ) public virtual { - require(block.timestamp <= deadline, "ERC20Permit: expired deadline"); + if (block.timestamp > deadline) { + revert ERC2612ExpiredSignature(deadline); + } bytes32 structHash = keccak256(abi.encode(_PERMIT_TYPEHASH, owner, spender, value, _useNonce(owner), deadline)); bytes32 hash = _hashTypedDataV4(structHash); address signer = ECDSA.recover(hash, v, r, s); - require(signer == owner, "ERC20Permit: invalid signature"); + if (signer != owner) { + revert ERC2612InvalidSigner(signer, owner); + } _approve(owner, spender, value); } diff --git a/contracts/token/ERC20/extensions/ERC20Votes.sol b/contracts/token/ERC20/extensions/ERC20Votes.sol index c078878ec..98f798efe 100644 --- a/contracts/token/ERC20/extensions/ERC20Votes.sol +++ b/contracts/token/ERC20/extensions/ERC20Votes.sol @@ -23,6 +23,11 @@ import "../../../utils/math/SafeCast.sol"; * _Available since v4.2._ */ abstract contract ERC20Votes is ERC20, Votes { + /** + * @dev Total supply cap has been exceeded, introducing a risk of votes overflowing. + */ + error ERC20ExceededSafeSupply(uint256 increasedSupply, uint256 cap); + /** * @dev Maximum token supply. Defaults to `type(uint224).max` (2^224^ - 1). */ @@ -38,7 +43,11 @@ abstract contract ERC20Votes is ERC20, Votes { function _update(address from, address to, uint256 amount) internal virtual override { super._update(from, to, amount); if (from == address(0)) { - require(totalSupply() <= _maxSupply(), "ERC20Votes: total supply risks overflowing votes"); + uint256 supply = totalSupply(); + uint256 cap = _maxSupply(); + if (supply > cap) { + revert ERC20ExceededSafeSupply(supply, cap); + } } _transferVotingUnits(from, to, amount); } diff --git a/contracts/token/ERC20/extensions/ERC20Wrapper.sol b/contracts/token/ERC20/extensions/ERC20Wrapper.sol index bf2b225cf..389965e9c 100644 --- a/contracts/token/ERC20/extensions/ERC20Wrapper.sol +++ b/contracts/token/ERC20/extensions/ERC20Wrapper.sol @@ -18,8 +18,15 @@ import "../utils/SafeERC20.sol"; abstract contract ERC20Wrapper is ERC20 { IERC20 private immutable _underlying; + /** + * @dev The underlying token couldn't be wrapped. + */ + error ERC20InvalidUnderlying(address token); + constructor(IERC20 underlyingToken) { - require(underlyingToken != this, "ERC20Wrapper: cannot self wrap"); + if (underlyingToken == this) { + revert ERC20InvalidUnderlying(address(this)); + } _underlying = underlyingToken; } @@ -46,7 +53,9 @@ abstract contract ERC20Wrapper is ERC20 { */ function depositFor(address account, uint256 amount) public virtual returns (bool) { address sender = _msgSender(); - require(sender != address(this), "ERC20Wrapper: wrapper can't deposit"); + if (sender == address(this)) { + revert ERC20InvalidSender(address(this)); + } SafeERC20.safeTransferFrom(_underlying, sender, address(this), amount); _mint(account, amount); return true; diff --git a/contracts/token/ERC20/extensions/ERC4626.sol b/contracts/token/ERC20/extensions/ERC4626.sol index 665c95a02..9ea6789f7 100644 --- a/contracts/token/ERC20/extensions/ERC4626.sol +++ b/contracts/token/ERC20/extensions/ERC4626.sol @@ -53,6 +53,26 @@ abstract contract ERC4626 is ERC20, IERC4626 { IERC20 private immutable _asset; uint8 private immutable _underlyingDecimals; + /** + * @dev Attempted to deposit more assets than the max amount for `receiver`. + */ + error ERC4626ExceededMaxDeposit(address receiver, uint256 assets, uint256 max); + + /** + * @dev Attempted to mint more shares than the max amount for `receiver`. + */ + error ERC4626ExceededMaxMint(address receiver, uint256 shares, uint256 max); + + /** + * @dev Attempted to withdraw more assets than the max amount for `receiver`. + */ + error ERC4626ExceededMaxWithdraw(address owner, uint256 assets, uint256 max); + + /** + * @dev Attempted to redeem more shares than the max amount for `receiver`. + */ + error ERC4626ExceededMaxRedeem(address owner, uint256 shares, uint256 max); + /** * @dev Set the underlying asset contract. This must be an ERC20-compatible contract (ERC20 or ERC777). */ @@ -151,7 +171,10 @@ abstract contract ERC4626 is ERC20, IERC4626 { /** @dev See {IERC4626-deposit}. */ function deposit(uint256 assets, address receiver) public virtual returns (uint256) { - require(assets <= maxDeposit(receiver), "ERC4626: deposit more than max"); + uint256 maxAssets = maxDeposit(receiver); + if (assets > maxAssets) { + revert ERC4626ExceededMaxDeposit(receiver, assets, maxAssets); + } uint256 shares = previewDeposit(assets); _deposit(_msgSender(), receiver, assets, shares); @@ -165,7 +188,10 @@ abstract contract ERC4626 is ERC20, IERC4626 { * In this case, the shares will be minted without requiring any assets to be deposited. */ function mint(uint256 shares, address receiver) public virtual returns (uint256) { - require(shares <= maxMint(receiver), "ERC4626: mint more than max"); + uint256 maxShares = maxMint(receiver); + if (shares > maxShares) { + revert ERC4626ExceededMaxMint(receiver, shares, maxShares); + } uint256 assets = previewMint(shares); _deposit(_msgSender(), receiver, assets, shares); @@ -175,7 +201,10 @@ abstract contract ERC4626 is ERC20, IERC4626 { /** @dev See {IERC4626-withdraw}. */ function withdraw(uint256 assets, address receiver, address owner) public virtual returns (uint256) { - require(assets <= maxWithdraw(owner), "ERC4626: withdraw more than max"); + uint256 maxAssets = maxWithdraw(owner); + if (assets > maxAssets) { + revert ERC4626ExceededMaxWithdraw(owner, assets, maxAssets); + } uint256 shares = previewWithdraw(assets); _withdraw(_msgSender(), receiver, owner, assets, shares); @@ -185,7 +214,10 @@ abstract contract ERC4626 is ERC20, IERC4626 { /** @dev See {IERC4626-redeem}. */ function redeem(uint256 shares, address receiver, address owner) public virtual returns (uint256) { - require(shares <= maxRedeem(owner), "ERC4626: redeem more than max"); + uint256 maxShares = maxRedeem(owner); + if (shares > maxShares) { + revert ERC4626ExceededMaxRedeem(owner, shares, maxShares); + } uint256 assets = previewRedeem(shares); _withdraw(_msgSender(), receiver, owner, assets, shares); diff --git a/contracts/token/ERC20/utils/SafeERC20.sol b/contracts/token/ERC20/utils/SafeERC20.sol index b1532d1cb..599307e7f 100644 --- a/contracts/token/ERC20/utils/SafeERC20.sol +++ b/contracts/token/ERC20/utils/SafeERC20.sol @@ -19,6 +19,16 @@ import "../../../utils/Address.sol"; library SafeERC20 { using Address for address; + /** + * @dev An operation with an ERC20 token failed. + */ + error SafeERC20FailedOperation(address token); + + /** + * @dev Indicates a failed `decreaseAllowance` request. + */ + error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease); + /** * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value, * non-reverting calls are assumed to be successful. @@ -45,14 +55,16 @@ library SafeERC20 { } /** - * @dev Decrease the calling contract's allowance toward `spender` by `value`. If `token` returns no value, + * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no value, * non-reverting calls are assumed to be successful. */ - function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal { + function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal { unchecked { - uint256 oldAllowance = token.allowance(address(this), spender); - require(oldAllowance >= value, "SafeERC20: decreased allowance below zero"); - forceApprove(token, spender, oldAllowance - value); + uint256 currentAllowance = token.allowance(address(this), spender); + if (currentAllowance < requestedDecrease) { + revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease); + } + forceApprove(token, spender, currentAllowance - requestedDecrease); } } @@ -87,7 +99,9 @@ library SafeERC20 { uint256 nonceBefore = token.nonces(owner); token.permit(owner, spender, value, deadline, v, r, s); uint256 nonceAfter = token.nonces(owner); - require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed"); + if (nonceAfter != nonceBefore + 1) { + revert SafeERC20FailedOperation(address(token)); + } } /** @@ -101,8 +115,10 @@ library SafeERC20 { // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that // the target address contains contract code and also asserts for success in the low-level call. - bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed"); - require(returndata.length == 0 || abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed"); + bytes memory returndata = address(token).functionCall(data); + if (returndata.length != 0 && !abi.decode(returndata, (bool))) { + revert SafeERC20FailedOperation(address(token)); + } } /** diff --git a/contracts/token/ERC721/ERC721.sol b/contracts/token/ERC721/ERC721.sol index 3eb3f74cf..08f660a29 100644 --- a/contracts/token/ERC721/ERC721.sol +++ b/contracts/token/ERC721/ERC721.sol @@ -9,13 +9,14 @@ import "./extensions/IERC721Metadata.sol"; import "../../utils/Context.sol"; import "../../utils/Strings.sol"; import "../../utils/introspection/ERC165.sol"; +import "../../interfaces/draft-IERC6093.sol"; /** * @dev Implementation of https://eips.ethereum.org/EIPS/eip-721[ERC721] Non-Fungible Token Standard, including * the Metadata extension, but not including the Enumerable extension, which is available separately as * {ERC721Enumerable}. */ -contract ERC721 is Context, ERC165, IERC721, IERC721Metadata { +contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Errors { using Strings for uint256; // Token name @@ -58,7 +59,9 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata { * @dev See {IERC721-balanceOf}. */ function balanceOf(address owner) public view virtual returns (uint256) { - require(owner != address(0), "ERC721: address zero is not a valid owner"); + if (owner == address(0)) { + revert ERC721InvalidOwner(address(0)); + } return _balances[owner]; } @@ -67,7 +70,9 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata { */ function ownerOf(uint256 tokenId) public view virtual returns (address) { address owner = _ownerOf(tokenId); - require(owner != address(0), "ERC721: invalid token ID"); + if (owner == address(0)) { + revert ERC721NonexistentToken(tokenId); + } return owner; } @@ -109,12 +114,13 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata { */ function approve(address to, uint256 tokenId) public virtual { address owner = ownerOf(tokenId); - require(to != owner, "ERC721: approval to current owner"); + if (to == owner) { + revert ERC721InvalidOperator(owner); + } - require( - _msgSender() == owner || isApprovedForAll(owner, _msgSender()), - "ERC721: approve caller is not token owner or approved for all" - ); + if (_msgSender() != owner && !isApprovedForAll(owner, _msgSender())) { + revert ERC721InvalidApprover(_msgSender()); + } _approve(to, tokenId); } @@ -146,8 +152,9 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata { * @dev See {IERC721-transferFrom}. */ function transferFrom(address from, address to, uint256 tokenId) public virtual { - //solhint-disable-next-line max-line-length - require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner or approved"); + if (!_isApprovedOrOwner(_msgSender(), tokenId)) { + revert ERC721InsufficientApproval(_msgSender(), tokenId); + } _transfer(from, to, tokenId); } @@ -163,7 +170,9 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata { * @dev See {IERC721-safeTransferFrom}. */ function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) public virtual { - require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner or approved"); + if (!_isApprovedOrOwner(_msgSender(), tokenId)) { + revert ERC721InsufficientApproval(_msgSender(), tokenId); + } _safeTransfer(from, to, tokenId, data); } @@ -187,7 +196,9 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata { */ function _safeTransfer(address from, address to, uint256 tokenId, bytes memory data) internal virtual { _transfer(from, to, tokenId); - require(_checkOnERC721Received(from, to, tokenId, data), "ERC721: transfer to non ERC721Receiver implementer"); + if (!_checkOnERC721Received(from, to, tokenId, data)) { + revert ERC721InvalidReceiver(to); + } } /** @@ -241,10 +252,9 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata { */ function _safeMint(address to, uint256 tokenId, bytes memory data) internal virtual { _mint(to, tokenId); - require( - _checkOnERC721Received(address(0), to, tokenId, data), - "ERC721: transfer to non ERC721Receiver implementer" - ); + if (!_checkOnERC721Received(address(0), to, tokenId, data)) { + revert ERC721InvalidReceiver(to); + } } /** @@ -260,13 +270,19 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata { * Emits a {Transfer} event. */ function _mint(address to, uint256 tokenId) internal virtual { - require(to != address(0), "ERC721: mint to the zero address"); - require(!_exists(tokenId), "ERC721: token already minted"); + if (to == address(0)) { + revert ERC721InvalidReceiver(address(0)); + } + if (_exists(tokenId)) { + revert ERC721InvalidSender(address(0)); + } _beforeTokenTransfer(address(0), to, tokenId, 1); // Check that tokenId was not minted by `_beforeTokenTransfer` hook - require(!_exists(tokenId), "ERC721: token already minted"); + if (_exists(tokenId)) { + revert ERC721InvalidSender(address(0)); + } unchecked { // Will not overflow unless all 2**256 token ids are minted to the same owner. @@ -328,13 +344,21 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata { * Emits a {Transfer} event. */ function _transfer(address from, address to, uint256 tokenId) internal virtual { - require(ownerOf(tokenId) == from, "ERC721: transfer from incorrect owner"); - require(to != address(0), "ERC721: transfer to the zero address"); + address owner = ownerOf(tokenId); + if (owner != from) { + revert ERC721IncorrectOwner(from, tokenId, owner); + } + if (to == address(0)) { + revert ERC721InvalidReceiver(address(0)); + } _beforeTokenTransfer(from, to, tokenId, 1); // Check that tokenId was not transferred by `_beforeTokenTransfer` hook - require(ownerOf(tokenId) == from, "ERC721: transfer from incorrect owner"); + owner = ownerOf(tokenId); + if (owner != from) { + revert ERC721IncorrectOwner(from, tokenId, owner); + } // Clear approvals from the previous owner delete _tokenApprovals[tokenId]; @@ -372,7 +396,9 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata { * Emits an {ApprovalForAll} event. */ function _setApprovalForAll(address owner, address operator, bool approved) internal virtual { - require(owner != operator, "ERC721: approve to caller"); + if (owner == operator) { + revert ERC721InvalidOperator(owner); + } _operatorApprovals[owner][operator] = approved; emit ApprovalForAll(owner, operator, approved); } @@ -381,7 +407,9 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata { * @dev Reverts if the `tokenId` has not been minted yet. */ function _requireMinted(uint256 tokenId) internal view virtual { - require(_exists(tokenId), "ERC721: invalid token ID"); + if (!_exists(tokenId)) { + revert ERC721NonexistentToken(tokenId); + } } /** @@ -405,7 +433,7 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata { return retval == IERC721Receiver.onERC721Received.selector; } catch (bytes memory reason) { if (reason.length == 0) { - revert("ERC721: transfer to non ERC721Receiver implementer"); + revert ERC721InvalidReceiver(to); } else { /// @solidity memory-safe-assembly assembly { diff --git a/contracts/token/ERC721/extensions/ERC721Burnable.sol b/contracts/token/ERC721/extensions/ERC721Burnable.sol index 5489169e8..217f039ca 100644 --- a/contracts/token/ERC721/extensions/ERC721Burnable.sol +++ b/contracts/token/ERC721/extensions/ERC721Burnable.sol @@ -19,8 +19,9 @@ abstract contract ERC721Burnable is Context, ERC721 { * - The caller must own `tokenId` or be an approved operator. */ function burn(uint256 tokenId) public virtual { - //solhint-disable-next-line max-line-length - require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner or approved"); + if (!_isApprovedOrOwner(_msgSender(), tokenId)) { + revert ERC721InsufficientApproval(_msgSender(), tokenId); + } _burn(tokenId); } } diff --git a/contracts/token/ERC721/extensions/ERC721Consecutive.sol b/contracts/token/ERC721/extensions/ERC721Consecutive.sol index b7295e476..f1308cdab 100644 --- a/contracts/token/ERC721/extensions/ERC721Consecutive.sol +++ b/contracts/token/ERC721/extensions/ERC721Consecutive.sol @@ -36,6 +36,28 @@ abstract contract ERC721Consecutive is IERC2309, ERC721 { Checkpoints.Trace160 private _sequentialOwnership; BitMaps.BitMap private _sequentialBurn; + /** + * @dev Batch mint is restricted to the constructor. + * Any batch mint not emitting the {IERC721-Transfer} event outside of the constructor + * is non-ERC721 compliant. + */ + error ERC721ForbiddenBatchMint(); + + /** + * @dev Exceeds the max amount of mints per batch. + */ + error ERC721ExceededMaxBatchMint(uint256 batchSize, uint256 maxBatch); + + /** + * @dev Individual minting is not allowed. + */ + error ERC721ForbiddenMint(); + + /** + * @dev Batch burn is not supported. + */ + error ERC721ForbiddenBatchBurn(); + /** * @dev Maximum size of a batch of consecutive tokens. This is designed to limit stress on off-chain indexing * services that have to record one entry per token, and have protections against "unreasonably large" batches of @@ -86,9 +108,17 @@ abstract contract ERC721Consecutive is IERC2309, ERC721 { // minting a batch of size 0 is a no-op if (batchSize > 0) { - require(address(this).code.length == 0, "ERC721Consecutive: batch minting restricted to constructor"); - require(to != address(0), "ERC721Consecutive: mint to the zero address"); - require(batchSize <= _maxBatchSize(), "ERC721Consecutive: batch too large"); + if (address(this).code.length > 0) { + revert ERC721ForbiddenBatchMint(); + } + if (to == address(0)) { + revert ERC721InvalidReceiver(address(0)); + } + + uint256 maxBatchSize = _maxBatchSize(); + if (batchSize > maxBatchSize) { + revert ERC721ExceededMaxBatchMint(batchSize, maxBatchSize); + } // hook before _beforeTokenTransfer(address(0), to, next, batchSize); @@ -117,7 +147,9 @@ abstract contract ERC721Consecutive is IERC2309, ERC721 { * After construction, {_mintConsecutive} is no longer available and {_mint} becomes available. */ function _mint(address to, uint256 tokenId) internal virtual override { - require(address(this).code.length > 0, "ERC721Consecutive: can't mint during construction"); + if (address(this).code.length == 0) { + revert ERC721ForbiddenMint(); + } super._mint(to, tokenId); } @@ -137,7 +169,9 @@ abstract contract ERC721Consecutive is IERC2309, ERC721 { !_sequentialBurn.get(firstTokenId) ) // and the token was never marked as burnt { - require(batchSize == 1, "ERC721Consecutive: batch burn not supported"); + if (batchSize != 1) { + revert ERC721ForbiddenBatchBurn(); + } _sequentialBurn.set(firstTokenId); } super._afterTokenTransfer(from, to, firstTokenId, batchSize); diff --git a/contracts/token/ERC721/extensions/ERC721Enumerable.sol b/contracts/token/ERC721/extensions/ERC721Enumerable.sol index 8cea9e19a..18e2ba5d6 100644 --- a/contracts/token/ERC721/extensions/ERC721Enumerable.sol +++ b/contracts/token/ERC721/extensions/ERC721Enumerable.sol @@ -26,6 +26,18 @@ abstract contract ERC721Enumerable is ERC721, IERC721Enumerable { // Mapping from token id to position in the allTokens array mapping(uint256 => uint256) private _allTokensIndex; + /** + * @dev An `owner`'s token query was out of bounds for `index`. + * + * NOTE: The owner being `address(0)` indicates a global out of bounds index. + */ + error ERC721OutOfBoundsIndex(address owner, uint256 index); + + /** + * @dev Batch mint is not allowed. + */ + error ERC721EnumerableForbiddenBatchMint(); + /** * @dev See {IERC165-supportsInterface}. */ @@ -37,7 +49,9 @@ abstract contract ERC721Enumerable is ERC721, IERC721Enumerable { * @dev See {IERC721Enumerable-tokenOfOwnerByIndex}. */ function tokenOfOwnerByIndex(address owner, uint256 index) public view virtual returns (uint256) { - require(index < balanceOf(owner), "ERC721Enumerable: owner index out of bounds"); + if (index >= balanceOf(owner)) { + revert ERC721OutOfBoundsIndex(owner, index); + } return _ownedTokens[owner][index]; } @@ -52,7 +66,9 @@ abstract contract ERC721Enumerable is ERC721, IERC721Enumerable { * @dev See {IERC721Enumerable-tokenByIndex}. */ function tokenByIndex(uint256 index) public view virtual returns (uint256) { - require(index < totalSupply(), "ERC721Enumerable: global index out of bounds"); + if (index >= totalSupply()) { + revert ERC721OutOfBoundsIndex(address(0), index); + } return _allTokens[index]; } @@ -69,7 +85,7 @@ abstract contract ERC721Enumerable is ERC721, IERC721Enumerable { if (batchSize > 1) { // Will only trigger during construction. Batch transferring (minting) is not available afterwards. - revert("ERC721Enumerable: consecutive transfers not supported"); + revert ERC721EnumerableForbiddenBatchMint(); } uint256 tokenId = firstTokenId; diff --git a/contracts/token/ERC721/extensions/ERC721Pausable.sol b/contracts/token/ERC721/extensions/ERC721Pausable.sol index 0cadaa7c7..a9472c5dc 100644 --- a/contracts/token/ERC721/extensions/ERC721Pausable.sol +++ b/contracts/token/ERC721/extensions/ERC721Pausable.sol @@ -35,6 +35,6 @@ abstract contract ERC721Pausable is ERC721, Pausable { ) internal virtual override { super._beforeTokenTransfer(from, to, firstTokenId, batchSize); - require(!paused(), "ERC721Pausable: token transfer while paused"); + _requireNotPaused(); } } diff --git a/contracts/token/ERC721/extensions/ERC721URIStorage.sol b/contracts/token/ERC721/extensions/ERC721URIStorage.sol index 6350a0952..ae058122d 100644 --- a/contracts/token/ERC721/extensions/ERC721URIStorage.sol +++ b/contracts/token/ERC721/extensions/ERC721URIStorage.sol @@ -53,7 +53,9 @@ abstract contract ERC721URIStorage is IERC4906, ERC721 { * - `tokenId` must exist. */ function _setTokenURI(uint256 tokenId, string memory _tokenURI) internal virtual { - require(_exists(tokenId), "ERC721URIStorage: URI set of nonexistent token"); + if (!_exists(tokenId)) { + revert ERC721NonexistentToken(tokenId); + } _tokenURIs[tokenId] = _tokenURI; emit MetadataUpdate(tokenId); diff --git a/contracts/token/ERC721/extensions/ERC721Wrapper.sol b/contracts/token/ERC721/extensions/ERC721Wrapper.sol index 9226349f4..47a42c1f0 100644 --- a/contracts/token/ERC721/extensions/ERC721Wrapper.sol +++ b/contracts/token/ERC721/extensions/ERC721Wrapper.sol @@ -17,6 +17,11 @@ import "../ERC721.sol"; abstract contract ERC721Wrapper is ERC721, IERC721Receiver { IERC721 private immutable _underlying; + /** + * @dev The received ERC721 token couldn't be wrapped. + */ + error ERC721UnsupportedToken(address token); + constructor(IERC721 underlyingToken) { _underlying = underlyingToken; } @@ -46,7 +51,9 @@ abstract contract ERC721Wrapper is ERC721, IERC721Receiver { uint256 length = tokenIds.length; for (uint256 i = 0; i < length; ++i) { uint256 tokenId = tokenIds[i]; - require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721Wrapper: caller is not token owner or approved"); + if (!_isApprovedOrOwner(_msgSender(), tokenId)) { + revert ERC721InsufficientApproval(_msgSender(), tokenId); + } _burn(tokenId); // Checks were already performed at this point, and there's no way to retake ownership or approval from // the wrapped tokenId after this point, so it's safe to remove the reentrancy check for the next line. @@ -68,7 +75,9 @@ abstract contract ERC721Wrapper is ERC721, IERC721Receiver { * for recovering in that scenario. */ function onERC721Received(address, address from, uint256 tokenId, bytes memory) public virtual returns (bytes4) { - require(address(underlying()) == _msgSender(), "ERC721Wrapper: caller is not underlying"); + if (address(underlying()) != _msgSender()) { + revert ERC721UnsupportedToken(_msgSender()); + } _safeMint(from, tokenId); return IERC721Receiver.onERC721Received.selector; } @@ -78,7 +87,10 @@ abstract contract ERC721Wrapper is ERC721, IERC721Receiver { * function that can be exposed with access control if desired. */ function _recover(address account, uint256 tokenId) internal virtual returns (uint256) { - require(underlying().ownerOf(tokenId) == address(this), "ERC721Wrapper: wrapper is not token owner"); + address owner = underlying().ownerOf(tokenId); + if (owner != address(this)) { + revert ERC721IncorrectOwner(address(this), tokenId, owner); + } _safeMint(account, tokenId); return tokenId; } diff --git a/contracts/token/common/ERC2981.sol b/contracts/token/common/ERC2981.sol index 85e902733..21869ee25 100644 --- a/contracts/token/common/ERC2981.sol +++ b/contracts/token/common/ERC2981.sol @@ -30,6 +30,26 @@ abstract contract ERC2981 is IERC2981, ERC165 { RoyaltyInfo private _defaultRoyaltyInfo; mapping(uint256 => RoyaltyInfo) private _tokenRoyaltyInfo; + /** + * @dev The default royalty set is invalid (eg. (numerator / denominator) >= 1). + */ + error ERC2981InvalidDefaultRoyalty(uint256 numerator, uint256 denominator); + + /** + * @dev The default royalty receiver is invalid. + */ + error ERC2981InvalidDefaultRoyaltyReceiver(address receiver); + + /** + * @dev The royalty set for an specific `tokenId` is invalid (eg. (numerator / denominator) >= 1). + */ + error ERC2981InvalidTokenRoyalty(uint256 tokenId, uint256 numerator, uint256 denominator); + + /** + * @dev The royalty receiver for `tokenId` is invalid. + */ + error ERC2981InvalidTokenRoyaltyReceiver(uint256 tokenId, address receiver); + /** * @dev See {IERC165-supportsInterface}. */ @@ -70,8 +90,14 @@ abstract contract ERC2981 is IERC2981, ERC165 { * - `feeNumerator` cannot be greater than the fee denominator. */ function _setDefaultRoyalty(address receiver, uint96 feeNumerator) internal virtual { - require(feeNumerator <= _feeDenominator(), "ERC2981: royalty fee will exceed salePrice"); - require(receiver != address(0), "ERC2981: invalid receiver"); + uint256 denominator = _feeDenominator(); + if (feeNumerator > denominator) { + // Royalty fee will exceed the sale price + revert ERC2981InvalidDefaultRoyalty(feeNumerator, denominator); + } + if (receiver == address(0)) { + revert ERC2981InvalidDefaultRoyaltyReceiver(address(0)); + } _defaultRoyaltyInfo = RoyaltyInfo(receiver, feeNumerator); } @@ -92,8 +118,14 @@ abstract contract ERC2981 is IERC2981, ERC165 { * - `feeNumerator` cannot be greater than the fee denominator. */ function _setTokenRoyalty(uint256 tokenId, address receiver, uint96 feeNumerator) internal virtual { - require(feeNumerator <= _feeDenominator(), "ERC2981: royalty fee will exceed salePrice"); - require(receiver != address(0), "ERC2981: Invalid parameters"); + uint256 denominator = _feeDenominator(); + if (feeNumerator > denominator) { + // Royalty fee will exceed the sale price + revert ERC2981InvalidTokenRoyalty(tokenId, feeNumerator, denominator); + } + if (receiver == address(0)) { + revert ERC2981InvalidTokenRoyaltyReceiver(tokenId, address(0)); + } _tokenRoyaltyInfo[tokenId] = RoyaltyInfo(receiver, feeNumerator); } diff --git a/contracts/utils/Address.sol b/contracts/utils/Address.sol index 02f475620..859332b39 100644 --- a/contracts/utils/Address.sol +++ b/contracts/utils/Address.sol @@ -7,6 +7,21 @@ pragma solidity ^0.8.19; * @dev Collection of functions related to the address type */ library Address { + /** + * @dev The ETH balance of the account is not enough to perform the operation. + */ + error AddressInsufficientBalance(address account); + + /** + * @dev There's no code at `target` (it is not a contract). + */ + error AddressEmptyCode(address target); + + /** + * @dev A call to an address target failed. The target may have reverted. + */ + error FailedInnerCall(); + /** * @dev Replacement for Solidity's `transfer`: sends `amount` wei to * `recipient`, forwarding all available gas and reverting on errors. @@ -24,10 +39,14 @@ library Address { * https://solidity.readthedocs.io/en/v0.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. */ function sendValue(address payable recipient, uint256 amount) internal { - require(address(this).balance >= amount, "Address: insufficient balance"); + if (address(this).balance < amount) { + revert AddressInsufficientBalance(address(this)); + } (bool success, ) = recipient.call{value: amount}(""); - require(success, "Address: unable to send value, recipient may have reverted"); + if (!success) { + revert FailedInnerCall(); + } } /** @@ -49,21 +68,25 @@ library Address { * _Available since v3.1._ */ function functionCall(address target, bytes memory data) internal returns (bytes memory) { - return functionCallWithValue(target, data, 0, "Address: low-level call failed"); + return functionCallWithValue(target, data, 0, defaultRevert); } /** - * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with - * `errorMessage` as a fallback revert reason when `target` reverts. + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with a + * `customRevert` function as a fallback when `target` reverts. * - * _Available since v3.1._ + * Requirements: + * + * - `customRevert` must be a reverting function. + * + * _Available since v5.0._ */ function functionCall( address target, bytes memory data, - string memory errorMessage + function() internal view customRevert ) internal returns (bytes memory) { - return functionCallWithValue(target, data, 0, errorMessage); + return functionCallWithValue(target, data, 0, customRevert); } /** @@ -78,24 +101,30 @@ library Address { * _Available since v3.1._ */ function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) { - return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); + return functionCallWithValue(target, data, value, defaultRevert); } /** * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but - * with `errorMessage` as a fallback revert reason when `target` reverts. + * with a `customRevert` function as a fallback revert reason when `target` reverts. * - * _Available since v3.1._ + * Requirements: + * + * - `customRevert` must be a reverting function. + * + * _Available since v5.0._ */ function functionCallWithValue( address target, bytes memory data, uint256 value, - string memory errorMessage + function() internal view customRevert ) internal returns (bytes memory) { - require(address(this).balance >= value, "Address: insufficient balance for call"); + if (address(this).balance < value) { + revert AddressInsufficientBalance(address(this)); + } (bool success, bytes memory returndata) = target.call{value: value}(data); - return verifyCallResultFromTarget(target, success, returndata, errorMessage); + return verifyCallResultFromTarget(target, success, returndata, customRevert); } /** @@ -105,7 +134,7 @@ library Address { * _Available since v3.3._ */ function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { - return functionStaticCall(target, data, "Address: low-level static call failed"); + return functionStaticCall(target, data, defaultRevert); } /** @@ -117,10 +146,10 @@ library Address { function functionStaticCall( address target, bytes memory data, - string memory errorMessage + function() internal view customRevert ) internal view returns (bytes memory) { (bool success, bytes memory returndata) = target.staticcall(data); - return verifyCallResultFromTarget(target, success, returndata, errorMessage); + return verifyCallResultFromTarget(target, success, returndata, customRevert); } /** @@ -130,7 +159,7 @@ library Address { * _Available since v3.4._ */ function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { - return functionDelegateCall(target, data, "Address: low-level delegate call failed"); + return functionDelegateCall(target, data, defaultRevert); } /** @@ -142,55 +171,78 @@ library Address { function functionDelegateCall( address target, bytes memory data, - string memory errorMessage + function() internal view customRevert ) internal returns (bytes memory) { (bool success, bytes memory returndata) = target.delegatecall(data); - return verifyCallResultFromTarget(target, success, returndata, errorMessage); + return verifyCallResultFromTarget(target, success, returndata, customRevert); } /** * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling - * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract. + * the revert reason or using the provided `customRevert`) in case of unsuccessful call or if target was not a contract. * - * _Available since v4.8._ + * _Available since v5.0._ */ function verifyCallResultFromTarget( address target, bool success, bytes memory returndata, - string memory errorMessage + function() internal view customRevert ) internal view returns (bytes memory) { if (success) { if (returndata.length == 0) { // only check if target is a contract if the call was successful and the return data is empty // otherwise we already know that it was a contract - require(target.code.length > 0, "Address: call to non-contract"); + if (target.code.length == 0) { + revert AddressEmptyCode(target); + } } return returndata; } else { - _revert(returndata, errorMessage); + _revert(returndata, customRevert); } } /** * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the - * revert reason or using the provided one. + * revert reason or with a default revert error. + * + * _Available since v5.0._ + */ + function verifyCallResult(bool success, bytes memory returndata) internal view returns (bytes memory) { + return verifyCallResult(success, returndata, defaultRevert); + } + + /** + * @dev Same as {xref-Address-verifyCallResult-bool-bytes-}[`verifyCallResult`], but with a + * `customRevert` function as a fallback when `success` is `false`. * - * _Available since v4.3._ + * Requirements: + * + * - `customRevert` must be a reverting function. + * + * _Available since v5.0._ */ function verifyCallResult( bool success, bytes memory returndata, - string memory errorMessage - ) internal pure returns (bytes memory) { + function() internal view customRevert + ) internal view returns (bytes memory) { if (success) { return returndata; } else { - _revert(returndata, errorMessage); + _revert(returndata, customRevert); } } - function _revert(bytes memory returndata, string memory errorMessage) private pure { + /** + * @dev Default reverting function when no `customRevert` is provided in a function call. + */ + function defaultRevert() internal pure { + revert FailedInnerCall(); + } + + function _revert(bytes memory returndata, function() internal view customRevert) private view { // Look for revert reason and bubble it up if present if (returndata.length > 0) { // The easiest way to bubble the revert reason is using memory via assembly @@ -200,7 +252,8 @@ library Address { revert(add(32, returndata), returndata_size) } } else { - revert(errorMessage); + customRevert(); + revert FailedInnerCall(); } } } diff --git a/contracts/utils/Create2.sol b/contracts/utils/Create2.sol index d5776885f..24d27ea0b 100644 --- a/contracts/utils/Create2.sol +++ b/contracts/utils/Create2.sol @@ -13,6 +13,21 @@ pragma solidity ^0.8.19; * information. */ library Create2 { + /** + * @dev Not enough balance for performing a CREATE2 deploy. + */ + error Create2InsufficientBalance(uint256 balance, uint256 needed); + + /** + * @dev There's no code to deploy. + */ + error Create2EmptyBytecode(); + + /** + * @dev The deployment failed. + */ + error Create2FailedDeployment(); + /** * @dev Deploys a contract using `CREATE2`. The address where the contract * will be deployed can be known in advance via {computeAddress}. @@ -28,13 +43,19 @@ library Create2 { * - if `amount` is non-zero, `bytecode` must have a `payable` constructor. */ function deploy(uint256 amount, bytes32 salt, bytes memory bytecode) internal returns (address addr) { - require(address(this).balance >= amount, "Create2: insufficient balance"); - require(bytecode.length != 0, "Create2: bytecode length is zero"); + if (address(this).balance < amount) { + revert Create2InsufficientBalance(address(this).balance, amount); + } + if (bytecode.length == 0) { + revert Create2EmptyBytecode(); + } /// @solidity memory-safe-assembly assembly { addr := create2(amount, add(bytecode, 0x20), mload(bytecode), salt) } - require(addr != address(0), "Create2: Failed on deploy"); + if (addr == address(0)) { + revert Create2FailedDeployment(); + } } /** diff --git a/contracts/utils/Nonces.sol b/contracts/utils/Nonces.sol index 04b884797..f8ea1dfd3 100644 --- a/contracts/utils/Nonces.sol +++ b/contracts/utils/Nonces.sol @@ -5,6 +5,11 @@ pragma solidity ^0.8.19; * @dev Provides tracking nonces for addresses. Nonces will only increment. */ abstract contract Nonces { + /** + * @dev The nonce used for an `account` is not the expected current nonce. + */ + error InvalidAccountNonce(address account, uint256 currentNonce); + mapping(address => uint256) private _nonces; /** @@ -27,4 +32,15 @@ abstract contract Nonces { return _nonces[owner]++; } } + + /** + * @dev Same as {_useNonce} but checking that `nonce` is the next valid for `owner`. + */ + function _useCheckedNonce(address owner, uint256 nonce) internal virtual returns (uint256) { + uint256 current = _useNonce(owner); + if (nonce != current) { + revert InvalidAccountNonce(owner, current); + } + return current; + } } diff --git a/contracts/utils/StorageSlot.sol b/contracts/utils/StorageSlot.sol index f12640e7b..b0e918967 100644 --- a/contracts/utils/StorageSlot.sol +++ b/contracts/utils/StorageSlot.sol @@ -22,7 +22,7 @@ pragma solidity ^0.8.19; * } * * function _setImplementation(address newImplementation) internal { - * require(newImplementation.code.length > 0, "ERC1967: new implementation is not a contract"); + * require(newImplementation.code.length > 0); * StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation; * } * } diff --git a/contracts/utils/Strings.sol b/contracts/utils/Strings.sol index 050f6f9ca..65c8c8753 100644 --- a/contracts/utils/Strings.sol +++ b/contracts/utils/Strings.sol @@ -13,6 +13,11 @@ library Strings { bytes16 private constant _SYMBOLS = "0123456789abcdef"; uint8 private constant _ADDRESS_LENGTH = 20; + /** + * @dev The `value` string doesn't fit in the specified `length`. + */ + error StringsInsufficientHexLength(uint256 value, uint256 length); + /** * @dev Converts a `uint256` to its ASCII `string` decimal representation. */ @@ -58,14 +63,17 @@ library Strings { * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length. */ function toHexString(uint256 value, uint256 length) internal pure returns (string memory) { + uint256 localValue = value; bytes memory buffer = new bytes(2 * length + 2); buffer[0] = "0"; buffer[1] = "x"; for (uint256 i = 2 * length + 1; i > 1; --i) { - buffer[i] = _SYMBOLS[value & 0xf]; - value >>= 4; + buffer[i] = _SYMBOLS[localValue & 0xf]; + localValue >>= 4; + } + if (localValue != 0) { + revert StringsInsufficientHexLength(value, length); } - require(value == 0, "Strings: hex length insufficient"); return string(buffer); } diff --git a/contracts/utils/cryptography/ECDSA.sol b/contracts/utils/cryptography/ECDSA.sol index b907abfc0..b8f1affee 100644 --- a/contracts/utils/cryptography/ECDSA.sol +++ b/contracts/utils/cryptography/ECDSA.sol @@ -19,15 +19,30 @@ library ECDSA { InvalidSignatureS } - function _throwError(RecoverError error) private pure { + /** + * @dev The signature derives the `address(0)`. + */ + error ECDSAInvalidSignature(); + + /** + * @dev The signature has an invalid length. + */ + error ECDSAInvalidSignatureLength(uint256 length); + + /** + * @dev The signature has an S value that is in the upper half order. + */ + error ECDSAInvalidSignatureS(bytes32 s); + + function _throwError(RecoverError error, bytes32 errorArg) private pure { if (error == RecoverError.NoError) { return; // no error: do nothing } else if (error == RecoverError.InvalidSignature) { - revert("ECDSA: invalid signature"); + revert ECDSAInvalidSignature(); } else if (error == RecoverError.InvalidSignatureLength) { - revert("ECDSA: invalid signature length"); + revert ECDSAInvalidSignatureLength(uint256(errorArg)); } else if (error == RecoverError.InvalidSignatureS) { - revert("ECDSA: invalid signature 's' value"); + revert ECDSAInvalidSignatureS(errorArg); } } @@ -51,7 +66,7 @@ library ECDSA { * * _Available since v4.3._ */ - function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError) { + function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError, bytes32) { if (signature.length == 65) { bytes32 r; bytes32 s; @@ -66,7 +81,7 @@ library ECDSA { } return tryRecover(hash, v, r, s); } else { - return (address(0), RecoverError.InvalidSignatureLength); + return (address(0), RecoverError.InvalidSignatureLength, bytes32(signature.length)); } } @@ -85,8 +100,8 @@ library ECDSA { * be too long), and then calling {toEthSignedMessageHash} on it. */ function recover(bytes32 hash, bytes memory signature) internal pure returns (address) { - (address recovered, RecoverError error) = tryRecover(hash, signature); - _throwError(error); + (address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, signature); + _throwError(error, errorArg); return recovered; } @@ -97,7 +112,7 @@ library ECDSA { * * _Available since v4.3._ */ - function tryRecover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address, RecoverError) { + function tryRecover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address, RecoverError, bytes32) { unchecked { bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff); // We do not check for an overflow here since the shift operation results in 0 or 1. @@ -112,8 +127,8 @@ library ECDSA { * _Available since v4.2._ */ function recover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address) { - (address recovered, RecoverError error) = tryRecover(hash, r, vs); - _throwError(error); + (address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, r, vs); + _throwError(error, errorArg); return recovered; } @@ -123,7 +138,12 @@ library ECDSA { * * _Available since v4.3._ */ - function tryRecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address, RecoverError) { + function tryRecover( + bytes32 hash, + uint8 v, + bytes32 r, + bytes32 s + ) internal pure returns (address, RecoverError, bytes32) { // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most @@ -134,16 +154,16 @@ library ECDSA { // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept // these malleable signatures as well. if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) { - return (address(0), RecoverError.InvalidSignatureS); + return (address(0), RecoverError.InvalidSignatureS, s); } // If the signature is valid (and not malleable), return the signer address address signer = ecrecover(hash, v, r, s); if (signer == address(0)) { - return (address(0), RecoverError.InvalidSignature); + return (address(0), RecoverError.InvalidSignature, bytes32(0)); } - return (signer, RecoverError.NoError); + return (signer, RecoverError.NoError, bytes32(0)); } /** @@ -151,8 +171,8 @@ library ECDSA { * `r` and `s` signature fields separately. */ function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) { - (address recovered, RecoverError error) = tryRecover(hash, v, r, s); - _throwError(error); + (address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, v, r, s); + _throwError(error, errorArg); return recovered; } diff --git a/contracts/utils/cryptography/MerkleProof.sol b/contracts/utils/cryptography/MerkleProof.sol index 0bcdda2cd..ed88ea1db 100644 --- a/contracts/utils/cryptography/MerkleProof.sol +++ b/contracts/utils/cryptography/MerkleProof.sol @@ -18,6 +18,11 @@ pragma solidity ^0.8.19; * against this attack out of the box. */ library MerkleProof { + /** + *@dev The multiproof provided is not valid. + */ + error MerkleProofInvalidMultiproof(); + /** * @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree * defined by `root`. For this, a `proof` must be provided, containing @@ -124,7 +129,9 @@ library MerkleProof { uint256 totalHashes = proofFlags.length; // Check proof validity. - require(leavesLen + proof.length - 1 == totalHashes, "MerkleProof: invalid multiproof"); + if (leavesLen + proof.length - 1 != totalHashes) { + revert MerkleProofInvalidMultiproof(); + } // The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using // `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop". @@ -176,7 +183,9 @@ library MerkleProof { uint256 totalHashes = proofFlags.length; // Check proof validity. - require(leavesLen + proof.length - 1 == totalHashes, "MerkleProof: invalid multiproof"); + if (leavesLen + proof.length - 1 != totalHashes) { + revert MerkleProofInvalidMultiproof(); + } // The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using // `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop". diff --git a/contracts/utils/cryptography/SignatureChecker.sol b/contracts/utils/cryptography/SignatureChecker.sol index 941f7538f..25fdee5b3 100644 --- a/contracts/utils/cryptography/SignatureChecker.sol +++ b/contracts/utils/cryptography/SignatureChecker.sol @@ -22,7 +22,7 @@ library SignatureChecker { * change through time. It could return true at block N and false at block N+1 (or the opposite). */ function isValidSignatureNow(address signer, bytes32 hash, bytes memory signature) internal view returns (bool) { - (address recovered, ECDSA.RecoverError error) = ECDSA.tryRecover(hash, signature); + (address recovered, ECDSA.RecoverError error, ) = ECDSA.tryRecover(hash, signature); return (error == ECDSA.RecoverError.NoError && recovered == signer) || isValidERC1271SignatureNow(signer, hash, signature); diff --git a/contracts/utils/math/Math.sol b/contracts/utils/math/Math.sol index ff3dc8d9f..a2fc7ceb7 100644 --- a/contracts/utils/math/Math.sol +++ b/contracts/utils/math/Math.sol @@ -7,6 +7,11 @@ pragma solidity ^0.8.19; * @dev Standard math utilities missing in the Solidity language. */ library Math { + /** + * @dev Muldiv operation overflow. + */ + error MathOverflowedMulDiv(); + enum Rounding { Down, // Toward negative infinity Up, // Toward infinity @@ -140,7 +145,9 @@ library Math { } // Make sure the result is less than 2^256. Also prevents denominator == 0. - require(denominator > prod1, "Math: mulDiv overflow"); + if (denominator <= prod1) { + revert MathOverflowedMulDiv(); + } /////////////////////////////////////////////// // 512 by 256 division. diff --git a/contracts/utils/math/SafeCast.sol b/contracts/utils/math/SafeCast.sol index d9e21bb17..d3b86b088 100644 --- a/contracts/utils/math/SafeCast.sol +++ b/contracts/utils/math/SafeCast.sol @@ -17,6 +17,26 @@ pragma solidity ^0.8.19; * class of bugs, so it's recommended to use it always. */ library SafeCast { + /** + * @dev Value doesn't fit in an uint of `bits` size. + */ + error SafeCastOverflowedUintDowncast(uint8 bits, uint256 value); + + /** + * @dev An int value doesn't fit in an uint of `bits` size. + */ + error SafeCastOverflowedIntToUint(int256 value); + + /** + * @dev Value doesn't fit in an int of `bits` size. + */ + error SafeCastOverflowedIntDowncast(uint8 bits, int256 value); + + /** + * @dev An uint value doesn't fit in an int of `bits` size. + */ + error SafeCastOverflowedUintToInt(uint256 value); + /** * @dev Returns the downcasted uint248 from uint256, reverting on * overflow (when the input is greater than largest uint248). @@ -30,7 +50,9 @@ library SafeCast { * _Available since v4.7._ */ function toUint248(uint256 value) internal pure returns (uint248) { - require(value <= type(uint248).max, "SafeCast: value doesn't fit in 248 bits"); + if (value > type(uint248).max) { + revert SafeCastOverflowedUintDowncast(248, value); + } return uint248(value); } @@ -47,7 +69,9 @@ library SafeCast { * _Available since v4.7._ */ function toUint240(uint256 value) internal pure returns (uint240) { - require(value <= type(uint240).max, "SafeCast: value doesn't fit in 240 bits"); + if (value > type(uint240).max) { + revert SafeCastOverflowedUintDowncast(240, value); + } return uint240(value); } @@ -64,7 +88,9 @@ library SafeCast { * _Available since v4.7._ */ function toUint232(uint256 value) internal pure returns (uint232) { - require(value <= type(uint232).max, "SafeCast: value doesn't fit in 232 bits"); + if (value > type(uint232).max) { + revert SafeCastOverflowedUintDowncast(232, value); + } return uint232(value); } @@ -81,7 +107,9 @@ library SafeCast { * _Available since v4.2._ */ function toUint224(uint256 value) internal pure returns (uint224) { - require(value <= type(uint224).max, "SafeCast: value doesn't fit in 224 bits"); + if (value > type(uint224).max) { + revert SafeCastOverflowedUintDowncast(224, value); + } return uint224(value); } @@ -98,7 +126,9 @@ library SafeCast { * _Available since v4.7._ */ function toUint216(uint256 value) internal pure returns (uint216) { - require(value <= type(uint216).max, "SafeCast: value doesn't fit in 216 bits"); + if (value > type(uint216).max) { + revert SafeCastOverflowedUintDowncast(216, value); + } return uint216(value); } @@ -115,7 +145,9 @@ library SafeCast { * _Available since v4.7._ */ function toUint208(uint256 value) internal pure returns (uint208) { - require(value <= type(uint208).max, "SafeCast: value doesn't fit in 208 bits"); + if (value > type(uint208).max) { + revert SafeCastOverflowedUintDowncast(208, value); + } return uint208(value); } @@ -132,7 +164,9 @@ library SafeCast { * _Available since v4.7._ */ function toUint200(uint256 value) internal pure returns (uint200) { - require(value <= type(uint200).max, "SafeCast: value doesn't fit in 200 bits"); + if (value > type(uint200).max) { + revert SafeCastOverflowedUintDowncast(200, value); + } return uint200(value); } @@ -149,7 +183,9 @@ library SafeCast { * _Available since v4.7._ */ function toUint192(uint256 value) internal pure returns (uint192) { - require(value <= type(uint192).max, "SafeCast: value doesn't fit in 192 bits"); + if (value > type(uint192).max) { + revert SafeCastOverflowedUintDowncast(192, value); + } return uint192(value); } @@ -166,7 +202,9 @@ library SafeCast { * _Available since v4.7._ */ function toUint184(uint256 value) internal pure returns (uint184) { - require(value <= type(uint184).max, "SafeCast: value doesn't fit in 184 bits"); + if (value > type(uint184).max) { + revert SafeCastOverflowedUintDowncast(184, value); + } return uint184(value); } @@ -183,7 +221,9 @@ library SafeCast { * _Available since v4.7._ */ function toUint176(uint256 value) internal pure returns (uint176) { - require(value <= type(uint176).max, "SafeCast: value doesn't fit in 176 bits"); + if (value > type(uint176).max) { + revert SafeCastOverflowedUintDowncast(176, value); + } return uint176(value); } @@ -200,7 +240,9 @@ library SafeCast { * _Available since v4.7._ */ function toUint168(uint256 value) internal pure returns (uint168) { - require(value <= type(uint168).max, "SafeCast: value doesn't fit in 168 bits"); + if (value > type(uint168).max) { + revert SafeCastOverflowedUintDowncast(168, value); + } return uint168(value); } @@ -217,7 +259,9 @@ library SafeCast { * _Available since v4.7._ */ function toUint160(uint256 value) internal pure returns (uint160) { - require(value <= type(uint160).max, "SafeCast: value doesn't fit in 160 bits"); + if (value > type(uint160).max) { + revert SafeCastOverflowedUintDowncast(160, value); + } return uint160(value); } @@ -234,7 +278,9 @@ library SafeCast { * _Available since v4.7._ */ function toUint152(uint256 value) internal pure returns (uint152) { - require(value <= type(uint152).max, "SafeCast: value doesn't fit in 152 bits"); + if (value > type(uint152).max) { + revert SafeCastOverflowedUintDowncast(152, value); + } return uint152(value); } @@ -251,7 +297,9 @@ library SafeCast { * _Available since v4.7._ */ function toUint144(uint256 value) internal pure returns (uint144) { - require(value <= type(uint144).max, "SafeCast: value doesn't fit in 144 bits"); + if (value > type(uint144).max) { + revert SafeCastOverflowedUintDowncast(144, value); + } return uint144(value); } @@ -268,7 +316,9 @@ library SafeCast { * _Available since v4.7._ */ function toUint136(uint256 value) internal pure returns (uint136) { - require(value <= type(uint136).max, "SafeCast: value doesn't fit in 136 bits"); + if (value > type(uint136).max) { + revert SafeCastOverflowedUintDowncast(136, value); + } return uint136(value); } @@ -285,7 +335,9 @@ library SafeCast { * _Available since v2.5._ */ function toUint128(uint256 value) internal pure returns (uint128) { - require(value <= type(uint128).max, "SafeCast: value doesn't fit in 128 bits"); + if (value > type(uint128).max) { + revert SafeCastOverflowedUintDowncast(128, value); + } return uint128(value); } @@ -302,7 +354,9 @@ library SafeCast { * _Available since v4.7._ */ function toUint120(uint256 value) internal pure returns (uint120) { - require(value <= type(uint120).max, "SafeCast: value doesn't fit in 120 bits"); + if (value > type(uint120).max) { + revert SafeCastOverflowedUintDowncast(120, value); + } return uint120(value); } @@ -319,7 +373,9 @@ library SafeCast { * _Available since v4.7._ */ function toUint112(uint256 value) internal pure returns (uint112) { - require(value <= type(uint112).max, "SafeCast: value doesn't fit in 112 bits"); + if (value > type(uint112).max) { + revert SafeCastOverflowedUintDowncast(112, value); + } return uint112(value); } @@ -336,7 +392,9 @@ library SafeCast { * _Available since v4.7._ */ function toUint104(uint256 value) internal pure returns (uint104) { - require(value <= type(uint104).max, "SafeCast: value doesn't fit in 104 bits"); + if (value > type(uint104).max) { + revert SafeCastOverflowedUintDowncast(104, value); + } return uint104(value); } @@ -353,7 +411,9 @@ library SafeCast { * _Available since v4.2._ */ function toUint96(uint256 value) internal pure returns (uint96) { - require(value <= type(uint96).max, "SafeCast: value doesn't fit in 96 bits"); + if (value > type(uint96).max) { + revert SafeCastOverflowedUintDowncast(96, value); + } return uint96(value); } @@ -370,7 +430,9 @@ library SafeCast { * _Available since v4.7._ */ function toUint88(uint256 value) internal pure returns (uint88) { - require(value <= type(uint88).max, "SafeCast: value doesn't fit in 88 bits"); + if (value > type(uint88).max) { + revert SafeCastOverflowedUintDowncast(88, value); + } return uint88(value); } @@ -387,7 +449,9 @@ library SafeCast { * _Available since v4.7._ */ function toUint80(uint256 value) internal pure returns (uint80) { - require(value <= type(uint80).max, "SafeCast: value doesn't fit in 80 bits"); + if (value > type(uint80).max) { + revert SafeCastOverflowedUintDowncast(80, value); + } return uint80(value); } @@ -404,7 +468,9 @@ library SafeCast { * _Available since v4.7._ */ function toUint72(uint256 value) internal pure returns (uint72) { - require(value <= type(uint72).max, "SafeCast: value doesn't fit in 72 bits"); + if (value > type(uint72).max) { + revert SafeCastOverflowedUintDowncast(72, value); + } return uint72(value); } @@ -421,7 +487,9 @@ library SafeCast { * _Available since v2.5._ */ function toUint64(uint256 value) internal pure returns (uint64) { - require(value <= type(uint64).max, "SafeCast: value doesn't fit in 64 bits"); + if (value > type(uint64).max) { + revert SafeCastOverflowedUintDowncast(64, value); + } return uint64(value); } @@ -438,7 +506,9 @@ library SafeCast { * _Available since v4.7._ */ function toUint56(uint256 value) internal pure returns (uint56) { - require(value <= type(uint56).max, "SafeCast: value doesn't fit in 56 bits"); + if (value > type(uint56).max) { + revert SafeCastOverflowedUintDowncast(56, value); + } return uint56(value); } @@ -455,7 +525,9 @@ library SafeCast { * _Available since v4.7._ */ function toUint48(uint256 value) internal pure returns (uint48) { - require(value <= type(uint48).max, "SafeCast: value doesn't fit in 48 bits"); + if (value > type(uint48).max) { + revert SafeCastOverflowedUintDowncast(48, value); + } return uint48(value); } @@ -472,7 +544,9 @@ library SafeCast { * _Available since v4.7._ */ function toUint40(uint256 value) internal pure returns (uint40) { - require(value <= type(uint40).max, "SafeCast: value doesn't fit in 40 bits"); + if (value > type(uint40).max) { + revert SafeCastOverflowedUintDowncast(40, value); + } return uint40(value); } @@ -489,7 +563,9 @@ library SafeCast { * _Available since v2.5._ */ function toUint32(uint256 value) internal pure returns (uint32) { - require(value <= type(uint32).max, "SafeCast: value doesn't fit in 32 bits"); + if (value > type(uint32).max) { + revert SafeCastOverflowedUintDowncast(32, value); + } return uint32(value); } @@ -506,7 +582,9 @@ library SafeCast { * _Available since v4.7._ */ function toUint24(uint256 value) internal pure returns (uint24) { - require(value <= type(uint24).max, "SafeCast: value doesn't fit in 24 bits"); + if (value > type(uint24).max) { + revert SafeCastOverflowedUintDowncast(24, value); + } return uint24(value); } @@ -523,7 +601,9 @@ library SafeCast { * _Available since v2.5._ */ function toUint16(uint256 value) internal pure returns (uint16) { - require(value <= type(uint16).max, "SafeCast: value doesn't fit in 16 bits"); + if (value > type(uint16).max) { + revert SafeCastOverflowedUintDowncast(16, value); + } return uint16(value); } @@ -540,7 +620,9 @@ library SafeCast { * _Available since v2.5._ */ function toUint8(uint256 value) internal pure returns (uint8) { - require(value <= type(uint8).max, "SafeCast: value doesn't fit in 8 bits"); + if (value > type(uint8).max) { + revert SafeCastOverflowedUintDowncast(8, value); + } return uint8(value); } @@ -554,7 +636,9 @@ library SafeCast { * _Available since v3.0._ */ function toUint256(int256 value) internal pure returns (uint256) { - require(value >= 0, "SafeCast: value must be positive"); + if (value < 0) { + revert SafeCastOverflowedIntToUint(value); + } return uint256(value); } @@ -573,7 +657,9 @@ library SafeCast { */ function toInt248(int256 value) internal pure returns (int248 downcasted) { downcasted = int248(value); - require(downcasted == value, "SafeCast: value doesn't fit in 248 bits"); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(248, value); + } } /** @@ -591,7 +677,9 @@ library SafeCast { */ function toInt240(int256 value) internal pure returns (int240 downcasted) { downcasted = int240(value); - require(downcasted == value, "SafeCast: value doesn't fit in 240 bits"); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(240, value); + } } /** @@ -609,7 +697,9 @@ library SafeCast { */ function toInt232(int256 value) internal pure returns (int232 downcasted) { downcasted = int232(value); - require(downcasted == value, "SafeCast: value doesn't fit in 232 bits"); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(232, value); + } } /** @@ -627,7 +717,9 @@ library SafeCast { */ function toInt224(int256 value) internal pure returns (int224 downcasted) { downcasted = int224(value); - require(downcasted == value, "SafeCast: value doesn't fit in 224 bits"); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(224, value); + } } /** @@ -645,7 +737,9 @@ library SafeCast { */ function toInt216(int256 value) internal pure returns (int216 downcasted) { downcasted = int216(value); - require(downcasted == value, "SafeCast: value doesn't fit in 216 bits"); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(216, value); + } } /** @@ -663,7 +757,9 @@ library SafeCast { */ function toInt208(int256 value) internal pure returns (int208 downcasted) { downcasted = int208(value); - require(downcasted == value, "SafeCast: value doesn't fit in 208 bits"); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(208, value); + } } /** @@ -681,7 +777,9 @@ library SafeCast { */ function toInt200(int256 value) internal pure returns (int200 downcasted) { downcasted = int200(value); - require(downcasted == value, "SafeCast: value doesn't fit in 200 bits"); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(200, value); + } } /** @@ -699,7 +797,9 @@ library SafeCast { */ function toInt192(int256 value) internal pure returns (int192 downcasted) { downcasted = int192(value); - require(downcasted == value, "SafeCast: value doesn't fit in 192 bits"); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(192, value); + } } /** @@ -717,7 +817,9 @@ library SafeCast { */ function toInt184(int256 value) internal pure returns (int184 downcasted) { downcasted = int184(value); - require(downcasted == value, "SafeCast: value doesn't fit in 184 bits"); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(184, value); + } } /** @@ -735,7 +837,9 @@ library SafeCast { */ function toInt176(int256 value) internal pure returns (int176 downcasted) { downcasted = int176(value); - require(downcasted == value, "SafeCast: value doesn't fit in 176 bits"); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(176, value); + } } /** @@ -753,7 +857,9 @@ library SafeCast { */ function toInt168(int256 value) internal pure returns (int168 downcasted) { downcasted = int168(value); - require(downcasted == value, "SafeCast: value doesn't fit in 168 bits"); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(168, value); + } } /** @@ -771,7 +877,9 @@ library SafeCast { */ function toInt160(int256 value) internal pure returns (int160 downcasted) { downcasted = int160(value); - require(downcasted == value, "SafeCast: value doesn't fit in 160 bits"); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(160, value); + } } /** @@ -789,7 +897,9 @@ library SafeCast { */ function toInt152(int256 value) internal pure returns (int152 downcasted) { downcasted = int152(value); - require(downcasted == value, "SafeCast: value doesn't fit in 152 bits"); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(152, value); + } } /** @@ -807,7 +917,9 @@ library SafeCast { */ function toInt144(int256 value) internal pure returns (int144 downcasted) { downcasted = int144(value); - require(downcasted == value, "SafeCast: value doesn't fit in 144 bits"); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(144, value); + } } /** @@ -825,7 +937,9 @@ library SafeCast { */ function toInt136(int256 value) internal pure returns (int136 downcasted) { downcasted = int136(value); - require(downcasted == value, "SafeCast: value doesn't fit in 136 bits"); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(136, value); + } } /** @@ -843,7 +957,9 @@ library SafeCast { */ function toInt128(int256 value) internal pure returns (int128 downcasted) { downcasted = int128(value); - require(downcasted == value, "SafeCast: value doesn't fit in 128 bits"); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(128, value); + } } /** @@ -861,7 +977,9 @@ library SafeCast { */ function toInt120(int256 value) internal pure returns (int120 downcasted) { downcasted = int120(value); - require(downcasted == value, "SafeCast: value doesn't fit in 120 bits"); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(120, value); + } } /** @@ -879,7 +997,9 @@ library SafeCast { */ function toInt112(int256 value) internal pure returns (int112 downcasted) { downcasted = int112(value); - require(downcasted == value, "SafeCast: value doesn't fit in 112 bits"); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(112, value); + } } /** @@ -897,7 +1017,9 @@ library SafeCast { */ function toInt104(int256 value) internal pure returns (int104 downcasted) { downcasted = int104(value); - require(downcasted == value, "SafeCast: value doesn't fit in 104 bits"); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(104, value); + } } /** @@ -915,7 +1037,9 @@ library SafeCast { */ function toInt96(int256 value) internal pure returns (int96 downcasted) { downcasted = int96(value); - require(downcasted == value, "SafeCast: value doesn't fit in 96 bits"); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(96, value); + } } /** @@ -933,7 +1057,9 @@ library SafeCast { */ function toInt88(int256 value) internal pure returns (int88 downcasted) { downcasted = int88(value); - require(downcasted == value, "SafeCast: value doesn't fit in 88 bits"); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(88, value); + } } /** @@ -951,7 +1077,9 @@ library SafeCast { */ function toInt80(int256 value) internal pure returns (int80 downcasted) { downcasted = int80(value); - require(downcasted == value, "SafeCast: value doesn't fit in 80 bits"); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(80, value); + } } /** @@ -969,7 +1097,9 @@ library SafeCast { */ function toInt72(int256 value) internal pure returns (int72 downcasted) { downcasted = int72(value); - require(downcasted == value, "SafeCast: value doesn't fit in 72 bits"); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(72, value); + } } /** @@ -987,7 +1117,9 @@ library SafeCast { */ function toInt64(int256 value) internal pure returns (int64 downcasted) { downcasted = int64(value); - require(downcasted == value, "SafeCast: value doesn't fit in 64 bits"); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(64, value); + } } /** @@ -1005,7 +1137,9 @@ library SafeCast { */ function toInt56(int256 value) internal pure returns (int56 downcasted) { downcasted = int56(value); - require(downcasted == value, "SafeCast: value doesn't fit in 56 bits"); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(56, value); + } } /** @@ -1023,7 +1157,9 @@ library SafeCast { */ function toInt48(int256 value) internal pure returns (int48 downcasted) { downcasted = int48(value); - require(downcasted == value, "SafeCast: value doesn't fit in 48 bits"); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(48, value); + } } /** @@ -1041,7 +1177,9 @@ library SafeCast { */ function toInt40(int256 value) internal pure returns (int40 downcasted) { downcasted = int40(value); - require(downcasted == value, "SafeCast: value doesn't fit in 40 bits"); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(40, value); + } } /** @@ -1059,7 +1197,9 @@ library SafeCast { */ function toInt32(int256 value) internal pure returns (int32 downcasted) { downcasted = int32(value); - require(downcasted == value, "SafeCast: value doesn't fit in 32 bits"); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(32, value); + } } /** @@ -1077,7 +1217,9 @@ library SafeCast { */ function toInt24(int256 value) internal pure returns (int24 downcasted) { downcasted = int24(value); - require(downcasted == value, "SafeCast: value doesn't fit in 24 bits"); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(24, value); + } } /** @@ -1095,7 +1237,9 @@ library SafeCast { */ function toInt16(int256 value) internal pure returns (int16 downcasted) { downcasted = int16(value); - require(downcasted == value, "SafeCast: value doesn't fit in 16 bits"); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(16, value); + } } /** @@ -1113,7 +1257,9 @@ library SafeCast { */ function toInt8(int256 value) internal pure returns (int8 downcasted) { downcasted = int8(value); - require(downcasted == value, "SafeCast: value doesn't fit in 8 bits"); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(8, value); + } } /** @@ -1127,7 +1273,9 @@ library SafeCast { */ function toInt256(uint256 value) internal pure returns (int256) { // Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive - require(value <= uint256(type(int256).max), "SafeCast: value doesn't fit in an int256"); + if (value > uint256(type(int256).max)) { + revert SafeCastOverflowedUintToInt(value); + } return int256(value); } } diff --git a/contracts/utils/structs/Checkpoints.sol b/contracts/utils/structs/Checkpoints.sol index a0c45f659..7608d787e 100644 --- a/contracts/utils/structs/Checkpoints.sol +++ b/contracts/utils/structs/Checkpoints.sol @@ -17,6 +17,11 @@ import "../math/SafeCast.sol"; * _Available since v4.5._ */ library Checkpoints { + /** + * @dev A value was attempted to be inserted on a past checkpoint. + */ + error CheckpointUnorderedInsertion(); + struct Trace224 { Checkpoint224[] _checkpoints; } @@ -126,7 +131,9 @@ library Checkpoints { Checkpoint224 memory last = _unsafeAccess(self, pos - 1); // Checkpoint keys must be non-decreasing. - require(last._key <= key, "Checkpoint: decreasing keys"); + if (last._key > key) { + revert CheckpointUnorderedInsertion(); + } // Update or push new checkpoint if (last._key == key) { @@ -309,7 +316,9 @@ library Checkpoints { Checkpoint160 memory last = _unsafeAccess(self, pos - 1); // Checkpoint keys must be non-decreasing. - require(last._key <= key, "Checkpoint: decreasing keys"); + if (last._key > key) { + revert CheckpointUnorderedInsertion(); + } // Update or push new checkpoint if (last._key == key) { diff --git a/contracts/utils/structs/DoubleEndedQueue.sol b/contracts/utils/structs/DoubleEndedQueue.sol index be6e3898f..69db70040 100644 --- a/contracts/utils/structs/DoubleEndedQueue.sol +++ b/contracts/utils/structs/DoubleEndedQueue.sol @@ -22,12 +22,12 @@ library DoubleEndedQueue { /** * @dev An operation (e.g. {front}) couldn't be completed due to the queue being empty. */ - error Empty(); + error QueueEmpty(); /** * @dev An operation (e.g. {at}) couldn't be completed due to an index being out of bounds. */ - error OutOfBounds(); + error QueueOutOfBounds(); /** * @dev Indices are signed integers because the queue can grow in any direction. They are 128 bits so begin and end @@ -61,10 +61,10 @@ library DoubleEndedQueue { /** * @dev Removes the item at the end of the queue and returns it. * - * Reverts with `Empty` if the queue is empty. + * Reverts with `QueueEmpty` if the queue is empty. */ function popBack(Bytes32Deque storage deque) internal returns (bytes32 value) { - if (empty(deque)) revert Empty(); + if (empty(deque)) revert QueueEmpty(); int128 backIndex; unchecked { backIndex = deque._end - 1; @@ -89,10 +89,10 @@ library DoubleEndedQueue { /** * @dev Removes the item at the beginning of the queue and returns it. * - * Reverts with `Empty` if the queue is empty. + * Reverts with `QueueEmpty` if the queue is empty. */ function popFront(Bytes32Deque storage deque) internal returns (bytes32 value) { - if (empty(deque)) revert Empty(); + if (empty(deque)) revert QueueEmpty(); int128 frontIndex = deque._begin; value = deque._data[frontIndex]; delete deque._data[frontIndex]; @@ -104,10 +104,10 @@ library DoubleEndedQueue { /** * @dev Returns the item at the beginning of the queue. * - * Reverts with `Empty` if the queue is empty. + * Reverts with `QueueEmpty` if the queue is empty. */ function front(Bytes32Deque storage deque) internal view returns (bytes32 value) { - if (empty(deque)) revert Empty(); + if (empty(deque)) revert QueueEmpty(); int128 frontIndex = deque._begin; return deque._data[frontIndex]; } @@ -115,10 +115,10 @@ library DoubleEndedQueue { /** * @dev Returns the item at the end of the queue. * - * Reverts with `Empty` if the queue is empty. + * Reverts with `QueueEmpty` if the queue is empty. */ function back(Bytes32Deque storage deque) internal view returns (bytes32 value) { - if (empty(deque)) revert Empty(); + if (empty(deque)) revert QueueEmpty(); int128 backIndex; unchecked { backIndex = deque._end - 1; @@ -130,12 +130,12 @@ library DoubleEndedQueue { * @dev Return the item at a position in the queue given by `index`, with the first item at 0 and last item at * `length(deque) - 1`. * - * Reverts with `OutOfBounds` if the index is out of bounds. + * Reverts with `QueueOutOfBounds` if the index is out of bounds. */ function at(Bytes32Deque storage deque, uint256 index) internal view returns (bytes32 value) { // int256(deque._begin) is a safe upcast int128 idx = SafeCast.toInt128(int256(deque._begin) + SafeCast.toInt256(index)); - if (idx >= deque._end) revert OutOfBounds(); + if (idx >= deque._end) revert QueueOutOfBounds(); return deque._data[idx]; } diff --git a/contracts/utils/structs/EnumerableMap.sol b/contracts/utils/structs/EnumerableMap.sol index 4bd18055d..a474e82b3 100644 --- a/contracts/utils/structs/EnumerableMap.sol +++ b/contracts/utils/structs/EnumerableMap.sol @@ -57,6 +57,11 @@ library EnumerableMap { // This means that we can only create new EnumerableMaps for types that fit // in bytes32. + /** + * @dev Query for a nonexistent map key. + */ + error EnumerableMapNonexistentKey(bytes32 key); + struct Bytes32ToBytes32Map { // Storage of keys EnumerableSet.Bytes32Set _keys; @@ -136,7 +141,9 @@ library EnumerableMap { */ function get(Bytes32ToBytes32Map storage map, bytes32 key) internal view returns (bytes32) { bytes32 value = map._values[key]; - require(value != 0 || contains(map, key), "EnumerableMap: nonexistent key"); + if (value == 0 && !contains(map, key)) { + revert EnumerableMapNonexistentKey(key); + } return value; } diff --git a/scripts/generate/templates/Checkpoints.js b/scripts/generate/templates/Checkpoints.js index c85cfcbb3..d28134ce7 100644 --- a/scripts/generate/templates/Checkpoints.js +++ b/scripts/generate/templates/Checkpoints.js @@ -19,6 +19,13 @@ import "../math/SafeCast.sol"; */ `; +const errors = `\ + /** + * @dev A value was attempted to be inserted on a past checkpoint. + */ + error CheckpointUnorderedInsertion(); +`; + const template = opts => `\ struct ${opts.historyTypeName} { ${opts.checkpointTypeName}[] ${opts.checkpointFieldName}; @@ -145,7 +152,9 @@ function _insert( ${opts.checkpointTypeName} memory last = _unsafeAccess(self, pos - 1); // Checkpoint keys must be non-decreasing. - require(last.${opts.keyFieldName} <= key, "Checkpoint: decreasing keys"); + if(last.${opts.keyFieldName} > key) { + revert CheckpointUnorderedInsertion(); + } // Update or push new checkpoint if (last.${opts.keyFieldName} == key) { @@ -226,6 +235,7 @@ function _unsafeAccess(${opts.checkpointTypeName}[] storage self, uint256 pos) module.exports = format( header.trimEnd(), 'library Checkpoints {', + errors, OPTS.flatMap(opts => template(opts)), '}', ); diff --git a/scripts/generate/templates/EnumerableMap.js b/scripts/generate/templates/EnumerableMap.js index 13d3d8686..8899f4819 100644 --- a/scripts/generate/templates/EnumerableMap.js +++ b/scripts/generate/templates/EnumerableMap.js @@ -66,6 +66,11 @@ const defaultMap = () => `\ // This means that we can only create new EnumerableMaps for types that fit // in bytes32. +/** + * @dev Query for a nonexistent map key. + */ +error EnumerableMapNonexistentKey(bytes32 key); + struct Bytes32ToBytes32Map { // Storage of keys EnumerableSet.Bytes32Set _keys; @@ -149,7 +154,9 @@ function tryGet(Bytes32ToBytes32Map storage map, bytes32 key) internal view retu */ function get(Bytes32ToBytes32Map storage map, bytes32 key) internal view returns (bytes32) { bytes32 value = map._values[key]; - require(value != 0 || contains(map, key), "EnumerableMap: nonexistent key"); + if(value == 0 && !contains(map, key)) { + revert EnumerableMapNonexistentKey(key); + } return value; } diff --git a/scripts/generate/templates/SafeCast.js b/scripts/generate/templates/SafeCast.js index 0d78a2ca3..6a4a80c2b 100644 --- a/scripts/generate/templates/SafeCast.js +++ b/scripts/generate/templates/SafeCast.js @@ -77,6 +77,28 @@ pragma solidity ^0.8.19; */ `; +const errors = `\ + /** + * @dev Value doesn't fit in an uint of \`bits\` size. + */ + error SafeCastOverflowedUintDowncast(uint8 bits, uint256 value); + + /** + * @dev An int value doesn't fit in an uint of \`bits\` size. + */ + error SafeCastOverflowedIntToUint(int256 value); + + /** + * @dev Value doesn't fit in an int of \`bits\` size. + */ + error SafeCastOverflowedIntDowncast(uint8 bits, int256 value); + + /** + * @dev An uint value doesn't fit in an int of \`bits\` size. + */ + error SafeCastOverflowedUintToInt(uint256 value); +`; + const toUintDownCast = length => `\ /** * @dev Returns the downcasted uint${length} from uint256, reverting on @@ -91,7 +113,9 @@ const toUintDownCast = length => `\ * _Available since v${version('toUint(uint)', length)}._ */ function toUint${length}(uint256 value) internal pure returns (uint${length}) { - require(value <= type(uint${length}).max, "SafeCast: value doesn't fit in ${length} bits"); + if (value > type(uint${length}).max) { + revert SafeCastOverflowedUintDowncast(${length}, value); + } return uint${length}(value); } `; @@ -113,7 +137,9 @@ const toIntDownCast = length => `\ */ function toInt${length}(int256 value) internal pure returns (int${length} downcasted) { downcasted = int${length}(value); - require(downcasted == value, "SafeCast: value doesn't fit in ${length} bits"); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(${length}, value); + } } `; /* eslint-enable max-len */ @@ -130,7 +156,9 @@ const toInt = length => `\ */ function toInt${length}(uint${length} value) internal pure returns (int${length}) { // Note: Unsafe cast below is okay because \`type(int${length}).max\` is guaranteed to be positive - require(value <= uint${length}(type(int${length}).max), "SafeCast: value doesn't fit in an int${length}"); + if (value > uint${length}(type(int${length}).max)) { + revert SafeCastOverflowedUintToInt(value); + } return int${length}(value); } `; @@ -146,7 +174,9 @@ const toUint = length => `\ * _Available since v${version('toUint(int)', length)}._ */ function toUint${length}(int${length} value) internal pure returns (uint${length}) { - require(value >= 0, "SafeCast: value must be positive"); + if (value < 0) { + revert SafeCastOverflowedIntToUint(value); + } return uint${length}(value); } `; @@ -155,6 +185,7 @@ function toUint${length}(int${length} value) internal pure returns (uint${length module.exports = format( header.trimEnd(), 'library SafeCast {', + errors, [...LENGTHS.map(toUintDownCast), toUint(256), ...LENGTHS.map(toIntDownCast), toInt(256)], '}', ); diff --git a/scripts/generate/templates/StorageSlot.js b/scripts/generate/templates/StorageSlot.js index b51affc22..3e2263a0c 100644 --- a/scripts/generate/templates/StorageSlot.js +++ b/scripts/generate/templates/StorageSlot.js @@ -38,7 +38,7 @@ pragma solidity ^0.8.19; * } * * function _setImplementation(address newImplementation) internal { - * require(newImplementation.code.length > 0, "ERC1967: new implementation is not a contract"); + * require(newImplementation.code.length > 0); * StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation; * } * } diff --git a/test/access/AccessControl.behavior.js b/test/access/AccessControl.behavior.js index 3e61616a7..b1729c5d6 100644 --- a/test/access/AccessControl.behavior.js +++ b/test/access/AccessControl.behavior.js @@ -1,4 +1,5 @@ -const { expectEvent, expectRevert, constants, BN } = require('@openzeppelin/test-helpers'); +const { expectEvent, constants, BN } = require('@openzeppelin/test-helpers'); +const { expectRevertCustomError } = require('../helpers/customError'); const { expect } = require('chai'); const { time } = require('@nomicfoundation/hardhat-network-helpers'); @@ -12,7 +13,7 @@ const ROLE = web3.utils.soliditySha3('ROLE'); const OTHER_ROLE = web3.utils.soliditySha3('OTHER_ROLE'); const ZERO = web3.utils.toBN(0); -function shouldBehaveLikeAccessControl(errorPrefix, admin, authorized, other, otherAdmin) { +function shouldBehaveLikeAccessControl(admin, authorized, other, otherAdmin) { shouldSupportInterfaces(['AccessControl']); describe('default admin', function () { @@ -35,9 +36,10 @@ function shouldBehaveLikeAccessControl(errorPrefix, admin, authorized, other, ot }); it('non-admin cannot grant role to other accounts', async function () { - await expectRevert( + await expectRevertCustomError( this.accessControl.grantRole(ROLE, authorized, { from: other }), - `${errorPrefix}: account ${other.toLowerCase()} is missing role ${DEFAULT_ADMIN_ROLE}`, + 'AccessControlUnauthorizedAccount', + [other, DEFAULT_ADMIN_ROLE], ); }); @@ -69,9 +71,10 @@ function shouldBehaveLikeAccessControl(errorPrefix, admin, authorized, other, ot }); it('non-admin cannot revoke role', async function () { - await expectRevert( + await expectRevertCustomError( this.accessControl.revokeRole(ROLE, authorized, { from: other }), - `${errorPrefix}: account ${other.toLowerCase()} is missing role ${DEFAULT_ADMIN_ROLE}`, + 'AccessControlUnauthorizedAccount', + [other, DEFAULT_ADMIN_ROLE], ); }); @@ -103,9 +106,10 @@ function shouldBehaveLikeAccessControl(errorPrefix, admin, authorized, other, ot }); it('only the sender can renounce their roles', async function () { - await expectRevert( + await expectRevertCustomError( this.accessControl.renounceRole(ROLE, authorized, { from: admin }), - `${errorPrefix}: can only renounce roles for self`, + 'AccessControlBadConfirmation', + [], ); }); @@ -146,16 +150,18 @@ function shouldBehaveLikeAccessControl(errorPrefix, admin, authorized, other, ot }); it("a role's previous admins no longer grant roles", async function () { - await expectRevert( + await expectRevertCustomError( this.accessControl.grantRole(ROLE, authorized, { from: admin }), - `${errorPrefix}: account ${admin.toLowerCase()} is missing role ${OTHER_ROLE}`, + 'AccessControlUnauthorizedAccount', + [admin.toLowerCase(), OTHER_ROLE], ); }); it("a role's previous admins no longer revoke roles", async function () { - await expectRevert( + await expectRevertCustomError( this.accessControl.revokeRole(ROLE, authorized, { from: admin }), - `${errorPrefix}: account ${admin.toLowerCase()} is missing role ${OTHER_ROLE}`, + 'AccessControlUnauthorizedAccount', + [admin.toLowerCase(), OTHER_ROLE], ); }); }); @@ -170,22 +176,24 @@ function shouldBehaveLikeAccessControl(errorPrefix, admin, authorized, other, ot }); it("revert if sender doesn't have role #1", async function () { - await expectRevert( + await expectRevertCustomError( this.accessControl.methods['$_checkRole(bytes32)'](ROLE, { from: other }), - `${errorPrefix}: account ${other.toLowerCase()} is missing role ${ROLE}`, + 'AccessControlUnauthorizedAccount', + [other, ROLE], ); }); it("revert if sender doesn't have role #2", async function () { - await expectRevert( + await expectRevertCustomError( this.accessControl.methods['$_checkRole(bytes32)'](OTHER_ROLE, { from: authorized }), - `${errorPrefix}: account ${authorized.toLowerCase()} is missing role ${OTHER_ROLE}`, + 'AccessControlUnauthorizedAccount', + [authorized.toLowerCase(), OTHER_ROLE], ); }); }); } -function shouldBehaveLikeAccessControlEnumerable(errorPrefix, admin, authorized, other, otherAdmin, otherAuthorized) { +function shouldBehaveLikeAccessControlEnumerable(admin, authorized, other, otherAdmin, otherAuthorized) { shouldSupportInterfaces(['AccessControlEnumerable']); describe('enumerating', function () { @@ -215,18 +223,9 @@ function shouldBehaveLikeAccessControlEnumerable(errorPrefix, admin, authorized, }); } -function shouldBehaveLikeAccessControlDefaultAdminRules(errorPrefix, delay, defaultAdmin, newDefaultAdmin, other) { +function shouldBehaveLikeAccessControlDefaultAdminRules(delay, defaultAdmin, newDefaultAdmin, other) { shouldSupportInterfaces(['AccessControlDefaultAdminRules']); - function expectNoEvent(receipt, eventName) { - try { - expectEvent(receipt, eventName); - throw new Error(`${eventName} event found`); - } catch (err) { - expect(err.message).to.eq(`No '${eventName}' events found: expected false to equal true`); - } - } - for (const getter of ['owner', 'defaultAdmin']) { describe(`${getter}()`, function () { it('has a default set to the initial default admin', async function () { @@ -366,30 +365,34 @@ function shouldBehaveLikeAccessControlDefaultAdminRules(errorPrefix, delay, defa }); it('should revert if granting default admin role', async function () { - await expectRevert( + await expectRevertCustomError( this.accessControl.grantRole(DEFAULT_ADMIN_ROLE, defaultAdmin, { from: defaultAdmin }), - `${errorPrefix}: can't directly grant default admin role`, + 'AccessControlEnforcedDefaultAdminRules', + [], ); }); it('should revert if revoking default admin role', async function () { - await expectRevert( + await expectRevertCustomError( this.accessControl.revokeRole(DEFAULT_ADMIN_ROLE, defaultAdmin, { from: defaultAdmin }), - `${errorPrefix}: can't directly revoke default admin role`, + 'AccessControlEnforcedDefaultAdminRules', + [], ); }); it("should revert if defaultAdmin's admin is changed", async function () { - await expectRevert( + await expectRevertCustomError( this.accessControl.$_setRoleAdmin(DEFAULT_ADMIN_ROLE, defaultAdmin), - `${errorPrefix}: can't violate default admin rules`, + 'AccessControlEnforcedDefaultAdminRules', + [], ); }); it('should not grant the default admin role twice', async function () { - await expectRevert( + await expectRevertCustomError( this.accessControl.$_grantRole(DEFAULT_ADMIN_ROLE, defaultAdmin), - `${errorPrefix}: default admin already granted`, + 'AccessControlEnforcedDefaultAdminRules', + [], ); }); @@ -398,9 +401,10 @@ function shouldBehaveLikeAccessControlDefaultAdminRules(errorPrefix, delay, defa let acceptSchedule; it('reverts if called by non default admin accounts', async function () { - await expectRevert( + await expectRevertCustomError( this.accessControl.beginDefaultAdminTransfer(newDefaultAdmin, { from: other }), - `${errorPrefix}: account ${other.toLowerCase()} is missing role ${DEFAULT_ADMIN_ROLE}`, + 'AccessControlUnauthorizedAccount', + [other, DEFAULT_ADMIN_ROLE], ); }); @@ -456,7 +460,7 @@ function shouldBehaveLikeAccessControlDefaultAdminRules(errorPrefix, delay, defa await this.accessControl.acceptDefaultAdminTransfer({ from: newDefaultAdmin }); const receipt = await this.accessControl.beginDefaultAdminTransfer(other, { from: newDefaultAdmin }); - expectNoEvent(receipt, 'DefaultAdminTransferCanceled'); + expectEvent.notEmitted(receipt, 'DefaultAdminTransferCanceled'); }); }); @@ -506,9 +510,10 @@ function shouldBehaveLikeAccessControlDefaultAdminRules(errorPrefix, delay, defa it('should revert if caller is not pending default admin', async function () { await time.setNextBlockTimestamp(acceptSchedule.addn(1)); - await expectRevert( + await expectRevertCustomError( this.accessControl.acceptDefaultAdminTransfer({ from: other }), - `${errorPrefix}: pending admin must accept`, + 'AccessControlInvalidDefaultAdmin', + [other], ); }); @@ -549,9 +554,10 @@ function shouldBehaveLikeAccessControlDefaultAdminRules(errorPrefix, delay, defa ]) { it(`should revert if block.timestamp is ${tag} to schedule`, async function () { await time.setNextBlockTimestamp(acceptSchedule.toNumber() + fromSchedule); - await expectRevert( + await expectRevertCustomError( this.accessControl.acceptDefaultAdminTransfer({ from: newDefaultAdmin }), - `${errorPrefix}: transfer delay not passed`, + 'AccessControlEnforcedDefaultAdminDelay', + [acceptSchedule], ); }); } @@ -560,9 +566,10 @@ function shouldBehaveLikeAccessControlDefaultAdminRules(errorPrefix, delay, defa describe('cancels a default admin transfer', function () { it('reverts if called by non default admin accounts', async function () { - await expectRevert( + await expectRevertCustomError( this.accessControl.cancelDefaultAdminTransfer({ from: other }), - `${errorPrefix}: account ${other.toLowerCase()} is missing role ${DEFAULT_ADMIN_ROLE}`, + 'AccessControlUnauthorizedAccount', + [other, DEFAULT_ADMIN_ROLE], ); }); @@ -600,9 +607,10 @@ function shouldBehaveLikeAccessControlDefaultAdminRules(errorPrefix, delay, defa await time.setNextBlockTimestamp(acceptSchedule.addn(1)); // Previous pending default admin should not be able to accept after cancellation. - await expectRevert( + await expectRevertCustomError( this.accessControl.acceptDefaultAdminTransfer({ from: newDefaultAdmin }), - `${errorPrefix}: pending admin must accept`, + 'AccessControlInvalidDefaultAdmin', + [newDefaultAdmin], ); }); }); @@ -615,7 +623,7 @@ function shouldBehaveLikeAccessControlDefaultAdminRules(errorPrefix, delay, defa expect(newAdmin).to.equal(constants.ZERO_ADDRESS); expect(schedule).to.be.bignumber.equal(ZERO); - expectNoEvent(receipt, 'DefaultAdminTransferCanceled'); + expectEvent.notEmitted(receipt, 'DefaultAdminTransferCanceled'); }); }); }); @@ -634,9 +642,10 @@ function shouldBehaveLikeAccessControlDefaultAdminRules(errorPrefix, delay, defa it('reverts if caller is not default admin', async function () { await time.setNextBlockTimestamp(delayPassed); - await expectRevert( + await expectRevertCustomError( this.accessControl.renounceRole(DEFAULT_ADMIN_ROLE, other, { from: defaultAdmin }), - `${errorPrefix}: can only renounce roles for self`, + 'AccessControlBadConfirmation', + [], ); }); @@ -693,9 +702,10 @@ function shouldBehaveLikeAccessControlDefaultAdminRules(errorPrefix, delay, defa ]) { it(`reverts if block.timestamp is ${tag} to schedule`, async function () { await time.setNextBlockTimestamp(delayNotPassed.toNumber() + fromSchedule); - await expectRevert( + await expectRevertCustomError( this.accessControl.renounceRole(DEFAULT_ADMIN_ROLE, defaultAdmin, { from: defaultAdmin }), - `${errorPrefix}: only can renounce in two delayed steps`, + 'AccessControlEnforcedDefaultAdminDelay', + [expectedSchedule], ); }); } @@ -704,11 +714,12 @@ function shouldBehaveLikeAccessControlDefaultAdminRules(errorPrefix, delay, defa describe('changes delay', function () { it('reverts if called by non default admin accounts', async function () { - await expectRevert( + await expectRevertCustomError( this.accessControl.changeDefaultAdminDelay(time.duration.hours(4), { from: other, }), - `${errorPrefix}: account ${other.toLowerCase()} is missing role ${DEFAULT_ADMIN_ROLE}`, + 'AccessControlUnauthorizedAccount', + [other, DEFAULT_ADMIN_ROLE], ); }); @@ -792,7 +803,7 @@ function shouldBehaveLikeAccessControlDefaultAdminRules(errorPrefix, delay, defa from: defaultAdmin, }); - const eventMatcher = passed ? expectNoEvent : expectEvent; + const eventMatcher = passed ? expectEvent.notEmitted : expectEvent; eventMatcher(receipt, 'DefaultAdminDelayChangeCanceled'); }); } @@ -803,9 +814,10 @@ function shouldBehaveLikeAccessControlDefaultAdminRules(errorPrefix, delay, defa describe('rollbacks a delay change', function () { it('reverts if called by non default admin accounts', async function () { - await expectRevert( + await expectRevertCustomError( this.accessControl.rollbackDefaultAdminDelay({ from: other }), - `${errorPrefix}: account ${other.toLowerCase()} is missing role ${DEFAULT_ADMIN_ROLE}`, + 'AccessControlUnauthorizedAccount', + [other, DEFAULT_ADMIN_ROLE], ); }); @@ -841,7 +853,7 @@ function shouldBehaveLikeAccessControlDefaultAdminRules(errorPrefix, delay, defa const receipt = await this.accessControl.rollbackDefaultAdminDelay({ from: defaultAdmin }); - const eventMatcher = passed ? expectNoEvent : expectEvent; + const eventMatcher = passed ? expectEvent.notEmitted : expectEvent; eventMatcher(receipt, 'DefaultAdminDelayChangeCanceled'); }); } diff --git a/test/access/AccessControl.test.js b/test/access/AccessControl.test.js index 90efad3d0..14463b505 100644 --- a/test/access/AccessControl.test.js +++ b/test/access/AccessControl.test.js @@ -8,5 +8,5 @@ contract('AccessControl', function (accounts) { await this.accessControl.$_grantRole(DEFAULT_ADMIN_ROLE, accounts[0]); }); - shouldBehaveLikeAccessControl('AccessControl', ...accounts); + shouldBehaveLikeAccessControl(...accounts); }); diff --git a/test/access/AccessControlDefaultAdminRules.test.js b/test/access/AccessControlDefaultAdminRules.test.js index be112481e..b8eae3220 100644 --- a/test/access/AccessControlDefaultAdminRules.test.js +++ b/test/access/AccessControlDefaultAdminRules.test.js @@ -16,10 +16,11 @@ contract('AccessControlDefaultAdminRules', function (accounts) { it('initial admin not zero', async function () { await expectRevert( AccessControlDefaultAdminRules.new(delay, constants.ZERO_ADDRESS), - 'AccessControl: 0 default admin', + 'AccessControlInvalidDefaultAdmin', + [constants.ZERO_ADDRESS], ); }); - shouldBehaveLikeAccessControl('AccessControl', ...accounts); - shouldBehaveLikeAccessControlDefaultAdminRules('AccessControl', delay, ...accounts); + shouldBehaveLikeAccessControl(...accounts); + shouldBehaveLikeAccessControlDefaultAdminRules(delay, ...accounts); }); diff --git a/test/access/AccessControlEnumerable.test.js b/test/access/AccessControlEnumerable.test.js index 2aa59f4c0..0e1879700 100644 --- a/test/access/AccessControlEnumerable.test.js +++ b/test/access/AccessControlEnumerable.test.js @@ -12,6 +12,6 @@ contract('AccessControl', function (accounts) { await this.accessControl.$_grantRole(DEFAULT_ADMIN_ROLE, accounts[0]); }); - shouldBehaveLikeAccessControl('AccessControl', ...accounts); - shouldBehaveLikeAccessControlEnumerable('AccessControl', ...accounts); + shouldBehaveLikeAccessControl(...accounts); + shouldBehaveLikeAccessControlEnumerable(...accounts); }); diff --git a/test/access/Ownable.test.js b/test/access/Ownable.test.js index 07b8764a5..079d694d7 100644 --- a/test/access/Ownable.test.js +++ b/test/access/Ownable.test.js @@ -1,4 +1,6 @@ -const { constants, expectEvent, expectRevert } = require('@openzeppelin/test-helpers'); +const { constants, expectEvent } = require('@openzeppelin/test-helpers'); +const { expectRevertCustomError } = require('../helpers/customError'); + const { ZERO_ADDRESS } = constants; const { expect } = require('chai'); @@ -25,13 +27,18 @@ contract('Ownable', function (accounts) { }); it('prevents non-owners from transferring', async function () { - await expectRevert(this.ownable.transferOwnership(other, { from: other }), 'Ownable: caller is not the owner'); + await expectRevertCustomError( + this.ownable.transferOwnership(other, { from: other }), + 'OwnableUnauthorizedAccount', + [other], + ); }); it('guards ownership against stuck state', async function () { - await expectRevert( + await expectRevertCustomError( this.ownable.transferOwnership(ZERO_ADDRESS, { from: owner }), - 'Ownable: new owner is the zero address', + 'OwnableInvalidOwner', + [ZERO_ADDRESS], ); }); }); @@ -45,7 +52,9 @@ contract('Ownable', function (accounts) { }); it('prevents non-owners from renouncement', async function () { - await expectRevert(this.ownable.renounceOwnership({ from: other }), 'Ownable: caller is not the owner'); + await expectRevertCustomError(this.ownable.renounceOwnership({ from: other }), 'OwnableUnauthorizedAccount', [ + other, + ]); }); it('allows to recover access using the internal _transferOwnership', async function () { diff --git a/test/access/Ownable2Step.test.js b/test/access/Ownable2Step.test.js index dfda6b708..bdbac48fa 100644 --- a/test/access/Ownable2Step.test.js +++ b/test/access/Ownable2Step.test.js @@ -1,6 +1,7 @@ -const { constants, expectEvent, expectRevert } = require('@openzeppelin/test-helpers'); +const { constants, expectEvent } = require('@openzeppelin/test-helpers'); const { ZERO_ADDRESS } = constants; const { expect } = require('chai'); +const { expectRevertCustomError } = require('../helpers/customError'); const Ownable2Step = artifacts.require('$Ownable2Step'); @@ -29,14 +30,15 @@ contract('Ownable2Step', function (accounts) { it('guards transfer against invalid user', async function () { await this.ownable2Step.transferOwnership(accountA, { from: owner }); - await expectRevert( + await expectRevertCustomError( this.ownable2Step.acceptOwnership({ from: accountB }), - 'Ownable2Step: caller is not the new owner', + 'OwnableUnauthorizedAccount', + [accountB], ); }); }); - it('renouncing ownership', async function () { + describe('renouncing ownership', async function () { it('changes owner after renouncing ownership', async function () { await this.ownable2Step.renounceOwnership({ from: owner }); // If renounceOwnership is removed from parent an alternative is needed ... @@ -50,18 +52,19 @@ contract('Ownable2Step', function (accounts) { expect(await this.ownable2Step.pendingOwner()).to.equal(accountA); await this.ownable2Step.renounceOwnership({ from: owner }); expect(await this.ownable2Step.pendingOwner()).to.equal(ZERO_ADDRESS); - await expectRevert( + await expectRevertCustomError( this.ownable2Step.acceptOwnership({ from: accountA }), - 'Ownable2Step: caller is not the new owner', + 'OwnableUnauthorizedAccount', + [accountA], ); }); it('allows to recover access using the internal _transferOwnership', async function () { - await this.ownable.renounceOwnership({ from: owner }); - const receipt = await this.ownable.$_transferOwnership(accountA); + await this.ownable2Step.renounceOwnership({ from: owner }); + const receipt = await this.ownable2Step.$_transferOwnership(accountA); expectEvent(receipt, 'OwnershipTransferred'); - expect(await this.ownable.owner()).to.equal(accountA); + expect(await this.ownable2Step.owner()).to.equal(accountA); }); }); }); diff --git a/test/finance/VestingWallet.test.js b/test/finance/VestingWallet.test.js index 09205a99c..91ca04da0 100644 --- a/test/finance/VestingWallet.test.js +++ b/test/finance/VestingWallet.test.js @@ -1,7 +1,8 @@ -const { constants, expectEvent, expectRevert, time } = require('@openzeppelin/test-helpers'); +const { constants, expectEvent, time } = require('@openzeppelin/test-helpers'); const { web3 } = require('@openzeppelin/test-helpers/src/setup'); const { expect } = require('chai'); const { BNmin } = require('../helpers/math'); +const { expectRevertCustomError } = require('../helpers/customError'); const VestingWallet = artifacts.require('VestingWallet'); const ERC20 = artifacts.require('$ERC20'); @@ -20,9 +21,10 @@ contract('VestingWallet', function (accounts) { }); it('rejects zero address for beneficiary', async function () { - await expectRevert( + await expectRevertCustomError( VestingWallet.new(constants.ZERO_ADDRESS, this.start, duration), - 'VestingWallet: beneficiary is zero address', + 'VestingWalletInvalidBeneficiary', + [constants.ZERO_ADDRESS], ); }); diff --git a/test/governance/Governor.test.js b/test/governance/Governor.test.js index 96feaf3a7..909c38686 100644 --- a/test/governance/Governor.test.js +++ b/test/governance/Governor.test.js @@ -6,11 +6,13 @@ const { fromRpcSig } = require('ethereumjs-util'); const Enums = require('../helpers/enums'); const { getDomain, domainType } = require('../helpers/eip712'); -const { GovernorHelper } = require('../helpers/governance'); +const { GovernorHelper, proposalStatesToBitMap } = require('../helpers/governance'); const { clockFromReceipt } = require('../helpers/time'); +const { expectRevertCustomError } = require('../helpers/customError'); const { shouldSupportInterfaces } = require('../utils/introspection/SupportsInterface.behavior'); const { shouldBehaveLikeEIP6372 } = require('./utils/EIP6372.behavior'); +const { ZERO_BYTES32 } = require('@openzeppelin/test-helpers/src/constants'); const Governor = artifacts.require('$GovernorMock'); const CallReceiver = artifacts.require('CallReceiverMock'); @@ -237,32 +239,39 @@ contract('Governor', function (accounts) { describe('on propose', function () { it('if proposal already exists', async function () { await this.helper.propose(); - await expectRevert(this.helper.propose(), 'Governor: proposal already exists'); + await expectRevertCustomError(this.helper.propose(), 'GovernorUnexpectedProposalState', [ + this.proposal.id, + Enums.ProposalState.Pending, + ZERO_BYTES32, + ]); }); }); describe('on vote', function () { it('if proposal does not exist', async function () { - await expectRevert( + await expectRevertCustomError( this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }), - 'Governor: unknown proposal id', + 'GovernorNonexistentProposal', + [this.proposal.id], ); }); it('if voting has not started', async function () { await this.helper.propose(); - await expectRevert( + await expectRevertCustomError( this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }), - 'Governor: vote not currently active', + 'GovernorUnexpectedProposalState', + [this.proposal.id, Enums.ProposalState.Pending, proposalStatesToBitMap([Enums.ProposalState.Active])], ); }); it('if support value is invalid', async function () { await this.helper.propose(); await this.helper.waitForSnapshot(); - await expectRevert( + await expectRevertCustomError( this.helper.vote({ support: web3.utils.toBN('255') }), - 'GovernorVotingSimple: invalid value for enum VoteType', + 'GovernorInvalidVoteType', + [], ); }); @@ -270,50 +279,64 @@ contract('Governor', function (accounts) { await this.helper.propose(); await this.helper.waitForSnapshot(); await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }); - await expectRevert( + await expectRevertCustomError( this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }), - 'GovernorVotingSimple: vote already cast', + 'GovernorAlreadyCastVote', + [voter1], ); }); it('if voting is over', async function () { await this.helper.propose(); await this.helper.waitForDeadline(); - await expectRevert( + await expectRevertCustomError( this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }), - 'Governor: vote not currently active', + 'GovernorUnexpectedProposalState', + [this.proposal.id, Enums.ProposalState.Defeated, proposalStatesToBitMap([Enums.ProposalState.Active])], ); }); }); describe('on execute', function () { it('if proposal does not exist', async function () { - await expectRevert(this.helper.execute(), 'Governor: unknown proposal id'); + await expectRevertCustomError(this.helper.execute(), 'GovernorNonexistentProposal', [this.proposal.id]); }); it('if quorum is not reached', async function () { await this.helper.propose(); await this.helper.waitForSnapshot(); await this.helper.vote({ support: Enums.VoteType.For }, { from: voter3 }); - await expectRevert(this.helper.execute(), 'Governor: proposal not successful'); + await expectRevertCustomError(this.helper.execute(), 'GovernorUnexpectedProposalState', [ + this.proposal.id, + Enums.ProposalState.Active, + proposalStatesToBitMap([Enums.ProposalState.Succeeded, Enums.ProposalState.Queued]), + ]); }); it('if score not reached', async function () { await this.helper.propose(); await this.helper.waitForSnapshot(); await this.helper.vote({ support: Enums.VoteType.Against }, { from: voter1 }); - await expectRevert(this.helper.execute(), 'Governor: proposal not successful'); + await expectRevertCustomError(this.helper.execute(), 'GovernorUnexpectedProposalState', [ + this.proposal.id, + Enums.ProposalState.Active, + proposalStatesToBitMap([Enums.ProposalState.Succeeded, Enums.ProposalState.Queued]), + ]); }); it('if voting is not over', async function () { await this.helper.propose(); await this.helper.waitForSnapshot(); await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }); - await expectRevert(this.helper.execute(), 'Governor: proposal not successful'); + await expectRevertCustomError(this.helper.execute(), 'GovernorUnexpectedProposalState', [ + this.proposal.id, + Enums.ProposalState.Active, + proposalStatesToBitMap([Enums.ProposalState.Succeeded, Enums.ProposalState.Queued]), + ]); }); it('if receiver revert without reason', async function () { - this.proposal = this.helper.setProposal( + this.helper.setProposal( [ { target: this.receiver.address, @@ -327,11 +350,11 @@ contract('Governor', function (accounts) { await this.helper.waitForSnapshot(); await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }); await this.helper.waitForDeadline(); - await expectRevert(this.helper.execute(), 'Governor: call reverted without message'); + await expectRevertCustomError(this.helper.execute(), 'FailedInnerCall', []); }); it('if receiver revert with reason', async function () { - this.proposal = this.helper.setProposal( + this.helper.setProposal( [ { target: this.receiver.address, @@ -354,14 +377,20 @@ contract('Governor', function (accounts) { await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }); await this.helper.waitForDeadline(); await this.helper.execute(); - await expectRevert(this.helper.execute(), 'Governor: proposal not successful'); + await expectRevertCustomError(this.helper.execute(), 'GovernorUnexpectedProposalState', [ + this.proposal.id, + Enums.ProposalState.Executed, + proposalStatesToBitMap([Enums.ProposalState.Succeeded, Enums.ProposalState.Queued]), + ]); }); }); }); describe('state', function () { it('Unset', async function () { - await expectRevert(this.mock.state(this.proposal.id), 'Governor: unknown proposal id'); + await expectRevertCustomError(this.mock.state(this.proposal.id), 'GovernorNonexistentProposal', [ + this.proposal.id, + ]); }); it('Pending & Active', async function () { @@ -404,7 +433,9 @@ contract('Governor', function (accounts) { describe('cancel', function () { describe('internal', function () { it('before proposal', async function () { - await expectRevert(this.helper.cancel('internal'), 'Governor: unknown proposal id'); + await expectRevertCustomError(this.helper.cancel('internal'), 'GovernorNonexistentProposal', [ + this.proposal.id, + ]); }); it('after proposal', async function () { @@ -414,9 +445,10 @@ contract('Governor', function (accounts) { expect(await this.mock.state(this.proposal.id)).to.be.bignumber.equal(Enums.ProposalState.Canceled); await this.helper.waitForSnapshot(); - await expectRevert( + await expectRevertCustomError( this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }), - 'Governor: vote not currently active', + 'GovernorUnexpectedProposalState', + [this.proposal.id, Enums.ProposalState.Canceled, proposalStatesToBitMap([Enums.ProposalState.Active])], ); }); @@ -429,7 +461,11 @@ contract('Governor', function (accounts) { expect(await this.mock.state(this.proposal.id)).to.be.bignumber.equal(Enums.ProposalState.Canceled); await this.helper.waitForDeadline(); - await expectRevert(this.helper.execute(), 'Governor: proposal not successful'); + await expectRevertCustomError(this.helper.execute(), 'GovernorUnexpectedProposalState', [ + this.proposal.id, + Enums.ProposalState.Canceled, + proposalStatesToBitMap([Enums.ProposalState.Succeeded, Enums.ProposalState.Queued]), + ]); }); it('after deadline', async function () { @@ -441,7 +477,11 @@ contract('Governor', function (accounts) { await this.helper.cancel('internal'); expect(await this.mock.state(this.proposal.id)).to.be.bignumber.equal(Enums.ProposalState.Canceled); - await expectRevert(this.helper.execute(), 'Governor: proposal not successful'); + await expectRevertCustomError(this.helper.execute(), 'GovernorUnexpectedProposalState', [ + this.proposal.id, + Enums.ProposalState.Canceled, + proposalStatesToBitMap([Enums.ProposalState.Succeeded, Enums.ProposalState.Queued]), + ]); }); it('after execution', async function () { @@ -451,13 +491,22 @@ contract('Governor', function (accounts) { await this.helper.waitForDeadline(); await this.helper.execute(); - await expectRevert(this.helper.cancel('internal'), 'Governor: proposal not active'); + await expectRevertCustomError(this.helper.cancel('internal'), 'GovernorUnexpectedProposalState', [ + this.proposal.id, + Enums.ProposalState.Executed, + proposalStatesToBitMap( + [Enums.ProposalState.Canceled, Enums.ProposalState.Expired, Enums.ProposalState.Executed], + { inverted: true }, + ), + ]); }); }); describe('public', function () { it('before proposal', async function () { - await expectRevert(this.helper.cancel('external'), 'Governor: unknown proposal id'); + await expectRevertCustomError(this.helper.cancel('external'), 'GovernorNonexistentProposal', [ + this.proposal.id, + ]); }); it('after proposal', async function () { @@ -469,14 +518,20 @@ contract('Governor', function (accounts) { it('after proposal - restricted to proposer', async function () { await this.helper.propose(); - await expectRevert(this.helper.cancel('external', { from: owner }), 'Governor: only proposer can cancel'); + await expectRevertCustomError(this.helper.cancel('external', { from: owner }), 'GovernorOnlyProposer', [ + owner, + ]); }); it('after vote started', async function () { await this.helper.propose(); await this.helper.waitForSnapshot(1); // snapshot + 1 block - await expectRevert(this.helper.cancel('external'), 'Governor: too late to cancel'); + await expectRevertCustomError(this.helper.cancel('external'), 'GovernorUnexpectedProposalState', [ + this.proposal.id, + Enums.ProposalState.Active, + proposalStatesToBitMap([Enums.ProposalState.Pending]), + ]); }); it('after vote', async function () { @@ -484,7 +539,11 @@ contract('Governor', function (accounts) { await this.helper.waitForSnapshot(); await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }); - await expectRevert(this.helper.cancel('external'), 'Governor: too late to cancel'); + await expectRevertCustomError(this.helper.cancel('external'), 'GovernorUnexpectedProposalState', [ + this.proposal.id, + Enums.ProposalState.Active, + proposalStatesToBitMap([Enums.ProposalState.Pending]), + ]); }); it('after deadline', async function () { @@ -493,7 +552,11 @@ contract('Governor', function (accounts) { await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }); await this.helper.waitForDeadline(); - await expectRevert(this.helper.cancel('external'), 'Governor: too late to cancel'); + await expectRevertCustomError(this.helper.cancel('external'), 'GovernorUnexpectedProposalState', [ + this.proposal.id, + Enums.ProposalState.Succeeded, + proposalStatesToBitMap([Enums.ProposalState.Pending]), + ]); }); it('after execution', async function () { @@ -503,7 +566,11 @@ contract('Governor', function (accounts) { await this.helper.waitForDeadline(); await this.helper.execute(); - await expectRevert(this.helper.cancel('external'), 'Governor: too late to cancel'); + await expectRevertCustomError(this.helper.cancel('external'), 'GovernorUnexpectedProposalState', [ + this.proposal.id, + Enums.ProposalState.Executed, + proposalStatesToBitMap([Enums.ProposalState.Pending]), + ]); }); }); }); @@ -511,7 +578,7 @@ contract('Governor', function (accounts) { describe('proposal length', function () { it('empty', async function () { this.helper.setProposal([], ''); - await expectRevert(this.helper.propose(), 'Governor: empty proposal'); + await expectRevertCustomError(this.helper.propose(), 'GovernorInvalidProposalLength', [0, 0, 0]); }); it('mismatch #1', async function () { @@ -523,7 +590,7 @@ contract('Governor', function (accounts) { }, '', ); - await expectRevert(this.helper.propose(), 'Governor: invalid proposal length'); + await expectRevertCustomError(this.helper.propose(), 'GovernorInvalidProposalLength', [0, 1, 1]); }); it('mismatch #2', async function () { @@ -535,7 +602,7 @@ contract('Governor', function (accounts) { }, '', ); - await expectRevert(this.helper.propose(), 'Governor: invalid proposal length'); + await expectRevertCustomError(this.helper.propose(), 'GovernorInvalidProposalLength', [1, 1, 0]); }); it('mismatch #3', async function () { @@ -547,7 +614,7 @@ contract('Governor', function (accounts) { }, '', ); - await expectRevert(this.helper.propose(), 'Governor: invalid proposal length'); + await expectRevertCustomError(this.helper.propose(), 'GovernorInvalidProposalLength', [1, 0, 1]); }); }); @@ -636,15 +703,23 @@ contract('Governor', function (accounts) { describe('onlyGovernance updates', function () { it('setVotingDelay is protected', async function () { - await expectRevert(this.mock.setVotingDelay('0'), 'Governor: onlyGovernance'); + await expectRevertCustomError(this.mock.setVotingDelay('0', { from: owner }), 'GovernorOnlyExecutor', [ + owner, + ]); }); it('setVotingPeriod is protected', async function () { - await expectRevert(this.mock.setVotingPeriod('32'), 'Governor: onlyGovernance'); + await expectRevertCustomError(this.mock.setVotingPeriod('32', { from: owner }), 'GovernorOnlyExecutor', [ + owner, + ]); }); it('setProposalThreshold is protected', async function () { - await expectRevert(this.mock.setProposalThreshold('1000000000000000000'), 'Governor: onlyGovernance'); + await expectRevertCustomError( + this.mock.setProposalThreshold('1000000000000000000', { from: owner }), + 'GovernorOnlyExecutor', + [owner], + ); }); it('can setVotingDelay through governance', async function () { @@ -690,11 +765,12 @@ contract('Governor', function (accounts) { }); it('cannot setVotingPeriod to 0 through governance', async function () { + const votingPeriod = 0; this.helper.setProposal( [ { target: this.mock.address, - data: this.mock.contract.methods.setVotingPeriod('0').encodeABI(), + data: this.mock.contract.methods.setVotingPeriod(votingPeriod).encodeABI(), }, ], '', @@ -705,7 +781,7 @@ contract('Governor', function (accounts) { await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }); await this.helper.waitForDeadline(); - await expectRevert(this.helper.execute(), 'GovernorSettings: voting period too low'); + await expectRevertCustomError(this.helper.execute(), 'GovernorInvalidVotingPeriod', [votingPeriod]); }); it('can setProposalThreshold to 0 through governance', async function () { diff --git a/test/governance/TimelockController.test.js b/test/governance/TimelockController.test.js index e9ddfaf47..d8fcdce6c 100644 --- a/test/governance/TimelockController.test.js +++ b/test/governance/TimelockController.test.js @@ -4,6 +4,8 @@ const { ZERO_ADDRESS, ZERO_BYTES32 } = constants; const { expect } = require('chai'); const { shouldSupportInterfaces } = require('../utils/introspection/SupportsInterface.behavior'); +const { expectRevertCustomError } = require('../helpers/customError'); +const { OperationState } = require('../helpers/enums'); const TimelockController = artifacts.require('TimelockController'); const CallReceiverMock = artifacts.require('CallReceiverMock'); @@ -182,7 +184,7 @@ contract('TimelockController', function (accounts) { { from: proposer }, ); - await expectRevert( + await expectRevertCustomError( this.mock.schedule( this.operation.target, this.operation.value, @@ -192,12 +194,13 @@ contract('TimelockController', function (accounts) { MINDELAY, { from: proposer }, ), - 'TimelockController: operation already scheduled', + 'TimelockUnexpectedOperationState', + [this.operation.id, OperationState.Unset], ); }); it('prevent non-proposer from committing', async function () { - await expectRevert( + await expectRevertCustomError( this.mock.schedule( this.operation.target, this.operation.value, @@ -207,12 +210,13 @@ contract('TimelockController', function (accounts) { MINDELAY, { from: other }, ), - `AccessControl: account ${other.toLowerCase()} is missing role ${PROPOSER_ROLE}`, + `AccessControlUnauthorizedAccount`, + [other, PROPOSER_ROLE], ); }); it('enforce minimum delay', async function () { - await expectRevert( + await expectRevertCustomError( this.mock.schedule( this.operation.target, this.operation.value, @@ -222,7 +226,8 @@ contract('TimelockController', function (accounts) { MINDELAY - 1, { from: proposer }, ), - 'TimelockController: insufficient delay', + 'TimelockInsufficientDelay', + [MINDELAY, MINDELAY - 1], ); }); @@ -252,7 +257,7 @@ contract('TimelockController', function (accounts) { }); it('revert if operation is not scheduled', async function () { - await expectRevert( + await expectRevertCustomError( this.mock.execute( this.operation.target, this.operation.value, @@ -261,7 +266,8 @@ contract('TimelockController', function (accounts) { this.operation.salt, { from: executor }, ), - 'TimelockController: operation is not ready', + 'TimelockUnexpectedOperationState', + [this.operation.id, OperationState.Ready], ); }); @@ -279,7 +285,7 @@ contract('TimelockController', function (accounts) { }); it('revert if execution comes too early 1/2', async function () { - await expectRevert( + await expectRevertCustomError( this.mock.execute( this.operation.target, this.operation.value, @@ -288,7 +294,8 @@ contract('TimelockController', function (accounts) { this.operation.salt, { from: executor }, ), - 'TimelockController: operation is not ready', + 'TimelockUnexpectedOperationState', + [this.operation.id, OperationState.Ready], ); }); @@ -296,7 +303,7 @@ contract('TimelockController', function (accounts) { const timestamp = await this.mock.getTimestamp(this.operation.id); await time.increaseTo(timestamp - 5); // -1 is too tight, test sometime fails - await expectRevert( + await expectRevertCustomError( this.mock.execute( this.operation.target, this.operation.value, @@ -305,7 +312,8 @@ contract('TimelockController', function (accounts) { this.operation.salt, { from: executor }, ), - 'TimelockController: operation is not ready', + 'TimelockUnexpectedOperationState', + [this.operation.id, OperationState.Ready], ); }); @@ -334,7 +342,7 @@ contract('TimelockController', function (accounts) { }); it('prevent non-executor from revealing', async function () { - await expectRevert( + await expectRevertCustomError( this.mock.execute( this.operation.target, this.operation.value, @@ -343,7 +351,8 @@ contract('TimelockController', function (accounts) { this.operation.salt, { from: other }, ), - `AccessControl: account ${other.toLowerCase()} is missing role ${EXECUTOR_ROLE}`, + `AccessControlUnauthorizedAccount`, + [other, EXECUTOR_ROLE], ); }); @@ -389,7 +398,7 @@ contract('TimelockController', function (accounts) { await reentrant.enableRentrancy(this.mock.address, data); // Expect to fail - await expectRevert( + await expectRevertCustomError( this.mock.execute( reentrantOperation.target, reentrantOperation.value, @@ -398,7 +407,8 @@ contract('TimelockController', function (accounts) { reentrantOperation.salt, { from: executor }, ), - 'TimelockController: operation is not ready', + 'TimelockUnexpectedOperationState', + [reentrantOperation.id, OperationState.Ready], ); // Disable reentrancy @@ -484,7 +494,7 @@ contract('TimelockController', function (accounts) { { from: proposer }, ); - await expectRevert( + await expectRevertCustomError( this.mock.scheduleBatch( this.operation.targets, this.operation.values, @@ -494,12 +504,13 @@ contract('TimelockController', function (accounts) { MINDELAY, { from: proposer }, ), - 'TimelockController: operation already scheduled', + 'TimelockUnexpectedOperationState', + [this.operation.id, OperationState.Unset], ); }); it('length of batch parameter must match #1', async function () { - await expectRevert( + await expectRevertCustomError( this.mock.scheduleBatch( this.operation.targets, [], @@ -509,12 +520,13 @@ contract('TimelockController', function (accounts) { MINDELAY, { from: proposer }, ), - 'TimelockController: length mismatch', + 'TimelockInvalidOperationLength', + [this.operation.targets.length, this.operation.payloads.length, 0], ); }); it('length of batch parameter must match #1', async function () { - await expectRevert( + await expectRevertCustomError( this.mock.scheduleBatch( this.operation.targets, this.operation.values, @@ -524,12 +536,13 @@ contract('TimelockController', function (accounts) { MINDELAY, { from: proposer }, ), - 'TimelockController: length mismatch', + 'TimelockInvalidOperationLength', + [this.operation.targets.length, 0, this.operation.payloads.length], ); }); it('prevent non-proposer from committing', async function () { - await expectRevert( + await expectRevertCustomError( this.mock.scheduleBatch( this.operation.targets, this.operation.values, @@ -539,12 +552,13 @@ contract('TimelockController', function (accounts) { MINDELAY, { from: other }, ), - `AccessControl: account ${other.toLowerCase()} is missing role ${PROPOSER_ROLE}`, + `AccessControlUnauthorizedAccount`, + [other, PROPOSER_ROLE], ); }); it('enforce minimum delay', async function () { - await expectRevert( + await expectRevertCustomError( this.mock.scheduleBatch( this.operation.targets, this.operation.values, @@ -554,7 +568,8 @@ contract('TimelockController', function (accounts) { MINDELAY - 1, { from: proposer }, ), - 'TimelockController: insufficient delay', + 'TimelockInsufficientDelay', + [MINDELAY, MINDELAY - 1], ); }); }); @@ -571,7 +586,7 @@ contract('TimelockController', function (accounts) { }); it('revert if operation is not scheduled', async function () { - await expectRevert( + await expectRevertCustomError( this.mock.executeBatch( this.operation.targets, this.operation.values, @@ -580,7 +595,8 @@ contract('TimelockController', function (accounts) { this.operation.salt, { from: executor }, ), - 'TimelockController: operation is not ready', + 'TimelockUnexpectedOperationState', + [this.operation.id, OperationState.Ready], ); }); @@ -598,7 +614,7 @@ contract('TimelockController', function (accounts) { }); it('revert if execution comes too early 1/2', async function () { - await expectRevert( + await expectRevertCustomError( this.mock.executeBatch( this.operation.targets, this.operation.values, @@ -607,7 +623,8 @@ contract('TimelockController', function (accounts) { this.operation.salt, { from: executor }, ), - 'TimelockController: operation is not ready', + 'TimelockUnexpectedOperationState', + [this.operation.id, OperationState.Ready], ); }); @@ -615,7 +632,7 @@ contract('TimelockController', function (accounts) { const timestamp = await this.mock.getTimestamp(this.operation.id); await time.increaseTo(timestamp - 5); // -1 is to tight, test sometime fails - await expectRevert( + await expectRevertCustomError( this.mock.executeBatch( this.operation.targets, this.operation.values, @@ -624,7 +641,8 @@ contract('TimelockController', function (accounts) { this.operation.salt, { from: executor }, ), - 'TimelockController: operation is not ready', + 'TimelockUnexpectedOperationState', + [this.operation.id, OperationState.Ready], ); }); @@ -655,7 +673,7 @@ contract('TimelockController', function (accounts) { }); it('prevent non-executor from revealing', async function () { - await expectRevert( + await expectRevertCustomError( this.mock.executeBatch( this.operation.targets, this.operation.values, @@ -664,12 +682,13 @@ contract('TimelockController', function (accounts) { this.operation.salt, { from: other }, ), - `AccessControl: account ${other.toLowerCase()} is missing role ${EXECUTOR_ROLE}`, + `AccessControlUnauthorizedAccount`, + [other, EXECUTOR_ROLE], ); }); it('length mismatch #1', async function () { - await expectRevert( + await expectRevertCustomError( this.mock.executeBatch( [], this.operation.values, @@ -678,12 +697,13 @@ contract('TimelockController', function (accounts) { this.operation.salt, { from: executor }, ), - 'TimelockController: length mismatch', + 'TimelockInvalidOperationLength', + [0, this.operation.payloads.length, this.operation.values.length], ); }); it('length mismatch #2', async function () { - await expectRevert( + await expectRevertCustomError( this.mock.executeBatch( this.operation.targets, [], @@ -692,12 +712,13 @@ contract('TimelockController', function (accounts) { this.operation.salt, { from: executor }, ), - 'TimelockController: length mismatch', + 'TimelockInvalidOperationLength', + [this.operation.targets.length, this.operation.payloads.length, 0], ); }); it('length mismatch #3', async function () { - await expectRevert( + await expectRevertCustomError( this.mock.executeBatch( this.operation.targets, this.operation.values, @@ -706,7 +727,8 @@ contract('TimelockController', function (accounts) { this.operation.salt, { from: executor }, ), - 'TimelockController: length mismatch', + 'TimelockInvalidOperationLength', + [this.operation.targets.length, 0, this.operation.values.length], ); }); @@ -752,7 +774,7 @@ contract('TimelockController', function (accounts) { await reentrant.enableRentrancy(this.mock.address, data); // Expect to fail - await expectRevert( + await expectRevertCustomError( this.mock.executeBatch( reentrantBatchOperation.targets, reentrantBatchOperation.values, @@ -761,7 +783,8 @@ contract('TimelockController', function (accounts) { reentrantBatchOperation.salt, { from: executor }, ), - 'TimelockController: operation is not ready', + 'TimelockUnexpectedOperationState', + [reentrantBatchOperation.id, OperationState.Ready], ); // Disable reentrancy @@ -796,7 +819,7 @@ contract('TimelockController', function (accounts) { [0, 0, 0], [ this.callreceivermock.contract.methods.mockFunction().encodeABI(), - this.callreceivermock.contract.methods.mockFunctionThrows().encodeABI(), + this.callreceivermock.contract.methods.mockFunctionRevertsNoReason().encodeABI(), this.callreceivermock.contract.methods.mockFunction().encodeABI(), ], ZERO_BYTES32, @@ -813,7 +836,7 @@ contract('TimelockController', function (accounts) { { from: proposer }, ); await time.increase(MINDELAY); - await expectRevert( + await expectRevertCustomError( this.mock.executeBatch( operation.targets, operation.values, @@ -822,7 +845,8 @@ contract('TimelockController', function (accounts) { operation.salt, { from: executor }, ), - 'TimelockController: underlying transaction reverted', + 'FailedInnerCall', + [], ); }); }); @@ -854,16 +878,18 @@ contract('TimelockController', function (accounts) { }); it('cannot cancel invalid operation', async function () { - await expectRevert( + await expectRevertCustomError( this.mock.cancel(constants.ZERO_BYTES32, { from: canceller }), - 'TimelockController: operation cannot be cancelled', + 'TimelockUnexpectedOperationState', + [constants.ZERO_BYTES32, OperationState.Pending], ); }); it('prevent non-canceller from canceling', async function () { - await expectRevert( + await expectRevertCustomError( this.mock.cancel(this.operation.id, { from: other }), - `AccessControl: account ${other.toLowerCase()} is missing role ${CANCELLER_ROLE}`, + `AccessControlUnauthorizedAccount`, + [other, CANCELLER_ROLE], ); }); }); @@ -871,7 +897,7 @@ contract('TimelockController', function (accounts) { describe('maintenance', function () { it('prevent unauthorized maintenance', async function () { - await expectRevert(this.mock.updateDelay(0, { from: other }), 'TimelockController: caller must be timelock'); + await expectRevertCustomError(this.mock.updateDelay(0, { from: other }), 'TimelockUnauthorizedCaller', [other]); }); it('timelock scheduled maintenance', async function () { @@ -946,7 +972,7 @@ contract('TimelockController', function (accounts) { }); it('cannot execute before dependency', async function () { - await expectRevert( + await expectRevertCustomError( this.mock.execute( this.operation2.target, this.operation2.value, @@ -955,7 +981,8 @@ contract('TimelockController', function (accounts) { this.operation2.salt, { from: executor }, ), - 'TimelockController: missing dependency', + 'TimelockUnexecutedPredecessor', + [this.operation1.id], ); }); @@ -1032,11 +1059,12 @@ contract('TimelockController', function (accounts) { { from: proposer }, ); await time.increase(MINDELAY); - await expectRevert( + await expectRevertCustomError( this.mock.execute(operation.target, operation.value, operation.data, operation.predecessor, operation.salt, { from: executor, }), - 'TimelockController: underlying transaction reverted', + 'FailedInnerCall', + [], ); }); @@ -1059,11 +1087,11 @@ contract('TimelockController', function (accounts) { { from: proposer }, ); await time.increase(MINDELAY); - await expectRevert( + // Targeted function reverts with a panic code (0x1) + the timelock bubble the panic code + await expectRevert.unspecified( this.mock.execute(operation.target, operation.value, operation.data, operation.predecessor, operation.salt, { from: executor, }), - 'TimelockController: underlying transaction reverted', ); }); @@ -1086,12 +1114,13 @@ contract('TimelockController', function (accounts) { { from: proposer }, ); await time.increase(MINDELAY); - await expectRevert( + await expectRevertCustomError( this.mock.execute(operation.target, operation.value, operation.data, operation.predecessor, operation.salt, { from: executor, - gas: '70000', + gas: '100000', }), - 'TimelockController: underlying transaction reverted', + 'FailedInnerCall', + [], ); }); @@ -1154,11 +1183,12 @@ contract('TimelockController', function (accounts) { expect(await web3.eth.getBalance(this.mock.address)).to.be.bignumber.equal(web3.utils.toBN(0)); expect(await web3.eth.getBalance(this.callreceivermock.address)).to.be.bignumber.equal(web3.utils.toBN(0)); - await expectRevert( + await expectRevertCustomError( this.mock.execute(operation.target, operation.value, operation.data, operation.predecessor, operation.salt, { from: executor, }), - 'TimelockController: underlying transaction reverted', + 'FailedInnerCall', + [], ); expect(await web3.eth.getBalance(this.mock.address)).to.be.bignumber.equal(web3.utils.toBN(0)); @@ -1188,11 +1218,12 @@ contract('TimelockController', function (accounts) { expect(await web3.eth.getBalance(this.mock.address)).to.be.bignumber.equal(web3.utils.toBN(0)); expect(await web3.eth.getBalance(this.callreceivermock.address)).to.be.bignumber.equal(web3.utils.toBN(0)); - await expectRevert( + await expectRevertCustomError( this.mock.execute(operation.target, operation.value, operation.data, operation.predecessor, operation.salt, { from: executor, }), - 'TimelockController: underlying transaction reverted', + 'FailedInnerCall', + [], ); expect(await web3.eth.getBalance(this.mock.address)).to.be.bignumber.equal(web3.utils.toBN(0)); diff --git a/test/governance/compatibility/GovernorCompatibilityBravo.test.js b/test/governance/compatibility/GovernorCompatibilityBravo.test.js index 9c45277d1..4182dfb4e 100644 --- a/test/governance/compatibility/GovernorCompatibilityBravo.test.js +++ b/test/governance/compatibility/GovernorCompatibilityBravo.test.js @@ -1,9 +1,10 @@ -const { expectEvent, expectRevert } = require('@openzeppelin/test-helpers'); +const { expectEvent } = require('@openzeppelin/test-helpers'); const { expect } = require('chai'); const RLP = require('rlp'); const Enums = require('../../helpers/enums'); const { GovernorHelper } = require('../../helpers/governance'); const { clockFromReceipt } = require('../../helpers/time'); +const { expectRevertCustomError } = require('../../helpers/customError'); const Timelock = artifacts.require('CompTimelock'); const Governor = artifacts.require('$GovernorCompatibilityBravoMock'); @@ -38,6 +39,16 @@ contract('GovernorCompatibilityBravo', function (accounts) { const proposalThreshold = web3.utils.toWei('10'); const value = web3.utils.toWei('1'); + const votes = { + [owner]: tokenSupply, + [proposer]: proposalThreshold, + [voter1]: web3.utils.toWei('10'), + [voter2]: web3.utils.toWei('7'), + [voter3]: web3.utils.toWei('5'), + [voter4]: web3.utils.toWei('2'), + [other]: 0, + }; + for (const { mode, Token } of TOKENS) { describe(`using ${Token._json.contractName}`, function () { beforeEach(async function () { @@ -65,11 +76,11 @@ contract('GovernorCompatibilityBravo', function (accounts) { await web3.eth.sendTransaction({ from: owner, to: this.timelock.address, value }); await this.token.$_mint(owner, tokenSupply); - await this.helper.delegate({ token: this.token, to: proposer, value: proposalThreshold }, { from: owner }); - await this.helper.delegate({ token: this.token, to: voter1, value: web3.utils.toWei('10') }, { from: owner }); - await this.helper.delegate({ token: this.token, to: voter2, value: web3.utils.toWei('7') }, { from: owner }); - await this.helper.delegate({ token: this.token, to: voter3, value: web3.utils.toWei('5') }, { from: owner }); - await this.helper.delegate({ token: this.token, to: voter4, value: web3.utils.toWei('2') }, { from: owner }); + await this.helper.delegate({ token: this.token, to: proposer, value: votes[proposer] }, { from: owner }); + await this.helper.delegate({ token: this.token, to: voter1, value: votes[voter1] }, { from: owner }); + await this.helper.delegate({ token: this.token, to: voter2, value: votes[voter2] }, { from: owner }); + await this.helper.delegate({ token: this.token, to: voter3, value: votes[voter3] }, { from: owner }); + await this.helper.delegate({ token: this.token, to: voter4, value: votes[voter4] }, { from: owner }); // default proposal this.proposal = this.helper.setProposal( @@ -182,9 +193,10 @@ contract('GovernorCompatibilityBravo', function (accounts) { await this.helper.propose({ from: proposer }); await this.helper.waitForSnapshot(); await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }); - await expectRevert( + await expectRevertCustomError( this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }), - 'GovernorCompatibilityBravo: vote already cast', + 'GovernorAlreadyCastVote', + [voter1], ); }); @@ -226,36 +238,43 @@ contract('GovernorCompatibilityBravo', function (accounts) { it('with inconsistent array size for selector and arguments', async function () { const target = this.receiver.address; + const signatures = ['mockFunction()']; // One signature + const data = ['0x', this.receiver.contract.methods.mockFunctionWithArgs(17, 42).encodeABI()]; // Two data entries this.helper.setProposal( { targets: [target, target], values: [0, 0], - signatures: ['mockFunction()'], // One signature - data: ['0x', this.receiver.contract.methods.mockFunctionWithArgs(17, 42).encodeABI()], // Two data entries + signatures, + data, }, '', ); - await expectRevert(this.helper.propose({ from: proposer }), 'GovernorBravo: invalid signatures length'); + await expectRevertCustomError(this.helper.propose({ from: proposer }), 'GovernorInvalidSignaturesLength', [ + signatures.length, + data.length, + ]); }); describe('should revert', function () { describe('on propose', function () { it('if proposal does not meet proposalThreshold', async function () { - await expectRevert( - this.helper.propose({ from: other }), - 'Governor: proposer votes below proposal threshold', - ); + await expectRevertCustomError(this.helper.propose({ from: other }), 'GovernorInsufficientProposerVotes', [ + other, + votes[other], + proposalThreshold, + ]); }); }); describe('on vote', function () { - it('if vote type is invalide', async function () { + it('if vote type is invalid', async function () { await this.helper.propose({ from: proposer }); await this.helper.waitForSnapshot(); - await expectRevert( + await expectRevertCustomError( this.helper.vote({ support: 5 }, { from: voter1 }), - 'GovernorCompatibilityBravo: invalid vote type', + 'GovernorInvalidVoteType', + [], ); }); }); @@ -275,7 +294,11 @@ contract('GovernorCompatibilityBravo', function (accounts) { it('cannot cancel is proposer is still above threshold', async function () { await this.helper.propose({ from: proposer }); - await expectRevert(this.helper.cancel('external'), 'GovernorBravo: proposer above threshold'); + await expectRevertCustomError(this.helper.cancel('external'), 'GovernorInsufficientProposerVotes', [ + proposer, + votes[proposer], + proposalThreshold, + ]); }); }); }); diff --git a/test/governance/extensions/GovernorPreventLateQuorum.test.js b/test/governance/extensions/GovernorPreventLateQuorum.test.js index 4df5adb1c..17ae05a73 100644 --- a/test/governance/extensions/GovernorPreventLateQuorum.test.js +++ b/test/governance/extensions/GovernorPreventLateQuorum.test.js @@ -1,9 +1,10 @@ -const { expectEvent, expectRevert } = require('@openzeppelin/test-helpers'); +const { expectEvent } = require('@openzeppelin/test-helpers'); const { expect } = require('chai'); const Enums = require('../../helpers/enums'); const { GovernorHelper } = require('../../helpers/governance'); const { clockFromReceipt } = require('../../helpers/time'); +const { expectRevertCustomError } = require('../../helpers/customError'); const Governor = artifacts.require('$GovernorPreventLateQuorumMock'); const CallReceiver = artifacts.require('CallReceiverMock'); @@ -158,7 +159,11 @@ contract('GovernorPreventLateQuorum', function (accounts) { describe('onlyGovernance updates', function () { it('setLateQuorumVoteExtension is protected', async function () { - await expectRevert(this.mock.setLateQuorumVoteExtension(0), 'Governor: onlyGovernance'); + await expectRevertCustomError( + this.mock.setLateQuorumVoteExtension(0, { from: owner }), + 'GovernorOnlyExecutor', + [owner], + ); }); it('can setLateQuorumVoteExtension through governance', async function () { diff --git a/test/governance/extensions/GovernorTimelockCompound.test.js b/test/governance/extensions/GovernorTimelockCompound.test.js index 2cbce2600..8c6680aa8 100644 --- a/test/governance/extensions/GovernorTimelockCompound.test.js +++ b/test/governance/extensions/GovernorTimelockCompound.test.js @@ -3,7 +3,8 @@ const { expect } = require('chai'); const RLP = require('rlp'); const Enums = require('../../helpers/enums'); -const { GovernorHelper } = require('../../helpers/governance'); +const { GovernorHelper, proposalStatesToBitMap } = require('../../helpers/governance'); +const { expectRevertCustomError } = require('../../helpers/customError'); const { shouldSupportInterfaces } = require('../../utils/introspection/SupportsInterface.behavior'); @@ -129,7 +130,11 @@ contract('GovernorTimelockCompound', function (accounts) { await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }); await this.helper.waitForDeadline(); await this.helper.queue(); - await expectRevert(this.helper.queue(), 'Governor: proposal not successful'); + await expectRevertCustomError(this.helper.queue(), 'GovernorUnexpectedProposalState', [ + this.proposal.id, + Enums.ProposalState.Queued, + proposalStatesToBitMap([Enums.ProposalState.Succeeded]), + ]); }); it('if proposal contains duplicate calls', async function () { @@ -137,17 +142,14 @@ contract('GovernorTimelockCompound', function (accounts) { target: this.token.address, data: this.token.contract.methods.approve(this.receiver.address, constants.MAX_UINT256).encodeABI(), }; - this.helper.setProposal([action, action], ''); + const { id } = this.helper.setProposal([action, action], ''); await this.helper.propose(); await this.helper.waitForSnapshot(); await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }); await this.helper.waitForDeadline(); - await expectRevert( - this.helper.queue(), - 'GovernorTimelockCompound: identical proposal action already queued', - ); - await expectRevert(this.helper.execute(), 'GovernorTimelockCompound: proposal not yet queued'); + await expectRevertCustomError(this.helper.queue(), 'GovernorAlreadyQueuedProposal', [id]); + await expectRevertCustomError(this.helper.execute(), 'GovernorNotQueuedProposal', [id]); }); }); @@ -160,7 +162,7 @@ contract('GovernorTimelockCompound', function (accounts) { expect(await this.mock.state(this.proposal.id)).to.be.bignumber.equal(Enums.ProposalState.Succeeded); - await expectRevert(this.helper.execute(), 'GovernorTimelockCompound: proposal not yet queued'); + await expectRevertCustomError(this.helper.execute(), 'GovernorNotQueuedProposal', [this.proposal.id]); }); it('if too early', async function () { @@ -188,7 +190,11 @@ contract('GovernorTimelockCompound', function (accounts) { expect(await this.mock.state(this.proposal.id)).to.be.bignumber.equal(Enums.ProposalState.Expired); - await expectRevert(this.helper.execute(), 'Governor: proposal not successful'); + await expectRevertCustomError(this.helper.execute(), 'GovernorUnexpectedProposalState', [ + this.proposal.id, + Enums.ProposalState.Expired, + proposalStatesToBitMap([Enums.ProposalState.Succeeded, Enums.ProposalState.Queued]), + ]); }); it('if already executed', async function () { @@ -199,7 +205,11 @@ contract('GovernorTimelockCompound', function (accounts) { await this.helper.queue(); await this.helper.waitForEta(); await this.helper.execute(); - await expectRevert(this.helper.execute(), 'Governor: proposal not successful'); + await expectRevertCustomError(this.helper.execute(), 'GovernorUnexpectedProposalState', [ + this.proposal.id, + Enums.ProposalState.Executed, + proposalStatesToBitMap([Enums.ProposalState.Succeeded, Enums.ProposalState.Queued]), + ]); }); }); }); @@ -214,7 +224,11 @@ contract('GovernorTimelockCompound', function (accounts) { expectEvent(await this.helper.cancel('internal'), 'ProposalCanceled', { proposalId: this.proposal.id }); expect(await this.mock.state(this.proposal.id)).to.be.bignumber.equal(Enums.ProposalState.Canceled); - await expectRevert(this.helper.queue(), 'Governor: proposal not successful'); + await expectRevertCustomError(this.helper.queue(), 'GovernorUnexpectedProposalState', [ + this.proposal.id, + Enums.ProposalState.Canceled, + proposalStatesToBitMap([Enums.ProposalState.Succeeded]), + ]); }); it('cancel after queue prevents executing', async function () { @@ -227,7 +241,11 @@ contract('GovernorTimelockCompound', function (accounts) { expectEvent(await this.helper.cancel('internal'), 'ProposalCanceled', { proposalId: this.proposal.id }); expect(await this.mock.state(this.proposal.id)).to.be.bignumber.equal(Enums.ProposalState.Canceled); - await expectRevert(this.helper.execute(), 'Governor: proposal not successful'); + await expectRevertCustomError(this.helper.execute(), 'GovernorUnexpectedProposalState', [ + this.proposal.id, + Enums.ProposalState.Canceled, + proposalStatesToBitMap([Enums.ProposalState.Succeeded, Enums.ProposalState.Queued]), + ]); }); }); @@ -238,9 +256,12 @@ contract('GovernorTimelockCompound', function (accounts) { }); it('is protected', async function () { - await expectRevert( - this.mock.relay(this.token.address, 0, this.token.contract.methods.transfer(other, 1).encodeABI()), - 'Governor: onlyGovernance', + await expectRevertCustomError( + this.mock.relay(this.token.address, 0, this.token.contract.methods.transfer(other, 1).encodeABI(), { + from: owner, + }), + 'GovernorOnlyExecutor', + [owner], ); }); @@ -285,7 +306,11 @@ contract('GovernorTimelockCompound', function (accounts) { }); it('is protected', async function () { - await expectRevert(this.mock.updateTimelock(this.newTimelock.address), 'Governor: onlyGovernance'); + await expectRevertCustomError( + this.mock.updateTimelock(this.newTimelock.address, { from: owner }), + 'GovernorOnlyExecutor', + [owner], + ); }); it('can be executed through governance to', async function () { diff --git a/test/governance/extensions/GovernorTimelockControl.test.js b/test/governance/extensions/GovernorTimelockControl.test.js index af57ba90b..ef32148fa 100644 --- a/test/governance/extensions/GovernorTimelockControl.test.js +++ b/test/governance/extensions/GovernorTimelockControl.test.js @@ -2,7 +2,8 @@ const { constants, expectEvent, expectRevert, time } = require('@openzeppelin/te const { expect } = require('chai'); const Enums = require('../../helpers/enums'); -const { GovernorHelper } = require('../../helpers/governance'); +const { GovernorHelper, proposalStatesToBitMap } = require('../../helpers/governance'); +const { expectRevertCustomError } = require('../../helpers/customError'); const { shouldSupportInterfaces } = require('../../utils/introspection/SupportsInterface.behavior'); @@ -163,7 +164,11 @@ contract('GovernorTimelockControl', function (accounts) { await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }); await this.helper.waitForDeadline(); await this.helper.queue(); - await expectRevert(this.helper.queue(), 'Governor: proposal not successful'); + await expectRevertCustomError(this.helper.queue(), 'GovernorUnexpectedProposalState', [ + this.proposal.id, + Enums.ProposalState.Queued, + proposalStatesToBitMap([Enums.ProposalState.Succeeded]), + ]); }); }); @@ -176,7 +181,10 @@ contract('GovernorTimelockControl', function (accounts) { expect(await this.mock.state(this.proposal.id)).to.be.bignumber.equal(Enums.ProposalState.Succeeded); - await expectRevert(this.helper.execute(), 'TimelockController: operation is not ready'); + await expectRevertCustomError(this.helper.execute(), 'TimelockUnexpectedOperationState', [ + this.proposal.timelockid, + Enums.OperationState.Ready, + ]); }); it('if too early', async function () { @@ -188,7 +196,10 @@ contract('GovernorTimelockControl', function (accounts) { expect(await this.mock.state(this.proposal.id)).to.be.bignumber.equal(Enums.ProposalState.Queued); - await expectRevert(this.helper.execute(), 'TimelockController: operation is not ready'); + await expectRevertCustomError(this.helper.execute(), 'TimelockUnexpectedOperationState', [ + this.proposal.timelockid, + Enums.OperationState.Ready, + ]); }); it('if already executed', async function () { @@ -199,7 +210,11 @@ contract('GovernorTimelockControl', function (accounts) { await this.helper.queue(); await this.helper.waitForEta(); await this.helper.execute(); - await expectRevert(this.helper.execute(), 'Governor: proposal not successful'); + await expectRevertCustomError(this.helper.execute(), 'GovernorUnexpectedProposalState', [ + this.proposal.id, + Enums.ProposalState.Executed, + proposalStatesToBitMap([Enums.ProposalState.Succeeded, Enums.ProposalState.Queued]), + ]); }); it('if already executed by another proposer', async function () { @@ -216,7 +231,11 @@ contract('GovernorTimelockControl', function (accounts) { this.proposal.shortProposal[3], ); - await expectRevert(this.helper.execute(), 'Governor: proposal not successful'); + await expectRevertCustomError(this.helper.execute(), 'GovernorUnexpectedProposalState', [ + this.proposal.id, + Enums.ProposalState.Executed, + proposalStatesToBitMap([Enums.ProposalState.Succeeded, Enums.ProposalState.Queued]), + ]); }); }); }); @@ -231,7 +250,11 @@ contract('GovernorTimelockControl', function (accounts) { expectEvent(await this.helper.cancel('internal'), 'ProposalCanceled', { proposalId: this.proposal.id }); expect(await this.mock.state(this.proposal.id)).to.be.bignumber.equal(Enums.ProposalState.Canceled); - await expectRevert(this.helper.queue(), 'Governor: proposal not successful'); + await expectRevertCustomError(this.helper.queue(), 'GovernorUnexpectedProposalState', [ + this.proposal.id, + Enums.ProposalState.Canceled, + proposalStatesToBitMap([Enums.ProposalState.Succeeded]), + ]); }); it('cancel after queue prevents executing', async function () { @@ -244,7 +267,11 @@ contract('GovernorTimelockControl', function (accounts) { expectEvent(await this.helper.cancel('internal'), 'ProposalCanceled', { proposalId: this.proposal.id }); expect(await this.mock.state(this.proposal.id)).to.be.bignumber.equal(Enums.ProposalState.Canceled); - await expectRevert(this.helper.execute(), 'Governor: proposal not successful'); + await expectRevertCustomError(this.helper.execute(), 'GovernorUnexpectedProposalState', [ + this.proposal.id, + Enums.ProposalState.Canceled, + proposalStatesToBitMap([Enums.ProposalState.Succeeded, Enums.ProposalState.Queued]), + ]); }); it('cancel on timelock is reflected on governor', async function () { @@ -271,9 +298,12 @@ contract('GovernorTimelockControl', function (accounts) { }); it('is protected', async function () { - await expectRevert( - this.mock.relay(this.token.address, 0, this.token.contract.methods.transfer(other, 1).encodeABI()), - 'Governor: onlyGovernance', + await expectRevertCustomError( + this.mock.relay(this.token.address, 0, this.token.contract.methods.transfer(other, 1).encodeABI(), { + from: owner, + }), + 'GovernorOnlyExecutor', + [owner], ); }); @@ -346,28 +376,21 @@ contract('GovernorTimelockControl', function (accounts) { }); it('protected against other proposers', async function () { - await this.timelock.schedule( - this.mock.address, - web3.utils.toWei('0'), - this.mock.contract.methods.relay(constants.ZERO_ADDRESS, 0, '0x').encodeABI(), - constants.ZERO_BYTES32, - constants.ZERO_BYTES32, - 3600, - { from: owner }, - ); + const target = this.mock.address; + const value = web3.utils.toWei('0'); + const data = this.mock.contract.methods.relay(constants.ZERO_ADDRESS, 0, '0x').encodeABI(); + const predecessor = constants.ZERO_BYTES32; + const salt = constants.ZERO_BYTES32; + const delay = 3600; + + await this.timelock.schedule(target, value, data, predecessor, salt, delay, { from: owner }); await time.increase(3600); - await expectRevert( - this.timelock.execute( - this.mock.address, - web3.utils.toWei('0'), - this.mock.contract.methods.relay(constants.ZERO_ADDRESS, 0, '0x').encodeABI(), - constants.ZERO_BYTES32, - constants.ZERO_BYTES32, - { from: owner }, - ), - 'TimelockController: underlying transaction reverted', + await expectRevertCustomError( + this.timelock.execute(target, value, data, predecessor, salt, { from: owner }), + 'QueueEmpty', // Bubbled up from Governor + [], ); }); }); @@ -383,7 +406,11 @@ contract('GovernorTimelockControl', function (accounts) { }); it('is protected', async function () { - await expectRevert(this.mock.updateTimelock(this.newTimelock.address), 'Governor: onlyGovernance'); + await expectRevertCustomError( + this.mock.updateTimelock(this.newTimelock.address, { from: owner }), + 'GovernorOnlyExecutor', + [owner], + ); }); it('can be executed through governance to', async function () { diff --git a/test/governance/extensions/GovernorVotesQuorumFraction.test.js b/test/governance/extensions/GovernorVotesQuorumFraction.test.js index a69338ad8..ece9c78d6 100644 --- a/test/governance/extensions/GovernorVotesQuorumFraction.test.js +++ b/test/governance/extensions/GovernorVotesQuorumFraction.test.js @@ -1,9 +1,10 @@ -const { expectEvent, expectRevert, time } = require('@openzeppelin/test-helpers'); +const { expectEvent, time } = require('@openzeppelin/test-helpers'); const { expect } = require('chai'); const Enums = require('../../helpers/enums'); -const { GovernorHelper } = require('../../helpers/governance'); +const { GovernorHelper, proposalStatesToBitMap } = require('../../helpers/governance'); const { clock } = require('../../helpers/time'); +const { expectRevertCustomError } = require('../../helpers/customError'); const Governor = artifacts.require('$GovernorMock'); const CallReceiver = artifacts.require('CallReceiverMock'); @@ -84,12 +85,20 @@ contract('GovernorVotesQuorumFraction', function (accounts) { await this.helper.waitForSnapshot(); await this.helper.vote({ support: Enums.VoteType.For }, { from: voter2 }); await this.helper.waitForDeadline(); - await expectRevert(this.helper.execute(), 'Governor: proposal not successful'); + await expectRevertCustomError(this.helper.execute(), 'GovernorUnexpectedProposalState', [ + this.proposal.id, + Enums.ProposalState.Defeated, + proposalStatesToBitMap([Enums.ProposalState.Succeeded, Enums.ProposalState.Queued]), + ]); }); describe('onlyGovernance updates', function () { it('updateQuorumNumerator is protected', async function () { - await expectRevert(this.mock.updateQuorumNumerator(newRatio), 'Governor: onlyGovernance'); + await expectRevertCustomError( + this.mock.updateQuorumNumerator(newRatio, { from: owner }), + 'GovernorOnlyExecutor', + [owner], + ); }); it('can updateQuorumNumerator through governance', async function () { @@ -129,11 +138,12 @@ contract('GovernorVotesQuorumFraction', function (accounts) { }); it('cannot updateQuorumNumerator over the maximum', async function () { + const quorumNumerator = 101; this.helper.setProposal( [ { target: this.mock.address, - data: this.mock.contract.methods.updateQuorumNumerator('101').encodeABI(), + data: this.mock.contract.methods.updateQuorumNumerator(quorumNumerator).encodeABI(), }, ], '', @@ -144,10 +154,12 @@ contract('GovernorVotesQuorumFraction', function (accounts) { await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }); await this.helper.waitForDeadline(); - await expectRevert( - this.helper.execute(), - 'GovernorVotesQuorumFraction: quorumNumerator over quorumDenominator', - ); + const quorumDenominator = await this.mock.quorumDenominator(); + + await expectRevertCustomError(this.helper.execute(), 'GovernorInvalidQuorumFraction', [ + quorumNumerator, + quorumDenominator, + ]); }); }); }); diff --git a/test/governance/utils/Votes.behavior.js b/test/governance/utils/Votes.behavior.js index 37062e19c..20ebdba4f 100644 --- a/test/governance/utils/Votes.behavior.js +++ b/test/governance/utils/Votes.behavior.js @@ -1,4 +1,4 @@ -const { constants, expectEvent, expectRevert, time } = require('@openzeppelin/test-helpers'); +const { constants, expectEvent, time } = require('@openzeppelin/test-helpers'); const { MAX_UINT256, ZERO_ADDRESS } = constants; @@ -9,6 +9,7 @@ const Wallet = require('ethereumjs-wallet').default; const { shouldBehaveLikeEIP6372 } = require('./EIP6372.behavior'); const { getDomain, domainType } = require('../../helpers/eip712'); const { clockFromReceipt } = require('../../helpers/time'); +const { expectRevertCustomError } = require('../../helpers/customError'); const Delegation = [ { name: 'delegatee', type: 'address' }, @@ -176,7 +177,11 @@ function shouldBehaveLikeVotes(accounts, tokens, { mode = 'blocknumber', fungibl await this.votes.delegateBySig(delegatee, nonce, MAX_UINT256, v, r, s); - await expectRevert(this.votes.delegateBySig(delegatee, nonce, MAX_UINT256, v, r, s), 'Votes: invalid nonce'); + await expectRevertCustomError( + this.votes.delegateBySig(delegatee, nonce, MAX_UINT256, v, r, s), + 'InvalidAccountNonce', + [delegator.address, nonce + 1], + ); }); it('rejects bad delegatee', async function () { @@ -208,9 +213,10 @@ function shouldBehaveLikeVotes(accounts, tokens, { mode = 'blocknumber', fungibl delegator.getPrivateKey(), ); - await expectRevert( + await expectRevertCustomError( this.votes.delegateBySig(delegatee, nonce + 1, MAX_UINT256, v, r, s), - 'Votes: invalid nonce', + 'InvalidAccountNonce', + [delegator.address, 0], ); }); @@ -226,7 +232,11 @@ function shouldBehaveLikeVotes(accounts, tokens, { mode = 'blocknumber', fungibl delegator.getPrivateKey(), ); - await expectRevert(this.votes.delegateBySig(delegatee, nonce, expiry, v, r, s), 'Votes: signature expired'); + await expectRevertCustomError( + this.votes.delegateBySig(delegatee, nonce, expiry, v, r, s), + 'VotesExpiredSignature', + [expiry], + ); }); }); }); @@ -237,7 +247,12 @@ function shouldBehaveLikeVotes(accounts, tokens, { mode = 'blocknumber', fungibl }); it('reverts if block number >= current block', async function () { - await expectRevert(this.votes.getPastTotalSupply(5e10), 'future lookup'); + const timepoint = 5e10; + const clock = await this.votes.clock(); + await expectRevertCustomError(this.votes.getPastTotalSupply(timepoint), 'ERC5805FutureLookup', [ + timepoint, + clock, + ]); }); it('returns 0 if there are no checkpoints', async function () { @@ -285,7 +300,10 @@ function shouldBehaveLikeVotes(accounts, tokens, { mode = 'blocknumber', fungibl expect(await this.votes.getPastTotalSupply(t4.timepoint)).to.be.bignumber.equal(weight[2]); expect(await this.votes.getPastTotalSupply(t4.timepoint + 1)).to.be.bignumber.equal(weight[2]); expect(await this.votes.getPastTotalSupply(t5.timepoint)).to.be.bignumber.equal('0'); - await expectRevert(this.votes.getPastTotalSupply(t5.timepoint + 1), 'Votes: future lookup'); + await expectRevertCustomError(this.votes.getPastTotalSupply(t5.timepoint + 1), 'ERC5805FutureLookup', [ + t5.timepoint + 1, // timepoint + t5.timepoint + 1, // clock + ]); }); }); @@ -300,7 +318,12 @@ function shouldBehaveLikeVotes(accounts, tokens, { mode = 'blocknumber', fungibl describe('getPastVotes', function () { it('reverts if block number >= current block', async function () { - await expectRevert(this.votes.getPastVotes(accounts[2], 5e10), 'future lookup'); + const clock = await this.votes.clock(); + const timepoint = 5e10; // far in the future + await expectRevertCustomError(this.votes.getPastVotes(accounts[2], timepoint), 'ERC5805FutureLookup', [ + timepoint, + clock, + ]); }); it('returns 0 if there are no checkpoints', async function () { diff --git a/test/governance/utils/Votes.test.js b/test/governance/utils/Votes.test.js index 184ce0cec..b2b80f9fe 100644 --- a/test/governance/utils/Votes.test.js +++ b/test/governance/utils/Votes.test.js @@ -1,7 +1,8 @@ -const { constants, expectRevert } = require('@openzeppelin/test-helpers'); +const { constants } = require('@openzeppelin/test-helpers'); const { expect } = require('chai'); const { clockFromReceipt } = require('../../helpers/time'); const { BNsum } = require('../../helpers/math'); +const { expectRevertCustomError } = require('../../helpers/customError'); require('array.prototype.at/auto'); @@ -45,7 +46,11 @@ contract('Votes', function (accounts) { it('reverts if block number >= current block', async function () { const lastTxTimepoint = await clockFromReceipt[mode](this.txs.at(-1).receipt); - await expectRevert(this.votes.getPastTotalSupply(lastTxTimepoint + 1), 'Votes: future lookup'); + const clock = await this.votes.clock(); + await expectRevertCustomError(this.votes.getPastTotalSupply(lastTxTimepoint + 1), 'ERC5805FutureLookup', [ + lastTxTimepoint + 1, + clock, + ]); }); it('delegates', async function () { diff --git a/test/helpers/customError.js b/test/helpers/customError.js index 3cfcd7277..a193ab0cf 100644 --- a/test/helpers/customError.js +++ b/test/helpers/customError.js @@ -1,21 +1,42 @@ -const { config } = require('hardhat'); - -const optimizationsEnabled = config.solidity.compilers.some(c => c.settings.optimizer.enabled); +const { expect } = require('chai'); /** Revert handler that supports custom errors. */ -async function expectRevertCustomError(promise, reason) { +async function expectRevertCustomError(promise, expectedErrorName, args) { try { await promise; expect.fail("Expected promise to throw but it didn't"); } catch (revert) { - if (reason) { - if (optimizationsEnabled) { - // Optimizations currently mess with Hardhat's decoding of custom errors - expect(revert.message).to.include.oneOf([reason, 'unrecognized return data or custom error']); - } else { - expect(revert.message).to.include(reason); - } + if (!Array.isArray(args)) { + expect.fail('Expected 3rd array parameter for error arguments'); + } + // The revert message for custom errors looks like: + // VM Exception while processing transaction: + // reverted with custom error 'InvalidAccountNonce("0x70997970C51812dc3A010C7d01b50e0d17dc79C8", 0)' + + // We trim out anything inside the single quotes as comma-separated values + const [, error] = revert.message.match(/'(.*)'/); + + // Attempt to parse as an error + const match = error.match(/(?\w+)\((?.*)\)/); + if (!match) { + expect.fail(`Couldn't parse "${error}" as a custom error`); } + // Extract the error name and parameters + const errorName = match.groups.name; + const argMatches = [...match.groups.args.matchAll(/-?\w+/g)]; + + // Assert error name + expect(errorName).to.be.equal( + expectedErrorName, + `Unexpected custom error name (with found args: [${argMatches.map(([a]) => a)}])`, + ); + + // Coerce to string for comparison since `arg` can be either a number or hex. + const sanitizedExpected = args.map(arg => arg.toString().toLowerCase()); + const sanitizedActual = argMatches.map(([arg]) => arg.toString().toLowerCase()); + + // Assert argument equality + expect(sanitizedActual).to.have.members(sanitizedExpected, `Unexpected ${errorName} arguments`); } } diff --git a/test/helpers/enums.js b/test/helpers/enums.js index cc650abf4..d4a4fdbd0 100644 --- a/test/helpers/enums.js +++ b/test/helpers/enums.js @@ -7,4 +7,5 @@ module.exports = { ProposalState: Enum('Pending', 'Active', 'Canceled', 'Defeated', 'Succeeded', 'Queued', 'Expired', 'Executed'), VoteType: Enum('Against', 'For', 'Abstain'), Rounding: Enum('Down', 'Up', 'Zero'), + OperationState: Enum('Unset', 'Pending', 'Ready', 'Done'), }; diff --git a/test/helpers/governance.js b/test/helpers/governance.js index 4b38b7588..665c21605 100644 --- a/test/helpers/governance.js +++ b/test/helpers/governance.js @@ -1,4 +1,5 @@ const { forward } = require('../helpers/time'); +const { ProposalState } = require('./enums'); function zip(...args) { return Array(Math.max(...args.map(array => array.length))) @@ -196,6 +197,44 @@ class GovernorHelper { } } +/** + * Encodes a list ProposalStates into a bytes32 representation where each bit enabled corresponds to + * the underlying position in the `ProposalState` enum. For example: + * + * 0x000...10000 + * ^^^^^^------ ... + * ^----- Succeeded + * ^---- Defeated + * ^--- Canceled + * ^-- Active + * ^- Pending + */ +function proposalStatesToBitMap(proposalStates, options = {}) { + if (!Array.isArray(proposalStates)) { + proposalStates = [proposalStates]; + } + const statesCount = Object.keys(ProposalState).length; + let result = 0; + + const uniqueProposalStates = new Set(proposalStates.map(bn => bn.toNumber())); // Remove duplicates + for (const state of uniqueProposalStates) { + if (state < 0 || state >= statesCount) { + expect.fail(`ProposalState ${state} out of possible states (0...${statesCount}-1)`); + } else { + result |= 1 << state; + } + } + + if (options.inverted) { + const mask = 2 ** statesCount - 1; + result = result ^ mask; + } + + const hex = web3.utils.numberToHex(result); + return web3.utils.padLeft(hex, 64); +} + module.exports = { GovernorHelper, + proposalStatesToBitMap, }; diff --git a/test/metatx/MinimalForwarder.test.js b/test/metatx/MinimalForwarder.test.js index 4884cc760..c775c5e44 100644 --- a/test/metatx/MinimalForwarder.test.js +++ b/test/metatx/MinimalForwarder.test.js @@ -1,8 +1,9 @@ const ethSigUtil = require('eth-sig-util'); const Wallet = require('ethereumjs-wallet').default; const { getDomain, domainType } = require('../helpers/eip712'); +const { expectRevertCustomError } = require('../helpers/customError'); -const { expectRevert, constants } = require('@openzeppelin/test-helpers'); +const { constants, expectRevert } = require('@openzeppelin/test-helpers'); const { expect } = require('chai'); const MinimalForwarder = artifacts.require('MinimalForwarder'); @@ -27,6 +28,14 @@ contract('MinimalForwarder', function (accounts) { }); context('with message', function () { + const tamperedValues = { + from: accounts[0], + to: accounts[0], + value: web3.utils.toWei('1'), + nonce: 1234, + data: '0x1742', + }; + beforeEach(async function () { this.wallet = Wallet.generate(); this.sender = web3.utils.toChecksumAddress(this.wallet.getAddressString()); @@ -38,14 +47,15 @@ contract('MinimalForwarder', function (accounts) { nonce: Number(await this.forwarder.getNonce(this.sender)), data: '0x', }; - this.sign = () => + this.forgeData = req => ({ + types: this.types, + domain: this.domain, + primaryType: 'ForwardRequest', + message: { ...this.req, ...req }, + }); + this.sign = req => ethSigUtil.signTypedMessage(this.wallet.getPrivateKey(), { - data: { - types: this.types, - domain: this.domain, - primaryType: 'ForwardRequest', - message: this.req, - }, + data: this.forgeData(req), }); }); @@ -64,31 +74,29 @@ contract('MinimalForwarder', function (accounts) { }); }); - context('invalid signature', function () { - it('tampered from', async function () { - expect(await this.forwarder.verify({ ...this.req, from: accounts[0] }, this.sign())).to.be.equal(false); - }); - it('tampered to', async function () { - expect(await this.forwarder.verify({ ...this.req, to: accounts[0] }, this.sign())).to.be.equal(false); - }); - it('tampered value', async function () { - expect(await this.forwarder.verify({ ...this.req, value: web3.utils.toWei('1') }, this.sign())).to.be.equal( - false, - ); - }); - it('tampered nonce', async function () { - expect(await this.forwarder.verify({ ...this.req, nonce: this.req.nonce + 1 }, this.sign())).to.be.equal( - false, - ); - }); - it('tampered data', async function () { - expect(await this.forwarder.verify({ ...this.req, data: '0x1742' }, this.sign())).to.be.equal(false); - }); - it('tampered signature', async function () { + context('with tampered values', function () { + for (const [key, value] of Object.entries(tamperedValues)) { + it(`returns false with tampered ${key}`, async function () { + expect(await this.forwarder.verify(this.forgeData({ [key]: value }).message, this.sign())).to.be.equal( + false, + ); + }); + } + + it('returns false with tampered signature', async function () { const tamperedsign = web3.utils.hexToBytes(this.sign()); tamperedsign[42] ^= 0xff; expect(await this.forwarder.verify(this.req, web3.utils.bytesToHex(tamperedsign))).to.be.equal(false); }); + + it('returns false with valid signature for non-current nonce', async function () { + const req = { + ...this.req, + nonce: this.req.nonce + 1, + }; + const sig = this.sign(req); + expect(await this.forwarder.verify(req, sig)).to.be.equal(false); + }); }); }); @@ -109,44 +117,38 @@ contract('MinimalForwarder', function (accounts) { }); }); - context('invalid signature', function () { - it('tampered from', async function () { - await expectRevert( - this.forwarder.execute({ ...this.req, from: accounts[0] }, this.sign()), - 'MinimalForwarder: signature does not match request', + context('with tampered values', function () { + for (const [key, value] of Object.entries(tamperedValues)) { + it(`reverts with tampered ${key}`, async function () { + const sig = this.sign(); + const data = this.forgeData({ [key]: value }); + await expectRevertCustomError(this.forwarder.execute(data.message, sig), 'MinimalForwarderInvalidSigner', [ + ethSigUtil.recoverTypedSignature({ data, sig }), + data.message.from, + ]); + }); + } + + it('reverts with tampered signature', async function () { + const tamperedSig = web3.utils.hexToBytes(this.sign()); + tamperedSig[42] ^= 0xff; + await expectRevertCustomError( + this.forwarder.execute(this.req, web3.utils.bytesToHex(tamperedSig)), + 'MinimalForwarderInvalidSigner', + [ethSigUtil.recoverTypedSignature({ data: this.forgeData(), sig: tamperedSig }), this.req.from], ); }); - it('tampered to', async function () { - await expectRevert( - this.forwarder.execute({ ...this.req, to: accounts[0] }, this.sign()), - 'MinimalForwarder: signature does not match request', - ); - }); - it('tampered value', async function () { - await expectRevert( - this.forwarder.execute({ ...this.req, value: web3.utils.toWei('1') }, this.sign()), - 'MinimalForwarder: signature does not match request', - ); - }); - it('tampered nonce', async function () { - await expectRevert( - this.forwarder.execute({ ...this.req, nonce: this.req.nonce + 1 }, this.sign()), - 'MinimalForwarder: signature does not match request', - ); - }); - it('tampered data', async function () { - await expectRevert( - this.forwarder.execute({ ...this.req, data: '0x1742' }, this.sign()), - 'MinimalForwarder: signature does not match request', - ); - }); - it('tampered signature', async function () { - const tamperedsign = web3.utils.hexToBytes(this.sign()); - tamperedsign[42] ^= 0xff; - await expectRevert( - this.forwarder.execute(this.req, web3.utils.bytesToHex(tamperedsign)), - 'MinimalForwarder: signature does not match request', - ); + + it('reverts with valid signature for non-current nonce', async function () { + const req = { + ...this.req, + nonce: this.req.nonce + 1, + }; + const sig = this.sign(req); + await expectRevertCustomError(this.forwarder.execute(req, sig), 'MinimalForwarderInvalidNonce', [ + this.req.from, + this.req.nonce, + ]); }); }); diff --git a/test/proxy/Clones.test.js b/test/proxy/Clones.test.js index 947b2ed95..2edd1999c 100644 --- a/test/proxy/Clones.test.js +++ b/test/proxy/Clones.test.js @@ -1,7 +1,9 @@ -const { expectEvent, expectRevert } = require('@openzeppelin/test-helpers'); +const { expectEvent } = require('@openzeppelin/test-helpers'); const { computeCreate2Address } = require('../helpers/create2'); const { expect } = require('chai'); +const { expectRevertCustomError } = require('../helpers/customError'); + const shouldBehaveLikeClone = require('./Clones.behaviour'); const Clones = artifacts.require('$Clones'); @@ -36,7 +38,7 @@ contract('Clones', function (accounts) { // deploy once expectEvent(await factory.$cloneDeterministic(implementation, salt), 'return$cloneDeterministic'); // deploy twice - await expectRevert(factory.$cloneDeterministic(implementation, salt), 'ERC1167: create2 failed'); + await expectRevertCustomError(factory.$cloneDeterministic(implementation, salt), 'ERC1167FailedCreateClone', []); }); it('address prediction', async function () { diff --git a/test/proxy/beacon/BeaconProxy.test.js b/test/proxy/beacon/BeaconProxy.test.js index 68db10ddc..63d982397 100644 --- a/test/proxy/beacon/BeaconProxy.test.js +++ b/test/proxy/beacon/BeaconProxy.test.js @@ -1,6 +1,8 @@ const { expectRevert } = require('@openzeppelin/test-helpers'); const { getSlot, BeaconSlot } = require('../../helpers/erc1967'); +const { expectRevertCustomError } = require('../../helpers/customError'); + const { expect } = require('chai'); const UpgradeableBeacon = artifacts.require('UpgradeableBeacon'); @@ -15,7 +17,7 @@ contract('BeaconProxy', function (accounts) { describe('bad beacon is not accepted', async function () { it('non-contract beacon', async function () { - await expectRevert(BeaconProxy.new(anotherAccount, '0x'), 'ERC1967: new beacon is not a contract'); + await expectRevertCustomError(BeaconProxy.new(anotherAccount, '0x'), 'ERC1967InvalidBeacon', [anotherAccount]); }); it('non-compliant beacon', async function () { @@ -25,7 +27,10 @@ contract('BeaconProxy', function (accounts) { it('non-contract implementation', async function () { const beacon = await BadBeaconNotContract.new(); - await expectRevert(BeaconProxy.new(beacon.address, '0x'), 'ERC1967: beacon implementation is not a contract'); + const implementation = await beacon.implementation(); + await expectRevertCustomError(BeaconProxy.new(beacon.address, '0x'), 'ERC1967InvalidImplementation', [ + implementation, + ]); }); }); diff --git a/test/proxy/beacon/UpgradeableBeacon.test.js b/test/proxy/beacon/UpgradeableBeacon.test.js index c19b250b9..4c58f1740 100644 --- a/test/proxy/beacon/UpgradeableBeacon.test.js +++ b/test/proxy/beacon/UpgradeableBeacon.test.js @@ -1,6 +1,8 @@ -const { expectRevert, expectEvent } = require('@openzeppelin/test-helpers'); +const { expectEvent } = require('@openzeppelin/test-helpers'); const { expect } = require('chai'); +const { expectRevertCustomError } = require('../../helpers/customError'); + const UpgradeableBeacon = artifacts.require('UpgradeableBeacon'); const Implementation1 = artifacts.require('Implementation1'); const Implementation2 = artifacts.require('Implementation2'); @@ -9,10 +11,7 @@ contract('UpgradeableBeacon', function (accounts) { const [owner, other] = accounts; it('cannot be created with non-contract implementation', async function () { - await expectRevert( - UpgradeableBeacon.new(accounts[0], owner), - 'UpgradeableBeacon: implementation is not a contract', - ); + await expectRevertCustomError(UpgradeableBeacon.new(other, owner), 'BeaconInvalidImplementation', [other]); }); context('once deployed', async function () { @@ -33,15 +32,16 @@ contract('UpgradeableBeacon', function (accounts) { }); it('cannot be upgraded to a non-contract', async function () { - await expectRevert( - this.beacon.upgradeTo(other, { from: owner }), - 'UpgradeableBeacon: implementation is not a contract', - ); + await expectRevertCustomError(this.beacon.upgradeTo(other, { from: owner }), 'BeaconInvalidImplementation', [ + other, + ]); }); it('cannot be upgraded by other account', async function () { const v2 = await Implementation2.new(); - await expectRevert(this.beacon.upgradeTo(v2.address, { from: other }), 'Ownable: caller is not the owner'); + await expectRevertCustomError(this.beacon.upgradeTo(v2.address, { from: other }), 'OwnableUnauthorizedAccount', [ + other, + ]); }); }); }); diff --git a/test/proxy/transparent/ProxyAdmin.test.js b/test/proxy/transparent/ProxyAdmin.test.js index 85b6695df..d660ffc56 100644 --- a/test/proxy/transparent/ProxyAdmin.test.js +++ b/test/proxy/transparent/ProxyAdmin.test.js @@ -1,5 +1,4 @@ const { expectRevert } = require('@openzeppelin/test-helpers'); -const { getAddressInSlot, ImplementationSlot, AdminSlot } = require('../../helpers/erc1967'); const { expect } = require('chai'); const ImplV1 = artifacts.require('DummyImplementation'); const ImplV2 = artifacts.require('DummyImplementationV2'); @@ -7,6 +6,9 @@ const ProxyAdmin = artifacts.require('ProxyAdmin'); const TransparentUpgradeableProxy = artifacts.require('TransparentUpgradeableProxy'); const ITransparentUpgradeableProxy = artifacts.require('ITransparentUpgradeableProxy'); +const { getAddressInSlot, ImplementationSlot, AdminSlot } = require('../../helpers/erc1967'); +const { expectRevertCustomError } = require('../../helpers/customError'); + contract('ProxyAdmin', function (accounts) { const [proxyAdminOwner, newAdmin, anotherAccount] = accounts; @@ -32,9 +34,10 @@ contract('ProxyAdmin', function (accounts) { describe('#changeProxyAdmin', function () { it('fails to change proxy admin if its not the proxy owner', async function () { - await expectRevert( + await expectRevertCustomError( this.proxyAdmin.changeProxyAdmin(this.proxy.address, newAdmin, { from: anotherAccount }), - 'caller is not the owner', + 'OwnableUnauthorizedAccount', + [anotherAccount], ); }); @@ -49,9 +52,10 @@ contract('ProxyAdmin', function (accounts) { describe('#upgrade', function () { context('with unauthorized account', function () { it('fails to upgrade', async function () { - await expectRevert( + await expectRevertCustomError( this.proxyAdmin.upgrade(this.proxy.address, this.implementationV2.address, { from: anotherAccount }), - 'caller is not the owner', + 'OwnableUnauthorizedAccount', + [anotherAccount], ); }); }); @@ -70,11 +74,12 @@ contract('ProxyAdmin', function (accounts) { context('with unauthorized account', function () { it('fails to upgrade', async function () { const callData = new ImplV1('').contract.methods.initializeNonPayableWithValue(1337).encodeABI(); - await expectRevert( + await expectRevertCustomError( this.proxyAdmin.upgradeAndCall(this.proxy.address, this.implementationV2.address, callData, { from: anotherAccount, }), - 'caller is not the owner', + 'OwnableUnauthorizedAccount', + [anotherAccount], ); }); }); diff --git a/test/proxy/transparent/TransparentUpgradeableProxy.behaviour.js b/test/proxy/transparent/TransparentUpgradeableProxy.behaviour.js index 4012cfffc..c6e949156 100644 --- a/test/proxy/transparent/TransparentUpgradeableProxy.behaviour.js +++ b/test/proxy/transparent/TransparentUpgradeableProxy.behaviour.js @@ -1,6 +1,7 @@ const { BN, expectRevert, expectEvent, constants } = require('@openzeppelin/test-helpers'); const { ZERO_ADDRESS } = constants; const { getAddressInSlot, ImplementationSlot, AdminSlot } = require('../../helpers/erc1967'); +const { expectRevertCustomError } = require('../../helpers/customError'); const { expect } = require('chai'); const { web3 } = require('hardhat'); @@ -67,10 +68,9 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy(createProx describe('when the given implementation is the zero address', function () { it('reverts', async function () { - await expectRevert( - this.proxy.upgradeTo(ZERO_ADDRESS, { from }), - 'ERC1967: new implementation is not a contract', - ); + await expectRevertCustomError(this.proxy.upgradeTo(ZERO_ADDRESS, { from }), 'ERC1967InvalidImplementation', [ + ZERO_ADDRESS, + ]); }); }); }); @@ -289,9 +289,10 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy(createProx describe('when the new proposed admin is the zero address', function () { it('reverts', async function () { - await expectRevert( + await expectRevertCustomError( this.proxy.changeAdmin(ZERO_ADDRESS, { from: proxyAdminAddress }), - 'ERC1967: new admin is the zero address', + 'ERC1967InvalidAdmin', + [ZERO_ADDRESS], ); }); }); @@ -306,9 +307,10 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy(createProx }); it('proxy admin cannot call delegated functions', async function () { - await expectRevert( + await expectRevertCustomError( this.clashing.delegatedFunction({ from: proxyAdminAddress }), - 'TransparentUpgradeableProxy: admin cannot fallback to proxy target', + 'ProxyDeniedAdminAccess', + [], ); }); diff --git a/test/proxy/utils/Initializable.test.js b/test/proxy/utils/Initializable.test.js index 39c820b9d..e3e0fc02f 100644 --- a/test/proxy/utils/Initializable.test.js +++ b/test/proxy/utils/Initializable.test.js @@ -1,5 +1,6 @@ -const { expectEvent, expectRevert } = require('@openzeppelin/test-helpers'); +const { expectEvent } = require('@openzeppelin/test-helpers'); const { expect } = require('chai'); +const { expectRevertCustomError } = require('../../helpers/customError'); const InitializableMock = artifacts.require('InitializableMock'); const ConstructorInitializableMock = artifacts.require('ConstructorInitializableMock'); @@ -40,13 +41,13 @@ contract('Initializable', function () { }); it('initializer does not run again', async function () { - await expectRevert(this.contract.initialize(), 'Initializable: contract is already initialized'); + await expectRevertCustomError(this.contract.initialize(), 'AlreadyInitialized', []); }); }); describe('nested under an initializer', function () { it('initializer modifier reverts', async function () { - await expectRevert(this.contract.initializerNested(), 'Initializable: contract is already initialized'); + await expectRevertCustomError(this.contract.initializerNested(), 'AlreadyInitialized', []); }); it('onlyInitializing modifier succeeds', async function () { @@ -56,7 +57,7 @@ contract('Initializable', function () { }); it('cannot call onlyInitializable function outside the scope of an initializable function', async function () { - await expectRevert(this.contract.initializeOnlyInitializing(), 'Initializable: contract is not initializing'); + await expectRevertCustomError(this.contract.initializeOnlyInitializing(), 'NotInitializing', []); }); }); @@ -98,9 +99,9 @@ contract('Initializable', function () { it('cannot nest reinitializers', async function () { expect(await this.contract.counter()).to.be.bignumber.equal('0'); - await expectRevert(this.contract.nestedReinitialize(2, 2), 'Initializable: contract is already initialized'); - await expectRevert(this.contract.nestedReinitialize(2, 3), 'Initializable: contract is already initialized'); - await expectRevert(this.contract.nestedReinitialize(3, 2), 'Initializable: contract is already initialized'); + await expectRevertCustomError(this.contract.nestedReinitialize(2, 2), 'AlreadyInitialized', []); + await expectRevertCustomError(this.contract.nestedReinitialize(2, 3), 'AlreadyInitialized', []); + await expectRevertCustomError(this.contract.nestedReinitialize(3, 2), 'AlreadyInitialized', []); }); it('can chain reinitializers', async function () { @@ -119,18 +120,18 @@ contract('Initializable', function () { describe('contract locking', function () { it('prevents initialization', async function () { await this.contract.disableInitializers(); - await expectRevert(this.contract.initialize(), 'Initializable: contract is already initialized'); + await expectRevertCustomError(this.contract.initialize(), 'AlreadyInitialized', []); }); it('prevents re-initialization', async function () { await this.contract.disableInitializers(); - await expectRevert(this.contract.reinitialize(255), 'Initializable: contract is already initialized'); + await expectRevertCustomError(this.contract.reinitialize(255), 'AlreadyInitialized', []); }); it('can lock contract after initialization', async function () { await this.contract.initialize(); await this.contract.disableInitializers(); - await expectRevert(this.contract.reinitialize(255), 'Initializable: contract is already initialized'); + await expectRevertCustomError(this.contract.reinitialize(255), 'AlreadyInitialized', []); }); }); }); @@ -205,8 +206,8 @@ contract('Initializable', function () { describe('disabling initialization', function () { it('old and new patterns in bad sequence', async function () { - await expectRevert(DisableBad1.new(), 'Initializable: contract is already initialized'); - await expectRevert(DisableBad2.new(), 'Initializable: contract is initializing'); + await expectRevertCustomError(DisableBad1.new(), 'AlreadyInitialized', []); + await expectRevertCustomError(DisableBad2.new(), 'AlreadyInitialized', []); }); it('old and new patterns in good sequence', async function () { diff --git a/test/proxy/utils/UUPSUpgradeable.test.js b/test/proxy/utils/UUPSUpgradeable.test.js index b0c1b3f6f..ea1b1d51f 100644 --- a/test/proxy/utils/UUPSUpgradeable.test.js +++ b/test/proxy/utils/UUPSUpgradeable.test.js @@ -1,6 +1,7 @@ -const { expectEvent, expectRevert } = require('@openzeppelin/test-helpers'); +const { expectEvent } = require('@openzeppelin/test-helpers'); const { web3 } = require('@openzeppelin/test-helpers/src/setup'); const { getSlot, ImplementationSlot } = require('../../helpers/erc1967'); +const { expectRevertCustomError } = require('../../helpers/customError'); const ERC1967Proxy = artifacts.require('ERC1967Proxy'); const UUPSUpgradeableMock = artifacts.require('UUPSUpgradeableMock'); @@ -47,9 +48,10 @@ contract('UUPSUpgradeable', function () { // delegate to a non existing upgradeTo function causes a low level revert it('reject upgrade to non uups implementation', async function () { - await expectRevert( + await expectRevertCustomError( this.instance.upgradeTo(this.implUpgradeNonUUPS.address), - 'ERC1967Upgrade: new implementation is not UUPS', + 'ERC1967InvalidImplementation', + [this.implUpgradeNonUUPS.address], ); }); @@ -57,10 +59,9 @@ contract('UUPSUpgradeable', function () { const { address } = await ERC1967Proxy.new(this.implInitial.address, '0x'); const otherInstance = await UUPSUpgradeableMock.at(address); - await expectRevert( - this.instance.upgradeTo(otherInstance.address), - 'ERC1967Upgrade: new implementation is not UUPS', - ); + await expectRevertCustomError(this.instance.upgradeTo(otherInstance.address), 'ERC1967InvalidImplementation', [ + otherInstance.address, + ]); }); it('can upgrade from legacy implementations', async function () { diff --git a/test/security/Pausable.test.js b/test/security/Pausable.test.js index 5cca11e47..e60a62c74 100644 --- a/test/security/Pausable.test.js +++ b/test/security/Pausable.test.js @@ -1,7 +1,8 @@ -const { expectEvent, expectRevert } = require('@openzeppelin/test-helpers'); - +const { expectEvent } = require('@openzeppelin/test-helpers'); const { expect } = require('chai'); +const { expectRevertCustomError } = require('../helpers/customError'); + const PausableMock = artifacts.require('PausableMock'); contract('Pausable', function (accounts) { @@ -24,7 +25,7 @@ contract('Pausable', function (accounts) { }); it('cannot take drastic measure in non-pause', async function () { - await expectRevert(this.pausable.drasticMeasure(), 'Pausable: not paused'); + await expectRevertCustomError(this.pausable.drasticMeasure(), 'ExpectedPause', []); expect(await this.pausable.drasticMeasureTaken()).to.equal(false); }); @@ -38,7 +39,7 @@ contract('Pausable', function (accounts) { }); it('cannot perform normal process in pause', async function () { - await expectRevert(this.pausable.normalProcess(), 'Pausable: paused'); + await expectRevertCustomError(this.pausable.normalProcess(), 'EnforcedPause', []); }); it('can take a drastic measure in a pause', async function () { @@ -47,7 +48,7 @@ contract('Pausable', function (accounts) { }); it('reverts when re-pausing', async function () { - await expectRevert(this.pausable.pause(), 'Pausable: paused'); + await expectRevertCustomError(this.pausable.pause(), 'EnforcedPause', []); }); describe('unpausing', function () { @@ -72,11 +73,11 @@ contract('Pausable', function (accounts) { }); it('should prevent drastic measure', async function () { - await expectRevert(this.pausable.drasticMeasure(), 'Pausable: not paused'); + await expectRevertCustomError(this.pausable.drasticMeasure(), 'ExpectedPause', []); }); it('reverts when re-unpausing', async function () { - await expectRevert(this.pausable.unpause(), 'Pausable: not paused'); + await expectRevertCustomError(this.pausable.unpause(), 'ExpectedPause', []); }); }); }); diff --git a/test/security/ReentrancyGuard.test.js b/test/security/ReentrancyGuard.test.js index 1a80bc860..15355c098 100644 --- a/test/security/ReentrancyGuard.test.js +++ b/test/security/ReentrancyGuard.test.js @@ -1,7 +1,8 @@ const { expectRevert } = require('@openzeppelin/test-helpers'); - const { expect } = require('chai'); +const { expectRevertCustomError } = require('../helpers/customError'); + const ReentrancyMock = artifacts.require('ReentrancyMock'); const ReentrancyAttack = artifacts.require('ReentrancyAttack'); @@ -19,7 +20,7 @@ contract('ReentrancyGuard', function () { it('does not allow remote callback', async function () { const attacker = await ReentrancyAttack.new(); - await expectRevert(this.reentrancyMock.countAndCall(attacker.address), 'ReentrancyAttack: failed call'); + await expectRevert(this.reentrancyMock.countAndCall(attacker.address), 'ReentrancyAttack: failed call', []); }); it('_reentrancyGuardEntered should be true when guarded', async function () { @@ -34,10 +35,10 @@ contract('ReentrancyGuard', function () { // I put them here as documentation, and to monitor any changes // in the side-effects. it('does not allow local recursion', async function () { - await expectRevert(this.reentrancyMock.countLocalRecursive(10), 'ReentrancyGuard: reentrant call'); + await expectRevertCustomError(this.reentrancyMock.countLocalRecursive(10), 'ReentrancyGuardReentrantCall', []); }); it('does not allow indirect local recursion', async function () { - await expectRevert(this.reentrancyMock.countThisRecursive(10), 'ReentrancyMock: failed call'); + await expectRevert(this.reentrancyMock.countThisRecursive(10), 'ReentrancyMock: failed call', []); }); }); diff --git a/test/token/ERC1155/ERC1155.behavior.js b/test/token/ERC1155/ERC1155.behavior.js index c41c69c62..5e87f8d52 100644 --- a/test/token/ERC1155/ERC1155.behavior.js +++ b/test/token/ERC1155/ERC1155.behavior.js @@ -4,6 +4,7 @@ const { ZERO_ADDRESS } = constants; const { expect } = require('chai'); const { shouldSupportInterfaces } = require('../../utils/introspection/SupportsInterface.behavior'); +const { expectRevertCustomError } = require('../../helpers/customError'); const ERC1155ReceiverMock = artifacts.require('ERC1155ReceiverMock'); @@ -56,21 +57,19 @@ function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, m describe('balanceOfBatch', function () { it("reverts when input arrays don't match up", async function () { - await expectRevert( - this.token.balanceOfBatch( - [firstTokenHolder, secondTokenHolder, firstTokenHolder, secondTokenHolder], - [firstTokenId, secondTokenId, unknownTokenId], - ), - 'ERC1155: accounts and ids length mismatch', - ); + const accounts1 = [firstTokenHolder, secondTokenHolder, firstTokenHolder, secondTokenHolder]; + const ids1 = [firstTokenId, secondTokenId, unknownTokenId]; + await expectRevertCustomError(this.token.balanceOfBatch(accounts1, ids1), 'ERC1155InvalidArrayLength', [ + accounts1.length, + ids1.length, + ]); - await expectRevert( - this.token.balanceOfBatch( - [firstTokenHolder, secondTokenHolder], - [firstTokenId, secondTokenId, unknownTokenId], - ), - 'ERC1155: accounts and ids length mismatch', - ); + const accounts2 = [firstTokenHolder, secondTokenHolder]; + const ids2 = [firstTokenId, secondTokenId, unknownTokenId]; + await expectRevertCustomError(this.token.balanceOfBatch(accounts2, ids2), 'ERC1155InvalidArrayLength', [ + accounts2.length, + ids2.length, + ]); }); it('should return 0 as the balance when one of the addresses is the zero address', async function () { @@ -152,9 +151,10 @@ function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, m }); it('reverts if attempting to approve self as an operator', async function () { - await expectRevert( + await expectRevertCustomError( this.token.setApprovalForAll(multiTokenHolder, true, { from: multiTokenHolder }), - 'ERC1155: setting approval status for self', + 'ERC1155InvalidOperator', + [multiTokenHolder], ); }); }); @@ -170,20 +170,22 @@ function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, m }); it('reverts when transferring more than balance', async function () { - await expectRevert( + await expectRevertCustomError( this.token.safeTransferFrom(multiTokenHolder, recipient, firstTokenId, firstAmount.addn(1), '0x', { from: multiTokenHolder, }), - 'ERC1155: insufficient balance for transfer', + 'ERC1155InsufficientBalance', + [multiTokenHolder, firstAmount, firstAmount.addn(1), firstTokenId], ); }); it('reverts when transferring to zero address', async function () { - await expectRevert( + await expectRevertCustomError( this.token.safeTransferFrom(multiTokenHolder, ZERO_ADDRESS, firstTokenId, firstAmount, '0x', { from: multiTokenHolder, }), - 'ERC1155: transfer to the zero address', + 'ERC1155InvalidReceiver', + [ZERO_ADDRESS], ); }); @@ -247,11 +249,12 @@ function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, m }); it('reverts', async function () { - await expectRevert( + await expectRevertCustomError( this.token.safeTransferFrom(multiTokenHolder, recipient, firstTokenId, firstAmount, '0x', { from: proxy, }), - 'ERC1155: caller is not token owner or approved', + 'ERC1155InsufficientApprovalForAll', + [proxy, multiTokenHolder], ); }); }); @@ -371,11 +374,12 @@ function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, m }); it('reverts', async function () { - await expectRevert( + await expectRevertCustomError( this.token.safeTransferFrom(multiTokenHolder, this.receiver.address, firstTokenId, firstAmount, '0x', { from: multiTokenHolder, }), - 'ERC1155: ERC1155Receiver rejected tokens', + 'ERC1155InvalidReceiver', + [this.receiver.address], ); }); }); @@ -423,7 +427,7 @@ function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, m }); it('reverts when transferring amount more than any of balances', async function () { - await expectRevert( + await expectRevertCustomError( this.token.safeBatchTransferFrom( multiTokenHolder, recipient, @@ -432,38 +436,36 @@ function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, m '0x', { from: multiTokenHolder }, ), - 'ERC1155: insufficient balance for transfer', + 'ERC1155InsufficientBalance', + [multiTokenHolder, secondAmount, secondAmount.addn(1), secondTokenId], ); }); it("reverts when ids array length doesn't match amounts array length", async function () { - await expectRevert( - this.token.safeBatchTransferFrom( - multiTokenHolder, - recipient, - [firstTokenId], - [firstAmount, secondAmount], - '0x', - { from: multiTokenHolder }, - ), - 'ERC1155: ids and amounts length mismatch', + const ids1 = [firstTokenId]; + const amounts1 = [firstAmount, secondAmount]; + + await expectRevertCustomError( + this.token.safeBatchTransferFrom(multiTokenHolder, recipient, ids1, amounts1, '0x', { + from: multiTokenHolder, + }), + 'ERC1155InvalidArrayLength', + [ids1.length, amounts1.length], ); - await expectRevert( - this.token.safeBatchTransferFrom( - multiTokenHolder, - recipient, - [firstTokenId, secondTokenId], - [firstAmount], - '0x', - { from: multiTokenHolder }, - ), - 'ERC1155: ids and amounts length mismatch', + const ids2 = [firstTokenId, secondTokenId]; + const amounts2 = [firstAmount]; + await expectRevertCustomError( + this.token.safeBatchTransferFrom(multiTokenHolder, recipient, ids2, amounts2, '0x', { + from: multiTokenHolder, + }), + 'ERC1155InvalidArrayLength', + [ids2.length, amounts2.length], ); }); it('reverts when transferring to zero address', async function () { - await expectRevert( + await expectRevertCustomError( this.token.safeBatchTransferFrom( multiTokenHolder, ZERO_ADDRESS, @@ -472,7 +474,8 @@ function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, m '0x', { from: multiTokenHolder }, ), - 'ERC1155: transfer to the zero address', + 'ERC1155InvalidReceiver', + [ZERO_ADDRESS], ); }); @@ -530,7 +533,7 @@ function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, m }); it('reverts', async function () { - await expectRevert( + await expectRevertCustomError( this.token.safeBatchTransferFrom( multiTokenHolder, recipient, @@ -539,7 +542,8 @@ function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, m '0x', { from: proxy }, ), - 'ERC1155: caller is not token owner or approved', + 'ERC1155InsufficientApprovalForAll', + [proxy, multiTokenHolder], ); }); }); @@ -661,7 +665,7 @@ function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, m }); it('reverts', async function () { - await expectRevert( + await expectRevertCustomError( this.token.safeBatchTransferFrom( multiTokenHolder, this.receiver.address, @@ -670,7 +674,8 @@ function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, m '0x', { from: multiTokenHolder }, ), - 'ERC1155: ERC1155Receiver rejected tokens', + 'ERC1155InvalidReceiver', + [this.receiver.address], ); }); }); diff --git a/test/token/ERC1155/ERC1155.test.js b/test/token/ERC1155/ERC1155.test.js index 48197eeb5..23555dd54 100644 --- a/test/token/ERC1155/ERC1155.test.js +++ b/test/token/ERC1155/ERC1155.test.js @@ -1,8 +1,10 @@ -const { BN, constants, expectEvent, expectRevert } = require('@openzeppelin/test-helpers'); +const { BN, constants, expectEvent } = require('@openzeppelin/test-helpers'); const { ZERO_ADDRESS } = constants; const { expect } = require('chai'); +const { expectRevertCustomError } = require('../../helpers/customError'); + const { shouldBehaveLikeERC1155 } = require('./ERC1155.behavior'); const ERC1155Mock = artifacts.require('$ERC1155'); @@ -30,9 +32,10 @@ contract('ERC1155', function (accounts) { describe('_mint', function () { it('reverts with a zero destination address', async function () { - await expectRevert( + await expectRevertCustomError( this.token.$_mint(ZERO_ADDRESS, tokenId, mintAmount, data), - 'ERC1155: mint to the zero address', + 'ERC1155InvalidReceiver', + [ZERO_ADDRESS], ); }); @@ -59,21 +62,24 @@ contract('ERC1155', function (accounts) { describe('_mintBatch', function () { it('reverts with a zero destination address', async function () { - await expectRevert( + await expectRevertCustomError( this.token.$_mintBatch(ZERO_ADDRESS, tokenBatchIds, mintAmounts, data), - 'ERC1155: mint to the zero address', + 'ERC1155InvalidReceiver', + [ZERO_ADDRESS], ); }); it('reverts if length of inputs do not match', async function () { - await expectRevert( + await expectRevertCustomError( this.token.$_mintBatch(tokenBatchHolder, tokenBatchIds, mintAmounts.slice(1), data), - 'ERC1155: ids and amounts length mismatch', + 'ERC1155InvalidArrayLength', + [tokenBatchIds.length, mintAmounts.length - 1], ); - await expectRevert( + await expectRevertCustomError( this.token.$_mintBatch(tokenBatchHolder, tokenBatchIds.slice(1), mintAmounts, data), - 'ERC1155: ids and amounts length mismatch', + 'ERC1155InvalidArrayLength', + [tokenBatchIds.length - 1, mintAmounts.length], ); }); @@ -107,22 +113,26 @@ contract('ERC1155', function (accounts) { describe('_burn', function () { it("reverts when burning the zero account's tokens", async function () { - await expectRevert(this.token.$_burn(ZERO_ADDRESS, tokenId, mintAmount), 'ERC1155: burn from the zero address'); + await expectRevertCustomError(this.token.$_burn(ZERO_ADDRESS, tokenId, mintAmount), 'ERC1155InvalidSender', [ + ZERO_ADDRESS, + ]); }); it('reverts when burning a non-existent token id', async function () { - await expectRevert( + await expectRevertCustomError( this.token.$_burn(tokenHolder, tokenId, mintAmount), - 'ERC1155: insufficient balance for transfer', + 'ERC1155InsufficientBalance', + [tokenHolder, 0, mintAmount, tokenId], ); }); it('reverts when burning more than available tokens', async function () { await this.token.$_mint(tokenHolder, tokenId, mintAmount, data, { from: operator }); - await expectRevert( + await expectRevertCustomError( this.token.$_burn(tokenHolder, tokenId, mintAmount.addn(1)), - 'ERC1155: insufficient balance for transfer', + 'ERC1155InsufficientBalance', + [tokenHolder, mintAmount, mintAmount.addn(1), tokenId], ); }); @@ -150,28 +160,32 @@ contract('ERC1155', function (accounts) { describe('_burnBatch', function () { it("reverts when burning the zero account's tokens", async function () { - await expectRevert( + await expectRevertCustomError( this.token.$_burnBatch(ZERO_ADDRESS, tokenBatchIds, burnAmounts), - 'ERC1155: burn from the zero address', + 'ERC1155InvalidSender', + [ZERO_ADDRESS], ); }); it('reverts if length of inputs do not match', async function () { - await expectRevert( + await expectRevertCustomError( this.token.$_burnBatch(tokenBatchHolder, tokenBatchIds, burnAmounts.slice(1)), - 'ERC1155: ids and amounts length mismatch', + 'ERC1155InvalidArrayLength', + [tokenBatchIds.length, burnAmounts.length - 1], ); - await expectRevert( + await expectRevertCustomError( this.token.$_burnBatch(tokenBatchHolder, tokenBatchIds.slice(1), burnAmounts), - 'ERC1155: ids and amounts length mismatch', + 'ERC1155InvalidArrayLength', + [tokenBatchIds.length - 1, burnAmounts.length], ); }); it('reverts when burning a non-existent token id', async function () { - await expectRevert( + await expectRevertCustomError( this.token.$_burnBatch(tokenBatchHolder, tokenBatchIds, burnAmounts), - 'ERC1155: insufficient balance for transfer', + 'ERC1155InsufficientBalance', + [tokenBatchHolder, 0, tokenBatchIds[0], burnAmounts[0]], ); }); diff --git a/test/token/ERC1155/extensions/ERC1155Burnable.test.js b/test/token/ERC1155/extensions/ERC1155Burnable.test.js index f80d9935a..6af2308f8 100644 --- a/test/token/ERC1155/extensions/ERC1155Burnable.test.js +++ b/test/token/ERC1155/extensions/ERC1155Burnable.test.js @@ -1,7 +1,9 @@ -const { BN, expectRevert } = require('@openzeppelin/test-helpers'); +const { BN } = require('@openzeppelin/test-helpers'); const { expect } = require('chai'); +const { expectRevertCustomError } = require('../../../helpers/customError'); + const ERC1155Burnable = artifacts.require('$ERC1155Burnable'); contract('ERC1155Burnable', function (accounts) { @@ -34,9 +36,10 @@ contract('ERC1155Burnable', function (accounts) { }); it("unapproved accounts cannot burn the holder's tokens", async function () { - await expectRevert( + await expectRevertCustomError( this.token.burn(holder, tokenIds[0], amounts[0].subn(1), { from: other }), - 'ERC1155: caller is not token owner or approved', + 'ERC1155InsufficientApprovalForAll', + [other, holder], ); }); }); @@ -58,9 +61,10 @@ contract('ERC1155Burnable', function (accounts) { }); it("unapproved accounts cannot burn the holder's tokens", async function () { - await expectRevert( + await expectRevertCustomError( this.token.burnBatch(holder, tokenIds, [amounts[0].subn(1), amounts[1].subn(2)], { from: other }), - 'ERC1155: caller is not token owner or approved', + 'ERC1155InsufficientApprovalForAll', + [other, holder], ); }); }); diff --git a/test/token/ERC1155/extensions/ERC1155Pausable.test.js b/test/token/ERC1155/extensions/ERC1155Pausable.test.js index f4d5cedec..b0ac54bdb 100644 --- a/test/token/ERC1155/extensions/ERC1155Pausable.test.js +++ b/test/token/ERC1155/extensions/ERC1155Pausable.test.js @@ -1,6 +1,7 @@ -const { BN, expectRevert } = require('@openzeppelin/test-helpers'); +const { BN } = require('@openzeppelin/test-helpers'); const { expect } = require('chai'); +const { expectRevertCustomError } = require('../../../helpers/customError'); const ERC1155Pausable = artifacts.require('$ERC1155Pausable'); @@ -28,60 +29,64 @@ contract('ERC1155Pausable', function (accounts) { }); it('reverts when trying to safeTransferFrom from holder', async function () { - await expectRevert( + await expectRevertCustomError( this.token.safeTransferFrom(holder, receiver, firstTokenId, firstTokenAmount, '0x', { from: holder }), - 'ERC1155Pausable: token transfer while paused', + 'EnforcedPause', + [], ); }); it('reverts when trying to safeTransferFrom from operator', async function () { - await expectRevert( + await expectRevertCustomError( this.token.safeTransferFrom(holder, receiver, firstTokenId, firstTokenAmount, '0x', { from: operator }), - 'ERC1155Pausable: token transfer while paused', + 'EnforcedPause', + [], ); }); it('reverts when trying to safeBatchTransferFrom from holder', async function () { - await expectRevert( + await expectRevertCustomError( this.token.safeBatchTransferFrom(holder, receiver, [firstTokenId], [firstTokenAmount], '0x', { from: holder }), - 'ERC1155Pausable: token transfer while paused', + 'EnforcedPause', + [], ); }); it('reverts when trying to safeBatchTransferFrom from operator', async function () { - await expectRevert( + await expectRevertCustomError( this.token.safeBatchTransferFrom(holder, receiver, [firstTokenId], [firstTokenAmount], '0x', { from: operator, }), - 'ERC1155Pausable: token transfer while paused', + 'EnforcedPause', + [], ); }); it('reverts when trying to mint', async function () { - await expectRevert( + await expectRevertCustomError( this.token.$_mint(holder, secondTokenId, secondTokenAmount, '0x'), - 'ERC1155Pausable: token transfer while paused', + 'EnforcedPause', + [], ); }); it('reverts when trying to mintBatch', async function () { - await expectRevert( + await expectRevertCustomError( this.token.$_mintBatch(holder, [secondTokenId], [secondTokenAmount], '0x'), - 'ERC1155Pausable: token transfer while paused', + 'EnforcedPause', + [], ); }); it('reverts when trying to burn', async function () { - await expectRevert( - this.token.$_burn(holder, firstTokenId, firstTokenAmount), - 'ERC1155Pausable: token transfer while paused', - ); + await expectRevertCustomError(this.token.$_burn(holder, firstTokenId, firstTokenAmount), 'EnforcedPause', []); }); it('reverts when trying to burnBatch', async function () { - await expectRevert( + await expectRevertCustomError( this.token.$_burnBatch(holder, [firstTokenId], [firstTokenAmount]), - 'ERC1155Pausable: token transfer while paused', + 'EnforcedPause', + [], ); }); diff --git a/test/token/ERC20/ERC20.behavior.js b/test/token/ERC20/ERC20.behavior.js index 41e47f065..7f547b112 100644 --- a/test/token/ERC20/ERC20.behavior.js +++ b/test/token/ERC20/ERC20.behavior.js @@ -1,8 +1,10 @@ -const { BN, constants, expectEvent, expectRevert } = require('@openzeppelin/test-helpers'); +const { BN, constants, expectEvent } = require('@openzeppelin/test-helpers'); const { expect } = require('chai'); const { ZERO_ADDRESS, MAX_UINT256 } = constants; -function shouldBehaveLikeERC20(errorPrefix, initialSupply, initialHolder, recipient, anotherAccount) { +const { expectRevertCustomError } = require('../../helpers/customError'); + +function shouldBehaveLikeERC20(initialSupply, initialHolder, recipient, anotherAccount) { describe('total supply', function () { it('returns the total amount of tokens', async function () { expect(await this.token.totalSupply()).to.be.bignumber.equal(initialSupply); @@ -24,7 +26,7 @@ function shouldBehaveLikeERC20(errorPrefix, initialSupply, initialHolder, recipi }); describe('transfer', function () { - shouldBehaveLikeERC20Transfer(errorPrefix, initialHolder, recipient, initialSupply, function (from, to, value) { + shouldBehaveLikeERC20Transfer(initialHolder, recipient, initialSupply, function (from, to, value) { return this.token.transfer(to, value, { from }); }); }); @@ -85,9 +87,10 @@ function shouldBehaveLikeERC20(errorPrefix, initialSupply, initialHolder, recipi }); it('reverts', async function () { - await expectRevert( + await expectRevertCustomError( this.token.transferFrom(tokenOwner, to, amount, { from: spender }), - `${errorPrefix}: transfer amount exceeds balance`, + 'ERC20InsufficientBalance', + [tokenOwner, amount - 1, amount], ); }); }); @@ -104,9 +107,10 @@ function shouldBehaveLikeERC20(errorPrefix, initialSupply, initialHolder, recipi const amount = initialSupply; it('reverts', async function () { - await expectRevert( + await expectRevertCustomError( this.token.transferFrom(tokenOwner, to, amount, { from: spender }), - `${errorPrefix}: insufficient allowance`, + 'ERC20InsufficientAllowance', + [spender, allowance, amount], ); }); }); @@ -119,9 +123,10 @@ function shouldBehaveLikeERC20(errorPrefix, initialSupply, initialHolder, recipi }); it('reverts', async function () { - await expectRevert( + await expectRevertCustomError( this.token.transferFrom(tokenOwner, to, amount, { from: spender }), - `${errorPrefix}: transfer amount exceeds balance`, + 'ERC20InsufficientBalance', + [tokenOwner, amount - 1, amount], ); }); }); @@ -153,9 +158,10 @@ function shouldBehaveLikeERC20(errorPrefix, initialSupply, initialHolder, recipi }); it('reverts', async function () { - await expectRevert( + await expectRevertCustomError( this.token.transferFrom(tokenOwner, to, amount, { from: spender }), - `${errorPrefix}: transfer to the zero address`, + 'ERC20InvalidReceiver', + [ZERO_ADDRESS], ); }); }); @@ -167,31 +173,33 @@ function shouldBehaveLikeERC20(errorPrefix, initialSupply, initialHolder, recipi const to = recipient; it('reverts', async function () { - await expectRevert(this.token.transferFrom(tokenOwner, to, amount, { from: spender }), 'from the zero address'); + await expectRevertCustomError( + this.token.transferFrom(tokenOwner, to, amount, { from: spender }), + 'ERC20InvalidApprover', + [ZERO_ADDRESS], + ); }); }); }); describe('approve', function () { - shouldBehaveLikeERC20Approve( - errorPrefix, - initialHolder, - recipient, - initialSupply, - function (owner, spender, amount) { - return this.token.approve(spender, amount, { from: owner }); - }, - ); + shouldBehaveLikeERC20Approve(initialHolder, recipient, initialSupply, function (owner, spender, amount) { + return this.token.approve(spender, amount, { from: owner }); + }); }); } -function shouldBehaveLikeERC20Transfer(errorPrefix, from, to, balance, transfer) { +function shouldBehaveLikeERC20Transfer(from, to, balance, transfer) { describe('when the recipient is not the zero address', function () { describe('when the sender does not have enough balance', function () { const amount = balance.addn(1); it('reverts', async function () { - await expectRevert(transfer.call(this, from, to, amount), `${errorPrefix}: transfer amount exceeds balance`); + await expectRevertCustomError(transfer.call(this, from, to, amount), 'ERC20InsufficientBalance', [ + from, + balance, + amount, + ]); }); }); @@ -230,15 +238,14 @@ function shouldBehaveLikeERC20Transfer(errorPrefix, from, to, balance, transfer) describe('when the recipient is the zero address', function () { it('reverts', async function () { - await expectRevert( - transfer.call(this, from, ZERO_ADDRESS, balance), - `${errorPrefix}: transfer to the zero address`, - ); + await expectRevertCustomError(transfer.call(this, from, ZERO_ADDRESS, balance), 'ERC20InvalidReceiver', [ + ZERO_ADDRESS, + ]); }); }); } -function shouldBehaveLikeERC20Approve(errorPrefix, owner, spender, supply, approve) { +function shouldBehaveLikeERC20Approve(owner, spender, supply, approve) { describe('when the spender is not the zero address', function () { describe('when the sender has enough balance', function () { const amount = supply; @@ -307,10 +314,9 @@ function shouldBehaveLikeERC20Approve(errorPrefix, owner, spender, supply, appro describe('when the spender is the zero address', function () { it('reverts', async function () { - await expectRevert( - approve.call(this, owner, ZERO_ADDRESS, supply), - `${errorPrefix}: approve to the zero address`, - ); + await expectRevertCustomError(approve.call(this, owner, ZERO_ADDRESS, supply), `ERC20InvalidSpender`, [ + ZERO_ADDRESS, + ]); }); }); } diff --git a/test/token/ERC20/ERC20.test.js b/test/token/ERC20/ERC20.test.js index c29197578..7b97c56f1 100644 --- a/test/token/ERC20/ERC20.test.js +++ b/test/token/ERC20/ERC20.test.js @@ -7,6 +7,7 @@ const { shouldBehaveLikeERC20Transfer, shouldBehaveLikeERC20Approve, } = require('./ERC20.behavior'); +const { expectRevertCustomError } = require('../../helpers/customError'); const ERC20 = artifacts.require('$ERC20'); const ERC20Decimals = artifacts.require('$ERC20DecimalsMock'); @@ -45,7 +46,7 @@ contract('ERC20', function (accounts) { }); }); - shouldBehaveLikeERC20('ERC20', initialSupply, initialHolder, recipient, anotherAccount); + shouldBehaveLikeERC20(initialSupply, initialHolder, recipient, anotherAccount); describe('decrease allowance', function () { describe('when the spender is not the zero address', function () { @@ -54,9 +55,11 @@ contract('ERC20', function (accounts) { function shouldDecreaseApproval(amount) { describe('when there was no approved amount before', function () { it('reverts', async function () { - await expectRevert( + const allowance = await this.token.allowance(initialHolder, spender); + await expectRevertCustomError( this.token.decreaseAllowance(spender, amount, { from: initialHolder }), - 'ERC20: decreased allowance below zero', + 'ERC20FailedDecreaseAllowance', + [spender, allowance, amount], ); }); }); @@ -88,9 +91,10 @@ contract('ERC20', function (accounts) { }); it('reverts when more than the full allowance is removed', async function () { - await expectRevert( + await expectRevertCustomError( this.token.decreaseAllowance(spender, approvedAmount.addn(1), { from: initialHolder }), - 'ERC20: decreased allowance below zero', + 'ERC20FailedDecreaseAllowance', + [spender, approvedAmount, approvedAmount.addn(1)], ); }); }); @@ -114,9 +118,10 @@ contract('ERC20', function (accounts) { const spender = ZERO_ADDRESS; it('reverts', async function () { - await expectRevert( + await expectRevertCustomError( this.token.decreaseAllowance(spender, amount, { from: initialHolder }), - 'ERC20: decreased allowance below zero', + 'ERC20FailedDecreaseAllowance', + [spender, 0, amount], ); }); }); @@ -195,9 +200,10 @@ contract('ERC20', function (accounts) { const spender = ZERO_ADDRESS; it('reverts', async function () { - await expectRevert( + await expectRevertCustomError( this.token.increaseAllowance(spender, amount, { from: initialHolder }), - 'ERC20: approve to the zero address', + 'ERC20InvalidSpender', + [ZERO_ADDRESS], ); }); }); @@ -206,7 +212,7 @@ contract('ERC20', function (accounts) { describe('_mint', function () { const amount = new BN(50); it('rejects a null account', async function () { - await expectRevert(this.token.$_mint(ZERO_ADDRESS, amount), 'ERC20: mint to the zero address'); + await expectRevertCustomError(this.token.$_mint(ZERO_ADDRESS, amount), 'ERC20InvalidReceiver', [ZERO_ADDRESS]); }); it('rejects overflow', async function () { @@ -241,14 +247,15 @@ contract('ERC20', function (accounts) { describe('_burn', function () { it('rejects a null account', async function () { - await expectRevert(this.token.$_burn(ZERO_ADDRESS, new BN(1)), 'ERC20: burn from the zero address'); + await expectRevertCustomError(this.token.$_burn(ZERO_ADDRESS, new BN(1)), 'ERC20InvalidSender', [ZERO_ADDRESS]); }); describe('for a non zero account', function () { it('rejects burning more than balance', async function () { - await expectRevert( + await expectRevertCustomError( this.token.$_burn(initialHolder, initialSupply.addn(1)), - 'ERC20: transfer amount exceeds balance', + 'ERC20InsufficientBalance', + [initialHolder, initialSupply, initialSupply.addn(1)], ); }); @@ -325,30 +332,32 @@ contract('ERC20', function (accounts) { }); describe('_transfer', function () { - shouldBehaveLikeERC20Transfer('ERC20', initialHolder, recipient, initialSupply, function (from, to, amount) { + shouldBehaveLikeERC20Transfer(initialHolder, recipient, initialSupply, function (from, to, amount) { return this.token.$_transfer(from, to, amount); }); describe('when the sender is the zero address', function () { it('reverts', async function () { - await expectRevert( + await expectRevertCustomError( this.token.$_transfer(ZERO_ADDRESS, recipient, initialSupply), - 'ERC20: transfer from the zero address', + 'ERC20InvalidSender', + [ZERO_ADDRESS], ); }); }); }); describe('_approve', function () { - shouldBehaveLikeERC20Approve('ERC20', initialHolder, recipient, initialSupply, function (owner, spender, amount) { + shouldBehaveLikeERC20Approve(initialHolder, recipient, initialSupply, function (owner, spender, amount) { return this.token.$_approve(owner, spender, amount); }); describe('when the owner is the zero address', function () { it('reverts', async function () { - await expectRevert( + await expectRevertCustomError( this.token.$_approve(ZERO_ADDRESS, recipient, initialSupply), - 'ERC20: approve from the zero address', + 'ERC20InvalidApprover', + [ZERO_ADDRESS], ); }); }); diff --git a/test/token/ERC20/extensions/ERC20Burnable.behavior.js b/test/token/ERC20/extensions/ERC20Burnable.behavior.js index 448dda4ab..848e54b79 100644 --- a/test/token/ERC20/extensions/ERC20Burnable.behavior.js +++ b/test/token/ERC20/extensions/ERC20Burnable.behavior.js @@ -1,7 +1,8 @@ -const { BN, constants, expectEvent, expectRevert } = require('@openzeppelin/test-helpers'); +const { BN, constants, expectEvent } = require('@openzeppelin/test-helpers'); const { ZERO_ADDRESS } = constants; const { expect } = require('chai'); +const { expectRevertCustomError } = require('../../../helpers/customError'); function shouldBehaveLikeERC20Burnable(owner, initialBalance, [burner]) { describe('burn', function () { @@ -37,7 +38,11 @@ function shouldBehaveLikeERC20Burnable(owner, initialBalance, [burner]) { const amount = initialBalance.addn(1); it('reverts', async function () { - await expectRevert(this.token.burn(amount, { from: owner }), 'ERC20: transfer amount exceeds balance'); + await expectRevertCustomError(this.token.burn(amount, { from: owner }), 'ERC20InsufficientBalance', [ + owner, + initialBalance, + amount, + ]); }); }); }); @@ -83,9 +88,10 @@ function shouldBehaveLikeERC20Burnable(owner, initialBalance, [burner]) { it('reverts', async function () { await this.token.approve(burner, amount, { from: owner }); - await expectRevert( + await expectRevertCustomError( this.token.burnFrom(owner, amount, { from: burner }), - 'ERC20: transfer amount exceeds balance', + 'ERC20InsufficientBalance', + [owner, initialBalance, amount], ); }); }); @@ -95,9 +101,10 @@ function shouldBehaveLikeERC20Burnable(owner, initialBalance, [burner]) { it('reverts', async function () { await this.token.approve(burner, allowance, { from: owner }); - await expectRevert( + await expectRevertCustomError( this.token.burnFrom(owner, allowance.addn(1), { from: burner }), - 'ERC20: insufficient allowance', + 'ERC20InsufficientAllowance', + [burner, allowance, allowance.addn(1)], ); }); }); diff --git a/test/token/ERC20/extensions/ERC20Capped.behavior.js b/test/token/ERC20/extensions/ERC20Capped.behavior.js index 97bad1db1..c40e4fcc4 100644 --- a/test/token/ERC20/extensions/ERC20Capped.behavior.js +++ b/test/token/ERC20/extensions/ERC20Capped.behavior.js @@ -1,6 +1,5 @@ -const { expectRevert } = require('@openzeppelin/test-helpers'); - const { expect } = require('chai'); +const { expectRevertCustomError } = require('../../../helpers/customError'); function shouldBehaveLikeERC20Capped(accounts, cap) { describe('capped token', function () { @@ -17,12 +16,12 @@ function shouldBehaveLikeERC20Capped(accounts, cap) { it('fails to mint if the amount exceeds the cap', async function () { await this.token.$_mint(user, cap.subn(1)); - await expectRevert(this.token.$_mint(user, 2), 'ERC20Capped: cap exceeded'); + await expectRevertCustomError(this.token.$_mint(user, 2), 'ERC20ExceededCap', [cap.addn(1), cap]); }); it('fails to mint after cap is reached', async function () { await this.token.$_mint(user, cap); - await expectRevert(this.token.$_mint(user, 1), 'ERC20Capped: cap exceeded'); + await expectRevertCustomError(this.token.$_mint(user, 1), 'ERC20ExceededCap', [cap.addn(1), cap]); }); }); } diff --git a/test/token/ERC20/extensions/ERC20Capped.test.js b/test/token/ERC20/extensions/ERC20Capped.test.js index a86d38c1a..1f4a2bee3 100644 --- a/test/token/ERC20/extensions/ERC20Capped.test.js +++ b/test/token/ERC20/extensions/ERC20Capped.test.js @@ -1,5 +1,6 @@ -const { ether, expectRevert } = require('@openzeppelin/test-helpers'); +const { ether } = require('@openzeppelin/test-helpers'); const { shouldBehaveLikeERC20Capped } = require('./ERC20Capped.behavior'); +const { expectRevertCustomError } = require('../../../helpers/customError'); const ERC20Capped = artifacts.require('$ERC20Capped'); @@ -10,7 +11,7 @@ contract('ERC20Capped', function (accounts) { const symbol = 'MTKN'; it('requires a non-zero cap', async function () { - await expectRevert(ERC20Capped.new(name, symbol, 0), 'ERC20Capped: cap is 0'); + await expectRevertCustomError(ERC20Capped.new(name, symbol, 0), 'ERC20InvalidCap', [0]); }); context('once deployed', async function () { diff --git a/test/token/ERC20/extensions/ERC20FlashMint.test.js b/test/token/ERC20/extensions/ERC20FlashMint.test.js index ee9bedd26..a646704e2 100644 --- a/test/token/ERC20/extensions/ERC20FlashMint.test.js +++ b/test/token/ERC20/extensions/ERC20FlashMint.test.js @@ -2,6 +2,7 @@ const { BN, constants, expectEvent, expectRevert } = require('@openzeppelin/test-helpers'); const { expect } = require('chai'); +const { expectRevertCustomError } = require('../../../helpers/customError'); const { MAX_UINT256, ZERO_ADDRESS } = constants; const ERC20FlashMintMock = artifacts.require('$ERC20FlashMintMock'); @@ -37,7 +38,9 @@ contract('ERC20FlashMint', function (accounts) { }); it('token mismatch', async function () { - await expectRevert(this.token.flashFee(ZERO_ADDRESS, loanAmount), 'ERC20FlashMint: wrong token'); + await expectRevertCustomError(this.token.flashFee(ZERO_ADDRESS, loanAmount), 'ERC3156UnsupportedToken', [ + ZERO_ADDRESS, + ]); }); }); @@ -79,26 +82,29 @@ contract('ERC20FlashMint', function (accounts) { it('missing return value', async function () { const receiver = await ERC3156FlashBorrowerMock.new(false, true); - await expectRevert( + await expectRevertCustomError( this.token.flashLoan(receiver.address, this.token.address, loanAmount, '0x'), - 'ERC20FlashMint: invalid return value', + 'ERC3156InvalidReceiver', + [receiver.address], ); }); it('missing approval', async function () { const receiver = await ERC3156FlashBorrowerMock.new(true, false); - await expectRevert( + await expectRevertCustomError( this.token.flashLoan(receiver.address, this.token.address, loanAmount, '0x'), - 'ERC20: insufficient allowance', + 'ERC20InsufficientAllowance', + [this.token.address, 0, loanAmount], ); }); it('unavailable funds', async function () { const receiver = await ERC3156FlashBorrowerMock.new(true, true); const data = this.token.contract.methods.transfer(other, 10).encodeABI(); - await expectRevert( + await expectRevertCustomError( this.token.flashLoan(receiver.address, this.token.address, loanAmount, data), - 'ERC20: transfer amount exceeds balance', + 'ERC20InsufficientBalance', + [receiver.address, loanAmount - 10, loanAmount], ); }); diff --git a/test/token/ERC20/extensions/ERC20Pausable.test.js b/test/token/ERC20/extensions/ERC20Pausable.test.js index ead442b99..72bfc7769 100644 --- a/test/token/ERC20/extensions/ERC20Pausable.test.js +++ b/test/token/ERC20/extensions/ERC20Pausable.test.js @@ -1,6 +1,7 @@ -const { BN, expectRevert } = require('@openzeppelin/test-helpers'); +const { BN } = require('@openzeppelin/test-helpers'); const { expect } = require('chai'); +const { expectRevertCustomError } = require('../../../helpers/customError'); const ERC20Pausable = artifacts.require('$ERC20Pausable'); @@ -39,9 +40,10 @@ contract('ERC20Pausable', function (accounts) { it('reverts when trying to transfer when paused', async function () { await this.token.$_pause(); - await expectRevert( + await expectRevertCustomError( this.token.transfer(recipient, initialSupply, { from: holder }), - 'ERC20Pausable: token transfer while paused', + 'EnforcedPause', + [], ); }); }); @@ -73,9 +75,10 @@ contract('ERC20Pausable', function (accounts) { it('reverts when trying to transfer from when paused', async function () { await this.token.$_pause(); - await expectRevert( + await expectRevertCustomError( this.token.transferFrom(holder, recipient, allowance, { from: anotherAccount }), - 'ERC20Pausable: token transfer while paused', + 'EnforcedPause', + [], ); }); }); @@ -101,7 +104,7 @@ contract('ERC20Pausable', function (accounts) { it('reverts when trying to mint when paused', async function () { await this.token.$_pause(); - await expectRevert(this.token.$_mint(recipient, amount), 'ERC20Pausable: token transfer while paused'); + await expectRevertCustomError(this.token.$_mint(recipient, amount), 'EnforcedPause', []); }); }); @@ -126,7 +129,7 @@ contract('ERC20Pausable', function (accounts) { it('reverts when trying to burn when paused', async function () { await this.token.$_pause(); - await expectRevert(this.token.$_burn(holder, amount), 'ERC20Pausable: token transfer while paused'); + await expectRevertCustomError(this.token.$_burn(holder, amount), 'EnforcedPause', []); }); }); }); diff --git a/test/token/ERC20/extensions/draft-ERC20Permit.test.js b/test/token/ERC20/extensions/ERC20Permit.test.js similarity index 73% rename from test/token/ERC20/extensions/draft-ERC20Permit.test.js rename to test/token/ERC20/extensions/ERC20Permit.test.js index 33c43c479..388716d53 100644 --- a/test/token/ERC20/extensions/draft-ERC20Permit.test.js +++ b/test/token/ERC20/extensions/ERC20Permit.test.js @@ -1,6 +1,6 @@ /* eslint-disable */ -const { BN, constants, expectRevert, time } = require('@openzeppelin/test-helpers'); +const { BN, constants, time } = require('@openzeppelin/test-helpers'); const { expect } = require('chai'); const { MAX_UINT256 } = constants; @@ -12,13 +12,13 @@ const ERC20Permit = artifacts.require('$ERC20Permit'); const { Permit, getDomain, domainType, domainSeparator } = require('../../../helpers/eip712'); const { getChainId } = require('../../../helpers/chainid'); +const { expectRevertCustomError } = require('../../../helpers/customError'); contract('ERC20Permit', function (accounts) { const [initialHolder, spender] = accounts; const name = 'My Token'; const symbol = 'MTKN'; - const version = '1'; const initialSupply = new BN(100); @@ -65,15 +65,25 @@ contract('ERC20Permit', function (accounts) { }); it('rejects reused signature', async function () { - const { v, r, s } = await buildData(this.token) - .then(data => ethSigUtil.signTypedMessage(wallet.getPrivateKey(), { data })) - .then(fromRpcSig); + const sig = await buildData(this.token).then(data => + ethSigUtil.signTypedMessage(wallet.getPrivateKey(), { data }), + ); + const { r, s, v } = fromRpcSig(sig); await this.token.permit(owner, spender, value, maxDeadline, v, r, s); - await expectRevert( + const domain = await getDomain(this.token); + const typedMessage = { + primaryType: 'Permit', + types: { EIP712Domain: domainType(domain), Permit }, + domain, + message: { owner, spender, value, nonce: nonce + 1, deadline: maxDeadline }, + }; + + await expectRevertCustomError( this.token.permit(owner, spender, value, maxDeadline, v, r, s), - 'ERC20Permit: invalid signature', + 'ERC2612InvalidSigner', + [ethSigUtil.recoverTypedSignature({ data: typedMessage, sig }), owner], ); }); @@ -84,9 +94,10 @@ contract('ERC20Permit', function (accounts) { .then(data => ethSigUtil.signTypedMessage(otherWallet.getPrivateKey(), { data })) .then(fromRpcSig); - await expectRevert( + await expectRevertCustomError( this.token.permit(owner, spender, value, maxDeadline, v, r, s), - 'ERC20Permit: invalid signature', + 'ERC2612InvalidSigner', + [await otherWallet.getAddressString(), owner], ); }); @@ -97,7 +108,11 @@ contract('ERC20Permit', function (accounts) { .then(data => ethSigUtil.signTypedMessage(wallet.getPrivateKey(), { data })) .then(fromRpcSig); - await expectRevert(this.token.permit(owner, spender, value, deadline, v, r, s), 'ERC20Permit: expired deadline'); + await expectRevertCustomError( + this.token.permit(owner, spender, value, deadline, v, r, s), + 'ERC2612ExpiredSignature', + [deadline], + ); }); }); }); diff --git a/test/token/ERC20/extensions/ERC20Votes.test.js b/test/token/ERC20/extensions/ERC20Votes.test.js index e4ff58cd9..714a98adc 100644 --- a/test/token/ERC20/extensions/ERC20Votes.test.js +++ b/test/token/ERC20/extensions/ERC20Votes.test.js @@ -1,6 +1,6 @@ /* eslint-disable */ -const { BN, constants, expectEvent, expectRevert, time } = require('@openzeppelin/test-helpers'); +const { BN, constants, expectEvent, time } = require('@openzeppelin/test-helpers'); const { expect } = require('chai'); const { MAX_UINT256, ZERO_ADDRESS } = constants; @@ -12,6 +12,7 @@ const Wallet = require('ethereumjs-wallet').default; const { batchInBlock } = require('../../../helpers/txpool'); const { getDomain, domainType, domainSeparator } = require('../../../helpers/eip712'); const { clock, clockFromReceipt } = require('../../../helpers/time'); +const { expectRevertCustomError } = require('../../../helpers/customError'); const Delegation = [ { name: 'delegatee', type: 'address' }, @@ -48,7 +49,10 @@ contract('ERC20Votes', function (accounts) { it('minting restriction', async function () { const amount = new BN('2').pow(new BN('224')); - await expectRevert(this.token.$_mint(holder, amount), 'ERC20Votes: total supply risks overflowing votes'); + await expectRevertCustomError(this.token.$_mint(holder, amount), 'ERC20ExceededSafeSupply', [ + amount, + amount.subn(1), + ]); }); it('recent checkpoints', async function () { @@ -164,9 +168,10 @@ contract('ERC20Votes', function (accounts) { await this.token.delegateBySig(delegatorAddress, nonce, MAX_UINT256, v, r, s); - await expectRevert( + await expectRevertCustomError( this.token.delegateBySig(delegatorAddress, nonce, MAX_UINT256, v, r, s), - 'Votes: invalid nonce', + 'InvalidAccountNonce', + [delegatorAddress, nonce + 1], ); }); @@ -185,15 +190,25 @@ contract('ERC20Votes', function (accounts) { }); it('rejects bad nonce', async function () { - const { v, r, s } = await buildData(this.token, { + const sig = await buildData(this.token, { delegatee: delegatorAddress, nonce, expiry: MAX_UINT256, - }).then(data => fromRpcSig(ethSigUtil.signTypedMessage(delegator.getPrivateKey(), { data }))); + }).then(data => ethSigUtil.signTypedMessage(delegator.getPrivateKey(), { data })); + const { r, s, v } = fromRpcSig(sig); + + const domain = await getDomain(this.token); + const typedMessage = { + primaryType: 'Delegation', + types: { EIP712Domain: domainType(domain), Delegation }, + domain, + message: { delegatee: delegatorAddress, nonce: nonce + 1, expiry: MAX_UINT256 }, + }; - await expectRevert( + await expectRevertCustomError( this.token.delegateBySig(delegatorAddress, nonce + 1, MAX_UINT256, v, r, s), - 'Votes: invalid nonce', + 'InvalidAccountNonce', + [ethSigUtil.recoverTypedSignature({ data: typedMessage, sig }), nonce], ); }); @@ -205,9 +220,10 @@ contract('ERC20Votes', function (accounts) { expiry, }).then(data => fromRpcSig(ethSigUtil.signTypedMessage(delegator.getPrivateKey(), { data }))); - await expectRevert( + await expectRevertCustomError( this.token.delegateBySig(delegatorAddress, nonce, expiry, v, r, s), - 'Votes: signature expired', + 'VotesExpiredSignature', + [expiry], ); }); }); @@ -414,7 +430,8 @@ contract('ERC20Votes', function (accounts) { describe('getPastVotes', function () { it('reverts if block number >= current block', async function () { - await expectRevert(this.token.getPastVotes(other1, 5e10), 'Votes: future lookup'); + const clock = await this.token.clock(); + await expectRevertCustomError(this.token.getPastVotes(other1, 5e10), 'ERC5805FutureLookup', [5e10, clock]); }); it('returns 0 if there are no checkpoints', async function () { @@ -502,7 +519,8 @@ contract('ERC20Votes', function (accounts) { }); it('reverts if block number >= current block', async function () { - await expectRevert(this.token.getPastTotalSupply(5e10), 'Votes: future lookup'); + const clock = await this.token.clock(); + await expectRevertCustomError(this.token.getPastTotalSupply(5e10), 'ERC5805FutureLookup', [5e10, clock]); }); it('returns 0 if there are no checkpoints', async function () { diff --git a/test/token/ERC20/extensions/ERC20Wrapper.test.js b/test/token/ERC20/extensions/ERC20Wrapper.test.js index 774a9cbda..ffb97e8a2 100644 --- a/test/token/ERC20/extensions/ERC20Wrapper.test.js +++ b/test/token/ERC20/extensions/ERC20Wrapper.test.js @@ -1,14 +1,15 @@ -const { BN, constants, expectEvent, expectRevert } = require('@openzeppelin/test-helpers'); +const { BN, constants, expectEvent } = require('@openzeppelin/test-helpers'); const { expect } = require('chai'); const { ZERO_ADDRESS, MAX_UINT256 } = constants; const { shouldBehaveLikeERC20 } = require('../ERC20.behavior'); +const { expectRevertCustomError } = require('../../../helpers/customError'); const NotAnERC20 = artifacts.require('CallReceiverMock'); const ERC20Decimals = artifacts.require('$ERC20DecimalsMock'); const ERC20Wrapper = artifacts.require('$ERC20Wrapper'); -contract('ERC20', function (accounts) { +contract('ERC20Wrapper', function (accounts) { const [initialHolder, recipient, anotherAccount] = accounts; const name = 'My Token'; @@ -66,17 +67,19 @@ contract('ERC20', function (accounts) { }); it('missing approval', async function () { - await expectRevert( + await expectRevertCustomError( this.token.depositFor(initialHolder, initialSupply, { from: initialHolder }), - 'ERC20: insufficient allowance', + 'ERC20InsufficientAllowance', + [this.token.address, 0, initialSupply], ); }); it('missing balance', async function () { await this.underlying.approve(this.token.address, MAX_UINT256, { from: initialHolder }); - await expectRevert( + await expectRevertCustomError( this.token.depositFor(initialHolder, MAX_UINT256, { from: initialHolder }), - 'ERC20: transfer amount exceeds balance', + 'ERC20InsufficientBalance', + [initialHolder, initialSupply, MAX_UINT256], ); }); @@ -103,9 +106,10 @@ contract('ERC20', function (accounts) { }); it('missing balance', async function () { - await expectRevert( + await expectRevertCustomError( this.token.withdrawTo(initialHolder, MAX_UINT256, { from: initialHolder }), - 'ERC20: transfer amount exceeds balance', + 'ERC20InsufficientBalance', + [initialHolder, initialSupply, MAX_UINT256], ); }); @@ -185,6 +189,6 @@ contract('ERC20', function (accounts) { await this.token.depositFor(initialHolder, initialSupply, { from: initialHolder }); }); - shouldBehaveLikeERC20('ERC20', initialSupply, initialHolder, recipient, anotherAccount); + shouldBehaveLikeERC20(initialSupply, initialHolder, recipient, anotherAccount); }); }); diff --git a/test/token/ERC20/extensions/ERC4626.test.js b/test/token/ERC20/extensions/ERC4626.test.js index 55b3e5d20..d67486a60 100644 --- a/test/token/ERC20/extensions/ERC4626.test.js +++ b/test/token/ERC20/extensions/ERC4626.test.js @@ -2,6 +2,7 @@ const { constants, expectEvent, expectRevert } = require('@openzeppelin/test-hel const { expect } = require('chai'); const { Enum } = require('../../../helpers/enums'); +const { expectRevertCustomError } = require('../../../helpers/customError'); const ERC20Decimals = artifacts.require('$ERC20DecimalsMock'); const ERC4626 = artifacts.require('$ERC4626'); @@ -635,9 +636,11 @@ contract('ERC4626', function (accounts) { }); it('withdraw with approval', async function () { - await expectRevert( + const assets = await this.vault.previewWithdraw(parseToken(1)); + await expectRevertCustomError( this.vault.withdraw(parseToken(1), recipient, holder, { from: other }), - 'ERC20: insufficient allowance', + 'ERC20InsufficientAllowance', + [other, 0, assets], ); await this.vault.withdraw(parseToken(1), recipient, holder, { from: spender }); @@ -677,9 +680,10 @@ contract('ERC4626', function (accounts) { }); it('redeem with approval', async function () { - await expectRevert( + await expectRevertCustomError( this.vault.redeem(parseShare(100), recipient, holder, { from: other }), - 'ERC20: insufficient allowance', + 'ERC20InsufficientAllowance', + [other, 0, parseShare(100)], ); await this.vault.redeem(parseShare(100), recipient, holder, { from: spender }); diff --git a/test/token/ERC20/utils/SafeERC20.test.js b/test/token/ERC20/utils/SafeERC20.test.js index b0daf4384..eb6e26755 100644 --- a/test/token/ERC20/utils/SafeERC20.test.js +++ b/test/token/ERC20/utils/SafeERC20.test.js @@ -8,6 +8,7 @@ const ERC20PermitNoRevertMock = artifacts.require('$ERC20PermitNoRevertMock'); const ERC20ForceApproveMock = artifacts.require('$ERC20ForceApproveMock'); const { getDomain, domainType, Permit } = require('../../../helpers/eip712'); +const { expectRevertCustomError } = require('../../../helpers/customError'); const { fromRpcSig } = require('ethereumjs-util'); const ethSigUtil = require('eth-sig-util'); @@ -17,7 +18,7 @@ const name = 'ERC20Mock'; const symbol = 'ERC20Mock'; contract('SafeERC20', function (accounts) { - const [hasNoCode] = accounts; + const [hasNoCode, receiver, spender] = accounts; before(async function () { this.mock = await SafeERC20.new(); @@ -28,7 +29,35 @@ contract('SafeERC20', function (accounts) { this.token = { address: hasNoCode }; }); - shouldRevertOnAllCalls(accounts, 'Address: call to non-contract'); + it('reverts on transfer', async function () { + await expectRevertCustomError(this.mock.$safeTransfer(this.token.address, receiver, 0), 'AddressEmptyCode', [ + this.token.address, + ]); + }); + + it('reverts on transferFrom', async function () { + await expectRevertCustomError( + this.mock.$safeTransferFrom(this.token.address, this.mock.address, receiver, 0), + 'AddressEmptyCode', + [this.token.address], + ); + }); + + it('reverts on increaseAllowance', async function () { + // Call to 'token.allowance' does not return any data, resulting in a decoding error (revert without reason) + await expectRevert.unspecified(this.mock.$safeIncreaseAllowance(this.token.address, spender, 0)); + }); + + it('reverts on decreaseAllowance', async function () { + // Call to 'token.allowance' does not return any data, resulting in a decoding error (revert without reason) + await expectRevert.unspecified(this.mock.$safeDecreaseAllowance(this.token.address, spender, 0)); + }); + + it('reverts on forceApprove', async function () { + await expectRevertCustomError(this.mock.$forceApprove(this.token.address, spender, 0), 'AddressEmptyCode', [ + this.token.address, + ]); + }); }); describe('with token that returns false on all calls', function () { @@ -36,7 +65,45 @@ contract('SafeERC20', function (accounts) { this.token = await ERC20ReturnFalseMock.new(name, symbol); }); - shouldRevertOnAllCalls(accounts, 'SafeERC20: ERC20 operation did not succeed'); + it('reverts on transfer', async function () { + await expectRevertCustomError( + this.mock.$safeTransfer(this.token.address, receiver, 0), + 'SafeERC20FailedOperation', + [this.token.address], + ); + }); + + it('reverts on transferFrom', async function () { + await expectRevertCustomError( + this.mock.$safeTransferFrom(this.token.address, this.mock.address, receiver, 0), + 'SafeERC20FailedOperation', + [this.token.address], + ); + }); + + it('reverts on increaseAllowance', async function () { + await expectRevertCustomError( + this.mock.$safeIncreaseAllowance(this.token.address, spender, 0), + 'SafeERC20FailedOperation', + [this.token.address], + ); + }); + + it('reverts on decreaseAllowance', async function () { + await expectRevertCustomError( + this.mock.$safeDecreaseAllowance(this.token.address, spender, 0), + 'SafeERC20FailedOperation', + [this.token.address], + ); + }); + + it('reverts on forceApprove', async function () { + await expectRevertCustomError( + this.mock.$forceApprove(this.token.address, spender, 0), + 'SafeERC20FailedOperation', + [this.token.address], + ); + }); }); describe('with token that returns true on all calls', function () { @@ -118,7 +185,7 @@ contract('SafeERC20', function (accounts) { ); expect(await this.token.nonces(owner)).to.be.bignumber.equal('1'); // invalid call revert when called through the SafeERC20 library - await expectRevert( + await expectRevertCustomError( this.mock.$safePermit( this.token.address, this.data.message.owner, @@ -129,7 +196,8 @@ contract('SafeERC20', function (accounts) { this.signature.r, this.signature.s, ), - 'SafeERC20: permit did not succeed', + 'SafeERC20FailedOperation', + [this.token.address], ); expect(await this.token.nonces(owner)).to.be.bignumber.equal('1'); }); @@ -154,7 +222,7 @@ contract('SafeERC20', function (accounts) { ); // invalid call revert when called through the SafeERC20 library - await expectRevert( + await expectRevertCustomError( this.mock.$safePermit( this.token.address, this.data.message.owner, @@ -165,7 +233,8 @@ contract('SafeERC20', function (accounts) { invalidSignature.r, invalidSignature.s, ), - 'SafeERC20: permit did not succeed', + 'SafeERC20FailedOperation', + [this.token.address], ); }); }); @@ -200,30 +269,6 @@ contract('SafeERC20', function (accounts) { }); }); -function shouldRevertOnAllCalls([receiver, spender], reason) { - it('reverts on transfer', async function () { - await expectRevert(this.mock.$safeTransfer(this.token.address, receiver, 0), reason); - }); - - it('reverts on transferFrom', async function () { - await expectRevert(this.mock.$safeTransferFrom(this.token.address, this.mock.address, receiver, 0), reason); - }); - - it('reverts on increaseAllowance', async function () { - // [TODO] make sure it's reverting for the right reason - await expectRevert.unspecified(this.mock.$safeIncreaseAllowance(this.token.address, spender, 0)); - }); - - it('reverts on decreaseAllowance', async function () { - // [TODO] make sure it's reverting for the right reason - await expectRevert.unspecified(this.mock.$safeDecreaseAllowance(this.token.address, spender, 0)); - }); - - it('reverts on forceApprove', async function () { - await expectRevert(this.mock.$forceApprove(this.token.address, spender, 0), reason); - }); -} - function shouldOnlyRevertOnErrors([owner, receiver, spender]) { describe('transfers', function () { beforeEach(async function () { @@ -273,9 +318,10 @@ function shouldOnlyRevertOnErrors([owner, receiver, spender]) { }); it('reverts when decreasing the allowance', async function () { - await expectRevert( + await expectRevertCustomError( this.mock.$safeDecreaseAllowance(this.token.address, spender, 10), - 'SafeERC20: decreased allowance below zero', + 'SafeERC20FailedDecreaseAllowance', + [spender, 0, 10], ); }); }); @@ -306,9 +352,10 @@ function shouldOnlyRevertOnErrors([owner, receiver, spender]) { }); it('reverts when decreasing the allowance to a negative value', async function () { - await expectRevert( + await expectRevertCustomError( this.mock.$safeDecreaseAllowance(this.token.address, spender, 200), - 'SafeERC20: decreased allowance below zero', + 'SafeERC20FailedDecreaseAllowance', + [spender, 100, 200], ); }); }); diff --git a/test/token/ERC721/ERC721.behavior.js b/test/token/ERC721/ERC721.behavior.js index 6867db31f..7df429202 100644 --- a/test/token/ERC721/ERC721.behavior.js +++ b/test/token/ERC721/ERC721.behavior.js @@ -3,6 +3,7 @@ const { expect } = require('chai'); const { ZERO_ADDRESS } = constants; const { shouldSupportInterfaces } = require('../../utils/introspection/SupportsInterface.behavior'); +const { expectRevertCustomError } = require('../../helpers/customError'); const ERC721ReceiverMock = artifacts.require('ERC721ReceiverMock'); const NonERC721ReceiverMock = artifacts.require('CallReceiverMock'); @@ -20,7 +21,7 @@ const baseURI = 'https://api.example.com/v1/'; const RECEIVER_MAGIC_VALUE = '0x150b7a02'; -function shouldBehaveLikeERC721(errorPrefix, owner, newOwner, approved, anotherApproved, operator, other) { +function shouldBehaveLikeERC721(owner, newOwner, approved, anotherApproved, operator, other) { shouldSupportInterfaces(['ERC165', 'ERC721']); context('with minted tokens', function () { @@ -45,7 +46,7 @@ function shouldBehaveLikeERC721(errorPrefix, owner, newOwner, approved, anotherA context('when querying the zero address', function () { it('throws', async function () { - await expectRevert(this.token.balanceOf(ZERO_ADDRESS), 'ERC721: address zero is not a valid owner'); + await expectRevertCustomError(this.token.balanceOf(ZERO_ADDRESS), 'ERC721InvalidOwner', [ZERO_ADDRESS]); }); }); }); @@ -63,7 +64,7 @@ function shouldBehaveLikeERC721(errorPrefix, owner, newOwner, approved, anotherA const tokenId = nonExistentTokenId; it('reverts', async function () { - await expectRevert(this.token.ownerOf(tokenId), 'ERC721: invalid token ID'); + await expectRevertCustomError(this.token.ownerOf(tokenId), 'ERC721NonexistentToken', [tokenId]); }); }); }); @@ -172,36 +173,40 @@ function shouldBehaveLikeERC721(errorPrefix, owner, newOwner, approved, anotherA context('when the address of the previous owner is incorrect', function () { it('reverts', async function () { - await expectRevert( + await expectRevertCustomError( transferFunction.call(this, other, other, tokenId, { from: owner }), - 'ERC721: transfer from incorrect owner', + 'ERC721IncorrectOwner', + [other, tokenId, owner], ); }); }); context('when the sender is not authorized for the token id', function () { it('reverts', async function () { - await expectRevert( + await expectRevertCustomError( transferFunction.call(this, owner, other, tokenId, { from: other }), - 'ERC721: caller is not token owner or approved', + 'ERC721InsufficientApproval', + [other, tokenId], ); }); }); context('when the given token ID does not exist', function () { it('reverts', async function () { - await expectRevert( + await expectRevertCustomError( transferFunction.call(this, owner, other, nonExistentTokenId, { from: owner }), - 'ERC721: invalid token ID', + 'ERC721NonexistentToken', + [nonExistentTokenId], ); }); }); context('when the address to transfer the token to is the zero address', function () { it('reverts', async function () { - await expectRevert( + await expectRevertCustomError( transferFunction.call(this, owner, ZERO_ADDRESS, tokenId, { from: owner }), - 'ERC721: transfer to the zero address', + 'ERC721InvalidReceiver', + [ZERO_ADDRESS], ); }); }); @@ -259,9 +264,10 @@ function shouldBehaveLikeERC721(errorPrefix, owner, newOwner, approved, anotherA describe('with an invalid token id', function () { it('reverts', async function () { - await expectRevert( + await expectRevertCustomError( transferFun.call(this, owner, this.receiver.address, nonExistentTokenId, { from: owner }), - 'ERC721: invalid token ID', + 'ERC721NonexistentToken', + [nonExistentTokenId], ); }); }); @@ -279,9 +285,10 @@ function shouldBehaveLikeERC721(errorPrefix, owner, newOwner, approved, anotherA describe('to a receiver contract returning unexpected value', function () { it('reverts', async function () { const invalidReceiver = await ERC721ReceiverMock.new('0x42', Error.None); - await expectRevert( + await expectRevertCustomError( this.token.safeTransferFrom(owner, invalidReceiver.address, tokenId, { from: owner }), - 'ERC721: transfer to non ERC721Receiver implementer', + 'ERC721InvalidReceiver', + [invalidReceiver.address], ); }); }); @@ -299,9 +306,10 @@ function shouldBehaveLikeERC721(errorPrefix, owner, newOwner, approved, anotherA describe('to a receiver contract that reverts without message', function () { it('reverts', async function () { const revertingReceiver = await ERC721ReceiverMock.new(RECEIVER_MAGIC_VALUE, Error.RevertWithoutMessage); - await expectRevert( + await expectRevertCustomError( this.token.safeTransferFrom(owner, revertingReceiver.address, tokenId, { from: owner }), - 'ERC721: transfer to non ERC721Receiver implementer', + 'ERC721InvalidReceiver', + [revertingReceiver.address], ); }); }); @@ -318,9 +326,10 @@ function shouldBehaveLikeERC721(errorPrefix, owner, newOwner, approved, anotherA describe('to a contract that does not implement the required function', function () { it('reverts', async function () { const nonReceiver = await NonERC721ReceiverMock.new(); - await expectRevert( + await expectRevertCustomError( this.token.safeTransferFrom(owner, nonReceiver.address, tokenId, { from: owner }), - 'ERC721: transfer to non ERC721Receiver implementer', + 'ERC721InvalidReceiver', + [nonReceiver.address], ); }); }); @@ -357,9 +366,10 @@ function shouldBehaveLikeERC721(errorPrefix, owner, newOwner, approved, anotherA context('to a receiver contract returning unexpected value', function () { it('reverts', async function () { const invalidReceiver = await ERC721ReceiverMock.new('0x42', Error.None); - await expectRevert( + await expectRevertCustomError( this.token.$_safeMint(invalidReceiver.address, tokenId), - 'ERC721: transfer to non ERC721Receiver implementer', + 'ERC721InvalidReceiver', + [invalidReceiver.address], ); }); }); @@ -377,9 +387,10 @@ function shouldBehaveLikeERC721(errorPrefix, owner, newOwner, approved, anotherA context('to a receiver contract that reverts without message', function () { it('reverts', async function () { const revertingReceiver = await ERC721ReceiverMock.new(RECEIVER_MAGIC_VALUE, Error.RevertWithoutMessage); - await expectRevert( + await expectRevertCustomError( this.token.$_safeMint(revertingReceiver.address, tokenId), - 'ERC721: transfer to non ERC721Receiver implementer', + 'ERC721InvalidReceiver', + [revertingReceiver.address], ); }); }); @@ -394,9 +405,10 @@ function shouldBehaveLikeERC721(errorPrefix, owner, newOwner, approved, anotherA context('to a contract that does not implement the required function', function () { it('reverts', async function () { const nonReceiver = await NonERC721ReceiverMock.new(); - await expectRevert( + await expectRevertCustomError( this.token.$_safeMint(nonReceiver.address, tokenId), - 'ERC721: transfer to non ERC721Receiver implementer', + 'ERC721InvalidReceiver', + [nonReceiver.address], ); }); }); @@ -484,15 +496,18 @@ function shouldBehaveLikeERC721(errorPrefix, owner, newOwner, approved, anotherA context('when the address that receives the approval is the owner', function () { it('reverts', async function () { - await expectRevert(this.token.approve(owner, tokenId, { from: owner }), 'ERC721: approval to current owner'); + await expectRevertCustomError(this.token.approve(owner, tokenId, { from: owner }), 'ERC721InvalidOperator', [ + owner, + ]); }); }); context('when the sender does not own the given token ID', function () { it('reverts', async function () { - await expectRevert( + await expectRevertCustomError( this.token.approve(approved, tokenId, { from: other }), - 'ERC721: approve caller is not token owner or approved', + 'ERC721InvalidApprover', + [other], ); }); }); @@ -500,9 +515,10 @@ function shouldBehaveLikeERC721(errorPrefix, owner, newOwner, approved, anotherA context('when the sender is approved for the given token ID', function () { it('reverts', async function () { await this.token.approve(approved, tokenId, { from: owner }); - await expectRevert( + await expectRevertCustomError( this.token.approve(anotherApproved, tokenId, { from: approved }), - 'ERC721: approve caller is not token owner or approved for all', + 'ERC721InvalidApprover', + [approved], ); }); }); @@ -519,9 +535,10 @@ function shouldBehaveLikeERC721(errorPrefix, owner, newOwner, approved, anotherA context('when the given token ID does not exist', function () { it('reverts', async function () { - await expectRevert( + await expectRevertCustomError( this.token.approve(approved, nonExistentTokenId, { from: operator }), - 'ERC721: invalid token ID', + 'ERC721NonexistentToken', + [nonExistentTokenId], ); }); }); @@ -600,7 +617,11 @@ function shouldBehaveLikeERC721(errorPrefix, owner, newOwner, approved, anotherA context('when the operator is the owner', function () { it('reverts', async function () { - await expectRevert(this.token.setApprovalForAll(owner, true, { from: owner }), 'ERC721: approve to caller'); + await expectRevertCustomError( + this.token.setApprovalForAll(owner, true, { from: owner }), + 'ERC721InvalidOperator', + [owner], + ); }); }); }); @@ -608,7 +629,9 @@ function shouldBehaveLikeERC721(errorPrefix, owner, newOwner, approved, anotherA describe('getApproved', async function () { context('when token is not minted', async function () { it('reverts', async function () { - await expectRevert(this.token.getApproved(nonExistentTokenId), 'ERC721: invalid token ID'); + await expectRevertCustomError(this.token.getApproved(nonExistentTokenId), 'ERC721NonexistentToken', [ + nonExistentTokenId, + ]); }); }); @@ -632,7 +655,9 @@ function shouldBehaveLikeERC721(errorPrefix, owner, newOwner, approved, anotherA describe('_mint(address, uint256)', function () { it('reverts with a null destination address', async function () { - await expectRevert(this.token.$_mint(ZERO_ADDRESS, firstTokenId), 'ERC721: mint to the zero address'); + await expectRevertCustomError(this.token.$_mint(ZERO_ADDRESS, firstTokenId), 'ERC721InvalidReceiver', [ + ZERO_ADDRESS, + ]); }); context('with minted token', async function () { @@ -650,14 +675,16 @@ function shouldBehaveLikeERC721(errorPrefix, owner, newOwner, approved, anotherA }); it('reverts when adding a token id that already exists', async function () { - await expectRevert(this.token.$_mint(owner, firstTokenId), 'ERC721: token already minted'); + await expectRevertCustomError(this.token.$_mint(owner, firstTokenId), 'ERC721InvalidSender', [ZERO_ADDRESS]); }); }); }); describe('_burn', function () { it('reverts when burning a non-existent token id', async function () { - await expectRevert(this.token.$_burn(nonExistentTokenId), 'ERC721: invalid token ID'); + await expectRevertCustomError(this.token.$_burn(nonExistentTokenId), 'ERC721NonexistentToken', [ + nonExistentTokenId, + ]); }); context('with minted tokens', function () { @@ -677,18 +704,18 @@ function shouldBehaveLikeERC721(errorPrefix, owner, newOwner, approved, anotherA it('deletes the token', async function () { expect(await this.token.balanceOf(owner)).to.be.bignumber.equal('1'); - await expectRevert(this.token.ownerOf(firstTokenId), 'ERC721: invalid token ID'); + await expectRevertCustomError(this.token.ownerOf(firstTokenId), 'ERC721NonexistentToken', [firstTokenId]); }); it('reverts when burning a token id that has been deleted', async function () { - await expectRevert(this.token.$_burn(firstTokenId), 'ERC721: invalid token ID'); + await expectRevertCustomError(this.token.$_burn(firstTokenId), 'ERC721NonexistentToken', [firstTokenId]); }); }); }); }); } -function shouldBehaveLikeERC721Enumerable(errorPrefix, owner, newOwner, approved, anotherApproved, operator, other) { +function shouldBehaveLikeERC721Enumerable(owner, newOwner, approved, anotherApproved, operator, other) { shouldSupportInterfaces(['ERC721Enumerable']); context('with minted tokens', function () { @@ -713,13 +740,13 @@ function shouldBehaveLikeERC721Enumerable(errorPrefix, owner, newOwner, approved describe('when the index is greater than or equal to the total tokens owned by the given address', function () { it('reverts', async function () { - await expectRevert(this.token.tokenOfOwnerByIndex(owner, 2), 'ERC721Enumerable: owner index out of bounds'); + await expectRevertCustomError(this.token.tokenOfOwnerByIndex(owner, 2), 'ERC721OutOfBoundsIndex', [owner, 2]); }); }); describe('when the given address does not own any token', function () { it('reverts', async function () { - await expectRevert(this.token.tokenOfOwnerByIndex(other, 0), 'ERC721Enumerable: owner index out of bounds'); + await expectRevertCustomError(this.token.tokenOfOwnerByIndex(other, 0), 'ERC721OutOfBoundsIndex', [other, 0]); }); }); @@ -740,7 +767,7 @@ function shouldBehaveLikeERC721Enumerable(errorPrefix, owner, newOwner, approved it('returns empty collection for original owner', async function () { expect(await this.token.balanceOf(owner)).to.be.bignumber.equal('0'); - await expectRevert(this.token.tokenOfOwnerByIndex(owner, 0), 'ERC721Enumerable: owner index out of bounds'); + await expectRevertCustomError(this.token.tokenOfOwnerByIndex(owner, 0), 'ERC721OutOfBoundsIndex', [owner, 0]); }); }); }); @@ -755,7 +782,7 @@ function shouldBehaveLikeERC721Enumerable(errorPrefix, owner, newOwner, approved }); it('reverts if index is greater than supply', async function () { - await expectRevert(this.token.tokenByIndex(2), 'ERC721Enumerable: global index out of bounds'); + await expectRevertCustomError(this.token.tokenByIndex(2), 'ERC721OutOfBoundsIndex', [ZERO_ADDRESS, 2]); }); [firstTokenId, secondTokenId].forEach(function (tokenId) { @@ -781,7 +808,9 @@ function shouldBehaveLikeERC721Enumerable(errorPrefix, owner, newOwner, approved describe('_mint(address, uint256)', function () { it('reverts with a null destination address', async function () { - await expectRevert(this.token.$_mint(ZERO_ADDRESS, firstTokenId), 'ERC721: mint to the zero address'); + await expectRevertCustomError(this.token.$_mint(ZERO_ADDRESS, firstTokenId), 'ERC721InvalidReceiver', [ + ZERO_ADDRESS, + ]); }); context('with minted token', async function () { @@ -801,7 +830,7 @@ function shouldBehaveLikeERC721Enumerable(errorPrefix, owner, newOwner, approved describe('_burn', function () { it('reverts when burning a non-existent token id', async function () { - await expectRevert(this.token.$_burn(firstTokenId), 'ERC721: invalid token ID'); + await expectRevertCustomError(this.token.$_burn(firstTokenId), 'ERC721NonexistentToken', [firstTokenId]); }); context('with minted tokens', function () { @@ -826,14 +855,14 @@ function shouldBehaveLikeERC721Enumerable(errorPrefix, owner, newOwner, approved it('burns all tokens', async function () { await this.token.$_burn(secondTokenId, { from: owner }); expect(await this.token.totalSupply()).to.be.bignumber.equal('0'); - await expectRevert(this.token.tokenByIndex(0), 'ERC721Enumerable: global index out of bounds'); + await expectRevertCustomError(this.token.tokenByIndex(0), 'ERC721OutOfBoundsIndex', [ZERO_ADDRESS, 0]); }); }); }); }); } -function shouldBehaveLikeERC721Metadata(errorPrefix, name, symbol, owner) { +function shouldBehaveLikeERC721Metadata(name, symbol, owner) { shouldSupportInterfaces(['ERC721Metadata']); describe('metadata', function () { @@ -855,7 +884,9 @@ function shouldBehaveLikeERC721Metadata(errorPrefix, name, symbol, owner) { }); it('reverts when queried for non existent token id', async function () { - await expectRevert(this.token.tokenURI(nonExistentTokenId), 'ERC721: invalid token ID'); + await expectRevertCustomError(this.token.tokenURI(nonExistentTokenId), 'ERC721NonexistentToken', [ + nonExistentTokenId, + ]); }); describe('base URI', function () { diff --git a/test/token/ERC721/ERC721.test.js b/test/token/ERC721/ERC721.test.js index 312430cb9..372dd5069 100644 --- a/test/token/ERC721/ERC721.test.js +++ b/test/token/ERC721/ERC721.test.js @@ -10,6 +10,6 @@ contract('ERC721', function (accounts) { this.token = await ERC721.new(name, symbol); }); - shouldBehaveLikeERC721('ERC721', ...accounts); - shouldBehaveLikeERC721Metadata('ERC721', name, symbol, ...accounts); + shouldBehaveLikeERC721(...accounts); + shouldBehaveLikeERC721Metadata(name, symbol, ...accounts); }); diff --git a/test/token/ERC721/ERC721Enumerable.test.js b/test/token/ERC721/ERC721Enumerable.test.js index b32f22dd6..31c28d177 100644 --- a/test/token/ERC721/ERC721Enumerable.test.js +++ b/test/token/ERC721/ERC721Enumerable.test.js @@ -14,7 +14,7 @@ contract('ERC721Enumerable', function (accounts) { this.token = await ERC721Enumerable.new(name, symbol); }); - shouldBehaveLikeERC721('ERC721', ...accounts); - shouldBehaveLikeERC721Metadata('ERC721', name, symbol, ...accounts); - shouldBehaveLikeERC721Enumerable('ERC721', ...accounts); + shouldBehaveLikeERC721(...accounts); + shouldBehaveLikeERC721Metadata(name, symbol, ...accounts); + shouldBehaveLikeERC721Enumerable(...accounts); }); diff --git a/test/token/ERC721/extensions/ERC721Burnable.test.js b/test/token/ERC721/extensions/ERC721Burnable.test.js index 6a4bc6dbc..c6c076919 100644 --- a/test/token/ERC721/extensions/ERC721Burnable.test.js +++ b/test/token/ERC721/extensions/ERC721Burnable.test.js @@ -1,6 +1,7 @@ -const { BN, constants, expectEvent, expectRevert } = require('@openzeppelin/test-helpers'); +const { BN, constants, expectEvent } = require('@openzeppelin/test-helpers'); const { expect } = require('chai'); +const { expectRevertCustomError } = require('../../../helpers/customError'); const ERC721Burnable = artifacts.require('$ERC721Burnable'); @@ -34,7 +35,7 @@ contract('ERC721Burnable', function (accounts) { }); it('burns the given token ID and adjusts the balance of the owner', async function () { - await expectRevert(this.token.ownerOf(tokenId), 'ERC721: invalid token ID'); + await expectRevertCustomError(this.token.ownerOf(tokenId), 'ERC721NonexistentToken', [tokenId]); expect(await this.token.balanceOf(owner)).to.be.bignumber.equal('1'); }); @@ -55,14 +56,16 @@ contract('ERC721Burnable', function (accounts) { context('getApproved', function () { it('reverts', async function () { - await expectRevert(this.token.getApproved(tokenId), 'ERC721: invalid token ID'); + await expectRevertCustomError(this.token.getApproved(tokenId), 'ERC721NonexistentToken', [tokenId]); }); }); }); describe('when the given token ID was not tracked by this contract', function () { it('reverts', async function () { - await expectRevert(this.token.burn(unknownTokenId, { from: owner }), 'ERC721: invalid token ID'); + await expectRevertCustomError(this.token.burn(unknownTokenId, { from: owner }), 'ERC721NonexistentToken', [ + unknownTokenId, + ]); }); }); }); diff --git a/test/token/ERC721/extensions/ERC721Consecutive.test.js b/test/token/ERC721/extensions/ERC721Consecutive.test.js index d4f0b4f8a..172da86a6 100644 --- a/test/token/ERC721/extensions/ERC721Consecutive.test.js +++ b/test/token/ERC721/extensions/ERC721Consecutive.test.js @@ -1,6 +1,8 @@ -const { constants, expectEvent, expectRevert } = require('@openzeppelin/test-helpers'); +const { constants, expectEvent } = require('@openzeppelin/test-helpers'); const { expect } = require('chai'); const { sum } = require('../../../helpers/math'); +const { expectRevertCustomError } = require('../../../helpers/customError'); +const { ZERO_ADDRESS } = require('@openzeppelin/test-helpers/src/constants'); const ERC721ConsecutiveMock = artifacts.require('$ERC721ConsecutiveMock'); const ERC721ConsecutiveEnumerableMock = artifacts.require('$ERC721ConsecutiveEnumerableMock'); @@ -87,10 +89,7 @@ contract('ERC721Consecutive', function (accounts) { describe('minting after construction', function () { it('consecutive minting is not possible after construction', async function () { - await expectRevert( - this.token.$_mintConsecutive(user1, 10), - 'ERC721Consecutive: batch minting restricted to constructor', - ); + await expectRevertCustomError(this.token.$_mintConsecutive(user1, 10), 'ERC721ForbiddenBatchMint', []); }); it('simple minting is possible after construction', async function () { @@ -110,7 +109,7 @@ contract('ERC721Consecutive', function (accounts) { expect(await this.token.$_exists(tokenId)).to.be.equal(true); - await expectRevert(this.token.$_mint(user1, tokenId), 'ERC721: token already minted'); + await expectRevertCustomError(this.token.$_mint(user1, tokenId), 'ERC721InvalidSender', [ZERO_ADDRESS]); }); }); @@ -130,7 +129,7 @@ contract('ERC721Consecutive', function (accounts) { tokenId, }); - await expectRevert(this.token.ownerOf(tokenId), 'ERC721: invalid token ID'); + await expectRevertCustomError(this.token.ownerOf(tokenId), 'ERC721NonexistentToken', [tokenId]); expectEvent(await this.token.$_mint(user2, tokenId), 'Transfer', { from: constants.ZERO_ADDRESS, @@ -145,7 +144,7 @@ contract('ERC721Consecutive', function (accounts) { const tokenId = web3.utils.toBN(sum(...batches.map(({ amount }) => amount)) + offset); expect(await this.token.$_exists(tokenId)).to.be.equal(false); - await expectRevert(this.token.ownerOf(tokenId), 'ERC721: invalid token ID'); + await expectRevertCustomError(this.token.ownerOf(tokenId), 'ERC721NonexistentToken', [tokenId]); // mint await this.token.$_mint(user1, tokenId); @@ -161,7 +160,7 @@ contract('ERC721Consecutive', function (accounts) { }); expect(await this.token.$_exists(tokenId)).to.be.equal(false); - await expectRevert(this.token.ownerOf(tokenId), 'ERC721: invalid token ID'); + await expectRevertCustomError(this.token.ownerOf(tokenId), 'ERC721NonexistentToken', [tokenId]); // re-mint expectEvent(await this.token.$_mint(user2, tokenId), 'Transfer', { @@ -179,35 +178,39 @@ contract('ERC721Consecutive', function (accounts) { describe('invalid use', function () { it('cannot mint a batch larger than 5000', async function () { - await expectRevert( + await expectRevertCustomError( ERC721ConsecutiveMock.new(name, symbol, 0, [], [user1], ['5001']), - 'ERC721Consecutive: batch too large', + 'ERC721ExceededMaxBatchMint', + [5000, 5001], ); }); it('cannot use single minting during construction', async function () { - await expectRevert( + await expectRevertCustomError( ERC721ConsecutiveNoConstructorMintMock.new(name, symbol), - "ERC721Consecutive: can't mint during construction", + 'ERC721ForbiddenMint', + [], ); }); it('cannot use single minting during construction', async function () { - await expectRevert( + await expectRevertCustomError( ERC721ConsecutiveNoConstructorMintMock.new(name, symbol), - "ERC721Consecutive: can't mint during construction", + 'ERC721ForbiddenMint', + [], ); }); it('consecutive mint not compatible with enumerability', async function () { - await expectRevert( + await expectRevertCustomError( ERC721ConsecutiveEnumerableMock.new( name, symbol, batches.map(({ receiver }) => receiver), batches.map(({ amount }) => amount), ), - 'ERC721Enumerable: consecutive transfers not supported', + 'ERC721EnumerableForbiddenBatchMint', + [], ); }); }); diff --git a/test/token/ERC721/extensions/ERC721Pausable.test.js b/test/token/ERC721/extensions/ERC721Pausable.test.js index c7fc8233f..ec99dea96 100644 --- a/test/token/ERC721/extensions/ERC721Pausable.test.js +++ b/test/token/ERC721/extensions/ERC721Pausable.test.js @@ -1,6 +1,7 @@ -const { BN, constants, expectRevert } = require('@openzeppelin/test-helpers'); +const { BN, constants } = require('@openzeppelin/test-helpers'); const { expect } = require('chai'); +const { expectRevertCustomError } = require('../../../helpers/customError'); const ERC721Pausable = artifacts.require('$ERC721Pausable'); @@ -26,34 +27,37 @@ contract('ERC721Pausable', function (accounts) { }); it('reverts when trying to transferFrom', async function () { - await expectRevert( + await expectRevertCustomError( this.token.transferFrom(owner, receiver, firstTokenId, { from: owner }), - 'ERC721Pausable: token transfer while paused', + 'EnforcedPause', + [], ); }); it('reverts when trying to safeTransferFrom', async function () { - await expectRevert( + await expectRevertCustomError( this.token.safeTransferFrom(owner, receiver, firstTokenId, { from: owner }), - 'ERC721Pausable: token transfer while paused', + 'EnforcedPause', + [], ); }); it('reverts when trying to safeTransferFrom with data', async function () { - await expectRevert( + await expectRevertCustomError( this.token.methods['safeTransferFrom(address,address,uint256,bytes)'](owner, receiver, firstTokenId, mockData, { from: owner, }), - 'ERC721Pausable: token transfer while paused', + 'EnforcedPause', + [], ); }); it('reverts when trying to mint', async function () { - await expectRevert(this.token.$_mint(receiver, secondTokenId), 'ERC721Pausable: token transfer while paused'); + await expectRevertCustomError(this.token.$_mint(receiver, secondTokenId), 'EnforcedPause', []); }); it('reverts when trying to burn', async function () { - await expectRevert(this.token.$_burn(firstTokenId), 'ERC721Pausable: token transfer while paused'); + await expectRevertCustomError(this.token.$_burn(firstTokenId), 'EnforcedPause', []); }); describe('getApproved', function () { diff --git a/test/token/ERC721/extensions/ERC721URIStorage.test.js b/test/token/ERC721/extensions/ERC721URIStorage.test.js index 60c80066c..34738cae1 100644 --- a/test/token/ERC721/extensions/ERC721URIStorage.test.js +++ b/test/token/ERC721/extensions/ERC721URIStorage.test.js @@ -1,7 +1,8 @@ -const { BN, expectEvent, expectRevert } = require('@openzeppelin/test-helpers'); +const { BN, expectEvent } = require('@openzeppelin/test-helpers'); const { expect } = require('chai'); const { shouldSupportInterfaces } = require('../../../utils/introspection/SupportsInterface.behavior'); +const { expectRevertCustomError } = require('../../../helpers/customError'); const ERC721URIStorageMock = artifacts.require('$ERC721URIStorageMock'); @@ -33,7 +34,9 @@ contract('ERC721URIStorage', function (accounts) { }); it('reverts when queried for non existent token id', async function () { - await expectRevert(this.token.tokenURI(nonExistentTokenId), 'ERC721: invalid token ID'); + await expectRevertCustomError(this.token.tokenURI(nonExistentTokenId), 'ERC721NonexistentToken', [ + nonExistentTokenId, + ]); }); it('can be set for a token id', async function () { @@ -48,10 +51,9 @@ contract('ERC721URIStorage', function (accounts) { }); it('reverts when setting for non existent token id', async function () { - await expectRevert( - this.token.$_setTokenURI(nonExistentTokenId, sampleUri), - 'ERC721URIStorage: URI set of nonexistent token', - ); + await expectRevertCustomError(this.token.$_setTokenURI(nonExistentTokenId, sampleUri), 'ERC721NonexistentToken', [ + nonExistentTokenId, + ]); }); it('base URI can be set', async function () { @@ -85,7 +87,7 @@ contract('ERC721URIStorage', function (accounts) { await this.token.$_burn(firstTokenId, { from: owner }); expect(await this.token.$_exists(firstTokenId)).to.equal(false); - await expectRevert(this.token.tokenURI(firstTokenId), 'ERC721: invalid token ID'); + await expectRevertCustomError(this.token.tokenURI(firstTokenId), 'ERC721NonexistentToken', [firstTokenId]); }); it('tokens with URI can be burnt ', async function () { @@ -94,7 +96,7 @@ contract('ERC721URIStorage', function (accounts) { await this.token.$_burn(firstTokenId, { from: owner }); expect(await this.token.$_exists(firstTokenId)).to.equal(false); - await expectRevert(this.token.tokenURI(firstTokenId), 'ERC721: invalid token ID'); + await expectRevertCustomError(this.token.tokenURI(firstTokenId), 'ERC721NonexistentToken', [firstTokenId]); }); }); }); diff --git a/test/token/ERC721/extensions/ERC721Wrapper.test.js b/test/token/ERC721/extensions/ERC721Wrapper.test.js index 6e46d2e5a..683997744 100644 --- a/test/token/ERC721/extensions/ERC721Wrapper.test.js +++ b/test/token/ERC721/extensions/ERC721Wrapper.test.js @@ -1,7 +1,8 @@ -const { BN, expectEvent, constants, expectRevert } = require('@openzeppelin/test-helpers'); +const { BN, expectEvent, constants } = require('@openzeppelin/test-helpers'); const { expect } = require('chai'); const { shouldBehaveLikeERC721 } = require('../ERC721.behavior'); +const { expectRevertCustomError } = require('../../../helpers/customError'); const ERC721 = artifacts.require('$ERC721'); const ERC721Wrapper = artifacts.require('$ERC721Wrapper'); @@ -115,9 +116,10 @@ contract('ERC721Wrapper', function (accounts) { }); it('reverts with missing approval', async function () { - await expectRevert( + await expectRevertCustomError( this.token.depositFor(initialHolder, [firstTokenId], { from: initialHolder }), - 'ERC721: caller is not token owner or approved', + 'ERC721InsufficientApproval', + [this.token.address, firstTokenId], ); }); }); @@ -178,9 +180,10 @@ contract('ERC721Wrapper', function (accounts) { }); it("doesn't work for a non-owner nor approved", async function () { - await expectRevert( + await expectRevertCustomError( this.token.withdrawTo(initialHolder, [firstTokenId], { from: anotherAccount }), - 'ERC721Wrapper: caller is not token owner or approved', + 'ERC721InsufficientApproval', + [anotherAccount, firstTokenId], ); }); @@ -230,7 +233,7 @@ contract('ERC721Wrapper', function (accounts) { describe('onERC721Received', function () { it('only allows calls from underlying', async function () { - await expectRevert( + await expectRevertCustomError( this.token.onERC721Received( initialHolder, this.token.address, @@ -238,7 +241,8 @@ contract('ERC721Wrapper', function (accounts) { anotherAccount, // Correct data { from: anotherAccount }, ), - 'ERC721Wrapper: caller is not underlying', + 'ERC721UnsupportedToken', + [anotherAccount], ); }); @@ -270,14 +274,16 @@ contract('ERC721Wrapper', function (accounts) { }); it('reverts if there is nothing to recover', async function () { - await expectRevert( - this.token.$_recover(initialHolder, firstTokenId), - 'ERC721Wrapper: wrapper is not token owner', - ); + const owner = await this.underlying.ownerOf(firstTokenId); + await expectRevertCustomError(this.token.$_recover(initialHolder, firstTokenId), 'ERC721IncorrectOwner', [ + this.token.address, + firstTokenId, + owner, + ]); }); }); describe('ERC712 behavior', function () { - shouldBehaveLikeERC721('ERC721', ...accounts); + shouldBehaveLikeERC721(...accounts); }); }); diff --git a/test/token/common/ERC2981.behavior.js b/test/token/common/ERC2981.behavior.js index 5d0f67715..15efa239f 100644 --- a/test/token/common/ERC2981.behavior.js +++ b/test/token/common/ERC2981.behavior.js @@ -1,8 +1,9 @@ -const { BN, constants, expectRevert } = require('@openzeppelin/test-helpers'); +const { BN, constants } = require('@openzeppelin/test-helpers'); const { expect } = require('chai'); const { ZERO_ADDRESS } = constants; const { shouldSupportInterfaces } = require('../../utils/introspection/SupportsInterface.behavior'); +const { expectRevertCustomError } = require('../../helpers/customError'); function shouldBehaveLikeERC2981() { const royaltyFraction = new BN('10'); @@ -60,11 +61,18 @@ function shouldBehaveLikeERC2981() { }); it('reverts if invalid parameters', async function () { - await expectRevert(this.token.$_setDefaultRoyalty(ZERO_ADDRESS, royaltyFraction), 'ERC2981: invalid receiver'); + const royaltyDenominator = await this.token.$_feeDenominator(); + await expectRevertCustomError( + this.token.$_setDefaultRoyalty(ZERO_ADDRESS, royaltyFraction), + 'ERC2981InvalidDefaultRoyaltyReceiver', + [ZERO_ADDRESS], + ); - await expectRevert( - this.token.$_setDefaultRoyalty(this.account1, new BN('11000')), - 'ERC2981: royalty fee will exceed salePrice', + const anotherRoyaltyFraction = new BN('11000'); + await expectRevertCustomError( + this.token.$_setDefaultRoyalty(this.account1, anotherRoyaltyFraction), + 'ERC2981InvalidDefaultRoyalty', + [anotherRoyaltyFraction, royaltyDenominator], ); }); }); @@ -104,14 +112,18 @@ function shouldBehaveLikeERC2981() { }); it('reverts if invalid parameters', async function () { - await expectRevert( + const royaltyDenominator = await this.token.$_feeDenominator(); + await expectRevertCustomError( this.token.$_setTokenRoyalty(this.tokenId1, ZERO_ADDRESS, royaltyFraction), - 'ERC2981: Invalid parameters', + 'ERC2981InvalidTokenRoyaltyReceiver', + [this.tokenId1.toString(), ZERO_ADDRESS], ); - await expectRevert( - this.token.$_setTokenRoyalty(this.tokenId1, this.account1, new BN('11000')), - 'ERC2981: royalty fee will exceed salePrice', + const anotherRoyaltyFraction = new BN('11000'); + await expectRevertCustomError( + this.token.$_setTokenRoyalty(this.tokenId1, this.account1, anotherRoyaltyFraction), + 'ERC2981InvalidTokenRoyalty', + [this.tokenId1.toString(), anotherRoyaltyFraction, royaltyDenominator], ); }); diff --git a/test/utils/Address.test.js b/test/utils/Address.test.js index ea72ab610..beded18e1 100644 --- a/test/utils/Address.test.js +++ b/test/utils/Address.test.js @@ -1,7 +1,9 @@ const { balance, constants, ether, expectRevert, send, expectEvent } = require('@openzeppelin/test-helpers'); const { expect } = require('chai'); +const { expectRevertCustomError } = require('../helpers/customError'); const Address = artifacts.require('$Address'); +const AddressFnPointerMock = artifacts.require('$AddressFnPointerMock'); const EtherReceiver = artifacts.require('EtherReceiverMock'); const CallReceiverMock = artifacts.require('CallReceiverMock'); @@ -10,6 +12,7 @@ contract('Address', function (accounts) { beforeEach(async function () { this.mock = await Address.new(); + this.mockFnPointer = await AddressFnPointerMock.new(); }); describe('sendValue', function () { @@ -25,7 +28,9 @@ contract('Address', function (accounts) { }); it('reverts when sending non-zero amounts', async function () { - await expectRevert(this.mock.$sendValue(other, 1), 'Address: insufficient balance'); + await expectRevertCustomError(this.mock.$sendValue(other, 1), 'AddressInsufficientBalance', [ + this.mock.address, + ]); }); }); @@ -52,7 +57,9 @@ contract('Address', function (accounts) { }); it('reverts when sending more than the balance', async function () { - await expectRevert(this.mock.$sendValue(recipient, funds.addn(1)), 'Address: insufficient balance'); + await expectRevertCustomError(this.mock.$sendValue(recipient, funds.addn(1)), 'AddressInsufficientBalance', [ + this.mock.address, + ]); }); context('with contract recipient', function () { @@ -71,10 +78,7 @@ contract('Address', function (accounts) { it('reverts on recipient revert', async function () { await this.target.setAcceptEther(false); - await expectRevert( - this.mock.$sendValue(this.target.address, funds), - 'Address: unable to send value, recipient may have reverted', - ); + await expectRevertCustomError(this.mock.$sendValue(this.target.address, funds), 'FailedInnerCall', []); }); }); }); @@ -91,7 +95,7 @@ contract('Address', function (accounts) { const receipt = await this.mock.$functionCall(this.target.address, abiEncodedCall); - expectEvent(receipt, 'return$functionCall_address_bytes', { + expectEvent(receipt, 'return$functionCall', { ret0: web3.eth.abi.encodeParameters(['string'], ['0x1234']), }); await expectEvent.inTransaction(receipt.tx, CallReceiverMock, 'MockFunctionCalled'); @@ -108,9 +112,10 @@ contract('Address', function (accounts) { it('reverts when the called function reverts with no reason', async function () { const abiEncodedCall = this.target.contract.methods.mockFunctionRevertsNoReason().encodeABI(); - await expectRevert( + await expectRevertCustomError( this.mock.$functionCall(this.target.address, abiEncodedCall), - 'Address: low-level call failed', + 'FailedInnerCall', + [], ); }); @@ -123,9 +128,10 @@ contract('Address', function (accounts) { it('reverts when the called function runs out of gas', async function () { const abiEncodedCall = this.target.contract.methods.mockFunctionOutOfGas().encodeABI(); - await expectRevert( + await expectRevertCustomError( this.mock.$functionCall(this.target.address, abiEncodedCall, { gas: '120000' }), - 'Address: low-level call failed', + 'FailedInnerCall', + [], ); }); @@ -135,9 +141,12 @@ contract('Address', function (accounts) { await expectRevert.unspecified(this.mock.$functionCall(this.target.address, abiEncodedCall)); }); - it('bubbles up error message if specified', async function () { - const errorMsg = 'Address: expected error'; - await expectRevert(this.mock.$functionCall(this.target.address, '0x12345678', errorMsg), errorMsg); + it('bubbles up error if specified', async function () { + await expectRevertCustomError( + this.mockFnPointer.functionCall(this.target.address, '0x12345678'), + 'CustomRevert', + [], + ); }); it('reverts when function does not exist', async function () { @@ -150,9 +159,10 @@ contract('Address', function (accounts) { [], ); - await expectRevert( + await expectRevertCustomError( this.mock.$functionCall(this.target.address, abiEncodedCall), - 'Address: low-level call failed', + 'FailedInnerCall', + [], ); }); }); @@ -162,7 +172,9 @@ contract('Address', function (accounts) { const [recipient] = accounts; const abiEncodedCall = this.target.contract.methods.mockFunction().encodeABI(); - await expectRevert(this.mock.$functionCall(recipient, abiEncodedCall), 'Address: call to non-contract'); + await expectRevertCustomError(this.mock.$functionCall(recipient, abiEncodedCall), 'AddressEmptyCode', [ + recipient, + ]); }); }); }); @@ -177,7 +189,7 @@ contract('Address', function (accounts) { const abiEncodedCall = this.target.contract.methods.mockFunction().encodeABI(); const receipt = await this.mock.$functionCallWithValue(this.target.address, abiEncodedCall, 0); - expectEvent(receipt, 'return$functionCallWithValue_address_bytes_uint256', { + expectEvent(receipt, 'return$functionCallWithValue', { ret0: web3.eth.abi.encodeParameters(['string'], ['0x1234']), }); await expectEvent.inTransaction(receipt.tx, CallReceiverMock, 'MockFunctionCalled'); @@ -190,9 +202,10 @@ contract('Address', function (accounts) { it('reverts if insufficient sender balance', async function () { const abiEncodedCall = this.target.contract.methods.mockFunction().encodeABI(); - await expectRevert( + await expectRevertCustomError( this.mock.$functionCallWithValue(this.target.address, abiEncodedCall, amount), - 'Address: insufficient balance for call', + 'AddressInsufficientBalance', + [this.mock.address], ); }); @@ -204,7 +217,7 @@ contract('Address', function (accounts) { await send.ether(other, this.mock.address, amount); const receipt = await this.mock.$functionCallWithValue(this.target.address, abiEncodedCall, amount); - expectEvent(receipt, 'return$functionCallWithValue_address_bytes_uint256', { + expectEvent(receipt, 'return$functionCallWithValue', { ret0: web3.eth.abi.encodeParameters(['string'], ['0x1234']), }); await expectEvent.inTransaction(receipt.tx, CallReceiverMock, 'MockFunctionCalled'); @@ -223,7 +236,7 @@ contract('Address', function (accounts) { from: other, value: amount, }); - expectEvent(receipt, 'return$functionCallWithValue_address_bytes_uint256', { + expectEvent(receipt, 'return$functionCallWithValue', { ret0: web3.eth.abi.encodeParameters(['string'], ['0x1234']), }); await expectEvent.inTransaction(receipt.tx, CallReceiverMock, 'MockFunctionCalled'); @@ -235,15 +248,19 @@ contract('Address', function (accounts) { const abiEncodedCall = this.target.contract.methods.mockFunctionNonPayable().encodeABI(); await send.ether(other, this.mock.address, amount); - await expectRevert( + await expectRevertCustomError( this.mock.$functionCallWithValue(this.target.address, abiEncodedCall, amount), - 'Address: low-level call with value failed', + 'FailedInnerCall', + [], ); }); - it('bubbles up error message if specified', async function () { - const errorMsg = 'Address: expected error'; - await expectRevert(this.mock.$functionCallWithValue(this.target.address, '0x12345678', 0, errorMsg), errorMsg); + it('bubbles up error if specified', async function () { + await expectRevertCustomError( + this.mockFnPointer.functionCallWithValue(this.target.address, '0x12345678', 0), + 'CustomRevert', + [], + ); }); }); }); @@ -264,9 +281,10 @@ contract('Address', function (accounts) { it('reverts on a non-static function', async function () { const abiEncodedCall = this.target.contract.methods.mockFunction().encodeABI(); - await expectRevert( + await expectRevertCustomError( this.mock.$functionStaticCall(this.target.address, abiEncodedCall), - 'Address: low-level static call failed', + 'FailedInnerCall', + [], ); }); @@ -283,12 +301,17 @@ contract('Address', function (accounts) { const [recipient] = accounts; const abiEncodedCall = this.target.contract.methods.mockFunction().encodeABI(); - await expectRevert(this.mock.$functionStaticCall(recipient, abiEncodedCall), 'Address: call to non-contract'); + await expectRevertCustomError(this.mock.$functionStaticCall(recipient, abiEncodedCall), 'AddressEmptyCode', [ + recipient, + ]); }); - it('bubbles up error message if specified', async function () { - const errorMsg = 'Address: expected error'; - await expectRevert(this.mock.$functionCallWithValue(this.target.address, '0x12345678', 0, errorMsg), errorMsg); + it('bubbles up error if specified', async function () { + await expectRevertCustomError( + this.mockFnPointer.functionCallWithValue(this.target.address, '0x12345678', 0), + 'CustomRevert', + [], + ); }); }); @@ -308,7 +331,7 @@ contract('Address', function (accounts) { expectEvent( await this.mock.$functionDelegateCall(this.target.address, abiEncodedCall), - 'return$functionDelegateCall_address_bytes', + 'return$functionDelegateCall', { ret0: web3.eth.abi.encodeParameters(['string'], ['0x1234']) }, ); @@ -328,24 +351,32 @@ contract('Address', function (accounts) { const [recipient] = accounts; const abiEncodedCall = this.target.contract.methods.mockFunction().encodeABI(); - await expectRevert(this.mock.$functionDelegateCall(recipient, abiEncodedCall), 'Address: call to non-contract'); + await expectRevertCustomError(this.mock.$functionDelegateCall(recipient, abiEncodedCall), 'AddressEmptyCode', [ + recipient, + ]); }); - it('bubbles up error message if specified', async function () { - const errorMsg = 'Address: expected error'; - await expectRevert(this.mock.$functionCallWithValue(this.target.address, '0x12345678', 0, errorMsg), errorMsg); + it('bubbles up error if specified', async function () { + await expectRevertCustomError( + this.mockFnPointer.functionCallWithValue(this.target.address, '0x12345678', 0), + 'CustomRevert', + [], + ); }); }); describe('verifyCallResult', function () { it('returns returndata on success', async function () { const returndata = '0x123abc'; - expect(await this.mock.$verifyCallResult(true, returndata, '')).to.equal(returndata); + expect(await this.mockFnPointer.verifyCallResult(true, returndata)).to.equal(returndata); + }); + + it('reverts with return data and error', async function () { + await expectRevertCustomError(this.mockFnPointer.verifyCallResult(false, '0x'), 'CustomRevert', []); }); - it('reverts with return data and error m', async function () { - const errorMsg = 'Address: expected error'; - await expectRevert(this.mock.$verifyCallResult(false, '0x', errorMsg), errorMsg); + it('reverts expecting error if provided onRevert is a non-reverting function', async function () { + await expectRevertCustomError(this.mockFnPointer.verifyCallResultVoid(false, '0x'), 'FailedInnerCall', []); }); }); }); diff --git a/test/utils/Create2.test.js b/test/utils/Create2.test.js index 526602600..f88d5504c 100644 --- a/test/utils/Create2.test.js +++ b/test/utils/Create2.test.js @@ -1,6 +1,7 @@ const { balance, ether, expectEvent, expectRevert, send } = require('@openzeppelin/test-helpers'); const { computeCreate2Address } = require('../helpers/create2'); const { expect } = require('chai'); +const { expectRevertCustomError } = require('../helpers/customError'); const Create2 = artifacts.require('$Create2'); const VestingWallet = artifacts.require('VestingWallet'); @@ -78,15 +79,22 @@ contract('Create2', function (accounts) { it('fails deploying a contract in an existent address', async function () { expectEvent(await this.factory.$deploy(0, saltHex, constructorByteCode), 'return$deploy'); - await expectRevert(this.factory.$deploy(0, saltHex, constructorByteCode), 'Create2: Failed on deploy'); + // TODO: Make sure it actually throws "Create2FailedDeployment". + // For some unknown reason, the revert reason sometimes return: + // `revert with unrecognized return data or custom error` + await expectRevert.unspecified(this.factory.$deploy(0, saltHex, constructorByteCode)); }); it('fails deploying a contract if the bytecode length is zero', async function () { - await expectRevert(this.factory.$deploy(0, saltHex, '0x'), 'Create2: bytecode length is zero'); + await expectRevertCustomError(this.factory.$deploy(0, saltHex, '0x'), 'Create2EmptyBytecode', []); }); it('fails deploying a contract if factory contract does not have sufficient balance', async function () { - await expectRevert(this.factory.$deploy(1, saltHex, constructorByteCode), 'Create2: insufficient balance'); + await expectRevertCustomError( + this.factory.$deploy(1, saltHex, constructorByteCode), + 'Create2InsufficientBalance', + [0, 1], + ); }); }); }); diff --git a/test/utils/Multicall.test.js b/test/utils/Multicall.test.js index cfb800769..65443cd0a 100644 --- a/test/utils/Multicall.test.js +++ b/test/utils/Multicall.test.js @@ -1,4 +1,5 @@ -const { BN, expectRevert } = require('@openzeppelin/test-helpers'); +const { BN } = require('@openzeppelin/test-helpers'); +const { expectRevertCustomError } = require('../helpers/customError'); const ERC20MulticallMock = artifacts.require('$ERC20MulticallMock'); @@ -50,7 +51,7 @@ contract('Multicall', function (accounts) { { from: deployer }, ); - await expectRevert(call, 'ERC20: transfer amount exceeds balance'); + await expectRevertCustomError(call, 'ERC20InsufficientBalance', [deployer, 0, amount]); expect(await this.multicallToken.balanceOf(alice)).to.be.bignumber.equal(new BN('0')); }); @@ -63,6 +64,6 @@ contract('Multicall', function (accounts) { { from: deployer }, ); - await expectRevert(call, 'ERC20: transfer amount exceeds balance'); + await expectRevertCustomError(call, 'ERC20InsufficientBalance', [deployer, 0, amount]); }); }); diff --git a/test/utils/Nonces.test.js b/test/utils/Nonces.test.js index 4a01bb1bc..361eeeeec 100644 --- a/test/utils/Nonces.test.js +++ b/test/utils/Nonces.test.js @@ -1,4 +1,5 @@ const expectEvent = require('@openzeppelin/test-helpers/src/expectEvent'); +const { expectRevertCustomError } = require('../helpers/customError'); require('@openzeppelin/test-helpers'); @@ -15,22 +16,57 @@ contract('Nonces', function (accounts) { expect(await this.nonces.nonces(sender)).to.be.bignumber.equal('0'); }); - it('increment a nonce', async function () { - expect(await this.nonces.nonces(sender)).to.be.bignumber.equal('0'); + describe('_useNonce', function () { + it('increments a nonce', async function () { + expect(await this.nonces.nonces(sender)).to.be.bignumber.equal('0'); + + const { receipt } = await this.nonces.$_useNonce(sender); + expectEvent(receipt, 'return$_useNonce', ['0']); + + expect(await this.nonces.nonces(sender)).to.be.bignumber.equal('1'); + }); - const { receipt } = await this.nonces.$_useNonce(sender); - expectEvent(receipt, 'return$_useNonce', ['0']); + it("increments only sender's nonce", async function () { + expect(await this.nonces.nonces(sender)).to.be.bignumber.equal('0'); + expect(await this.nonces.nonces(other)).to.be.bignumber.equal('0'); - expect(await this.nonces.nonces(sender)).to.be.bignumber.equal('1'); + await this.nonces.$_useNonce(sender); + + expect(await this.nonces.nonces(sender)).to.be.bignumber.equal('1'); + expect(await this.nonces.nonces(other)).to.be.bignumber.equal('0'); + }); }); - it('nonce is specific to address argument', async function () { - expect(await this.nonces.nonces(sender)).to.be.bignumber.equal('0'); - expect(await this.nonces.nonces(other)).to.be.bignumber.equal('0'); + describe('_useCheckedNonce', function () { + it('increments a nonce', async function () { + const currentNonce = await this.nonces.nonces(sender); + expect(currentNonce).to.be.bignumber.equal('0'); + + const { receipt } = await this.nonces.$_useCheckedNonce(sender, currentNonce); + expectEvent(receipt, 'return$_useCheckedNonce', [currentNonce]); + + expect(await this.nonces.nonces(sender)).to.be.bignumber.equal('1'); + }); + + it("increments only sender's nonce", async function () { + const currentNonce = await this.nonces.nonces(sender); + + expect(currentNonce).to.be.bignumber.equal('0'); + expect(await this.nonces.nonces(other)).to.be.bignumber.equal('0'); + + await this.nonces.$_useCheckedNonce(sender, currentNonce); - await this.nonces.$_useNonce(sender); + expect(await this.nonces.nonces(sender)).to.be.bignumber.equal('1'); + expect(await this.nonces.nonces(other)).to.be.bignumber.equal('0'); + }); - expect(await this.nonces.nonces(sender)).to.be.bignumber.equal('1'); - expect(await this.nonces.nonces(other)).to.be.bignumber.equal('0'); + it('reverts when nonce is not the expected', async function () { + const currentNonce = await this.nonces.nonces(sender); + await expectRevertCustomError( + this.nonces.$_useCheckedNonce(sender, currentNonce.addn(1)), + 'InvalidAccountNonce', + [sender, currentNonce], + ); + }); }); }); diff --git a/test/utils/ShortStrings.test.js b/test/utils/ShortStrings.test.js index f5cd82fbd..189281d38 100644 --- a/test/utils/ShortStrings.test.js +++ b/test/utils/ShortStrings.test.js @@ -29,7 +29,7 @@ contract('ShortStrings', function () { const decoded = await this.mock.$toString(encoded); expect(decoded).to.be.equal(str); } else { - await expectRevertCustomError(this.mock.$toShortString(str), `StringTooLong("${str}")`); + await expectRevertCustomError(this.mock.$toShortString(str), 'StringTooLong', [str]); } }); @@ -41,7 +41,7 @@ contract('ShortStrings', function () { if (str.length < 32) { expect(await promise).to.be.equal(str); } else { - await expectRevertCustomError(promise, 'InvalidShortString()'); + await expectRevertCustomError(promise, 'InvalidShortString', []); } const length = await this.mock.$byteLengthWithFallback(ret0, 0); diff --git a/test/utils/Strings.test.js b/test/utils/Strings.test.js index 6658871a0..09b958a61 100644 --- a/test/utils/Strings.test.js +++ b/test/utils/Strings.test.js @@ -1,4 +1,5 @@ -const { BN, constants, expectRevert } = require('@openzeppelin/test-helpers'); +const { BN, constants } = require('@openzeppelin/test-helpers'); +const { expectRevertCustomError } = require('../helpers/customError'); const { expect } = require('chai'); @@ -92,9 +93,11 @@ contract('Strings', function () { }); it('converts a positive number (short)', async function () { - await expectRevert( - this.strings.methods['$toHexString(uint256,uint256)'](0x4132, 1), - 'Strings: hex length insufficient', + const length = 1; + await expectRevertCustomError( + this.strings.methods['$toHexString(uint256,uint256)'](0x4132, length), + `StringsInsufficientHexLength`, + [0x4132, length], ); }); diff --git a/test/utils/cryptography/ECDSA.test.js b/test/utils/cryptography/ECDSA.test.js index ae737086b..3fd112a18 100644 --- a/test/utils/cryptography/ECDSA.test.js +++ b/test/utils/cryptography/ECDSA.test.js @@ -1,4 +1,5 @@ -const { expectRevert } = require('@openzeppelin/test-helpers'); +require('@openzeppelin/test-helpers'); +const { expectRevertCustomError } = require('../../helpers/customError'); const { toEthSignedMessageHash, toDataWithIntendedValidatorHash } = require('../../helpers/sign'); const { expect } = require('chai'); @@ -51,17 +52,18 @@ contract('ECDSA', function (accounts) { context('recover with invalid signature', function () { it('with short signature', async function () { - await expectRevert(this.ecdsa.$recover(TEST_MESSAGE, '0x1234'), 'ECDSA: invalid signature length'); + await expectRevertCustomError(this.ecdsa.$recover(TEST_MESSAGE, '0x1234'), 'ECDSAInvalidSignatureLength', [2]); }); it('with long signature', async function () { - await expectRevert( + await expectRevertCustomError( // eslint-disable-next-line max-len this.ecdsa.$recover( TEST_MESSAGE, '0x01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789', ), - 'ECDSA: invalid signature length', + 'ECDSAInvalidSignatureLength', + [85], ); }); }); @@ -93,7 +95,7 @@ contract('ECDSA', function (accounts) { // eslint-disable-next-line max-len const signature = '0x332ce75a821c982f9127538858900d87d3ec1f9f737338ad67cad133fa48feff48e6fa0c18abc62e42820f05943e47af3e9fbe306ce74d64094bdf1691ee53e01c'; - await expectRevert(this.ecdsa.$recover(TEST_MESSAGE, signature), 'ECDSA: invalid signature'); + await expectRevertCustomError(this.ecdsa.$recover(TEST_MESSAGE, signature), 'ECDSAInvalidSignature', []); }); }); @@ -141,11 +143,12 @@ contract('ECDSA', function (accounts) { it('reverts wrong v values', async function () { for (const v of ['00', '01']) { const signature = signatureWithoutV + v; - await expectRevert(this.ecdsa.$recover(TEST_MESSAGE, signature), 'ECDSA: invalid signature'); + await expectRevertCustomError(this.ecdsa.$recover(TEST_MESSAGE, signature), 'ECDSAInvalidSignature', []); - await expectRevert( + await expectRevertCustomError( this.ecdsa.methods['$recover(bytes32,uint8,bytes32,bytes32)'](TEST_MESSAGE, ...split(signature)), - 'ECDSA: invalid signature', + 'ECDSAInvalidSignature', + [], ); } }); @@ -153,9 +156,10 @@ contract('ECDSA', function (accounts) { it('rejects short EIP2098 format', async function () { const v = '1b'; // 27 = 1b. const signature = signatureWithoutV + v; - await expectRevert( + await expectRevertCustomError( this.ecdsa.$recover(TEST_MESSAGE, to2098Format(signature)), - 'ECDSA: invalid signature length', + 'ECDSAInvalidSignatureLength', + [64], ); }); }); @@ -203,11 +207,12 @@ contract('ECDSA', function (accounts) { it('reverts invalid v values', async function () { for (const v of ['00', '01']) { const signature = signatureWithoutV + v; - await expectRevert(this.ecdsa.$recover(TEST_MESSAGE, signature), 'ECDSA: invalid signature'); + await expectRevertCustomError(this.ecdsa.$recover(TEST_MESSAGE, signature), 'ECDSAInvalidSignature', []); - await expectRevert( + await expectRevertCustomError( this.ecdsa.methods['$recover(bytes32,uint8,bytes32,bytes32)'](TEST_MESSAGE, ...split(signature)), - 'ECDSA: invalid signature', + 'ECDSAInvalidSignature', + [], ); } }); @@ -215,9 +220,10 @@ contract('ECDSA', function (accounts) { it('rejects short EIP2098 format', async function () { const v = '1c'; // 27 = 1b. const signature = signatureWithoutV + v; - await expectRevert( + await expectRevertCustomError( this.ecdsa.$recover(TEST_MESSAGE, to2098Format(signature)), - 'ECDSA: invalid signature length', + 'ECDSAInvalidSignatureLength', + [64], ); }); }); @@ -227,10 +233,12 @@ contract('ECDSA', function (accounts) { // eslint-disable-next-line max-len const highSSignature = '0xe742ff452d41413616a5bf43fe15dd88294e983d3d36206c2712f39083d638bde0a0fc89be718fbc1033e1d30d78be1c68081562ed2e97af876f286f3453231d1b'; - await expectRevert(this.ecdsa.$recover(message, highSSignature), "ECDSA: invalid signature 's' value"); - await expectRevert( - this.ecdsa.methods['$recover(bytes32,uint8,bytes32,bytes32)'](TEST_MESSAGE, ...split(highSSignature)), - "ECDSA: invalid signature 's' value", + const [r, v, s] = split(highSSignature); + await expectRevertCustomError(this.ecdsa.$recover(message, highSSignature), 'ECDSAInvalidSignatureS', [s]); + await expectRevertCustomError( + this.ecdsa.methods['$recover(bytes32,uint8,bytes32,bytes32)'](TEST_MESSAGE, r, v, s), + 'ECDSAInvalidSignatureS', + [s], ); expect(() => to2098Format(highSSignature)).to.throw("invalid signature 's' value"); }); diff --git a/test/utils/cryptography/MerkleProof.test.js b/test/utils/cryptography/MerkleProof.test.js index 62157b56a..43ef76bfa 100644 --- a/test/utils/cryptography/MerkleProof.test.js +++ b/test/utils/cryptography/MerkleProof.test.js @@ -1,10 +1,10 @@ -require('@openzeppelin/test-helpers'); - const { expectRevert } = require('@openzeppelin/test-helpers'); + const { MerkleTree } = require('merkletreejs'); const keccak256 = require('keccak256'); const { expect } = require('chai'); +const { expectRevertCustomError } = require('../../helpers/customError'); const MerkleProof = artifacts.require('$MerkleProof'); @@ -106,23 +106,25 @@ contract('MerkleProof', function () { const root = merkleTree.getRoot(); - await expectRevert( + await expectRevertCustomError( this.merkleProof.$multiProofVerify( [leaves[1], fill, merkleTree.layers[1][1]], [false, false, false], root, [leaves[0], badLeaf], // A, E ), - 'MerkleProof: invalid multiproof', + 'MerkleProofInvalidMultiproof', + [], ); - await expectRevert( + await expectRevertCustomError( this.merkleProof.$multiProofVerifyCalldata( [leaves[1], fill, merkleTree.layers[1][1]], [false, false, false], root, [leaves[0], badLeaf], // A, E ), - 'MerkleProof: invalid multiproof', + 'MerkleProofInvalidMultiproof', + [], ); }); diff --git a/test/utils/math/SafeCast.test.js b/test/utils/math/SafeCast.test.js index 63223f5d1..4b8ec5a72 100644 --- a/test/utils/math/SafeCast.test.js +++ b/test/utils/math/SafeCast.test.js @@ -1,6 +1,7 @@ -const { BN, expectRevert } = require('@openzeppelin/test-helpers'); +const { BN } = require('@openzeppelin/test-helpers'); const { expect } = require('chai'); const { range } = require('../../../scripts/helpers'); +const { expectRevertCustomError } = require('../../helpers/customError'); const SafeCast = artifacts.require('$SafeCast'); @@ -26,16 +27,18 @@ contract('SafeCast', async function () { }); it(`reverts when downcasting 2^${bits} (${maxValue.addn(1)})`, async function () { - await expectRevert( + await expectRevertCustomError( this.safeCast[`$toUint${bits}`](maxValue.addn(1)), - `SafeCast: value doesn't fit in ${bits} bits`, + `SafeCastOverflowedUintDowncast`, + [bits, maxValue.addn(1)], ); }); it(`reverts when downcasting 2^${bits} + 1 (${maxValue.addn(2)})`, async function () { - await expectRevert( + await expectRevertCustomError( this.safeCast[`$toUint${bits}`](maxValue.addn(2)), - `SafeCast: value doesn't fit in ${bits} bits`, + `SafeCastOverflowedUintDowncast`, + [bits, maxValue.addn(2)], ); }); }); @@ -60,11 +63,11 @@ contract('SafeCast', async function () { }); it('reverts when casting -1', async function () { - await expectRevert(this.safeCast.$toUint256(-1), 'SafeCast: value must be positive'); + await expectRevertCustomError(this.safeCast.$toUint256(-1), `SafeCastOverflowedIntToUint`, [-1]); }); it(`reverts when casting INT256_MIN (${minInt256})`, async function () { - await expectRevert(this.safeCast.$toUint256(minInt256), 'SafeCast: value must be positive'); + await expectRevertCustomError(this.safeCast.$toUint256(minInt256), `SafeCastOverflowedIntToUint`, [minInt256]); }); }); @@ -94,30 +97,34 @@ contract('SafeCast', async function () { }); it(`reverts when downcasting -2^${bits - 1} - 1 (${minValue.subn(1)})`, async function () { - await expectRevert( + await expectRevertCustomError( this.safeCast[`$toInt${bits}`](minValue.subn(1)), - `SafeCast: value doesn't fit in ${bits} bits`, + `SafeCastOverflowedIntDowncast`, + [bits, minValue.subn(1)], ); }); it(`reverts when downcasting -2^${bits - 1} - 2 (${minValue.subn(2)})`, async function () { - await expectRevert( + await expectRevertCustomError( this.safeCast[`$toInt${bits}`](minValue.subn(2)), - `SafeCast: value doesn't fit in ${bits} bits`, + `SafeCastOverflowedIntDowncast`, + [bits, minValue.subn(2)], ); }); it(`reverts when downcasting 2^${bits - 1} (${maxValue.addn(1)})`, async function () { - await expectRevert( + await expectRevertCustomError( this.safeCast[`$toInt${bits}`](maxValue.addn(1)), - `SafeCast: value doesn't fit in ${bits} bits`, + `SafeCastOverflowedIntDowncast`, + [bits, maxValue.addn(1)], ); }); it(`reverts when downcasting 2^${bits - 1} + 1 (${maxValue.addn(2)})`, async function () { - await expectRevert( + await expectRevertCustomError( this.safeCast[`$toInt${bits}`](maxValue.addn(2)), - `SafeCast: value doesn't fit in ${bits} bits`, + `SafeCastOverflowedIntDowncast`, + [bits, maxValue.addn(2)], ); }); }); @@ -142,11 +149,13 @@ contract('SafeCast', async function () { }); it(`reverts when casting INT256_MAX + 1 (${maxInt256.addn(1)})`, async function () { - await expectRevert(this.safeCast.$toInt256(maxInt256.addn(1)), "SafeCast: value doesn't fit in an int256"); + await expectRevertCustomError(this.safeCast.$toInt256(maxInt256.addn(1)), 'SafeCastOverflowedUintToInt', [ + maxInt256.addn(1), + ]); }); it(`reverts when casting UINT256_MAX (${maxUint256})`, async function () { - await expectRevert(this.safeCast.$toInt256(maxUint256), "SafeCast: value doesn't fit in an int256"); + await expectRevertCustomError(this.safeCast.$toInt256(maxUint256), 'SafeCastOverflowedUintToInt', [maxUint256]); }); }); }); diff --git a/test/utils/structs/Checkpoints.test.js b/test/utils/structs/Checkpoints.test.js index ad95373a4..d82180127 100644 --- a/test/utils/structs/Checkpoints.test.js +++ b/test/utils/structs/Checkpoints.test.js @@ -1,7 +1,9 @@ -const { expectRevert } = require('@openzeppelin/test-helpers'); +require('@openzeppelin/test-helpers'); + const { expect } = require('chai'); const { VALUE_SIZES } = require('../../../scripts/generate/templates/Checkpoints.opts.js'); +const { expectRevertCustomError } = require('../../helpers/customError.js'); const $Checkpoints = artifacts.require('$Checkpoints'); @@ -77,7 +79,11 @@ contract('Checkpoints', function () { }); it('cannot push values in the past', async function () { - await expectRevert(this.methods.push(last(this.checkpoints).key - 1, '0'), 'Checkpoint: decreasing keys'); + await expectRevertCustomError( + this.methods.push(last(this.checkpoints).key - 1, '0'), + 'CheckpointUnorderedInsertion', + [], + ); }); it('can update last value', async function () { diff --git a/test/utils/structs/DoubleEndedQueue.test.js b/test/utils/structs/DoubleEndedQueue.test.js index 2fbb8dc25..cbf37d76b 100644 --- a/test/utils/structs/DoubleEndedQueue.test.js +++ b/test/utils/structs/DoubleEndedQueue.test.js @@ -30,10 +30,10 @@ contract('DoubleEndedQueue', function () { }); it('reverts on accesses', async function () { - await expectRevertCustomError(this.deque.$popBack(0), 'Empty()'); - await expectRevertCustomError(this.deque.$popFront(0), 'Empty()'); - await expectRevertCustomError(this.deque.$back(0), 'Empty()'); - await expectRevertCustomError(this.deque.$front(0), 'Empty()'); + await expectRevertCustomError(this.deque.$popBack(0), 'QueueEmpty', []); + await expectRevertCustomError(this.deque.$popFront(0), 'QueueEmpty', []); + await expectRevertCustomError(this.deque.$back(0), 'QueueEmpty', []); + await expectRevertCustomError(this.deque.$front(0), 'QueueEmpty', []); }); }); @@ -54,7 +54,7 @@ contract('DoubleEndedQueue', function () { }); it('out of bounds access', async function () { - await expectRevertCustomError(this.deque.$at(0, this.content.length), 'OutOfBounds()'); + await expectRevertCustomError(this.deque.$at(0, this.content.length), 'QueueOutOfBounds', []); }); describe('push', function () { diff --git a/test/utils/structs/EnumerableMap.behavior.js b/test/utils/structs/EnumerableMap.behavior.js index 3db45df6a..67b19e39a 100644 --- a/test/utils/structs/EnumerableMap.behavior.js +++ b/test/utils/structs/EnumerableMap.behavior.js @@ -1,7 +1,8 @@ -const { expectEvent, expectRevert } = require('@openzeppelin/test-helpers'); +const { expectEvent } = require('@openzeppelin/test-helpers'); const { expect } = require('chai'); const zip = require('lodash.zip'); +const { expectRevertCustomError } = require('../../helpers/customError'); function shouldBehaveLikeMap(keys, values, zeroValue, methods, events) { const [keyA, keyB, keyC] = keys; @@ -150,7 +151,10 @@ function shouldBehaveLikeMap(keys, values, zeroValue, methods, events) { expect(await methods.get(this.map, keyA).then(r => r.toString())).to.be.equal(valueA.toString()); }); it('missing value', async function () { - await expectRevert(methods.get(this.map, keyB), 'EnumerableMap: nonexistent key'); + const key = web3.utils.toHex(keyB); + await expectRevertCustomError(methods.get(this.map, keyB), 'EnumerableMapNonexistentKey', [ + key.length == 66 ? key : web3.utils.padLeft(key, 64, '0'), + ]); }); }); From fe08f58c69ab97ccb3ad54a568e7ac8335e01396 Mon Sep 17 00:00:00 2001 From: Zack Reneau-Wedeen Date: Tue, 13 Jun 2023 15:50:16 -0400 Subject: [PATCH 095/182] Update README.md (#4335) --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index aba991710..8b44917fe 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,9 @@ Finally, you may want to take a look at the [guides on our blog](https://blog.op This project is maintained by [OpenZeppelin](https://openzeppelin.com) with the goal of providing a secure and reliable library of smart contract components for the ecosystem. We address security through risk management in various areas such as engineering and open source best practices, scoping and API design, multi-layered review processes, and incident response preparedness. -The security policy is detailed in [`SECURITY.md`](./SECURITY.md), and specifies how you can report security vulnerabilities, which versions will receive security patches, and how to stay informed about them. We run a [bug bounty program on Immunefi](https://immunefi.com/bounty/openzeppelin) to reward the responsible disclosure of vulnerabilities. +The [OpenZeppelin Contracts Security Center](https://contracts.openzeppelin.com/security) contains more details about the secure development process. + +The security policy is detailed in [`SECURITY.md`](./SECURITY.md) as well, and specifies how you can report security vulnerabilities, which versions will receive security patches, and how to stay informed about them. We run a [bug bounty program on Immunefi](https://immunefi.com/bounty/openzeppelin) to reward the responsible disclosure of vulnerabilities. The engineering guidelines we follow to promote project quality can be found in [`GUIDELINES.md`](./GUIDELINES.md). From d6c7cee32191850d3635222826985f46996e64fd Mon Sep 17 00:00:00 2001 From: Zack Reneau-Wedeen Date: Tue, 13 Jun 2023 16:32:24 -0400 Subject: [PATCH 096/182] Update index.adoc (#4336) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ernesto García --- docs/modules/ROOT/pages/index.adoc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/modules/ROOT/pages/index.adoc b/docs/modules/ROOT/pages/index.adoc index b3b51abfc..b46a8d5a3 100644 --- a/docs/modules/ROOT/pages/index.adoc +++ b/docs/modules/ROOT/pages/index.adoc @@ -45,6 +45,8 @@ To keep your system secure, you should **always** use the installed code as-is, Please report any security issues you find via our https://www.immunefi.com/bounty/openzeppelin[bug bounty program on Immunefi] or directly to security@openzeppelin.org. +The https://contracts.openzeppelin.com/security[Security Center] contains more details about the secure development process. + [[next-steps]] == Learn More From 604025400f9be5c32581bb6ab03a46bbc09c5562 Mon Sep 17 00:00:00 2001 From: Renan Souza Date: Tue, 13 Jun 2023 23:54:09 -0300 Subject: [PATCH 097/182] Add EIP-712 `name` and `version` getters (#4303) Co-authored-by: Hadrien Croubois Co-authored-by: ernestognw --- .changeset/little-falcons-build.md | 5 ++ contracts/utils/cryptography/EIP712.sol | 30 ++++++++- scripts/upgradeable/upgradeable.patch | 90 +++++++++++++------------ test/utils/cryptography/EIP712.test.js | 8 +++ 4 files changed, 88 insertions(+), 45 deletions(-) create mode 100644 .changeset/little-falcons-build.md diff --git a/.changeset/little-falcons-build.md b/.changeset/little-falcons-build.md new file mode 100644 index 000000000..b310a8ae6 --- /dev/null +++ b/.changeset/little-falcons-build.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': minor +--- + +`EIP712`: Add internal getters for the name and version strings diff --git a/contracts/utils/cryptography/EIP712.sol b/contracts/utils/cryptography/EIP712.sol index b9c9c9d19..2628014f1 100644 --- a/contracts/utils/cryptography/EIP712.sol +++ b/contracts/utils/cryptography/EIP712.sol @@ -130,12 +130,38 @@ abstract contract EIP712 is IERC5267 { { return ( hex"0f", // 01111 - _name.toStringWithFallback(_nameFallback), - _version.toStringWithFallback(_versionFallback), + _EIP712Name(), + _EIP712Version(), block.chainid, address(this), bytes32(0), new uint256[](0) ); } + + /** + * @dev The name parameter for the EIP712 domain. + * + * NOTE: By default this function reads _name which is an immutable value. + * It only reads from storage if necessary (in case the value is too large to fit in a ShortString). + * + * _Available since v5.0._ + */ + // solhint-disable-next-line func-name-mixedcase + function _EIP712Name() internal view returns (string memory) { + return _name.toStringWithFallback(_nameFallback); + } + + /** + * @dev The version parameter for the EIP712 domain. + * + * NOTE: By default this function reads _version which is an immutable value. + * It only reads from storage if necessary (in case the value is too large to fit in a ShortString). + * + * _Available since v5.0._ + */ + // solhint-disable-next-line func-name-mixedcase + function _EIP712Version() internal view returns (string memory) { + return _version.toStringWithFallback(_versionFallback); + } } diff --git a/scripts/upgradeable/upgradeable.patch b/scripts/upgradeable/upgradeable.patch index b29e7d12b..554775ffe 100644 --- a/scripts/upgradeable/upgradeable.patch +++ b/scripts/upgradeable/upgradeable.patch @@ -59,10 +59,10 @@ index ff596b0c..00000000 - - diff --git a/README.md b/README.md -index 9fc95518..53130e3c 100644 +index aba99171..6656267b 100644 --- a/README.md +++ b/README.md -@@ -16,17 +16,20 @@ +@@ -19,17 +19,20 @@ :building_construction: **Want to scale your decentralized application?** Check out [OpenZeppelin Defender](https://openzeppelin.com/defender) — a secure platform for automating and monitoring your operations. @@ -85,7 +85,7 @@ index 9fc95518..53130e3c 100644 ### Usage -@@ -35,10 +38,11 @@ Once installed, you can use the contracts in the library by importing them: +@@ -38,10 +41,11 @@ Once installed, you can use the contracts in the library by importing them: ```solidity pragma solidity ^0.8.19; @@ -101,7 +101,7 @@ index 9fc95518..53130e3c 100644 } ``` diff --git a/contracts/finance/VestingWallet.sol b/contracts/finance/VestingWallet.sol -index bb70d19f..38513771 100644 +index 5b7e1b15..1ca745d6 100644 --- a/contracts/finance/VestingWallet.sol +++ b/contracts/finance/VestingWallet.sol @@ -18,6 +18,8 @@ import "../utils/Context.sol"; @@ -114,7 +114,7 @@ index bb70d19f..38513771 100644 contract VestingWallet is Context { event EtherReleased(uint256 amount); diff --git a/contracts/governance/extensions/GovernorVotes.sol b/contracts/governance/extensions/GovernorVotes.sol -index 64431711..885f0e42 100644 +index 5d8318f4..ef3cde55 100644 --- a/contracts/governance/extensions/GovernorVotes.sol +++ b/contracts/governance/extensions/GovernorVotes.sol @@ -10,6 +10,8 @@ import "../../interfaces/IERC5805.sol"; @@ -127,7 +127,7 @@ index 64431711..885f0e42 100644 abstract contract GovernorVotes is Governor { IERC5805 public immutable token; diff --git a/contracts/package.json b/contracts/package.json -index 55e70b17..ceefb984 100644 +index 4d0f576b..822fd471 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -1,5 +1,5 @@ @@ -135,7 +135,7 @@ index 55e70b17..ceefb984 100644 - "name": "@openzeppelin/contracts", + "name": "@openzeppelin/contracts-upgradeable", "description": "Secure Smart Contract library for Solidity", - "version": "4.8.2", + "version": "4.9.0", "files": [ @@ -13,7 +13,7 @@ }, @@ -147,7 +147,7 @@ index 55e70b17..ceefb984 100644 "keywords": [ "solidity", diff --git a/contracts/token/ERC20/extensions/ERC20Capped.sol b/contracts/token/ERC20/extensions/ERC20Capped.sol -index 16f830d1..9ef98148 100644 +index cda07265..d314148c 100644 --- a/contracts/token/ERC20/extensions/ERC20Capped.sol +++ b/contracts/token/ERC20/extensions/ERC20Capped.sol @@ -7,6 +7,8 @@ import "../ERC20.sol"; @@ -160,20 +160,20 @@ index 16f830d1..9ef98148 100644 abstract contract ERC20Capped is ERC20 { uint256 private immutable _cap; diff --git a/contracts/token/ERC20/extensions/ERC20Permit.sol b/contracts/token/ERC20/extensions/ERC20Permit.sol -index a357199b..9dc8e894 100644 +index 9379e445..e02f0644 100644 --- a/contracts/token/ERC20/extensions/ERC20Permit.sol +++ b/contracts/token/ERC20/extensions/ERC20Permit.sol -@@ -18,6 +18,8 @@ import "../../../utils/Counters.sol"; +@@ -18,6 +18,8 @@ import "../../../utils/Nonces.sol"; * need to send a transaction, and thus is not required to hold Ether at all. * * _Available since v3.4._ + * + * @custom:storage-size 51 */ - abstract contract ERC20Permit is ERC20, IERC20Permit, EIP712 { - using Counters for Counters.Counter; + abstract contract ERC20Permit is ERC20, IERC20Permit, EIP712, Nonces { + // solhint-disable-next-line var-name-mixedcase diff --git a/contracts/token/ERC20/extensions/ERC20Wrapper.sol b/contracts/token/ERC20/extensions/ERC20Wrapper.sol -index bfe782e4..7264fe32 100644 +index bf2b225c..0e5b3628 100644 --- a/contracts/token/ERC20/extensions/ERC20Wrapper.sol +++ b/contracts/token/ERC20/extensions/ERC20Wrapper.sol @@ -14,6 +14,8 @@ import "../utils/SafeERC20.sol"; @@ -186,7 +186,7 @@ index bfe782e4..7264fe32 100644 abstract contract ERC20Wrapper is ERC20 { IERC20 private immutable _underlying; diff --git a/contracts/utils/cryptography/EIP712.sol b/contracts/utils/cryptography/EIP712.sol -index 6a4e1cad..55d8eced 100644 +index 2628014f..7d5193c8 100644 --- a/contracts/utils/cryptography/EIP712.sol +++ b/contracts/utils/cryptography/EIP712.sol @@ -4,7 +4,6 @@ @@ -268,7 +268,7 @@ index 6a4e1cad..55d8eced 100644 } /** -@@ -129,14 +114,80 @@ abstract contract EIP712 is IERC5267 { +@@ -128,6 +113,10 @@ abstract contract EIP712 is IERC5267 { uint256[] memory extensions ) { @@ -278,34 +278,34 @@ index 6a4e1cad..55d8eced 100644 + return ( hex"0f", // 01111 -- _name.toStringWithFallback(_nameFallback), -- _version.toStringWithFallback(_versionFallback), -+ _EIP712Name(), -+ _EIP712Version(), - block.chainid, - address(this), - bytes32(0), - new uint256[](0) - ); - } -+ -+ /** -+ * @dev The name parameter for the EIP712 domain. -+ * + _EIP712Name(), +@@ -142,26 +131,62 @@ abstract contract EIP712 is IERC5267 { + /** + * @dev The name parameter for the EIP712 domain. + * +- * NOTE: By default this function reads _name which is an immutable value. +- * It only reads from storage if necessary (in case the value is too large to fit in a ShortString). +- * +- * _Available since v5.0._ + * NOTE: This function reads from storage by default, but can be redefined to return a constant value if gas costs + * are a concern. -+ */ -+ function _EIP712Name() internal virtual view returns (string memory) { + */ +- // solhint-disable-next-line func-name-mixedcase +- function _EIP712Name() internal view returns (string memory) { +- return _name.toStringWithFallback(_nameFallback); ++ function _EIP712Name() internal view virtual returns (string memory) { + return _name; -+ } -+ -+ /** -+ * @dev The version parameter for the EIP712 domain. -+ * + } + + /** + * @dev The version parameter for the EIP712 domain. + * +- * NOTE: By default this function reads _version which is an immutable value. +- * It only reads from storage if necessary (in case the value is too large to fit in a ShortString). + * NOTE: This function reads from storage by default, but can be redefined to return a constant value if gas costs + * are a concern. + */ -+ function _EIP712Version() internal virtual view returns (string memory) { ++ function _EIP712Version() internal view virtual returns (string memory) { + return _version; + } + @@ -332,9 +332,13 @@ index 6a4e1cad..55d8eced 100644 + + /** + * @dev The hash of the version parameter for the EIP712 domain. -+ * + * +- * _Available since v5.0._ + * NOTE: In previous versions this function was virtual. In this version you should override `_EIP712Version` instead. -+ */ + */ +- // solhint-disable-next-line func-name-mixedcase +- function _EIP712Version() internal view returns (string memory) { +- return _version.toStringWithFallback(_versionFallback); + function _EIP712VersionHash() internal view returns (bytes32) { + string memory version = _EIP712Version(); + if (bytes(version).length > 0) { @@ -349,13 +353,13 @@ index 6a4e1cad..55d8eced 100644 + return keccak256(""); + } + } -+ } + } } diff --git a/package.json b/package.json -index 8458dd61..b4672240 100644 +index c070915f..9a513cac 100644 --- a/package.json +++ b/package.json -@@ -36,7 +36,7 @@ +@@ -33,7 +33,7 @@ }, "repository": { "type": "git", @@ -365,7 +369,7 @@ index 8458dd61..b4672240 100644 "keywords": [ "solidity", diff --git a/test/utils/cryptography/EIP712.test.js b/test/utils/cryptography/EIP712.test.js -index 54a4e772..ba4602ed 100644 +index 7ea535b7..32e3a370 100644 --- a/test/utils/cryptography/EIP712.test.js +++ b/test/utils/cryptography/EIP712.test.js @@ -47,26 +47,6 @@ contract('EIP712', function (accounts) { diff --git a/test/utils/cryptography/EIP712.test.js b/test/utils/cryptography/EIP712.test.js index 54a4e7723..7ea535b7f 100644 --- a/test/utils/cryptography/EIP712.test.js +++ b/test/utils/cryptography/EIP712.test.js @@ -98,6 +98,14 @@ contract('EIP712', function (accounts) { await this.eip712.verify(signature, wallet.getAddressString(), message.to, message.contents); }); + + it('name', async function () { + expect(await this.eip712.$_EIP712Name()).to.be.equal(name); + }); + + it('version', async function () { + expect(await this.eip712.$_EIP712Version()).to.be.equal(version); + }); }); } }); From 7cc2cbfeb56a00bfe055ebe0d27da902d95ed3d3 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Wed, 14 Jun 2023 16:00:38 +0200 Subject: [PATCH 098/182] Cleanup the structure of GovernorTimelockControl.test.js (#4302) --- .../GovernorTimelockControl.test.js | 554 +++++++++--------- 1 file changed, 262 insertions(+), 292 deletions(-) diff --git a/test/governance/extensions/GovernorTimelockControl.test.js b/test/governance/extensions/GovernorTimelockControl.test.js index ef32148fa..10a7f05b2 100644 --- a/test/governance/extensions/GovernorTimelockControl.test.js +++ b/test/governance/extensions/GovernorTimelockControl.test.js @@ -136,323 +136,204 @@ contract('GovernorTimelockControl', function (accounts) { await this.helper.propose(); await this.helper.waitForSnapshot(); await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }); - await this.helper.vote({ support: Enums.VoteType.For }, { from: voter2 }); - await this.helper.vote({ support: Enums.VoteType.Against }, { from: voter3 }); - await this.helper.vote({ support: Enums.VoteType.Abstain }, { from: voter4 }); await this.helper.waitForDeadline(); - const txQueue = await this.helper.queue(); - await this.helper.waitForEta(); - const txExecute = await this.helper.execute(); + await this.helper.queue(); + await expectRevertCustomError(this.helper.queue(), 'GovernorUnexpectedProposalState', [ + this.proposal.id, + Enums.ProposalState.Queued, + proposalStatesToBitMap([Enums.ProposalState.Succeeded]), + ]); + }); + }); - expectEvent(txQueue, 'ProposalQueued', { proposalId: this.proposal.id }); - await expectEvent.inTransaction(txQueue.tx, this.timelock, 'CallScheduled', { - id: this.proposal.timelockid, - }); + describe('on execute', function () { + it('if not queued', async function () { + await this.helper.propose(); + await this.helper.waitForSnapshot(); + await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }); + await this.helper.waitForDeadline(+1); - expectEvent(txExecute, 'ProposalExecuted', { proposalId: this.proposal.id }); - await expectEvent.inTransaction(txExecute.tx, this.timelock, 'CallExecuted', { - id: this.proposal.timelockid, - }); - await expectEvent.inTransaction(txExecute.tx, this.receiver, 'MockFunctionCalled'); + expect(await this.mock.state(this.proposal.id)).to.be.bignumber.equal(Enums.ProposalState.Succeeded); + + await expectRevertCustomError(this.helper.execute(), 'TimelockUnexpectedOperationState', [ + this.proposal.timelockid, + Enums.OperationState.Ready, + ]); }); - describe('should revert', function () { - describe('on queue', function () { - it('if already queued', async function () { - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }); - await this.helper.waitForDeadline(); - await this.helper.queue(); - await expectRevertCustomError(this.helper.queue(), 'GovernorUnexpectedProposalState', [ - this.proposal.id, - Enums.ProposalState.Queued, - proposalStatesToBitMap([Enums.ProposalState.Succeeded]), - ]); - }); - }); + it('if too early', async function () { + await this.helper.propose(); + await this.helper.waitForSnapshot(); + await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }); + await this.helper.waitForDeadline(); + await this.helper.queue(); - describe('on execute', function () { - it('if not queued', async function () { - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }); - await this.helper.waitForDeadline(+1); - - expect(await this.mock.state(this.proposal.id)).to.be.bignumber.equal(Enums.ProposalState.Succeeded); - - await expectRevertCustomError(this.helper.execute(), 'TimelockUnexpectedOperationState', [ - this.proposal.timelockid, - Enums.OperationState.Ready, - ]); - }); - - it('if too early', async function () { - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }); - await this.helper.waitForDeadline(); - await this.helper.queue(); - - expect(await this.mock.state(this.proposal.id)).to.be.bignumber.equal(Enums.ProposalState.Queued); - - await expectRevertCustomError(this.helper.execute(), 'TimelockUnexpectedOperationState', [ - this.proposal.timelockid, - Enums.OperationState.Ready, - ]); - }); - - it('if already executed', async function () { - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }); - await this.helper.waitForDeadline(); - await this.helper.queue(); - await this.helper.waitForEta(); - await this.helper.execute(); - await expectRevertCustomError(this.helper.execute(), 'GovernorUnexpectedProposalState', [ - this.proposal.id, - Enums.ProposalState.Executed, - proposalStatesToBitMap([Enums.ProposalState.Succeeded, Enums.ProposalState.Queued]), - ]); - }); - - it('if already executed by another proposer', async function () { - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }); - await this.helper.waitForDeadline(); - await this.helper.queue(); - await this.helper.waitForEta(); - - await this.timelock.executeBatch( - ...this.proposal.shortProposal.slice(0, 3), - '0x0', - this.proposal.shortProposal[3], - ); - - await expectRevertCustomError(this.helper.execute(), 'GovernorUnexpectedProposalState', [ - this.proposal.id, - Enums.ProposalState.Executed, - proposalStatesToBitMap([Enums.ProposalState.Succeeded, Enums.ProposalState.Queued]), - ]); - }); - }); + expect(await this.mock.state(this.proposal.id)).to.be.bignumber.equal(Enums.ProposalState.Queued); + + await expectRevertCustomError(this.helper.execute(), 'TimelockUnexpectedOperationState', [ + this.proposal.timelockid, + Enums.OperationState.Ready, + ]); }); - describe('cancel', function () { - it('cancel before queue prevents scheduling', async function () { - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }); - await this.helper.waitForDeadline(); - - expectEvent(await this.helper.cancel('internal'), 'ProposalCanceled', { proposalId: this.proposal.id }); - - expect(await this.mock.state(this.proposal.id)).to.be.bignumber.equal(Enums.ProposalState.Canceled); - await expectRevertCustomError(this.helper.queue(), 'GovernorUnexpectedProposalState', [ - this.proposal.id, - Enums.ProposalState.Canceled, - proposalStatesToBitMap([Enums.ProposalState.Succeeded]), - ]); - }); + it('if already executed', async function () { + await this.helper.propose(); + await this.helper.waitForSnapshot(); + await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }); + await this.helper.waitForDeadline(); + await this.helper.queue(); + await this.helper.waitForEta(); + await this.helper.execute(); + await expectRevertCustomError(this.helper.execute(), 'GovernorUnexpectedProposalState', [ + this.proposal.id, + Enums.ProposalState.Executed, + proposalStatesToBitMap([Enums.ProposalState.Succeeded, Enums.ProposalState.Queued]), + ]); + }); - it('cancel after queue prevents executing', async function () { - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }); - await this.helper.waitForDeadline(); - await this.helper.queue(); - - expectEvent(await this.helper.cancel('internal'), 'ProposalCanceled', { proposalId: this.proposal.id }); - - expect(await this.mock.state(this.proposal.id)).to.be.bignumber.equal(Enums.ProposalState.Canceled); - await expectRevertCustomError(this.helper.execute(), 'GovernorUnexpectedProposalState', [ - this.proposal.id, - Enums.ProposalState.Canceled, - proposalStatesToBitMap([Enums.ProposalState.Succeeded, Enums.ProposalState.Queued]), - ]); - }); + it('if already executed by another proposer', async function () { + await this.helper.propose(); + await this.helper.waitForSnapshot(); + await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }); + await this.helper.waitForDeadline(); + await this.helper.queue(); + await this.helper.waitForEta(); + + await this.timelock.executeBatch( + ...this.proposal.shortProposal.slice(0, 3), + '0x0', + this.proposal.shortProposal[3], + ); + + await expectRevertCustomError(this.helper.execute(), 'GovernorUnexpectedProposalState', [ + this.proposal.id, + Enums.ProposalState.Executed, + proposalStatesToBitMap([Enums.ProposalState.Succeeded, Enums.ProposalState.Queued]), + ]); + }); + }); + }); + + describe('cancel', function () { + it('cancel before queue prevents scheduling', async function () { + await this.helper.propose(); + await this.helper.waitForSnapshot(); + await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }); + await this.helper.waitForDeadline(); + + expectEvent(await this.helper.cancel('internal'), 'ProposalCanceled', { proposalId: this.proposal.id }); + + expect(await this.mock.state(this.proposal.id)).to.be.bignumber.equal(Enums.ProposalState.Canceled); + await expectRevertCustomError(this.helper.queue(), 'GovernorUnexpectedProposalState', [ + this.proposal.id, + Enums.ProposalState.Canceled, + proposalStatesToBitMap([Enums.ProposalState.Succeeded]), + ]); + }); - it('cancel on timelock is reflected on governor', async function () { - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }); - await this.helper.waitForDeadline(); - await this.helper.queue(); + it('cancel after queue prevents executing', async function () { + await this.helper.propose(); + await this.helper.waitForSnapshot(); + await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }); + await this.helper.waitForDeadline(); + await this.helper.queue(); + + expectEvent(await this.helper.cancel('internal'), 'ProposalCanceled', { proposalId: this.proposal.id }); + + expect(await this.mock.state(this.proposal.id)).to.be.bignumber.equal(Enums.ProposalState.Canceled); + await expectRevertCustomError(this.helper.execute(), 'GovernorUnexpectedProposalState', [ + this.proposal.id, + Enums.ProposalState.Canceled, + proposalStatesToBitMap([Enums.ProposalState.Succeeded, Enums.ProposalState.Queued]), + ]); + }); - expect(await this.mock.state(this.proposal.id)).to.be.bignumber.equal(Enums.ProposalState.Queued); + it('cancel on timelock is reflected on governor', async function () { + await this.helper.propose(); + await this.helper.waitForSnapshot(); + await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }); + await this.helper.waitForDeadline(); + await this.helper.queue(); - expectEvent(await this.timelock.cancel(this.proposal.timelockid, { from: owner }), 'Cancelled', { - id: this.proposal.timelockid, - }); + expect(await this.mock.state(this.proposal.id)).to.be.bignumber.equal(Enums.ProposalState.Queued); - expect(await this.mock.state(this.proposal.id)).to.be.bignumber.equal(Enums.ProposalState.Canceled); - }); + expectEvent(await this.timelock.cancel(this.proposal.timelockid, { from: owner }), 'Cancelled', { + id: this.proposal.timelockid, }); - describe('onlyGovernance', function () { - describe('relay', function () { - beforeEach(async function () { - await this.token.$_mint(this.mock.address, 1); - }); - - it('is protected', async function () { - await expectRevertCustomError( - this.mock.relay(this.token.address, 0, this.token.contract.methods.transfer(other, 1).encodeABI(), { - from: owner, - }), - 'GovernorOnlyExecutor', - [owner], - ); - }); - - it('can be executed through governance', async function () { - this.helper.setProposal( - [ - { - target: this.mock.address, - data: this.mock.contract.methods - .relay(this.token.address, 0, this.token.contract.methods.transfer(other, 1).encodeABI()) - .encodeABI(), - }, - ], - '', - ); - - expect(await this.token.balanceOf(this.mock.address), 1); - expect(await this.token.balanceOf(other), 0); - - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }); - await this.helper.waitForDeadline(); - await this.helper.queue(); - await this.helper.waitForEta(); - const txExecute = await this.helper.execute(); - - expect(await this.token.balanceOf(this.mock.address), 0); - expect(await this.token.balanceOf(other), 1); - - await expectEvent.inTransaction(txExecute.tx, this.token, 'Transfer', { - from: this.mock.address, - to: other, - value: '1', - }); - }); - - it('is payable and can transfer eth to EOA', async function () { - const t2g = web3.utils.toBN(128); // timelock to governor - const g2o = web3.utils.toBN(100); // governor to eoa (other) - - this.helper.setProposal( - [ - { - target: this.mock.address, - value: t2g, - data: this.mock.contract.methods.relay(other, g2o, '0x').encodeABI(), - }, - ], - '', - ); - - expect(await web3.eth.getBalance(this.mock.address)).to.be.bignumber.equal(web3.utils.toBN(0)); - const timelockBalance = await web3.eth.getBalance(this.timelock.address).then(web3.utils.toBN); - const otherBalance = await web3.eth.getBalance(other).then(web3.utils.toBN); - - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }); - await this.helper.waitForDeadline(); - await this.helper.queue(); - await this.helper.waitForEta(); - await this.helper.execute(); - - expect(await web3.eth.getBalance(this.timelock.address)).to.be.bignumber.equal( - timelockBalance.sub(t2g), - ); - expect(await web3.eth.getBalance(this.mock.address)).to.be.bignumber.equal(t2g.sub(g2o)); - expect(await web3.eth.getBalance(other)).to.be.bignumber.equal(otherBalance.add(g2o)); - }); - - it('protected against other proposers', async function () { - const target = this.mock.address; - const value = web3.utils.toWei('0'); - const data = this.mock.contract.methods.relay(constants.ZERO_ADDRESS, 0, '0x').encodeABI(); - const predecessor = constants.ZERO_BYTES32; - const salt = constants.ZERO_BYTES32; - const delay = 3600; - - await this.timelock.schedule(target, value, data, predecessor, salt, delay, { from: owner }); - - await time.increase(3600); - - await expectRevertCustomError( - this.timelock.execute(target, value, data, predecessor, salt, { from: owner }), - 'QueueEmpty', // Bubbled up from Governor - [], - ); - }); - }); + expect(await this.mock.state(this.proposal.id)).to.be.bignumber.equal(Enums.ProposalState.Canceled); + }); + }); + + describe('onlyGovernance', function () { + describe('relay', function () { + beforeEach(async function () { + await this.token.$_mint(this.mock.address, 1); + }); + + it('is protected', async function () { + await expectRevertCustomError( + this.mock.relay(this.token.address, 0, this.token.contract.methods.transfer(other, 1).encodeABI(), { + from: owner, + }), + 'GovernorOnlyExecutor', + [owner], + ); + }); + + it('can be executed through governance', async function () { + this.helper.setProposal( + [ + { + target: this.mock.address, + data: this.mock.contract.methods + .relay(this.token.address, 0, this.token.contract.methods.transfer(other, 1).encodeABI()) + .encodeABI(), + }, + ], + '', + ); - describe('updateTimelock', function () { - beforeEach(async function () { - this.newTimelock = await Timelock.new( - 3600, - [this.mock.address], - [this.mock.address], - constants.ZERO_ADDRESS, - ); - }); - - it('is protected', async function () { - await expectRevertCustomError( - this.mock.updateTimelock(this.newTimelock.address, { from: owner }), - 'GovernorOnlyExecutor', - [owner], - ); - }); - - it('can be executed through governance to', async function () { - this.helper.setProposal( - [ - { - target: this.mock.address, - data: this.mock.contract.methods.updateTimelock(this.newTimelock.address).encodeABI(), - }, - ], - '', - ); - - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }); - await this.helper.waitForDeadline(); - await this.helper.queue(); - await this.helper.waitForEta(); - const txExecute = await this.helper.execute(); - - expectEvent(txExecute, 'TimelockChange', { - oldTimelock: this.timelock.address, - newTimelock: this.newTimelock.address, - }); - - expect(await this.mock.timelock()).to.be.bignumber.equal(this.newTimelock.address); - }); + expect(await this.token.balanceOf(this.mock.address), 1); + expect(await this.token.balanceOf(other), 0); + + await this.helper.propose(); + await this.helper.waitForSnapshot(); + await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }); + await this.helper.waitForDeadline(); + await this.helper.queue(); + await this.helper.waitForEta(); + const txExecute = await this.helper.execute(); + + expect(await this.token.balanceOf(this.mock.address), 0); + expect(await this.token.balanceOf(other), 1); + + await expectEvent.inTransaction(txExecute.tx, this.token, 'Transfer', { + from: this.mock.address, + to: other, + value: '1', }); }); - it('clear queue of pending governor calls', async function () { + it('is payable and can transfer eth to EOA', async function () { + const t2g = web3.utils.toBN(128); // timelock to governor + const g2o = web3.utils.toBN(100); // governor to eoa (other) + this.helper.setProposal( [ { target: this.mock.address, - data: this.mock.contract.methods.nonGovernanceFunction().encodeABI(), + value: t2g, + data: this.mock.contract.methods.relay(other, g2o, '0x').encodeABI(), }, ], '', ); + expect(await web3.eth.getBalance(this.mock.address)).to.be.bignumber.equal(web3.utils.toBN(0)); + const timelockBalance = await web3.eth.getBalance(this.timelock.address).then(web3.utils.toBN); + const otherBalance = await web3.eth.getBalance(other).then(web3.utils.toBN); + await this.helper.propose(); await this.helper.waitForSnapshot(); await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }); @@ -461,11 +342,100 @@ contract('GovernorTimelockControl', function (accounts) { await this.helper.waitForEta(); await this.helper.execute(); - // This path clears _governanceCall as part of the afterExecute call, - // but we have not way to check that the cleanup actually happened other - // then coverage reports. + expect(await web3.eth.getBalance(this.timelock.address)).to.be.bignumber.equal(timelockBalance.sub(t2g)); + expect(await web3.eth.getBalance(this.mock.address)).to.be.bignumber.equal(t2g.sub(g2o)); + expect(await web3.eth.getBalance(other)).to.be.bignumber.equal(otherBalance.add(g2o)); + }); + + it('protected against other proposers', async function () { + const target = this.mock.address; + const value = web3.utils.toWei('0'); + const data = this.mock.contract.methods.relay(constants.ZERO_ADDRESS, 0, '0x').encodeABI(); + const predecessor = constants.ZERO_BYTES32; + const salt = constants.ZERO_BYTES32; + const delay = 3600; + + await this.timelock.schedule(target, value, data, predecessor, salt, delay, { from: owner }); + + await time.increase(3600); + + await expectRevertCustomError( + this.timelock.execute(target, value, data, predecessor, salt, { from: owner }), + 'QueueEmpty', // Bubbled up from Governor + [], + ); }); }); + + describe('updateTimelock', function () { + beforeEach(async function () { + this.newTimelock = await Timelock.new( + 3600, + [this.mock.address], + [this.mock.address], + constants.ZERO_ADDRESS, + ); + }); + + it('is protected', async function () { + await expectRevertCustomError( + this.mock.updateTimelock(this.newTimelock.address, { from: owner }), + 'GovernorOnlyExecutor', + [owner], + ); + }); + + it('can be executed through governance to', async function () { + this.helper.setProposal( + [ + { + target: this.mock.address, + data: this.mock.contract.methods.updateTimelock(this.newTimelock.address).encodeABI(), + }, + ], + '', + ); + + await this.helper.propose(); + await this.helper.waitForSnapshot(); + await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }); + await this.helper.waitForDeadline(); + await this.helper.queue(); + await this.helper.waitForEta(); + const txExecute = await this.helper.execute(); + + expectEvent(txExecute, 'TimelockChange', { + oldTimelock: this.timelock.address, + newTimelock: this.newTimelock.address, + }); + + expect(await this.mock.timelock()).to.be.bignumber.equal(this.newTimelock.address); + }); + }); + }); + + it('clear queue of pending governor calls', async function () { + this.helper.setProposal( + [ + { + target: this.mock.address, + data: this.mock.contract.methods.nonGovernanceFunction().encodeABI(), + }, + ], + '', + ); + + await this.helper.propose(); + await this.helper.waitForSnapshot(); + await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }); + await this.helper.waitForDeadline(); + await this.helper.queue(); + await this.helper.waitForEta(); + await this.helper.execute(); + + // This path clears _governanceCall as part of the afterExecute call, + // but we have not way to check that the cleanup actually happened other + // then coverage reports. }); }); } From 5cc1ea0a390d18490d99922242d3633a6b43653a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Wed, 14 Jun 2023 13:01:33 -0600 Subject: [PATCH 099/182] Add `GUIDELINES.md` for marking `abstract` contracts (#4010) Co-authored-by: Hadrien Croubois Co-authored-by: Francisco Giordano --- GUIDELINES.md | 7 +++++++ contracts/token/ERC1155/ERC1155.sol | 2 +- contracts/token/ERC1155/utils/ERC1155Holder.sol | 2 +- contracts/token/ERC20/ERC20.sol | 2 +- contracts/token/ERC721/ERC721.sol | 2 +- contracts/token/ERC721/utils/ERC721Holder.sol | 2 +- test/token/ERC1155/utils/ERC1155Holder.test.js | 2 +- test/token/ERC721/utils/ERC721Holder.test.js | 2 +- 8 files changed, 14 insertions(+), 7 deletions(-) diff --git a/GUIDELINES.md b/GUIDELINES.md index 1dd606ddd..71f166405 100644 --- a/GUIDELINES.md +++ b/GUIDELINES.md @@ -114,6 +114,13 @@ In addition to the official Solidity Style Guide we have a number of other conve interface IERC777 { ``` +* Contracts not intended to be used standalone should be marked abstract + so they are required to be inherited to other contracts. + + ```solidity + abstract contract AccessControl is ..., { + ``` + * Unchecked arithmetic blocks should contain comments explaining why overflow is guaranteed not to happen. If the reason is immediately apparent from the line above the unchecked block, the comment may be omitted. * Custom errors should be declared following the [EIP-6093](https://eips.ethereum.org/EIPS/eip-6093) rationale whenever reasonable. Also, consider the following: diff --git a/contracts/token/ERC1155/ERC1155.sol b/contracts/token/ERC1155/ERC1155.sol index 05bcc3fa1..610868489 100644 --- a/contracts/token/ERC1155/ERC1155.sol +++ b/contracts/token/ERC1155/ERC1155.sol @@ -17,7 +17,7 @@ import "../../interfaces/draft-IERC6093.sol"; * * _Available since v3.1._ */ -contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI, IERC1155Errors { +abstract contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI, IERC1155Errors { // Mapping from token ID to account balances mapping(uint256 => mapping(address => uint256)) private _balances; diff --git a/contracts/token/ERC1155/utils/ERC1155Holder.sol b/contracts/token/ERC1155/utils/ERC1155Holder.sol index b06a37f18..d24a1a53a 100644 --- a/contracts/token/ERC1155/utils/ERC1155Holder.sol +++ b/contracts/token/ERC1155/utils/ERC1155Holder.sol @@ -13,7 +13,7 @@ import "./ERC1155Receiver.sol"; * * @dev _Available since v3.1._ */ -contract ERC1155Holder is ERC1155Receiver { +abstract contract ERC1155Holder is ERC1155Receiver { function onERC1155Received( address, address, diff --git a/contracts/token/ERC20/ERC20.sol b/contracts/token/ERC20/ERC20.sol index dd46c15dd..aacfe79b7 100644 --- a/contracts/token/ERC20/ERC20.sol +++ b/contracts/token/ERC20/ERC20.sol @@ -35,7 +35,7 @@ import "../../interfaces/draft-IERC6093.sol"; * functions have been added to mitigate the well-known issues around setting * allowances. See {IERC20-approve}. */ -contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors { +abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors { mapping(address => uint256) private _balances; mapping(address => mapping(address => uint256)) private _allowances; diff --git a/contracts/token/ERC721/ERC721.sol b/contracts/token/ERC721/ERC721.sol index 08f660a29..a26255c7c 100644 --- a/contracts/token/ERC721/ERC721.sol +++ b/contracts/token/ERC721/ERC721.sol @@ -16,7 +16,7 @@ import "../../interfaces/draft-IERC6093.sol"; * the Metadata extension, but not including the Enumerable extension, which is available separately as * {ERC721Enumerable}. */ -contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Errors { +abstract contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Errors { using Strings for uint256; // Token name diff --git a/contracts/token/ERC721/utils/ERC721Holder.sol b/contracts/token/ERC721/utils/ERC721Holder.sol index 9f5b2e9f9..a3ee8b5f0 100644 --- a/contracts/token/ERC721/utils/ERC721Holder.sol +++ b/contracts/token/ERC721/utils/ERC721Holder.sol @@ -11,7 +11,7 @@ import "../IERC721Receiver.sol"; * Accepts all token transfers. * Make sure the contract is able to use its token with {IERC721-safeTransferFrom}, {IERC721-approve} or {IERC721-setApprovalForAll}. */ -contract ERC721Holder is IERC721Receiver { +abstract contract ERC721Holder is IERC721Receiver { /** * @dev See {IERC721Receiver-onERC721Received}. * diff --git a/test/token/ERC1155/utils/ERC1155Holder.test.js b/test/token/ERC1155/utils/ERC1155Holder.test.js index 864e89b50..8d8541640 100644 --- a/test/token/ERC1155/utils/ERC1155Holder.test.js +++ b/test/token/ERC1155/utils/ERC1155Holder.test.js @@ -1,6 +1,6 @@ const { BN } = require('@openzeppelin/test-helpers'); -const ERC1155Holder = artifacts.require('ERC1155Holder'); +const ERC1155Holder = artifacts.require('$ERC1155Holder'); const ERC1155 = artifacts.require('$ERC1155'); const { expect } = require('chai'); diff --git a/test/token/ERC721/utils/ERC721Holder.test.js b/test/token/ERC721/utils/ERC721Holder.test.js index 0fd822280..4aa2b7948 100644 --- a/test/token/ERC721/utils/ERC721Holder.test.js +++ b/test/token/ERC721/utils/ERC721Holder.test.js @@ -1,6 +1,6 @@ const { expect } = require('chai'); -const ERC721Holder = artifacts.require('ERC721Holder'); +const ERC721Holder = artifacts.require('$ERC721Holder'); const ERC721 = artifacts.require('$ERC721'); contract('ERC721Holder', function (accounts) { From ef103f37e4eb18514f0d1d8511c38e9fffe2afce Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Wed, 14 Jun 2023 21:11:12 +0200 Subject: [PATCH 100/182] Replace some uses of abi.encodePacked with more explicit alternatives (#4296) Co-authored-by: Francisco --- .changeset/flat-bottles-wonder.md | 5 +++++ .../governance/compatibility/GovernorCompatibilityBravo.sol | 2 +- contracts/mocks/ReentrancyAttack.sol | 4 ++-- contracts/mocks/ReentrancyMock.sol | 3 +-- contracts/token/ERC1155/extensions/ERC1155URIStorage.sol | 4 ++-- contracts/token/ERC721/ERC721.sol | 2 +- contracts/token/ERC721/extensions/ERC721URIStorage.sol | 4 ++-- contracts/utils/Strings.sol | 2 +- 8 files changed, 15 insertions(+), 11 deletions(-) create mode 100644 .changeset/flat-bottles-wonder.md diff --git a/.changeset/flat-bottles-wonder.md b/.changeset/flat-bottles-wonder.md new file mode 100644 index 000000000..099ea8339 --- /dev/null +++ b/.changeset/flat-bottles-wonder.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': minor +--- + +Replace some uses of `abi.encodePacked` with clearer alternatives (e.g. `bytes.concat`, `string.concat`). diff --git a/contracts/governance/compatibility/GovernorCompatibilityBravo.sol b/contracts/governance/compatibility/GovernorCompatibilityBravo.sol index 670be9591..8d6d9ccb4 100644 --- a/contracts/governance/compatibility/GovernorCompatibilityBravo.sol +++ b/contracts/governance/compatibility/GovernorCompatibilityBravo.sol @@ -155,7 +155,7 @@ abstract contract GovernorCompatibilityBravo is IGovernorTimelock, IGovernorComp for (uint256 i = 0; i < fullcalldatas.length; ++i) { fullcalldatas[i] = bytes(signatures[i]).length == 0 ? calldatas[i] - : abi.encodePacked(bytes4(keccak256(bytes(signatures[i]))), calldatas[i]); + : bytes.concat(abi.encodeWithSignature(signatures[i]), calldatas[i]); } return fullcalldatas; diff --git a/contracts/mocks/ReentrancyAttack.sol b/contracts/mocks/ReentrancyAttack.sol index 2da8b1f1a..df2924301 100644 --- a/contracts/mocks/ReentrancyAttack.sol +++ b/contracts/mocks/ReentrancyAttack.sol @@ -5,8 +5,8 @@ pragma solidity ^0.8.19; import "../utils/Context.sol"; contract ReentrancyAttack is Context { - function callSender(bytes4 data) public { - (bool success, ) = _msgSender().call(abi.encodeWithSelector(data)); + function callSender(bytes calldata data) public { + (bool success, ) = _msgSender().call(data); require(success, "ReentrancyAttack: failed call"); } } diff --git a/contracts/mocks/ReentrancyMock.sol b/contracts/mocks/ReentrancyMock.sol index b4819dd59..053e53d77 100644 --- a/contracts/mocks/ReentrancyMock.sol +++ b/contracts/mocks/ReentrancyMock.sol @@ -33,8 +33,7 @@ contract ReentrancyMock is ReentrancyGuard { function countAndCall(ReentrancyAttack attacker) public nonReentrant { _count(); - bytes4 func = bytes4(keccak256("callback()")); - attacker.callSender(func); + attacker.callSender(abi.encodeCall(this.callback, ())); } function _count() private { diff --git a/contracts/token/ERC1155/extensions/ERC1155URIStorage.sol b/contracts/token/ERC1155/extensions/ERC1155URIStorage.sol index 5b0da2b33..79782a42b 100644 --- a/contracts/token/ERC1155/extensions/ERC1155URIStorage.sol +++ b/contracts/token/ERC1155/extensions/ERC1155URIStorage.sol @@ -42,8 +42,8 @@ abstract contract ERC1155URIStorage is ERC1155 { function uri(uint256 tokenId) public view virtual override returns (string memory) { string memory tokenURI = _tokenURIs[tokenId]; - // If token URI is set, concatenate base URI and tokenURI (via abi.encodePacked). - return bytes(tokenURI).length > 0 ? string(abi.encodePacked(_baseURI, tokenURI)) : super.uri(tokenId); + // If token URI is set, concatenate base URI and tokenURI (via string.concat). + return bytes(tokenURI).length > 0 ? string.concat(_baseURI, tokenURI) : super.uri(tokenId); } /** diff --git a/contracts/token/ERC721/ERC721.sol b/contracts/token/ERC721/ERC721.sol index a26255c7c..21ed95813 100644 --- a/contracts/token/ERC721/ERC721.sol +++ b/contracts/token/ERC721/ERC721.sol @@ -97,7 +97,7 @@ abstract contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Er _requireMinted(tokenId); string memory baseURI = _baseURI(); - return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenId.toString())) : ""; + return bytes(baseURI).length > 0 ? string.concat(baseURI, tokenId.toString()) : ""; } /** diff --git a/contracts/token/ERC721/extensions/ERC721URIStorage.sol b/contracts/token/ERC721/extensions/ERC721URIStorage.sol index ae058122d..ae625fc28 100644 --- a/contracts/token/ERC721/extensions/ERC721URIStorage.sol +++ b/contracts/token/ERC721/extensions/ERC721URIStorage.sol @@ -35,9 +35,9 @@ abstract contract ERC721URIStorage is IERC4906, ERC721 { if (bytes(base).length == 0) { return _tokenURI; } - // If both are set, concatenate the baseURI and tokenURI (via abi.encodePacked). + // If both are set, concatenate the baseURI and tokenURI (via string.concat). if (bytes(_tokenURI).length > 0) { - return string(abi.encodePacked(base, _tokenURI)); + return string.concat(base, _tokenURI); } return super.tokenURI(tokenId); diff --git a/contracts/utils/Strings.sol b/contracts/utils/Strings.sol index 65c8c8753..574717f86 100644 --- a/contracts/utils/Strings.sol +++ b/contracts/utils/Strings.sol @@ -47,7 +47,7 @@ library Strings { * @dev Converts a `int256` to its ASCII `string` decimal representation. */ function toString(int256 value) internal pure returns (string memory) { - return string(abi.encodePacked(value < 0 ? "-" : "", toString(SignedMath.abs(value)))); + return string.concat(value < 0 ? "-" : "", toString(SignedMath.abs(value))); } /** From ac5480e7caa8544da5ca629524bf11ea9af2751a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Wed, 14 Jun 2023 14:11:37 -0600 Subject: [PATCH 101/182] Merge release-v4.9 branch (#4352) --- CHANGELOG.md | 4 ++++ contracts/governance/Governor.sol | 2 +- contracts/package.json | 2 +- package.json | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d4e13046..abbb1b347 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -59,6 +59,10 @@ function supportsInterface(bytes4 interfaceId) public view virtual override retu } ``` +## 4.9.1 (2023-06-07) + +- `Governor`: Add a mechanism to restrict the address of the proposer using a suffix in the description. + ## 4.9.0 (2023-05-23) - `ReentrancyGuard`: Add a `_reentrancyGuardEntered` function to expose the guard status. ([#3714](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3714)) diff --git a/contracts/governance/Governor.sol b/contracts/governance/Governor.sol index b42fe1034..83bbfb083 100644 --- a/contracts/governance/Governor.sol +++ b/contracts/governance/Governor.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.9.0) (governance/Governor.sol) +// OpenZeppelin Contracts (last updated v4.9.1) (governance/Governor.sol) pragma solidity ^0.8.19; diff --git a/contracts/package.json b/contracts/package.json index 4d0f576bb..4711a6b3f 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -1,7 +1,7 @@ { "name": "@openzeppelin/contracts", "description": "Secure Smart Contract library for Solidity", - "version": "4.9.0", + "version": "4.9.1", "files": [ "**/*.sol", "/build/contracts/*.json", diff --git a/package.json b/package.json index c070915f9..c44154133 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "openzeppelin-solidity", "description": "Secure Smart Contract library for Solidity", - "version": "4.9.0", + "version": "4.9.1", "files": [ "/contracts/**/*.sol", "/build/contracts/*.json", From 247753426034263e07ef5dc7f0cf3483fb6c53b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Wed, 14 Jun 2023 14:21:42 -0600 Subject: [PATCH 102/182] Change behavior of ceilDiv(0, 0) and improve test coverage (#4348) --- .changeset/blue-scissors-design.md | 5 ++ contracts/mocks/token/ERC20Reentrant.sol | 2 +- contracts/mocks/token/ERC4626LimitsMock.sol | 23 ++++++ contracts/utils/math/Math.sol | 5 ++ test/token/ERC1155/ERC1155.behavior.js | 8 ++ test/token/ERC20/extensions/ERC4626.test.js | 74 +++++++++++++++++++ .../ERC721/extensions/ERC721Burnable.test.js | 11 ++- .../extensions/ERC721Consecutive.test.js | 19 +++++ test/utils/math/Math.test.js | 18 +++++ test/utils/structs/Checkpoints.test.js | 15 ++++ 10 files changed, 178 insertions(+), 2 deletions(-) create mode 100644 .changeset/blue-scissors-design.md create mode 100644 contracts/mocks/token/ERC4626LimitsMock.sol diff --git a/.changeset/blue-scissors-design.md b/.changeset/blue-scissors-design.md new file mode 100644 index 000000000..c2f815aae --- /dev/null +++ b/.changeset/blue-scissors-design.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': minor +--- + +`Math`: Make `ceilDiv` to revert on 0 division even if the numerator is 0 diff --git a/contracts/mocks/token/ERC20Reentrant.sol b/contracts/mocks/token/ERC20Reentrant.sol index ee803b9e1..00ba74260 100644 --- a/contracts/mocks/token/ERC20Reentrant.sol +++ b/contracts/mocks/token/ERC20Reentrant.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.19; import "../../token/ERC20/ERC20.sol"; -import "../../token/ERC20/extensions/ERC4626.sol"; +import "../../utils/Address.sol"; contract ERC20Reentrant is ERC20("TEST", "TST") { enum Type { diff --git a/contracts/mocks/token/ERC4626LimitsMock.sol b/contracts/mocks/token/ERC4626LimitsMock.sol new file mode 100644 index 000000000..a47826bb8 --- /dev/null +++ b/contracts/mocks/token/ERC4626LimitsMock.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.19; + +import "../../token/ERC20/extensions/ERC4626.sol"; + +abstract contract ERC4626LimitsMock is ERC4626 { + uint256 _maxDeposit; + uint256 _maxMint; + + constructor() { + _maxDeposit = 100 ether; + _maxMint = 100 ether; + } + + function maxDeposit(address) public view override returns (uint256) { + return _maxDeposit; + } + + function maxMint(address) public view override returns (uint256) { + return _maxMint; + } +} diff --git a/contracts/utils/math/Math.sol b/contracts/utils/math/Math.sol index a2fc7ceb7..d372295d7 100644 --- a/contracts/utils/math/Math.sol +++ b/contracts/utils/math/Math.sol @@ -114,6 +114,11 @@ library Math { * of rounding down. */ function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) { + if (b == 0) { + // Guarantee the same behavior as in a regular Solidity division. + return a / b; + } + // (a + b - 1) / b can overflow on addition, so we distribute. return a == 0 ? 0 : (a - 1) / b + 1; } diff --git a/test/token/ERC1155/ERC1155.behavior.js b/test/token/ERC1155/ERC1155.behavior.js index 5e87f8d52..1693c4aa6 100644 --- a/test/token/ERC1155/ERC1155.behavior.js +++ b/test/token/ERC1155/ERC1155.behavior.js @@ -479,6 +479,14 @@ function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, m ); }); + it('reverts when transferring from zero address', async function () { + await expectRevertCustomError( + this.token.$_safeBatchTransferFrom(ZERO_ADDRESS, multiTokenHolder, [firstTokenId], [firstAmount], '0x'), + 'ERC1155InvalidSender', + [ZERO_ADDRESS], + ); + }); + function batchTransferWasSuccessful({ operator, from, ids, values }) { it('debits transferred balances from sender', async function () { const newBalances = await this.token.balanceOfBatch(new Array(ids.length).fill(from), ids); diff --git a/test/token/ERC20/extensions/ERC4626.test.js b/test/token/ERC20/extensions/ERC4626.test.js index d67486a60..99d6009e4 100644 --- a/test/token/ERC20/extensions/ERC4626.test.js +++ b/test/token/ERC20/extensions/ERC4626.test.js @@ -6,6 +6,7 @@ const { expectRevertCustomError } = require('../../../helpers/customError'); const ERC20Decimals = artifacts.require('$ERC20DecimalsMock'); const ERC4626 = artifacts.require('$ERC4626'); +const ERC4626LimitsMock = artifacts.require('$ERC4626LimitsMock'); const ERC4626OffsetMock = artifacts.require('$ERC4626OffsetMock'); const ERC4626FeesMock = artifacts.require('$ERC4626FeesMock'); const ERC20ExcessDecimalsMock = artifacts.require('ERC20ExcessDecimalsMock'); @@ -220,6 +221,49 @@ contract('ERC4626', function (accounts) { }); }); + describe('limits', async function () { + beforeEach(async function () { + this.token = await ERC20Decimals.new(name, symbol, decimals); + this.vault = await ERC4626LimitsMock.new(name + ' Vault', symbol + 'V', this.token.address); + }); + + it('reverts on deposit() above max deposit', async function () { + const maxDeposit = await this.vault.maxDeposit(holder); + await expectRevertCustomError(this.vault.deposit(maxDeposit.addn(1), recipient), 'ERC4626ExceededMaxDeposit', [ + recipient, + maxDeposit.addn(1), + maxDeposit, + ]); + }); + + it('reverts on mint() above max mint', async function () { + const maxMint = await this.vault.maxMint(holder); + await expectRevertCustomError(this.vault.mint(maxMint.addn(1), recipient), 'ERC4626ExceededMaxMint', [ + recipient, + maxMint.addn(1), + maxMint, + ]); + }); + + it('reverts on withdraw() above max withdraw', async function () { + const maxWithdraw = await this.vault.maxWithdraw(holder); + await expectRevertCustomError( + this.vault.withdraw(maxWithdraw.addn(1), recipient, holder), + 'ERC4626ExceededMaxWithdraw', + [holder, maxWithdraw.addn(1), maxWithdraw], + ); + }); + + it('reverts on redeem() above max redeem', async function () { + const maxRedeem = await this.vault.maxRedeem(holder); + await expectRevertCustomError( + this.vault.redeem(maxRedeem.addn(1), recipient, holder), + 'ERC4626ExceededMaxRedeem', + [holder, maxRedeem.addn(1), maxRedeem], + ); + }); + }); + for (const offset of [0, 6, 18].map(web3.utils.toBN)) { const parseToken = token => web3.utils.toBN(10).pow(decimals).muln(token); const parseShare = share => web3.utils.toBN(10).pow(decimals.add(offset)).muln(share); @@ -849,6 +893,9 @@ contract('ERC4626', function (accounts) { expect(await this.vault.balanceOf(user2)).to.be.bignumber.equal('0'); expect(await this.vault.convertToAssets(await this.vault.balanceOf(user1))).to.be.bignumber.equal('2000'); expect(await this.vault.convertToAssets(await this.vault.balanceOf(user2))).to.be.bignumber.equal('0'); + expect(await this.vault.convertToShares(await this.token.balanceOf(this.vault.address))).to.be.bignumber.equal( + '2000', + ); expect(await this.vault.totalSupply()).to.be.bignumber.equal('2000'); expect(await this.vault.totalAssets()).to.be.bignumber.equal('2000'); } @@ -872,6 +919,9 @@ contract('ERC4626', function (accounts) { expect(await this.vault.balanceOf(user2)).to.be.bignumber.equal('4000'); expect(await this.vault.convertToAssets(await this.vault.balanceOf(user1))).to.be.bignumber.equal('2000'); expect(await this.vault.convertToAssets(await this.vault.balanceOf(user2))).to.be.bignumber.equal('4000'); + expect(await this.vault.convertToShares(await this.token.balanceOf(this.vault.address))).to.be.bignumber.equal( + '6000', + ); expect(await this.vault.totalSupply()).to.be.bignumber.equal('6000'); expect(await this.vault.totalAssets()).to.be.bignumber.equal('6000'); } @@ -883,6 +933,9 @@ contract('ERC4626', function (accounts) { expect(await this.vault.balanceOf(user2)).to.be.bignumber.equal('4000'); expect(await this.vault.convertToAssets(await this.vault.balanceOf(user1))).to.be.bignumber.equal('2999'); // used to be 3000, but virtual assets/shares captures part of the yield expect(await this.vault.convertToAssets(await this.vault.balanceOf(user2))).to.be.bignumber.equal('5999'); // used to be 6000, but virtual assets/shares captures part of the yield + expect(await this.vault.convertToShares(await this.token.balanceOf(this.vault.address))).to.be.bignumber.equal( + '6000', + ); expect(await this.vault.totalSupply()).to.be.bignumber.equal('6000'); expect(await this.vault.totalAssets()).to.be.bignumber.equal('9000'); @@ -904,6 +957,9 @@ contract('ERC4626', function (accounts) { expect(await this.vault.balanceOf(user2)).to.be.bignumber.equal('4000'); expect(await this.vault.convertToAssets(await this.vault.balanceOf(user1))).to.be.bignumber.equal('4999'); expect(await this.vault.convertToAssets(await this.vault.balanceOf(user2))).to.be.bignumber.equal('6000'); + expect(await this.vault.convertToShares(await this.token.balanceOf(this.vault.address))).to.be.bignumber.equal( + '7333', + ); expect(await this.vault.totalSupply()).to.be.bignumber.equal('7333'); expect(await this.vault.totalAssets()).to.be.bignumber.equal('11000'); } @@ -928,6 +984,9 @@ contract('ERC4626', function (accounts) { expect(await this.vault.balanceOf(user2)).to.be.bignumber.equal('6000'); expect(await this.vault.convertToAssets(await this.vault.balanceOf(user1))).to.be.bignumber.equal('4999'); // used to be 5000 expect(await this.vault.convertToAssets(await this.vault.balanceOf(user2))).to.be.bignumber.equal('9000'); + expect(await this.vault.convertToShares(await this.token.balanceOf(this.vault.address))).to.be.bignumber.equal( + '9333', + ); expect(await this.vault.totalSupply()).to.be.bignumber.equal('9333'); expect(await this.vault.totalAssets()).to.be.bignumber.equal('14000'); // used to be 14001 } @@ -940,6 +999,9 @@ contract('ERC4626', function (accounts) { expect(await this.vault.balanceOf(user2)).to.be.bignumber.equal('6000'); expect(await this.vault.convertToAssets(await this.vault.balanceOf(user1))).to.be.bignumber.equal('6070'); // used to be 6071 expect(await this.vault.convertToAssets(await this.vault.balanceOf(user2))).to.be.bignumber.equal('10928'); // used to be 10929 + expect(await this.vault.convertToShares(await this.token.balanceOf(this.vault.address))).to.be.bignumber.equal( + '9333', + ); expect(await this.vault.totalSupply()).to.be.bignumber.equal('9333'); expect(await this.vault.totalAssets()).to.be.bignumber.equal('17000'); // used to be 17001 @@ -961,6 +1023,9 @@ contract('ERC4626', function (accounts) { expect(await this.vault.balanceOf(user2)).to.be.bignumber.equal('6000'); expect(await this.vault.convertToAssets(await this.vault.balanceOf(user1))).to.be.bignumber.equal('3643'); expect(await this.vault.convertToAssets(await this.vault.balanceOf(user2))).to.be.bignumber.equal('10929'); + expect(await this.vault.convertToShares(await this.token.balanceOf(this.vault.address))).to.be.bignumber.equal( + '8000', + ); expect(await this.vault.totalSupply()).to.be.bignumber.equal('8000'); expect(await this.vault.totalAssets()).to.be.bignumber.equal('14573'); } @@ -983,6 +1048,9 @@ contract('ERC4626', function (accounts) { expect(await this.vault.balanceOf(user2)).to.be.bignumber.equal('4392'); expect(await this.vault.convertToAssets(await this.vault.balanceOf(user1))).to.be.bignumber.equal('3643'); expect(await this.vault.convertToAssets(await this.vault.balanceOf(user2))).to.be.bignumber.equal('8000'); + expect(await this.vault.convertToShares(await this.token.balanceOf(this.vault.address))).to.be.bignumber.equal( + '6392', + ); expect(await this.vault.totalSupply()).to.be.bignumber.equal('6392'); expect(await this.vault.totalAssets()).to.be.bignumber.equal('11644'); } @@ -1006,6 +1074,9 @@ contract('ERC4626', function (accounts) { expect(await this.vault.balanceOf(user2)).to.be.bignumber.equal('4392'); expect(await this.vault.convertToAssets(await this.vault.balanceOf(user1))).to.be.bignumber.equal('0'); expect(await this.vault.convertToAssets(await this.vault.balanceOf(user2))).to.be.bignumber.equal('8000'); // used to be 8001 + expect(await this.vault.convertToShares(await this.token.balanceOf(this.vault.address))).to.be.bignumber.equal( + '4392', + ); expect(await this.vault.totalSupply()).to.be.bignumber.equal('4392'); expect(await this.vault.totalAssets()).to.be.bignumber.equal('8001'); } @@ -1028,6 +1099,9 @@ contract('ERC4626', function (accounts) { expect(await this.vault.balanceOf(user2)).to.be.bignumber.equal('0'); expect(await this.vault.convertToAssets(await this.vault.balanceOf(user1))).to.be.bignumber.equal('0'); expect(await this.vault.convertToAssets(await this.vault.balanceOf(user2))).to.be.bignumber.equal('0'); + expect(await this.vault.convertToShares(await this.token.balanceOf(this.vault.address))).to.be.bignumber.equal( + '0', + ); expect(await this.vault.totalSupply()).to.be.bignumber.equal('0'); expect(await this.vault.totalAssets()).to.be.bignumber.equal('1'); // used to be 0 } diff --git a/test/token/ERC721/extensions/ERC721Burnable.test.js b/test/token/ERC721/extensions/ERC721Burnable.test.js index c6c076919..df059e090 100644 --- a/test/token/ERC721/extensions/ERC721Burnable.test.js +++ b/test/token/ERC721/extensions/ERC721Burnable.test.js @@ -6,7 +6,7 @@ const { expectRevertCustomError } = require('../../../helpers/customError'); const ERC721Burnable = artifacts.require('$ERC721Burnable'); contract('ERC721Burnable', function (accounts) { - const [owner, approved] = accounts; + const [owner, approved, another] = accounts; const firstTokenId = new BN(1); const secondTokenId = new BN(2); @@ -61,6 +61,15 @@ contract('ERC721Burnable', function (accounts) { }); }); + describe('when there is no previous approval burned', function () { + it('reverts', async function () { + await expectRevertCustomError(this.token.burn(tokenId, { from: another }), 'ERC721InsufficientApproval', [ + another, + tokenId, + ]); + }); + }); + describe('when the given token ID was not tracked by this contract', function () { it('reverts', async function () { await expectRevertCustomError(this.token.burn(unknownTokenId, { from: owner }), 'ERC721NonexistentToken', [ diff --git a/test/token/ERC721/extensions/ERC721Consecutive.test.js b/test/token/ERC721/extensions/ERC721Consecutive.test.js index 172da86a6..55c26dffe 100644 --- a/test/token/ERC721/extensions/ERC721Consecutive.test.js +++ b/test/token/ERC721/extensions/ERC721Consecutive.test.js @@ -85,6 +85,14 @@ contract('ERC721Consecutive', function (accounts) { expect(await this.token.getVotes(account)).to.be.bignumber.equal(web3.utils.toBN(balance)); } }); + + it('reverts on consecutive minting to the zero address', async function () { + await expectRevertCustomError( + ERC721ConsecutiveMock.new(name, symbol, offset, delegates, [ZERO_ADDRESS], [10]), + 'ERC721InvalidReceiver', + [ZERO_ADDRESS], + ); + }); }); describe('minting after construction', function () { @@ -172,6 +180,17 @@ contract('ERC721Consecutive', function (accounts) { expect(await this.token.$_exists(tokenId)).to.be.equal(true); expect(await this.token.ownerOf(tokenId), user2); }); + + it('reverts burning batches of size != 1', async function () { + const tokenId = batches[0].amount + offset; + const receiver = batches[0].receiver; + + await expectRevertCustomError( + this.token.$_afterTokenTransfer(receiver, ZERO_ADDRESS, tokenId, 2), + 'ERC721ForbiddenBatchBurn', + [], + ); + }); }); }); } diff --git a/test/utils/math/Math.test.js b/test/utils/math/Math.test.js index df459c5f8..afd822b17 100644 --- a/test/utils/math/Math.test.js +++ b/test/utils/math/Math.test.js @@ -2,6 +2,7 @@ const { BN, constants, expectRevert } = require('@openzeppelin/test-helpers'); const { expect } = require('chai'); const { MAX_UINT256 } = constants; const { Rounding } = require('../../helpers/enums.js'); +const { expectRevertCustomError } = require('../../helpers/customError.js'); const Math = artifacts.require('$Math'); @@ -204,6 +205,19 @@ contract('Math', function () { }); describe('ceilDiv', function () { + it('reverts on zero division', async function () { + const a = new BN('2'); + const b = new BN('0'); + // It's unspecified because it's a low level 0 division error + await expectRevert.unspecified(this.math.$ceilDiv(a, b)); + }); + + it('does not round up a zero result', async function () { + const a = new BN('0'); + const b = new BN('2'); + expect(await this.math.$ceilDiv(a, b)).to.be.bignumber.equal('0'); + }); + it('does not round up on exact division', async function () { const a = new BN('10'); const b = new BN('5'); @@ -233,6 +247,10 @@ contract('Math', function () { await expectRevert.unspecified(this.math.$mulDiv(1, 1, 0, Rounding.Down)); }); + it('reverts with result higher than 2 ^ 256', async function () { + await expectRevertCustomError(this.math.$mulDiv(5, MAX_UINT256, 2, Rounding.Down), 'MathOverflowedMulDiv', []); + }); + describe('does round down', async function () { it('small values', async function () { expect(await this.math.$mulDiv('3', '4', '5', Rounding.Down)).to.be.bignumber.equal('2'); diff --git a/test/utils/structs/Checkpoints.test.js b/test/utils/structs/Checkpoints.test.js index d82180127..936ac565a 100644 --- a/test/utils/structs/Checkpoints.test.js +++ b/test/utils/structs/Checkpoints.test.js @@ -4,6 +4,7 @@ const { expect } = require('chai'); const { VALUE_SIZES } = require('../../../scripts/generate/templates/Checkpoints.opts.js'); const { expectRevertCustomError } = require('../../helpers/customError.js'); +const { expectRevert } = require('@openzeppelin/test-helpers'); const $Checkpoints = artifacts.require('$Checkpoints'); @@ -22,6 +23,7 @@ contract('Checkpoints', function () { describe(`Trace${length}`, function () { beforeEach(async function () { this.methods = { + at: (...args) => this.mock.methods[`$at_${libraryName}_Trace${length}(uint256,uint32)`](0, ...args), latest: (...args) => this.mock.methods[`$latest_${libraryName}_Trace${length}(uint256)`](0, ...args), latestCheckpoint: (...args) => this.mock.methods[`$latestCheckpoint_${libraryName}_Trace${length}(uint256)`](0, ...args), @@ -35,6 +37,11 @@ contract('Checkpoints', function () { }); describe('without checkpoints', function () { + it('at zero reverts', async function () { + // Reverts with array out of bound access, which is unspecified + await expectRevert.unspecified(this.methods.at(0)); + }); + it('returns zero as latest value', async function () { expect(await this.methods.latest()).to.be.bignumber.equal('0'); @@ -65,6 +72,14 @@ contract('Checkpoints', function () { } }); + it('at keys', async function () { + for (const [index, { key, value }] of this.checkpoints.entries()) { + const at = await this.methods.at(index); + expect(at._value).to.be.bignumber.equal(value); + expect(at._key).to.be.bignumber.equal(key); + } + }); + it('length', async function () { expect(await this.methods.length()).to.be.bignumber.equal(this.checkpoints.length.toString()); }); From 05ef6924ac7e5db711c9bd588479c559bd383aba Mon Sep 17 00:00:00 2001 From: Claudia Barcelo Date: Thu, 15 Jun 2023 04:39:34 +0200 Subject: [PATCH 103/182] Optimize array access in ERC1155 (#4300) Co-authored-by: Hadrien Croubois Co-authored-by: Francisco Giordano --- .changeset/hot-coins-judge.md | 5 +++++ .changeset/tough-drinks-hammer.md | 5 +++++ contracts/token/ERC1155/ERC1155.sol | 14 +++++++++----- contracts/utils/Arrays.sol | 22 ++++++++++++++++++++++ 4 files changed, 41 insertions(+), 5 deletions(-) create mode 100644 .changeset/hot-coins-judge.md create mode 100644 .changeset/tough-drinks-hammer.md diff --git a/.changeset/hot-coins-judge.md b/.changeset/hot-coins-judge.md new file mode 100644 index 000000000..e544af467 --- /dev/null +++ b/.changeset/hot-coins-judge.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': minor +--- + +`Arrays`: Add `unsafeMemoryAccess` helpers to read from a memory array without checking the length. diff --git a/.changeset/tough-drinks-hammer.md b/.changeset/tough-drinks-hammer.md new file mode 100644 index 000000000..51b3836e4 --- /dev/null +++ b/.changeset/tough-drinks-hammer.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': patch +--- + +`ERC1155`: Optimize array accesses by skipping bounds checking when unnecessary. diff --git a/contracts/token/ERC1155/ERC1155.sol b/contracts/token/ERC1155/ERC1155.sol index 610868489..5369d7732 100644 --- a/contracts/token/ERC1155/ERC1155.sol +++ b/contracts/token/ERC1155/ERC1155.sol @@ -8,6 +8,7 @@ import "./IERC1155Receiver.sol"; import "./extensions/IERC1155MetadataURI.sol"; import "../../utils/Context.sol"; import "../../utils/introspection/ERC165.sol"; +import "../../utils/Arrays.sol"; import "../../interfaces/draft-IERC6093.sol"; /** @@ -18,6 +19,9 @@ import "../../interfaces/draft-IERC6093.sol"; * _Available since v3.1._ */ abstract contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI, IERC1155Errors { + using Arrays for uint256[]; + using Arrays for address[]; + // Mapping from token ID to account balances mapping(uint256 => mapping(address => uint256)) private _balances; @@ -87,7 +91,7 @@ abstract contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI, IER uint256[] memory batchBalances = new uint256[](accounts.length); for (uint256 i = 0; i < accounts.length; ++i) { - batchBalances[i] = balanceOf(accounts[i], ids[i]); + batchBalances[i] = balanceOf(accounts.unsafeMemoryAccess(i), ids.unsafeMemoryAccess(i)); } return batchBalances; @@ -157,8 +161,8 @@ abstract contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI, IER address operator = _msgSender(); for (uint256 i = 0; i < ids.length; ++i) { - uint256 id = ids[i]; - uint256 amount = amounts[i]; + uint256 id = ids.unsafeMemoryAccess(i); + uint256 amount = amounts.unsafeMemoryAccess(i); if (from != address(0)) { uint256 fromBalance = _balances[id][from]; @@ -176,8 +180,8 @@ abstract contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI, IER } if (ids.length == 1) { - uint256 id = ids[0]; - uint256 amount = amounts[0]; + uint256 id = ids.unsafeMemoryAccess(0); + uint256 amount = amounts.unsafeMemoryAccess(0); emit TransferSingle(operator, from, to, id, amount); if (to != address(0)) { _doSafeTransferAcceptanceCheck(operator, from, to, id, amount, data); diff --git a/contracts/utils/Arrays.sol b/contracts/utils/Arrays.sol index 383a0820a..f4ef45645 100644 --- a/contracts/utils/Arrays.sol +++ b/contracts/utils/Arrays.sol @@ -102,4 +102,26 @@ library Arrays { } return slot.getUint256Slot(); } + + /** + * @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check. + * + * WARNING: Only use if you are certain `pos` is lower than the array length. + */ + function unsafeMemoryAccess(uint256[] memory arr, uint256 pos) internal pure returns (uint256 res) { + assembly { + res := mload(add(add(arr, 0x20), mul(pos, 0x20))) + } + } + + /** + * @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check. + * + * WARNING: Only use if you are certain `pos` is lower than the array length. + */ + function unsafeMemoryAccess(address[] memory arr, uint256 pos) internal pure returns (address res) { + assembly { + res := mload(add(add(arr, 0x20), mul(pos, 0x20))) + } + } } From ff85c7b0eb90c85f080a05b65e2b335104ff3923 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Thu, 15 Jun 2023 22:01:04 +0200 Subject: [PATCH 104/182] Make ERC1967Upgrades a library instead of an abstract contract (#4325) --- .changeset/grumpy-worms-tease.md | 5 ++ contracts/mocks/proxy/UUPSLegacy.sol | 15 ++-- contracts/mocks/proxy/UUPSUpgradeableMock.sol | 4 +- contracts/proxy/ERC1967/ERC1967Proxy.sol | 8 +- .../{ERC1967Upgrade.sol => ERC1967Utils.sol} | 80 ++++++++++++------- contracts/proxy/README.adoc | 4 +- contracts/proxy/beacon/BeaconProxy.sol | 8 +- .../TransparentUpgradeableProxy.sol | 10 +-- contracts/proxy/utils/UUPSUpgradeable.sol | 12 +-- hardhat.config.js | 2 +- scripts/upgradeable/transpile.sh | 2 +- 11 files changed, 89 insertions(+), 61 deletions(-) create mode 100644 .changeset/grumpy-worms-tease.md rename contracts/proxy/ERC1967/{ERC1967Upgrade.sol => ERC1967Utils.sol} (65%) diff --git a/.changeset/grumpy-worms-tease.md b/.changeset/grumpy-worms-tease.md new file mode 100644 index 000000000..910b996c6 --- /dev/null +++ b/.changeset/grumpy-worms-tease.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': major +--- + +`ERC1967Utils`: Refactor the `ERC1967Upgrade` abstract contract as a library. diff --git a/contracts/mocks/proxy/UUPSLegacy.sol b/contracts/mocks/proxy/UUPSLegacy.sol index f8ea7214b..6812f39b7 100644 --- a/contracts/mocks/proxy/UUPSLegacy.sol +++ b/contracts/mocks/proxy/UUPSLegacy.sol @@ -7,18 +7,18 @@ import "./UUPSUpgradeableMock.sol"; // This contract implements the pre-4.5 UUPS upgrade function with a rollback test. // It's used to test that newer UUPS contracts are considered valid upgrades by older UUPS contracts. contract UUPSUpgradeableLegacyMock is UUPSUpgradeableMock { - // Inlined from ERC1967Upgrade + // Inlined from ERC1967Utils bytes32 private constant _ROLLBACK_SLOT = 0x4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd9143; - // ERC1967Upgrade._setImplementation is private so we reproduce it here. + // ERC1967Utils._setImplementation is private so we reproduce it here. // An extra underscore prevents a name clash error. function __setImplementation(address newImplementation) private { require(newImplementation.code.length > 0, "ERC1967: new implementation is not a contract"); - StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation; + StorageSlot.getAddressSlot(ERC1967Utils.IMPLEMENTATION_SLOT).value = newImplementation; } function _upgradeToAndCallSecureLegacyV1(address newImplementation, bytes memory data, bool forceCall) internal { - address oldImplementation = _getImplementation(); + address oldImplementation = ERC1967Utils.getImplementation(); // Initial upgrade and setup call __setImplementation(newImplementation); @@ -34,9 +34,12 @@ contract UUPSUpgradeableLegacyMock is UUPSUpgradeableMock { Address.functionDelegateCall(newImplementation, abi.encodeCall(this.upgradeTo, (oldImplementation))); rollbackTesting.value = false; // Check rollback was effective - require(oldImplementation == _getImplementation(), "ERC1967Upgrade: upgrade breaks further upgrades"); + require( + oldImplementation == ERC1967Utils.getImplementation(), + "ERC1967Utils: upgrade breaks further upgrades" + ); // Finally reset to the new implementation and log the upgrade - _upgradeTo(newImplementation); + ERC1967Utils.upgradeTo(newImplementation); } } diff --git a/contracts/mocks/proxy/UUPSUpgradeableMock.sol b/contracts/mocks/proxy/UUPSUpgradeableMock.sol index 60eed4c93..81af96490 100644 --- a/contracts/mocks/proxy/UUPSUpgradeableMock.sol +++ b/contracts/mocks/proxy/UUPSUpgradeableMock.sol @@ -23,10 +23,10 @@ contract UUPSUpgradeableMock is NonUpgradeableMock, UUPSUpgradeable { contract UUPSUpgradeableUnsafeMock is UUPSUpgradeableMock { function upgradeTo(address newImplementation) public override { - _upgradeToAndCall(newImplementation, bytes(""), false); + ERC1967Utils.upgradeToAndCall(newImplementation, bytes(""), false); } function upgradeToAndCall(address newImplementation, bytes memory data) public payable override { - _upgradeToAndCall(newImplementation, data, false); + ERC1967Utils.upgradeToAndCall(newImplementation, data, false); } } diff --git a/contracts/proxy/ERC1967/ERC1967Proxy.sol b/contracts/proxy/ERC1967/ERC1967Proxy.sol index ea5c204f8..5a752f13d 100644 --- a/contracts/proxy/ERC1967/ERC1967Proxy.sol +++ b/contracts/proxy/ERC1967/ERC1967Proxy.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.19; import "../Proxy.sol"; -import "./ERC1967Upgrade.sol"; +import "./ERC1967Utils.sol"; /** * @dev This contract implements an upgradeable proxy. It is upgradeable because calls are delegated to an @@ -12,7 +12,7 @@ import "./ERC1967Upgrade.sol"; * https://eips.ethereum.org/EIPS/eip-1967[EIP1967], so that it doesn't conflict with the storage layout of the * implementation behind the proxy. */ -contract ERC1967Proxy is Proxy, ERC1967Upgrade { +contract ERC1967Proxy is Proxy { /** * @dev Initializes the upgradeable proxy with an initial implementation specified by `_logic`. * @@ -20,7 +20,7 @@ contract ERC1967Proxy is Proxy, ERC1967Upgrade { * function call, and allows initializing the storage of the proxy like a Solidity constructor. */ constructor(address _logic, bytes memory _data) payable { - _upgradeToAndCall(_logic, _data, false); + ERC1967Utils.upgradeToAndCall(_logic, _data, false); } /** @@ -31,6 +31,6 @@ contract ERC1967Proxy is Proxy, ERC1967Upgrade { * `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc` */ function _implementation() internal view virtual override returns (address impl) { - return _getImplementation(); + return ERC1967Utils.getImplementation(); } } diff --git a/contracts/proxy/ERC1967/ERC1967Upgrade.sol b/contracts/proxy/ERC1967/ERC1967Utils.sol similarity index 65% rename from contracts/proxy/ERC1967/ERC1967Upgrade.sol rename to contracts/proxy/ERC1967/ERC1967Utils.sol index ca6b92580..55f5c6aab 100644 --- a/contracts/proxy/ERC1967/ERC1967Upgrade.sol +++ b/contracts/proxy/ERC1967/ERC1967Utils.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.9.0) (proxy/ERC1967/ERC1967Upgrade.sol) +// OpenZeppelin Contracts (last updated v4.9.0) (proxy/ERC1967/ERC1967Utils.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import "../beacon/IBeacon.sol"; import "../../interfaces/IERC1967.sol"; @@ -15,7 +15,24 @@ import "../../utils/StorageSlot.sol"; * * _Available since v4.1._ */ -abstract contract ERC1967Upgrade is IERC1967 { +library ERC1967Utils { + // We re-declare ERC-1967 events here because they can't be used directly from IERC1967. + // This will be fixed in Solidity 0.8.21. At that point we should remove these events. + /** + * @dev Emitted when the implementation is upgraded. + */ + event Upgraded(address indexed implementation); + + /** + * @dev Emitted when the admin account has changed. + */ + event AdminChanged(address previousAdmin, address newAdmin); + + /** + * @dev Emitted when the beacon is changed. + */ + event BeaconUpgraded(address indexed beacon); + // This is the keccak-256 hash of "eip1967.proxy.rollback" subtracted by 1 bytes32 private constant _ROLLBACK_SLOT = 0x4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd9143; @@ -24,7 +41,8 @@ abstract contract ERC1967Upgrade is IERC1967 { * This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1, and is * validated in the constructor. */ - bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; + // solhint-disable-next-line private-vars-leading-underscore + bytes32 internal constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; /** * @dev The `implementation` of the proxy is invalid. @@ -49,8 +67,8 @@ abstract contract ERC1967Upgrade is IERC1967 { /** * @dev Returns the current implementation address. */ - function _getImplementation() internal view returns (address) { - return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value; + function getImplementation() internal view returns (address) { + return StorageSlot.getAddressSlot(IMPLEMENTATION_SLOT).value; } /** @@ -60,15 +78,15 @@ abstract contract ERC1967Upgrade is IERC1967 { if (newImplementation.code.length == 0) { revert ERC1967InvalidImplementation(newImplementation); } - StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation; + StorageSlot.getAddressSlot(IMPLEMENTATION_SLOT).value = newImplementation; } /** * @dev Perform implementation upgrade * - * Emits an {Upgraded} event. + * Emits an {IERC1967-Upgraded} event. */ - function _upgradeTo(address newImplementation) internal { + function upgradeTo(address newImplementation) internal { _setImplementation(newImplementation); emit Upgraded(newImplementation); } @@ -76,10 +94,10 @@ abstract contract ERC1967Upgrade is IERC1967 { /** * @dev Perform implementation upgrade with additional setup call. * - * Emits an {Upgraded} event. + * Emits an {IERC1967-Upgraded} event. */ - function _upgradeToAndCall(address newImplementation, bytes memory data, bool forceCall) internal { - _upgradeTo(newImplementation); + function upgradeToAndCall(address newImplementation, bytes memory data, bool forceCall) internal { + upgradeTo(newImplementation); if (data.length > 0 || forceCall) { Address.functionDelegateCall(newImplementation, data); } @@ -88,9 +106,9 @@ abstract contract ERC1967Upgrade is IERC1967 { /** * @dev Perform implementation upgrade with security checks for UUPS proxies, and additional setup call. * - * Emits an {Upgraded} event. + * Emits an {IERC1967-Upgraded} event. */ - function _upgradeToAndCallUUPS(address newImplementation, bytes memory data, bool forceCall) internal { + function upgradeToAndCallUUPS(address newImplementation, bytes memory data, bool forceCall) internal { // Upgrades from old implementations will perform a rollback test. This test requires the new // implementation to upgrade back to the old, non-ERC1822 compliant, implementation. Removing // this special case will break upgrade paths from old UUPS implementation to new ones. @@ -98,14 +116,14 @@ abstract contract ERC1967Upgrade is IERC1967 { _setImplementation(newImplementation); } else { try IERC1822Proxiable(newImplementation).proxiableUUID() returns (bytes32 slot) { - if (slot != _IMPLEMENTATION_SLOT) { + if (slot != IMPLEMENTATION_SLOT) { revert ERC1967UnsupportedProxiableUUID(slot); } } catch { // The implementation is not UUPS revert ERC1967InvalidImplementation(newImplementation); } - _upgradeToAndCall(newImplementation, data, forceCall); + upgradeToAndCall(newImplementation, data, forceCall); } } @@ -114,7 +132,8 @@ abstract contract ERC1967Upgrade is IERC1967 { * This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1, and is * validated in the constructor. */ - bytes32 internal constant _ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; + // solhint-disable-next-line private-vars-leading-underscore + bytes32 internal constant ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; /** * @dev Returns the current admin. @@ -123,8 +142,8 @@ abstract contract ERC1967Upgrade is IERC1967 { * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call. * `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103` */ - function _getAdmin() internal view returns (address) { - return StorageSlot.getAddressSlot(_ADMIN_SLOT).value; + function getAdmin() internal view returns (address) { + return StorageSlot.getAddressSlot(ADMIN_SLOT).value; } /** @@ -134,30 +153,31 @@ abstract contract ERC1967Upgrade is IERC1967 { if (newAdmin == address(0)) { revert ERC1967InvalidAdmin(address(0)); } - StorageSlot.getAddressSlot(_ADMIN_SLOT).value = newAdmin; + StorageSlot.getAddressSlot(ADMIN_SLOT).value = newAdmin; } /** * @dev Changes the admin of the proxy. * - * Emits an {AdminChanged} event. + * Emits an {IERC1967-AdminChanged} event. */ - function _changeAdmin(address newAdmin) internal { - emit AdminChanged(_getAdmin(), newAdmin); + function changeAdmin(address newAdmin) internal { + emit AdminChanged(getAdmin(), newAdmin); _setAdmin(newAdmin); } /** * @dev The storage slot of the UpgradeableBeacon contract which defines the implementation for this proxy. - * This is bytes32(uint256(keccak256('eip1967.proxy.beacon')) - 1)) and is validated in the constructor. + * This is bytes32(uint256(keccak256('eip1967.proxy.beacon')) - 1) and is validated in the constructor. */ - bytes32 internal constant _BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50; + // solhint-disable-next-line private-vars-leading-underscore + bytes32 internal constant BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50; /** * @dev Returns the current beacon. */ - function _getBeacon() internal view returns (address) { - return StorageSlot.getAddressSlot(_BEACON_SLOT).value; + function getBeacon() internal view returns (address) { + return StorageSlot.getAddressSlot(BEACON_SLOT).value; } /** @@ -173,16 +193,16 @@ abstract contract ERC1967Upgrade is IERC1967 { revert ERC1967InvalidImplementation(beaconImplementation); } - StorageSlot.getAddressSlot(_BEACON_SLOT).value = newBeacon; + StorageSlot.getAddressSlot(BEACON_SLOT).value = newBeacon; } /** * @dev Perform beacon upgrade with additional setup call. Note: This upgrades the address of the beacon, it does * not upgrade the implementation contained in the beacon (see {UpgradeableBeacon-_setImplementation} for that). * - * Emits a {BeaconUpgraded} event. + * Emits an {IERC1967-BeaconUpgraded} event. */ - function _upgradeBeaconToAndCall(address newBeacon, bytes memory data, bool forceCall) internal { + function upgradeBeaconToAndCall(address newBeacon, bytes memory data, bool forceCall) internal { _setBeacon(newBeacon); emit BeaconUpgraded(newBeacon); if (data.length > 0 || forceCall) { diff --git a/contracts/proxy/README.adoc b/contracts/proxy/README.adoc index 89717a7bf..43b7f65b1 100644 --- a/contracts/proxy/README.adoc +++ b/contracts/proxy/README.adoc @@ -11,7 +11,7 @@ Most of the proxies below are built on an abstract base contract. In order to avoid clashes with the storage variables of the implementation contract behind a proxy, we use https://eips.ethereum.org/EIPS/eip-1967[EIP1967] storage slots. -- {ERC1967Upgrade}: Internal functions to get and set the storage slots defined in EIP1967. +- {ERC1967Utils}: Internal functions to get and set the storage slots defined in EIP1967. - {ERC1967Proxy}: A proxy using EIP1967 storage slots. Not upgradeable by default. There are two alternative ways to add upgradeability to an ERC1967 proxy. Their differences are explained below in <>. @@ -60,7 +60,7 @@ The current implementation of this security mechanism uses https://eips.ethereum {{ERC1967Proxy}} -{{ERC1967Upgrade}} +{{ERC1967Utils}} == Transparent Proxy diff --git a/contracts/proxy/beacon/BeaconProxy.sol b/contracts/proxy/beacon/BeaconProxy.sol index d603e38a7..dc6b5c90b 100644 --- a/contracts/proxy/beacon/BeaconProxy.sol +++ b/contracts/proxy/beacon/BeaconProxy.sol @@ -5,7 +5,7 @@ pragma solidity ^0.8.19; import "./IBeacon.sol"; import "../Proxy.sol"; -import "../ERC1967/ERC1967Upgrade.sol"; +import "../ERC1967/ERC1967Utils.sol"; /** * @dev This contract implements a proxy that gets the implementation address for each call from an {UpgradeableBeacon}. @@ -15,7 +15,7 @@ import "../ERC1967/ERC1967Upgrade.sol"; * * _Available since v3.4._ */ -contract BeaconProxy is Proxy, ERC1967Upgrade { +contract BeaconProxy is Proxy { /** * @dev Initializes the proxy with `beacon`. * @@ -28,13 +28,13 @@ contract BeaconProxy is Proxy, ERC1967Upgrade { * - `beacon` must be a contract with the interface {IBeacon}. */ constructor(address beacon, bytes memory data) payable { - _upgradeBeaconToAndCall(beacon, data, false); + ERC1967Utils.upgradeBeaconToAndCall(beacon, data, false); } /** * @dev Returns the current implementation address of the associated beacon. */ function _implementation() internal view virtual override returns (address) { - return IBeacon(_getBeacon()).implementation(); + return IBeacon(ERC1967Utils.getBeacon()).implementation(); } } diff --git a/contracts/proxy/transparent/TransparentUpgradeableProxy.sol b/contracts/proxy/transparent/TransparentUpgradeableProxy.sol index 4536e28c8..2b022ccad 100644 --- a/contracts/proxy/transparent/TransparentUpgradeableProxy.sol +++ b/contracts/proxy/transparent/TransparentUpgradeableProxy.sol @@ -67,14 +67,14 @@ contract TransparentUpgradeableProxy is ERC1967Proxy { * optionally initialized with `_data` as explained in {ERC1967Proxy-constructor}. */ constructor(address _logic, address admin_, bytes memory _data) payable ERC1967Proxy(_logic, _data) { - _changeAdmin(admin_); + ERC1967Utils.changeAdmin(admin_); } /** * @dev If caller is the admin process the call internally, otherwise transparently fallback to the proxy behavior */ function _fallback() internal virtual override { - if (msg.sender == _getAdmin()) { + if (msg.sender == ERC1967Utils.getAdmin()) { bytes memory ret; bytes4 selector = msg.sig; if (selector == ITransparentUpgradeableProxy.upgradeTo.selector) { @@ -103,7 +103,7 @@ contract TransparentUpgradeableProxy is ERC1967Proxy { _requireZeroValue(); address newAdmin = abi.decode(msg.data[4:], (address)); - _changeAdmin(newAdmin); + ERC1967Utils.changeAdmin(newAdmin); return ""; } @@ -115,7 +115,7 @@ contract TransparentUpgradeableProxy is ERC1967Proxy { _requireZeroValue(); address newImplementation = abi.decode(msg.data[4:], (address)); - _upgradeToAndCall(newImplementation, bytes(""), false); + ERC1967Utils.upgradeToAndCall(newImplementation, bytes(""), false); return ""; } @@ -127,7 +127,7 @@ contract TransparentUpgradeableProxy is ERC1967Proxy { */ function _dispatchUpgradeToAndCall() private returns (bytes memory) { (address newImplementation, bytes memory data) = abi.decode(msg.data[4:], (address, bytes)); - _upgradeToAndCall(newImplementation, data, true); + ERC1967Utils.upgradeToAndCall(newImplementation, data, true); return ""; } diff --git a/contracts/proxy/utils/UUPSUpgradeable.sol b/contracts/proxy/utils/UUPSUpgradeable.sol index 41a72ef9c..d213f2d61 100644 --- a/contracts/proxy/utils/UUPSUpgradeable.sol +++ b/contracts/proxy/utils/UUPSUpgradeable.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.19; import "../../interfaces/draft-IERC1822.sol"; -import "../ERC1967/ERC1967Upgrade.sol"; +import "../ERC1967/ERC1967Utils.sol"; /** * @dev An upgradeability mechanism designed for UUPS proxies. The functions included here can perform an upgrade of an @@ -18,7 +18,7 @@ import "../ERC1967/ERC1967Upgrade.sol"; * * _Available since v4.1._ */ -abstract contract UUPSUpgradeable is IERC1822Proxiable, ERC1967Upgrade { +abstract contract UUPSUpgradeable is IERC1822Proxiable { /// @custom:oz-upgrades-unsafe-allow state-variable-immutable state-variable-assignment address private immutable __self = address(this); @@ -39,7 +39,7 @@ abstract contract UUPSUpgradeable is IERC1822Proxiable, ERC1967Upgrade { // Must be called through delegatecall revert UUPSUnauthorizedCallContext(); } - if (_getImplementation() != __self) { + if (ERC1967Utils.getImplementation() != __self) { // Must be called through an active proxy revert UUPSUnauthorizedCallContext(); } @@ -67,7 +67,7 @@ abstract contract UUPSUpgradeable is IERC1822Proxiable, ERC1967Upgrade { * function revert if invoked through a proxy. This is guaranteed by the `notDelegated` modifier. */ function proxiableUUID() external view virtual notDelegated returns (bytes32) { - return _IMPLEMENTATION_SLOT; + return ERC1967Utils.IMPLEMENTATION_SLOT; } /** @@ -81,7 +81,7 @@ abstract contract UUPSUpgradeable is IERC1822Proxiable, ERC1967Upgrade { */ function upgradeTo(address newImplementation) public virtual onlyProxy { _authorizeUpgrade(newImplementation); - _upgradeToAndCallUUPS(newImplementation, new bytes(0), false); + ERC1967Utils.upgradeToAndCallUUPS(newImplementation, new bytes(0), false); } /** @@ -96,7 +96,7 @@ abstract contract UUPSUpgradeable is IERC1822Proxiable, ERC1967Upgrade { */ function upgradeToAndCall(address newImplementation, bytes memory data) public payable virtual onlyProxy { _authorizeUpgrade(newImplementation); - _upgradeToAndCallUUPS(newImplementation, data, true); + ERC1967Utils.upgradeToAndCallUUPS(newImplementation, data, true); } /** diff --git a/hardhat.config.js b/hardhat.config.js index 66367e96e..6cb8b9144 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -40,7 +40,7 @@ const argv = require('yargs/yargs')() compiler: { alias: 'compileVersion', type: 'string', - default: '0.8.19', + default: '0.8.20', }, coinmarketcap: { alias: 'coinmarketcapApiKey', diff --git a/scripts/upgradeable/transpile.sh b/scripts/upgradeable/transpile.sh index e3aa31cc0..fbffe844e 100644 --- a/scripts/upgradeable/transpile.sh +++ b/scripts/upgradeable/transpile.sh @@ -29,7 +29,7 @@ npx @openzeppelin/upgrade-safe-transpiler@latest -D \ -x 'contracts/proxy/**/*' \ -x '!contracts/proxy/Clones.sol' \ -x '!contracts/proxy/ERC1967/ERC1967Storage.sol' \ - -x '!contracts/proxy/ERC1967/ERC1967Upgrade.sol' \ + -x '!contracts/proxy/ERC1967/ERC1967Utils.sol' \ -x '!contracts/proxy/utils/UUPSUpgradeable.sol' \ -x '!contracts/proxy/beacon/IBeacon.sol' \ -p 'contracts/**/presets/**/*' From c014c8f1484b2fcbac5fae9c05b6d63771f0d716 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Thu, 15 Jun 2023 23:27:36 +0200 Subject: [PATCH 105/182] Use ERC721Holder & ERC1155Holder in the TimelockController (#4284) Co-authored-by: Francisco --- contracts/governance/TimelockController.sol | 39 ++++----------------- scripts/checks/inheritance-ordering.js | 2 +- 2 files changed, 8 insertions(+), 33 deletions(-) diff --git a/contracts/governance/TimelockController.sol b/contracts/governance/TimelockController.sol index 4772a252f..a25cd7b4a 100644 --- a/contracts/governance/TimelockController.sol +++ b/contracts/governance/TimelockController.sol @@ -4,8 +4,8 @@ pragma solidity ^0.8.19; import "../access/AccessControl.sol"; -import "../token/ERC721/IERC721Receiver.sol"; -import "../token/ERC1155/IERC1155Receiver.sol"; +import "../token/ERC721/utils/ERC721Holder.sol"; +import "../token/ERC1155/utils/ERC1155Holder.sol"; import "../utils/Address.sol"; /** @@ -23,7 +23,7 @@ import "../utils/Address.sol"; * * _Available since v3.3._ */ -contract TimelockController is AccessControl, IERC721Receiver, IERC1155Receiver { +contract TimelockController is AccessControl, ERC721Holder, ERC1155Holder { bytes32 public constant PROPOSER_ROLE = keccak256("PROPOSER_ROLE"); bytes32 public constant EXECUTOR_ROLE = keccak256("EXECUTOR_ROLE"); bytes32 public constant CANCELLER_ROLE = keccak256("CANCELLER_ROLE"); @@ -155,8 +155,10 @@ contract TimelockController is AccessControl, IERC721Receiver, IERC1155Receiver /** * @dev See {IERC165-supportsInterface}. */ - function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, AccessControl) returns (bool) { - return interfaceId == type(IERC1155Receiver).interfaceId || super.supportsInterface(interfaceId); + function supportsInterface( + bytes4 interfaceId + ) public view virtual override(AccessControl, ERC1155Receiver) returns (bool) { + return super.supportsInterface(interfaceId); } /** @@ -430,31 +432,4 @@ contract TimelockController is AccessControl, IERC721Receiver, IERC1155Receiver emit MinDelayChange(_minDelay, newDelay); _minDelay = newDelay; } - - /** - * @dev See {IERC721Receiver-onERC721Received}. - */ - function onERC721Received(address, address, uint256, bytes memory) public virtual returns (bytes4) { - return this.onERC721Received.selector; - } - - /** - * @dev See {IERC1155Receiver-onERC1155Received}. - */ - function onERC1155Received(address, address, uint256, uint256, bytes memory) public virtual returns (bytes4) { - return this.onERC1155Received.selector; - } - - /** - * @dev See {IERC1155Receiver-onERC1155BatchReceived}. - */ - function onERC1155BatchReceived( - address, - address, - uint256[] memory, - uint256[] memory, - bytes memory - ) public virtual returns (bytes4) { - return this.onERC1155BatchReceived.selector; - } } diff --git a/scripts/checks/inheritance-ordering.js b/scripts/checks/inheritance-ordering.js index 45c707e6f..72aa37ef7 100755 --- a/scripts/checks/inheritance-ordering.js +++ b/scripts/checks/inheritance-ordering.js @@ -13,7 +13,7 @@ for (const artifact of artifacts) { const linearized = []; for (const source in solcOutput.contracts) { - if (source.includes('/mocks/')) { + if (['contracts-exposed/', 'contracts/mocks/'].some(pattern => source.startsWith(pattern))) { continue; } From 67248738953d4fd8601744386cd92262024c576d Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Thu, 15 Jun 2023 23:54:35 +0200 Subject: [PATCH 106/182] Output contract name where storage inconsistency was found (#4357) --- scripts/checks/compare-layout.js | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/checks/compare-layout.js b/scripts/checks/compare-layout.js index 7b7df9928..4368b77fb 100644 --- a/scripts/checks/compare-layout.js +++ b/scripts/checks/compare-layout.js @@ -10,6 +10,7 @@ for (const name in oldLayout) { if (name in newLayout) { const report = getStorageUpgradeReport(oldLayout[name], newLayout[name], {}); if (!report.ok) { + console.log(`Storage layout incompatilibity found in ${name}:`); console.log(report.explain()); process.exitCode = 1; } From cd48b3eab380254b08d7893a5a7bf568a33c5259 Mon Sep 17 00:00:00 2001 From: Claudia Barcelo Date: Fri, 16 Jun 2023 02:43:17 +0200 Subject: [PATCH 107/182] Add validation in Governor on ERC-721 or ERC-1155 received (#4314) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Hadrien Croubois Co-authored-by: Francisco Co-authored-by: Ernesto García --- .changeset/mighty-donuts-smile.md | 6 + .changeset/thin-camels-matter.md | 5 + contracts/governance/Governor.sol | 14 +- contracts/mocks/token/ERC1155ReceiverMock.sol | 33 ++++- contracts/token/ERC1155/ERC1155.sol | 30 +++-- .../GovernorTimelockCompound.test.js | 66 ++++++++++ .../GovernorTimelockControl.test.js | 66 ++++++++++ test/token/ERC1155/ERC1155.behavior.js | 123 +++++++++++++++--- 8 files changed, 306 insertions(+), 37 deletions(-) create mode 100644 .changeset/mighty-donuts-smile.md create mode 100644 .changeset/thin-camels-matter.md diff --git a/.changeset/mighty-donuts-smile.md b/.changeset/mighty-donuts-smile.md new file mode 100644 index 000000000..5885a7370 --- /dev/null +++ b/.changeset/mighty-donuts-smile.md @@ -0,0 +1,6 @@ +--- +'openzeppelin-solidity': patch +--- + +`Governor`: Add validation in ERC1155 and ERC721 receiver hooks to ensure Governor is the executor. + diff --git a/.changeset/thin-camels-matter.md b/.changeset/thin-camels-matter.md new file mode 100644 index 000000000..c832b1163 --- /dev/null +++ b/.changeset/thin-camels-matter.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': minor +--- + +`ERC1155`: Bubble errors triggered in the `onERC1155Received` and `onERC1155BatchReceived` hooks. diff --git a/contracts/governance/Governor.sol b/contracts/governance/Governor.sol index 83bbfb083..a8654ac35 100644 --- a/contracts/governance/Governor.sol +++ b/contracts/governance/Governor.sol @@ -70,7 +70,7 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor, IERC721Receive * governance protocol (since v4.6). */ modifier onlyGovernance() { - if (_msgSender() != _executor()) { + if (_executor() != _msgSender()) { revert GovernorOnlyExecutor(_msgSender()); } if (_executor() != address(this)) { @@ -631,20 +631,29 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor, IERC721Receive /** * @dev See {IERC721Receiver-onERC721Received}. + * Receiving tokens is disabled if the governance executor is other than the governor itself (eg. when using with a timelock). */ function onERC721Received(address, address, uint256, bytes memory) public virtual returns (bytes4) { + if (_executor() != address(this)) { + revert GovernorDisabledDeposit(); + } return this.onERC721Received.selector; } /** * @dev See {IERC1155Receiver-onERC1155Received}. + * Receiving tokens is disabled if the governance executor is other than the governor itself (eg. when using with a timelock). */ function onERC1155Received(address, address, uint256, uint256, bytes memory) public virtual returns (bytes4) { + if (_executor() != address(this)) { + revert GovernorDisabledDeposit(); + } return this.onERC1155Received.selector; } /** * @dev See {IERC1155Receiver-onERC1155BatchReceived}. + * Receiving tokens is disabled if the governance executor is other than the governor itself (eg. when using with a timelock). */ function onERC1155BatchReceived( address, @@ -653,6 +662,9 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor, IERC721Receive uint256[] memory, bytes memory ) public virtual returns (bytes4) { + if (_executor() != address(this)) { + revert GovernorDisabledDeposit(); + } return this.onERC1155BatchReceived.selector; } diff --git a/contracts/mocks/token/ERC1155ReceiverMock.sol b/contracts/mocks/token/ERC1155ReceiverMock.sol index 2b591c058..c4cdbf20e 100644 --- a/contracts/mocks/token/ERC1155ReceiverMock.sol +++ b/contracts/mocks/token/ERC1155ReceiverMock.sol @@ -6,15 +6,24 @@ import "../../token/ERC1155/IERC1155Receiver.sol"; import "../../utils/introspection/ERC165.sol"; contract ERC1155ReceiverMock is ERC165, IERC1155Receiver { + enum RevertType { + None, + Empty, + String, + Custom + } + bytes4 private _recRetval; - bool private _recReverts; + RevertType private _recReverts; bytes4 private _batRetval; - bool private _batReverts; + RevertType private _batReverts; event Received(address operator, address from, uint256 id, uint256 value, bytes data, uint256 gas); event BatchReceived(address operator, address from, uint256[] ids, uint256[] values, bytes data, uint256 gas); - constructor(bytes4 recRetval, bool recReverts, bytes4 batRetval, bool batReverts) { + error ERC1155ReceiverMockError(); + + constructor(bytes4 recRetval, RevertType recReverts, bytes4 batRetval, RevertType batReverts) { _recRetval = recRetval; _recReverts = recReverts; _batRetval = batRetval; @@ -28,7 +37,14 @@ contract ERC1155ReceiverMock is ERC165, IERC1155Receiver { uint256 value, bytes calldata data ) external returns (bytes4) { - require(!_recReverts, "ERC1155ReceiverMock: reverting on receive"); + if (_recReverts == RevertType.Empty) { + revert(); + } else if (_recReverts == RevertType.String) { + revert("ERC1155ReceiverMock: reverting on receive"); + } else if (_recReverts == RevertType.Custom) { + revert ERC1155ReceiverMockError(); + } + emit Received(operator, from, id, value, data, gasleft()); return _recRetval; } @@ -40,7 +56,14 @@ contract ERC1155ReceiverMock is ERC165, IERC1155Receiver { uint256[] calldata values, bytes calldata data ) external returns (bytes4) { - require(!_batReverts, "ERC1155ReceiverMock: reverting on batch receive"); + if (_batReverts == RevertType.Empty) { + revert(); + } else if (_batReverts == RevertType.String) { + revert("ERC1155ReceiverMock: reverting on batch receive"); + } else if (_batReverts == RevertType.Custom) { + revert ERC1155ReceiverMockError(); + } + emit BatchReceived(operator, from, ids, values, data, gasleft()); return _batRetval; } diff --git a/contracts/token/ERC1155/ERC1155.sol b/contracts/token/ERC1155/ERC1155.sol index 5369d7732..9129992f4 100644 --- a/contracts/token/ERC1155/ERC1155.sol +++ b/contracts/token/ERC1155/ERC1155.sol @@ -364,11 +364,16 @@ abstract contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI, IER // Tokens rejected revert ERC1155InvalidReceiver(to); } - } catch Error(string memory reason) { - revert(reason); - } catch { - // non-ERC1155Receiver implementer - revert ERC1155InvalidReceiver(to); + } catch (bytes memory reason) { + if (reason.length == 0) { + // non-ERC1155Receiver implementer + revert ERC1155InvalidReceiver(to); + } else { + /// @solidity memory-safe-assembly + assembly { + revert(add(32, reason), mload(reason)) + } + } } } } @@ -389,11 +394,16 @@ abstract contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI, IER // Tokens rejected revert ERC1155InvalidReceiver(to); } - } catch Error(string memory reason) { - revert(reason); - } catch { - // non-ERC1155Receiver implementer - revert ERC1155InvalidReceiver(to); + } catch (bytes memory reason) { + if (reason.length == 0) { + // non-ERC1155Receiver implementer + revert ERC1155InvalidReceiver(to); + } else { + /// @solidity memory-safe-assembly + assembly { + revert(add(32, reason), mload(reason)) + } + } } } } diff --git a/test/governance/extensions/GovernorTimelockCompound.test.js b/test/governance/extensions/GovernorTimelockCompound.test.js index 8c6680aa8..c406baf8d 100644 --- a/test/governance/extensions/GovernorTimelockCompound.test.js +++ b/test/governance/extensions/GovernorTimelockCompound.test.js @@ -11,6 +11,8 @@ const { shouldSupportInterfaces } = require('../../utils/introspection/SupportsI const Timelock = artifacts.require('CompTimelock'); const Governor = artifacts.require('$GovernorTimelockCompoundMock'); const CallReceiver = artifacts.require('CallReceiverMock'); +const ERC721 = artifacts.require('$ERC721'); +const ERC1155 = artifacts.require('$ERC1155'); function makeContractAddress(creator, nonce) { return web3.utils.toChecksumAddress( @@ -212,6 +214,70 @@ contract('GovernorTimelockCompound', function (accounts) { ]); }); }); + + describe('on safe receive', function () { + describe('ERC721', function () { + const name = 'Non Fungible Token'; + const symbol = 'NFT'; + const tokenId = web3.utils.toBN(1); + + beforeEach(async function () { + this.token = await ERC721.new(name, symbol); + await this.token.$_mint(owner, tokenId); + }); + + it("can't receive an ERC721 safeTransfer", async function () { + await expectRevertCustomError( + this.token.safeTransferFrom(owner, this.mock.address, tokenId, { from: owner }), + 'GovernorDisabledDeposit', + [], + ); + }); + }); + + describe('ERC1155', function () { + const uri = 'https://token-cdn-domain/{id}.json'; + const tokenIds = { + 1: web3.utils.toBN(1000), + 2: web3.utils.toBN(2000), + 3: web3.utils.toBN(3000), + }; + + beforeEach(async function () { + this.token = await ERC1155.new(uri); + await this.token.$_mintBatch(owner, Object.keys(tokenIds), Object.values(tokenIds), '0x'); + }); + + it("can't receive ERC1155 safeTransfer", async function () { + await expectRevertCustomError( + this.token.safeTransferFrom( + owner, + this.mock.address, + ...Object.entries(tokenIds)[0], // id + amount + '0x', + { from: owner }, + ), + 'GovernorDisabledDeposit', + [], + ); + }); + + it("can't receive ERC1155 safeBatchTransfer", async function () { + await expectRevertCustomError( + this.token.safeBatchTransferFrom( + owner, + this.mock.address, + Object.keys(tokenIds), + Object.values(tokenIds), + '0x', + { from: owner }, + ), + 'GovernorDisabledDeposit', + [], + ); + }); + }); + }); }); describe('cancel', function () { diff --git a/test/governance/extensions/GovernorTimelockControl.test.js b/test/governance/extensions/GovernorTimelockControl.test.js index 10a7f05b2..3265dfa68 100644 --- a/test/governance/extensions/GovernorTimelockControl.test.js +++ b/test/governance/extensions/GovernorTimelockControl.test.js @@ -10,6 +10,8 @@ const { shouldSupportInterfaces } = require('../../utils/introspection/SupportsI const Timelock = artifacts.require('TimelockController'); const Governor = artifacts.require('$GovernorTimelockControlMock'); const CallReceiver = artifacts.require('CallReceiverMock'); +const ERC721 = artifacts.require('$ERC721'); +const ERC1155 = artifacts.require('$ERC1155'); const TOKENS = [ { Token: artifacts.require('$ERC20Votes'), mode: 'blocknumber' }, @@ -412,6 +414,70 @@ contract('GovernorTimelockControl', function (accounts) { expect(await this.mock.timelock()).to.be.bignumber.equal(this.newTimelock.address); }); }); + + describe('on safe receive', function () { + describe('ERC721', function () { + const name = 'Non Fungible Token'; + const symbol = 'NFT'; + const tokenId = web3.utils.toBN(1); + + beforeEach(async function () { + this.token = await ERC721.new(name, symbol); + await this.token.$_mint(owner, tokenId); + }); + + it("can't receive an ERC721 safeTransfer", async function () { + await expectRevertCustomError( + this.token.safeTransferFrom(owner, this.mock.address, tokenId, { from: owner }), + 'GovernorDisabledDeposit', + [], + ); + }); + }); + + describe('ERC1155', function () { + const uri = 'https://token-cdn-domain/{id}.json'; + const tokenIds = { + 1: web3.utils.toBN(1000), + 2: web3.utils.toBN(2000), + 3: web3.utils.toBN(3000), + }; + + beforeEach(async function () { + this.token = await ERC1155.new(uri); + await this.token.$_mintBatch(owner, Object.keys(tokenIds), Object.values(tokenIds), '0x'); + }); + + it("can't receive ERC1155 safeTransfer", async function () { + await expectRevertCustomError( + this.token.safeTransferFrom( + owner, + this.mock.address, + ...Object.entries(tokenIds)[0], // id + amount + '0x', + { from: owner }, + ), + 'GovernorDisabledDeposit', + [], + ); + }); + + it("can't receive ERC1155 safeBatchTransfer", async function () { + await expectRevertCustomError( + this.token.safeBatchTransferFrom( + owner, + this.mock.address, + Object.keys(tokenIds), + Object.values(tokenIds), + '0x', + { from: owner }, + ), + 'GovernorDisabledDeposit', + [], + ); + }); + }); + }); }); it('clear queue of pending governor calls', async function () { diff --git a/test/token/ERC1155/ERC1155.behavior.js b/test/token/ERC1155/ERC1155.behavior.js index 1693c4aa6..36f67d321 100644 --- a/test/token/ERC1155/ERC1155.behavior.js +++ b/test/token/ERC1155/ERC1155.behavior.js @@ -5,8 +5,10 @@ const { expect } = require('chai'); const { shouldSupportInterfaces } = require('../../utils/introspection/SupportsInterface.behavior'); const { expectRevertCustomError } = require('../../helpers/customError'); +const { Enum } = require('../../helpers/enums'); const ERC1155ReceiverMock = artifacts.require('ERC1155ReceiverMock'); +const RevertType = Enum('None', 'Empty', 'String', 'Custom'); function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, multiTokenHolder, recipient, proxy]) { const firstTokenId = new BN(1); @@ -296,9 +298,9 @@ function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, m beforeEach(async function () { this.receiver = await ERC1155ReceiverMock.new( RECEIVER_SINGLE_MAGIC_VALUE, - false, + RevertType.None, RECEIVER_BATCH_MAGIC_VALUE, - false, + RevertType.None, ); }); @@ -370,7 +372,12 @@ function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, m context('to a receiver contract returning unexpected value', function () { beforeEach(async function () { - this.receiver = await ERC1155ReceiverMock.new('0x00c0ffee', false, RECEIVER_BATCH_MAGIC_VALUE, false); + this.receiver = await ERC1155ReceiverMock.new( + '0x00c0ffee', + RevertType.None, + RECEIVER_BATCH_MAGIC_VALUE, + RevertType.None, + ); }); it('reverts', async function () { @@ -385,23 +392,55 @@ function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, m }); context('to a receiver contract that reverts', function () { - beforeEach(async function () { - this.receiver = await ERC1155ReceiverMock.new( + it('with empty reason', async function () { + const receiver = await ERC1155ReceiverMock.new( RECEIVER_SINGLE_MAGIC_VALUE, - true, + RevertType.Empty, RECEIVER_BATCH_MAGIC_VALUE, - false, + RevertType.None, + ); + + await expectRevertCustomError( + this.token.safeTransferFrom(multiTokenHolder, receiver.address, firstTokenId, firstAmount, '0x', { + from: multiTokenHolder, + }), + 'ERC1155InvalidReceiver', + [receiver.address], ); }); - it('reverts', async function () { + it('with reason string', async function () { + const receiver = await ERC1155ReceiverMock.new( + RECEIVER_SINGLE_MAGIC_VALUE, + RevertType.String, + RECEIVER_BATCH_MAGIC_VALUE, + RevertType.None, + ); + await expectRevert( - this.token.safeTransferFrom(multiTokenHolder, this.receiver.address, firstTokenId, firstAmount, '0x', { + this.token.safeTransferFrom(multiTokenHolder, receiver.address, firstTokenId, firstAmount, '0x', { from: multiTokenHolder, }), 'ERC1155ReceiverMock: reverting on receive', ); }); + + it('with custom error', async function () { + const receiver = await ERC1155ReceiverMock.new( + RECEIVER_SINGLE_MAGIC_VALUE, + RevertType.Custom, + RECEIVER_BATCH_MAGIC_VALUE, + RevertType.None, + ); + + await expectRevertCustomError( + this.token.safeTransferFrom(multiTokenHolder, receiver.address, firstTokenId, firstAmount, '0x', { + from: multiTokenHolder, + }), + 'ERC1155ReceiverMockError', + [], + ); + }); }); context('to a contract that does not implement the required function', function () { @@ -590,9 +629,9 @@ function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, m beforeEach(async function () { this.receiver = await ERC1155ReceiverMock.new( RECEIVER_SINGLE_MAGIC_VALUE, - false, + RevertType.None, RECEIVER_BATCH_MAGIC_VALUE, - false, + RevertType.None, ); }); @@ -666,9 +705,9 @@ function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, m beforeEach(async function () { this.receiver = await ERC1155ReceiverMock.new( RECEIVER_SINGLE_MAGIC_VALUE, - false, + RevertType.None, RECEIVER_SINGLE_MAGIC_VALUE, - false, + RevertType.None, ); }); @@ -689,20 +728,40 @@ function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, m }); context('to a receiver contract that reverts', function () { - beforeEach(async function () { - this.receiver = await ERC1155ReceiverMock.new( + it('with empty reason', async function () { + const receiver = await ERC1155ReceiverMock.new( RECEIVER_SINGLE_MAGIC_VALUE, - false, + RevertType.None, RECEIVER_BATCH_MAGIC_VALUE, - true, + RevertType.Empty, + ); + + await expectRevertCustomError( + this.token.safeBatchTransferFrom( + multiTokenHolder, + receiver.address, + [firstTokenId, secondTokenId], + [firstAmount, secondAmount], + '0x', + { from: multiTokenHolder }, + ), + 'ERC1155InvalidReceiver', + [receiver.address], ); }); - it('reverts', async function () { + it('with reason string', async function () { + const receiver = await ERC1155ReceiverMock.new( + RECEIVER_SINGLE_MAGIC_VALUE, + RevertType.None, + RECEIVER_BATCH_MAGIC_VALUE, + RevertType.String, + ); + await expectRevert( this.token.safeBatchTransferFrom( multiTokenHolder, - this.receiver.address, + receiver.address, [firstTokenId, secondTokenId], [firstAmount, secondAmount], '0x', @@ -711,15 +770,37 @@ function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, m 'ERC1155ReceiverMock: reverting on batch receive', ); }); + + it('with custom error', async function () { + const receiver = await ERC1155ReceiverMock.new( + RECEIVER_SINGLE_MAGIC_VALUE, + RevertType.None, + RECEIVER_BATCH_MAGIC_VALUE, + RevertType.Custom, + ); + + await expectRevertCustomError( + this.token.safeBatchTransferFrom( + multiTokenHolder, + receiver.address, + [firstTokenId, secondTokenId], + [firstAmount, secondAmount], + '0x', + { from: multiTokenHolder }, + ), + 'ERC1155ReceiverMockError', + [], + ); + }); }); context('to a receiver contract that reverts only on single transfers', function () { beforeEach(async function () { this.receiver = await ERC1155ReceiverMock.new( RECEIVER_SINGLE_MAGIC_VALUE, - true, + RevertType.String, RECEIVER_BATCH_MAGIC_VALUE, - false, + RevertType.None, ); this.toWhom = this.receiver.address; From 002a7c8812e73c282b91e14541ce9b93a6de1172 Mon Sep 17 00:00:00 2001 From: Francisco Date: Fri, 16 Jun 2023 15:42:19 -0300 Subject: [PATCH 108/182] Remove automatic conflict resolution for merge from release branch (#4362) --- .github/workflows/release-cycle.yml | 8 ++++-- .../release/workflow/prepare-release-merge.sh | 27 ------------------- 2 files changed, 6 insertions(+), 29 deletions(-) delete mode 100644 scripts/release/workflow/prepare-release-merge.sh diff --git a/.github/workflows/release-cycle.yml b/.github/workflows/release-cycle.yml index 2fd66458d..9d35022dc 100644 --- a/.github/workflows/release-cycle.yml +++ b/.github/workflows/release-cycle.yml @@ -192,6 +192,8 @@ jobs: pull-requests: write if: needs.state.outputs.merge == 'true' runs-on: ubuntu-latest + env: + MERGE_BRANCH: merge/${{ github.ref_name }} steps: - uses: actions/checkout@v3 with: @@ -200,7 +202,9 @@ jobs: uses: ./.github/actions/setup - run: bash scripts/git-user-config.sh - name: Create branch to merge - run: bash scripts/release/workflow/prepare-release-merge.sh + run: | + git checkout -B "$MERGE_BRANCH" "$GITHUB_REF_NAME" + git push -f origin "$MERGE_BRANCH" - name: Create PR back to master uses: actions/github-script@v6 with: @@ -208,7 +212,7 @@ jobs: await github.rest.pulls.create({ owner: context.repo.owner, repo: context.repo.repo, - head: 'merge/${{ github.ref_name }}', + head: process.env.MERGE_BRANCH, base: 'master', title: '${{ format('Merge {0} branch', github.ref_name) }}' }); diff --git a/scripts/release/workflow/prepare-release-merge.sh b/scripts/release/workflow/prepare-release-merge.sh deleted file mode 100644 index 4e6da5145..000000000 --- a/scripts/release/workflow/prepare-release-merge.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -# Define merge branch name -MERGE_BRANCH=merge/$GITHUB_REF_NAME - -# Create the branch and force to start from ref -git checkout -B "$MERGE_BRANCH" "$GITHUB_REF_NAME" - -# Get deleted changesets in this branch that might conflict with master -# --diff-filter=D - Only deleted files -readarray -t DELETED_CHANGESETS < <(git diff origin/master --diff-filter=D --name-only -- '.changeset/*.md') - -# Merge master, which will take those files cherry-picked. Auto-resolve conflicts favoring master. -# Ignore conflicts that can't be resolved. -git merge origin/master -m "Merge master to $GITHUB_REF_NAME" -X theirs || true - -# Remove the originally deleted changesets to correctly sync with master -rm -f "${DELETED_CHANGESETS[@]}" - -# Only git add deleted files -git ls-files --deleted .changeset/ | xargs git add - -# Allow empty here since there may be no changes if `rm -f` failed for all changesets -git commit --allow-empty -m "Sync changesets with master" -git push -f origin "$MERGE_BRANCH" From 1a77a508f93e2df058cb082def4753a060aefa8f Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Sat, 17 Jun 2023 04:25:58 +0200 Subject: [PATCH 109/182] Move upgradeToAndCallUUPS to UUPSUpgradeable (#4356) Co-authored-by: ernestognw --- .changeset/hip-beds-provide.md | 5 ++ contracts/mocks/proxy/UUPSLegacy.sol | 54 ------------- contracts/mocks/proxy/UUPSUpgradeableMock.sol | 6 ++ contracts/proxy/ERC1967/ERC1967Utils.sol | 34 -------- .../TransparentUpgradeableProxy.sol | 1 + contracts/proxy/utils/UUPSUpgradeable.sol | 36 +++++++-- test/proxy/utils/UUPSUpgradeable.test.js | 80 +++++++++++++------ 7 files changed, 97 insertions(+), 119 deletions(-) create mode 100644 .changeset/hip-beds-provide.md delete mode 100644 contracts/mocks/proxy/UUPSLegacy.sol diff --git a/.changeset/hip-beds-provide.md b/.changeset/hip-beds-provide.md new file mode 100644 index 000000000..c67283813 --- /dev/null +++ b/.changeset/hip-beds-provide.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': major +--- + +Move the logic to validate ERC-1822 during an upgrade from `ERC1967Utils` to `UUPSUpgradeable`. diff --git a/contracts/mocks/proxy/UUPSLegacy.sol b/contracts/mocks/proxy/UUPSLegacy.sol deleted file mode 100644 index 6812f39b7..000000000 --- a/contracts/mocks/proxy/UUPSLegacy.sol +++ /dev/null @@ -1,54 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.19; - -import "./UUPSUpgradeableMock.sol"; - -// This contract implements the pre-4.5 UUPS upgrade function with a rollback test. -// It's used to test that newer UUPS contracts are considered valid upgrades by older UUPS contracts. -contract UUPSUpgradeableLegacyMock is UUPSUpgradeableMock { - // Inlined from ERC1967Utils - bytes32 private constant _ROLLBACK_SLOT = 0x4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd9143; - - // ERC1967Utils._setImplementation is private so we reproduce it here. - // An extra underscore prevents a name clash error. - function __setImplementation(address newImplementation) private { - require(newImplementation.code.length > 0, "ERC1967: new implementation is not a contract"); - StorageSlot.getAddressSlot(ERC1967Utils.IMPLEMENTATION_SLOT).value = newImplementation; - } - - function _upgradeToAndCallSecureLegacyV1(address newImplementation, bytes memory data, bool forceCall) internal { - address oldImplementation = ERC1967Utils.getImplementation(); - - // Initial upgrade and setup call - __setImplementation(newImplementation); - if (data.length > 0 || forceCall) { - Address.functionDelegateCall(newImplementation, data); - } - - // Perform rollback test if not already in progress - StorageSlot.BooleanSlot storage rollbackTesting = StorageSlot.getBooleanSlot(_ROLLBACK_SLOT); - if (!rollbackTesting.value) { - // Trigger rollback using upgradeTo from the new implementation - rollbackTesting.value = true; - Address.functionDelegateCall(newImplementation, abi.encodeCall(this.upgradeTo, (oldImplementation))); - rollbackTesting.value = false; - // Check rollback was effective - require( - oldImplementation == ERC1967Utils.getImplementation(), - "ERC1967Utils: upgrade breaks further upgrades" - ); - // Finally reset to the new implementation and log the upgrade - ERC1967Utils.upgradeTo(newImplementation); - } - } - - // hooking into the old mechanism - function upgradeTo(address newImplementation) public override { - _upgradeToAndCallSecureLegacyV1(newImplementation, bytes(""), false); - } - - function upgradeToAndCall(address newImplementation, bytes memory data) public payable override { - _upgradeToAndCallSecureLegacyV1(newImplementation, data, false); - } -} diff --git a/contracts/mocks/proxy/UUPSUpgradeableMock.sol b/contracts/mocks/proxy/UUPSUpgradeableMock.sol index 81af96490..77744998b 100644 --- a/contracts/mocks/proxy/UUPSUpgradeableMock.sol +++ b/contracts/mocks/proxy/UUPSUpgradeableMock.sol @@ -30,3 +30,9 @@ contract UUPSUpgradeableUnsafeMock is UUPSUpgradeableMock { ERC1967Utils.upgradeToAndCall(newImplementation, data, false); } } + +contract UUPSUnsupportedProxiableUUID is UUPSUpgradeableMock { + function proxiableUUID() external pure override returns (bytes32) { + return keccak256("invalid UUID"); + } +} diff --git a/contracts/proxy/ERC1967/ERC1967Utils.sol b/contracts/proxy/ERC1967/ERC1967Utils.sol index 55f5c6aab..c244982bf 100644 --- a/contracts/proxy/ERC1967/ERC1967Utils.sol +++ b/contracts/proxy/ERC1967/ERC1967Utils.sol @@ -4,8 +4,6 @@ pragma solidity ^0.8.20; import "../beacon/IBeacon.sol"; -import "../../interfaces/IERC1967.sol"; -import "../../interfaces/draft-IERC1822.sol"; import "../../utils/Address.sol"; import "../../utils/StorageSlot.sol"; @@ -33,9 +31,6 @@ library ERC1967Utils { */ event BeaconUpgraded(address indexed beacon); - // This is the keccak-256 hash of "eip1967.proxy.rollback" subtracted by 1 - bytes32 private constant _ROLLBACK_SLOT = 0x4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd9143; - /** * @dev Storage slot with the address of the current implementation. * This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1, and is @@ -59,11 +54,6 @@ library ERC1967Utils { */ error ERC1967InvalidBeacon(address beacon); - /** - * @dev The storage `slot` is unsupported as a UUID. - */ - error ERC1967UnsupportedProxiableUUID(bytes32 slot); - /** * @dev Returns the current implementation address. */ @@ -103,30 +93,6 @@ library ERC1967Utils { } } - /** - * @dev Perform implementation upgrade with security checks for UUPS proxies, and additional setup call. - * - * Emits an {IERC1967-Upgraded} event. - */ - function upgradeToAndCallUUPS(address newImplementation, bytes memory data, bool forceCall) internal { - // Upgrades from old implementations will perform a rollback test. This test requires the new - // implementation to upgrade back to the old, non-ERC1822 compliant, implementation. Removing - // this special case will break upgrade paths from old UUPS implementation to new ones. - if (StorageSlot.getBooleanSlot(_ROLLBACK_SLOT).value) { - _setImplementation(newImplementation); - } else { - try IERC1822Proxiable(newImplementation).proxiableUUID() returns (bytes32 slot) { - if (slot != IMPLEMENTATION_SLOT) { - revert ERC1967UnsupportedProxiableUUID(slot); - } - } catch { - // The implementation is not UUPS - revert ERC1967InvalidImplementation(newImplementation); - } - upgradeToAndCall(newImplementation, data, forceCall); - } - } - /** * @dev Storage slot with the admin of the contract. * This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1, and is diff --git a/contracts/proxy/transparent/TransparentUpgradeableProxy.sol b/contracts/proxy/transparent/TransparentUpgradeableProxy.sol index 2b022ccad..4f7ae9666 100644 --- a/contracts/proxy/transparent/TransparentUpgradeableProxy.sol +++ b/contracts/proxy/transparent/TransparentUpgradeableProxy.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.19; import "../ERC1967/ERC1967Proxy.sol"; +import "../../interfaces/IERC1967.sol"; /** * @dev Interface for {TransparentUpgradeableProxy}. In order to implement transparency, {TransparentUpgradeableProxy} diff --git a/contracts/proxy/utils/UUPSUpgradeable.sol b/contracts/proxy/utils/UUPSUpgradeable.sol index d213f2d61..07de178bc 100644 --- a/contracts/proxy/utils/UUPSUpgradeable.sol +++ b/contracts/proxy/utils/UUPSUpgradeable.sol @@ -27,6 +27,11 @@ abstract contract UUPSUpgradeable is IERC1822Proxiable { */ error UUPSUnauthorizedCallContext(); + /** + * @dev The storage `slot` is unsupported as a UUID. + */ + error UUPSUnsupportedProxiableUUID(bytes32 slot); + /** * @dev Check that the execution is being performed through a delegatecall call and that the execution context is * a proxy contract with an implementation (as defined in ERC1967) pointing to self. This should only be the case @@ -35,12 +40,10 @@ abstract contract UUPSUpgradeable is IERC1822Proxiable { * fail. */ modifier onlyProxy() { - if (address(this) == __self) { - // Must be called through delegatecall - revert UUPSUnauthorizedCallContext(); - } - if (ERC1967Utils.getImplementation() != __self) { - // Must be called through an active proxy + if ( + address(this) == __self || // Must be called through delegatecall + ERC1967Utils.getImplementation() != __self // Must be called through an active proxy + ) { revert UUPSUnauthorizedCallContext(); } _; @@ -81,7 +84,7 @@ abstract contract UUPSUpgradeable is IERC1822Proxiable { */ function upgradeTo(address newImplementation) public virtual onlyProxy { _authorizeUpgrade(newImplementation); - ERC1967Utils.upgradeToAndCallUUPS(newImplementation, new bytes(0), false); + _upgradeToAndCallUUPS(newImplementation, new bytes(0), false); } /** @@ -96,7 +99,7 @@ abstract contract UUPSUpgradeable is IERC1822Proxiable { */ function upgradeToAndCall(address newImplementation, bytes memory data) public payable virtual onlyProxy { _authorizeUpgrade(newImplementation); - ERC1967Utils.upgradeToAndCallUUPS(newImplementation, data, true); + _upgradeToAndCallUUPS(newImplementation, data, true); } /** @@ -110,4 +113,21 @@ abstract contract UUPSUpgradeable is IERC1822Proxiable { * ``` */ function _authorizeUpgrade(address newImplementation) internal virtual; + + /** + * @dev Perform implementation upgrade with security checks for UUPS proxies, and additional setup call. + * + * Emits an {IERC1967-Upgraded} event. + */ + function _upgradeToAndCallUUPS(address newImplementation, bytes memory data, bool forceCall) private { + try IERC1822Proxiable(newImplementation).proxiableUUID() returns (bytes32 slot) { + if (slot != ERC1967Utils.IMPLEMENTATION_SLOT) { + revert UUPSUnsupportedProxiableUUID(slot); + } + ERC1967Utils.upgradeToAndCall(newImplementation, data, forceCall); + } catch { + // The implementation is not UUPS + revert ERC1967Utils.ERC1967InvalidImplementation(newImplementation); + } + } } diff --git a/test/proxy/utils/UUPSUpgradeable.test.js b/test/proxy/utils/UUPSUpgradeable.test.js index ea1b1d51f..95319c305 100644 --- a/test/proxy/utils/UUPSUpgradeable.test.js +++ b/test/proxy/utils/UUPSUpgradeable.test.js @@ -1,13 +1,13 @@ const { expectEvent } = require('@openzeppelin/test-helpers'); -const { web3 } = require('@openzeppelin/test-helpers/src/setup'); -const { getSlot, ImplementationSlot } = require('../../helpers/erc1967'); +const { getAddressInSlot, ImplementationSlot } = require('../../helpers/erc1967'); const { expectRevertCustomError } = require('../../helpers/customError'); const ERC1967Proxy = artifacts.require('ERC1967Proxy'); const UUPSUpgradeableMock = artifacts.require('UUPSUpgradeableMock'); const UUPSUpgradeableUnsafeMock = artifacts.require('UUPSUpgradeableUnsafeMock'); -const UUPSUpgradeableLegacyMock = artifacts.require('UUPSUpgradeableLegacyMock'); const NonUpgradeableMock = artifacts.require('NonUpgradeableMock'); +const UUPSUnsupportedProxiableUUID = artifacts.require('UUPSUnsupportedProxiableUUID'); +const Address = artifacts.require('$Address'); contract('UUPSUpgradeable', function () { before(async function () { @@ -15,6 +15,8 @@ contract('UUPSUpgradeable', function () { this.implUpgradeOk = await UUPSUpgradeableMock.new(); this.implUpgradeUnsafe = await UUPSUpgradeableUnsafeMock.new(); this.implUpgradeNonUUPS = await NonUpgradeableMock.new(); + this.implUnsupportedUUID = await UUPSUnsupportedProxiableUUID.new(); + this.helper = await Address.new(); }); beforeEach(async function () { @@ -26,6 +28,7 @@ contract('UUPSUpgradeable', function () { const { receipt } = await this.instance.upgradeTo(this.implUpgradeOk.address); expect(receipt.logs.filter(({ event }) => event === 'Upgraded').length).to.be.equal(1); expectEvent(receipt, 'Upgraded', { implementation: this.implUpgradeOk.address }); + expect(await getAddressInSlot(this.instance, ImplementationSlot)).to.be.equal(this.implUpgradeOk.address); }); it('upgrade to upgradeable implementation with call', async function () { @@ -37,13 +40,64 @@ contract('UUPSUpgradeable', function () { ); expect(receipt.logs.filter(({ event }) => event === 'Upgraded').length).to.be.equal(1); expectEvent(receipt, 'Upgraded', { implementation: this.implUpgradeOk.address }); + expect(await getAddressInSlot(this.instance, ImplementationSlot)).to.be.equal(this.implUpgradeOk.address); expect(await this.instance.current()).to.be.bignumber.equal('1'); }); + it('calling upgradeTo on the implementation reverts', async function () { + await expectRevertCustomError( + this.implInitial.upgradeTo(this.implUpgradeOk.address), + 'UUPSUnauthorizedCallContext', + [], + ); + }); + + it('calling upgradeToAndCall on the implementation reverts', async function () { + await expectRevertCustomError( + this.implInitial.upgradeToAndCall( + this.implUpgradeOk.address, + this.implUpgradeOk.contract.methods.increment().encodeABI(), + ), + 'UUPSUnauthorizedCallContext', + [], + ); + }); + + it('calling upgradeTo from a contract that is not an ERC1967 proxy (with the right implementation) reverts', async function () { + await expectRevertCustomError( + this.helper.$functionDelegateCall( + this.implUpgradeOk.address, + this.implUpgradeOk.contract.methods.upgradeTo(this.implUpgradeUnsafe.address).encodeABI(), + ), + 'UUPSUnauthorizedCallContext', + [], + ); + }); + + it('calling upgradeToAndCall from a contract that is not an ERC1967 proxy (with the right implementation) reverts', async function () { + await expectRevertCustomError( + this.helper.$functionDelegateCall( + this.implUpgradeOk.address, + this.implUpgradeOk.contract.methods.upgradeToAndCall(this.implUpgradeUnsafe.address, '0x').encodeABI(), + ), + 'UUPSUnauthorizedCallContext', + [], + ); + }); + + it('rejects upgrading to an unsupported UUID', async function () { + await expectRevertCustomError( + this.instance.upgradeTo(this.implUnsupportedUUID.address), + 'UUPSUnsupportedProxiableUUID', + [web3.utils.keccak256('invalid UUID')], + ); + }); + it('upgrade to and unsafe upgradeable implementation', async function () { const { receipt } = await this.instance.upgradeTo(this.implUpgradeUnsafe.address); expectEvent(receipt, 'Upgraded', { implementation: this.implUpgradeUnsafe.address }); + expect(await getAddressInSlot(this.instance, ImplementationSlot)).to.be.equal(this.implUpgradeUnsafe.address); }); // delegate to a non existing upgradeTo function causes a low level revert @@ -63,24 +117,4 @@ contract('UUPSUpgradeable', function () { otherInstance.address, ]); }); - - it('can upgrade from legacy implementations', async function () { - const legacyImpl = await UUPSUpgradeableLegacyMock.new(); - const legacyInstance = await ERC1967Proxy.new(legacyImpl.address, '0x').then(({ address }) => - UUPSUpgradeableLegacyMock.at(address), - ); - - const receipt = await legacyInstance.upgradeTo(this.implInitial.address); - - const UpgradedEvents = receipt.logs.filter( - ({ address, event }) => address === legacyInstance.address && event === 'Upgraded', - ); - expect(UpgradedEvents.length).to.be.equal(1); - - expectEvent(receipt, 'Upgraded', { implementation: this.implInitial.address }); - - const implementationSlot = await getSlot(legacyInstance, ImplementationSlot); - const implementationAddress = web3.utils.toChecksumAddress(implementationSlot.substr(-40)); - expect(implementationAddress).to.be.equal(this.implInitial.address); - }); }); From c95a44513011e019bdc9757f9f35ae2359ea76c0 Mon Sep 17 00:00:00 2001 From: Francisco Date: Mon, 19 Jun 2023 15:55:35 -0300 Subject: [PATCH 110/182] Detect MerkleProof multiproof invariant violation (#4367) Co-authored-by: Hadrien Croubois --- .changeset/shy-crews-teach.md | 5 ++++ contracts/utils/cryptography/MerkleProof.sol | 12 ++++++++-- test/utils/cryptography/MerkleProof.test.js | 25 ++++++++++++++++++++ 3 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 .changeset/shy-crews-teach.md diff --git a/.changeset/shy-crews-teach.md b/.changeset/shy-crews-teach.md new file mode 100644 index 000000000..8ab929bf8 --- /dev/null +++ b/.changeset/shy-crews-teach.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': patch +--- + +`MerkleProof`: Fix a bug in `processMultiProof` and `processMultiProofCalldata` that allows proving arbitrary leaves if the tree contains a node with value 0 at depth 1. diff --git a/contracts/utils/cryptography/MerkleProof.sol b/contracts/utils/cryptography/MerkleProof.sol index ed88ea1db..39826d8c6 100644 --- a/contracts/utils/cryptography/MerkleProof.sol +++ b/contracts/utils/cryptography/MerkleProof.sol @@ -126,10 +126,11 @@ library MerkleProof { // `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of // the merkle tree. uint256 leavesLen = leaves.length; + uint256 proofLen = proof.length; uint256 totalHashes = proofFlags.length; // Check proof validity. - if (leavesLen + proof.length - 1 != totalHashes) { + if (leavesLen + proofLen - 1 != totalHashes) { revert MerkleProofInvalidMultiproof(); } @@ -153,6 +154,9 @@ library MerkleProof { } if (totalHashes > 0) { + if (proofPos != proofLen) { + revert MerkleProofInvalidMultiproof(); + } unchecked { return hashes[totalHashes - 1]; } @@ -180,10 +184,11 @@ library MerkleProof { // `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of // the merkle tree. uint256 leavesLen = leaves.length; + uint256 proofLen = proof.length; uint256 totalHashes = proofFlags.length; // Check proof validity. - if (leavesLen + proof.length - 1 != totalHashes) { + if (leavesLen + proofLen - 1 != totalHashes) { revert MerkleProofInvalidMultiproof(); } @@ -207,6 +212,9 @@ library MerkleProof { } if (totalHashes > 0) { + if (proofPos != proofLen) { + revert MerkleProofInvalidMultiproof(); + } unchecked { return hashes[totalHashes - 1]; } diff --git a/test/utils/cryptography/MerkleProof.test.js b/test/utils/cryptography/MerkleProof.test.js index 43ef76bfa..5b87bc525 100644 --- a/test/utils/cryptography/MerkleProof.test.js +++ b/test/utils/cryptography/MerkleProof.test.js @@ -178,5 +178,30 @@ contract('MerkleProof', function () { expect(await this.merkleProof.$multiProofVerify([root], [], root, [])).to.equal(true); expect(await this.merkleProof.$multiProofVerifyCalldata([root], [], root, [])).to.equal(true); }); + + it('reverts processing manipulated proofs with a zero-value node at depth 1', async function () { + // Create a merkle tree that contains a zero leaf at depth 1 + const leaves = [keccak256('real leaf'), Buffer.alloc(32, 0)]; + const merkleTree = new MerkleTree(leaves, keccak256, { sortPairs: true }); + + const root = merkleTree.getRoot(); + + // Now we can pass any **malicious** fake leaves as valid! + const maliciousLeaves = ['malicious', 'leaves'].map(keccak256).sort(Buffer.compare); + const maliciousProof = [leaves[0], leaves[0]]; + const maliciousProofFlags = [true, true, false]; + + await expectRevertCustomError( + this.merkleProof.$multiProofVerify(maliciousProof, maliciousProofFlags, root, maliciousLeaves), + 'MerkleProofInvalidMultiproof', + [], + ); + + await expectRevertCustomError( + this.merkleProof.$multiProofVerifyCalldata(maliciousProof, maliciousProofFlags, root, maliciousLeaves), + 'MerkleProofInvalidMultiproof', + [], + ); + }); }); }); From fc19a7947c316d5047148762298dc650b5c0ed42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Mon, 19 Jun 2023 16:33:21 -0600 Subject: [PATCH 111/182] Change release cycle `prBackExists` definition (#4365) --- scripts/release/workflow/state.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/release/workflow/state.js b/scripts/release/workflow/state.js index 5cfaafb86..914e8de02 100644 --- a/scripts/release/workflow/state.js +++ b/scripts/release/workflow/state.js @@ -47,7 +47,7 @@ function shouldRunMerge({ hasPendingChangesets, prBackExists, }) { - return isReleaseBranch && isPush && !prerelease && isCurrentFinalVersion && !hasPendingChangesets && prBackExists; + return isReleaseBranch && isPush && !prerelease && isCurrentFinalVersion && !hasPendingChangesets && !prBackExists; } async function getState({ github, context, core }) { @@ -79,7 +79,7 @@ async function getState({ github, context, core }) { state: 'open', }); - state.prBackExists = prs.length === 0; + state.prBackExists = prs.length !== 0; state.isPublishedOnNpm = await isPublishedOnNpm(packageName, version); From 1f4e33fb725ad49e28a82447cad1d1ce752a4b0c Mon Sep 17 00:00:00 2001 From: Balaji Shetty Pachai <32358081+balajipachai@users.noreply.github.com> Date: Tue, 20 Jun 2023 05:26:00 +0530 Subject: [PATCH 112/182] Add toStringSigned to Strings.sol (#4330) Co-authored-by: Francisco Co-authored-by: Hadrien Croubois --- .changeset/tasty-tomatoes-turn.md | 5 +++++ contracts/utils/Strings.sol | 2 +- test/utils/Strings.test.js | 8 ++++---- 3 files changed, 10 insertions(+), 5 deletions(-) create mode 100644 .changeset/tasty-tomatoes-turn.md diff --git a/.changeset/tasty-tomatoes-turn.md b/.changeset/tasty-tomatoes-turn.md new file mode 100644 index 000000000..3fe46a9b1 --- /dev/null +++ b/.changeset/tasty-tomatoes-turn.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': major +--- + +`Strings`: Rename `toString(int256)` to `toStringSigned(int256)`. diff --git a/contracts/utils/Strings.sol b/contracts/utils/Strings.sol index 574717f86..afbf463d1 100644 --- a/contracts/utils/Strings.sol +++ b/contracts/utils/Strings.sol @@ -46,7 +46,7 @@ library Strings { /** * @dev Converts a `int256` to its ASCII `string` decimal representation. */ - function toString(int256 value) internal pure returns (string memory) { + function toStringSigned(int256 value) internal pure returns (string memory) { return string.concat(value < 0 ? "-" : "", toString(SignedMath.abs(value))); } diff --git a/test/utils/Strings.test.js b/test/utils/Strings.test.js index 09b958a61..2435fc71c 100644 --- a/test/utils/Strings.test.js +++ b/test/utils/Strings.test.js @@ -48,22 +48,22 @@ contract('Strings', function () { describe('int256', function () { it('converts MAX_INT256', async function () { const value = constants.MAX_INT256; - expect(await this.strings.methods['$toString(int256)'](value)).to.equal(value.toString(10)); + expect(await this.strings.methods['$toStringSigned(int256)'](value)).to.equal(value.toString(10)); }); it('converts MIN_INT256', async function () { const value = constants.MIN_INT256; - expect(await this.strings.methods['$toString(int256)'](value)).to.equal(value.toString(10)); + expect(await this.strings.methods['$toStringSigned(int256)'](value)).to.equal(value.toString(10)); }); for (const value of values) { it(`convert ${value}`, async function () { - expect(await this.strings.methods['$toString(int256)'](value)).to.equal(value); + expect(await this.strings.methods['$toStringSigned(int256)'](value)).to.equal(value); }); it(`convert negative ${value}`, async function () { const negated = new BN(value).neg(); - expect(await this.strings.methods['$toString(int256)'](negated)).to.equal(negated.toString(10)); + expect(await this.strings.methods['$toStringSigned(int256)'](negated)).to.equal(negated.toString(10)); }); } }); From 1d0dbcf9ab4dc1588c0c41c1b836d20854350c95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Mon, 19 Jun 2023 20:57:30 -0600 Subject: [PATCH 113/182] Make `TransparentUpgradeableProxy` admin immutable (#4354) Co-authored-by: Hadrien Croubois Co-authored-by: Francisco --- .changeset/happy-falcons-walk.md | 5 + contracts/mocks/DummyImplementation.sol | 7 ++ .../mocks/proxy/ClashingImplementation.sol | 2 +- contracts/proxy/README.adoc | 2 +- contracts/proxy/transparent/ProxyAdmin.sol | 11 --- .../TransparentUpgradeableProxy.sol | 48 +++++---- test/proxy/transparent/ProxyAdmin.test.js | 21 +--- .../TransparentUpgradeableProxy.behaviour.js | 99 +++++++++---------- 8 files changed, 84 insertions(+), 111 deletions(-) create mode 100644 .changeset/happy-falcons-walk.md diff --git a/.changeset/happy-falcons-walk.md b/.changeset/happy-falcons-walk.md new file mode 100644 index 000000000..bba9642aa --- /dev/null +++ b/.changeset/happy-falcons-walk.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': major +--- + +`TransparentUpgradeableProxy`: Admin is now stored in an immutable variable (set during construction) to avoid unnecessary storage reads on every proxy call. This removed the ability to ever change the admin. Transfer of the upgrade capability is exclusively handled through the ownership of the `ProxyAdmin`. diff --git a/contracts/mocks/DummyImplementation.sol b/contracts/mocks/DummyImplementation.sol index 85503c36e..71761a755 100644 --- a/contracts/mocks/DummyImplementation.sol +++ b/contracts/mocks/DummyImplementation.sol @@ -2,6 +2,8 @@ pragma solidity ^0.8.19; +import "../proxy/ERC1967/ERC1967Utils.sol"; + abstract contract Impl { function version() public pure virtual returns (string memory); } @@ -44,6 +46,11 @@ contract DummyImplementation { function reverts() public pure { require(false, "DummyImplementation reverted"); } + + // Use for forcing an unsafe TransparentUpgradeableProxy admin override + function unsafeOverrideAdmin(address newAdmin) public { + StorageSlot.getAddressSlot(ERC1967Utils.ADMIN_SLOT).value = newAdmin; + } } contract DummyImplementationV2 is DummyImplementation { diff --git a/contracts/mocks/proxy/ClashingImplementation.sol b/contracts/mocks/proxy/ClashingImplementation.sol index 957bc34be..89904b91f 100644 --- a/contracts/mocks/proxy/ClashingImplementation.sol +++ b/contracts/mocks/proxy/ClashingImplementation.sol @@ -9,7 +9,7 @@ pragma solidity ^0.8.19; contract ClashingImplementation { event ClashingImplementationCall(); - function changeAdmin(address) external payable { + function upgradeTo(address) external payable { emit ClashingImplementationCall(); } diff --git a/contracts/proxy/README.adoc b/contracts/proxy/README.adoc index 43b7f65b1..3c4a78d19 100644 --- a/contracts/proxy/README.adoc +++ b/contracts/proxy/README.adoc @@ -16,7 +16,7 @@ In order to avoid clashes with the storage variables of the implementation contr There are two alternative ways to add upgradeability to an ERC1967 proxy. Their differences are explained below in <>. -- {TransparentUpgradeableProxy}: A proxy with a built in admin and upgrade interface. +- {TransparentUpgradeableProxy}: A proxy with a built-in immutable admin and upgrade interface. - {UUPSUpgradeable}: An upgradeability mechanism to be included in the implementation contract. CAUTION: Using upgradeable proxies correctly and securely is a difficult task that requires deep knowledge of the proxy pattern, Solidity, and the EVM. Unless you want a lot of low level control, we recommend using the xref:upgrades-plugins::index.adoc[OpenZeppelin Upgrades Plugins] for Truffle and Hardhat. diff --git a/contracts/proxy/transparent/ProxyAdmin.sol b/contracts/proxy/transparent/ProxyAdmin.sol index 490f552f8..e8578a585 100644 --- a/contracts/proxy/transparent/ProxyAdmin.sol +++ b/contracts/proxy/transparent/ProxyAdmin.sol @@ -16,17 +16,6 @@ contract ProxyAdmin is Ownable { */ constructor(address initialOwner) Ownable(initialOwner) {} - /** - * @dev Changes the admin of `proxy` to `newAdmin`. - * - * Requirements: - * - * - This contract must be the current admin of `proxy`. - */ - function changeProxyAdmin(ITransparentUpgradeableProxy proxy, address newAdmin) public virtual onlyOwner { - proxy.changeAdmin(newAdmin); - } - /** * @dev Upgrades `proxy` to `implementation`. See {TransparentUpgradeableProxy-upgradeTo}. * diff --git a/contracts/proxy/transparent/TransparentUpgradeableProxy.sol b/contracts/proxy/transparent/TransparentUpgradeableProxy.sol index 4f7ae9666..71ce665d0 100644 --- a/contracts/proxy/transparent/TransparentUpgradeableProxy.sol +++ b/contracts/proxy/transparent/TransparentUpgradeableProxy.sol @@ -13,15 +13,13 @@ import "../../interfaces/IERC1967.sol"; * include them in the ABI so this interface must be used to interact with it. */ interface ITransparentUpgradeableProxy is IERC1967 { - function changeAdmin(address) external; - function upgradeTo(address) external; function upgradeToAndCall(address, bytes memory) external payable; } /** - * @dev This contract implements a proxy that is upgradeable by an admin. + * @dev This contract implements a proxy that is upgradeable by an immutable admin. * * To avoid https://medium.com/nomic-labs-blog/malicious-backdoors-in-ethereum-proxies-62629adf3357[proxy selector * clashing], which can potentially be used in an attack, this contract uses the @@ -31,15 +29,16 @@ interface ITransparentUpgradeableProxy is IERC1967 { * 1. If any account other than the admin calls the proxy, the call will be forwarded to the implementation, even if * that call matches one of the admin functions exposed by the proxy itself. * 2. If the admin calls the proxy, it can access the admin functions, but its calls will never be forwarded to the - * implementation. If the admin tries to call a function on the implementation it will fail with an error that says - * "admin cannot fallback to proxy target". + * implementation. If the admin tries to call a function on the implementation it will fail with an error indicating the + * proxy admin cannot fallback to the target implementation. * - * These properties mean that the admin account can only be used for admin actions like upgrading the proxy or changing - * the admin, so it's best if it's a dedicated account that is not used for anything else. This will avoid headaches due - * to sudden errors when trying to call a function from the proxy implementation. + * These properties mean that the admin account can only be used for upgrading the proxy, so it's best if it's a dedicated + * account that is not used for anything else. This will avoid headaches due to sudden errors when trying to call a function + * from the proxy implementation. * * Our recommendation is for the dedicated account to be an instance of the {ProxyAdmin} contract. If set up this way, - * you should think of the `ProxyAdmin` instance as the real administrative interface of your proxy. + * you should think of the `ProxyAdmin` instance as the real administrative interface of your proxy, which extends from the + * {Ownable} contract to allow for changing the proxy's admin owner. * * NOTE: The real interface of this proxy is that defined in `ITransparentUpgradeableProxy`. This contract does not * inherit from that interface, and instead the admin functions are implicitly implemented using a custom dispatch @@ -47,12 +46,23 @@ interface ITransparentUpgradeableProxy is IERC1967 { * fully implement transparency without decoding reverts caused by selector clashes between the proxy and the * implementation. * + * IMPORTANT: This contract avoids unnecessary storage reads by setting the admin only during construction as an immutable variable, + * preventing any changes thereafter. However, the admin slot defined in ERC-1967 can still be overwritten by the implementation + * logic pointed to by this proxy. In such cases, the contract may end up in an undesirable state where the admin slot is different + * from the actual admin. + * * WARNING: It is not recommended to extend this contract to add additional external functions. If you do so, the compiler * will not check that there are no selector conflicts, due to the note above. A selector clash between any new function * and the functions declared in {ITransparentUpgradeableProxy} will be resolved in favor of the new one. This could * render the admin operations inaccessible, which could prevent upgradeability. Transparency may also be compromised. */ contract TransparentUpgradeableProxy is ERC1967Proxy { + // An immutable address for the admin avoid unnecessary SLOADs before each call + // at the expense of removing the ability to change the admin once it's set. + // This is acceptable if the admin is always a ProxyAdmin instance or similar contract + // with its own ability to transfer the permissions to another account. + address private immutable _admin; + /** * @dev The proxy caller is the current admin, and can't fallback to the proxy target. */ @@ -68,6 +78,8 @@ contract TransparentUpgradeableProxy is ERC1967Proxy { * optionally initialized with `_data` as explained in {ERC1967Proxy-constructor}. */ constructor(address _logic, address admin_, bytes memory _data) payable ERC1967Proxy(_logic, _data) { + _admin = admin_; + // Set the storage value and emit an event for ERC-1967 compatibility ERC1967Utils.changeAdmin(admin_); } @@ -75,15 +87,13 @@ contract TransparentUpgradeableProxy is ERC1967Proxy { * @dev If caller is the admin process the call internally, otherwise transparently fallback to the proxy behavior */ function _fallback() internal virtual override { - if (msg.sender == ERC1967Utils.getAdmin()) { + if (msg.sender == _admin) { bytes memory ret; bytes4 selector = msg.sig; if (selector == ITransparentUpgradeableProxy.upgradeTo.selector) { ret = _dispatchUpgradeTo(); } else if (selector == ITransparentUpgradeableProxy.upgradeToAndCall.selector) { ret = _dispatchUpgradeToAndCall(); - } else if (selector == ITransparentUpgradeableProxy.changeAdmin.selector) { - ret = _dispatchChangeAdmin(); } else { revert ProxyDeniedAdminAccess(); } @@ -95,20 +105,6 @@ contract TransparentUpgradeableProxy is ERC1967Proxy { } } - /** - * @dev Changes the admin of the proxy. - * - * Emits an {AdminChanged} event. - */ - function _dispatchChangeAdmin() private returns (bytes memory) { - _requireZeroValue(); - - address newAdmin = abi.decode(msg.data[4:], (address)); - ERC1967Utils.changeAdmin(newAdmin); - - return ""; - } - /** * @dev Upgrade the implementation of the proxy. */ diff --git a/test/proxy/transparent/ProxyAdmin.test.js b/test/proxy/transparent/ProxyAdmin.test.js index d660ffc56..23f7ce9b2 100644 --- a/test/proxy/transparent/ProxyAdmin.test.js +++ b/test/proxy/transparent/ProxyAdmin.test.js @@ -6,11 +6,11 @@ const ProxyAdmin = artifacts.require('ProxyAdmin'); const TransparentUpgradeableProxy = artifacts.require('TransparentUpgradeableProxy'); const ITransparentUpgradeableProxy = artifacts.require('ITransparentUpgradeableProxy'); -const { getAddressInSlot, ImplementationSlot, AdminSlot } = require('../../helpers/erc1967'); +const { getAddressInSlot, ImplementationSlot } = require('../../helpers/erc1967'); const { expectRevertCustomError } = require('../../helpers/customError'); contract('ProxyAdmin', function (accounts) { - const [proxyAdminOwner, newAdmin, anotherAccount] = accounts; + const [proxyAdminOwner, anotherAccount] = accounts; before('set implementations', async function () { this.implementationV1 = await ImplV1.new(); @@ -32,23 +32,6 @@ contract('ProxyAdmin', function (accounts) { expect(await this.proxyAdmin.owner()).to.equal(proxyAdminOwner); }); - describe('#changeProxyAdmin', function () { - it('fails to change proxy admin if its not the proxy owner', async function () { - await expectRevertCustomError( - this.proxyAdmin.changeProxyAdmin(this.proxy.address, newAdmin, { from: anotherAccount }), - 'OwnableUnauthorizedAccount', - [anotherAccount], - ); - }); - - it('changes proxy admin', async function () { - await this.proxyAdmin.changeProxyAdmin(this.proxy.address, newAdmin, { from: proxyAdminOwner }); - - const newProxyAdmin = await getAddressInSlot(this.proxy, AdminSlot); - expect(newProxyAdmin).to.be.equal(newAdmin); - }); - }); - describe('#upgrade', function () { context('with unauthorized account', function () { it('fails to upgrade', async function () { diff --git a/test/proxy/transparent/TransparentUpgradeableProxy.behaviour.js b/test/proxy/transparent/TransparentUpgradeableProxy.behaviour.js index c6e949156..1a03b84db 100644 --- a/test/proxy/transparent/TransparentUpgradeableProxy.behaviour.js +++ b/test/proxy/transparent/TransparentUpgradeableProxy.behaviour.js @@ -47,6 +47,32 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy(createProx }); }); + describe('proxy admin', function () { + it('emits AdminChanged event during construction', async function () { + expectEvent.inConstruction(this.proxy, 'AdminChanged', { + previousAdmin: ZERO_ADDRESS, + newAdmin: proxyAdminAddress, + }); + }); + + it('sets the admin in the storage', async function () { + expect(await getAddressInSlot(this.proxy, AdminSlot)).to.be.equal(proxyAdminAddress); + }); + + it('can overwrite the admin by the implementation', async function () { + const dummy = new DummyImplementation(this.proxyAddress); + await dummy.unsafeOverrideAdmin(anotherAccount); + const ERC1967AdminSlotValue = await getAddressInSlot(this.proxy, AdminSlot); + expect(ERC1967AdminSlotValue).to.be.equal(anotherAccount); + + // Still allows previous admin to execute admin operations + expect(ERC1967AdminSlotValue).to.not.equal(proxyAdminAddress); + expectEvent(await this.proxy.upgradeTo(this.implementationV1, { from: proxyAdminAddress }), 'Upgraded', { + implementation: this.implementationV1, + }); + }); + }); + describe('upgradeTo', function () { describe('when the sender is the admin', function () { const from = proxyAdminAddress; @@ -258,51 +284,14 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy(createProx }); }); - describe('changeAdmin', function () { - describe('when the new proposed admin is not the zero address', function () { - const newAdmin = anotherAccount; - - describe('when the sender is the admin', function () { - beforeEach('transferring', async function () { - this.receipt = await this.proxy.changeAdmin(newAdmin, { from: proxyAdminAddress }); - }); - - it('assigns new proxy admin', async function () { - const newProxyAdmin = await getAddressInSlot(this.proxy, AdminSlot); - expect(newProxyAdmin).to.be.equal(anotherAccount); - }); - - it('emits an event', function () { - expectEvent(this.receipt, 'AdminChanged', { - previousAdmin: proxyAdminAddress, - newAdmin: newAdmin, - }); - }); - }); - - describe('when the sender is not the admin', function () { - it('reverts', async function () { - await expectRevert.unspecified(this.proxy.changeAdmin(newAdmin, { from: anotherAccount })); - }); - }); - }); - - describe('when the new proposed admin is the zero address', function () { - it('reverts', async function () { - await expectRevertCustomError( - this.proxy.changeAdmin(ZERO_ADDRESS, { from: proxyAdminAddress }), - 'ERC1967InvalidAdmin', - [ZERO_ADDRESS], - ); - }); - }); - }); - describe('transparent proxy', function () { beforeEach('creating proxy', async function () { const initializeData = Buffer.from(''); - this.impl = await ClashingImplementation.new(); - this.proxy = await createProxy(this.impl.address, proxyAdminAddress, initializeData, { from: proxyAdminOwner }); + this.clashingImplV0 = (await ClashingImplementation.new()).address; + this.clashingImplV1 = (await ClashingImplementation.new()).address; + this.proxy = await createProxy(this.clashingImplV0, proxyAdminAddress, initializeData, { + from: proxyAdminOwner, + }); this.clashing = new ClashingImplementation(this.proxy.address); }); @@ -315,24 +304,28 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy(createProx }); describe('when function names clash', function () { - it('when sender is proxy admin should run the proxy function', async function () { - const receipt = await this.proxy.changeAdmin(anotherAccount, { from: proxyAdminAddress, value: 0 }); - expectEvent(receipt, 'AdminChanged'); + it('executes the proxy function if the sender is the admin', async function () { + const receipt = await this.proxy.upgradeTo(this.clashingImplV1, { from: proxyAdminAddress, value: 0 }); + expectEvent(receipt, 'Upgraded', { implementation: this.clashingImplV1 }); }); - it('when sender is other should delegate to implementation', async function () { - const receipt = await this.proxy.changeAdmin(anotherAccount, { from: anotherAccount, value: 0 }); - expectEvent.notEmitted(receipt, 'AdminChanged'); + it('delegates the call to implementation when sender is not the admin', async function () { + const receipt = await this.proxy.upgradeTo(this.clashingImplV1, { from: anotherAccount, value: 0 }); + expectEvent.notEmitted(receipt, 'Upgraded'); expectEvent.inTransaction(receipt.tx, this.clashing, 'ClashingImplementationCall'); }); - it('when sender is proxy admin value should not be accepted', async function () { - await expectRevert.unspecified(this.proxy.changeAdmin(anotherAccount, { from: proxyAdminAddress, value: 1 })); + it('requires 0 value calling upgradeTo by proxy admin', async function () { + await expectRevertCustomError( + this.proxy.upgradeTo(this.clashingImplV1, { from: proxyAdminAddress, value: 1 }), + 'ProxyNonPayableFunction', + [], + ); }); - it('when sender is other value should be accepted', async function () { - const receipt = await this.proxy.changeAdmin(anotherAccount, { from: anotherAccount, value: 1 }); - expectEvent.notEmitted(receipt, 'AdminChanged'); + it('allows calling with value if sender is not the admin', async function () { + const receipt = await this.proxy.upgradeTo(this.clashingImplV1, { from: anotherAccount, value: 1 }); + expectEvent.notEmitted(receipt, 'Upgraded'); expectEvent.inTransaction(receipt.tx, this.clashing, 'ClashingImplementationCall'); }); }); From 365aca6d60bbb99f725e7a5086ae5219c6c6bd8b Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Tue, 20 Jun 2023 05:20:02 +0200 Subject: [PATCH 114/182] Improve tests for ERC1155 and ERC721 error handling (#3781) Co-authored-by: Francisco --- contracts/mocks/token/ERC1155ReceiverMock.sol | 44 +-- contracts/mocks/token/ERC721ReceiverMock.sol | 21 +- test/token/ERC1155/ERC1155.behavior.js | 299 +++++++++--------- test/token/ERC721/ERC721.behavior.js | 60 +++- 4 files changed, 237 insertions(+), 187 deletions(-) diff --git a/contracts/mocks/token/ERC1155ReceiverMock.sol b/contracts/mocks/token/ERC1155ReceiverMock.sol index c4cdbf20e..a5d7233cf 100644 --- a/contracts/mocks/token/ERC1155ReceiverMock.sol +++ b/contracts/mocks/token/ERC1155ReceiverMock.sol @@ -8,26 +8,24 @@ import "../../utils/introspection/ERC165.sol"; contract ERC1155ReceiverMock is ERC165, IERC1155Receiver { enum RevertType { None, - Empty, - String, - Custom + RevertWithoutMessage, + RevertWithMessage, + RevertWithCustomError, + Panic } - bytes4 private _recRetval; - RevertType private _recReverts; - bytes4 private _batRetval; - RevertType private _batReverts; + bytes4 private immutable _recRetval; + bytes4 private immutable _batRetval; + RevertType private immutable _error; event Received(address operator, address from, uint256 id, uint256 value, bytes data, uint256 gas); event BatchReceived(address operator, address from, uint256[] ids, uint256[] values, bytes data, uint256 gas); + error CustomError(bytes4); - error ERC1155ReceiverMockError(); - - constructor(bytes4 recRetval, RevertType recReverts, bytes4 batRetval, RevertType batReverts) { + constructor(bytes4 recRetval, bytes4 batRetval, RevertType error) { _recRetval = recRetval; - _recReverts = recReverts; _batRetval = batRetval; - _batReverts = batReverts; + _error = error; } function onERC1155Received( @@ -37,12 +35,15 @@ contract ERC1155ReceiverMock is ERC165, IERC1155Receiver { uint256 value, bytes calldata data ) external returns (bytes4) { - if (_recReverts == RevertType.Empty) { + if (_error == RevertType.RevertWithoutMessage) { revert(); - } else if (_recReverts == RevertType.String) { + } else if (_error == RevertType.RevertWithMessage) { revert("ERC1155ReceiverMock: reverting on receive"); - } else if (_recReverts == RevertType.Custom) { - revert ERC1155ReceiverMockError(); + } else if (_error == RevertType.RevertWithCustomError) { + revert CustomError(_recRetval); + } else if (_error == RevertType.Panic) { + uint256 a = uint256(0) / uint256(0); + a; } emit Received(operator, from, id, value, data, gasleft()); @@ -56,12 +57,15 @@ contract ERC1155ReceiverMock is ERC165, IERC1155Receiver { uint256[] calldata values, bytes calldata data ) external returns (bytes4) { - if (_batReverts == RevertType.Empty) { + if (_error == RevertType.RevertWithoutMessage) { revert(); - } else if (_batReverts == RevertType.String) { + } else if (_error == RevertType.RevertWithMessage) { revert("ERC1155ReceiverMock: reverting on batch receive"); - } else if (_batReverts == RevertType.Custom) { - revert ERC1155ReceiverMockError(); + } else if (_error == RevertType.RevertWithCustomError) { + revert CustomError(_recRetval); + } else if (_error == RevertType.Panic) { + uint256 a = uint256(0) / uint256(0); + a; } emit BatchReceived(operator, from, ids, values, data, gasleft()); diff --git a/contracts/mocks/token/ERC721ReceiverMock.sol b/contracts/mocks/token/ERC721ReceiverMock.sol index e97ceacf3..416613174 100644 --- a/contracts/mocks/token/ERC721ReceiverMock.sol +++ b/contracts/mocks/token/ERC721ReceiverMock.sol @@ -5,19 +5,21 @@ pragma solidity ^0.8.19; import "../../token/ERC721/IERC721Receiver.sol"; contract ERC721ReceiverMock is IERC721Receiver { - enum Error { + enum RevertType { None, - RevertWithMessage, RevertWithoutMessage, + RevertWithMessage, + RevertWithCustomError, Panic } bytes4 private immutable _retval; - Error private immutable _error; + RevertType private immutable _error; event Received(address operator, address from, uint256 tokenId, bytes data, uint256 gas); + error CustomError(bytes4); - constructor(bytes4 retval, Error error) { + constructor(bytes4 retval, RevertType error) { _retval = retval; _error = error; } @@ -28,14 +30,17 @@ contract ERC721ReceiverMock is IERC721Receiver { uint256 tokenId, bytes memory data ) public returns (bytes4) { - if (_error == Error.RevertWithMessage) { - revert("ERC721ReceiverMock: reverting"); - } else if (_error == Error.RevertWithoutMessage) { + if (_error == RevertType.RevertWithoutMessage) { revert(); - } else if (_error == Error.Panic) { + } else if (_error == RevertType.RevertWithMessage) { + revert("ERC721ReceiverMock: reverting"); + } else if (_error == RevertType.RevertWithCustomError) { + revert CustomError(_retval); + } else if (_error == RevertType.Panic) { uint256 a = uint256(0) / uint256(0); a; } + emit Received(operator, from, tokenId, data, gasleft()); return _retval; } diff --git a/test/token/ERC1155/ERC1155.behavior.js b/test/token/ERC1155/ERC1155.behavior.js index 36f67d321..3391be4c8 100644 --- a/test/token/ERC1155/ERC1155.behavior.js +++ b/test/token/ERC1155/ERC1155.behavior.js @@ -1,14 +1,13 @@ const { BN, constants, expectEvent, expectRevert } = require('@openzeppelin/test-helpers'); -const { ZERO_ADDRESS } = constants; - const { expect } = require('chai'); +const { ZERO_ADDRESS } = constants; const { shouldSupportInterfaces } = require('../../utils/introspection/SupportsInterface.behavior'); const { expectRevertCustomError } = require('../../helpers/customError'); const { Enum } = require('../../helpers/enums'); const ERC1155ReceiverMock = artifacts.require('ERC1155ReceiverMock'); -const RevertType = Enum('None', 'Empty', 'String', 'Custom'); +const RevertType = Enum('None', 'RevertWithoutMessage', 'RevertWithMessage', 'RevertWithCustomError', 'Panic'); function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, multiTokenHolder, recipient, proxy]) { const firstTokenId = new BN(1); @@ -298,7 +297,6 @@ function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, m beforeEach(async function () { this.receiver = await ERC1155ReceiverMock.new( RECEIVER_SINGLE_MAGIC_VALUE, - RevertType.None, RECEIVER_BATCH_MAGIC_VALUE, RevertType.None, ); @@ -372,12 +370,7 @@ function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, m context('to a receiver contract returning unexpected value', function () { beforeEach(async function () { - this.receiver = await ERC1155ReceiverMock.new( - '0x00c0ffee', - RevertType.None, - RECEIVER_BATCH_MAGIC_VALUE, - RevertType.None, - ); + this.receiver = await ERC1155ReceiverMock.new('0x00c0ffee', RECEIVER_BATCH_MAGIC_VALUE, RevertType.None); }); it('reverts', async function () { @@ -392,54 +385,81 @@ function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, m }); context('to a receiver contract that reverts', function () { - it('with empty reason', async function () { - const receiver = await ERC1155ReceiverMock.new( - RECEIVER_SINGLE_MAGIC_VALUE, - RevertType.Empty, - RECEIVER_BATCH_MAGIC_VALUE, - RevertType.None, - ); + context('with a revert string', function () { + beforeEach(async function () { + this.receiver = await ERC1155ReceiverMock.new( + RECEIVER_SINGLE_MAGIC_VALUE, + RECEIVER_BATCH_MAGIC_VALUE, + RevertType.RevertWithMessage, + ); + }); - await expectRevertCustomError( - this.token.safeTransferFrom(multiTokenHolder, receiver.address, firstTokenId, firstAmount, '0x', { - from: multiTokenHolder, - }), - 'ERC1155InvalidReceiver', - [receiver.address], - ); + it('reverts', async function () { + await expectRevert( + this.token.safeTransferFrom(multiTokenHolder, this.receiver.address, firstTokenId, firstAmount, '0x', { + from: multiTokenHolder, + }), + 'ERC1155ReceiverMock: reverting on receive', + ); + }); }); - it('with reason string', async function () { - const receiver = await ERC1155ReceiverMock.new( - RECEIVER_SINGLE_MAGIC_VALUE, - RevertType.String, - RECEIVER_BATCH_MAGIC_VALUE, - RevertType.None, - ); + context('without a revert string', function () { + beforeEach(async function () { + this.receiver = await ERC1155ReceiverMock.new( + RECEIVER_SINGLE_MAGIC_VALUE, + RECEIVER_BATCH_MAGIC_VALUE, + RevertType.RevertWithoutMessage, + ); + }); - await expectRevert( - this.token.safeTransferFrom(multiTokenHolder, receiver.address, firstTokenId, firstAmount, '0x', { - from: multiTokenHolder, - }), - 'ERC1155ReceiverMock: reverting on receive', - ); + it('reverts', async function () { + await expectRevertCustomError( + this.token.safeTransferFrom(multiTokenHolder, this.receiver.address, firstTokenId, firstAmount, '0x', { + from: multiTokenHolder, + }), + 'ERC1155InvalidReceiver', + [this.receiver.address], + ); + }); }); - it('with custom error', async function () { - const receiver = await ERC1155ReceiverMock.new( - RECEIVER_SINGLE_MAGIC_VALUE, - RevertType.Custom, - RECEIVER_BATCH_MAGIC_VALUE, - RevertType.None, - ); + context('with a custom error', function () { + beforeEach(async function () { + this.receiver = await ERC1155ReceiverMock.new( + RECEIVER_SINGLE_MAGIC_VALUE, + RECEIVER_BATCH_MAGIC_VALUE, + RevertType.RevertWithCustomError, + ); + }); - await expectRevertCustomError( - this.token.safeTransferFrom(multiTokenHolder, receiver.address, firstTokenId, firstAmount, '0x', { - from: multiTokenHolder, - }), - 'ERC1155ReceiverMockError', - [], - ); + it('reverts', async function () { + await expectRevertCustomError( + this.token.safeTransferFrom(multiTokenHolder, this.receiver.address, firstTokenId, firstAmount, '0x', { + from: multiTokenHolder, + }), + 'CustomError', + [RECEIVER_SINGLE_MAGIC_VALUE], + ); + }); + }); + + context('with a panic', function () { + beforeEach(async function () { + this.receiver = await ERC1155ReceiverMock.new( + RECEIVER_SINGLE_MAGIC_VALUE, + RECEIVER_BATCH_MAGIC_VALUE, + RevertType.Panic, + ); + }); + + it('reverts', async function () { + await expectRevert.unspecified( + this.token.safeTransferFrom(multiTokenHolder, this.receiver.address, firstTokenId, firstAmount, '0x', { + from: multiTokenHolder, + }), + ); + }); }); }); @@ -629,7 +649,6 @@ function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, m beforeEach(async function () { this.receiver = await ERC1155ReceiverMock.new( RECEIVER_SINGLE_MAGIC_VALUE, - RevertType.None, RECEIVER_BATCH_MAGIC_VALUE, RevertType.None, ); @@ -705,7 +724,6 @@ function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, m beforeEach(async function () { this.receiver = await ERC1155ReceiverMock.new( RECEIVER_SINGLE_MAGIC_VALUE, - RevertType.None, RECEIVER_SINGLE_MAGIC_VALUE, RevertType.None, ); @@ -728,107 +746,100 @@ function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, m }); context('to a receiver contract that reverts', function () { - it('with empty reason', async function () { - const receiver = await ERC1155ReceiverMock.new( - RECEIVER_SINGLE_MAGIC_VALUE, - RevertType.None, - RECEIVER_BATCH_MAGIC_VALUE, - RevertType.Empty, - ); - - await expectRevertCustomError( - this.token.safeBatchTransferFrom( - multiTokenHolder, - receiver.address, - [firstTokenId, secondTokenId], - [firstAmount, secondAmount], - '0x', - { from: multiTokenHolder }, - ), - 'ERC1155InvalidReceiver', - [receiver.address], - ); - }); - - it('with reason string', async function () { - const receiver = await ERC1155ReceiverMock.new( - RECEIVER_SINGLE_MAGIC_VALUE, - RevertType.None, - RECEIVER_BATCH_MAGIC_VALUE, - RevertType.String, - ); + context('with a revert string', function () { + beforeEach(async function () { + this.receiver = await ERC1155ReceiverMock.new( + RECEIVER_SINGLE_MAGIC_VALUE, + RECEIVER_BATCH_MAGIC_VALUE, + RevertType.RevertWithMessage, + ); + }); - await expectRevert( - this.token.safeBatchTransferFrom( - multiTokenHolder, - receiver.address, - [firstTokenId, secondTokenId], - [firstAmount, secondAmount], - '0x', - { from: multiTokenHolder }, - ), - 'ERC1155ReceiverMock: reverting on batch receive', - ); + it('reverts', async function () { + await expectRevert( + this.token.safeBatchTransferFrom( + multiTokenHolder, + this.receiver.address, + [firstTokenId, secondTokenId], + [firstAmount, secondAmount], + '0x', + { from: multiTokenHolder }, + ), + 'ERC1155ReceiverMock: reverting on batch receive', + ); + }); }); - it('with custom error', async function () { - const receiver = await ERC1155ReceiverMock.new( - RECEIVER_SINGLE_MAGIC_VALUE, - RevertType.None, - RECEIVER_BATCH_MAGIC_VALUE, - RevertType.Custom, - ); + context('without a revert string', function () { + beforeEach(async function () { + this.receiver = await ERC1155ReceiverMock.new( + RECEIVER_SINGLE_MAGIC_VALUE, + RECEIVER_BATCH_MAGIC_VALUE, + RevertType.RevertWithoutMessage, + ); + }); - await expectRevertCustomError( - this.token.safeBatchTransferFrom( - multiTokenHolder, - receiver.address, - [firstTokenId, secondTokenId], - [firstAmount, secondAmount], - '0x', - { from: multiTokenHolder }, - ), - 'ERC1155ReceiverMockError', - [], - ); + it('reverts', async function () { + await expectRevertCustomError( + this.token.safeBatchTransferFrom( + multiTokenHolder, + this.receiver.address, + [firstTokenId, secondTokenId], + [firstAmount, secondAmount], + '0x', + { from: multiTokenHolder }, + ), + 'ERC1155InvalidReceiver', + [this.receiver.address], + ); + }); }); - }); - context('to a receiver contract that reverts only on single transfers', function () { - beforeEach(async function () { - this.receiver = await ERC1155ReceiverMock.new( - RECEIVER_SINGLE_MAGIC_VALUE, - RevertType.String, - RECEIVER_BATCH_MAGIC_VALUE, - RevertType.None, - ); + context('with a custom error', function () { + beforeEach(async function () { + this.receiver = await ERC1155ReceiverMock.new( + RECEIVER_SINGLE_MAGIC_VALUE, + RECEIVER_BATCH_MAGIC_VALUE, + RevertType.RevertWithCustomError, + ); + }); - this.toWhom = this.receiver.address; - this.transferReceipt = await this.token.safeBatchTransferFrom( - multiTokenHolder, - this.receiver.address, - [firstTokenId, secondTokenId], - [firstAmount, secondAmount], - '0x', - { from: multiTokenHolder }, - ); - this.transferLogs = this.transferReceipt; + it('reverts', async function () { + await expectRevertCustomError( + this.token.safeBatchTransferFrom( + multiTokenHolder, + this.receiver.address, + [firstTokenId, secondTokenId], + [firstAmount, secondAmount], + '0x', + { from: multiTokenHolder }, + ), + 'CustomError', + [RECEIVER_SINGLE_MAGIC_VALUE], + ); + }); }); - batchTransferWasSuccessful.call(this, { - operator: multiTokenHolder, - from: multiTokenHolder, - ids: [firstTokenId, secondTokenId], - values: [firstAmount, secondAmount], - }); + context('with a panic', function () { + beforeEach(async function () { + this.receiver = await ERC1155ReceiverMock.new( + RECEIVER_SINGLE_MAGIC_VALUE, + RECEIVER_BATCH_MAGIC_VALUE, + RevertType.Panic, + ); + }); - it('calls onERC1155BatchReceived', async function () { - await expectEvent.inTransaction(this.transferReceipt.tx, ERC1155ReceiverMock, 'BatchReceived', { - operator: multiTokenHolder, - from: multiTokenHolder, - // ids: [firstTokenId, secondTokenId], - // values: [firstAmount, secondAmount], - data: null, + it('reverts', async function () { + await expectRevert.unspecified( + this.token.safeBatchTransferFrom( + multiTokenHolder, + this.receiver.address, + [firstTokenId, secondTokenId], + [firstAmount, secondAmount], + '0x', + { from: multiTokenHolder }, + ), + ); }); }); }); diff --git a/test/token/ERC721/ERC721.behavior.js b/test/token/ERC721/ERC721.behavior.js index 7df429202..75700f6ab 100644 --- a/test/token/ERC721/ERC721.behavior.js +++ b/test/token/ERC721/ERC721.behavior.js @@ -4,14 +4,12 @@ const { ZERO_ADDRESS } = constants; const { shouldSupportInterfaces } = require('../../utils/introspection/SupportsInterface.behavior'); const { expectRevertCustomError } = require('../../helpers/customError'); +const { Enum } = require('../../helpers/enums'); const ERC721ReceiverMock = artifacts.require('ERC721ReceiverMock'); const NonERC721ReceiverMock = artifacts.require('CallReceiverMock'); -const Error = ['None', 'RevertWithMessage', 'RevertWithoutMessage', 'Panic'].reduce( - (acc, entry, idx) => Object.assign({ [entry]: idx }, acc), - {}, -); +const RevertType = Enum('None', 'RevertWithoutMessage', 'RevertWithMessage', 'RevertWithCustomError', 'Panic'); const firstTokenId = new BN('5042'); const secondTokenId = new BN('79217'); @@ -234,7 +232,7 @@ function shouldBehaveLikeERC721(owner, newOwner, approved, anotherApproved, oper describe('to a valid receiver contract', function () { beforeEach(async function () { - this.receiver = await ERC721ReceiverMock.new(RECEIVER_MAGIC_VALUE, Error.None); + this.receiver = await ERC721ReceiverMock.new(RECEIVER_MAGIC_VALUE, RevertType.None); this.toWhom = this.receiver.address; }); @@ -284,7 +282,7 @@ function shouldBehaveLikeERC721(owner, newOwner, approved, anotherApproved, oper describe('to a receiver contract returning unexpected value', function () { it('reverts', async function () { - const invalidReceiver = await ERC721ReceiverMock.new('0x42', Error.None); + const invalidReceiver = await ERC721ReceiverMock.new('0x42', RevertType.None); await expectRevertCustomError( this.token.safeTransferFrom(owner, invalidReceiver.address, tokenId, { from: owner }), 'ERC721InvalidReceiver', @@ -295,7 +293,7 @@ function shouldBehaveLikeERC721(owner, newOwner, approved, anotherApproved, oper describe('to a receiver contract that reverts with message', function () { it('reverts', async function () { - const revertingReceiver = await ERC721ReceiverMock.new(RECEIVER_MAGIC_VALUE, Error.RevertWithMessage); + const revertingReceiver = await ERC721ReceiverMock.new(RECEIVER_MAGIC_VALUE, RevertType.RevertWithMessage); await expectRevert( this.token.safeTransferFrom(owner, revertingReceiver.address, tokenId, { from: owner }), 'ERC721ReceiverMock: reverting', @@ -305,7 +303,10 @@ function shouldBehaveLikeERC721(owner, newOwner, approved, anotherApproved, oper describe('to a receiver contract that reverts without message', function () { it('reverts', async function () { - const revertingReceiver = await ERC721ReceiverMock.new(RECEIVER_MAGIC_VALUE, Error.RevertWithoutMessage); + const revertingReceiver = await ERC721ReceiverMock.new( + RECEIVER_MAGIC_VALUE, + RevertType.RevertWithoutMessage, + ); await expectRevertCustomError( this.token.safeTransferFrom(owner, revertingReceiver.address, tokenId, { from: owner }), 'ERC721InvalidReceiver', @@ -314,9 +315,23 @@ function shouldBehaveLikeERC721(owner, newOwner, approved, anotherApproved, oper }); }); + describe('to a receiver contract that reverts with custom error', function () { + it('reverts', async function () { + const revertingReceiver = await ERC721ReceiverMock.new( + RECEIVER_MAGIC_VALUE, + RevertType.RevertWithCustomError, + ); + await expectRevertCustomError( + this.token.safeTransferFrom(owner, revertingReceiver.address, tokenId, { from: owner }), + 'CustomError', + [RECEIVER_MAGIC_VALUE], + ); + }); + }); + describe('to a receiver contract that panics', function () { it('reverts', async function () { - const revertingReceiver = await ERC721ReceiverMock.new(RECEIVER_MAGIC_VALUE, Error.Panic); + const revertingReceiver = await ERC721ReceiverMock.new(RECEIVER_MAGIC_VALUE, RevertType.Panic); await expectRevert.unspecified( this.token.safeTransferFrom(owner, revertingReceiver.address, tokenId, { from: owner }), ); @@ -343,7 +358,7 @@ function shouldBehaveLikeERC721(owner, newOwner, approved, anotherApproved, oper describe('via safeMint', function () { // regular minting is tested in ERC721Mintable.test.js and others it('calls onERC721Received — with data', async function () { - this.receiver = await ERC721ReceiverMock.new(RECEIVER_MAGIC_VALUE, Error.None); + this.receiver = await ERC721ReceiverMock.new(RECEIVER_MAGIC_VALUE, RevertType.None); const receipt = await this.token.$_safeMint(this.receiver.address, tokenId, data); await expectEvent.inTransaction(receipt.tx, ERC721ReceiverMock, 'Received', { @@ -354,7 +369,7 @@ function shouldBehaveLikeERC721(owner, newOwner, approved, anotherApproved, oper }); it('calls onERC721Received — without data', async function () { - this.receiver = await ERC721ReceiverMock.new(RECEIVER_MAGIC_VALUE, Error.None); + this.receiver = await ERC721ReceiverMock.new(RECEIVER_MAGIC_VALUE, RevertType.None); const receipt = await this.token.$_safeMint(this.receiver.address, tokenId); await expectEvent.inTransaction(receipt.tx, ERC721ReceiverMock, 'Received', { @@ -365,7 +380,7 @@ function shouldBehaveLikeERC721(owner, newOwner, approved, anotherApproved, oper context('to a receiver contract returning unexpected value', function () { it('reverts', async function () { - const invalidReceiver = await ERC721ReceiverMock.new('0x42', Error.None); + const invalidReceiver = await ERC721ReceiverMock.new('0x42', RevertType.None); await expectRevertCustomError( this.token.$_safeMint(invalidReceiver.address, tokenId), 'ERC721InvalidReceiver', @@ -376,7 +391,7 @@ function shouldBehaveLikeERC721(owner, newOwner, approved, anotherApproved, oper context('to a receiver contract that reverts with message', function () { it('reverts', async function () { - const revertingReceiver = await ERC721ReceiverMock.new(RECEIVER_MAGIC_VALUE, Error.RevertWithMessage); + const revertingReceiver = await ERC721ReceiverMock.new(RECEIVER_MAGIC_VALUE, RevertType.RevertWithMessage); await expectRevert( this.token.$_safeMint(revertingReceiver.address, tokenId), 'ERC721ReceiverMock: reverting', @@ -386,7 +401,10 @@ function shouldBehaveLikeERC721(owner, newOwner, approved, anotherApproved, oper context('to a receiver contract that reverts without message', function () { it('reverts', async function () { - const revertingReceiver = await ERC721ReceiverMock.new(RECEIVER_MAGIC_VALUE, Error.RevertWithoutMessage); + const revertingReceiver = await ERC721ReceiverMock.new( + RECEIVER_MAGIC_VALUE, + RevertType.RevertWithoutMessage, + ); await expectRevertCustomError( this.token.$_safeMint(revertingReceiver.address, tokenId), 'ERC721InvalidReceiver', @@ -395,9 +413,21 @@ function shouldBehaveLikeERC721(owner, newOwner, approved, anotherApproved, oper }); }); + context('to a receiver contract that reverts with custom error', function () { + it('reverts', async function () { + const revertingReceiver = await ERC721ReceiverMock.new( + RECEIVER_MAGIC_VALUE, + RevertType.RevertWithCustomError, + ); + await expectRevertCustomError(this.token.$_safeMint(revertingReceiver.address, tokenId), 'CustomError', [ + RECEIVER_MAGIC_VALUE, + ]); + }); + }); + context('to a receiver contract that panics', function () { it('reverts', async function () { - const revertingReceiver = await ERC721ReceiverMock.new(RECEIVER_MAGIC_VALUE, Error.Panic); + const revertingReceiver = await ERC721ReceiverMock.new(RECEIVER_MAGIC_VALUE, RevertType.Panic); await expectRevert.unspecified(this.token.$_safeMint(revertingReceiver.address, tokenId)); }); }); From 2271e2c58d007894c5fe23c4f03a95f645ac9175 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Tue, 20 Jun 2023 10:01:30 +0200 Subject: [PATCH 115/182] Use clones for testing non ERC1967 proxies (#4371) --- test/proxy/utils/UUPSUpgradeable.test.js | 25 ++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/test/proxy/utils/UUPSUpgradeable.test.js b/test/proxy/utils/UUPSUpgradeable.test.js index 95319c305..6a8104248 100644 --- a/test/proxy/utils/UUPSUpgradeable.test.js +++ b/test/proxy/utils/UUPSUpgradeable.test.js @@ -7,7 +7,7 @@ const UUPSUpgradeableMock = artifacts.require('UUPSUpgradeableMock'); const UUPSUpgradeableUnsafeMock = artifacts.require('UUPSUpgradeableUnsafeMock'); const NonUpgradeableMock = artifacts.require('NonUpgradeableMock'); const UUPSUnsupportedProxiableUUID = artifacts.require('UUPSUnsupportedProxiableUUID'); -const Address = artifacts.require('$Address'); +const Clones = artifacts.require('$Clones'); contract('UUPSUpgradeable', function () { before(async function () { @@ -16,7 +16,8 @@ contract('UUPSUpgradeable', function () { this.implUpgradeUnsafe = await UUPSUpgradeableUnsafeMock.new(); this.implUpgradeNonUUPS = await NonUpgradeableMock.new(); this.implUnsupportedUUID = await UUPSUnsupportedProxiableUUID.new(); - this.helper = await Address.new(); + // Used for testing non ERC1967 compliant proxies (clones are proxies that don't use the ERC1967 implementation slot) + this.cloneFactory = await Clones.new(); }); beforeEach(async function () { @@ -65,22 +66,26 @@ contract('UUPSUpgradeable', function () { }); it('calling upgradeTo from a contract that is not an ERC1967 proxy (with the right implementation) reverts', async function () { + const receipt = await this.cloneFactory.$clone(this.implUpgradeOk.address); + const instance = await UUPSUpgradeableMock.at( + receipt.logs.find(({ event }) => event === 'return$clone').args.instance, + ); + await expectRevertCustomError( - this.helper.$functionDelegateCall( - this.implUpgradeOk.address, - this.implUpgradeOk.contract.methods.upgradeTo(this.implUpgradeUnsafe.address).encodeABI(), - ), + instance.upgradeTo(this.implUpgradeUnsafe.address), 'UUPSUnauthorizedCallContext', [], ); }); it('calling upgradeToAndCall from a contract that is not an ERC1967 proxy (with the right implementation) reverts', async function () { + const receipt = await this.cloneFactory.$clone(this.implUpgradeOk.address); + const instance = await UUPSUpgradeableMock.at( + receipt.logs.find(({ event }) => event === 'return$clone').args.instance, + ); + await expectRevertCustomError( - this.helper.$functionDelegateCall( - this.implUpgradeOk.address, - this.implUpgradeOk.contract.methods.upgradeToAndCall(this.implUpgradeUnsafe.address, '0x').encodeABI(), - ), + instance.upgradeToAndCall(this.implUpgradeUnsafe.address, '0x'), 'UUPSUnauthorizedCallContext', [], ); From 9fa550c62f8f086f2d874f4113819e1eaf33f686 Mon Sep 17 00:00:00 2001 From: Francisco Date: Tue, 20 Jun 2023 13:06:39 -0300 Subject: [PATCH 116/182] Fix attempt to delete nonexistent npm tag (#4374) --- scripts/release/workflow/publish.sh | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/scripts/release/workflow/publish.sh b/scripts/release/workflow/publish.sh index 41a9975cb..e490e5d00 100644 --- a/scripts/release/workflow/publish.sh +++ b/scripts/release/workflow/publish.sh @@ -2,19 +2,25 @@ set -euo pipefail +PACKAGE_JSON_NAME="$(tar xfO "$TARBALL" package/package.json | jq -r .name)" +PACKAGE_JSON_VERSION="$(tar xfO "$TARBALL" package/package.json | jq -r .version)" + # Intentionally escape $ to avoid interpolation and writing the token to disk echo "//registry.npmjs.org/:_authToken=\${NPM_TOKEN}" > .npmrc # Actual publish npm publish "$TARBALL" --tag "$TAG" +# Clean up tags delete_tag() { - PACKAGE_JSON_NAME="$(tar xfO "$TARBALL" package/package.json | jq -r .name)" npm dist-tag rm "$PACKAGE_JSON_NAME" "$1" } if [ "$TAG" = tmp ]; then delete_tag "$TAG" elif [ "$TAG" = latest ]; then - delete_tag next + # Delete the next tag if it exists and is a prerelease for what is currently being published + if npm dist-tag ls "$PACKAGE_JSON_NAME" | grep -q "next: $PACKAGE_JSON_VERSION"; then + delete_tag next + fi fi From b66c77a1fc7bb43fa07ee0371df07ac3364214bb Mon Sep 17 00:00:00 2001 From: Francisco Date: Tue, 20 Jun 2023 13:06:56 -0300 Subject: [PATCH 117/182] Merge branch 'release-v4.9' into master (#4373) --- .changeset/shy-crews-teach.md | 5 ----- CHANGELOG.md | 4 ++++ contracts/package.json | 2 +- contracts/utils/cryptography/MerkleProof.sol | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 6 files changed, 9 insertions(+), 10 deletions(-) delete mode 100644 .changeset/shy-crews-teach.md diff --git a/.changeset/shy-crews-teach.md b/.changeset/shy-crews-teach.md deleted file mode 100644 index 8ab929bf8..000000000 --- a/.changeset/shy-crews-teach.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': patch ---- - -`MerkleProof`: Fix a bug in `processMultiProof` and `processMultiProofCalldata` that allows proving arbitrary leaves if the tree contains a node with value 0 at depth 1. diff --git a/CHANGELOG.md b/CHANGELOG.md index abbb1b347..bbe7502e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -59,6 +59,10 @@ function supportsInterface(bytes4 interfaceId) public view virtual override retu } ``` +## 4.9.2 (2023-06-16) + +- `MerkleProof`: Fix a bug in `processMultiProof` and `processMultiProofCalldata` that allows proving arbitrary leaves if the tree contains a node with value 0 at depth 1. + ## 4.9.1 (2023-06-07) - `Governor`: Add a mechanism to restrict the address of the proposer using a suffix in the description. diff --git a/contracts/package.json b/contracts/package.json index 4711a6b3f..df141192d 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -1,7 +1,7 @@ { "name": "@openzeppelin/contracts", "description": "Secure Smart Contract library for Solidity", - "version": "4.9.1", + "version": "4.9.2", "files": [ "**/*.sol", "/build/contracts/*.json", diff --git a/contracts/utils/cryptography/MerkleProof.sol b/contracts/utils/cryptography/MerkleProof.sol index 39826d8c6..94586ff7b 100644 --- a/contracts/utils/cryptography/MerkleProof.sol +++ b/contracts/utils/cryptography/MerkleProof.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.9.0) (utils/cryptography/MerkleProof.sol) +// OpenZeppelin Contracts (last updated v4.9.2) (utils/cryptography/MerkleProof.sol) pragma solidity ^0.8.19; diff --git a/package-lock.json b/package-lock.json index edf1e9284..d4cb52694 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "openzeppelin-solidity", - "version": "4.9.0", + "version": "4.9.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "openzeppelin-solidity", - "version": "4.9.0", + "version": "4.9.2", "license": "MIT", "devDependencies": { "@changesets/changelog-github": "^0.4.8", diff --git a/package.json b/package.json index c44154133..37e8f8710 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "openzeppelin-solidity", "description": "Secure Smart Contract library for Solidity", - "version": "4.9.1", + "version": "4.9.2", "files": [ "/contracts/**/*.sol", "/build/contracts/*.json", From dac2457a800aaa3d75b7fc999d0c7c323ec2faff Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Tue, 20 Jun 2023 18:34:10 +0200 Subject: [PATCH 118/182] Improve customError testing (#4376) --- test/helpers/customError.js | 71 +++++++++++++++++++------------------ 1 file changed, 36 insertions(+), 35 deletions(-) diff --git a/test/helpers/customError.js b/test/helpers/customError.js index a193ab0cf..e38170b78 100644 --- a/test/helpers/customError.js +++ b/test/helpers/customError.js @@ -2,42 +2,43 @@ const { expect } = require('chai'); /** Revert handler that supports custom errors. */ async function expectRevertCustomError(promise, expectedErrorName, args) { - try { - await promise; - expect.fail("Expected promise to throw but it didn't"); - } catch (revert) { - if (!Array.isArray(args)) { - expect.fail('Expected 3rd array parameter for error arguments'); - } - // The revert message for custom errors looks like: - // VM Exception while processing transaction: - // reverted with custom error 'InvalidAccountNonce("0x70997970C51812dc3A010C7d01b50e0d17dc79C8", 0)' - - // We trim out anything inside the single quotes as comma-separated values - const [, error] = revert.message.match(/'(.*)'/); - - // Attempt to parse as an error - const match = error.match(/(?\w+)\((?.*)\)/); - if (!match) { - expect.fail(`Couldn't parse "${error}" as a custom error`); - } - // Extract the error name and parameters - const errorName = match.groups.name; - const argMatches = [...match.groups.args.matchAll(/-?\w+/g)]; - - // Assert error name - expect(errorName).to.be.equal( - expectedErrorName, - `Unexpected custom error name (with found args: [${argMatches.map(([a]) => a)}])`, - ); - - // Coerce to string for comparison since `arg` can be either a number or hex. - const sanitizedExpected = args.map(arg => arg.toString().toLowerCase()); - const sanitizedActual = argMatches.map(([arg]) => arg.toString().toLowerCase()); - - // Assert argument equality - expect(sanitizedActual).to.have.members(sanitizedExpected, `Unexpected ${errorName} arguments`); + if (!Array.isArray(args)) { + expect.fail('Expected 3rd array parameter for error arguments'); } + + await promise.then( + () => expect.fail("Expected promise to throw but it didn't"), + ({ message }) => { + // The revert message for custom errors looks like: + // VM Exception while processing transaction: + // reverted with custom error 'InvalidAccountNonce("0x70997970C51812dc3A010C7d01b50e0d17dc79C8", 0)' + + // We trim out anything inside the single quotes as comma-separated values + const [, error] = message.match(/'(.*)'/); + + // Attempt to parse as an error + const match = error.match(/(?\w+)\((?.*)\)/); + if (!match) { + expect.fail(`Couldn't parse "${error}" as a custom error`); + } + // Extract the error name and parameters + const errorName = match.groups.name; + const argMatches = [...match.groups.args.matchAll(/-?\w+/g)]; + + // Assert error name + expect(errorName).to.be.equal( + expectedErrorName, + `Unexpected custom error name (with found args: [${argMatches.map(([a]) => a)}])`, + ); + + // Coerce to string for comparison since `arg` can be either a number or hex. + const sanitizedExpected = args.map(arg => arg.toString().toLowerCase()); + const sanitizedActual = argMatches.map(([arg]) => arg.toString().toLowerCase()); + + // Assert argument equality + expect(sanitizedActual).to.have.members(sanitizedExpected, `Unexpected ${errorName} arguments`); + }, + ); } module.exports = { From 6ddacdbde856e203e222e3adc461dccce0c2930b Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Tue, 20 Jun 2023 21:38:03 +0200 Subject: [PATCH 119/182] Cleanup timelockId on execution for gas refund (#4118) Co-authored-by: Francisco --- .changeset/hot-plums-approve.md | 5 +++++ .../extensions/GovernorTimelockControl.sol | 18 +++++++++++++----- 2 files changed, 18 insertions(+), 5 deletions(-) create mode 100644 .changeset/hot-plums-approve.md diff --git a/.changeset/hot-plums-approve.md b/.changeset/hot-plums-approve.md new file mode 100644 index 000000000..131559027 --- /dev/null +++ b/.changeset/hot-plums-approve.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': minor +--- + +`GovernorTimelockControl`: Clean up timelock id on execution for gas refund. diff --git a/contracts/governance/extensions/GovernorTimelockControl.sol b/contracts/governance/extensions/GovernorTimelockControl.sol index 888406ba7..fadbcc701 100644 --- a/contracts/governance/extensions/GovernorTimelockControl.sol +++ b/contracts/governance/extensions/GovernorTimelockControl.sol @@ -60,11 +60,13 @@ abstract contract GovernorTimelockControl is IGovernorTimelock, Governor { bytes32 queueid = _timelockIds[proposalId]; if (queueid == bytes32(0)) { return currentState; - } else if (_timelock.isOperationDone(queueid)) { - return ProposalState.Executed; } else if (_timelock.isOperationPending(queueid)) { return ProposalState.Queued; + } else if (_timelock.isOperationDone(queueid)) { + // This can happen if the proposal is executed directly on the timelock. + return ProposalState.Executed; } else { + // This can happen if the proposal is canceled directly on the timelock. return ProposalState.Canceled; } } @@ -117,13 +119,16 @@ abstract contract GovernorTimelockControl is IGovernorTimelock, Governor { * @dev Overridden execute function that run the already queued proposal through the timelock. */ function _execute( - uint256 /* proposalId */, + uint256 proposalId, address[] memory targets, uint256[] memory values, bytes[] memory calldatas, bytes32 descriptionHash ) internal virtual override { + // execute _timelock.executeBatch{value: msg.value}(targets, values, calldatas, 0, descriptionHash); + // cleanup for refund + delete _timelockIds[proposalId]; } /** @@ -140,9 +145,12 @@ abstract contract GovernorTimelockControl is IGovernorTimelock, Governor { bytes32 descriptionHash ) internal virtual override returns (uint256) { uint256 proposalId = super._cancel(targets, values, calldatas, descriptionHash); + bytes32 timelockId = _timelockIds[proposalId]; - if (_timelockIds[proposalId] != 0) { - _timelock.cancel(_timelockIds[proposalId]); + if (timelockId != 0) { + // cancel + _timelock.cancel(timelockId); + // cleanup delete _timelockIds[proposalId]; } From 1e0e4e20bb3a73436d3b02a9a9d3e607ea89a931 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Thu, 22 Jun 2023 18:41:56 +0200 Subject: [PATCH 120/182] Do not emit Approval event when calling transferFrom (#4370) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ernesto García Co-authored-by: Francisco --- .changeset/heavy-drinks-fail.md | 5 + contracts/mocks/token/ERC20ApprovalMock.sol | 10 + contracts/token/ERC20/ERC20.sol | 27 +- test/token/ERC20/ERC20.behavior.js | 26 +- test/token/ERC20/ERC20.test.js | 544 +++++++++--------- .../ERC20/extensions/ERC20Wrapper.test.js | 20 +- 6 files changed, 341 insertions(+), 291 deletions(-) create mode 100644 .changeset/heavy-drinks-fail.md create mode 100644 contracts/mocks/token/ERC20ApprovalMock.sol diff --git a/.changeset/heavy-drinks-fail.md b/.changeset/heavy-drinks-fail.md new file mode 100644 index 000000000..bbe93ca90 --- /dev/null +++ b/.changeset/heavy-drinks-fail.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': major +--- + +`ERC20`: Remove `Approval` event previously emitted in `transferFrom` to indicate that part of the allowance was consumed. With this change, allowances are no longer reconstructible from events. See the code for guidelines on how to re-enable this event if needed. diff --git a/contracts/mocks/token/ERC20ApprovalMock.sol b/contracts/mocks/token/ERC20ApprovalMock.sol new file mode 100644 index 000000000..da8f51e1e --- /dev/null +++ b/contracts/mocks/token/ERC20ApprovalMock.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import "../../token/ERC20/ERC20.sol"; + +abstract contract ERC20ApprovalMock is ERC20 { + function _approve(address owner, address spender, uint256 amount, bool) internal virtual override { + super._approve(owner, spender, amount, true); + } +} diff --git a/contracts/token/ERC20/ERC20.sol b/contracts/token/ERC20/ERC20.sol index aacfe79b7..5ddebf22b 100644 --- a/contracts/token/ERC20/ERC20.sol +++ b/contracts/token/ERC20/ERC20.sol @@ -311,6 +311,27 @@ abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors { * - `spender` cannot be the zero address. */ function _approve(address owner, address spender, uint256 amount) internal virtual { + _approve(owner, spender, amount, true); + } + + /** + * @dev Alternative version of {_approve} with an optional flag that can enable or disable the Approval event. + * + * By default (when calling {_approve}) the flag is set to true. On the other hand, approval changes made by + * `_spendAllowance` during the `transferFrom` operation set the flag to false. This saves gas by not emitting any + * `Approval` event during `transferFrom` operations. + * + * Anyone who wishes to continue emitting `Approval` events on the`transferFrom` operation can force the flag to true + * using the following override: + * ``` + * function _approve(address owner, address spender, uint256 amount, bool) internal virtual override { + * super._approve(owner, spender, amount, true); + * } + * ``` + * + * Requirements are the same as {_approve}. + */ + function _approve(address owner, address spender, uint256 amount, bool emitEvent) internal virtual { if (owner == address(0)) { revert ERC20InvalidApprover(address(0)); } @@ -318,7 +339,9 @@ abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors { revert ERC20InvalidSpender(address(0)); } _allowances[owner][spender] = amount; - emit Approval(owner, spender, amount); + if (emitEvent) { + emit Approval(owner, spender, amount); + } } /** @@ -336,7 +359,7 @@ abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors { revert ERC20InsufficientAllowance(spender, currentAllowance, amount); } unchecked { - _approve(owner, spender, currentAllowance - amount); + _approve(owner, spender, currentAllowance - amount, false); } } } diff --git a/test/token/ERC20/ERC20.behavior.js b/test/token/ERC20/ERC20.behavior.js index 7f547b112..bb2efda89 100644 --- a/test/token/ERC20/ERC20.behavior.js +++ b/test/token/ERC20/ERC20.behavior.js @@ -4,7 +4,10 @@ const { ZERO_ADDRESS, MAX_UINT256 } = constants; const { expectRevertCustomError } = require('../../helpers/customError'); -function shouldBehaveLikeERC20(initialSupply, initialHolder, recipient, anotherAccount) { +function shouldBehaveLikeERC20(initialSupply, accounts, opts = {}) { + const [initialHolder, recipient, anotherAccount] = accounts; + const { forcedApproval } = opts; + describe('total supply', function () { it('returns the total amount of tokens', async function () { expect(await this.token.totalSupply()).to.be.bignumber.equal(initialSupply); @@ -70,13 +73,22 @@ function shouldBehaveLikeERC20(initialSupply, initialHolder, recipient, anotherA }); }); - it('emits an approval event', async function () { - expectEvent(await this.token.transferFrom(tokenOwner, to, amount, { from: spender }), 'Approval', { - owner: tokenOwner, - spender: spender, - value: await this.token.allowance(tokenOwner, spender), + if (forcedApproval) { + it('emits an approval event', async function () { + expectEvent(await this.token.transferFrom(tokenOwner, to, amount, { from: spender }), 'Approval', { + owner: tokenOwner, + spender: spender, + value: await this.token.allowance(tokenOwner, spender), + }); }); - }); + } else { + it('does not emit an approval event', async function () { + expectEvent.notEmitted( + await this.token.transferFrom(tokenOwner, to, amount, { from: spender }), + 'Approval', + ); + }); + } }); describe('when the token owner does not have enough balance', function () { diff --git a/test/token/ERC20/ERC20.test.js b/test/token/ERC20/ERC20.test.js index 7b97c56f1..ef6d82f2b 100644 --- a/test/token/ERC20/ERC20.test.js +++ b/test/token/ERC20/ERC20.test.js @@ -9,357 +9,357 @@ const { } = require('./ERC20.behavior'); const { expectRevertCustomError } = require('../../helpers/customError'); -const ERC20 = artifacts.require('$ERC20'); -const ERC20Decimals = artifacts.require('$ERC20DecimalsMock'); +const TOKENS = [ + { Token: artifacts.require('$ERC20') }, + { Token: artifacts.require('$ERC20ApprovalMock'), forcedApproval: true }, +]; contract('ERC20', function (accounts) { - const [initialHolder, recipient, anotherAccount] = accounts; + const [initialHolder, recipient] = accounts; const name = 'My Token'; const symbol = 'MTKN'; - const initialSupply = new BN(100); - beforeEach(async function () { - this.token = await ERC20.new(name, symbol); - await this.token.$_mint(initialHolder, initialSupply); - }); - - it('has a name', async function () { - expect(await this.token.name()).to.equal(name); - }); - - it('has a symbol', async function () { - expect(await this.token.symbol()).to.equal(symbol); - }); - - it('has 18 decimals', async function () { - expect(await this.token.decimals()).to.be.bignumber.equal('18'); - }); - - describe('set decimals', function () { - const decimals = new BN(6); - - it('can set decimals during construction', async function () { - const token = await ERC20Decimals.new(name, symbol, decimals); - expect(await token.decimals()).to.be.bignumber.equal(decimals); - }); - }); - - shouldBehaveLikeERC20(initialSupply, initialHolder, recipient, anotherAccount); + for (const { Token, forcedApproval } of TOKENS) { + describe(`using ${Token._json.contractName}`, function () { + beforeEach(async function () { + this.token = await Token.new(name, symbol); + await this.token.$_mint(initialHolder, initialSupply); + }); - describe('decrease allowance', function () { - describe('when the spender is not the zero address', function () { - const spender = recipient; + shouldBehaveLikeERC20(initialSupply, accounts, { forcedApproval }); - function shouldDecreaseApproval(amount) { - describe('when there was no approved amount before', function () { - it('reverts', async function () { - const allowance = await this.token.allowance(initialHolder, spender); - await expectRevertCustomError( - this.token.decreaseAllowance(spender, amount, { from: initialHolder }), - 'ERC20FailedDecreaseAllowance', - [spender, allowance, amount], - ); - }); - }); + it('has a name', async function () { + expect(await this.token.name()).to.equal(name); + }); - describe('when the spender had an approved amount', function () { - const approvedAmount = amount; + it('has a symbol', async function () { + expect(await this.token.symbol()).to.equal(symbol); + }); - beforeEach(async function () { - await this.token.approve(spender, approvedAmount, { from: initialHolder }); - }); + it('has 18 decimals', async function () { + expect(await this.token.decimals()).to.be.bignumber.equal('18'); + }); - it('emits an approval event', async function () { - expectEvent( - await this.token.decreaseAllowance(spender, approvedAmount, { from: initialHolder }), - 'Approval', - { owner: initialHolder, spender: spender, value: new BN(0) }, - ); + describe('decrease allowance', function () { + describe('when the spender is not the zero address', function () { + const spender = recipient; + + function shouldDecreaseApproval(amount) { + describe('when there was no approved amount before', function () { + it('reverts', async function () { + const allowance = await this.token.allowance(initialHolder, spender); + await expectRevertCustomError( + this.token.decreaseAllowance(spender, amount, { from: initialHolder }), + 'ERC20FailedDecreaseAllowance', + [spender, allowance, amount], + ); + }); + }); + + describe('when the spender had an approved amount', function () { + const approvedAmount = amount; + + beforeEach(async function () { + await this.token.approve(spender, approvedAmount, { from: initialHolder }); + }); + + it('emits an approval event', async function () { + expectEvent( + await this.token.decreaseAllowance(spender, approvedAmount, { from: initialHolder }), + 'Approval', + { owner: initialHolder, spender: spender, value: new BN(0) }, + ); + }); + + it('decreases the spender allowance subtracting the requested amount', async function () { + await this.token.decreaseAllowance(spender, approvedAmount.subn(1), { from: initialHolder }); + + expect(await this.token.allowance(initialHolder, spender)).to.be.bignumber.equal('1'); + }); + + it('sets the allowance to zero when all allowance is removed', async function () { + await this.token.decreaseAllowance(spender, approvedAmount, { from: initialHolder }); + expect(await this.token.allowance(initialHolder, spender)).to.be.bignumber.equal('0'); + }); + + it('reverts when more than the full allowance is removed', async function () { + await expectRevertCustomError( + this.token.decreaseAllowance(spender, approvedAmount.addn(1), { from: initialHolder }), + 'ERC20FailedDecreaseAllowance', + [spender, approvedAmount, approvedAmount.addn(1)], + ); + }); + }); + } + + describe('when the sender has enough balance', function () { + const amount = initialSupply; + + shouldDecreaseApproval(amount); }); - it('decreases the spender allowance subtracting the requested amount', async function () { - await this.token.decreaseAllowance(spender, approvedAmount.subn(1), { from: initialHolder }); + describe('when the sender does not have enough balance', function () { + const amount = initialSupply.addn(1); - expect(await this.token.allowance(initialHolder, spender)).to.be.bignumber.equal('1'); + shouldDecreaseApproval(amount); }); + }); - it('sets the allowance to zero when all allowance is removed', async function () { - await this.token.decreaseAllowance(spender, approvedAmount, { from: initialHolder }); - expect(await this.token.allowance(initialHolder, spender)).to.be.bignumber.equal('0'); - }); + describe('when the spender is the zero address', function () { + const amount = initialSupply; + const spender = ZERO_ADDRESS; - it('reverts when more than the full allowance is removed', async function () { + it('reverts', async function () { await expectRevertCustomError( - this.token.decreaseAllowance(spender, approvedAmount.addn(1), { from: initialHolder }), + this.token.decreaseAllowance(spender, amount, { from: initialHolder }), 'ERC20FailedDecreaseAllowance', - [spender, approvedAmount, approvedAmount.addn(1)], + [spender, 0, amount], ); }); }); - } - - describe('when the sender has enough balance', function () { - const amount = initialSupply; - - shouldDecreaseApproval(amount); }); - describe('when the sender does not have enough balance', function () { - const amount = initialSupply.addn(1); + describe('increase allowance', function () { + const amount = initialSupply; - shouldDecreaseApproval(amount); - }); - }); + describe('when the spender is not the zero address', function () { + const spender = recipient; + + describe('when the sender has enough balance', function () { + it('emits an approval event', async function () { + expectEvent(await this.token.increaseAllowance(spender, amount, { from: initialHolder }), 'Approval', { + owner: initialHolder, + spender: spender, + value: amount, + }); + }); + + describe('when there was no approved amount before', function () { + it('approves the requested amount', async function () { + await this.token.increaseAllowance(spender, amount, { from: initialHolder }); + + expect(await this.token.allowance(initialHolder, spender)).to.be.bignumber.equal(amount); + }); + }); + + describe('when the spender had an approved amount', function () { + beforeEach(async function () { + await this.token.approve(spender, new BN(1), { from: initialHolder }); + }); + + it('increases the spender allowance adding the requested amount', async function () { + await this.token.increaseAllowance(spender, amount, { from: initialHolder }); + + expect(await this.token.allowance(initialHolder, spender)).to.be.bignumber.equal(amount.addn(1)); + }); + }); + }); - describe('when the spender is the zero address', function () { - const amount = initialSupply; - const spender = ZERO_ADDRESS; + describe('when the sender does not have enough balance', function () { + const amount = initialSupply.addn(1); - it('reverts', async function () { - await expectRevertCustomError( - this.token.decreaseAllowance(spender, amount, { from: initialHolder }), - 'ERC20FailedDecreaseAllowance', - [spender, 0, amount], - ); - }); - }); - }); + it('emits an approval event', async function () { + expectEvent(await this.token.increaseAllowance(spender, amount, { from: initialHolder }), 'Approval', { + owner: initialHolder, + spender: spender, + value: amount, + }); + }); - describe('increase allowance', function () { - const amount = initialSupply; + describe('when there was no approved amount before', function () { + it('approves the requested amount', async function () { + await this.token.increaseAllowance(spender, amount, { from: initialHolder }); - describe('when the spender is not the zero address', function () { - const spender = recipient; + expect(await this.token.allowance(initialHolder, spender)).to.be.bignumber.equal(amount); + }); + }); - describe('when the sender has enough balance', function () { - it('emits an approval event', async function () { - expectEvent(await this.token.increaseAllowance(spender, amount, { from: initialHolder }), 'Approval', { - owner: initialHolder, - spender: spender, - value: amount, - }); - }); + describe('when the spender had an approved amount', function () { + beforeEach(async function () { + await this.token.approve(spender, new BN(1), { from: initialHolder }); + }); - describe('when there was no approved amount before', function () { - it('approves the requested amount', async function () { - await this.token.increaseAllowance(spender, amount, { from: initialHolder }); + it('increases the spender allowance adding the requested amount', async function () { + await this.token.increaseAllowance(spender, amount, { from: initialHolder }); - expect(await this.token.allowance(initialHolder, spender)).to.be.bignumber.equal(amount); + expect(await this.token.allowance(initialHolder, spender)).to.be.bignumber.equal(amount.addn(1)); + }); + }); }); }); - describe('when the spender had an approved amount', function () { - beforeEach(async function () { - await this.token.approve(spender, new BN(1), { from: initialHolder }); - }); + describe('when the spender is the zero address', function () { + const spender = ZERO_ADDRESS; - it('increases the spender allowance adding the requested amount', async function () { - await this.token.increaseAllowance(spender, amount, { from: initialHolder }); - - expect(await this.token.allowance(initialHolder, spender)).to.be.bignumber.equal(amount.addn(1)); + it('reverts', async function () { + await expectRevertCustomError( + this.token.increaseAllowance(spender, amount, { from: initialHolder }), + 'ERC20InvalidSpender', + [ZERO_ADDRESS], + ); }); }); }); - describe('when the sender does not have enough balance', function () { - const amount = initialSupply.addn(1); + describe('_mint', function () { + const amount = new BN(50); + it('rejects a null account', async function () { + await expectRevertCustomError(this.token.$_mint(ZERO_ADDRESS, amount), 'ERC20InvalidReceiver', [ + ZERO_ADDRESS, + ]); + }); - it('emits an approval event', async function () { - expectEvent(await this.token.increaseAllowance(spender, amount, { from: initialHolder }), 'Approval', { - owner: initialHolder, - spender: spender, - value: amount, - }); + it('rejects overflow', async function () { + const maxUint256 = new BN('2').pow(new BN(256)).subn(1); + await expectRevert( + this.token.$_mint(recipient, maxUint256), + 'reverted with panic code 0x11 (Arithmetic operation underflowed or overflowed outside of an unchecked block)', + ); }); - describe('when there was no approved amount before', function () { - it('approves the requested amount', async function () { - await this.token.increaseAllowance(spender, amount, { from: initialHolder }); + describe('for a non zero account', function () { + beforeEach('minting', async function () { + this.receipt = await this.token.$_mint(recipient, amount); + }); - expect(await this.token.allowance(initialHolder, spender)).to.be.bignumber.equal(amount); + it('increments totalSupply', async function () { + const expectedSupply = initialSupply.add(amount); + expect(await this.token.totalSupply()).to.be.bignumber.equal(expectedSupply); }); - }); - describe('when the spender had an approved amount', function () { - beforeEach(async function () { - await this.token.approve(spender, new BN(1), { from: initialHolder }); + it('increments recipient balance', async function () { + expect(await this.token.balanceOf(recipient)).to.be.bignumber.equal(amount); }); - it('increases the spender allowance adding the requested amount', async function () { - await this.token.increaseAllowance(spender, amount, { from: initialHolder }); + it('emits Transfer event', async function () { + const event = expectEvent(this.receipt, 'Transfer', { from: ZERO_ADDRESS, to: recipient }); - expect(await this.token.allowance(initialHolder, spender)).to.be.bignumber.equal(amount.addn(1)); + expect(event.args.value).to.be.bignumber.equal(amount); }); }); }); - }); - describe('when the spender is the zero address', function () { - const spender = ZERO_ADDRESS; - - it('reverts', async function () { - await expectRevertCustomError( - this.token.increaseAllowance(spender, amount, { from: initialHolder }), - 'ERC20InvalidSpender', - [ZERO_ADDRESS], - ); - }); - }); - }); - - describe('_mint', function () { - const amount = new BN(50); - it('rejects a null account', async function () { - await expectRevertCustomError(this.token.$_mint(ZERO_ADDRESS, amount), 'ERC20InvalidReceiver', [ZERO_ADDRESS]); - }); - - it('rejects overflow', async function () { - const maxUint256 = new BN('2').pow(new BN(256)).subn(1); - await expectRevert( - this.token.$_mint(recipient, maxUint256), - 'reverted with panic code 0x11 (Arithmetic operation underflowed or overflowed outside of an unchecked block)', - ); - }); + describe('_burn', function () { + it('rejects a null account', async function () { + await expectRevertCustomError(this.token.$_burn(ZERO_ADDRESS, new BN(1)), 'ERC20InvalidSender', [ + ZERO_ADDRESS, + ]); + }); - describe('for a non zero account', function () { - beforeEach('minting', async function () { - this.receipt = await this.token.$_mint(recipient, amount); - }); + describe('for a non zero account', function () { + it('rejects burning more than balance', async function () { + await expectRevertCustomError( + this.token.$_burn(initialHolder, initialSupply.addn(1)), + 'ERC20InsufficientBalance', + [initialHolder, initialSupply, initialSupply.addn(1)], + ); + }); - it('increments totalSupply', async function () { - const expectedSupply = initialSupply.add(amount); - expect(await this.token.totalSupply()).to.be.bignumber.equal(expectedSupply); - }); + const describeBurn = function (description, amount) { + describe(description, function () { + beforeEach('burning', async function () { + this.receipt = await this.token.$_burn(initialHolder, amount); + }); - it('increments recipient balance', async function () { - expect(await this.token.balanceOf(recipient)).to.be.bignumber.equal(amount); - }); + it('decrements totalSupply', async function () { + const expectedSupply = initialSupply.sub(amount); + expect(await this.token.totalSupply()).to.be.bignumber.equal(expectedSupply); + }); - it('emits Transfer event', async function () { - const event = expectEvent(this.receipt, 'Transfer', { from: ZERO_ADDRESS, to: recipient }); + it('decrements initialHolder balance', async function () { + const expectedBalance = initialSupply.sub(amount); + expect(await this.token.balanceOf(initialHolder)).to.be.bignumber.equal(expectedBalance); + }); - expect(event.args.value).to.be.bignumber.equal(amount); - }); - }); - }); + it('emits Transfer event', async function () { + const event = expectEvent(this.receipt, 'Transfer', { from: initialHolder, to: ZERO_ADDRESS }); - describe('_burn', function () { - it('rejects a null account', async function () { - await expectRevertCustomError(this.token.$_burn(ZERO_ADDRESS, new BN(1)), 'ERC20InvalidSender', [ZERO_ADDRESS]); - }); + expect(event.args.value).to.be.bignumber.equal(amount); + }); + }); + }; - describe('for a non zero account', function () { - it('rejects burning more than balance', async function () { - await expectRevertCustomError( - this.token.$_burn(initialHolder, initialSupply.addn(1)), - 'ERC20InsufficientBalance', - [initialHolder, initialSupply, initialSupply.addn(1)], - ); + describeBurn('for entire balance', initialSupply); + describeBurn('for less amount than balance', initialSupply.subn(1)); + }); }); - const describeBurn = function (description, amount) { - describe(description, function () { - beforeEach('burning', async function () { - this.receipt = await this.token.$_burn(initialHolder, amount); - }); + describe('_update', function () { + const amount = new BN(1); - it('decrements totalSupply', async function () { - const expectedSupply = initialSupply.sub(amount); - expect(await this.token.totalSupply()).to.be.bignumber.equal(expectedSupply); - }); + it('from is the zero address', async function () { + const balanceBefore = await this.token.balanceOf(initialHolder); + const totalSupply = await this.token.totalSupply(); - it('decrements initialHolder balance', async function () { - const expectedBalance = initialSupply.sub(amount); - expect(await this.token.balanceOf(initialHolder)).to.be.bignumber.equal(expectedBalance); + expectEvent(await this.token.$_update(ZERO_ADDRESS, initialHolder, amount), 'Transfer', { + from: ZERO_ADDRESS, + to: initialHolder, + value: amount, }); + expect(await this.token.totalSupply()).to.be.bignumber.equal(totalSupply.add(amount)); + expect(await this.token.balanceOf(initialHolder)).to.be.bignumber.equal(balanceBefore.add(amount)); + }); - it('emits Transfer event', async function () { - const event = expectEvent(this.receipt, 'Transfer', { from: initialHolder, to: ZERO_ADDRESS }); + it('to is the zero address', async function () { + const balanceBefore = await this.token.balanceOf(initialHolder); + const totalSupply = await this.token.totalSupply(); - expect(event.args.value).to.be.bignumber.equal(amount); + expectEvent(await this.token.$_update(initialHolder, ZERO_ADDRESS, amount), 'Transfer', { + from: initialHolder, + to: ZERO_ADDRESS, + value: amount, }); + expect(await this.token.totalSupply()).to.be.bignumber.equal(totalSupply.sub(amount)); + expect(await this.token.balanceOf(initialHolder)).to.be.bignumber.equal(balanceBefore.sub(amount)); }); - }; - - describeBurn('for entire balance', initialSupply); - describeBurn('for less amount than balance', initialSupply.subn(1)); - }); - }); - describe('_update', function () { - const amount = new BN(1); + it('from and to are the zero address', async function () { + const totalSupply = await this.token.totalSupply(); - it('from is the zero address', async function () { - const balanceBefore = await this.token.balanceOf(initialHolder); - const totalSupply = await this.token.totalSupply(); + await this.token.$_update(ZERO_ADDRESS, ZERO_ADDRESS, amount); - expectEvent(await this.token.$_update(ZERO_ADDRESS, initialHolder, amount), 'Transfer', { - from: ZERO_ADDRESS, - to: initialHolder, - value: amount, - }); - expect(await this.token.totalSupply()).to.be.bignumber.equal(totalSupply.add(amount)); - expect(await this.token.balanceOf(initialHolder)).to.be.bignumber.equal(balanceBefore.add(amount)); - }); - - it('to is the zero address', async function () { - const balanceBefore = await this.token.balanceOf(initialHolder); - const totalSupply = await this.token.totalSupply(); - - expectEvent(await this.token.$_update(initialHolder, ZERO_ADDRESS, amount), 'Transfer', { - from: initialHolder, - to: ZERO_ADDRESS, - value: amount, - }); - expect(await this.token.totalSupply()).to.be.bignumber.equal(totalSupply.sub(amount)); - expect(await this.token.balanceOf(initialHolder)).to.be.bignumber.equal(balanceBefore.sub(amount)); - }); - - it('from and to are the zero address', async function () { - const totalSupply = await this.token.totalSupply(); - - await this.token.$_update(ZERO_ADDRESS, ZERO_ADDRESS, amount); - - expect(await this.token.totalSupply()).to.be.bignumber.equal(totalSupply); - expectEvent(await this.token.$_update(ZERO_ADDRESS, ZERO_ADDRESS, amount), 'Transfer', { - from: ZERO_ADDRESS, - to: ZERO_ADDRESS, - value: amount, + expect(await this.token.totalSupply()).to.be.bignumber.equal(totalSupply); + expectEvent(await this.token.$_update(ZERO_ADDRESS, ZERO_ADDRESS, amount), 'Transfer', { + from: ZERO_ADDRESS, + to: ZERO_ADDRESS, + value: amount, + }); + }); }); - }); - }); - describe('_transfer', function () { - shouldBehaveLikeERC20Transfer(initialHolder, recipient, initialSupply, function (from, to, amount) { - return this.token.$_transfer(from, to, amount); - }); + describe('_transfer', function () { + shouldBehaveLikeERC20Transfer(initialHolder, recipient, initialSupply, function (from, to, amount) { + return this.token.$_transfer(from, to, amount); + }); - describe('when the sender is the zero address', function () { - it('reverts', async function () { - await expectRevertCustomError( - this.token.$_transfer(ZERO_ADDRESS, recipient, initialSupply), - 'ERC20InvalidSender', - [ZERO_ADDRESS], - ); + describe('when the sender is the zero address', function () { + it('reverts', async function () { + await expectRevertCustomError( + this.token.$_transfer(ZERO_ADDRESS, recipient, initialSupply), + 'ERC20InvalidSender', + [ZERO_ADDRESS], + ); + }); + }); }); - }); - }); - describe('_approve', function () { - shouldBehaveLikeERC20Approve(initialHolder, recipient, initialSupply, function (owner, spender, amount) { - return this.token.$_approve(owner, spender, amount); - }); + describe('_approve', function () { + shouldBehaveLikeERC20Approve(initialHolder, recipient, initialSupply, function (owner, spender, amount) { + return this.token.$_approve(owner, spender, amount); + }); - describe('when the owner is the zero address', function () { - it('reverts', async function () { - await expectRevertCustomError( - this.token.$_approve(ZERO_ADDRESS, recipient, initialSupply), - 'ERC20InvalidApprover', - [ZERO_ADDRESS], - ); + describe('when the owner is the zero address', function () { + it('reverts', async function () { + await expectRevertCustomError( + this.token.$_approve(ZERO_ADDRESS, recipient, initialSupply), + 'ERC20InvalidApprover', + [ZERO_ADDRESS], + ); + }); + }); }); }); - }); + } }); diff --git a/test/token/ERC20/extensions/ERC20Wrapper.test.js b/test/token/ERC20/extensions/ERC20Wrapper.test.js index ffb97e8a2..94415d088 100644 --- a/test/token/ERC20/extensions/ERC20Wrapper.test.js +++ b/test/token/ERC20/extensions/ERC20Wrapper.test.js @@ -10,7 +10,7 @@ const ERC20Decimals = artifacts.require('$ERC20DecimalsMock'); const ERC20Wrapper = artifacts.require('$ERC20Wrapper'); contract('ERC20Wrapper', function (accounts) { - const [initialHolder, recipient, anotherAccount] = accounts; + const [initialHolder, receiver] = accounts; const name = 'My Token'; const symbol = 'MTKN'; @@ -85,7 +85,7 @@ contract('ERC20Wrapper', function (accounts) { it('to other account', async function () { await this.underlying.approve(this.token.address, initialSupply, { from: initialHolder }); - const { tx } = await this.token.depositFor(anotherAccount, initialSupply, { from: initialHolder }); + const { tx } = await this.token.depositFor(receiver, initialSupply, { from: initialHolder }); await expectEvent.inTransaction(tx, this.underlying, 'Transfer', { from: initialHolder, to: this.token.address, @@ -93,7 +93,7 @@ contract('ERC20Wrapper', function (accounts) { }); await expectEvent.inTransaction(tx, this.token, 'Transfer', { from: ZERO_ADDRESS, - to: anotherAccount, + to: receiver, value: initialSupply, }); }); @@ -144,10 +144,10 @@ contract('ERC20Wrapper', function (accounts) { }); it('to other account', async function () { - const { tx } = await this.token.withdrawTo(anotherAccount, initialSupply, { from: initialHolder }); + const { tx } = await this.token.withdrawTo(receiver, initialSupply, { from: initialHolder }); await expectEvent.inTransaction(tx, this.underlying, 'Transfer', { from: this.token.address, - to: anotherAccount, + to: receiver, value: initialSupply, }); await expectEvent.inTransaction(tx, this.token, 'Transfer', { @@ -163,10 +163,10 @@ contract('ERC20Wrapper', function (accounts) { await this.underlying.approve(this.token.address, initialSupply, { from: initialHolder }); await this.token.depositFor(initialHolder, initialSupply, { from: initialHolder }); - const { tx } = await this.token.$_recover(anotherAccount); + const { tx } = await this.token.$_recover(receiver); await expectEvent.inTransaction(tx, this.token, 'Transfer', { from: ZERO_ADDRESS, - to: anotherAccount, + to: receiver, value: '0', }); }); @@ -174,10 +174,10 @@ contract('ERC20Wrapper', function (accounts) { it('something to recover', async function () { await this.underlying.transfer(this.token.address, initialSupply, { from: initialHolder }); - const { tx } = await this.token.$_recover(anotherAccount); + const { tx } = await this.token.$_recover(receiver); await expectEvent.inTransaction(tx, this.token, 'Transfer', { from: ZERO_ADDRESS, - to: anotherAccount, + to: receiver, value: initialSupply, }); }); @@ -189,6 +189,6 @@ contract('ERC20Wrapper', function (accounts) { await this.token.depositFor(initialHolder, initialSupply, { from: initialHolder }); }); - shouldBehaveLikeERC20(initialSupply, initialHolder, recipient, anotherAccount); + shouldBehaveLikeERC20(initialSupply, accounts); }); }); From a7a94c77463acea95d979aae1580fb0ddc3b6a1e Mon Sep 17 00:00:00 2001 From: Sergei Tikhomirov Date: Fri, 23 Jun 2023 00:34:53 +0200 Subject: [PATCH 121/182] Update comment to reflect code logic in Ownable.sol (#4369) --- contracts/access/Ownable.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/access/Ownable.sol b/contracts/access/Ownable.sol index f4394378b..627226cf7 100644 --- a/contracts/access/Ownable.sol +++ b/contracts/access/Ownable.sol @@ -10,8 +10,8 @@ import "../utils/Context.sol"; * there is an account (an owner) that can be granted exclusive access to * specific functions. * - * By default, the owner account will be the one that deploys the contract. This - * can later be changed with {transferOwnership}. + * The initial owner is set to the address provided by the deployer. This can + * later be changed with {transferOwnership}. * * This module is used through inheritance. It will make available the modifier * `onlyOwner`, which can be applied to your functions to restrict their use to @@ -33,7 +33,7 @@ abstract contract Ownable is Context { event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); /** - * @dev Initializes the contract setting the deployer as the initial owner. + * @dev Initializes the contract setting the address provided by the deployer as the initial owner. */ constructor(address initialOwner) { _transferOwnership(initialOwner); From da89c438f1fced499308713e4ad155f8bfae932a Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Fri, 23 Jun 2023 18:05:22 +0200 Subject: [PATCH 122/182] Pack Governor's ProposalCore into a single slot (#4268) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ernesto García Co-authored-by: Francisco --- .changeset/grumpy-bulldogs-call.md | 5 ++++ contracts/governance/Governor.sol | 30 ++++++++----------- contracts/governance/IGovernor.sol | 7 +++++ .../extensions/GovernorPreventLateQuorum.sol | 14 ++++----- .../extensions/GovernorSettings.sol | 17 ++++++----- .../extensions/GovernorTimelockCompound.sol | 4 +-- 6 files changed, 43 insertions(+), 34 deletions(-) create mode 100644 .changeset/grumpy-bulldogs-call.md diff --git a/.changeset/grumpy-bulldogs-call.md b/.changeset/grumpy-bulldogs-call.md new file mode 100644 index 000000000..c034587f3 --- /dev/null +++ b/.changeset/grumpy-bulldogs-call.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': major +--- + +`Governor`: Optimized use of storage for proposal data diff --git a/contracts/governance/Governor.sol b/contracts/governance/Governor.sol index a8654ac35..bae5d62e6 100644 --- a/contracts/governance/Governor.sol +++ b/contracts/governance/Governor.sol @@ -34,14 +34,9 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor, IERC721Receive // solhint-disable var-name-mixedcase struct ProposalCore { - // --- start retyped from Timers.BlockNumber at offset 0x00 --- - uint64 voteStart; address proposer; - bytes4 __gap_unused0; - // --- start retyped from Timers.BlockNumber at offset 0x20 --- - uint64 voteEnd; - bytes24 __gap_unused1; - // --- Remaining fields starting at offset 0x40 --------------- + uint48 voteStart; + uint32 voteDuration; bool executed; bool canceled; } @@ -166,7 +161,9 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor, IERC721Receive * @dev See {IGovernor-state}. */ function state(uint256 proposalId) public view virtual override returns (ProposalState) { - ProposalCore storage proposal = _proposals[proposalId]; + // ProposalCore is just one slot. We can load it from storage to memory with a single sload and use memory + // object as a cache. This avoid duplicating expensive sloads. + ProposalCore memory proposal = _proposals[proposalId]; if (proposal.executed) { return ProposalState.Executed; @@ -219,7 +216,7 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor, IERC721Receive * @dev See {IGovernor-proposalDeadline}. */ function proposalDeadline(uint256 proposalId) public view virtual override returns (uint256) { - return _proposals[proposalId].voteEnd; + return _proposals[proposalId].voteStart + _proposals[proposalId].voteDuration; } /** @@ -300,16 +297,14 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor, IERC721Receive } uint256 snapshot = currentTimepoint + votingDelay(); - uint256 deadline = snapshot + votingPeriod(); + uint256 duration = votingPeriod(); _proposals[proposalId] = ProposalCore({ proposer: proposer, - voteStart: SafeCast.toUint64(snapshot), - voteEnd: SafeCast.toUint64(deadline), + voteStart: SafeCast.toUint48(snapshot), + voteDuration: SafeCast.toUint32(duration), executed: false, - canceled: false, - __gap_unused0: 0, - __gap_unused1: 0 + canceled: false }); emit ProposalCreated( @@ -320,7 +315,7 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor, IERC721Receive new string[](targets.length), calldatas, snapshot, - deadline, + snapshot + duration, description ); @@ -592,13 +587,12 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor, IERC721Receive string memory reason, bytes memory params ) internal virtual returns (uint256) { - ProposalCore storage proposal = _proposals[proposalId]; ProposalState currentState = state(proposalId); if (currentState != ProposalState.Active) { revert GovernorUnexpectedProposalState(proposalId, currentState, _encodeStateBitmap(ProposalState.Active)); } - uint256 weight = _getVotes(account, proposal.voteStart, params); + uint256 weight = _getVotes(account, proposalSnapshot(proposalId), params); _countVote(proposalId, account, support, weight, params); if (params.length == 0) { diff --git a/contracts/governance/IGovernor.sol b/contracts/governance/IGovernor.sol index 5b51f088a..0adde6795 100644 --- a/contracts/governance/IGovernor.sol +++ b/contracts/governance/IGovernor.sol @@ -222,6 +222,9 @@ abstract contract IGovernor is IERC165, IERC6372 { * * This can be increased to leave time for users to buy voting power, or delegate it, before the voting of a * proposal starts. + * + * NOTE: While this interface returns a uint256, timepoints are stored as uint48 following the ERC-6372 clock type. + * Consequently this value must fit in a uint48 (when added to the current clock). See {IERC6372-clock}. */ function votingDelay() public view virtual returns (uint256); @@ -232,6 +235,10 @@ abstract contract IGovernor is IERC165, IERC6372 { * * NOTE: The {votingDelay} can delay the start of the vote. This must be considered when setting the voting * duration compared to the voting delay. + * + * NOTE: This value is stored when the proposal is submitted so that possible changes to the value do not affect + * proposals that have already been submitted. The type used to save it is a uint32. Consequently, while this + * interface returns a uint256, the value it returns should fit in a uint32. */ function votingPeriod() public view virtual returns (uint256); diff --git a/contracts/governance/extensions/GovernorPreventLateQuorum.sol b/contracts/governance/extensions/GovernorPreventLateQuorum.sol index abb81128e..3e730174e 100644 --- a/contracts/governance/extensions/GovernorPreventLateQuorum.sol +++ b/contracts/governance/extensions/GovernorPreventLateQuorum.sol @@ -18,10 +18,10 @@ import "../../utils/math/Math.sol"; * _Available since v4.5._ */ abstract contract GovernorPreventLateQuorum is Governor { - uint64 private _voteExtension; + uint48 private _voteExtension; /// @custom:oz-retyped-from mapping(uint256 => Timers.BlockNumber) - mapping(uint256 => uint64) private _extendedDeadlines; + mapping(uint256 => uint48) private _extendedDeadlines; /// @dev Emitted when a proposal deadline is pushed back due to reaching quorum late in its voting period. event ProposalExtended(uint256 indexed proposalId, uint64 extendedDeadline); @@ -34,7 +34,7 @@ abstract contract GovernorPreventLateQuorum is Governor { * clock mode) that is required to pass since the moment a proposal reaches quorum until its voting period ends. If * necessary the voting period will be extended beyond the one set during proposal creation. */ - constructor(uint64 initialVoteExtension) { + constructor(uint48 initialVoteExtension) { _setLateQuorumVoteExtension(initialVoteExtension); } @@ -62,7 +62,7 @@ abstract contract GovernorPreventLateQuorum is Governor { uint256 result = super._castVote(proposalId, account, support, reason, params); if (_extendedDeadlines[proposalId] == 0 && _quorumReached(proposalId)) { - uint64 extendedDeadline = clock() + lateQuorumVoteExtension(); + uint48 extendedDeadline = clock() + lateQuorumVoteExtension(); if (extendedDeadline > proposalDeadline(proposalId)) { emit ProposalExtended(proposalId, extendedDeadline); @@ -78,7 +78,7 @@ abstract contract GovernorPreventLateQuorum is Governor { * @dev Returns the current value of the vote extension parameter: the number of blocks that are required to pass * from the time a proposal reaches quorum until its voting period ends. */ - function lateQuorumVoteExtension() public view virtual returns (uint64) { + function lateQuorumVoteExtension() public view virtual returns (uint48) { return _voteExtension; } @@ -88,7 +88,7 @@ abstract contract GovernorPreventLateQuorum is Governor { * * Emits a {LateQuorumVoteExtensionSet} event. */ - function setLateQuorumVoteExtension(uint64 newVoteExtension) public virtual onlyGovernance { + function setLateQuorumVoteExtension(uint48 newVoteExtension) public virtual onlyGovernance { _setLateQuorumVoteExtension(newVoteExtension); } @@ -98,7 +98,7 @@ abstract contract GovernorPreventLateQuorum is Governor { * * Emits a {LateQuorumVoteExtensionSet} event. */ - function _setLateQuorumVoteExtension(uint64 newVoteExtension) internal virtual { + function _setLateQuorumVoteExtension(uint48 newVoteExtension) internal virtual { emit LateQuorumVoteExtensionSet(_voteExtension, newVoteExtension); _voteExtension = newVoteExtension; } diff --git a/contracts/governance/extensions/GovernorSettings.sol b/contracts/governance/extensions/GovernorSettings.sol index 64e081cc5..6168689ad 100644 --- a/contracts/governance/extensions/GovernorSettings.sol +++ b/contracts/governance/extensions/GovernorSettings.sol @@ -11,9 +11,12 @@ import "../Governor.sol"; * _Available since v4.4._ */ abstract contract GovernorSettings is Governor { - uint256 private _votingDelay; - uint256 private _votingPeriod; + // amount of token uint256 private _proposalThreshold; + // timepoint: limited to uint48 in core (same as clock() type) + uint48 private _votingDelay; + // duration: limited to uint32 in core + uint32 private _votingPeriod; event VotingDelaySet(uint256 oldVotingDelay, uint256 newVotingDelay); event VotingPeriodSet(uint256 oldVotingPeriod, uint256 newVotingPeriod); @@ -22,7 +25,7 @@ abstract contract GovernorSettings is Governor { /** * @dev Initialize the governance parameters. */ - constructor(uint256 initialVotingDelay, uint256 initialVotingPeriod, uint256 initialProposalThreshold) { + constructor(uint48 initialVotingDelay, uint32 initialVotingPeriod, uint256 initialProposalThreshold) { _setVotingDelay(initialVotingDelay); _setVotingPeriod(initialVotingPeriod); _setProposalThreshold(initialProposalThreshold); @@ -54,7 +57,7 @@ abstract contract GovernorSettings is Governor { * * Emits a {VotingDelaySet} event. */ - function setVotingDelay(uint256 newVotingDelay) public virtual onlyGovernance { + function setVotingDelay(uint48 newVotingDelay) public virtual onlyGovernance { _setVotingDelay(newVotingDelay); } @@ -63,7 +66,7 @@ abstract contract GovernorSettings is Governor { * * Emits a {VotingPeriodSet} event. */ - function setVotingPeriod(uint256 newVotingPeriod) public virtual onlyGovernance { + function setVotingPeriod(uint32 newVotingPeriod) public virtual onlyGovernance { _setVotingPeriod(newVotingPeriod); } @@ -81,7 +84,7 @@ abstract contract GovernorSettings is Governor { * * Emits a {VotingDelaySet} event. */ - function _setVotingDelay(uint256 newVotingDelay) internal virtual { + function _setVotingDelay(uint48 newVotingDelay) internal virtual { emit VotingDelaySet(_votingDelay, newVotingDelay); _votingDelay = newVotingDelay; } @@ -91,7 +94,7 @@ abstract contract GovernorSettings is Governor { * * Emits a {VotingPeriodSet} event. */ - function _setVotingPeriod(uint256 newVotingPeriod) internal virtual { + function _setVotingPeriod(uint32 newVotingPeriod) internal virtual { // voting period must be at least one block long if (newVotingPeriod == 0) { revert GovernorInvalidVotingPeriod(0); diff --git a/contracts/governance/extensions/GovernorTimelockCompound.sol b/contracts/governance/extensions/GovernorTimelockCompound.sol index 21439b4db..ed4d916a6 100644 --- a/contracts/governance/extensions/GovernorTimelockCompound.sol +++ b/contracts/governance/extensions/GovernorTimelockCompound.sol @@ -24,7 +24,7 @@ abstract contract GovernorTimelockCompound is IGovernorTimelock, Governor { ICompoundTimelock private _timelock; /// @custom:oz-retyped-from mapping(uint256 => GovernorTimelockCompound.ProposalTimelock) - mapping(uint256 => uint64) private _proposalTimelocks; + mapping(uint256 => uint256) private _proposalTimelocks; /** * @dev Emitted when the timelock controller used for proposal execution is modified. @@ -100,7 +100,7 @@ abstract contract GovernorTimelockCompound is IGovernorTimelock, Governor { } uint256 eta = block.timestamp + _timelock.delay(); - _proposalTimelocks[proposalId] = SafeCast.toUint64(eta); + _proposalTimelocks[proposalId] = eta; for (uint256 i = 0; i < targets.length; ++i) { if (_timelock.queuedTransactions(keccak256(abi.encode(targets[i], values[i], "", calldatas[i], eta)))) { From c44c220254d5decbc06ecc5008d7e8884c11bbb9 Mon Sep 17 00:00:00 2001 From: Renan Souza Date: Fri, 23 Jun 2023 19:37:27 -0300 Subject: [PATCH 123/182] Update Ownable2Step docs (#4384) Co-authored-by: Francisco --- contracts/access/Ownable2Step.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/access/Ownable2Step.sol b/contracts/access/Ownable2Step.sol index 61005b7e3..f76eb2bba 100644 --- a/contracts/access/Ownable2Step.sol +++ b/contracts/access/Ownable2Step.sol @@ -10,7 +10,7 @@ import "./Ownable.sol"; * there is an account (an owner) that can be granted exclusive access to * specific functions. * - * By default, the owner account will be the one that deploys the contract. This + * The initial owner is specified at deployment time in the constructor for `Ownable`. This * can later be changed with {transferOwnership} and {acceptOwnership}. * * This module is used through inheritance. It will make available all functions From cb4bf950df5ae43356c4935b3900446f6dc20261 Mon Sep 17 00:00:00 2001 From: Francisco Date: Sat, 24 Jun 2023 00:23:25 -0300 Subject: [PATCH 124/182] Add unreleased disclaimer in readme --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bbe7502e6..2936877e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## Unreleased + +> **Warning** Version 5.0 is under active development and should not be used. Install the releases from npm or use the version tags in the repository. + ### Removals The following contracts, libraries and functions were removed: From 8cab922347e79732f6a532a75da5081ba7447a71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Mon, 26 Jun 2023 06:20:01 -0600 Subject: [PATCH 125/182] Rename `ERC1155InsufficientApprovalForAll` to `ERC1155MissingApprovalForAll` (#4381) --- contracts/interfaces/draft-IERC6093.sol | 2 +- contracts/token/ERC1155/ERC1155.sol | 4 ++-- contracts/token/ERC1155/extensions/ERC1155Burnable.sol | 4 ++-- test/token/ERC1155/ERC1155.behavior.js | 4 ++-- test/token/ERC1155/extensions/ERC1155Burnable.test.js | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/contracts/interfaces/draft-IERC6093.sol b/contracts/interfaces/draft-IERC6093.sol index cebda56a3..fbe31051a 100644 --- a/contracts/interfaces/draft-IERC6093.sol +++ b/contracts/interfaces/draft-IERC6093.sol @@ -138,7 +138,7 @@ interface IERC1155Errors { * @param operator Address that may be allowed to operate on tokens without being their owner. * @param owner Address of the current owner of a token. */ - error ERC1155InsufficientApprovalForAll(address operator, address owner); + error ERC1155MissingApprovalForAll(address operator, address owner); /** * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals. diff --git a/contracts/token/ERC1155/ERC1155.sol b/contracts/token/ERC1155/ERC1155.sol index 9129992f4..e4bd74826 100644 --- a/contracts/token/ERC1155/ERC1155.sol +++ b/contracts/token/ERC1155/ERC1155.sol @@ -116,7 +116,7 @@ abstract contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI, IER */ function safeTransferFrom(address from, address to, uint256 id, uint256 amount, bytes memory data) public virtual { if (from != _msgSender() && !isApprovedForAll(from, _msgSender())) { - revert ERC1155InsufficientApprovalForAll(_msgSender(), from); + revert ERC1155MissingApprovalForAll(_msgSender(), from); } _safeTransferFrom(from, to, id, amount, data); } @@ -132,7 +132,7 @@ abstract contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI, IER bytes memory data ) public virtual { if (from != _msgSender() && !isApprovedForAll(from, _msgSender())) { - revert ERC1155InsufficientApprovalForAll(_msgSender(), from); + revert ERC1155MissingApprovalForAll(_msgSender(), from); } _safeBatchTransferFrom(from, to, ids, amounts, data); } diff --git a/contracts/token/ERC1155/extensions/ERC1155Burnable.sol b/contracts/token/ERC1155/extensions/ERC1155Burnable.sol index ff099cb58..d7bee05d9 100644 --- a/contracts/token/ERC1155/extensions/ERC1155Burnable.sol +++ b/contracts/token/ERC1155/extensions/ERC1155Burnable.sol @@ -14,7 +14,7 @@ import "../ERC1155.sol"; abstract contract ERC1155Burnable is ERC1155 { function burn(address account, uint256 id, uint256 value) public virtual { if (account != _msgSender() && !isApprovedForAll(account, _msgSender())) { - revert ERC1155InsufficientApprovalForAll(_msgSender(), account); + revert ERC1155MissingApprovalForAll(_msgSender(), account); } _burn(account, id, value); @@ -22,7 +22,7 @@ abstract contract ERC1155Burnable is ERC1155 { function burnBatch(address account, uint256[] memory ids, uint256[] memory values) public virtual { if (account != _msgSender() && !isApprovedForAll(account, _msgSender())) { - revert ERC1155InsufficientApprovalForAll(_msgSender(), account); + revert ERC1155MissingApprovalForAll(_msgSender(), account); } _burnBatch(account, ids, values); diff --git a/test/token/ERC1155/ERC1155.behavior.js b/test/token/ERC1155/ERC1155.behavior.js index 3391be4c8..4bf4a7319 100644 --- a/test/token/ERC1155/ERC1155.behavior.js +++ b/test/token/ERC1155/ERC1155.behavior.js @@ -254,7 +254,7 @@ function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, m this.token.safeTransferFrom(multiTokenHolder, recipient, firstTokenId, firstAmount, '0x', { from: proxy, }), - 'ERC1155InsufficientApprovalForAll', + 'ERC1155MissingApprovalForAll', [proxy, multiTokenHolder], ); }); @@ -609,7 +609,7 @@ function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, m '0x', { from: proxy }, ), - 'ERC1155InsufficientApprovalForAll', + 'ERC1155MissingApprovalForAll', [proxy, multiTokenHolder], ); }); diff --git a/test/token/ERC1155/extensions/ERC1155Burnable.test.js b/test/token/ERC1155/extensions/ERC1155Burnable.test.js index 6af2308f8..65a2f95f4 100644 --- a/test/token/ERC1155/extensions/ERC1155Burnable.test.js +++ b/test/token/ERC1155/extensions/ERC1155Burnable.test.js @@ -38,7 +38,7 @@ contract('ERC1155Burnable', function (accounts) { it("unapproved accounts cannot burn the holder's tokens", async function () { await expectRevertCustomError( this.token.burn(holder, tokenIds[0], amounts[0].subn(1), { from: other }), - 'ERC1155InsufficientApprovalForAll', + 'ERC1155MissingApprovalForAll', [other, holder], ); }); @@ -63,7 +63,7 @@ contract('ERC1155Burnable', function (accounts) { it("unapproved accounts cannot burn the holder's tokens", async function () { await expectRevertCustomError( this.token.burnBatch(holder, tokenIds, [amounts[0].subn(1), amounts[1].subn(2)], { from: other }), - 'ERC1155InsufficientApprovalForAll', + 'ERC1155MissingApprovalForAll', [other, holder], ); }); From f29307cfe08c7d76d96a38bf94bab5fec223c943 Mon Sep 17 00:00:00 2001 From: Francisco Date: Mon, 26 Jun 2023 11:36:46 -0300 Subject: [PATCH 126/182] Add Foundry installation instructions with required warnings (#4389) --- README.md | 12 ++++++++++- scripts/upgradeable/upgradeable.patch | 31 ++++++++++++++++----------- 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 8b44917fe..9d1c405b6 100644 --- a/README.md +++ b/README.md @@ -23,13 +23,23 @@ ### Installation +#### Hardhat, Truffle (npm) + ``` $ npm install @openzeppelin/contracts ``` OpenZeppelin Contracts features a [stable API](https://docs.openzeppelin.com/contracts/releases-stability#api-stability), which means that your contracts won't break unexpectedly when upgrading to a newer minor version. -An alternative to npm is to use the GitHub repository (`openzeppelin/openzeppelin-contracts`) to retrieve the contracts. When doing this, make sure to specify the tag for a release such as `v4.5.0`, instead of using the `master` branch. +#### Foundry (git) + +> **Warning** When installing via git, it is a common error to use the `master` branch. This is a development branch that should be avoided in favor of tagged releases. The release process involves security measures that the `master` branch does not guarantee. + +> **Warning** Foundry installs the latest version initially, but subsequent `forge update` commands will use the `master` branch. + +``` +$ forge install OpenZeppelin/openzeppelin-contracts +``` ### Usage diff --git a/scripts/upgradeable/upgradeable.patch b/scripts/upgradeable/upgradeable.patch index 554775ffe..ecd0a3711 100644 --- a/scripts/upgradeable/upgradeable.patch +++ b/scripts/upgradeable/upgradeable.patch @@ -59,10 +59,10 @@ index ff596b0c..00000000 - - diff --git a/README.md b/README.md -index aba99171..6656267b 100644 +index 9d1c405b..c264e29c 100644 --- a/README.md +++ b/README.md -@@ -19,17 +19,20 @@ +@@ -19,6 +19,9 @@ :building_construction: **Want to scale your decentralized application?** Check out [OpenZeppelin Defender](https://openzeppelin.com/defender) — a secure platform for automating and monitoring your operations. @@ -72,6 +72,8 @@ index aba99171..6656267b 100644 ## Overview ### Installation +@@ -26,7 +29,7 @@ + #### Hardhat, Truffle (npm) ``` -$ npm install @openzeppelin/contracts @@ -79,13 +81,16 @@ index aba99171..6656267b 100644 ``` OpenZeppelin Contracts features a [stable API](https://docs.openzeppelin.com/contracts/releases-stability#api-stability), which means that your contracts won't break unexpectedly when upgrading to a newer minor version. +@@ -38,7 +41,7 @@ OpenZeppelin Contracts features a [stable API](https://docs.openzeppelin.com/con + > **Warning** Foundry installs the latest version initially, but subsequent `forge update` commands will use the `master` branch. --An alternative to npm is to use the GitHub repository (`openzeppelin/openzeppelin-contracts`) to retrieve the contracts. When doing this, make sure to specify the tag for a release such as `v4.5.0`, instead of using the `master` branch. -+An alternative to npm is to use the GitHub repository (`openzeppelin/openzeppelin-contracts-upgradeable`) to retrieve the contracts. When doing this, make sure to specify the tag for a release such as `v4.5.0`, instead of using the `master` branch. + ``` +-$ forge install OpenZeppelin/openzeppelin-contracts ++$ forge install OpenZeppelin/openzeppelin-contracts-upgradeable + ``` ### Usage - -@@ -38,10 +41,11 @@ Once installed, you can use the contracts in the library by importing them: +@@ -48,10 +51,11 @@ Once installed, you can use the contracts in the library by importing them: ```solidity pragma solidity ^0.8.19; @@ -101,7 +106,7 @@ index aba99171..6656267b 100644 } ``` diff --git a/contracts/finance/VestingWallet.sol b/contracts/finance/VestingWallet.sol -index 5b7e1b15..1ca745d6 100644 +index ebdf0a33..8888803e 100644 --- a/contracts/finance/VestingWallet.sol +++ b/contracts/finance/VestingWallet.sol @@ -18,6 +18,8 @@ import "../utils/Context.sol"; @@ -127,7 +132,7 @@ index 5d8318f4..ef3cde55 100644 abstract contract GovernorVotes is Governor { IERC5805 public immutable token; diff --git a/contracts/package.json b/contracts/package.json -index 4d0f576b..822fd471 100644 +index df141192..1cf90ad1 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -1,5 +1,5 @@ @@ -135,7 +140,7 @@ index 4d0f576b..822fd471 100644 - "name": "@openzeppelin/contracts", + "name": "@openzeppelin/contracts-upgradeable", "description": "Secure Smart Contract library for Solidity", - "version": "4.9.0", + "version": "4.9.2", "files": [ @@ -13,7 +13,7 @@ }, @@ -147,7 +152,7 @@ index 4d0f576b..822fd471 100644 "keywords": [ "solidity", diff --git a/contracts/token/ERC20/extensions/ERC20Capped.sol b/contracts/token/ERC20/extensions/ERC20Capped.sol -index cda07265..d314148c 100644 +index 41e9ce5c..1d910dfa 100644 --- a/contracts/token/ERC20/extensions/ERC20Capped.sol +++ b/contracts/token/ERC20/extensions/ERC20Capped.sol @@ -7,6 +7,8 @@ import "../ERC20.sol"; @@ -160,7 +165,7 @@ index cda07265..d314148c 100644 abstract contract ERC20Capped is ERC20 { uint256 private immutable _cap; diff --git a/contracts/token/ERC20/extensions/ERC20Permit.sol b/contracts/token/ERC20/extensions/ERC20Permit.sol -index 9379e445..e02f0644 100644 +index 4378eb7c..1da9e731 100644 --- a/contracts/token/ERC20/extensions/ERC20Permit.sol +++ b/contracts/token/ERC20/extensions/ERC20Permit.sol @@ -18,6 +18,8 @@ import "../../../utils/Nonces.sol"; @@ -173,7 +178,7 @@ index 9379e445..e02f0644 100644 abstract contract ERC20Permit is ERC20, IERC20Permit, EIP712, Nonces { // solhint-disable-next-line var-name-mixedcase diff --git a/contracts/token/ERC20/extensions/ERC20Wrapper.sol b/contracts/token/ERC20/extensions/ERC20Wrapper.sol -index bf2b225c..0e5b3628 100644 +index 389965e9..66436b14 100644 --- a/contracts/token/ERC20/extensions/ERC20Wrapper.sol +++ b/contracts/token/ERC20/extensions/ERC20Wrapper.sol @@ -14,6 +14,8 @@ import "../utils/SafeERC20.sol"; @@ -356,7 +361,7 @@ index 2628014f..7d5193c8 100644 } } diff --git a/package.json b/package.json -index c070915f..9a513cac 100644 +index 37e8f871..d098669f 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ From 023894deefe6af0cf1e6120df744dffaccaf9040 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Thu, 29 Jun 2023 10:00:35 -0600 Subject: [PATCH 127/182] Add `ERC2771Forwarder` as an enhanced successor to `MinimalForwarder` (#4346) Co-authored-by: Francisco --- .changeset/blue-horses-do.md | 5 + .github/workflows/checks.yml | 1 + contracts/metatx/ERC2771Forwarder.sol | 289 ++++++++++++++++ contracts/metatx/MinimalForwarder.sol | 104 ------ contracts/metatx/README.adoc | 2 +- contracts/utils/cryptography/EIP712.sol | 2 +- test/metatx/ERC2771Context.test.js | 29 +- test/metatx/ERC2771Forwarder.test.js | 433 ++++++++++++++++++++++++ test/metatx/MinimalForwarder.test.js | 171 ---------- 9 files changed, 749 insertions(+), 287 deletions(-) create mode 100644 .changeset/blue-horses-do.md create mode 100644 contracts/metatx/ERC2771Forwarder.sol delete mode 100644 contracts/metatx/MinimalForwarder.sol create mode 100644 test/metatx/ERC2771Forwarder.test.js delete mode 100644 test/metatx/MinimalForwarder.test.js diff --git a/.changeset/blue-horses-do.md b/.changeset/blue-horses-do.md new file mode 100644 index 000000000..9df604fe4 --- /dev/null +++ b/.changeset/blue-horses-do.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': major +--- + +`ERC2771Forwarder`: Added `deadline` for expiring transactions, batching, and more secure handling of `msg.value`. diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 122d39564..15cc88e96 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -100,6 +100,7 @@ jobs: - uses: crytic/slither-action@v0.3.0 with: node-version: 18.15 + slither-version: 0.9.3 codespell: runs-on: ubuntu-latest diff --git a/contracts/metatx/ERC2771Forwarder.sol b/contracts/metatx/ERC2771Forwarder.sol new file mode 100644 index 000000000..651fdce0b --- /dev/null +++ b/contracts/metatx/ERC2771Forwarder.sol @@ -0,0 +1,289 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.9.0) (metatx/ERC2771Forwarder.sol) + +pragma solidity ^0.8.19; + +import "../utils/cryptography/ECDSA.sol"; +import "../utils/cryptography/EIP712.sol"; +import "../utils/Nonces.sol"; +import "../utils/Address.sol"; + +/** + * @dev A forwarder compatible with ERC2771 contracts. See {ERC2771Context}. + * + * This forwarder operates on forward requests that include: + * + * * `from`: An address to operate on behalf of. It is required to be equal to the request signer. + * * `to`: The address that should be called. + * * `value`: The amount of native token to attach with the requested call. + * * `gas`: The amount of gas limit that will be forwarded with the requested call. + * * `nonce`: A unique transaction ordering identifier to avoid replayability and request invalidation. + * * `deadline`: A timestamp after which the request is not executable anymore. + * * `data`: Encoded `msg.data` to send with the requested call. + */ +contract ERC2771Forwarder is EIP712, Nonces { + using ECDSA for bytes32; + + struct ForwardRequestData { + address from; + address to; + uint256 value; + uint256 gas; + uint48 deadline; + bytes data; + bytes signature; + } + + bytes32 private constant _FORWARD_REQUEST_TYPEHASH = + keccak256( + "ForwardRequest(address from,address to,uint256 value,uint256 gas,uint256 nonce,uint48 deadline,bytes data)" + ); + + /** + * @dev Emitted when a `ForwardRequest` is executed. + * + * NOTE: An unsuccessful forward request could be due to an invalid signature, an expired deadline, + * or simply a revert in the requested call. The contract guarantees that the relayer is not able to force + * the requested call to run out of gas. + */ + event ExecutedForwardRequest(address indexed signer, uint256 nonce, bool success); + + /** + * @dev The request `from` doesn't match with the recovered `signer`. + */ + error ERC2771ForwarderInvalidSigner(address signer, address from); + + /** + * @dev The `requestedValue` doesn't match with the available `msgValue`. + */ + error ERC2771ForwarderMismatchedValue(uint256 requestedValue, uint256 msgValue); + + /** + * @dev The request `deadline` has expired. + */ + error ERC2771ForwarderExpiredRequest(uint48 deadline); + + /** + * @dev See {EIP712-constructor}. + */ + constructor(string memory name) EIP712(name, "1") {} + + /** + * @dev Returns `true` if a request is valid for a provided `signature` at the current block timestamp. + * + * A transaction is considered valid when it hasn't expired (deadline is not met), and the signer + * matches the `from` parameter of the signed request. + * + * NOTE: A request may return false here but it won't cause {executeBatch} to revert if a refund + * receiver is provided. + */ + function verify(ForwardRequestData calldata request) public view virtual returns (bool) { + (bool alive, bool signerMatch, ) = _validate(request); + return alive && signerMatch; + } + + /** + * @dev Executes a `request` on behalf of `signature`'s signer using the ERC-2771 protocol. The gas + * provided to the requested call may not be exactly the amount requested, but the call will not run + * out of gas. Will revert if the request is invalid or the call reverts, in this case the nonce is not consumed. + * + * Requirements: + * + * - The request value should be equal to the provided `msg.value`. + * - The request should be valid according to {verify}. + */ + function execute(ForwardRequestData calldata request) public payable virtual { + // We make sure that msg.value and request.value match exactly. + // If the request is invalid or the call reverts, this whole function + // will revert, ensuring value isn't stuck. + if (msg.value != request.value) { + revert ERC2771ForwarderMismatchedValue(request.value, msg.value); + } + + if (!_execute(request, true)) { + revert Address.FailedInnerCall(); + } + } + + /** + * @dev Batch version of {execute} with optional refunding and atomic execution. + * + * In case a batch contains at least one invalid request (see {verify}), the + * request will be skipped and the `refundReceiver` parameter will receive back the + * unused requested value at the end of the execution. This is done to prevent reverting + * the entire batch when a request is invalid or has already been submitted. + * + * If the `refundReceiver` is the `address(0)`, this function will revert when at least + * one of the requests was not valid instead of skipping it. This could be useful if + * a batch is required to get executed atomically (at least at the top-level). For example, + * refunding (and thus atomicity) can be opt-out if the relayer is using a service that avoids + * including reverted transactions. + * + * Requirements: + * + * - The sum of the requests' values should be equal to the provided `msg.value`. + * - All of the requests should be valid (see {verify}) when `refundReceiver` is the zero address. + * + * NOTE: Setting a zero `refundReceiver` guarantees an all-or-nothing requests execution only for + * the first-level forwarded calls. In case a forwarded request calls to a contract with another + * subcall, the second-level call may revert without the top-level call reverting. + */ + function executeBatch( + ForwardRequestData[] calldata requests, + address payable refundReceiver + ) public payable virtual { + bool atomic = refundReceiver == address(0); + + uint256 requestsValue; + uint256 refundValue; + + for (uint256 i; i < requests.length; ++i) { + requestsValue += requests[i].value; + bool success = _execute(requests[i], atomic); + if (!success) { + refundValue += requests[i].value; + } + } + + // The batch should revert if there's a mismatched msg.value provided + // to avoid request value tampering + if (requestsValue != msg.value) { + revert ERC2771ForwarderMismatchedValue(requestsValue, msg.value); + } + + // Some requests with value were invalid (possibly due to frontrunning). + // To avoid leaving ETH in the contract this value is refunded. + if (refundValue != 0) { + // We know refundReceiver != address(0) && requestsValue == msg.value + // meaning we can ensure refundValue is not taken from the original contract's balance + // and refundReceiver is a known account. + Address.sendValue(refundReceiver, refundValue); + } + } + + /** + * @dev Validates if the provided request can be executed at current block timestamp with + * the given `request.signature` on behalf of `request.signer`. + */ + function _validate( + ForwardRequestData calldata request + ) internal view virtual returns (bool alive, bool signerMatch, address signer) { + signer = _recoverForwardRequestSigner(request); + return (request.deadline >= block.timestamp, signer == request.from, signer); + } + + /** + * @dev Recovers the signer of an EIP712 message hash for a forward `request` and its corresponding `signature`. + * See {ECDSA-recover}. + */ + function _recoverForwardRequestSigner(ForwardRequestData calldata request) internal view virtual returns (address) { + return + _hashTypedDataV4( + keccak256( + abi.encode( + _FORWARD_REQUEST_TYPEHASH, + request.from, + request.to, + request.value, + request.gas, + nonces(request.from), + request.deadline, + keccak256(request.data) + ) + ) + ).recover(request.signature); + } + + /** + * @dev Validates and executes a signed request returning the request call `success` value. + * + * Internal function without msg.value validation. + * + * Requirements: + * + * - The caller must have provided enough gas to forward with the call. + * - The request must be valid (see {verify}) if the `requireValidRequest` is true. + * + * Emits an {ExecutedForwardRequest} event. + * + * IMPORTANT: Using this function doesn't check that all the `msg.value` was sent, potentially + * leaving value stuck in the contract. + */ + function _execute( + ForwardRequestData calldata request, + bool requireValidRequest + ) internal virtual returns (bool success) { + (bool alive, bool signerMatch, address signer) = _validate(request); + + // Need to explicitly specify if a revert is required since non-reverting is default for + // batches and reversion is opt-in since it could be useful in some scenarios + if (requireValidRequest) { + if (!alive) { + revert ERC2771ForwarderExpiredRequest(request.deadline); + } + + if (!signerMatch) { + revert ERC2771ForwarderInvalidSigner(signer, request.from); + } + } + + // Ignore an invalid request because requireValidRequest = false + if (signerMatch && alive) { + // Nonce should be used before the call to prevent reusing by reentrancy + uint256 currentNonce = _useNonce(signer); + + (success, ) = request.to.call{gas: request.gas, value: request.value}( + abi.encodePacked(request.data, request.from) + ); + + _checkForwardedGas(request); + + emit ExecutedForwardRequest(signer, currentNonce, success); + } + } + + /** + * @dev Checks if the requested gas was correctly forwarded to the callee. + * + * As a consequence of https://eips.ethereum.org/EIPS/eip-150[EIP-150]: + * - At most `gasleft() - floor(gasleft() / 64)` is forwarded to the callee. + * - At least `floor(gasleft() / 64)` is kept in the caller. + * + * It reverts consuming all the available gas if the forwarded gas is not the requested gas. + * + * IMPORTANT: This function should be called exactly the end of the forwarded call. Any gas consumed + * in between will make room for bypassing this check. + */ + function _checkForwardedGas(ForwardRequestData calldata request) private view { + // To avoid insufficient gas griefing attacks, as referenced in https://ronan.eth.limo/blog/ethereum-gas-dangers/ + // + // A malicious relayer can attempt to shrink the gas forwarded so that the underlying call reverts out-of-gas + // but the forwarding itself still succeeds. In order to make sure that the subcall received sufficient gas, + // we will inspect gasleft() after the forwarding. + // + // Let X be the gas available before the subcall, such that the subcall gets at most X * 63 / 64. + // We can't know X after CALL dynamic costs, but we want it to be such that X * 63 / 64 >= req.gas. + // Let Y be the gas used in the subcall. gasleft() measured immediately after the subcall will be gasleft() = X - Y. + // If the subcall ran out of gas, then Y = X * 63 / 64 and gasleft() = X - Y = X / 64. + // Under this assumption req.gas / 63 > gasleft() is true is true if and only if + // req.gas / 63 > X / 64, or equivalently req.gas > X * 63 / 64. + // This means that if the subcall runs out of gas we are able to detect that insufficient gas was passed. + // + // We will now also see that req.gas / 63 > gasleft() implies that req.gas >= X * 63 / 64. + // The contract guarantees Y <= req.gas, thus gasleft() = X - Y >= X - req.gas. + // - req.gas / 63 > gasleft() + // - req.gas / 63 >= X - req.gas + // - req.gas >= X * 63 / 64 + // In other words if req.gas < X * 63 / 64 then req.gas / 63 <= gasleft(), thus if the relayer behaves honestly + // the forwarding does not revert. + if (gasleft() < request.gas / 63) { + // We explicitly trigger invalid opcode to consume all gas and bubble-up the effects, since + // neither revert or assert consume all gas since Solidity 0.8.0 + // https://docs.soliditylang.org/en/v0.8.0/control-structures.html#panic-via-assert-and-error-via-require + /// @solidity memory-safe-assembly + assembly { + invalid() + } + } + } +} diff --git a/contracts/metatx/MinimalForwarder.sol b/contracts/metatx/MinimalForwarder.sol deleted file mode 100644 index b5267aa10..000000000 --- a/contracts/metatx/MinimalForwarder.sol +++ /dev/null @@ -1,104 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.9.0) (metatx/MinimalForwarder.sol) - -pragma solidity ^0.8.19; - -import "../utils/cryptography/ECDSA.sol"; -import "../utils/cryptography/EIP712.sol"; - -/** - * @dev Simple minimal forwarder to be used together with an ERC2771 compatible contract. See {ERC2771Context}. - * - * MinimalForwarder is mainly meant for testing, as it is missing features to be a good production-ready forwarder. This - * contract does not intend to have all the properties that are needed for a sound forwarding system. A fully - * functioning forwarding system with good properties requires more complexity. We suggest you look at other projects - * such as the GSN which do have the goal of building a system like that. - */ -contract MinimalForwarder is EIP712 { - using ECDSA for bytes32; - - struct ForwardRequest { - address from; - address to; - uint256 value; - uint256 gas; - uint256 nonce; - bytes data; - } - - bytes32 private constant _TYPEHASH = - keccak256("ForwardRequest(address from,address to,uint256 value,uint256 gas,uint256 nonce,bytes data)"); - - mapping(address => uint256) private _nonces; - - /** - * @dev The request `from` doesn't match with the recovered `signer`. - */ - error MinimalForwarderInvalidSigner(address signer, address from); - - /** - * @dev The request nonce doesn't match with the `current` nonce for the request signer. - */ - error MinimalForwarderInvalidNonce(address signer, uint256 current); - - constructor() EIP712("MinimalForwarder", "0.0.1") {} - - function getNonce(address from) public view returns (uint256) { - return _nonces[from]; - } - - function verify(ForwardRequest calldata req, bytes calldata signature) public view returns (bool) { - address signer = _recover(req, signature); - (bool correctFrom, bool correctNonce) = _validateReq(req, signer); - return correctFrom && correctNonce; - } - - function execute( - ForwardRequest calldata req, - bytes calldata signature - ) public payable returns (bool, bytes memory) { - address signer = _recover(req, signature); - (bool correctFrom, bool correctNonce) = _validateReq(req, signer); - - if (!correctFrom) { - revert MinimalForwarderInvalidSigner(signer, req.from); - } - if (!correctNonce) { - revert MinimalForwarderInvalidNonce(signer, _nonces[req.from]); - } - - _nonces[req.from] = req.nonce + 1; - - (bool success, bytes memory returndata) = req.to.call{gas: req.gas, value: req.value}( - abi.encodePacked(req.data, req.from) - ); - - // Validate that the relayer has sent enough gas for the call. - // See https://ronan.eth.limo/blog/ethereum-gas-dangers/ - if (gasleft() <= req.gas / 63) { - // We explicitly trigger invalid opcode to consume all gas and bubble-up the effects, since - // neither revert or assert consume all gas since Solidity 0.8.0 - // https://docs.soliditylang.org/en/v0.8.0/control-structures.html#panic-via-assert-and-error-via-require - /// @solidity memory-safe-assembly - assembly { - invalid() - } - } - - return (success, returndata); - } - - function _recover(ForwardRequest calldata req, bytes calldata signature) internal view returns (address) { - return - _hashTypedDataV4( - keccak256(abi.encode(_TYPEHASH, req.from, req.to, req.value, req.gas, req.nonce, keccak256(req.data))) - ).recover(signature); - } - - function _validateReq( - ForwardRequest calldata req, - address signer - ) internal view returns (bool correctFrom, bool correctNonce) { - return (signer == req.from, _nonces[req.from] == req.nonce); - } -} diff --git a/contracts/metatx/README.adoc b/contracts/metatx/README.adoc index eccdeaf97..9f25802e4 100644 --- a/contracts/metatx/README.adoc +++ b/contracts/metatx/README.adoc @@ -9,4 +9,4 @@ NOTE: This document is better viewed at https://docs.openzeppelin.com/contracts/ == Utils -{{MinimalForwarder}} +{{ERC2771Forwarder}} diff --git a/contracts/utils/cryptography/EIP712.sol b/contracts/utils/cryptography/EIP712.sol index 2628014f1..1089de005 100644 --- a/contracts/utils/cryptography/EIP712.sol +++ b/contracts/utils/cryptography/EIP712.sol @@ -110,7 +110,7 @@ abstract contract EIP712 is IERC5267 { } /** - * @dev See {EIP-5267}. + * @dev See {IERC-5267}. * * _Available since v4.9._ */ diff --git a/test/metatx/ERC2771Context.test.js b/test/metatx/ERC2771Context.test.js index 6c298d3d9..3dd4b4153 100644 --- a/test/metatx/ERC2771Context.test.js +++ b/test/metatx/ERC2771Context.test.js @@ -6,14 +6,16 @@ const { expectEvent } = require('@openzeppelin/test-helpers'); const { expect } = require('chai'); const ERC2771ContextMock = artifacts.require('ERC2771ContextMock'); -const MinimalForwarder = artifacts.require('MinimalForwarder'); +const ERC2771Forwarder = artifacts.require('ERC2771Forwarder'); const ContextMockCaller = artifacts.require('ContextMockCaller'); const { shouldBehaveLikeRegularContext } = require('../utils/Context.behavior'); contract('ERC2771Context', function (accounts) { + const MAX_UINT48 = web3.utils.toBN(1).shln(48).subn(1).toString(); + beforeEach(async function () { - this.forwarder = await MinimalForwarder.new(); + this.forwarder = await ERC2771Forwarder.new('ERC2771Forwarder'); this.recipient = await ERC2771ContextMock.new(this.forwarder.address); this.domain = await getDomain(this.forwarder); @@ -25,6 +27,7 @@ contract('ERC2771Context', function (accounts) { { name: 'value', type: 'uint256' }, { name: 'gas', type: 'uint256' }, { name: 'nonce', type: 'uint256' }, + { name: 'deadline', type: 'uint48' }, { name: 'data', type: 'bytes' }, ], }; @@ -63,14 +66,17 @@ contract('ERC2771Context', function (accounts) { to: this.recipient.address, value: '0', gas: '100000', - nonce: (await this.forwarder.getNonce(this.sender)).toString(), + nonce: (await this.forwarder.nonces(this.sender)).toString(), + deadline: MAX_UINT48, data, }; - const sign = ethSigUtil.signTypedMessage(this.wallet.getPrivateKey(), { data: { ...this.data, message: req } }); - expect(await this.forwarder.verify(req, sign)).to.equal(true); + req.signature = ethSigUtil.signTypedMessage(this.wallet.getPrivateKey(), { + data: { ...this.data, message: req }, + }); + expect(await this.forwarder.verify(req)).to.equal(true); - const { tx } = await this.forwarder.execute(req, sign); + const { tx } = await this.forwarder.execute(req); await expectEvent.inTransaction(tx, ERC2771ContextMock, 'Sender', { sender: this.sender }); }); }); @@ -86,14 +92,17 @@ contract('ERC2771Context', function (accounts) { to: this.recipient.address, value: '0', gas: '100000', - nonce: (await this.forwarder.getNonce(this.sender)).toString(), + nonce: (await this.forwarder.nonces(this.sender)).toString(), + deadline: MAX_UINT48, data, }; - const sign = ethSigUtil.signTypedMessage(this.wallet.getPrivateKey(), { data: { ...this.data, message: req } }); - expect(await this.forwarder.verify(req, sign)).to.equal(true); + req.signature = ethSigUtil.signTypedMessage(this.wallet.getPrivateKey(), { + data: { ...this.data, message: req }, + }); + expect(await this.forwarder.verify(req)).to.equal(true); - const { tx } = await this.forwarder.execute(req, sign); + const { tx } = await this.forwarder.execute(req); await expectEvent.inTransaction(tx, ERC2771ContextMock, 'Data', { data, integerValue, stringValue }); }); }); diff --git a/test/metatx/ERC2771Forwarder.test.js b/test/metatx/ERC2771Forwarder.test.js new file mode 100644 index 000000000..fa84ccdc3 --- /dev/null +++ b/test/metatx/ERC2771Forwarder.test.js @@ -0,0 +1,433 @@ +const ethSigUtil = require('eth-sig-util'); +const Wallet = require('ethereumjs-wallet').default; +const { getDomain, domainType } = require('../helpers/eip712'); +const { expectRevertCustomError } = require('../helpers/customError'); + +const { constants, expectRevert, expectEvent, time } = require('@openzeppelin/test-helpers'); +const { expect } = require('chai'); + +const ERC2771Forwarder = artifacts.require('ERC2771Forwarder'); +const CallReceiverMock = artifacts.require('CallReceiverMock'); + +contract('ERC2771Forwarder', function (accounts) { + const [, refundReceiver, another] = accounts; + + const tamperedValues = { + from: another, + to: another, + value: web3.utils.toWei('0.5'), + data: '0x1742', + deadline: 0xdeadbeef, + }; + + beforeEach(async function () { + this.forwarder = await ERC2771Forwarder.new('ERC2771Forwarder'); + + this.domain = await getDomain(this.forwarder); + this.types = { + EIP712Domain: domainType(this.domain), + ForwardRequest: [ + { name: 'from', type: 'address' }, + { name: 'to', type: 'address' }, + { name: 'value', type: 'uint256' }, + { name: 'gas', type: 'uint256' }, + { name: 'nonce', type: 'uint256' }, + { name: 'deadline', type: 'uint48' }, + { name: 'data', type: 'bytes' }, + ], + }; + + this.alice = Wallet.generate(); + this.alice.address = web3.utils.toChecksumAddress(this.alice.getAddressString()); + + this.timestamp = await time.latest(); + this.request = { + from: this.alice.address, + to: constants.ZERO_ADDRESS, + value: '0', + gas: '100000', + data: '0x', + deadline: this.timestamp.toNumber() + 60, // 1 minute + }; + this.requestData = { + ...this.request, + nonce: (await this.forwarder.nonces(this.alice.address)).toString(), + }; + + this.forgeData = request => ({ + types: this.types, + domain: this.domain, + primaryType: 'ForwardRequest', + message: { ...this.requestData, ...request }, + }); + this.sign = (privateKey, request) => + ethSigUtil.signTypedMessage(privateKey, { + data: this.forgeData(request), + }); + + this.requestData.signature = this.sign(this.alice.getPrivateKey()); + }); + + context('verify', function () { + context('with valid signature', function () { + it('returns true without altering the nonce', async function () { + expect(await this.forwarder.nonces(this.requestData.from)).to.be.bignumber.equal( + web3.utils.toBN(this.requestData.nonce), + ); + expect(await this.forwarder.verify(this.requestData)).to.be.equal(true); + expect(await this.forwarder.nonces(this.requestData.from)).to.be.bignumber.equal( + web3.utils.toBN(this.requestData.nonce), + ); + }); + }); + + context('with tampered values', function () { + for (const [key, value] of Object.entries(tamperedValues)) { + it(`returns false with tampered ${key}`, async function () { + expect(await this.forwarder.verify(this.forgeData({ [key]: value }).message)).to.be.equal(false); + }); + } + + it('returns false with tampered signature', async function () { + const tamperedsign = web3.utils.hexToBytes(this.requestData.signature); + tamperedsign[42] ^= 0xff; + this.requestData.signature = web3.utils.bytesToHex(tamperedsign); + expect(await this.forwarder.verify(this.requestData)).to.be.equal(false); + }); + + it('returns false with valid signature for non-current nonce', async function () { + const req = { + ...this.requestData, + nonce: this.requestData.nonce + 1, + }; + req.signature = this.sign(this.alice.getPrivateKey(), req); + expect(await this.forwarder.verify(req)).to.be.equal(false); + }); + + it('returns false with valid signature for expired deadline', async function () { + const req = { + ...this.requestData, + deadline: this.timestamp - 1, + }; + req.signature = this.sign(this.alice.getPrivateKey(), req); + expect(await this.forwarder.verify(req)).to.be.equal(false); + }); + }); + }); + + context('execute', function () { + context('with valid requests', function () { + beforeEach(async function () { + expect(await this.forwarder.nonces(this.requestData.from)).to.be.bignumber.equal( + web3.utils.toBN(this.requestData.nonce), + ); + }); + + it('emits an event and consumes nonce for a successful request', async function () { + const receipt = await this.forwarder.execute(this.requestData); + expectEvent(receipt, 'ExecutedForwardRequest', { + signer: this.requestData.from, + nonce: web3.utils.toBN(this.requestData.nonce), + success: true, + }); + expect(await this.forwarder.nonces(this.requestData.from)).to.be.bignumber.equal( + web3.utils.toBN(this.requestData.nonce + 1), + ); + }); + + it('reverts with an unsuccessful request', async function () { + const receiver = await CallReceiverMock.new(); + const req = { + ...this.requestData, + to: receiver.address, + data: receiver.contract.methods.mockFunctionRevertsNoReason().encodeABI(), + }; + req.signature = this.sign(this.alice.getPrivateKey(), req); + await expectRevertCustomError(this.forwarder.execute(req), 'FailedInnerCall', []); + }); + }); + + context('with tampered request', function () { + for (const [key, value] of Object.entries(tamperedValues)) { + it(`reverts with tampered ${key}`, async function () { + const data = this.forgeData({ [key]: value }); + await expectRevertCustomError( + this.forwarder.execute(data.message, { + value: key == 'value' ? value : 0, // To avoid MismatchedValue error + }), + 'ERC2771ForwarderInvalidSigner', + [ethSigUtil.recoverTypedSignature({ data, sig: this.requestData.signature }), data.message.from], + ); + }); + } + + it('reverts with tampered signature', async function () { + const tamperedSig = web3.utils.hexToBytes(this.requestData.signature); + tamperedSig[42] ^= 0xff; + this.requestData.signature = web3.utils.bytesToHex(tamperedSig); + await expectRevertCustomError(this.forwarder.execute(this.requestData), 'ERC2771ForwarderInvalidSigner', [ + ethSigUtil.recoverTypedSignature({ data: this.forgeData(), sig: tamperedSig }), + this.requestData.from, + ]); + }); + + it('reverts with valid signature for non-current nonce', async function () { + // Execute first a request + await this.forwarder.execute(this.requestData); + + // And then fail due to an already used nonce + await expectRevertCustomError(this.forwarder.execute(this.requestData), 'ERC2771ForwarderInvalidSigner', [ + ethSigUtil.recoverTypedSignature({ + data: this.forgeData({ ...this.requestData, nonce: this.requestData.nonce + 1 }), + sig: this.requestData.signature, + }), + this.requestData.from, + ]); + }); + + it('reverts with valid signature for expired deadline', async function () { + const req = { + ...this.requestData, + deadline: this.timestamp - 1, + }; + req.signature = this.sign(this.alice.getPrivateKey(), req); + await expectRevertCustomError(this.forwarder.execute(req), 'ERC2771ForwarderExpiredRequest', [ + this.timestamp - 1, + ]); + }); + + it('reverts with valid signature but mismatched value', async function () { + const value = 100; + const req = { + ...this.requestData, + value, + }; + req.signature = this.sign(this.alice.getPrivateKey(), req); + await expectRevertCustomError(this.forwarder.execute(req), 'ERC2771ForwarderMismatchedValue', [0, value]); + }); + }); + + it('bubbles out of gas', async function () { + const receiver = await CallReceiverMock.new(); + const gasAvailable = 100000; + this.requestData.to = receiver.address; + this.requestData.data = receiver.contract.methods.mockFunctionOutOfGas().encodeABI(); + this.requestData.gas = 1000000; + + this.requestData.signature = this.sign(this.alice.getPrivateKey()); + + await expectRevert.assertion(this.forwarder.execute(this.requestData, { gas: gasAvailable })); + + const { transactions } = await web3.eth.getBlock('latest'); + const { gasUsed } = await web3.eth.getTransactionReceipt(transactions[0]); + + expect(gasUsed).to.be.equal(gasAvailable); + }); + }); + + context('executeBatch', function () { + const batchValue = requestDatas => requestDatas.reduce((value, request) => value + Number(request.value), 0); + + beforeEach(async function () { + this.bob = Wallet.generate(); + this.bob.address = web3.utils.toChecksumAddress(this.bob.getAddressString()); + + this.eve = Wallet.generate(); + this.eve.address = web3.utils.toChecksumAddress(this.eve.getAddressString()); + + this.signers = [this.alice, this.bob, this.eve]; + + this.requestDatas = await Promise.all( + this.signers.map(async ({ address }) => ({ + ...this.requestData, + from: address, + nonce: (await this.forwarder.nonces(address)).toString(), + value: web3.utils.toWei('10', 'gwei'), + })), + ); + + this.requestDatas = this.requestDatas.map((requestData, i) => ({ + ...requestData, + signature: this.sign(this.signers[i].getPrivateKey(), requestData), + })); + + this.msgValue = batchValue(this.requestDatas); + }); + + context('with valid requests', function () { + beforeEach(async function () { + for (const request of this.requestDatas) { + expect(await this.forwarder.verify(request)).to.be.equal(true); + } + + this.receipt = await this.forwarder.executeBatch(this.requestDatas, another, { value: this.msgValue }); + }); + + it('emits events', async function () { + for (const request of this.requestDatas) { + expectEvent(this.receipt, 'ExecutedForwardRequest', { + signer: request.from, + nonce: web3.utils.toBN(request.nonce), + success: true, + }); + } + }); + + it('increase nonces', async function () { + for (const request of this.requestDatas) { + expect(await this.forwarder.nonces(request.from)).to.be.bignumber.eq(web3.utils.toBN(request.nonce + 1)); + } + }); + }); + + context('with tampered requests', function () { + beforeEach(async function () { + this.idx = 1; // Tampered idx + }); + + it('reverts with mismatched value', async function () { + this.requestDatas[this.idx].value = 100; + this.requestDatas[this.idx].signature = this.sign( + this.signers[this.idx].getPrivateKey(), + this.requestDatas[this.idx], + ); + await expectRevertCustomError( + this.forwarder.executeBatch(this.requestDatas, another, { value: this.msgValue }), + 'ERC2771ForwarderMismatchedValue', + [batchValue(this.requestDatas), this.msgValue], + ); + }); + + context('when the refund receiver is the zero address', function () { + beforeEach(function () { + this.refundReceiver = constants.ZERO_ADDRESS; + }); + + for (const [key, value] of Object.entries(tamperedValues)) { + it(`reverts with at least one tampered request ${key}`, async function () { + const data = this.forgeData({ ...this.requestDatas[this.idx], [key]: value }); + + this.requestDatas[this.idx] = data.message; + + await expectRevertCustomError( + this.forwarder.executeBatch(this.requestDatas, this.refundReceiver, { value: this.msgValue }), + 'ERC2771ForwarderInvalidSigner', + [ + ethSigUtil.recoverTypedSignature({ data, sig: this.requestDatas[this.idx].signature }), + data.message.from, + ], + ); + }); + } + + it('reverts with at least one tampered request signature', async function () { + const tamperedSig = web3.utils.hexToBytes(this.requestDatas[this.idx].signature); + tamperedSig[42] ^= 0xff; + + this.requestDatas[this.idx].signature = web3.utils.bytesToHex(tamperedSig); + + await expectRevertCustomError( + this.forwarder.executeBatch(this.requestDatas, this.refundReceiver, { value: this.msgValue }), + 'ERC2771ForwarderInvalidSigner', + [ + ethSigUtil.recoverTypedSignature({ + data: this.forgeData(this.requestDatas[this.idx]), + sig: this.requestDatas[this.idx].signature, + }), + this.requestDatas[this.idx].from, + ], + ); + }); + + it('reverts with at least one valid signature for non-current nonce', async function () { + // Execute first a request + await this.forwarder.execute(this.requestDatas[this.idx], { value: this.requestDatas[this.idx].value }); + + // And then fail due to an already used nonce + await expectRevertCustomError( + this.forwarder.executeBatch(this.requestDatas, this.refundReceiver, { value: this.msgValue }), + 'ERC2771ForwarderInvalidSigner', + [ + ethSigUtil.recoverTypedSignature({ + data: this.forgeData({ ...this.requestDatas[this.idx], nonce: this.requestDatas[this.idx].nonce + 1 }), + sig: this.requestDatas[this.idx].signature, + }), + this.requestDatas[this.idx].from, + ], + ); + }); + + it('reverts with at least one valid signature for expired deadline', async function () { + this.requestDatas[this.idx].deadline = this.timestamp.toNumber() - 1; + this.requestDatas[this.idx].signature = this.sign( + this.signers[this.idx].getPrivateKey(), + this.requestDatas[this.idx], + ); + await expectRevertCustomError( + this.forwarder.executeBatch(this.requestDatas, this.refundReceiver, { value: this.msgValue }), + 'ERC2771ForwarderExpiredRequest', + [this.timestamp.toNumber() - 1], + ); + }); + }); + + context('when the refund receiver is a known address', function () { + beforeEach(async function () { + this.refundReceiver = refundReceiver; + this.initialRefundReceiverBalance = web3.utils.toBN(await web3.eth.getBalance(this.refundReceiver)); + this.initialTamperedRequestNonce = await this.forwarder.nonces(this.requestDatas[this.idx].from); + }); + + for (const [key, value] of Object.entries(tamperedValues)) { + it(`ignores a request with tampered ${key} and refunds its value`, async function () { + const data = this.forgeData({ ...this.requestDatas[this.idx], [key]: value }); + + this.requestDatas[this.idx] = data.message; + + const receipt = await this.forwarder.executeBatch(this.requestDatas, this.refundReceiver, { + value: batchValue(this.requestDatas), + }); + expect(receipt.logs.filter(({ event }) => event === 'ExecutedForwardRequest').length).to.be.equal(2); + }); + } + + it('ignores a request with a valid signature for non-current nonce', async function () { + // Execute first a request + await this.forwarder.execute(this.requestDatas[this.idx], { value: this.requestDatas[this.idx].value }); + this.initialTamperedRequestNonce++; // Should be already incremented by the individual `execute` + + // And then ignore the same request in a batch due to an already used nonce + const receipt = await this.forwarder.executeBatch(this.requestDatas, this.refundReceiver, { + value: this.msgValue, + }); + expect(receipt.logs.filter(({ event }) => event === 'ExecutedForwardRequest').length).to.be.equal(2); + }); + + it('ignores a request with a valid signature for expired deadline', async function () { + this.requestDatas[this.idx].deadline = this.timestamp.toNumber() - 1; + this.requestDatas[this.idx].signature = this.sign( + this.signers[this.idx].getPrivateKey(), + this.requestDatas[this.idx], + ); + + const receipt = await this.forwarder.executeBatch(this.requestDatas, this.refundReceiver, { + value: this.msgValue, + }); + expect(receipt.logs.filter(({ event }) => event === 'ExecutedForwardRequest').length).to.be.equal(2); + }); + + afterEach(async function () { + // The invalid request value was refunded + expect(await web3.eth.getBalance(this.refundReceiver)).to.be.bignumber.equal( + this.initialRefundReceiverBalance.add(web3.utils.toBN(this.requestDatas[this.idx].value)), + ); + + // The invalid request from's nonce was not incremented + expect(await this.forwarder.nonces(this.requestDatas[this.idx].from)).to.be.bignumber.eq( + web3.utils.toBN(this.initialTamperedRequestNonce), + ); + }); + }); + }); + }); +}); diff --git a/test/metatx/MinimalForwarder.test.js b/test/metatx/MinimalForwarder.test.js deleted file mode 100644 index c775c5e44..000000000 --- a/test/metatx/MinimalForwarder.test.js +++ /dev/null @@ -1,171 +0,0 @@ -const ethSigUtil = require('eth-sig-util'); -const Wallet = require('ethereumjs-wallet').default; -const { getDomain, domainType } = require('../helpers/eip712'); -const { expectRevertCustomError } = require('../helpers/customError'); - -const { constants, expectRevert } = require('@openzeppelin/test-helpers'); -const { expect } = require('chai'); - -const MinimalForwarder = artifacts.require('MinimalForwarder'); -const CallReceiverMock = artifacts.require('CallReceiverMock'); - -contract('MinimalForwarder', function (accounts) { - beforeEach(async function () { - this.forwarder = await MinimalForwarder.new(); - - this.domain = await getDomain(this.forwarder); - this.types = { - EIP712Domain: domainType(this.domain), - ForwardRequest: [ - { name: 'from', type: 'address' }, - { name: 'to', type: 'address' }, - { name: 'value', type: 'uint256' }, - { name: 'gas', type: 'uint256' }, - { name: 'nonce', type: 'uint256' }, - { name: 'data', type: 'bytes' }, - ], - }; - }); - - context('with message', function () { - const tamperedValues = { - from: accounts[0], - to: accounts[0], - value: web3.utils.toWei('1'), - nonce: 1234, - data: '0x1742', - }; - - beforeEach(async function () { - this.wallet = Wallet.generate(); - this.sender = web3.utils.toChecksumAddress(this.wallet.getAddressString()); - this.req = { - from: this.sender, - to: constants.ZERO_ADDRESS, - value: '0', - gas: '100000', - nonce: Number(await this.forwarder.getNonce(this.sender)), - data: '0x', - }; - this.forgeData = req => ({ - types: this.types, - domain: this.domain, - primaryType: 'ForwardRequest', - message: { ...this.req, ...req }, - }); - this.sign = req => - ethSigUtil.signTypedMessage(this.wallet.getPrivateKey(), { - data: this.forgeData(req), - }); - }); - - context('verify', function () { - context('valid signature', function () { - beforeEach(async function () { - expect(await this.forwarder.getNonce(this.req.from)).to.be.bignumber.equal(web3.utils.toBN(this.req.nonce)); - }); - - it('success', async function () { - expect(await this.forwarder.verify(this.req, this.sign())).to.be.equal(true); - }); - - afterEach(async function () { - expect(await this.forwarder.getNonce(this.req.from)).to.be.bignumber.equal(web3.utils.toBN(this.req.nonce)); - }); - }); - - context('with tampered values', function () { - for (const [key, value] of Object.entries(tamperedValues)) { - it(`returns false with tampered ${key}`, async function () { - expect(await this.forwarder.verify(this.forgeData({ [key]: value }).message, this.sign())).to.be.equal( - false, - ); - }); - } - - it('returns false with tampered signature', async function () { - const tamperedsign = web3.utils.hexToBytes(this.sign()); - tamperedsign[42] ^= 0xff; - expect(await this.forwarder.verify(this.req, web3.utils.bytesToHex(tamperedsign))).to.be.equal(false); - }); - - it('returns false with valid signature for non-current nonce', async function () { - const req = { - ...this.req, - nonce: this.req.nonce + 1, - }; - const sig = this.sign(req); - expect(await this.forwarder.verify(req, sig)).to.be.equal(false); - }); - }); - }); - - context('execute', function () { - context('valid signature', function () { - beforeEach(async function () { - expect(await this.forwarder.getNonce(this.req.from)).to.be.bignumber.equal(web3.utils.toBN(this.req.nonce)); - }); - - it('success', async function () { - await this.forwarder.execute(this.req, this.sign()); // expect to not revert - }); - - afterEach(async function () { - expect(await this.forwarder.getNonce(this.req.from)).to.be.bignumber.equal( - web3.utils.toBN(this.req.nonce + 1), - ); - }); - }); - - context('with tampered values', function () { - for (const [key, value] of Object.entries(tamperedValues)) { - it(`reverts with tampered ${key}`, async function () { - const sig = this.sign(); - const data = this.forgeData({ [key]: value }); - await expectRevertCustomError(this.forwarder.execute(data.message, sig), 'MinimalForwarderInvalidSigner', [ - ethSigUtil.recoverTypedSignature({ data, sig }), - data.message.from, - ]); - }); - } - - it('reverts with tampered signature', async function () { - const tamperedSig = web3.utils.hexToBytes(this.sign()); - tamperedSig[42] ^= 0xff; - await expectRevertCustomError( - this.forwarder.execute(this.req, web3.utils.bytesToHex(tamperedSig)), - 'MinimalForwarderInvalidSigner', - [ethSigUtil.recoverTypedSignature({ data: this.forgeData(), sig: tamperedSig }), this.req.from], - ); - }); - - it('reverts with valid signature for non-current nonce', async function () { - const req = { - ...this.req, - nonce: this.req.nonce + 1, - }; - const sig = this.sign(req); - await expectRevertCustomError(this.forwarder.execute(req, sig), 'MinimalForwarderInvalidNonce', [ - this.req.from, - this.req.nonce, - ]); - }); - }); - - it('bubble out of gas', async function () { - const receiver = await CallReceiverMock.new(); - const gasAvailable = 100000; - this.req.to = receiver.address; - this.req.data = receiver.contract.methods.mockFunctionOutOfGas().encodeABI(); - this.req.gas = 1000000; - - await expectRevert.assertion(this.forwarder.execute(this.req, this.sign(), { gas: gasAvailable })); - - const { transactions } = await web3.eth.getBlock('latest'); - const { gasUsed } = await web3.eth.getTransactionReceipt(transactions[0]); - - expect(gasUsed).to.be.equal(gasAvailable); - }); - }); - }); -}); From d6a8b2ccd7dfd9001942233d6ce0bbecbaab1771 Mon Sep 17 00:00:00 2001 From: Renan Souza Date: Thu, 29 Jun 2023 13:41:44 -0300 Subject: [PATCH 128/182] Fix details AccessControl-test (#4391) --- test/access/AccessControl.behavior.js | 2 +- test/access/AccessControlEnumerable.test.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/access/AccessControl.behavior.js b/test/access/AccessControl.behavior.js index b1729c5d6..20804f049 100644 --- a/test/access/AccessControl.behavior.js +++ b/test/access/AccessControl.behavior.js @@ -382,7 +382,7 @@ function shouldBehaveLikeAccessControlDefaultAdminRules(delay, defaultAdmin, new it("should revert if defaultAdmin's admin is changed", async function () { await expectRevertCustomError( - this.accessControl.$_setRoleAdmin(DEFAULT_ADMIN_ROLE, defaultAdmin), + this.accessControl.$_setRoleAdmin(DEFAULT_ADMIN_ROLE, OTHER_ROLE), 'AccessControlEnforcedDefaultAdminRules', [], ); diff --git a/test/access/AccessControlEnumerable.test.js b/test/access/AccessControlEnumerable.test.js index 0e1879700..429f22f8f 100644 --- a/test/access/AccessControlEnumerable.test.js +++ b/test/access/AccessControlEnumerable.test.js @@ -6,7 +6,7 @@ const { const AccessControlEnumerable = artifacts.require('$AccessControlEnumerable'); -contract('AccessControl', function (accounts) { +contract('AccessControlEnumerable', function (accounts) { beforeEach(async function () { this.accessControl = await AccessControlEnumerable.new({ from: accounts[0] }); await this.accessControl.$_grantRole(DEFAULT_ADMIN_ROLE, accounts[0]); From 874c2d3c02ec1bce6af9a30bc828d3fe2079136b Mon Sep 17 00:00:00 2001 From: Francisco Date: Thu, 29 Jun 2023 17:12:26 -0300 Subject: [PATCH 129/182] Use explicit imports (#4399) Co-authored-by: Hadrien Croubois Co-authored-by: ernestognw --- .changeset/orange-apes-draw.md | 5 +++ .solhint.json | 3 +- README.md | 2 +- contracts/access/AccessControl.sol | 8 ++-- .../access/AccessControlDefaultAdminRules.sol | 9 +++-- contracts/access/AccessControlEnumerable.sol | 6 +-- .../IAccessControlDefaultAdminRules.sol | 2 +- contracts/access/IAccessControlEnumerable.sol | 2 +- contracts/access/Ownable.sol | 2 +- contracts/access/Ownable2Step.sol | 2 +- contracts/finance/VestingWallet.sol | 7 ++-- contracts/governance/Governor.sol | 20 +++++----- contracts/governance/IGovernor.sol | 4 +- contracts/governance/TimelockController.sol | 9 +++-- .../GovernorCompatibilityBravo.sol | 8 ++-- .../IGovernorCompatibilityBravo.sol | 2 +- .../extensions/GovernorCountingSimple.sol | 2 +- .../extensions/GovernorPreventLateQuorum.sol | 4 +- .../extensions/GovernorSettings.sol | 2 +- .../extensions/GovernorTimelockCompound.sol | 10 +++-- .../extensions/GovernorTimelockControl.sol | 7 ++-- .../governance/extensions/GovernorVotes.sol | 6 ++- .../GovernorVotesQuorumFraction.sol | 6 +-- .../extensions/IGovernorTimelock.sol | 2 +- contracts/governance/utils/Votes.sol | 12 +++--- contracts/interfaces/IERC1155.sol | 2 +- contracts/interfaces/IERC1155MetadataURI.sol | 2 +- contracts/interfaces/IERC1155Receiver.sol | 2 +- contracts/interfaces/IERC1363.sol | 4 +- contracts/interfaces/IERC165.sol | 2 +- contracts/interfaces/IERC20.sol | 2 +- contracts/interfaces/IERC20Metadata.sol | 2 +- contracts/interfaces/IERC2612.sol | 2 +- contracts/interfaces/IERC2981.sol | 2 +- contracts/interfaces/IERC3156.sol | 4 +- contracts/interfaces/IERC3156FlashLender.sol | 2 +- contracts/interfaces/IERC4626.sol | 4 +- contracts/interfaces/IERC4906.sol | 4 +- contracts/interfaces/IERC5805.sol | 4 +- contracts/interfaces/IERC721.sol | 2 +- contracts/interfaces/IERC721Enumerable.sol | 2 +- contracts/interfaces/IERC721Metadata.sol | 2 +- contracts/interfaces/IERC721Receiver.sol | 2 +- contracts/metatx/ERC2771Context.sol | 2 +- contracts/metatx/ERC2771Forwarder.sol | 8 ++-- contracts/mocks/AddressFnPointersMock.sol | 2 +- contracts/mocks/ArraysMock.sol | 2 +- contracts/mocks/ContextMock.sol | 2 +- contracts/mocks/DummyImplementation.sol | 3 +- contracts/mocks/EIP712Verifier.sol | 4 +- contracts/mocks/ERC1271WalletMock.sol | 6 +-- .../ERC165/ERC165InterfacesSupported.sol | 2 +- contracts/mocks/ERC165/ERC165ReturnBomb.sol | 2 +- contracts/mocks/ERC2771ContextMock.sol | 5 ++- contracts/mocks/ERC3156FlashBorrowerMock.sol | 6 +-- contracts/mocks/InitializableMock.sol | 2 +- contracts/mocks/MulticallTest.sol | 2 +- .../MultipleInheritanceInitializableMocks.sol | 2 +- contracts/mocks/PausableMock.sol | 2 +- contracts/mocks/ReentrancyAttack.sol | 2 +- contracts/mocks/ReentrancyMock.sol | 4 +- contracts/mocks/RegressionImplementation.sol | 2 +- .../SingleInheritanceInitializableMocks.sol | 2 +- contracts/mocks/StorageSlotMock.sol | 2 +- contracts/mocks/TimelockReentrant.sol | 2 +- contracts/mocks/VotesMock.sol | 2 +- contracts/mocks/docs/ERC4626Fees.sol | 5 ++- .../mocks/docs/governance/MyGovernor.sol | 13 ++++--- contracts/mocks/docs/governance/MyToken.sol | 7 ++-- .../docs/governance/MyTokenTimestampBased.sol | 7 ++-- .../mocks/docs/governance/MyTokenWrapped.sol | 9 +++-- .../GovernorCompatibilityBravoMock.sol | 10 +++-- contracts/mocks/governance/GovernorMock.sol | 7 ++-- .../GovernorPreventLateQuorumMock.sol | 9 +++-- .../GovernorTimelockCompoundMock.sol | 9 +++-- .../GovernorTimelockControlMock.sol | 9 +++-- .../mocks/governance/GovernorVoteMock.sol | 4 +- .../governance/GovernorWithParamsMock.sol | 5 ++- contracts/mocks/proxy/UUPSUpgradeableMock.sol | 3 +- contracts/mocks/token/ERC1155ReceiverMock.sol | 4 +- contracts/mocks/token/ERC20ApprovalMock.sol | 2 +- contracts/mocks/token/ERC20DecimalsMock.sol | 2 +- contracts/mocks/token/ERC20FlashMintMock.sol | 2 +- .../mocks/token/ERC20ForceApproveMock.sol | 2 +- contracts/mocks/token/ERC20Mock.sol | 2 +- contracts/mocks/token/ERC20MulticallMock.sol | 4 +- contracts/mocks/token/ERC20NoReturnMock.sol | 2 +- .../mocks/token/ERC20PermitNoRevertMock.sol | 4 +- contracts/mocks/token/ERC20Reentrant.sol | 4 +- .../mocks/token/ERC20ReturnFalseMock.sol | 2 +- .../mocks/token/ERC20VotesLegacyMock.sol | 10 ++--- contracts/mocks/token/ERC4626LimitsMock.sol | 2 +- contracts/mocks/token/ERC4626Mock.sol | 3 +- contracts/mocks/token/ERC4626OffsetMock.sol | 2 +- contracts/mocks/token/ERC4646FeesMock.sol | 2 +- .../token/ERC721ConsecutiveEnumerableMock.sol | 5 ++- .../mocks/token/ERC721ConsecutiveMock.sol | 8 ++-- contracts/mocks/token/ERC721ReceiverMock.sol | 2 +- .../mocks/token/ERC721URIStorageMock.sol | 2 +- contracts/mocks/token/VotesTimestamp.sol | 5 ++- contracts/proxy/ERC1967/ERC1967Proxy.sol | 4 +- contracts/proxy/ERC1967/ERC1967Utils.sol | 6 +-- contracts/proxy/beacon/BeaconProxy.sol | 6 +-- contracts/proxy/beacon/UpgradeableBeacon.sol | 4 +- contracts/proxy/transparent/ProxyAdmin.sol | 4 +- .../TransparentUpgradeableProxy.sol | 5 ++- contracts/proxy/utils/Initializable.sol | 2 +- contracts/proxy/utils/UUPSUpgradeable.sol | 4 +- contracts/security/Pausable.sol | 2 +- contracts/token/ERC1155/ERC1155.sol | 14 +++---- contracts/token/ERC1155/IERC1155.sol | 2 +- contracts/token/ERC1155/IERC1155Receiver.sol | 2 +- .../ERC1155/extensions/ERC1155Burnable.sol | 2 +- .../ERC1155/extensions/ERC1155Pausable.sol | 4 +- .../ERC1155/extensions/ERC1155Supply.sol | 2 +- .../ERC1155/extensions/ERC1155URIStorage.sol | 4 +- .../extensions/IERC1155MetadataURI.sol | 2 +- .../token/ERC1155/utils/ERC1155Holder.sol | 2 +- .../token/ERC1155/utils/ERC1155Receiver.sol | 4 +- contracts/token/ERC20/ERC20.sol | 8 ++-- .../token/ERC20/extensions/ERC20Burnable.sol | 4 +- .../token/ERC20/extensions/ERC20Capped.sol | 2 +- .../token/ERC20/extensions/ERC20FlashMint.sol | 6 +-- .../token/ERC20/extensions/ERC20Pausable.sol | 4 +- .../token/ERC20/extensions/ERC20Permit.sol | 10 ++--- .../token/ERC20/extensions/ERC20Votes.sol | 7 ++-- .../token/ERC20/extensions/ERC20Wrapper.sol | 4 +- contracts/token/ERC20/extensions/ERC4626.sol | 8 ++-- .../token/ERC20/extensions/IERC20Metadata.sol | 2 +- contracts/token/ERC20/utils/SafeERC20.sol | 6 +-- contracts/token/ERC721/ERC721.sol | 14 +++---- contracts/token/ERC721/IERC721.sol | 2 +- .../ERC721/extensions/ERC721Burnable.sol | 4 +- .../ERC721/extensions/ERC721Consecutive.sol | 8 ++-- .../ERC721/extensions/ERC721Enumerable.sol | 5 ++- .../ERC721/extensions/ERC721Pausable.sol | 4 +- .../token/ERC721/extensions/ERC721Royalty.sol | 6 +-- .../ERC721/extensions/ERC721URIStorage.sol | 6 ++- .../token/ERC721/extensions/ERC721Votes.sol | 4 +- .../token/ERC721/extensions/ERC721Wrapper.sol | 3 +- .../ERC721/extensions/IERC721Enumerable.sol | 2 +- .../ERC721/extensions/IERC721Metadata.sol | 2 +- contracts/token/ERC721/utils/ERC721Holder.sol | 2 +- contracts/token/common/ERC2981.sol | 4 +- contracts/utils/Arrays.sol | 4 +- contracts/utils/Multicall.sol | 2 +- contracts/utils/ShortStrings.sol | 2 +- contracts/utils/Strings.sol | 4 +- contracts/utils/cryptography/ECDSA.sol | 2 +- contracts/utils/cryptography/EIP712.sol | 6 +-- .../utils/cryptography/SignatureChecker.sol | 4 +- contracts/utils/introspection/ERC165.sol | 2 +- .../utils/introspection/ERC165Checker.sol | 2 +- contracts/utils/structs/Checkpoints.sol | 4 +- contracts/utils/structs/DoubleEndedQueue.sol | 2 +- contracts/utils/structs/EnumerableMap.sol | 2 +- docs/modules/ROOT/pages/access-control.adoc | 14 +++---- docs/modules/ROOT/pages/erc1155.adoc | 4 +- docs/modules/ROOT/pages/erc20.adoc | 2 +- docs/modules/ROOT/pages/erc721.adoc | 2 +- .../ROOT/pages/extending-contracts.adoc | 6 +-- docs/modules/ROOT/pages/governance.adoc | 12 +++--- docs/modules/ROOT/pages/index.adoc | 2 +- docs/modules/ROOT/pages/upgradeable.adoc | 4 +- docs/modules/ROOT/pages/utilities.adoc | 6 +-- package-lock.json | 14 +++---- package.json | 2 +- scripts/generate/templates/Checkpoints.js | 6 +-- scripts/generate/templates/Checkpoints.t.js | 6 +-- scripts/generate/templates/EnumerableMap.js | 2 +- scripts/upgradeable/upgradeable.patch | 38 +++++++++---------- test/governance/Governor.t.sol | 6 +-- .../ERC721/extensions/ERC721Consecutive.t.sol | 5 ++- test/utils/ShortStrings.t.sol | 4 +- test/utils/math/Math.t.sol | 4 +- test/utils/structs/Checkpoints.t.sol | 6 +-- 176 files changed, 428 insertions(+), 379 deletions(-) create mode 100644 .changeset/orange-apes-draw.md diff --git a/.changeset/orange-apes-draw.md b/.changeset/orange-apes-draw.md new file mode 100644 index 000000000..5f2b7d928 --- /dev/null +++ b/.changeset/orange-apes-draw.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': major +--- + +Switched to using explicit Solidity import statements. Some previously available symbols may now have to be separately imported. diff --git a/.solhint.json b/.solhint.json index 772928849..cb8a8af6d 100644 --- a/.solhint.json +++ b/.solhint.json @@ -9,6 +9,7 @@ "modifier-name-mixedcase": "error", "private-vars-leading-underscore": "error", "var-name-mixedcase": "error", - "imports-on-top": "error" + "imports-on-top": "error", + "no-global-import": "error" } } diff --git a/README.md b/README.md index 9d1c405b6..27627f439 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ Once installed, you can use the contracts in the library by importing them: ```solidity pragma solidity ^0.8.19; -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; +import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; contract MyCollectible is ERC721 { constructor() ERC721("MyCollectible", "MCO") { diff --git a/contracts/access/AccessControl.sol b/contracts/access/AccessControl.sol index 12dc770b3..8465fefbd 100644 --- a/contracts/access/AccessControl.sol +++ b/contracts/access/AccessControl.sol @@ -3,10 +3,10 @@ pragma solidity ^0.8.19; -import "./IAccessControl.sol"; -import "../utils/Context.sol"; -import "../utils/Strings.sol"; -import "../utils/introspection/ERC165.sol"; +import {IAccessControl} from "./IAccessControl.sol"; +import {Context} from "../utils/Context.sol"; +import {Strings} from "../utils/Strings.sol"; +import {ERC165} from "../utils/introspection/ERC165.sol"; /** * @dev Contract module that allows children to implement role-based access diff --git a/contracts/access/AccessControlDefaultAdminRules.sol b/contracts/access/AccessControlDefaultAdminRules.sol index e27eaf3db..ebc40a5bf 100644 --- a/contracts/access/AccessControlDefaultAdminRules.sol +++ b/contracts/access/AccessControlDefaultAdminRules.sol @@ -3,10 +3,11 @@ pragma solidity ^0.8.19; -import "./AccessControl.sol"; -import "./IAccessControlDefaultAdminRules.sol"; -import "../utils/math/SafeCast.sol"; -import "../interfaces/IERC5313.sol"; +import {AccessControl, IAccessControl} from "./AccessControl.sol"; +import {IAccessControlDefaultAdminRules} from "./IAccessControlDefaultAdminRules.sol"; +import {SafeCast} from "../utils/math/SafeCast.sol"; +import {Math} from "../utils/math/Math.sol"; +import {IERC5313} from "../interfaces/IERC5313.sol"; /** * @dev Extension of {AccessControl} that allows specifying special rules to manage diff --git a/contracts/access/AccessControlEnumerable.sol b/contracts/access/AccessControlEnumerable.sol index 297d34536..6f160e4e8 100644 --- a/contracts/access/AccessControlEnumerable.sol +++ b/contracts/access/AccessControlEnumerable.sol @@ -3,9 +3,9 @@ pragma solidity ^0.8.19; -import "./IAccessControlEnumerable.sol"; -import "./AccessControl.sol"; -import "../utils/structs/EnumerableSet.sol"; +import {IAccessControlEnumerable} from "./IAccessControlEnumerable.sol"; +import {AccessControl} from "./AccessControl.sol"; +import {EnumerableSet} from "../utils/structs/EnumerableSet.sol"; /** * @dev Extension of {AccessControl} that allows enumerating the members of each role. diff --git a/contracts/access/IAccessControlDefaultAdminRules.sol b/contracts/access/IAccessControlDefaultAdminRules.sol index fbecfe120..5e61f965d 100644 --- a/contracts/access/IAccessControlDefaultAdminRules.sol +++ b/contracts/access/IAccessControlDefaultAdminRules.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.19; -import "./IAccessControl.sol"; +import {IAccessControl} from "./IAccessControl.sol"; /** * @dev External interface of AccessControlDefaultAdminRules declared to support ERC165 detection. diff --git a/contracts/access/IAccessControlEnumerable.sol b/contracts/access/IAccessControlEnumerable.sol index 240c61157..1bd88a4f4 100644 --- a/contracts/access/IAccessControlEnumerable.sol +++ b/contracts/access/IAccessControlEnumerable.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.19; -import "./IAccessControl.sol"; +import {IAccessControl} from "./IAccessControl.sol"; /** * @dev External interface of AccessControlEnumerable declared to support ERC165 detection. diff --git a/contracts/access/Ownable.sol b/contracts/access/Ownable.sol index 627226cf7..ca09e0350 100644 --- a/contracts/access/Ownable.sol +++ b/contracts/access/Ownable.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.19; -import "../utils/Context.sol"; +import {Context} from "../utils/Context.sol"; /** * @dev Contract module which provides a basic access control mechanism, where diff --git a/contracts/access/Ownable2Step.sol b/contracts/access/Ownable2Step.sol index f76eb2bba..3cbab1798 100644 --- a/contracts/access/Ownable2Step.sol +++ b/contracts/access/Ownable2Step.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.19; -import "./Ownable.sol"; +import {Ownable} from "./Ownable.sol"; /** * @dev Contract module which provides access control mechanism, where diff --git a/contracts/finance/VestingWallet.sol b/contracts/finance/VestingWallet.sol index ebdf0a330..f776a7ca4 100644 --- a/contracts/finance/VestingWallet.sol +++ b/contracts/finance/VestingWallet.sol @@ -2,9 +2,10 @@ // OpenZeppelin Contracts (last updated v4.9.0) (finance/VestingWallet.sol) pragma solidity ^0.8.19; -import "../token/ERC20/utils/SafeERC20.sol"; -import "../utils/Address.sol"; -import "../utils/Context.sol"; +import {IERC20} from "../token/ERC20/IERC20.sol"; +import {SafeERC20} from "../token/ERC20/utils/SafeERC20.sol"; +import {Address} from "../utils/Address.sol"; +import {Context} from "../utils/Context.sol"; /** * @title VestingWallet diff --git a/contracts/governance/Governor.sol b/contracts/governance/Governor.sol index bae5d62e6..d0ace4f03 100644 --- a/contracts/governance/Governor.sol +++ b/contracts/governance/Governor.sol @@ -3,16 +3,16 @@ pragma solidity ^0.8.19; -import "../token/ERC721/IERC721Receiver.sol"; -import "../token/ERC1155/IERC1155Receiver.sol"; -import "../utils/cryptography/ECDSA.sol"; -import "../utils/cryptography/EIP712.sol"; -import "../utils/introspection/ERC165.sol"; -import "../utils/math/SafeCast.sol"; -import "../utils/structs/DoubleEndedQueue.sol"; -import "../utils/Address.sol"; -import "../utils/Context.sol"; -import "./IGovernor.sol"; +import {IERC721Receiver} from "../token/ERC721/IERC721Receiver.sol"; +import {IERC1155Receiver} from "../token/ERC1155/IERC1155Receiver.sol"; +import {ECDSA} from "../utils/cryptography/ECDSA.sol"; +import {EIP712} from "../utils/cryptography/EIP712.sol"; +import {IERC165, ERC165} from "../utils/introspection/ERC165.sol"; +import {SafeCast} from "../utils/math/SafeCast.sol"; +import {DoubleEndedQueue} from "../utils/structs/DoubleEndedQueue.sol"; +import {Address} from "../utils/Address.sol"; +import {Context} from "../utils/Context.sol"; +import {IGovernor, IERC6372} from "./IGovernor.sol"; /** * @dev Core of the governance system, designed to be extended though various modules. diff --git a/contracts/governance/IGovernor.sol b/contracts/governance/IGovernor.sol index 0adde6795..2eeedd313 100644 --- a/contracts/governance/IGovernor.sol +++ b/contracts/governance/IGovernor.sol @@ -3,8 +3,8 @@ pragma solidity ^0.8.19; -import "../interfaces/IERC165.sol"; -import "../interfaces/IERC6372.sol"; +import {IERC165} from "../interfaces/IERC165.sol"; +import {IERC6372} from "../interfaces/IERC6372.sol"; /** * @dev Interface of the {Governor} core. diff --git a/contracts/governance/TimelockController.sol b/contracts/governance/TimelockController.sol index a25cd7b4a..45d8642e8 100644 --- a/contracts/governance/TimelockController.sol +++ b/contracts/governance/TimelockController.sol @@ -3,10 +3,11 @@ pragma solidity ^0.8.19; -import "../access/AccessControl.sol"; -import "../token/ERC721/utils/ERC721Holder.sol"; -import "../token/ERC1155/utils/ERC1155Holder.sol"; -import "../utils/Address.sol"; +import {AccessControl} from "../access/AccessControl.sol"; +import {ERC721Holder} from "../token/ERC721/utils/ERC721Holder.sol"; +import {ERC1155Holder} from "../token/ERC1155/utils/ERC1155Holder.sol"; +import {ERC1155Receiver} from "../token/ERC1155/utils/ERC1155Receiver.sol"; +import {Address} from "../utils/Address.sol"; /** * @dev Contract module which acts as a timelocked controller. When set as the diff --git a/contracts/governance/compatibility/GovernorCompatibilityBravo.sol b/contracts/governance/compatibility/GovernorCompatibilityBravo.sol index 8d6d9ccb4..445f71be0 100644 --- a/contracts/governance/compatibility/GovernorCompatibilityBravo.sol +++ b/contracts/governance/compatibility/GovernorCompatibilityBravo.sol @@ -3,10 +3,10 @@ pragma solidity ^0.8.19; -import "../../utils/math/SafeCast.sol"; -import "../extensions/IGovernorTimelock.sol"; -import "../Governor.sol"; -import "./IGovernorCompatibilityBravo.sol"; +import {SafeCast} from "../../utils/math/SafeCast.sol"; +import {IGovernorTimelock} from "../extensions/IGovernorTimelock.sol"; +import {IGovernor, Governor} from "../Governor.sol"; +import {IGovernorCompatibilityBravo} from "./IGovernorCompatibilityBravo.sol"; /** * @dev Compatibility layer that implements GovernorBravo compatibility on top of {Governor}. diff --git a/contracts/governance/compatibility/IGovernorCompatibilityBravo.sol b/contracts/governance/compatibility/IGovernorCompatibilityBravo.sol index 197936ab9..4bd593077 100644 --- a/contracts/governance/compatibility/IGovernorCompatibilityBravo.sol +++ b/contracts/governance/compatibility/IGovernorCompatibilityBravo.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.19; -import "../IGovernor.sol"; +import {IGovernor} from "../IGovernor.sol"; /** * @dev Interface extension that adds missing functions to the {Governor} core to provide `GovernorBravo` compatibility. diff --git a/contracts/governance/extensions/GovernorCountingSimple.sol b/contracts/governance/extensions/GovernorCountingSimple.sol index 315f4ad45..8934995de 100644 --- a/contracts/governance/extensions/GovernorCountingSimple.sol +++ b/contracts/governance/extensions/GovernorCountingSimple.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.19; -import "../Governor.sol"; +import {Governor} from "../Governor.sol"; /** * @dev Extension of {Governor} for simple, 3 options, vote counting. diff --git a/contracts/governance/extensions/GovernorPreventLateQuorum.sol b/contracts/governance/extensions/GovernorPreventLateQuorum.sol index 3e730174e..3e034acad 100644 --- a/contracts/governance/extensions/GovernorPreventLateQuorum.sol +++ b/contracts/governance/extensions/GovernorPreventLateQuorum.sol @@ -3,8 +3,8 @@ pragma solidity ^0.8.19; -import "../Governor.sol"; -import "../../utils/math/Math.sol"; +import {Governor} from "../Governor.sol"; +import {Math} from "../../utils/math/Math.sol"; /** * @dev A module that ensures there is a minimum voting period after quorum is reached. This prevents a large voter from diff --git a/contracts/governance/extensions/GovernorSettings.sol b/contracts/governance/extensions/GovernorSettings.sol index 6168689ad..5d98fbf34 100644 --- a/contracts/governance/extensions/GovernorSettings.sol +++ b/contracts/governance/extensions/GovernorSettings.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.19; -import "../Governor.sol"; +import {Governor} from "../Governor.sol"; /** * @dev Extension of {Governor} for settings updatable through governance. diff --git a/contracts/governance/extensions/GovernorTimelockCompound.sol b/contracts/governance/extensions/GovernorTimelockCompound.sol index ed4d916a6..cd61d1ff8 100644 --- a/contracts/governance/extensions/GovernorTimelockCompound.sol +++ b/contracts/governance/extensions/GovernorTimelockCompound.sol @@ -3,10 +3,12 @@ pragma solidity ^0.8.19; -import "./IGovernorTimelock.sol"; -import "../Governor.sol"; -import "../../utils/math/SafeCast.sol"; -import "../../vendor/compound/ICompoundTimelock.sol"; +import {IGovernorTimelock} from "./IGovernorTimelock.sol"; +import {IGovernor, Governor} from "../Governor.sol"; +import {SafeCast} from "../../utils/math/SafeCast.sol"; +import {ICompoundTimelock} from "../../vendor/compound/ICompoundTimelock.sol"; +import {IERC165} from "../../interfaces/IERC165.sol"; +import {Address} from "../../utils/Address.sol"; /** * @dev Extension of {Governor} that binds the execution process to a Compound Timelock. This adds a delay, enforced by diff --git a/contracts/governance/extensions/GovernorTimelockControl.sol b/contracts/governance/extensions/GovernorTimelockControl.sol index fadbcc701..fefe31555 100644 --- a/contracts/governance/extensions/GovernorTimelockControl.sol +++ b/contracts/governance/extensions/GovernorTimelockControl.sol @@ -3,9 +3,10 @@ pragma solidity ^0.8.19; -import "./IGovernorTimelock.sol"; -import "../Governor.sol"; -import "../TimelockController.sol"; +import {IGovernorTimelock} from "./IGovernorTimelock.sol"; +import {IGovernor, Governor} from "../Governor.sol"; +import {TimelockController} from "../TimelockController.sol"; +import {IERC165} from "../../interfaces/IERC165.sol"; /** * @dev Extension of {Governor} that binds the execution process to an instance of {TimelockController}. This adds a diff --git a/contracts/governance/extensions/GovernorVotes.sol b/contracts/governance/extensions/GovernorVotes.sol index 5d8318f46..bb14d7fde 100644 --- a/contracts/governance/extensions/GovernorVotes.sol +++ b/contracts/governance/extensions/GovernorVotes.sol @@ -3,8 +3,10 @@ pragma solidity ^0.8.19; -import "../Governor.sol"; -import "../../interfaces/IERC5805.sol"; +import {Governor} from "../Governor.sol"; +import {IVotes} from "../utils/IVotes.sol"; +import {IERC5805} from "../../interfaces/IERC5805.sol"; +import {SafeCast} from "../../utils/math/SafeCast.sol"; /** * @dev Extension of {Governor} for voting weight extraction from an {ERC20Votes} token, or since v4.5 an {ERC721Votes} token. diff --git a/contracts/governance/extensions/GovernorVotesQuorumFraction.sol b/contracts/governance/extensions/GovernorVotesQuorumFraction.sol index 0094fecd6..bb1c7d2ea 100644 --- a/contracts/governance/extensions/GovernorVotesQuorumFraction.sol +++ b/contracts/governance/extensions/GovernorVotesQuorumFraction.sol @@ -3,9 +3,9 @@ pragma solidity ^0.8.19; -import "./GovernorVotes.sol"; -import "../../utils/math/SafeCast.sol"; -import "../../utils/structs/Checkpoints.sol"; +import {GovernorVotes} from "./GovernorVotes.sol"; +import {SafeCast} from "../../utils/math/SafeCast.sol"; +import {Checkpoints} from "../../utils/structs/Checkpoints.sol"; /** * @dev Extension of {Governor} for voting weight extraction from an {ERC20Votes} token and a quorum expressed as a diff --git a/contracts/governance/extensions/IGovernorTimelock.sol b/contracts/governance/extensions/IGovernorTimelock.sol index c51429481..00254853a 100644 --- a/contracts/governance/extensions/IGovernorTimelock.sol +++ b/contracts/governance/extensions/IGovernorTimelock.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.19; -import "../IGovernor.sol"; +import {IGovernor} from "../IGovernor.sol"; /** * @dev Extension of the {IGovernor} for timelock supporting modules. diff --git a/contracts/governance/utils/Votes.sol b/contracts/governance/utils/Votes.sol index 09eb4e22c..c0ff69e62 100644 --- a/contracts/governance/utils/Votes.sol +++ b/contracts/governance/utils/Votes.sol @@ -2,11 +2,13 @@ // OpenZeppelin Contracts (last updated v4.9.0) (governance/utils/Votes.sol) pragma solidity ^0.8.19; -import "../../interfaces/IERC5805.sol"; -import "../../utils/Context.sol"; -import "../../utils/Nonces.sol"; -import "../../utils/cryptography/EIP712.sol"; -import "../../utils/structs/Checkpoints.sol"; +import {IERC5805} from "../../interfaces/IERC5805.sol"; +import {Context} from "../../utils/Context.sol"; +import {Nonces} from "../../utils/Nonces.sol"; +import {EIP712} from "../../utils/cryptography/EIP712.sol"; +import {Checkpoints} from "../../utils/structs/Checkpoints.sol"; +import {SafeCast} from "../../utils/math/SafeCast.sol"; +import {ECDSA} from "../../utils/cryptography/ECDSA.sol"; /** * @dev This is a base abstract contract that tracks voting units, which are a measure of voting power that can be diff --git a/contracts/interfaces/IERC1155.sol b/contracts/interfaces/IERC1155.sol index 8f7527f91..6c9ef7f6c 100644 --- a/contracts/interfaces/IERC1155.sol +++ b/contracts/interfaces/IERC1155.sol @@ -3,4 +3,4 @@ pragma solidity ^0.8.19; -import "../token/ERC1155/IERC1155.sol"; +import {IERC1155} from "../token/ERC1155/IERC1155.sol"; diff --git a/contracts/interfaces/IERC1155MetadataURI.sol b/contracts/interfaces/IERC1155MetadataURI.sol index 61b36c2e8..7c202be48 100644 --- a/contracts/interfaces/IERC1155MetadataURI.sol +++ b/contracts/interfaces/IERC1155MetadataURI.sol @@ -3,4 +3,4 @@ pragma solidity ^0.8.19; -import "../token/ERC1155/extensions/IERC1155MetadataURI.sol"; +import {IERC1155MetadataURI} from "../token/ERC1155/extensions/IERC1155MetadataURI.sol"; diff --git a/contracts/interfaces/IERC1155Receiver.sol b/contracts/interfaces/IERC1155Receiver.sol index b5cd186b7..c67ed38bd 100644 --- a/contracts/interfaces/IERC1155Receiver.sol +++ b/contracts/interfaces/IERC1155Receiver.sol @@ -3,4 +3,4 @@ pragma solidity ^0.8.19; -import "../token/ERC1155/IERC1155Receiver.sol"; +import {IERC1155Receiver} from "../token/ERC1155/IERC1155Receiver.sol"; diff --git a/contracts/interfaces/IERC1363.sol b/contracts/interfaces/IERC1363.sol index 95c5b8992..2bbba32b6 100644 --- a/contracts/interfaces/IERC1363.sol +++ b/contracts/interfaces/IERC1363.sol @@ -3,8 +3,8 @@ pragma solidity ^0.8.19; -import "./IERC20.sol"; -import "./IERC165.sol"; +import {IERC20} from "./IERC20.sol"; +import {IERC165} from "./IERC165.sol"; /** * @dev Interface of an ERC1363 compliant contract, as defined in the diff --git a/contracts/interfaces/IERC165.sol b/contracts/interfaces/IERC165.sol index f4d90264c..3ac6e69dd 100644 --- a/contracts/interfaces/IERC165.sol +++ b/contracts/interfaces/IERC165.sol @@ -3,4 +3,4 @@ pragma solidity ^0.8.19; -import "../utils/introspection/IERC165.sol"; +import {IERC165} from "../utils/introspection/IERC165.sol"; diff --git a/contracts/interfaces/IERC20.sol b/contracts/interfaces/IERC20.sol index dd559e980..3ff660480 100644 --- a/contracts/interfaces/IERC20.sol +++ b/contracts/interfaces/IERC20.sol @@ -3,4 +3,4 @@ pragma solidity ^0.8.19; -import "../token/ERC20/IERC20.sol"; +import {IERC20} from "../token/ERC20/IERC20.sol"; diff --git a/contracts/interfaces/IERC20Metadata.sol b/contracts/interfaces/IERC20Metadata.sol index 061fd4a2f..edb680446 100644 --- a/contracts/interfaces/IERC20Metadata.sol +++ b/contracts/interfaces/IERC20Metadata.sol @@ -3,4 +3,4 @@ pragma solidity ^0.8.19; -import "../token/ERC20/extensions/IERC20Metadata.sol"; +import {IERC20Metadata} from "../token/ERC20/extensions/IERC20Metadata.sol"; diff --git a/contracts/interfaces/IERC2612.sol b/contracts/interfaces/IERC2612.sol index 582eea81d..3a1b35266 100644 --- a/contracts/interfaces/IERC2612.sol +++ b/contracts/interfaces/IERC2612.sol @@ -3,6 +3,6 @@ pragma solidity ^0.8.19; -import "../token/ERC20/extensions/IERC20Permit.sol"; +import {IERC20Permit} from "../token/ERC20/extensions/IERC20Permit.sol"; interface IERC2612 is IERC20Permit {} diff --git a/contracts/interfaces/IERC2981.sol b/contracts/interfaces/IERC2981.sol index 1b1476782..ba0f98c99 100644 --- a/contracts/interfaces/IERC2981.sol +++ b/contracts/interfaces/IERC2981.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.19; -import "../utils/introspection/IERC165.sol"; +import {IERC165} from "../utils/introspection/IERC165.sol"; /** * @dev Interface for the NFT Royalty Standard. diff --git a/contracts/interfaces/IERC3156.sol b/contracts/interfaces/IERC3156.sol index 280b25be1..6bd56088b 100644 --- a/contracts/interfaces/IERC3156.sol +++ b/contracts/interfaces/IERC3156.sol @@ -3,5 +3,5 @@ pragma solidity ^0.8.19; -import "./IERC3156FlashBorrower.sol"; -import "./IERC3156FlashLender.sol"; +import {IERC3156FlashBorrower} from "./IERC3156FlashBorrower.sol"; +import {IERC3156FlashLender} from "./IERC3156FlashLender.sol"; diff --git a/contracts/interfaces/IERC3156FlashLender.sol b/contracts/interfaces/IERC3156FlashLender.sol index 89a486fdb..e5dd9c523 100644 --- a/contracts/interfaces/IERC3156FlashLender.sol +++ b/contracts/interfaces/IERC3156FlashLender.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.19; -import "./IERC3156FlashBorrower.sol"; +import {IERC3156FlashBorrower} from "./IERC3156FlashBorrower.sol"; /** * @dev Interface of the ERC3156 FlashLender, as defined in diff --git a/contracts/interfaces/IERC4626.sol b/contracts/interfaces/IERC4626.sol index 946de9155..27735f6b9 100644 --- a/contracts/interfaces/IERC4626.sol +++ b/contracts/interfaces/IERC4626.sol @@ -3,8 +3,8 @@ pragma solidity ^0.8.19; -import "../token/ERC20/IERC20.sol"; -import "../token/ERC20/extensions/IERC20Metadata.sol"; +import {IERC20} from "../token/ERC20/IERC20.sol"; +import {IERC20Metadata} from "../token/ERC20/extensions/IERC20Metadata.sol"; /** * @dev Interface of the ERC4626 "Tokenized Vault Standard", as defined in diff --git a/contracts/interfaces/IERC4906.sol b/contracts/interfaces/IERC4906.sol index 96e709228..fecdb0c52 100644 --- a/contracts/interfaces/IERC4906.sol +++ b/contracts/interfaces/IERC4906.sol @@ -3,8 +3,8 @@ pragma solidity ^0.8.19; -import "./IERC165.sol"; -import "./IERC721.sol"; +import {IERC165} from "./IERC165.sol"; +import {IERC721} from "./IERC721.sol"; /// @title EIP-721 Metadata Update Extension interface IERC4906 is IERC165, IERC721 { diff --git a/contracts/interfaces/IERC5805.sol b/contracts/interfaces/IERC5805.sol index 3c0a3a6c6..6eb89919f 100644 --- a/contracts/interfaces/IERC5805.sol +++ b/contracts/interfaces/IERC5805.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.19; -import "../governance/utils/IVotes.sol"; -import "./IERC6372.sol"; +import {IVotes} from "../governance/utils/IVotes.sol"; +import {IERC6372} from "./IERC6372.sol"; interface IERC5805 is IERC6372, IVotes {} diff --git a/contracts/interfaces/IERC721.sol b/contracts/interfaces/IERC721.sol index e840f28c7..f086ab6fb 100644 --- a/contracts/interfaces/IERC721.sol +++ b/contracts/interfaces/IERC721.sol @@ -3,4 +3,4 @@ pragma solidity ^0.8.19; -import "../token/ERC721/IERC721.sol"; +import {IERC721} from "../token/ERC721/IERC721.sol"; diff --git a/contracts/interfaces/IERC721Enumerable.sol b/contracts/interfaces/IERC721Enumerable.sol index fafda5998..e5ce68d99 100644 --- a/contracts/interfaces/IERC721Enumerable.sol +++ b/contracts/interfaces/IERC721Enumerable.sol @@ -3,4 +3,4 @@ pragma solidity ^0.8.19; -import "../token/ERC721/extensions/IERC721Enumerable.sol"; +import {IERC721Enumerable} from "../token/ERC721/extensions/IERC721Enumerable.sol"; diff --git a/contracts/interfaces/IERC721Metadata.sol b/contracts/interfaces/IERC721Metadata.sol index f14433e8d..5b2ad5687 100644 --- a/contracts/interfaces/IERC721Metadata.sol +++ b/contracts/interfaces/IERC721Metadata.sol @@ -3,4 +3,4 @@ pragma solidity ^0.8.19; -import "../token/ERC721/extensions/IERC721Metadata.sol"; +import {IERC721Metadata} from "../token/ERC721/extensions/IERC721Metadata.sol"; diff --git a/contracts/interfaces/IERC721Receiver.sol b/contracts/interfaces/IERC721Receiver.sol index 9e62fa734..6ee3d3d80 100644 --- a/contracts/interfaces/IERC721Receiver.sol +++ b/contracts/interfaces/IERC721Receiver.sol @@ -3,4 +3,4 @@ pragma solidity ^0.8.19; -import "../token/ERC721/IERC721Receiver.sol"; +import {IERC721Receiver} from "../token/ERC721/IERC721Receiver.sol"; diff --git a/contracts/metatx/ERC2771Context.sol b/contracts/metatx/ERC2771Context.sol index e02ffcc19..35de9f7ba 100644 --- a/contracts/metatx/ERC2771Context.sol +++ b/contracts/metatx/ERC2771Context.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.19; -import "../utils/Context.sol"; +import {Context} from "../utils/Context.sol"; /** * @dev Context variant with ERC2771 support. diff --git a/contracts/metatx/ERC2771Forwarder.sol b/contracts/metatx/ERC2771Forwarder.sol index 651fdce0b..290b438b8 100644 --- a/contracts/metatx/ERC2771Forwarder.sol +++ b/contracts/metatx/ERC2771Forwarder.sol @@ -3,10 +3,10 @@ pragma solidity ^0.8.19; -import "../utils/cryptography/ECDSA.sol"; -import "../utils/cryptography/EIP712.sol"; -import "../utils/Nonces.sol"; -import "../utils/Address.sol"; +import {ECDSA} from "../utils/cryptography/ECDSA.sol"; +import {EIP712} from "../utils/cryptography/EIP712.sol"; +import {Nonces} from "../utils/Nonces.sol"; +import {Address} from "../utils/Address.sol"; /** * @dev A forwarder compatible with ERC2771 contracts. See {ERC2771Context}. diff --git a/contracts/mocks/AddressFnPointersMock.sol b/contracts/mocks/AddressFnPointersMock.sol index c696b3ec1..9d16a5263 100644 --- a/contracts/mocks/AddressFnPointersMock.sol +++ b/contracts/mocks/AddressFnPointersMock.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.0; -import "../utils/Address.sol"; +import {Address} from "../utils/Address.sol"; /** * @dev A mock to expose `Address`'s functions with function pointers. diff --git a/contracts/mocks/ArraysMock.sol b/contracts/mocks/ArraysMock.sol index b341edc62..7c049b7df 100644 --- a/contracts/mocks/ArraysMock.sol +++ b/contracts/mocks/ArraysMock.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.19; -import "../utils/Arrays.sol"; +import {Arrays} from "../utils/Arrays.sol"; contract Uint256ArraysMock { using Arrays for uint256[]; diff --git a/contracts/mocks/ContextMock.sol b/contracts/mocks/ContextMock.sol index 2e7751d0e..fd105aa91 100644 --- a/contracts/mocks/ContextMock.sol +++ b/contracts/mocks/ContextMock.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.19; -import "../utils/Context.sol"; +import {Context} from "../utils/Context.sol"; contract ContextMock is Context { event Sender(address sender); diff --git a/contracts/mocks/DummyImplementation.sol b/contracts/mocks/DummyImplementation.sol index 71761a755..4d3254602 100644 --- a/contracts/mocks/DummyImplementation.sol +++ b/contracts/mocks/DummyImplementation.sol @@ -2,7 +2,8 @@ pragma solidity ^0.8.19; -import "../proxy/ERC1967/ERC1967Utils.sol"; +import {ERC1967Utils} from "../proxy/ERC1967/ERC1967Utils.sol"; +import {StorageSlot} from "../utils/StorageSlot.sol"; abstract contract Impl { function version() public pure virtual returns (string memory); diff --git a/contracts/mocks/EIP712Verifier.sol b/contracts/mocks/EIP712Verifier.sol index ea28162ba..91a1150f6 100644 --- a/contracts/mocks/EIP712Verifier.sol +++ b/contracts/mocks/EIP712Verifier.sol @@ -2,8 +2,8 @@ pragma solidity ^0.8.19; -import "../utils/cryptography/ECDSA.sol"; -import "../utils/cryptography/EIP712.sol"; +import {ECDSA} from "../utils/cryptography/ECDSA.sol"; +import {EIP712} from "../utils/cryptography/EIP712.sol"; abstract contract EIP712Verifier is EIP712 { function verify(bytes memory signature, address signer, address mailTo, string memory mailContents) external view { diff --git a/contracts/mocks/ERC1271WalletMock.sol b/contracts/mocks/ERC1271WalletMock.sol index 2bc390636..e142fe73e 100644 --- a/contracts/mocks/ERC1271WalletMock.sol +++ b/contracts/mocks/ERC1271WalletMock.sol @@ -2,9 +2,9 @@ pragma solidity ^0.8.19; -import "../access/Ownable.sol"; -import "../interfaces/IERC1271.sol"; -import "../utils/cryptography/ECDSA.sol"; +import {Ownable} from "../access/Ownable.sol"; +import {IERC1271} from "../interfaces/IERC1271.sol"; +import {ECDSA} from "../utils/cryptography/ECDSA.sol"; contract ERC1271WalletMock is Ownable, IERC1271 { constructor(address originalOwner) Ownable(originalOwner) {} diff --git a/contracts/mocks/ERC165/ERC165InterfacesSupported.sol b/contracts/mocks/ERC165/ERC165InterfacesSupported.sol index d21d7c2d0..c9a1df485 100644 --- a/contracts/mocks/ERC165/ERC165InterfacesSupported.sol +++ b/contracts/mocks/ERC165/ERC165InterfacesSupported.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.19; -import "../../utils/introspection/IERC165.sol"; +import {IERC165} from "../../utils/introspection/IERC165.sol"; /** * https://eips.ethereum.org/EIPS/eip-214#specification diff --git a/contracts/mocks/ERC165/ERC165ReturnBomb.sol b/contracts/mocks/ERC165/ERC165ReturnBomb.sol index d2a64151f..99f4685cf 100644 --- a/contracts/mocks/ERC165/ERC165ReturnBomb.sol +++ b/contracts/mocks/ERC165/ERC165ReturnBomb.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.19; -import "../../utils/introspection/IERC165.sol"; +import {IERC165} from "../../utils/introspection/IERC165.sol"; contract ERC165ReturnBombMock is IERC165 { function supportsInterface(bytes4 interfaceId) public pure override returns (bool) { diff --git a/contracts/mocks/ERC2771ContextMock.sol b/contracts/mocks/ERC2771ContextMock.sol index 8c2cb43fd..3cdc211d8 100644 --- a/contracts/mocks/ERC2771ContextMock.sol +++ b/contracts/mocks/ERC2771ContextMock.sol @@ -2,8 +2,9 @@ pragma solidity ^0.8.19; -import "./ContextMock.sol"; -import "../metatx/ERC2771Context.sol"; +import {ContextMock} from "./ContextMock.sol"; +import {Context} from "../utils/Context.sol"; +import {ERC2771Context} from "../metatx/ERC2771Context.sol"; // By inheriting from ERC2771Context, Context's internal functions are overridden automatically contract ERC2771ContextMock is ContextMock, ERC2771Context { diff --git a/contracts/mocks/ERC3156FlashBorrowerMock.sol b/contracts/mocks/ERC3156FlashBorrowerMock.sol index abf836606..c426a7ef7 100644 --- a/contracts/mocks/ERC3156FlashBorrowerMock.sol +++ b/contracts/mocks/ERC3156FlashBorrowerMock.sol @@ -2,9 +2,9 @@ pragma solidity ^0.8.19; -import "../token/ERC20/IERC20.sol"; -import "../interfaces/IERC3156.sol"; -import "../utils/Address.sol"; +import {IERC20} from "../token/ERC20/IERC20.sol"; +import {IERC3156FlashBorrower} from "../interfaces/IERC3156.sol"; +import {Address} from "../utils/Address.sol"; /** * @dev WARNING: this IERC3156FlashBorrower mock implementation is for testing purposes ONLY. diff --git a/contracts/mocks/InitializableMock.sol b/contracts/mocks/InitializableMock.sol index 513aac052..155aaefb7 100644 --- a/contracts/mocks/InitializableMock.sol +++ b/contracts/mocks/InitializableMock.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.19; -import "../proxy/utils/Initializable.sol"; +import {Initializable} from "../proxy/utils/Initializable.sol"; /** * @title InitializableMock diff --git a/contracts/mocks/MulticallTest.sol b/contracts/mocks/MulticallTest.sol index cf89c58df..89a18b3c9 100644 --- a/contracts/mocks/MulticallTest.sol +++ b/contracts/mocks/MulticallTest.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.19; -import "./token/ERC20MulticallMock.sol"; +import {ERC20MulticallMock} from "./token/ERC20MulticallMock.sol"; contract MulticallTest { function checkReturnValues( diff --git a/contracts/mocks/MultipleInheritanceInitializableMocks.sol b/contracts/mocks/MultipleInheritanceInitializableMocks.sol index cb62942ce..d5982f0e3 100644 --- a/contracts/mocks/MultipleInheritanceInitializableMocks.sol +++ b/contracts/mocks/MultipleInheritanceInitializableMocks.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.19; -import "../proxy/utils/Initializable.sol"; +import {Initializable} from "../proxy/utils/Initializable.sol"; // Sample contracts showing upgradeability with multiple inheritance. // Child contract inherits from Father and Mother contracts, and Father extends from Gramps. diff --git a/contracts/mocks/PausableMock.sol b/contracts/mocks/PausableMock.sol index 85d45a3af..56bd4f45f 100644 --- a/contracts/mocks/PausableMock.sol +++ b/contracts/mocks/PausableMock.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.19; -import "../security/Pausable.sol"; +import {Pausable} from "../security/Pausable.sol"; contract PausableMock is Pausable { bool public drasticMeasureTaken; diff --git a/contracts/mocks/ReentrancyAttack.sol b/contracts/mocks/ReentrancyAttack.sol index df2924301..d0ba74c31 100644 --- a/contracts/mocks/ReentrancyAttack.sol +++ b/contracts/mocks/ReentrancyAttack.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.19; -import "../utils/Context.sol"; +import {Context} from "../utils/Context.sol"; contract ReentrancyAttack is Context { function callSender(bytes calldata data) public { diff --git a/contracts/mocks/ReentrancyMock.sol b/contracts/mocks/ReentrancyMock.sol index 053e53d77..1754c1557 100644 --- a/contracts/mocks/ReentrancyMock.sol +++ b/contracts/mocks/ReentrancyMock.sol @@ -2,8 +2,8 @@ pragma solidity ^0.8.19; -import "../security/ReentrancyGuard.sol"; -import "./ReentrancyAttack.sol"; +import {ReentrancyGuard} from "../security/ReentrancyGuard.sol"; +import {ReentrancyAttack} from "./ReentrancyAttack.sol"; contract ReentrancyMock is ReentrancyGuard { uint256 public counter; diff --git a/contracts/mocks/RegressionImplementation.sol b/contracts/mocks/RegressionImplementation.sol index f258bbc0e..bfcf52c44 100644 --- a/contracts/mocks/RegressionImplementation.sol +++ b/contracts/mocks/RegressionImplementation.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.19; -import "../proxy/utils/Initializable.sol"; +import {Initializable} from "../proxy/utils/Initializable.sol"; contract Implementation1 is Initializable { uint256 internal _value; diff --git a/contracts/mocks/SingleInheritanceInitializableMocks.sol b/contracts/mocks/SingleInheritanceInitializableMocks.sol index 2b5fad4eb..d755826d9 100644 --- a/contracts/mocks/SingleInheritanceInitializableMocks.sol +++ b/contracts/mocks/SingleInheritanceInitializableMocks.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.19; -import "../proxy/utils/Initializable.sol"; +import {Initializable} from "../proxy/utils/Initializable.sol"; /** * @title MigratableMockV1 diff --git a/contracts/mocks/StorageSlotMock.sol b/contracts/mocks/StorageSlotMock.sol index 62dd23d6a..7bbd7b232 100644 --- a/contracts/mocks/StorageSlotMock.sol +++ b/contracts/mocks/StorageSlotMock.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.19; -import "../utils/StorageSlot.sol"; +import {StorageSlot} from "../utils/StorageSlot.sol"; contract StorageSlotMock { using StorageSlot for *; diff --git a/contracts/mocks/TimelockReentrant.sol b/contracts/mocks/TimelockReentrant.sol index 803a2b037..de55a87f3 100644 --- a/contracts/mocks/TimelockReentrant.sol +++ b/contracts/mocks/TimelockReentrant.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; -import "../utils/Address.sol"; +import {Address} from "../utils/Address.sol"; contract TimelockReentrant { address private _reenterTarget; diff --git a/contracts/mocks/VotesMock.sol b/contracts/mocks/VotesMock.sol index 697d33448..0bac0087c 100644 --- a/contracts/mocks/VotesMock.sol +++ b/contracts/mocks/VotesMock.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.19; -import "../governance/utils/Votes.sol"; +import {Votes} from "../governance/utils/Votes.sol"; abstract contract VotesMock is Votes { mapping(address => uint256) private _votingUnits; diff --git a/contracts/mocks/docs/ERC4626Fees.sol b/contracts/mocks/docs/ERC4626Fees.sol index 703e4245a..c82279a60 100644 --- a/contracts/mocks/docs/ERC4626Fees.sol +++ b/contracts/mocks/docs/ERC4626Fees.sol @@ -2,7 +2,10 @@ pragma solidity ^0.8.19; -import "../../token/ERC20/extensions/ERC4626.sol"; +import {IERC20} from "../../token/ERC20/IERC20.sol"; +import {ERC4626} from "../../token/ERC20/extensions/ERC4626.sol"; +import {SafeERC20} from "../../token/ERC20/utils/SafeERC20.sol"; +import {Math} from "../../utils/math/Math.sol"; abstract contract ERC4626Fees is ERC4626 { using Math for uint256; diff --git a/contracts/mocks/docs/governance/MyGovernor.sol b/contracts/mocks/docs/governance/MyGovernor.sol index 095523b3d..f788f04a6 100644 --- a/contracts/mocks/docs/governance/MyGovernor.sol +++ b/contracts/mocks/docs/governance/MyGovernor.sol @@ -1,11 +1,14 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; -import "../../../governance/Governor.sol"; -import "../../../governance/compatibility/GovernorCompatibilityBravo.sol"; -import "../../../governance/extensions/GovernorVotes.sol"; -import "../../../governance/extensions/GovernorVotesQuorumFraction.sol"; -import "../../../governance/extensions/GovernorTimelockControl.sol"; +import {IGovernor, Governor} from "../../../governance/Governor.sol"; +import {GovernorCompatibilityBravo} from "../../../governance/compatibility/GovernorCompatibilityBravo.sol"; +import {GovernorVotes} from "../../../governance/extensions/GovernorVotes.sol"; +import {GovernorVotesQuorumFraction} from "../../../governance/extensions/GovernorVotesQuorumFraction.sol"; +import {GovernorTimelockControl} from "../../../governance/extensions/GovernorTimelockControl.sol"; +import {TimelockController} from "../../../governance/TimelockController.sol"; +import {IVotes} from "../../../governance/utils/IVotes.sol"; +import {IERC165} from "../../../interfaces/IERC165.sol"; contract MyGovernor is Governor, diff --git a/contracts/mocks/docs/governance/MyToken.sol b/contracts/mocks/docs/governance/MyToken.sol index f7707ff9d..7a700212b 100644 --- a/contracts/mocks/docs/governance/MyToken.sol +++ b/contracts/mocks/docs/governance/MyToken.sol @@ -1,9 +1,10 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; -import "../../../token/ERC20/ERC20.sol"; -import "../../../token/ERC20/extensions/ERC20Permit.sol"; -import "../../../token/ERC20/extensions/ERC20Votes.sol"; +import {ERC20} from "../../../token/ERC20/ERC20.sol"; +import {ERC20Permit} from "../../../token/ERC20/extensions/ERC20Permit.sol"; +import {ERC20Votes} from "../../../token/ERC20/extensions/ERC20Votes.sol"; +import {Nonces} from "../../../utils/Nonces.sol"; contract MyToken is ERC20, ERC20Permit, ERC20Votes { constructor() ERC20("MyToken", "MTK") ERC20Permit("MyToken") {} diff --git a/contracts/mocks/docs/governance/MyTokenTimestampBased.sol b/contracts/mocks/docs/governance/MyTokenTimestampBased.sol index 1c688c250..100d10f51 100644 --- a/contracts/mocks/docs/governance/MyTokenTimestampBased.sol +++ b/contracts/mocks/docs/governance/MyTokenTimestampBased.sol @@ -1,9 +1,10 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; -import "../../../token/ERC20/ERC20.sol"; -import "../../../token/ERC20/extensions/ERC20Permit.sol"; -import "../../../token/ERC20/extensions/ERC20Votes.sol"; +import {ERC20} from "../../../token/ERC20/ERC20.sol"; +import {ERC20Permit} from "../../../token/ERC20/extensions/ERC20Permit.sol"; +import {ERC20Votes} from "../../../token/ERC20/extensions/ERC20Votes.sol"; +import {Nonces} from "../../../utils/Nonces.sol"; contract MyTokenTimestampBased is ERC20, ERC20Permit, ERC20Votes { constructor() ERC20("MyTokenTimestampBased", "MTK") ERC20Permit("MyTokenTimestampBased") {} diff --git a/contracts/mocks/docs/governance/MyTokenWrapped.sol b/contracts/mocks/docs/governance/MyTokenWrapped.sol index 6637dccc3..94182187a 100644 --- a/contracts/mocks/docs/governance/MyTokenWrapped.sol +++ b/contracts/mocks/docs/governance/MyTokenWrapped.sol @@ -1,10 +1,11 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; -import "../../../token/ERC20/ERC20.sol"; -import "../../../token/ERC20/extensions/ERC20Permit.sol"; -import "../../../token/ERC20/extensions/ERC20Votes.sol"; -import "../../../token/ERC20/extensions/ERC20Wrapper.sol"; +import {IERC20, ERC20} from "../../../token/ERC20/ERC20.sol"; +import {ERC20Permit} from "../../../token/ERC20/extensions/ERC20Permit.sol"; +import {ERC20Votes} from "../../../token/ERC20/extensions/ERC20Votes.sol"; +import {ERC20Wrapper} from "../../../token/ERC20/extensions/ERC20Wrapper.sol"; +import {Nonces} from "../../../utils/Nonces.sol"; contract MyTokenWrapped is ERC20, ERC20Permit, ERC20Votes, ERC20Wrapper { constructor( diff --git a/contracts/mocks/governance/GovernorCompatibilityBravoMock.sol b/contracts/mocks/governance/GovernorCompatibilityBravoMock.sol index 6e14a357d..516dd8ff2 100644 --- a/contracts/mocks/governance/GovernorCompatibilityBravoMock.sol +++ b/contracts/mocks/governance/GovernorCompatibilityBravoMock.sol @@ -2,10 +2,12 @@ pragma solidity ^0.8.19; -import "../../governance/compatibility/GovernorCompatibilityBravo.sol"; -import "../../governance/extensions/GovernorTimelockCompound.sol"; -import "../../governance/extensions/GovernorSettings.sol"; -import "../../governance/extensions/GovernorVotes.sol"; +import {IGovernor, Governor} from "../../governance/Governor.sol"; +import {GovernorCompatibilityBravo} from "../../governance/compatibility/GovernorCompatibilityBravo.sol"; +import {IGovernorTimelock, GovernorTimelockCompound} from "../../governance/extensions/GovernorTimelockCompound.sol"; +import {GovernorSettings} from "../../governance/extensions/GovernorSettings.sol"; +import {GovernorVotes} from "../../governance/extensions/GovernorVotes.sol"; +import {IERC165} from "../../interfaces/IERC165.sol"; abstract contract GovernorCompatibilityBravoMock is GovernorCompatibilityBravo, diff --git a/contracts/mocks/governance/GovernorMock.sol b/contracts/mocks/governance/GovernorMock.sol index 9104c3f32..64024b2ce 100644 --- a/contracts/mocks/governance/GovernorMock.sol +++ b/contracts/mocks/governance/GovernorMock.sol @@ -2,9 +2,10 @@ pragma solidity ^0.8.19; -import "../../governance/extensions/GovernorSettings.sol"; -import "../../governance/extensions/GovernorCountingSimple.sol"; -import "../../governance/extensions/GovernorVotesQuorumFraction.sol"; +import {Governor} from "../../governance/Governor.sol"; +import {GovernorSettings} from "../../governance/extensions/GovernorSettings.sol"; +import {GovernorCountingSimple} from "../../governance/extensions/GovernorCountingSimple.sol"; +import {GovernorVotesQuorumFraction} from "../../governance/extensions/GovernorVotesQuorumFraction.sol"; abstract contract GovernorMock is GovernorSettings, GovernorVotesQuorumFraction, GovernorCountingSimple { function proposalThreshold() public view override(Governor, GovernorSettings) returns (uint256) { diff --git a/contracts/mocks/governance/GovernorPreventLateQuorumMock.sol b/contracts/mocks/governance/GovernorPreventLateQuorumMock.sol index 4c1b408e6..785e5df5c 100644 --- a/contracts/mocks/governance/GovernorPreventLateQuorumMock.sol +++ b/contracts/mocks/governance/GovernorPreventLateQuorumMock.sol @@ -2,10 +2,11 @@ pragma solidity ^0.8.19; -import "../../governance/extensions/GovernorPreventLateQuorum.sol"; -import "../../governance/extensions/GovernorSettings.sol"; -import "../../governance/extensions/GovernorCountingSimple.sol"; -import "../../governance/extensions/GovernorVotes.sol"; +import {Governor} from "../../governance/Governor.sol"; +import {GovernorPreventLateQuorum} from "../../governance/extensions/GovernorPreventLateQuorum.sol"; +import {GovernorSettings} from "../../governance/extensions/GovernorSettings.sol"; +import {GovernorCountingSimple} from "../../governance/extensions/GovernorCountingSimple.sol"; +import {GovernorVotes} from "../../governance/extensions/GovernorVotes.sol"; abstract contract GovernorPreventLateQuorumMock is GovernorSettings, diff --git a/contracts/mocks/governance/GovernorTimelockCompoundMock.sol b/contracts/mocks/governance/GovernorTimelockCompoundMock.sol index 134d66133..102dffae8 100644 --- a/contracts/mocks/governance/GovernorTimelockCompoundMock.sol +++ b/contracts/mocks/governance/GovernorTimelockCompoundMock.sol @@ -2,10 +2,11 @@ pragma solidity ^0.8.19; -import "../../governance/extensions/GovernorTimelockCompound.sol"; -import "../../governance/extensions/GovernorSettings.sol"; -import "../../governance/extensions/GovernorCountingSimple.sol"; -import "../../governance/extensions/GovernorVotesQuorumFraction.sol"; +import {IGovernor, Governor} from "../../governance/Governor.sol"; +import {GovernorTimelockCompound} from "../../governance/extensions/GovernorTimelockCompound.sol"; +import {GovernorSettings} from "../../governance/extensions/GovernorSettings.sol"; +import {GovernorCountingSimple} from "../../governance/extensions/GovernorCountingSimple.sol"; +import {GovernorVotesQuorumFraction} from "../../governance/extensions/GovernorVotesQuorumFraction.sol"; abstract contract GovernorTimelockCompoundMock is GovernorSettings, diff --git a/contracts/mocks/governance/GovernorTimelockControlMock.sol b/contracts/mocks/governance/GovernorTimelockControlMock.sol index 28376835a..5c83acd64 100644 --- a/contracts/mocks/governance/GovernorTimelockControlMock.sol +++ b/contracts/mocks/governance/GovernorTimelockControlMock.sol @@ -2,10 +2,11 @@ pragma solidity ^0.8.19; -import "../../governance/extensions/GovernorTimelockControl.sol"; -import "../../governance/extensions/GovernorSettings.sol"; -import "../../governance/extensions/GovernorCountingSimple.sol"; -import "../../governance/extensions/GovernorVotesQuorumFraction.sol"; +import {IGovernor, Governor} from "../../governance/Governor.sol"; +import {GovernorTimelockControl} from "../../governance/extensions/GovernorTimelockControl.sol"; +import {GovernorSettings} from "../../governance/extensions/GovernorSettings.sol"; +import {GovernorCountingSimple} from "../../governance/extensions/GovernorCountingSimple.sol"; +import {GovernorVotesQuorumFraction} from "../../governance/extensions/GovernorVotesQuorumFraction.sol"; abstract contract GovernorTimelockControlMock is GovernorSettings, diff --git a/contracts/mocks/governance/GovernorVoteMock.sol b/contracts/mocks/governance/GovernorVoteMock.sol index 1d0722ecf..ecb80a4fb 100644 --- a/contracts/mocks/governance/GovernorVoteMock.sol +++ b/contracts/mocks/governance/GovernorVoteMock.sol @@ -2,8 +2,8 @@ pragma solidity ^0.8.19; -import "../../governance/extensions/GovernorCountingSimple.sol"; -import "../../governance/extensions/GovernorVotes.sol"; +import {GovernorCountingSimple} from "../../governance/extensions/GovernorCountingSimple.sol"; +import {GovernorVotes} from "../../governance/extensions/GovernorVotes.sol"; abstract contract GovernorVoteMocks is GovernorVotes, GovernorCountingSimple { function quorum(uint256) public pure override returns (uint256) { diff --git a/contracts/mocks/governance/GovernorWithParamsMock.sol b/contracts/mocks/governance/GovernorWithParamsMock.sol index f8de83710..f67bd4b71 100644 --- a/contracts/mocks/governance/GovernorWithParamsMock.sol +++ b/contracts/mocks/governance/GovernorWithParamsMock.sol @@ -2,8 +2,9 @@ pragma solidity ^0.8.19; -import "../../governance/extensions/GovernorCountingSimple.sol"; -import "../../governance/extensions/GovernorVotes.sol"; +import {Governor} from "../../governance/Governor.sol"; +import {GovernorCountingSimple} from "../../governance/extensions/GovernorCountingSimple.sol"; +import {GovernorVotes} from "../../governance/extensions/GovernorVotes.sol"; abstract contract GovernorWithParamsMock is GovernorVotes, GovernorCountingSimple { event CountParams(uint256 uintParam, string strParam); diff --git a/contracts/mocks/proxy/UUPSUpgradeableMock.sol b/contracts/mocks/proxy/UUPSUpgradeableMock.sol index 77744998b..4333e63e8 100644 --- a/contracts/mocks/proxy/UUPSUpgradeableMock.sol +++ b/contracts/mocks/proxy/UUPSUpgradeableMock.sol @@ -2,7 +2,8 @@ pragma solidity ^0.8.19; -import "../../proxy/utils/UUPSUpgradeable.sol"; +import {UUPSUpgradeable} from "../../proxy/utils/UUPSUpgradeable.sol"; +import {ERC1967Utils} from "../../proxy/ERC1967/ERC1967Utils.sol"; contract NonUpgradeableMock { uint256 internal _counter; diff --git a/contracts/mocks/token/ERC1155ReceiverMock.sol b/contracts/mocks/token/ERC1155ReceiverMock.sol index a5d7233cf..b02617333 100644 --- a/contracts/mocks/token/ERC1155ReceiverMock.sol +++ b/contracts/mocks/token/ERC1155ReceiverMock.sol @@ -2,8 +2,8 @@ pragma solidity ^0.8.19; -import "../../token/ERC1155/IERC1155Receiver.sol"; -import "../../utils/introspection/ERC165.sol"; +import {IERC1155Receiver} from "../../token/ERC1155/IERC1155Receiver.sol"; +import {ERC165} from "../../utils/introspection/ERC165.sol"; contract ERC1155ReceiverMock is ERC165, IERC1155Receiver { enum RevertType { diff --git a/contracts/mocks/token/ERC20ApprovalMock.sol b/contracts/mocks/token/ERC20ApprovalMock.sol index da8f51e1e..3caa7d0d2 100644 --- a/contracts/mocks/token/ERC20ApprovalMock.sol +++ b/contracts/mocks/token/ERC20ApprovalMock.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; -import "../../token/ERC20/ERC20.sol"; +import {ERC20} from "../../token/ERC20/ERC20.sol"; abstract contract ERC20ApprovalMock is ERC20 { function _approve(address owner, address spender, uint256 amount, bool) internal virtual override { diff --git a/contracts/mocks/token/ERC20DecimalsMock.sol b/contracts/mocks/token/ERC20DecimalsMock.sol index 5699d31d2..3ecc77f4e 100644 --- a/contracts/mocks/token/ERC20DecimalsMock.sol +++ b/contracts/mocks/token/ERC20DecimalsMock.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.19; -import "../../token/ERC20/ERC20.sol"; +import {ERC20} from "../../token/ERC20/ERC20.sol"; abstract contract ERC20DecimalsMock is ERC20 { uint8 private immutable _decimals; diff --git a/contracts/mocks/token/ERC20FlashMintMock.sol b/contracts/mocks/token/ERC20FlashMintMock.sol index 05c4db57d..1831f53bd 100644 --- a/contracts/mocks/token/ERC20FlashMintMock.sol +++ b/contracts/mocks/token/ERC20FlashMintMock.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.19; -import "../../token/ERC20/extensions/ERC20FlashMint.sol"; +import {ERC20FlashMint} from "../../token/ERC20/extensions/ERC20FlashMint.sol"; abstract contract ERC20FlashMintMock is ERC20FlashMint { uint256 _flashFeeAmount; diff --git a/contracts/mocks/token/ERC20ForceApproveMock.sol b/contracts/mocks/token/ERC20ForceApproveMock.sol index 42b417f41..f6f71a7e8 100644 --- a/contracts/mocks/token/ERC20ForceApproveMock.sol +++ b/contracts/mocks/token/ERC20ForceApproveMock.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.19; -import "../../token/ERC20/ERC20.sol"; +import {ERC20} from "../../token/ERC20/ERC20.sol"; // contract that replicate USDT (0xdac17f958d2ee523a2206206994597c13d831ec7) approval beavior abstract contract ERC20ForceApproveMock is ERC20 { diff --git a/contracts/mocks/token/ERC20Mock.sol b/contracts/mocks/token/ERC20Mock.sol index 5fb134f60..cd5f9d444 100644 --- a/contracts/mocks/token/ERC20Mock.sol +++ b/contracts/mocks/token/ERC20Mock.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; -import "../../token/ERC20/ERC20.sol"; +import {ERC20} from "../../token/ERC20/ERC20.sol"; contract ERC20Mock is ERC20 { constructor() ERC20("ERC20Mock", "E20M") {} diff --git a/contracts/mocks/token/ERC20MulticallMock.sol b/contracts/mocks/token/ERC20MulticallMock.sol index b8259a75a..87aaf8923 100644 --- a/contracts/mocks/token/ERC20MulticallMock.sol +++ b/contracts/mocks/token/ERC20MulticallMock.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.19; -import "../../token/ERC20/ERC20.sol"; -import "../../utils/Multicall.sol"; +import {ERC20} from "../../token/ERC20/ERC20.sol"; +import {Multicall} from "../../utils/Multicall.sol"; abstract contract ERC20MulticallMock is ERC20, Multicall {} diff --git a/contracts/mocks/token/ERC20NoReturnMock.sol b/contracts/mocks/token/ERC20NoReturnMock.sol index 7d597ad89..e7f234cff 100644 --- a/contracts/mocks/token/ERC20NoReturnMock.sol +++ b/contracts/mocks/token/ERC20NoReturnMock.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.19; -import "../../token/ERC20/ERC20.sol"; +import {ERC20} from "../../token/ERC20/ERC20.sol"; abstract contract ERC20NoReturnMock is ERC20 { function transfer(address to, uint256 amount) public override returns (bool) { diff --git a/contracts/mocks/token/ERC20PermitNoRevertMock.sol b/contracts/mocks/token/ERC20PermitNoRevertMock.sol index 510176cf5..23395d75d 100644 --- a/contracts/mocks/token/ERC20PermitNoRevertMock.sol +++ b/contracts/mocks/token/ERC20PermitNoRevertMock.sol @@ -2,8 +2,8 @@ pragma solidity ^0.8.19; -import "../../token/ERC20/ERC20.sol"; -import "../../token/ERC20/extensions/ERC20Permit.sol"; +import {ERC20} from "../../token/ERC20/ERC20.sol"; +import {ERC20Permit} from "../../token/ERC20/extensions/ERC20Permit.sol"; abstract contract ERC20PermitNoRevertMock is ERC20Permit { function permitThatMayRevert( diff --git a/contracts/mocks/token/ERC20Reentrant.sol b/contracts/mocks/token/ERC20Reentrant.sol index 00ba74260..063e9f3d5 100644 --- a/contracts/mocks/token/ERC20Reentrant.sol +++ b/contracts/mocks/token/ERC20Reentrant.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; -import "../../token/ERC20/ERC20.sol"; -import "../../utils/Address.sol"; +import {ERC20} from "../../token/ERC20/ERC20.sol"; +import {Address} from "../../utils/Address.sol"; contract ERC20Reentrant is ERC20("TEST", "TST") { enum Type { diff --git a/contracts/mocks/token/ERC20ReturnFalseMock.sol b/contracts/mocks/token/ERC20ReturnFalseMock.sol index 763a120e9..e8014b87b 100644 --- a/contracts/mocks/token/ERC20ReturnFalseMock.sol +++ b/contracts/mocks/token/ERC20ReturnFalseMock.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.19; -import "../../token/ERC20/ERC20.sol"; +import {ERC20} from "../../token/ERC20/ERC20.sol"; abstract contract ERC20ReturnFalseMock is ERC20 { function transfer(address, uint256) public pure override returns (bool) { diff --git a/contracts/mocks/token/ERC20VotesLegacyMock.sol b/contracts/mocks/token/ERC20VotesLegacyMock.sol index 88ba84236..6a2078510 100644 --- a/contracts/mocks/token/ERC20VotesLegacyMock.sol +++ b/contracts/mocks/token/ERC20VotesLegacyMock.sol @@ -2,11 +2,11 @@ pragma solidity ^0.8.19; -import "../../token/ERC20/extensions/ERC20Permit.sol"; -import "../../utils/math/Math.sol"; -import "../../governance/utils/IVotes.sol"; -import "../../utils/math/SafeCast.sol"; -import "../../utils/cryptography/ECDSA.sol"; +import {ERC20Permit} from "../../token/ERC20/extensions/ERC20Permit.sol"; +import {Math} from "../../utils/math/Math.sol"; +import {IVotes} from "../../governance/utils/IVotes.sol"; +import {SafeCast} from "../../utils/math/SafeCast.sol"; +import {ECDSA} from "../../utils/cryptography/ECDSA.sol"; /** * @dev Copied from the master branch at commit 86de1e8b6c3fa6b4efa4a5435869d2521be0f5f5 diff --git a/contracts/mocks/token/ERC4626LimitsMock.sol b/contracts/mocks/token/ERC4626LimitsMock.sol index a47826bb8..fe5d724c8 100644 --- a/contracts/mocks/token/ERC4626LimitsMock.sol +++ b/contracts/mocks/token/ERC4626LimitsMock.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.19; -import "../../token/ERC20/extensions/ERC4626.sol"; +import {ERC4626} from "../../token/ERC20/extensions/ERC4626.sol"; abstract contract ERC4626LimitsMock is ERC4626 { uint256 _maxDeposit; diff --git a/contracts/mocks/token/ERC4626Mock.sol b/contracts/mocks/token/ERC4626Mock.sol index 3713e0791..151606ca5 100644 --- a/contracts/mocks/token/ERC4626Mock.sol +++ b/contracts/mocks/token/ERC4626Mock.sol @@ -1,7 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; -import "../../token/ERC20/extensions/ERC4626.sol"; +import {IERC20, ERC20} from "../../token/ERC20/ERC20.sol"; +import {ERC4626} from "../../token/ERC20/extensions/ERC4626.sol"; contract ERC4626Mock is ERC4626 { constructor(address underlying) ERC20("ERC4626Mock", "E4626M") ERC4626(IERC20(underlying)) {} diff --git a/contracts/mocks/token/ERC4626OffsetMock.sol b/contracts/mocks/token/ERC4626OffsetMock.sol index b3c31cdd2..b1dac7d5d 100644 --- a/contracts/mocks/token/ERC4626OffsetMock.sol +++ b/contracts/mocks/token/ERC4626OffsetMock.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.19; -import "../../token/ERC20/extensions/ERC4626.sol"; +import {ERC4626} from "../../token/ERC20/extensions/ERC4626.sol"; abstract contract ERC4626OffsetMock is ERC4626 { uint8 private immutable _offset; diff --git a/contracts/mocks/token/ERC4646FeesMock.sol b/contracts/mocks/token/ERC4646FeesMock.sol index 4c6c3c662..f8fde293c 100644 --- a/contracts/mocks/token/ERC4646FeesMock.sol +++ b/contracts/mocks/token/ERC4646FeesMock.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.19; -import "../docs/ERC4626Fees.sol"; +import {ERC4626Fees} from "../docs/ERC4626Fees.sol"; abstract contract ERC4626FeesMock is ERC4626Fees { uint256 private immutable _entryFeeBasePointValue; diff --git a/contracts/mocks/token/ERC721ConsecutiveEnumerableMock.sol b/contracts/mocks/token/ERC721ConsecutiveEnumerableMock.sol index a2ee8199a..081b8d99d 100644 --- a/contracts/mocks/token/ERC721ConsecutiveEnumerableMock.sol +++ b/contracts/mocks/token/ERC721ConsecutiveEnumerableMock.sol @@ -2,8 +2,9 @@ pragma solidity ^0.8.19; -import "../../token/ERC721/extensions/ERC721Consecutive.sol"; -import "../../token/ERC721/extensions/ERC721Enumerable.sol"; +import {ERC721} from "../../token/ERC721/ERC721.sol"; +import {ERC721Consecutive} from "../../token/ERC721/extensions/ERC721Consecutive.sol"; +import {ERC721Enumerable} from "../../token/ERC721/extensions/ERC721Enumerable.sol"; contract ERC721ConsecutiveEnumerableMock is ERC721Consecutive, ERC721Enumerable { constructor( diff --git a/contracts/mocks/token/ERC721ConsecutiveMock.sol b/contracts/mocks/token/ERC721ConsecutiveMock.sol index 4aae388e0..166959e92 100644 --- a/contracts/mocks/token/ERC721ConsecutiveMock.sol +++ b/contracts/mocks/token/ERC721ConsecutiveMock.sol @@ -2,9 +2,11 @@ pragma solidity ^0.8.19; -import "../../token/ERC721/extensions/ERC721Consecutive.sol"; -import "../../token/ERC721/extensions/ERC721Pausable.sol"; -import "../../token/ERC721/extensions/ERC721Votes.sol"; +import {ERC721} from "../../token/ERC721/ERC721.sol"; +import {ERC721Consecutive} from "../../token/ERC721/extensions/ERC721Consecutive.sol"; +import {ERC721Pausable} from "../../token/ERC721/extensions/ERC721Pausable.sol"; +import {ERC721Votes} from "../../token/ERC721/extensions/ERC721Votes.sol"; +import {EIP712} from "../../utils/cryptography/EIP712.sol"; /** * @title ERC721ConsecutiveMock diff --git a/contracts/mocks/token/ERC721ReceiverMock.sol b/contracts/mocks/token/ERC721ReceiverMock.sol index 416613174..f1c842e49 100644 --- a/contracts/mocks/token/ERC721ReceiverMock.sol +++ b/contracts/mocks/token/ERC721ReceiverMock.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.19; -import "../../token/ERC721/IERC721Receiver.sol"; +import {IERC721Receiver} from "../../token/ERC721/IERC721Receiver.sol"; contract ERC721ReceiverMock is IERC721Receiver { enum RevertType { diff --git a/contracts/mocks/token/ERC721URIStorageMock.sol b/contracts/mocks/token/ERC721URIStorageMock.sol index 1808e47f7..569a1c0f4 100644 --- a/contracts/mocks/token/ERC721URIStorageMock.sol +++ b/contracts/mocks/token/ERC721URIStorageMock.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.19; -import "../../token/ERC721/extensions/ERC721URIStorage.sol"; +import {ERC721URIStorage} from "../../token/ERC721/extensions/ERC721URIStorage.sol"; abstract contract ERC721URIStorageMock is ERC721URIStorage { string private _baseTokenURI; diff --git a/contracts/mocks/token/VotesTimestamp.sol b/contracts/mocks/token/VotesTimestamp.sol index 7e3d8ca6d..b54dbda97 100644 --- a/contracts/mocks/token/VotesTimestamp.sol +++ b/contracts/mocks/token/VotesTimestamp.sol @@ -2,8 +2,9 @@ pragma solidity ^0.8.19; -import "../../token/ERC20/extensions/ERC20Votes.sol"; -import "../../token/ERC721/extensions/ERC721Votes.sol"; +import {ERC20Votes} from "../../token/ERC20/extensions/ERC20Votes.sol"; +import {ERC721Votes} from "../../token/ERC721/extensions/ERC721Votes.sol"; +import {SafeCast} from "../../utils/math/SafeCast.sol"; abstract contract ERC20VotesTimestampMock is ERC20Votes { function clock() public view virtual override returns (uint48) { diff --git a/contracts/proxy/ERC1967/ERC1967Proxy.sol b/contracts/proxy/ERC1967/ERC1967Proxy.sol index 5a752f13d..e7c90c706 100644 --- a/contracts/proxy/ERC1967/ERC1967Proxy.sol +++ b/contracts/proxy/ERC1967/ERC1967Proxy.sol @@ -3,8 +3,8 @@ pragma solidity ^0.8.19; -import "../Proxy.sol"; -import "./ERC1967Utils.sol"; +import {Proxy} from "../Proxy.sol"; +import {ERC1967Utils} from "./ERC1967Utils.sol"; /** * @dev This contract implements an upgradeable proxy. It is upgradeable because calls are delegated to an diff --git a/contracts/proxy/ERC1967/ERC1967Utils.sol b/contracts/proxy/ERC1967/ERC1967Utils.sol index c244982bf..843fa6584 100644 --- a/contracts/proxy/ERC1967/ERC1967Utils.sol +++ b/contracts/proxy/ERC1967/ERC1967Utils.sol @@ -3,9 +3,9 @@ pragma solidity ^0.8.20; -import "../beacon/IBeacon.sol"; -import "../../utils/Address.sol"; -import "../../utils/StorageSlot.sol"; +import {IBeacon} from "../beacon/IBeacon.sol"; +import {Address} from "../../utils/Address.sol"; +import {StorageSlot} from "../../utils/StorageSlot.sol"; /** * @dev This abstract contract provides getters and event emitting update functions for diff --git a/contracts/proxy/beacon/BeaconProxy.sol b/contracts/proxy/beacon/BeaconProxy.sol index dc6b5c90b..57883b692 100644 --- a/contracts/proxy/beacon/BeaconProxy.sol +++ b/contracts/proxy/beacon/BeaconProxy.sol @@ -3,9 +3,9 @@ pragma solidity ^0.8.19; -import "./IBeacon.sol"; -import "../Proxy.sol"; -import "../ERC1967/ERC1967Utils.sol"; +import {IBeacon} from "./IBeacon.sol"; +import {Proxy} from "../Proxy.sol"; +import {ERC1967Utils} from "../ERC1967/ERC1967Utils.sol"; /** * @dev This contract implements a proxy that gets the implementation address for each call from an {UpgradeableBeacon}. diff --git a/contracts/proxy/beacon/UpgradeableBeacon.sol b/contracts/proxy/beacon/UpgradeableBeacon.sol index c5e64ea59..0c72ddf63 100644 --- a/contracts/proxy/beacon/UpgradeableBeacon.sol +++ b/contracts/proxy/beacon/UpgradeableBeacon.sol @@ -3,8 +3,8 @@ pragma solidity ^0.8.19; -import "./IBeacon.sol"; -import "../../access/Ownable.sol"; +import {IBeacon} from "./IBeacon.sol"; +import {Ownable} from "../../access/Ownable.sol"; /** * @dev This contract is used in conjunction with one or more instances of {BeaconProxy} to determine their diff --git a/contracts/proxy/transparent/ProxyAdmin.sol b/contracts/proxy/transparent/ProxyAdmin.sol index e8578a585..344c942f2 100644 --- a/contracts/proxy/transparent/ProxyAdmin.sol +++ b/contracts/proxy/transparent/ProxyAdmin.sol @@ -3,8 +3,8 @@ pragma solidity ^0.8.19; -import "./TransparentUpgradeableProxy.sol"; -import "../../access/Ownable.sol"; +import {ITransparentUpgradeableProxy, TransparentUpgradeableProxy} from "./TransparentUpgradeableProxy.sol"; +import {Ownable} from "../../access/Ownable.sol"; /** * @dev This is an auxiliary contract meant to be assigned as the admin of a {TransparentUpgradeableProxy}. For an diff --git a/contracts/proxy/transparent/TransparentUpgradeableProxy.sol b/contracts/proxy/transparent/TransparentUpgradeableProxy.sol index 71ce665d0..3d6e11f38 100644 --- a/contracts/proxy/transparent/TransparentUpgradeableProxy.sol +++ b/contracts/proxy/transparent/TransparentUpgradeableProxy.sol @@ -3,8 +3,9 @@ pragma solidity ^0.8.19; -import "../ERC1967/ERC1967Proxy.sol"; -import "../../interfaces/IERC1967.sol"; +import {ERC1967Utils} from "../ERC1967/ERC1967Utils.sol"; +import {ERC1967Proxy} from "../ERC1967/ERC1967Proxy.sol"; +import {IERC1967} from "../../interfaces/IERC1967.sol"; /** * @dev Interface for {TransparentUpgradeableProxy}. In order to implement transparency, {TransparentUpgradeableProxy} diff --git a/contracts/proxy/utils/Initializable.sol b/contracts/proxy/utils/Initializable.sol index 3ae5e4a65..86e99531b 100644 --- a/contracts/proxy/utils/Initializable.sol +++ b/contracts/proxy/utils/Initializable.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.19; -import "../../utils/Address.sol"; +import {Address} from "../../utils/Address.sol"; /** * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed diff --git a/contracts/proxy/utils/UUPSUpgradeable.sol b/contracts/proxy/utils/UUPSUpgradeable.sol index 07de178bc..7d1515bbb 100644 --- a/contracts/proxy/utils/UUPSUpgradeable.sol +++ b/contracts/proxy/utils/UUPSUpgradeable.sol @@ -3,8 +3,8 @@ pragma solidity ^0.8.19; -import "../../interfaces/draft-IERC1822.sol"; -import "../ERC1967/ERC1967Utils.sol"; +import {IERC1822Proxiable} from "../../interfaces/draft-IERC1822.sol"; +import {ERC1967Utils} from "../ERC1967/ERC1967Utils.sol"; /** * @dev An upgradeability mechanism designed for UUPS proxies. The functions included here can perform an upgrade of an diff --git a/contracts/security/Pausable.sol b/contracts/security/Pausable.sol index dc0afa663..7a54b2511 100644 --- a/contracts/security/Pausable.sol +++ b/contracts/security/Pausable.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.19; -import "../utils/Context.sol"; +import {Context} from "../utils/Context.sol"; /** * @dev Contract module which allows children to implement an emergency stop diff --git a/contracts/token/ERC1155/ERC1155.sol b/contracts/token/ERC1155/ERC1155.sol index e4bd74826..9d70714fa 100644 --- a/contracts/token/ERC1155/ERC1155.sol +++ b/contracts/token/ERC1155/ERC1155.sol @@ -3,13 +3,13 @@ pragma solidity ^0.8.19; -import "./IERC1155.sol"; -import "./IERC1155Receiver.sol"; -import "./extensions/IERC1155MetadataURI.sol"; -import "../../utils/Context.sol"; -import "../../utils/introspection/ERC165.sol"; -import "../../utils/Arrays.sol"; -import "../../interfaces/draft-IERC6093.sol"; +import {IERC1155} from "./IERC1155.sol"; +import {IERC1155Receiver} from "./IERC1155Receiver.sol"; +import {IERC1155MetadataURI} from "./extensions/IERC1155MetadataURI.sol"; +import {Context} from "../../utils/Context.sol"; +import {IERC165, ERC165} from "../../utils/introspection/ERC165.sol"; +import {Arrays} from "../../utils/Arrays.sol"; +import {IERC1155Errors} from "../../interfaces/draft-IERC6093.sol"; /** * @dev Implementation of the basic standard multi-token. diff --git a/contracts/token/ERC1155/IERC1155.sol b/contracts/token/ERC1155/IERC1155.sol index 16b2b71a3..c450eec2f 100644 --- a/contracts/token/ERC1155/IERC1155.sol +++ b/contracts/token/ERC1155/IERC1155.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.19; -import "../../utils/introspection/IERC165.sol"; +import {IERC165} from "../../utils/introspection/IERC165.sol"; /** * @dev Required interface of an ERC1155 compliant contract, as defined in the diff --git a/contracts/token/ERC1155/IERC1155Receiver.sol b/contracts/token/ERC1155/IERC1155Receiver.sol index a272f61b0..04ef783f1 100644 --- a/contracts/token/ERC1155/IERC1155Receiver.sol +++ b/contracts/token/ERC1155/IERC1155Receiver.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.19; -import "../../utils/introspection/IERC165.sol"; +import {IERC165} from "../../utils/introspection/IERC165.sol"; /** * @dev _Available since v3.1._ diff --git a/contracts/token/ERC1155/extensions/ERC1155Burnable.sol b/contracts/token/ERC1155/extensions/ERC1155Burnable.sol index d7bee05d9..5ebfcf6c4 100644 --- a/contracts/token/ERC1155/extensions/ERC1155Burnable.sol +++ b/contracts/token/ERC1155/extensions/ERC1155Burnable.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.19; -import "../ERC1155.sol"; +import {ERC1155} from "../ERC1155.sol"; /** * @dev Extension of {ERC1155} that allows token holders to destroy both their diff --git a/contracts/token/ERC1155/extensions/ERC1155Pausable.sol b/contracts/token/ERC1155/extensions/ERC1155Pausable.sol index f8357062c..05aa8a780 100644 --- a/contracts/token/ERC1155/extensions/ERC1155Pausable.sol +++ b/contracts/token/ERC1155/extensions/ERC1155Pausable.sol @@ -3,8 +3,8 @@ pragma solidity ^0.8.19; -import "../ERC1155.sol"; -import "../../../security/Pausable.sol"; +import {ERC1155} from "../ERC1155.sol"; +import {Pausable} from "../../../security/Pausable.sol"; /** * @dev ERC1155 token with pausable token transfers, minting and burning. diff --git a/contracts/token/ERC1155/extensions/ERC1155Supply.sol b/contracts/token/ERC1155/extensions/ERC1155Supply.sol index f32fbb74b..eefa0ebf7 100644 --- a/contracts/token/ERC1155/extensions/ERC1155Supply.sol +++ b/contracts/token/ERC1155/extensions/ERC1155Supply.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.19; -import "../ERC1155.sol"; +import {ERC1155} from "../ERC1155.sol"; /** * @dev Extension of ERC1155 that adds tracking of total supply per id. diff --git a/contracts/token/ERC1155/extensions/ERC1155URIStorage.sol b/contracts/token/ERC1155/extensions/ERC1155URIStorage.sol index 79782a42b..303b452b8 100644 --- a/contracts/token/ERC1155/extensions/ERC1155URIStorage.sol +++ b/contracts/token/ERC1155/extensions/ERC1155URIStorage.sol @@ -3,8 +3,8 @@ pragma solidity ^0.8.19; -import "../../../utils/Strings.sol"; -import "../ERC1155.sol"; +import {Strings} from "../../../utils/Strings.sol"; +import {ERC1155} from "../ERC1155.sol"; /** * @dev ERC1155 token with storage based token URI management. diff --git a/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol b/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol index 2c998995e..88462ef87 100644 --- a/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol +++ b/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.19; -import "../IERC1155.sol"; +import {IERC1155} from "../IERC1155.sol"; /** * @dev Interface of the optional ERC1155MetadataExtension interface, as defined diff --git a/contracts/token/ERC1155/utils/ERC1155Holder.sol b/contracts/token/ERC1155/utils/ERC1155Holder.sol index d24a1a53a..87c42b02f 100644 --- a/contracts/token/ERC1155/utils/ERC1155Holder.sol +++ b/contracts/token/ERC1155/utils/ERC1155Holder.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.19; -import "./ERC1155Receiver.sol"; +import {ERC1155Receiver} from "./ERC1155Receiver.sol"; /** * Simple implementation of `ERC1155Receiver` that will allow a contract to hold ERC1155 tokens. diff --git a/contracts/token/ERC1155/utils/ERC1155Receiver.sol b/contracts/token/ERC1155/utils/ERC1155Receiver.sol index 6fb21491f..cf948ced7 100644 --- a/contracts/token/ERC1155/utils/ERC1155Receiver.sol +++ b/contracts/token/ERC1155/utils/ERC1155Receiver.sol @@ -3,8 +3,8 @@ pragma solidity ^0.8.19; -import "../IERC1155Receiver.sol"; -import "../../../utils/introspection/ERC165.sol"; +import {IERC1155Receiver} from "../IERC1155Receiver.sol"; +import {IERC165, ERC165} from "../../../utils/introspection/ERC165.sol"; /** * @dev _Available since v3.1._ diff --git a/contracts/token/ERC20/ERC20.sol b/contracts/token/ERC20/ERC20.sol index 5ddebf22b..d43880da6 100644 --- a/contracts/token/ERC20/ERC20.sol +++ b/contracts/token/ERC20/ERC20.sol @@ -3,10 +3,10 @@ pragma solidity ^0.8.19; -import "./IERC20.sol"; -import "./extensions/IERC20Metadata.sol"; -import "../../utils/Context.sol"; -import "../../interfaces/draft-IERC6093.sol"; +import {IERC20} from "./IERC20.sol"; +import {IERC20Metadata} from "./extensions/IERC20Metadata.sol"; +import {Context} from "../../utils/Context.sol"; +import {IERC20Errors} from "../../interfaces/draft-IERC6093.sol"; /** * @dev Implementation of the {IERC20} interface. diff --git a/contracts/token/ERC20/extensions/ERC20Burnable.sol b/contracts/token/ERC20/extensions/ERC20Burnable.sol index cae186b64..2a6d28963 100644 --- a/contracts/token/ERC20/extensions/ERC20Burnable.sol +++ b/contracts/token/ERC20/extensions/ERC20Burnable.sol @@ -3,8 +3,8 @@ pragma solidity ^0.8.19; -import "../ERC20.sol"; -import "../../../utils/Context.sol"; +import {ERC20} from "../ERC20.sol"; +import {Context} from "../../../utils/Context.sol"; /** * @dev Extension of {ERC20} that allows token holders to destroy both their own diff --git a/contracts/token/ERC20/extensions/ERC20Capped.sol b/contracts/token/ERC20/extensions/ERC20Capped.sol index 41e9ce5cf..98ec71443 100644 --- a/contracts/token/ERC20/extensions/ERC20Capped.sol +++ b/contracts/token/ERC20/extensions/ERC20Capped.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.19; -import "../ERC20.sol"; +import {ERC20} from "../ERC20.sol"; /** * @dev Extension of {ERC20} that adds a cap to the supply of tokens. diff --git a/contracts/token/ERC20/extensions/ERC20FlashMint.sol b/contracts/token/ERC20/extensions/ERC20FlashMint.sol index 09c20bacc..b63c75f71 100644 --- a/contracts/token/ERC20/extensions/ERC20FlashMint.sol +++ b/contracts/token/ERC20/extensions/ERC20FlashMint.sol @@ -3,9 +3,9 @@ pragma solidity ^0.8.19; -import "../../../interfaces/IERC3156FlashBorrower.sol"; -import "../../../interfaces/IERC3156FlashLender.sol"; -import "../ERC20.sol"; +import {IERC3156FlashBorrower} from "../../../interfaces/IERC3156FlashBorrower.sol"; +import {IERC3156FlashLender} from "../../../interfaces/IERC3156FlashLender.sol"; +import {ERC20} from "../ERC20.sol"; /** * @dev Implementation of the ERC3156 Flash loans extension, as defined in diff --git a/contracts/token/ERC20/extensions/ERC20Pausable.sol b/contracts/token/ERC20/extensions/ERC20Pausable.sol index 5ef50f9c6..59f451639 100644 --- a/contracts/token/ERC20/extensions/ERC20Pausable.sol +++ b/contracts/token/ERC20/extensions/ERC20Pausable.sol @@ -3,8 +3,8 @@ pragma solidity ^0.8.19; -import "../ERC20.sol"; -import "../../../security/Pausable.sol"; +import {ERC20} from "../ERC20.sol"; +import {Pausable} from "../../../security/Pausable.sol"; /** * @dev ERC20 token with pausable token transfers, minting and burning. diff --git a/contracts/token/ERC20/extensions/ERC20Permit.sol b/contracts/token/ERC20/extensions/ERC20Permit.sol index 4378eb7c1..8778f4baf 100644 --- a/contracts/token/ERC20/extensions/ERC20Permit.sol +++ b/contracts/token/ERC20/extensions/ERC20Permit.sol @@ -3,11 +3,11 @@ pragma solidity ^0.8.19; -import "./IERC20Permit.sol"; -import "../ERC20.sol"; -import "../../../utils/cryptography/ECDSA.sol"; -import "../../../utils/cryptography/EIP712.sol"; -import "../../../utils/Nonces.sol"; +import {IERC20Permit} from "./IERC20Permit.sol"; +import {ERC20} from "../ERC20.sol"; +import {ECDSA} from "../../../utils/cryptography/ECDSA.sol"; +import {EIP712} from "../../../utils/cryptography/EIP712.sol"; +import {Nonces} from "../../../utils/Nonces.sol"; /** * @dev Implementation of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in diff --git a/contracts/token/ERC20/extensions/ERC20Votes.sol b/contracts/token/ERC20/extensions/ERC20Votes.sol index 98f798efe..db5f71241 100644 --- a/contracts/token/ERC20/extensions/ERC20Votes.sol +++ b/contracts/token/ERC20/extensions/ERC20Votes.sol @@ -3,9 +3,10 @@ pragma solidity ^0.8.19; -import "../ERC20.sol"; -import "../../../governance/utils/Votes.sol"; -import "../../../utils/math/SafeCast.sol"; +import {ERC20} from "../ERC20.sol"; +import {Votes} from "../../../governance/utils/Votes.sol"; +import {SafeCast} from "../../../utils/math/SafeCast.sol"; +import {Checkpoints} from "../../../utils/structs/Checkpoints.sol"; /** * @dev Extension of ERC20 to support Compound-like voting and delegation. This version is more generic than Compound's, diff --git a/contracts/token/ERC20/extensions/ERC20Wrapper.sol b/contracts/token/ERC20/extensions/ERC20Wrapper.sol index 389965e9c..2cbff6223 100644 --- a/contracts/token/ERC20/extensions/ERC20Wrapper.sol +++ b/contracts/token/ERC20/extensions/ERC20Wrapper.sol @@ -3,8 +3,8 @@ pragma solidity ^0.8.19; -import "../ERC20.sol"; -import "../utils/SafeERC20.sol"; +import {IERC20, IERC20Metadata, ERC20} from "../ERC20.sol"; +import {SafeERC20} from "../utils/SafeERC20.sol"; /** * @dev Extension of the ERC20 token contract to support token wrapping. diff --git a/contracts/token/ERC20/extensions/ERC4626.sol b/contracts/token/ERC20/extensions/ERC4626.sol index 9ea6789f7..cb5e03da8 100644 --- a/contracts/token/ERC20/extensions/ERC4626.sol +++ b/contracts/token/ERC20/extensions/ERC4626.sol @@ -3,10 +3,10 @@ pragma solidity ^0.8.19; -import "../ERC20.sol"; -import "../utils/SafeERC20.sol"; -import "../../../interfaces/IERC4626.sol"; -import "../../../utils/math/Math.sol"; +import {IERC20, IERC20Metadata, ERC20} from "../ERC20.sol"; +import {SafeERC20} from "../utils/SafeERC20.sol"; +import {IERC4626} from "../../../interfaces/IERC4626.sol"; +import {Math} from "../../../utils/math/Math.sol"; /** * @dev Implementation of the ERC4626 "Tokenized Vault Standard" as defined in diff --git a/contracts/token/ERC20/extensions/IERC20Metadata.sol b/contracts/token/ERC20/extensions/IERC20Metadata.sol index 1cf7e0b2e..bdfe81145 100644 --- a/contracts/token/ERC20/extensions/IERC20Metadata.sol +++ b/contracts/token/ERC20/extensions/IERC20Metadata.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.19; -import "../IERC20.sol"; +import {IERC20} from "../IERC20.sol"; /** * @dev Interface for the optional metadata functions from the ERC20 standard. diff --git a/contracts/token/ERC20/utils/SafeERC20.sol b/contracts/token/ERC20/utils/SafeERC20.sol index 599307e7f..0ec21573b 100644 --- a/contracts/token/ERC20/utils/SafeERC20.sol +++ b/contracts/token/ERC20/utils/SafeERC20.sol @@ -3,9 +3,9 @@ pragma solidity ^0.8.19; -import "../IERC20.sol"; -import "../extensions/IERC20Permit.sol"; -import "../../../utils/Address.sol"; +import {IERC20} from "../IERC20.sol"; +import {IERC20Permit} from "../extensions/IERC20Permit.sol"; +import {Address} from "../../../utils/Address.sol"; /** * @title SafeERC20 diff --git a/contracts/token/ERC721/ERC721.sol b/contracts/token/ERC721/ERC721.sol index 21ed95813..4232c616e 100644 --- a/contracts/token/ERC721/ERC721.sol +++ b/contracts/token/ERC721/ERC721.sol @@ -3,13 +3,13 @@ pragma solidity ^0.8.19; -import "./IERC721.sol"; -import "./IERC721Receiver.sol"; -import "./extensions/IERC721Metadata.sol"; -import "../../utils/Context.sol"; -import "../../utils/Strings.sol"; -import "../../utils/introspection/ERC165.sol"; -import "../../interfaces/draft-IERC6093.sol"; +import {IERC721} from "./IERC721.sol"; +import {IERC721Receiver} from "./IERC721Receiver.sol"; +import {IERC721Metadata} from "./extensions/IERC721Metadata.sol"; +import {Context} from "../../utils/Context.sol"; +import {Strings} from "../../utils/Strings.sol"; +import {IERC165, ERC165} from "../../utils/introspection/ERC165.sol"; +import {IERC721Errors} from "../../interfaces/draft-IERC6093.sol"; /** * @dev Implementation of https://eips.ethereum.org/EIPS/eip-721[ERC721] Non-Fungible Token Standard, including diff --git a/contracts/token/ERC721/IERC721.sol b/contracts/token/ERC721/IERC721.sol index ba4c108ef..3b2db67cb 100644 --- a/contracts/token/ERC721/IERC721.sol +++ b/contracts/token/ERC721/IERC721.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.19; -import "../../utils/introspection/IERC165.sol"; +import {IERC165} from "../../utils/introspection/IERC165.sol"; /** * @dev Required interface of an ERC721 compliant contract. diff --git a/contracts/token/ERC721/extensions/ERC721Burnable.sol b/contracts/token/ERC721/extensions/ERC721Burnable.sol index 217f039ca..607265328 100644 --- a/contracts/token/ERC721/extensions/ERC721Burnable.sol +++ b/contracts/token/ERC721/extensions/ERC721Burnable.sol @@ -3,8 +3,8 @@ pragma solidity ^0.8.19; -import "../ERC721.sol"; -import "../../../utils/Context.sol"; +import {ERC721} from "../ERC721.sol"; +import {Context} from "../../../utils/Context.sol"; /** * @title ERC721 Burnable Token diff --git a/contracts/token/ERC721/extensions/ERC721Consecutive.sol b/contracts/token/ERC721/extensions/ERC721Consecutive.sol index f1308cdab..e25edfcd9 100644 --- a/contracts/token/ERC721/extensions/ERC721Consecutive.sol +++ b/contracts/token/ERC721/extensions/ERC721Consecutive.sol @@ -3,10 +3,10 @@ pragma solidity ^0.8.19; -import "../ERC721.sol"; -import "../../../interfaces/IERC2309.sol"; -import "../../../utils/structs/BitMaps.sol"; -import "../../../utils/structs/Checkpoints.sol"; +import {ERC721} from "../ERC721.sol"; +import {IERC2309} from "../../../interfaces/IERC2309.sol"; +import {BitMaps} from "../../../utils/structs/BitMaps.sol"; +import {Checkpoints} from "../../../utils/structs/Checkpoints.sol"; /** * @dev Implementation of the ERC2309 "Consecutive Transfer Extension" as defined in diff --git a/contracts/token/ERC721/extensions/ERC721Enumerable.sol b/contracts/token/ERC721/extensions/ERC721Enumerable.sol index 18e2ba5d6..2e33123b6 100644 --- a/contracts/token/ERC721/extensions/ERC721Enumerable.sol +++ b/contracts/token/ERC721/extensions/ERC721Enumerable.sol @@ -3,8 +3,9 @@ pragma solidity ^0.8.19; -import "../ERC721.sol"; -import "./IERC721Enumerable.sol"; +import {ERC721} from "../ERC721.sol"; +import {IERC721Enumerable} from "./IERC721Enumerable.sol"; +import {IERC165} from "../../../utils/introspection/ERC165.sol"; /** * @dev This implements an optional extension of {ERC721} defined in the EIP that adds enumerability diff --git a/contracts/token/ERC721/extensions/ERC721Pausable.sol b/contracts/token/ERC721/extensions/ERC721Pausable.sol index a9472c5dc..a4effc549 100644 --- a/contracts/token/ERC721/extensions/ERC721Pausable.sol +++ b/contracts/token/ERC721/extensions/ERC721Pausable.sol @@ -3,8 +3,8 @@ pragma solidity ^0.8.19; -import "../ERC721.sol"; -import "../../../security/Pausable.sol"; +import {ERC721} from "../ERC721.sol"; +import {Pausable} from "../../../security/Pausable.sol"; /** * @dev ERC721 token with pausable token transfers, minting and burning. diff --git a/contracts/token/ERC721/extensions/ERC721Royalty.sol b/contracts/token/ERC721/extensions/ERC721Royalty.sol index 7d6ef6c04..b4518dc24 100644 --- a/contracts/token/ERC721/extensions/ERC721Royalty.sol +++ b/contracts/token/ERC721/extensions/ERC721Royalty.sol @@ -3,9 +3,9 @@ pragma solidity ^0.8.19; -import "../ERC721.sol"; -import "../../common/ERC2981.sol"; -import "../../../utils/introspection/ERC165.sol"; +import {ERC721} from "../ERC721.sol"; +import {ERC2981} from "../../common/ERC2981.sol"; +import {ERC165} from "../../../utils/introspection/ERC165.sol"; /** * @dev Extension of ERC721 with the ERC2981 NFT Royalty Standard, a standardized way to retrieve royalty payment diff --git a/contracts/token/ERC721/extensions/ERC721URIStorage.sol b/contracts/token/ERC721/extensions/ERC721URIStorage.sol index ae625fc28..737d28e27 100644 --- a/contracts/token/ERC721/extensions/ERC721URIStorage.sol +++ b/contracts/token/ERC721/extensions/ERC721URIStorage.sol @@ -3,8 +3,10 @@ pragma solidity ^0.8.19; -import "../ERC721.sol"; -import "../../../interfaces/IERC4906.sol"; +import {ERC721} from "../ERC721.sol"; +import {Strings} from "../../../utils/Strings.sol"; +import {IERC4906} from "../../../interfaces/IERC4906.sol"; +import {IERC165} from "../../../interfaces/IERC165.sol"; /** * @dev ERC721 token with storage based token URI management. diff --git a/contracts/token/ERC721/extensions/ERC721Votes.sol b/contracts/token/ERC721/extensions/ERC721Votes.sol index 89b2e073c..1e694e4c7 100644 --- a/contracts/token/ERC721/extensions/ERC721Votes.sol +++ b/contracts/token/ERC721/extensions/ERC721Votes.sol @@ -3,8 +3,8 @@ pragma solidity ^0.8.19; -import "../ERC721.sol"; -import "../../../governance/utils/Votes.sol"; +import {ERC721} from "../ERC721.sol"; +import {Votes} from "../../../governance/utils/Votes.sol"; /** * @dev Extension of ERC721 to support voting and delegation as implemented by {Votes}, where each individual NFT counts diff --git a/contracts/token/ERC721/extensions/ERC721Wrapper.sol b/contracts/token/ERC721/extensions/ERC721Wrapper.sol index 47a42c1f0..b8c396c3e 100644 --- a/contracts/token/ERC721/extensions/ERC721Wrapper.sol +++ b/contracts/token/ERC721/extensions/ERC721Wrapper.sol @@ -3,7 +3,8 @@ pragma solidity ^0.8.19; -import "../ERC721.sol"; +import {IERC721, ERC721} from "../ERC721.sol"; +import {IERC721Receiver} from "../IERC721Receiver.sol"; /** * @dev Extension of the ERC721 token contract to support token wrapping. diff --git a/contracts/token/ERC721/extensions/IERC721Enumerable.sol b/contracts/token/ERC721/extensions/IERC721Enumerable.sol index d5fe6330c..75b04581f 100644 --- a/contracts/token/ERC721/extensions/IERC721Enumerable.sol +++ b/contracts/token/ERC721/extensions/IERC721Enumerable.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.19; -import "../IERC721.sol"; +import {IERC721} from "../IERC721.sol"; /** * @title ERC-721 Non-Fungible Token Standard, optional enumeration extension diff --git a/contracts/token/ERC721/extensions/IERC721Metadata.sol b/contracts/token/ERC721/extensions/IERC721Metadata.sol index 0b085812c..3d2846231 100644 --- a/contracts/token/ERC721/extensions/IERC721Metadata.sol +++ b/contracts/token/ERC721/extensions/IERC721Metadata.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.19; -import "../IERC721.sol"; +import {IERC721} from "../IERC721.sol"; /** * @title ERC-721 Non-Fungible Token Standard, optional metadata extension diff --git a/contracts/token/ERC721/utils/ERC721Holder.sol b/contracts/token/ERC721/utils/ERC721Holder.sol index a3ee8b5f0..e1fad1e23 100644 --- a/contracts/token/ERC721/utils/ERC721Holder.sol +++ b/contracts/token/ERC721/utils/ERC721Holder.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.19; -import "../IERC721Receiver.sol"; +import {IERC721Receiver} from "../IERC721Receiver.sol"; /** * @dev Implementation of the {IERC721Receiver} interface. diff --git a/contracts/token/common/ERC2981.sol b/contracts/token/common/ERC2981.sol index 21869ee25..b9183e34e 100644 --- a/contracts/token/common/ERC2981.sol +++ b/contracts/token/common/ERC2981.sol @@ -3,8 +3,8 @@ pragma solidity ^0.8.19; -import "../../interfaces/IERC2981.sol"; -import "../../utils/introspection/ERC165.sol"; +import {IERC2981} from "../../interfaces/IERC2981.sol"; +import {IERC165, ERC165} from "../../utils/introspection/ERC165.sol"; /** * @dev Implementation of the NFT Royalty Standard, a standardized way to retrieve royalty payment information. diff --git a/contracts/utils/Arrays.sol b/contracts/utils/Arrays.sol index f4ef45645..7dbc86fdd 100644 --- a/contracts/utils/Arrays.sol +++ b/contracts/utils/Arrays.sol @@ -3,8 +3,8 @@ pragma solidity ^0.8.19; -import "./StorageSlot.sol"; -import "./math/Math.sol"; +import {StorageSlot} from "./StorageSlot.sol"; +import {Math} from "./math/Math.sol"; /** * @dev Collection of functions related to array types. diff --git a/contracts/utils/Multicall.sol b/contracts/utils/Multicall.sol index 8e0ef8195..fed9844c4 100644 --- a/contracts/utils/Multicall.sol +++ b/contracts/utils/Multicall.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.19; -import "./Address.sol"; +import {Address} from "./Address.sol"; /** * @dev Provides a function to batch together multiple calls in a single external call. diff --git a/contracts/utils/ShortStrings.sol b/contracts/utils/ShortStrings.sol index a6cb1e620..f90ccd77a 100644 --- a/contracts/utils/ShortStrings.sol +++ b/contracts/utils/ShortStrings.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.19; -import "./StorageSlot.sol"; +import {StorageSlot} from "./StorageSlot.sol"; // | string | 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA | // | length | 0x BB | diff --git a/contracts/utils/Strings.sol b/contracts/utils/Strings.sol index afbf463d1..fd8d66229 100644 --- a/contracts/utils/Strings.sol +++ b/contracts/utils/Strings.sol @@ -3,8 +3,8 @@ pragma solidity ^0.8.19; -import "./math/Math.sol"; -import "./math/SignedMath.sol"; +import {Math} from "./math/Math.sol"; +import {SignedMath} from "./math/SignedMath.sol"; /** * @dev String operations. diff --git a/contracts/utils/cryptography/ECDSA.sol b/contracts/utils/cryptography/ECDSA.sol index b8f1affee..5e67a7591 100644 --- a/contracts/utils/cryptography/ECDSA.sol +++ b/contracts/utils/cryptography/ECDSA.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.19; -import "../Strings.sol"; +import {Strings} from "../Strings.sol"; /** * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations. diff --git a/contracts/utils/cryptography/EIP712.sol b/contracts/utils/cryptography/EIP712.sol index 1089de005..d94e956af 100644 --- a/contracts/utils/cryptography/EIP712.sol +++ b/contracts/utils/cryptography/EIP712.sol @@ -3,9 +3,9 @@ pragma solidity ^0.8.19; -import "./ECDSA.sol"; -import "../ShortStrings.sol"; -import "../../interfaces/IERC5267.sol"; +import {ECDSA} from "./ECDSA.sol"; +import {ShortStrings, ShortString} from "../ShortStrings.sol"; +import {IERC5267} from "../../interfaces/IERC5267.sol"; /** * @dev https://eips.ethereum.org/EIPS/eip-712[EIP 712] is a standard for hashing and signing of typed structured data. diff --git a/contracts/utils/cryptography/SignatureChecker.sol b/contracts/utils/cryptography/SignatureChecker.sol index 25fdee5b3..dfc512b39 100644 --- a/contracts/utils/cryptography/SignatureChecker.sol +++ b/contracts/utils/cryptography/SignatureChecker.sol @@ -3,8 +3,8 @@ pragma solidity ^0.8.19; -import "./ECDSA.sol"; -import "../../interfaces/IERC1271.sol"; +import {ECDSA} from "./ECDSA.sol"; +import {IERC1271} from "../../interfaces/IERC1271.sol"; /** * @dev Signature verification helper that can be used instead of `ECDSA.recover` to seamlessly support both ECDSA diff --git a/contracts/utils/introspection/ERC165.sol b/contracts/utils/introspection/ERC165.sol index 111913007..56fe1b80a 100644 --- a/contracts/utils/introspection/ERC165.sol +++ b/contracts/utils/introspection/ERC165.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.19; -import "./IERC165.sol"; +import {IERC165} from "./IERC165.sol"; /** * @dev Implementation of the {IERC165} interface. diff --git a/contracts/utils/introspection/ERC165Checker.sol b/contracts/utils/introspection/ERC165Checker.sol index c27b2f17d..d614c6755 100644 --- a/contracts/utils/introspection/ERC165Checker.sol +++ b/contracts/utils/introspection/ERC165Checker.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.19; -import "./IERC165.sol"; +import {IERC165} from "./IERC165.sol"; /** * @dev Library used to query support of an interface declared via {IERC165}. diff --git a/contracts/utils/structs/Checkpoints.sol b/contracts/utils/structs/Checkpoints.sol index 7608d787e..47ad91bbf 100644 --- a/contracts/utils/structs/Checkpoints.sol +++ b/contracts/utils/structs/Checkpoints.sol @@ -4,8 +4,8 @@ pragma solidity ^0.8.19; -import "../math/Math.sol"; -import "../math/SafeCast.sol"; +import {Math} from "../math/Math.sol"; +import {SafeCast} from "../math/SafeCast.sol"; /** * @dev This library defines the `History` struct, for checkpointing values as they change at different points in diff --git a/contracts/utils/structs/DoubleEndedQueue.sol b/contracts/utils/structs/DoubleEndedQueue.sol index 69db70040..5183bad8c 100644 --- a/contracts/utils/structs/DoubleEndedQueue.sol +++ b/contracts/utils/structs/DoubleEndedQueue.sol @@ -2,7 +2,7 @@ // OpenZeppelin Contracts (last updated v4.9.0) (utils/structs/DoubleEndedQueue.sol) pragma solidity ^0.8.19; -import "../math/SafeCast.sol"; +import {SafeCast} from "../math/SafeCast.sol"; /** * @dev A sequence of items with the ability to efficiently push and pop items (i.e. insert and remove) on both ends of diff --git a/contracts/utils/structs/EnumerableMap.sol b/contracts/utils/structs/EnumerableMap.sol index a474e82b3..c0ad9a2c9 100644 --- a/contracts/utils/structs/EnumerableMap.sol +++ b/contracts/utils/structs/EnumerableMap.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.19; -import "./EnumerableSet.sol"; +import {EnumerableSet} from "./EnumerableSet.sol"; /** * @dev Library for managing an enumerable variant of Solidity's diff --git a/docs/modules/ROOT/pages/access-control.adoc b/docs/modules/ROOT/pages/access-control.adoc index 11a5fab0c..aa28376ad 100644 --- a/docs/modules/ROOT/pages/access-control.adoc +++ b/docs/modules/ROOT/pages/access-control.adoc @@ -15,7 +15,7 @@ OpenZeppelin Contracts provides xref:api:access.adoc#Ownable[`Ownable`] for impl // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; -import "@openzeppelin/contracts/access/Ownable.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; contract MyContract is Ownable { function normalThing() public { @@ -64,8 +64,8 @@ Here's a simple example of using `AccessControl` in an xref:tokens.adoc#ERC20[`E // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; -import "@openzeppelin/contracts/access/AccessControl.sol"; -import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; contract MyToken is ERC20, AccessControl { // Create a new role identifier for the minter role @@ -96,8 +96,8 @@ Let's augment our ERC20 token example by also defining a 'burner' role, which le // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; -import "@openzeppelin/contracts/access/AccessControl.sol"; -import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; contract MyToken is ERC20, AccessControl { bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); @@ -141,8 +141,8 @@ Let's take a look at the ERC20 token example, this time taking advantage of the // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; -import "@openzeppelin/contracts/access/AccessControl.sol"; -import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; contract MyToken is ERC20, AccessControl { bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); diff --git a/docs/modules/ROOT/pages/erc1155.adoc b/docs/modules/ROOT/pages/erc1155.adoc index 067bde89e..f1d2f88ce 100644 --- a/docs/modules/ROOT/pages/erc1155.adoc +++ b/docs/modules/ROOT/pages/erc1155.adoc @@ -36,7 +36,7 @@ Here's what a contract for tokenized items might look like: // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; -import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; +import {ERC1155} from "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; contract GameItems is ERC1155 { uint256 public constant GOLD = 0; @@ -136,7 +136,7 @@ In order for our contract to receive ERC1155 tokens we can inherit from the conv // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; -import "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol"; +import {ERC1155Holder} from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol"; contract MyContract is ERC1155Holder { } diff --git a/docs/modules/ROOT/pages/erc20.adoc b/docs/modules/ROOT/pages/erc20.adoc index 817d364b1..2ffa60f2a 100644 --- a/docs/modules/ROOT/pages/erc20.adoc +++ b/docs/modules/ROOT/pages/erc20.adoc @@ -17,7 +17,7 @@ Here's what our GLD token might look like. // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; -import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; contract GLDToken is ERC20 { constructor(uint256 initialSupply) ERC20("Gold", "GLD") { diff --git a/docs/modules/ROOT/pages/erc721.adoc b/docs/modules/ROOT/pages/erc721.adoc index 6d839c81e..cd4d14b8f 100644 --- a/docs/modules/ROOT/pages/erc721.adoc +++ b/docs/modules/ROOT/pages/erc721.adoc @@ -16,7 +16,7 @@ Here's what a contract for tokenized items might look like: // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; -import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; +import {ERC721URIStorage} from "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; contract GameItem is ERC721URIStorage { uint256 private _nextTokenId; diff --git a/docs/modules/ROOT/pages/extending-contracts.adoc b/docs/modules/ROOT/pages/extending-contracts.adoc index df93f2133..ac6730984 100644 --- a/docs/modules/ROOT/pages/extending-contracts.adoc +++ b/docs/modules/ROOT/pages/extending-contracts.adoc @@ -22,7 +22,7 @@ For example, imagine you want to change xref:api:access.adoc#AccessControl[`Acce // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; -import "@openzeppelin/contracts/access/AccessControl.sol"; +import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; contract ModifiedAccessControl is AccessControl { // Override the revokeRole function @@ -50,7 +50,7 @@ Here is a modified version of xref:api:access.adoc#AccessControl[`AccessControl` // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; -import "@openzeppelin/contracts/access/AccessControl.sol"; +import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; contract ModifiedAccessControl is AccessControl { function revokeRole(bytes32 role, address account) public override { @@ -82,7 +82,7 @@ Here's how you would implement the `IERC721Receiver` pattern in `ERC20`, using t ```solidity pragma solidity ^0.8.19; -import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; contract ERC20WithSafeTransfer is ERC20 { function _beforeTokenTransfer(address from, address to, uint256 amount) diff --git a/docs/modules/ROOT/pages/governance.adoc b/docs/modules/ROOT/pages/governance.adoc index 3d2bc05f3..cae2f14f6 100644 --- a/docs/modules/ROOT/pages/governance.adoc +++ b/docs/modules/ROOT/pages/governance.adoc @@ -200,11 +200,12 @@ The Governor will automatically detect the clock mode used by the token and adap // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; -import "@openzeppelin/contracts/governance/Governor.sol"; -import "@openzeppelin/contracts/governance/compatibility/GovernorCompatibilityBravo.sol"; -import "@openzeppelin/contracts/governance/extensions/GovernorVotes.sol"; -import "@openzeppelin/contracts/governance/extensions/GovernorVotesQuorumFraction.sol"; -import "@openzeppelin/contracts/governance/extensions/GovernorTimelockControl.sol"; +import {Governor} from "@openzeppelin/contracts/governance/Governor.sol"; +import {GovernorCompatibilityBravo} from "@openzeppelin/contracts/governance/compatibility/GovernorCompatibilityBravo.sol"; +import {GovernorVotes} from "@openzeppelin/contracts/governance/extensions/GovernorVotes.sol"; +import {GovernorVotesQuorumFraction} from "@openzeppelin/contracts/governance/extensions/GovernorVotesQuorumFraction.sol"; +import {GovernorTimelockControl} from "@openzeppelin/contracts/governance/extensions/GovernorTimelockControl.sol"; +import {IVotes} from "@openzeppelin/contracts/governance/utils/IVotes.sol"; contract MyGovernor is Governor, GovernorCompatibilityBravo, GovernorVotes, GovernorVotesQuorumFraction, GovernorTimelockControl { constructor(IVotes _token, TimelockController _timelock) @@ -228,6 +229,7 @@ contract MyGovernor is Governor, GovernorCompatibilityBravo, GovernorVotes, Gove // ... } + ``` === Disclaimer diff --git a/docs/modules/ROOT/pages/index.adoc b/docs/modules/ROOT/pages/index.adoc index b46a8d5a3..b24412ae7 100644 --- a/docs/modules/ROOT/pages/index.adoc +++ b/docs/modules/ROOT/pages/index.adoc @@ -28,7 +28,7 @@ Once installed, you can use the contracts in the library by importing them: // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; +import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; contract MyNFT is ERC721 { constructor() ERC721("MyNFT", "MNFT") { diff --git a/docs/modules/ROOT/pages/upgradeable.adoc b/docs/modules/ROOT/pages/upgradeable.adoc index 2b8d27204..7324c9bad 100644 --- a/docs/modules/ROOT/pages/upgradeable.adoc +++ b/docs/modules/ROOT/pages/upgradeable.adoc @@ -21,8 +21,8 @@ $ npm install @openzeppelin/contracts-upgradeable The package replicates the structure of the main OpenZeppelin Contracts package, but every file and contract has the suffix `Upgradeable`. ```diff --import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -+import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol"; +-import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; ++import {ERC721Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol"; -contract MyCollectible is ERC721 { +contract MyCollectible is ERC721Upgradeable { diff --git a/docs/modules/ROOT/pages/utilities.adoc b/docs/modules/ROOT/pages/utilities.adoc index 0b178d9fe..3a1ba5420 100644 --- a/docs/modules/ROOT/pages/utilities.adoc +++ b/docs/modules/ROOT/pages/utilities.adoc @@ -110,9 +110,9 @@ Here is an example to send JSON Metadata through a Base64 Data URI using an ERC7 // contracts/My721Token.sol // SPDX-License-Identifier: MIT -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import "@openzeppelin/contracts/utils/Strings.sol"; -import "@openzeppelin/contracts/utils/Base64.sol"; +import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; +import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; +import {Base64} from "@openzeppelin/contracts/utils/Base64.sol"; contract My721Token is ERC721 { using Strings for uint256; diff --git a/package-lock.json b/package-lock.json index d4cb52694..a85611941 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,7 +29,7 @@ "glob": "^8.0.3", "graphlib": "^2.1.8", "hardhat": "^2.9.1", - "hardhat-exposed": "^0.3.3", + "hardhat-exposed": "^0.3.11", "hardhat-gas-reporter": "^1.0.4", "hardhat-ignore-warnings": "^0.2.0", "keccak256": "^1.0.2", @@ -7477,9 +7477,9 @@ } }, "node_modules/hardhat-exposed": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/hardhat-exposed/-/hardhat-exposed-0.3.6.tgz", - "integrity": "sha512-jb03M+GolhEfjDYJyPkGJzcAs1/eZrVufMADXQDwng2iuUJD1zgzM3k1oq2YQihRxguQDsLE1nuHIn6N8o5GmQ==", + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/hardhat-exposed/-/hardhat-exposed-0.3.11.tgz", + "integrity": "sha512-2Gwdfx1YpSWUX80kuq0zM1tNqDJoTBCd5Q5oXKPxz6STSy1TiDyDb1miUr4Dwc/Dp2lzWzM+fZjpUrMe5O08Hw==", "dev": true, "dependencies": { "micromatch": "^4.0.4", @@ -21420,9 +21420,9 @@ } }, "hardhat-exposed": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/hardhat-exposed/-/hardhat-exposed-0.3.6.tgz", - "integrity": "sha512-jb03M+GolhEfjDYJyPkGJzcAs1/eZrVufMADXQDwng2iuUJD1zgzM3k1oq2YQihRxguQDsLE1nuHIn6N8o5GmQ==", + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/hardhat-exposed/-/hardhat-exposed-0.3.11.tgz", + "integrity": "sha512-2Gwdfx1YpSWUX80kuq0zM1tNqDJoTBCd5Q5oXKPxz6STSy1TiDyDb1miUr4Dwc/Dp2lzWzM+fZjpUrMe5O08Hw==", "dev": true, "requires": { "micromatch": "^4.0.4", diff --git a/package.json b/package.json index 37e8f8710..9eae67320 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,7 @@ "glob": "^8.0.3", "graphlib": "^2.1.8", "hardhat": "^2.9.1", - "hardhat-exposed": "^0.3.3", + "hardhat-exposed": "^0.3.11", "hardhat-gas-reporter": "^1.0.4", "hardhat-ignore-warnings": "^0.2.0", "keccak256": "^1.0.2", diff --git a/scripts/generate/templates/Checkpoints.js b/scripts/generate/templates/Checkpoints.js index d28134ce7..d5ab745a0 100644 --- a/scripts/generate/templates/Checkpoints.js +++ b/scripts/generate/templates/Checkpoints.js @@ -5,8 +5,8 @@ const { OPTS } = require('./Checkpoints.opts.js'); const header = `\ pragma solidity ^0.8.19; -import "../math/Math.sol"; -import "../math/SafeCast.sol"; +import {Math} from "../math/Math.sol"; +import {SafeCast} from "../math/SafeCast.sol"; /** * @dev This library defines the \`History\` struct, for checkpointing values as they change at different points in @@ -21,7 +21,7 @@ import "../math/SafeCast.sol"; const errors = `\ /** - * @dev A value was attempted to be inserted on a past checkpoint. + * @dev A value was attempted to be inserted on a past checkpoint. */ error CheckpointUnorderedInsertion(); `; diff --git a/scripts/generate/templates/Checkpoints.t.js b/scripts/generate/templates/Checkpoints.t.js index c5b225f38..492e5f8c2 100644 --- a/scripts/generate/templates/Checkpoints.t.js +++ b/scripts/generate/templates/Checkpoints.t.js @@ -6,9 +6,9 @@ const { OPTS } = require('./Checkpoints.opts.js'); const header = `\ pragma solidity ^0.8.19; -import "forge-std/Test.sol"; -import "../../../contracts/utils/math/SafeCast.sol"; -import "../../../contracts/utils/structs/Checkpoints.sol"; +import {Test} from "forge-std/Test.sol"; +import {SafeCast} from "../../../contracts/utils/math/SafeCast.sol"; +import {Checkpoints} from "../../../contracts/utils/structs/Checkpoints.sol"; `; /* eslint-disable max-len */ diff --git a/scripts/generate/templates/EnumerableMap.js b/scripts/generate/templates/EnumerableMap.js index 8899f4819..032f08681 100644 --- a/scripts/generate/templates/EnumerableMap.js +++ b/scripts/generate/templates/EnumerableMap.js @@ -12,7 +12,7 @@ const TYPES = [ const header = `\ pragma solidity ^0.8.19; -import "./EnumerableSet.sol"; +import {EnumerableSet} from "./EnumerableSet.sol"; /** * @dev Library for managing an enumerable variant of Solidity's diff --git a/scripts/upgradeable/upgradeable.patch b/scripts/upgradeable/upgradeable.patch index ecd0a3711..466d1d1ca 100644 --- a/scripts/upgradeable/upgradeable.patch +++ b/scripts/upgradeable/upgradeable.patch @@ -59,7 +59,7 @@ index ff596b0c..00000000 - - diff --git a/README.md b/README.md -index 9d1c405b..c264e29c 100644 +index 27627f43..e42a66e8 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,9 @@ @@ -94,8 +94,8 @@ index 9d1c405b..c264e29c 100644 ```solidity pragma solidity ^0.8.19; --import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -+import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol"; +-import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; ++import {ERC721Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol"; -contract MyCollectible is ERC721 { - constructor() ERC721("MyCollectible", "MCO") { @@ -106,10 +106,10 @@ index 9d1c405b..c264e29c 100644 } ``` diff --git a/contracts/finance/VestingWallet.sol b/contracts/finance/VestingWallet.sol -index ebdf0a33..8888803e 100644 +index f776a7ca..d7a7e0b0 100644 --- a/contracts/finance/VestingWallet.sol +++ b/contracts/finance/VestingWallet.sol -@@ -18,6 +18,8 @@ import "../utils/Context.sol"; +@@ -19,6 +19,8 @@ import {Context} from "../utils/Context.sol"; * * By setting the duration to 0, one can configure this contract to behave like an asset timelock that hold tokens for * a beneficiary until a specified time. @@ -119,10 +119,10 @@ index ebdf0a33..8888803e 100644 contract VestingWallet is Context { event EtherReleased(uint256 amount); diff --git a/contracts/governance/extensions/GovernorVotes.sol b/contracts/governance/extensions/GovernorVotes.sol -index 5d8318f4..ef3cde55 100644 +index bb14d7fd..0785ebbd 100644 --- a/contracts/governance/extensions/GovernorVotes.sol +++ b/contracts/governance/extensions/GovernorVotes.sol -@@ -10,6 +10,8 @@ import "../../interfaces/IERC5805.sol"; +@@ -12,6 +12,8 @@ import {SafeCast} from "../../utils/math/SafeCast.sol"; * @dev Extension of {Governor} for voting weight extraction from an {ERC20Votes} token, or since v4.5 an {ERC721Votes} token. * * _Available since v4.3._ @@ -152,10 +152,10 @@ index df141192..1cf90ad1 100644 "keywords": [ "solidity", diff --git a/contracts/token/ERC20/extensions/ERC20Capped.sol b/contracts/token/ERC20/extensions/ERC20Capped.sol -index 41e9ce5c..1d910dfa 100644 +index 98ec7144..4992115b 100644 --- a/contracts/token/ERC20/extensions/ERC20Capped.sol +++ b/contracts/token/ERC20/extensions/ERC20Capped.sol -@@ -7,6 +7,8 @@ import "../ERC20.sol"; +@@ -7,6 +7,8 @@ import {ERC20} from "../ERC20.sol"; /** * @dev Extension of {ERC20} that adds a cap to the supply of tokens. @@ -165,10 +165,10 @@ index 41e9ce5c..1d910dfa 100644 abstract contract ERC20Capped is ERC20 { uint256 private immutable _cap; diff --git a/contracts/token/ERC20/extensions/ERC20Permit.sol b/contracts/token/ERC20/extensions/ERC20Permit.sol -index 4378eb7c..1da9e731 100644 +index 8778f4ba..825d4e16 100644 --- a/contracts/token/ERC20/extensions/ERC20Permit.sol +++ b/contracts/token/ERC20/extensions/ERC20Permit.sol -@@ -18,6 +18,8 @@ import "../../../utils/Nonces.sol"; +@@ -18,6 +18,8 @@ import {Nonces} from "../../../utils/Nonces.sol"; * need to send a transaction, and thus is not required to hold Ether at all. * * _Available since v3.4._ @@ -178,10 +178,10 @@ index 4378eb7c..1da9e731 100644 abstract contract ERC20Permit is ERC20, IERC20Permit, EIP712, Nonces { // solhint-disable-next-line var-name-mixedcase diff --git a/contracts/token/ERC20/extensions/ERC20Wrapper.sol b/contracts/token/ERC20/extensions/ERC20Wrapper.sol -index 389965e9..66436b14 100644 +index 2cbff622..97f43d7a 100644 --- a/contracts/token/ERC20/extensions/ERC20Wrapper.sol +++ b/contracts/token/ERC20/extensions/ERC20Wrapper.sol -@@ -14,6 +14,8 @@ import "../utils/SafeERC20.sol"; +@@ -14,6 +14,8 @@ import {SafeERC20} from "../utils/SafeERC20.sol"; * wrapping of an existing "basic" ERC20 into a governance token. * * _Available since v4.2._ @@ -191,18 +191,18 @@ index 389965e9..66436b14 100644 abstract contract ERC20Wrapper is ERC20 { IERC20 private immutable _underlying; diff --git a/contracts/utils/cryptography/EIP712.sol b/contracts/utils/cryptography/EIP712.sol -index 2628014f..7d5193c8 100644 +index d94e956a..86bb5713 100644 --- a/contracts/utils/cryptography/EIP712.sol +++ b/contracts/utils/cryptography/EIP712.sol @@ -4,7 +4,6 @@ pragma solidity ^0.8.19; - import "./ECDSA.sol"; --import "../ShortStrings.sol"; - import "../../interfaces/IERC5267.sol"; + import {ECDSA} from "./ECDSA.sol"; +-import {ShortStrings, ShortString} from "../ShortStrings.sol"; + import {IERC5267} from "../../interfaces/IERC5267.sol"; /** -@@ -30,27 +29,19 @@ import "../../interfaces/IERC5267.sol"; +@@ -30,27 +29,19 @@ import {IERC5267} from "../../interfaces/IERC5267.sol"; * * _Available since v3.4._ * @@ -361,7 +361,7 @@ index 2628014f..7d5193c8 100644 } } diff --git a/package.json b/package.json -index 37e8f871..d098669f 100644 +index 9eae6732..b3a56366 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ diff --git a/test/governance/Governor.t.sol b/test/governance/Governor.t.sol index 43c4c5ddd..1732fa2f9 100644 --- a/test/governance/Governor.t.sol +++ b/test/governance/Governor.t.sol @@ -2,9 +2,9 @@ pragma solidity ^0.8.19; -import "forge-std/Test.sol"; -import "../../contracts/utils/Strings.sol"; -import "../../contracts/governance/Governor.sol"; +import {Test} from "forge-std/Test.sol"; +import {Strings} from "../../contracts/utils/Strings.sol"; +import {Governor} from "../../contracts/governance/Governor.sol"; contract GovernorInternalTest is Test, Governor { constructor() Governor("") {} diff --git a/test/token/ERC721/extensions/ERC721Consecutive.t.sol b/test/token/ERC721/extensions/ERC721Consecutive.t.sol index 896a5ef57..617b17a46 100644 --- a/test/token/ERC721/extensions/ERC721Consecutive.t.sol +++ b/test/token/ERC721/extensions/ERC721Consecutive.t.sol @@ -4,8 +4,9 @@ pragma solidity ^0.8.19; // solhint-disable func-name-mixedcase -import "../../../../contracts/token/ERC721/extensions/ERC721Consecutive.sol"; -import "forge-std/Test.sol"; +import {ERC721} from "../../../../contracts/token/ERC721/ERC721.sol"; +import {ERC721Consecutive} from "../../../../contracts/token/ERC721/extensions/ERC721Consecutive.sol"; +import {Test, StdUtils} from "forge-std/Test.sol"; function toSingleton(address account) pure returns (address[] memory) { address[] memory accounts = new address[](1); diff --git a/test/utils/ShortStrings.t.sol b/test/utils/ShortStrings.t.sol index b70793bd7..c6aa5355b 100644 --- a/test/utils/ShortStrings.t.sol +++ b/test/utils/ShortStrings.t.sol @@ -2,9 +2,9 @@ pragma solidity ^0.8.19; -import "forge-std/Test.sol"; +import {Test} from "forge-std/Test.sol"; -import "../../contracts/utils/ShortStrings.sol"; +import {ShortStrings, ShortString} from "../../contracts/utils/ShortStrings.sol"; contract ShortStringsTest is Test { string _fallback; diff --git a/test/utils/math/Math.t.sol b/test/utils/math/Math.t.sol index 61f558e0e..5a6be476f 100644 --- a/test/utils/math/Math.t.sol +++ b/test/utils/math/Math.t.sol @@ -2,9 +2,9 @@ pragma solidity ^0.8.19; -import "forge-std/Test.sol"; +import {Test} from "forge-std/Test.sol"; -import "../../../contracts/utils/math/Math.sol"; +import {Math} from "../../../contracts/utils/math/Math.sol"; contract MathTest is Test { // CEILDIV diff --git a/test/utils/structs/Checkpoints.t.sol b/test/utils/structs/Checkpoints.t.sol index 1fb6dcb4e..bbc309226 100644 --- a/test/utils/structs/Checkpoints.t.sol +++ b/test/utils/structs/Checkpoints.t.sol @@ -3,9 +3,9 @@ pragma solidity ^0.8.19; -import "forge-std/Test.sol"; -import "../../../contracts/utils/math/SafeCast.sol"; -import "../../../contracts/utils/structs/Checkpoints.sol"; +import {Test} from "forge-std/Test.sol"; +import {SafeCast} from "../../../contracts/utils/math/SafeCast.sol"; +import {Checkpoints} from "../../../contracts/utils/structs/Checkpoints.sol"; contract CheckpointsTrace224Test is Test { using Checkpoints for Checkpoints.Trace224; From 04342118dcea68981405e4335ff781918a9748c8 Mon Sep 17 00:00:00 2001 From: Hicham010 <47334105+Hicham010@users.noreply.github.com> Date: Fri, 30 Jun 2023 18:08:46 +0200 Subject: [PATCH 130/182] Fix visibility in ERC721._checkOnERC721Received documentation (#4386) --- contracts/token/ERC721/ERC721.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/token/ERC721/ERC721.sol b/contracts/token/ERC721/ERC721.sol index 4232c616e..ba2b1573f 100644 --- a/contracts/token/ERC721/ERC721.sol +++ b/contracts/token/ERC721/ERC721.sol @@ -413,7 +413,7 @@ abstract contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Er } /** - * @dev Internal function to invoke {IERC721Receiver-onERC721Received} on a target address. + * @dev Private function to invoke {IERC721Receiver-onERC721Received} on a target address. * The call is not executed if the target address is not a contract. * * @param from address representing the previous owner of the given token ID From 621b867b1a14c40c44403f69c71d5317bd49a8da Mon Sep 17 00:00:00 2001 From: Sebastian T F Date: Fri, 30 Jun 2023 22:18:37 +0530 Subject: [PATCH 131/182] Imrove `BitMaps` documentation (#4400) Co-authored-by: Francisco Co-authored-by: ernestognw --- contracts/utils/structs/BitMaps.sol | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/contracts/utils/structs/BitMaps.sol b/contracts/utils/structs/BitMaps.sol index 9786c7f8e..5b62d2f07 100644 --- a/contracts/utils/structs/BitMaps.sol +++ b/contracts/utils/structs/BitMaps.sol @@ -3,8 +3,17 @@ pragma solidity ^0.8.19; /** - * @dev Library for managing uint256 to bool mapping in a compact and efficient way, providing the keys are sequential. + * @dev Library for managing uint256 to bool mapping in a compact and efficient way, provided the keys are sequential. * Largely inspired by Uniswap's https://github.com/Uniswap/merkle-distributor/blob/master/contracts/MerkleDistributor.sol[merkle-distributor]. + * + * BitMaps pack 256 booleans across each bit of a single 256-bit slot of `uint256` type. + * Hence booleans corresponding to 256 _sequential_ indices would only consume a single slot, + * unlike the regular `bool` which would consume an entire slot for a single value. + * + * This results in gas savings in two ways: + * + * - Setting a zero value to non-zero only once every 256 times + * - Accessing the same warm slot for every 256 _sequential_ indices */ library BitMaps { struct BitMap { From 37270eb08a15f096e9af38610dabdeacff0b0351 Mon Sep 17 00:00:00 2001 From: Francisco Date: Fri, 30 Jun 2023 16:52:45 -0300 Subject: [PATCH 132/182] Add security considerations to ERC2771Forwarder (#4406) --- contracts/metatx/ERC2771Forwarder.sol | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/contracts/metatx/ERC2771Forwarder.sol b/contracts/metatx/ERC2771Forwarder.sol index 290b438b8..f271d5d3b 100644 --- a/contracts/metatx/ERC2771Forwarder.sol +++ b/contracts/metatx/ERC2771Forwarder.sol @@ -20,6 +20,25 @@ import {Address} from "../utils/Address.sol"; * * `nonce`: A unique transaction ordering identifier to avoid replayability and request invalidation. * * `deadline`: A timestamp after which the request is not executable anymore. * * `data`: Encoded `msg.data` to send with the requested call. + * + * Relayers are able to submit batches if they are processing a high volume of requests. With high + * throughput, relayers may run into limitations of the chain such as limits on the number of + * transactions in the mempool. In these cases the recommendation is to distribute the load among + * multiple accounts. + * + * ==== Security Considerations + * + * If a relayer submits a forward request, it should be willing to pay up to 100% of the gas amount + * specified in the request. This contract does not implement any kind of retribution for this gas, + * and it is assumed that there is an out of band incentive for relayers to pay for execution on + * behalf of signers. Often, the relayer is operated by a project that will consider it a user + * acquisition cost. + * + * By offering to pay for gas, relayers are at risk of having that gas used by an attacker toward + * some other purpose that is not aligned with the expected out of band incentives. If you operate a + * relayer, consider whitelisting target contracts and function selectors. When relaying ERC-721 or + * ERC-1155 transfers specifically, consider rejecting the use of the `data` field, since it can be + * used to execute arbitrary code. */ contract ERC2771Forwarder is EIP712, Nonces { using ECDSA for bytes32; From 06861dce54a0145ede32dcd11e2a6181c250eb0d Mon Sep 17 00:00:00 2001 From: Paul Razvan Berg Date: Sat, 1 Jul 2023 07:36:10 +0300 Subject: [PATCH 133/182] Update docs for `SafeERC20.forceApprove` (#4231) --- contracts/token/ERC20/utils/SafeERC20.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/token/ERC20/utils/SafeERC20.sol b/contracts/token/ERC20/utils/SafeERC20.sol index 0ec21573b..51468368f 100644 --- a/contracts/token/ERC20/utils/SafeERC20.sol +++ b/contracts/token/ERC20/utils/SafeERC20.sol @@ -70,8 +70,8 @@ library SafeERC20 { /** * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value, - * non-reverting calls are assumed to be successful. Compatible with tokens that require the approval to be set to - * 0 before setting it to a non-zero value. + * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval + * to be set to zero before setting it to a non-zero value, such as USDT. */ function forceApprove(IERC20 token, address spender, uint256 value) internal { bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value)); From bb64458928d08759e1f36cc0c11a8e76c976de0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Mon, 3 Jul 2023 12:02:06 -0600 Subject: [PATCH 134/182] Implement recommendations from 5.0 audit Phase 1A (#4398) Co-authored-by: Francisco Giordano Co-authored-by: Hadrien Croubois --- contracts/governance/TimelockController.sol | 5 +- contracts/governance/utils/IVotes.sol | 4 +- contracts/interfaces/draft-IERC6093.sol | 1 + contracts/token/ERC1155/ERC1155.sol | 173 ++++++++++------ contracts/token/ERC1155/IERC1155.sol | 14 +- .../ERC1155/extensions/ERC1155Pausable.sol | 7 +- .../ERC1155/extensions/ERC1155Supply.sol | 41 ++-- contracts/token/ERC20/ERC20.sol | 96 ++++----- contracts/token/ERC20/IERC20.sol | 19 +- .../token/ERC20/extensions/ERC20Burnable.sol | 18 +- .../token/ERC20/extensions/ERC20Capped.sol | 4 +- .../token/ERC20/extensions/ERC20FlashMint.sol | 42 ++-- .../token/ERC20/extensions/ERC20Pausable.sol | 6 +- .../token/ERC20/extensions/ERC20Votes.sol | 11 +- .../token/ERC20/extensions/ERC20Wrapper.sol | 18 +- contracts/token/ERC721/ERC721.sol | 4 +- .../ERC721/extensions/ERC721Pausable.sol | 2 +- contracts/utils/Nonces.sol | 2 +- contracts/utils/structs/Checkpoints.sol | 9 +- scripts/generate/templates/Checkpoints.js | 7 +- test/governance/utils/Votes.behavior.js | 16 +- test/token/ERC1155/ERC1155.behavior.js | 195 +++++++++++------- test/token/ERC1155/ERC1155.test.js | 74 +++---- .../extensions/ERC1155Burnable.test.js | 18 +- .../extensions/ERC1155Pausable.test.js | 26 +-- .../ERC1155/extensions/ERC1155Supply.test.js | 40 ++-- .../extensions/ERC1155URIStorage.test.js | 6 +- .../token/ERC1155/utils/ERC1155Holder.test.js | 12 +- test/token/ERC20/ERC20.behavior.js | 122 +++++------ test/token/ERC20/ERC20.test.js | 144 +++++++------ .../extensions/ERC20Burnable.behavior.js | 60 +++--- .../ERC20/extensions/ERC20Capped.behavior.js | 4 +- .../ERC20/extensions/ERC20FlashMint.test.js | 48 ++--- .../ERC20/extensions/ERC20Pausable.test.js | 24 +-- .../token/ERC20/extensions/ERC20Votes.test.js | 38 ++-- .../ERC20/extensions/ERC20Wrapper.test.js | 17 ++ test/token/ERC20/extensions/ERC4626.test.js | 110 +++++----- .../ERC721/extensions/ERC721Votes.test.js | 8 +- 38 files changed, 779 insertions(+), 666 deletions(-) diff --git a/contracts/governance/TimelockController.sol b/contracts/governance/TimelockController.sol index 45d8642e8..56a25fe30 100644 --- a/contracts/governance/TimelockController.sol +++ b/contracts/governance/TimelockController.sol @@ -427,8 +427,9 @@ contract TimelockController is AccessControl, ERC721Holder, ERC1155Holder { * an operation where the timelock is the target and the data is the ABI-encoded call to this function. */ function updateDelay(uint256 newDelay) external virtual { - if (msg.sender != address(this)) { - revert TimelockUnauthorizedCaller(msg.sender); + address sender = _msgSender(); + if (sender != address(this)) { + revert TimelockUnauthorizedCaller(sender); } emit MinDelayChange(_minDelay, newDelay); _minDelay = newDelay; diff --git a/contracts/governance/utils/IVotes.sol b/contracts/governance/utils/IVotes.sol index a8a20856f..ee17721ff 100644 --- a/contracts/governance/utils/IVotes.sol +++ b/contracts/governance/utils/IVotes.sol @@ -19,9 +19,9 @@ interface IVotes { event DelegateChanged(address indexed delegator, address indexed fromDelegate, address indexed toDelegate); /** - * @dev Emitted when a token transfer or delegate change results in changes to a delegate's number of votes. + * @dev Emitted when a token transfer or delegate change results in changes to a delegate's number of voting units. */ - event DelegateVotesChanged(address indexed delegate, uint256 previousBalance, uint256 newBalance); + event DelegateVotesChanged(address indexed delegate, uint256 previousVotes, uint256 newVotes); /** * @dev Returns the current amount of votes that `account` has. diff --git a/contracts/interfaces/draft-IERC6093.sol b/contracts/interfaces/draft-IERC6093.sol index fbe31051a..08e77553c 100644 --- a/contracts/interfaces/draft-IERC6093.sol +++ b/contracts/interfaces/draft-IERC6093.sol @@ -118,6 +118,7 @@ interface IERC1155Errors { * @param sender Address whose tokens are being transferred. * @param balance Current balance for the interacting account. * @param needed Minimum amount required to perform a transfer. + * @param tokenId Identifier number of a token. */ error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId); diff --git a/contracts/token/ERC1155/ERC1155.sol b/contracts/token/ERC1155/ERC1155.sol index 9d70714fa..a9028f323 100644 --- a/contracts/token/ERC1155/ERC1155.sol +++ b/contracts/token/ERC1155/ERC1155.sol @@ -58,16 +58,12 @@ abstract contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI, IER * Clients calling this function must replace the `\{id\}` substring with the * actual token type ID. */ - function uri(uint256) public view virtual returns (string memory) { + function uri(uint256 /* id */) public view virtual returns (string memory) { return _uri; } /** * @dev See {IERC1155-balanceOf}. - * - * Requirements: - * - * - `account` cannot be the zero address. */ function balanceOf(address account, uint256 id) public view virtual returns (uint256) { return _balances[id][account]; @@ -114,11 +110,12 @@ abstract contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI, IER /** * @dev See {IERC1155-safeTransferFrom}. */ - function safeTransferFrom(address from, address to, uint256 id, uint256 amount, bytes memory data) public virtual { - if (from != _msgSender() && !isApprovedForAll(from, _msgSender())) { - revert ERC1155MissingApprovalForAll(_msgSender(), from); + function safeTransferFrom(address from, address to, uint256 id, uint256 value, bytes memory data) public virtual { + address sender = _msgSender(); + if (from != sender && !isApprovedForAll(from, sender)) { + revert ERC1155MissingApprovalForAll(sender, from); } - _safeTransferFrom(from, to, id, amount, data); + _safeTransferFrom(from, to, id, value, data); } /** @@ -128,17 +125,18 @@ abstract contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI, IER address from, address to, uint256[] memory ids, - uint256[] memory amounts, + uint256[] memory values, bytes memory data ) public virtual { - if (from != _msgSender() && !isApprovedForAll(from, _msgSender())) { - revert ERC1155MissingApprovalForAll(_msgSender(), from); + address sender = _msgSender(); + if (from != sender && !isApprovedForAll(from, sender)) { + revert ERC1155MissingApprovalForAll(sender, from); } - _safeBatchTransferFrom(from, to, ids, amounts, data); + _safeBatchTransferFrom(from, to, ids, values, data); } /** - * @dev Transfers `amount` tokens of token type `id` from `from` to `to`. Will mint (or burn) if `from` (or `to`) is the zero address. + * @dev Transfers a `value` amount of tokens of type `id` from `from` to `to`. Will mint (or burn) if `from` (or `to`) is the zero address. * * Emits a {TransferSingle} event if the arrays contain one element, and {TransferBatch} otherwise. * @@ -146,75 +144,96 @@ abstract contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI, IER * * - If `to` refers to a smart contract, it must implement either {IERC1155Receiver-onERC1155Received} * or {IERC1155Receiver-onERC1155BatchReceived} and return the acceptance magic value. + * - `ids` and `values` must have the same length. + * + * NOTE: The ERC-1155 acceptance check is not performed in this function. See {_updateWithAcceptanceCheck} instead. */ - function _update( - address from, - address to, - uint256[] memory ids, - uint256[] memory amounts, - bytes memory data - ) internal virtual { - if (ids.length != amounts.length) { - revert ERC1155InvalidArrayLength(ids.length, amounts.length); + function _update(address from, address to, uint256[] memory ids, uint256[] memory values) internal virtual { + if (ids.length != values.length) { + revert ERC1155InvalidArrayLength(ids.length, values.length); } address operator = _msgSender(); for (uint256 i = 0; i < ids.length; ++i) { uint256 id = ids.unsafeMemoryAccess(i); - uint256 amount = amounts.unsafeMemoryAccess(i); + uint256 value = values.unsafeMemoryAccess(i); if (from != address(0)) { uint256 fromBalance = _balances[id][from]; - if (fromBalance < amount) { - revert ERC1155InsufficientBalance(from, fromBalance, amount, id); + if (fromBalance < value) { + revert ERC1155InsufficientBalance(from, fromBalance, value, id); } unchecked { - _balances[id][from] = fromBalance - amount; + // Overflow not possible: value <= fromBalance + _balances[id][from] = fromBalance - value; } } if (to != address(0)) { - _balances[id][to] += amount; + _balances[id][to] += value; } } if (ids.length == 1) { uint256 id = ids.unsafeMemoryAccess(0); - uint256 amount = amounts.unsafeMemoryAccess(0); - emit TransferSingle(operator, from, to, id, amount); - if (to != address(0)) { - _doSafeTransferAcceptanceCheck(operator, from, to, id, amount, data); - } + uint256 value = values.unsafeMemoryAccess(0); + emit TransferSingle(operator, from, to, id, value); } else { - emit TransferBatch(operator, from, to, ids, amounts); - if (to != address(0)) { - _doSafeBatchTransferAcceptanceCheck(operator, from, to, ids, amounts, data); + emit TransferBatch(operator, from, to, ids, values); + } + } + + /** + * @dev Version of {_update} that performs the token acceptance check by calling {IERC1155Receiver-onERC1155Received} + * or {IERC1155Receiver-onERC1155BatchReceived} on the receiver address if it contains code (eg. is a smart contract + * at the moment of execution). + * + * IMPORTANT: Overriding this function is discouraged because it poses a reentrancy risk from the receiver. So any + * update to the contract state after this function would break the check-effect-interaction pattern. Consider + * overriding {_update} instead. + */ + function _updateWithAcceptanceCheck( + address from, + address to, + uint256[] memory ids, + uint256[] memory values, + bytes memory data + ) internal virtual { + _update(from, to, ids, values); + if (to != address(0)) { + address operator = _msgSender(); + if (ids.length == 1) { + uint256 id = ids.unsafeMemoryAccess(0); + uint256 value = values.unsafeMemoryAccess(0); + _doSafeTransferAcceptanceCheck(operator, from, to, id, value, data); + } else { + _doSafeBatchTransferAcceptanceCheck(operator, from, to, ids, values, data); } } } /** - * @dev Transfers `amount` tokens of token type `id` from `from` to `to`. + * @dev Transfers a `value` tokens of token type `id` from `from` to `to`. * * Emits a {TransferSingle} event. * * Requirements: * * - `to` cannot be the zero address. - * - `from` must have a balance of tokens of type `id` of at least `amount`. + * - `from` must have a balance of tokens of type `id` of at least `value` amount. * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the * acceptance magic value. */ - function _safeTransferFrom(address from, address to, uint256 id, uint256 amount, bytes memory data) internal { + function _safeTransferFrom(address from, address to, uint256 id, uint256 value, bytes memory data) internal { if (to == address(0)) { revert ERC1155InvalidReceiver(address(0)); } if (from == address(0)) { revert ERC1155InvalidSender(address(0)); } - (uint256[] memory ids, uint256[] memory amounts) = _asSingletonArrays(id, amount); - _update(from, to, ids, amounts, data); + (uint256[] memory ids, uint256[] memory values) = _asSingletonArrays(id, value); + _updateWithAcceptanceCheck(from, to, ids, values, data); } /** @@ -226,12 +245,13 @@ abstract contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI, IER * * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the * acceptance magic value. + * - `ids` and `values` must have the same length. */ function _safeBatchTransferFrom( address from, address to, uint256[] memory ids, - uint256[] memory amounts, + uint256[] memory values, bytes memory data ) internal { if (to == address(0)) { @@ -240,7 +260,7 @@ abstract contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI, IER if (from == address(0)) { revert ERC1155InvalidSender(address(0)); } - _update(from, to, ids, amounts, data); + _updateWithAcceptanceCheck(from, to, ids, values, data); } /** @@ -249,7 +269,7 @@ abstract contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI, IER * https://eips.ethereum.org/EIPS/eip-1155#metadata[defined in the EIP]. * * By this mechanism, any occurrence of the `\{id\}` substring in either the - * URI or any of the amounts in the JSON file at said URI will be replaced by + * URI or any of the values in the JSON file at said URI will be replaced by * clients with the token type ID. * * For example, the `https://token-cdn-domain/\{id\}.json` URI would be @@ -267,7 +287,7 @@ abstract contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI, IER } /** - * @dev Creates `amount` tokens of token type `id`, and assigns them to `to`. + * @dev Creates a `value` amount of tokens of type `id`, and assigns them to `to`. * * Emits a {TransferSingle} event. * @@ -277,12 +297,12 @@ abstract contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI, IER * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the * acceptance magic value. */ - function _mint(address to, uint256 id, uint256 amount, bytes memory data) internal { + function _mint(address to, uint256 id, uint256 value, bytes memory data) internal { if (to == address(0)) { revert ERC1155InvalidReceiver(address(0)); } - (uint256[] memory ids, uint256[] memory amounts) = _asSingletonArrays(id, amount); - _update(address(0), to, ids, amounts, data); + (uint256[] memory ids, uint256[] memory values) = _asSingletonArrays(id, value); + _updateWithAcceptanceCheck(address(0), to, ids, values, data); } /** @@ -292,33 +312,34 @@ abstract contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI, IER * * Requirements: * - * - `ids` and `amounts` must have the same length. + * - `ids` and `values` must have the same length. + * - `to` cannot be the zero address. * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the * acceptance magic value. */ - function _mintBatch(address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data) internal { + function _mintBatch(address to, uint256[] memory ids, uint256[] memory values, bytes memory data) internal { if (to == address(0)) { revert ERC1155InvalidReceiver(address(0)); } - _update(address(0), to, ids, amounts, data); + _updateWithAcceptanceCheck(address(0), to, ids, values, data); } /** - * @dev Destroys `amount` tokens of token type `id` from `from` + * @dev Destroys a `value` amount of tokens of type `id` from `from` * * Emits a {TransferSingle} event. * * Requirements: * * - `from` cannot be the zero address. - * - `from` must have at least `amount` tokens of token type `id`. + * - `from` must have at least `value` amount of tokens of type `id`. */ - function _burn(address from, uint256 id, uint256 amount) internal { + function _burn(address from, uint256 id, uint256 value) internal { if (from == address(0)) { revert ERC1155InvalidSender(address(0)); } - (uint256[] memory ids, uint256[] memory amounts) = _asSingletonArrays(id, amount); - _update(from, address(0), ids, amounts, ""); + (uint256[] memory ids, uint256[] memory values) = _asSingletonArrays(id, value); + _updateWithAcceptanceCheck(from, address(0), ids, values, ""); } /** @@ -328,38 +349,48 @@ abstract contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI, IER * * Requirements: * - * - `ids` and `amounts` must have the same length. + * - `from` cannot be the zero address. + * - `from` must have at least `value` amount of tokens of type `id`. + * - `ids` and `values` must have the same length. */ - function _burnBatch(address from, uint256[] memory ids, uint256[] memory amounts) internal { + function _burnBatch(address from, uint256[] memory ids, uint256[] memory values) internal { if (from == address(0)) { revert ERC1155InvalidSender(address(0)); } - _update(from, address(0), ids, amounts, ""); + _updateWithAcceptanceCheck(from, address(0), ids, values, ""); } /** * @dev Approve `operator` to operate on all of `owner` tokens * * Emits an {ApprovalForAll} event. + * + * Requirements: + * + * - `operator` cannot be the zero address. */ function _setApprovalForAll(address owner, address operator, bool approved) internal virtual { - if (owner == operator) { - revert ERC1155InvalidOperator(operator); + if (operator == address(0)) { + revert ERC1155InvalidOperator(address(0)); } _operatorApprovals[owner][operator] = approved; emit ApprovalForAll(owner, operator, approved); } + /** + * @dev Performs an acceptance check by calling {IERC1155-onERC1155Received} on the `to` address + * if it contains code at the moment of execution. + */ function _doSafeTransferAcceptanceCheck( address operator, address from, address to, uint256 id, - uint256 amount, + uint256 value, bytes memory data ) private { if (to.code.length > 0) { - try IERC1155Receiver(to).onERC1155Received(operator, from, id, amount, data) returns (bytes4 response) { + try IERC1155Receiver(to).onERC1155Received(operator, from, id, value, data) returns (bytes4 response) { if (response != IERC1155Receiver.onERC1155Received.selector) { // Tokens rejected revert ERC1155InvalidReceiver(to); @@ -378,16 +409,20 @@ abstract contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI, IER } } + /** + * @dev Performs a batch acceptance check by calling {IERC1155-onERC1155BatchReceived} on the `to` address + * if it contains code at the moment of execution. + */ function _doSafeBatchTransferAcceptanceCheck( address operator, address from, address to, uint256[] memory ids, - uint256[] memory amounts, + uint256[] memory values, bytes memory data ) private { if (to.code.length > 0) { - try IERC1155Receiver(to).onERC1155BatchReceived(operator, from, ids, amounts, data) returns ( + try IERC1155Receiver(to).onERC1155BatchReceived(operator, from, ids, values, data) returns ( bytes4 response ) { if (response != IERC1155Receiver.onERC1155BatchReceived.selector) { @@ -408,20 +443,28 @@ abstract contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI, IER } } + /** + * @dev Creates an array in memory with only one value for each of the elements provided. + */ function _asSingletonArrays( uint256 element1, uint256 element2 ) private pure returns (uint256[] memory array1, uint256[] memory array2) { /// @solidity memory-safe-assembly assembly { + // Load the free memory pointer array1 := mload(0x40) + // Set array length to 1 mstore(array1, 1) + // Store the single element at the next word after the length (where content starts) mstore(add(array1, 0x20), element1) + // Repeat for next array locating it right after the first array array2 := add(array1, 0x40) mstore(array2, 1) mstore(add(array2, 0x20), element2) + // Update the free memory pointer by pointing after the second array mstore(0x40, add(array2, 0x40)) } } diff --git a/contracts/token/ERC1155/IERC1155.sol b/contracts/token/ERC1155/IERC1155.sol index c450eec2f..1b0b0ae9d 100644 --- a/contracts/token/ERC1155/IERC1155.sol +++ b/contracts/token/ERC1155/IERC1155.sol @@ -13,7 +13,7 @@ import {IERC165} from "../../utils/introspection/IERC165.sol"; */ interface IERC1155 is IERC165 { /** - * @dev Emitted when `value` tokens of token type `id` are transferred from `from` to `to` by `operator`. + * @dev Emitted when `value` amount of tokens of type `id` are transferred from `from` to `to` by `operator`. */ event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value); @@ -45,7 +45,7 @@ interface IERC1155 is IERC165 { event URI(string value, uint256 indexed id); /** - * @dev Returns the amount of tokens of token type `id` owned by `account`. + * @dev Returns the value of tokens of token type `id` owned by `account`. * * Requirements: * @@ -84,7 +84,7 @@ interface IERC1155 is IERC165 { function isApprovedForAll(address account, address operator) external view returns (bool); /** - * @dev Transfers `amount` tokens of token type `id` from `from` to `to`. + * @dev Transfers a `value` amount of tokens of type `id` from `from` to `to`. * * WARNING: This function can potentially allow a reentrancy attack when transferring tokens * to an untrusted contract, when invoking {onERC1155Received} on the receiver. @@ -97,11 +97,11 @@ interface IERC1155 is IERC165 { * * - `to` cannot be the zero address. * - If the caller is not `from`, it must have been approved to spend ``from``'s tokens via {setApprovalForAll}. - * - `from` must have a balance of tokens of type `id` of at least `amount`. + * - `from` must have a balance of tokens of type `id` of at least `value` amount. * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the * acceptance magic value. */ - function safeTransferFrom(address from, address to, uint256 id, uint256 amount, bytes calldata data) external; + function safeTransferFrom(address from, address to, uint256 id, uint256 value, bytes calldata data) external; /** * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {safeTransferFrom}. @@ -116,7 +116,7 @@ interface IERC1155 is IERC165 { * * Requirements: * - * - `ids` and `amounts` must have the same length. + * - `ids` and `values` must have the same length. * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the * acceptance magic value. */ @@ -124,7 +124,7 @@ interface IERC1155 is IERC165 { address from, address to, uint256[] calldata ids, - uint256[] calldata amounts, + uint256[] calldata values, bytes calldata data ) external; } diff --git a/contracts/token/ERC1155/extensions/ERC1155Pausable.sol b/contracts/token/ERC1155/extensions/ERC1155Pausable.sol index 05aa8a780..960cd3b6e 100644 --- a/contracts/token/ERC1155/extensions/ERC1155Pausable.sol +++ b/contracts/token/ERC1155/extensions/ERC1155Pausable.sol @@ -17,7 +17,7 @@ import {Pausable} from "../../../security/Pausable.sol"; * addition to inheriting this contract, you must define both functions, invoking the * {Pausable-_pause} and {Pausable-_unpause} internal functions, with appropriate * access control, e.g. using {AccessControl} or {Ownable}. Not doing so will - * make the contract unpausable. + * make the contract pause mechanism of the contract unreachable, and thus unusable. * * _Available since v3.1._ */ @@ -33,9 +33,8 @@ abstract contract ERC1155Pausable is ERC1155, Pausable { address from, address to, uint256[] memory ids, - uint256[] memory amounts, - bytes memory data + uint256[] memory values ) internal virtual override whenNotPaused { - super._update(from, to, ids, amounts, data); + super._update(from, to, ids, values); } } diff --git a/contracts/token/ERC1155/extensions/ERC1155Supply.sol b/contracts/token/ERC1155/extensions/ERC1155Supply.sol index eefa0ebf7..ef2736339 100644 --- a/contracts/token/ERC1155/extensions/ERC1155Supply.sol +++ b/contracts/token/ERC1155/extensions/ERC1155Supply.sol @@ -15,20 +15,22 @@ import {ERC1155} from "../ERC1155.sol"; * * NOTE: This contract implies a global limit of 2**256 - 1 to the number of tokens * that can be minted. + * + * CAUTION: This extension should not be added in an upgrade to an already deployed contract. */ abstract contract ERC1155Supply is ERC1155 { mapping(uint256 => uint256) private _totalSupply; uint256 private _totalSupplyAll; /** - * @dev Total amount of tokens in with a given id. + * @dev Total value of tokens in with a given id. */ function totalSupply(uint256 id) public view virtual returns (uint256) { return _totalSupply[id]; } /** - * @dev Total amount of tokens. + * @dev Total value of tokens. */ function totalSupply() public view virtual returns (uint256) { return _totalSupplyAll; @@ -48,35 +50,38 @@ abstract contract ERC1155Supply is ERC1155 { address from, address to, uint256[] memory ids, - uint256[] memory amounts, - bytes memory data + uint256[] memory values ) internal virtual override { + super._update(from, to, ids, values); + if (from == address(0)) { - uint256 totalMintAmount = 0; + uint256 totalMintValue = 0; for (uint256 i = 0; i < ids.length; ++i) { - uint256 amount = amounts[i]; - _totalSupply[ids[i]] += amount; - totalMintAmount += amount; + uint256 value = values[i]; + // Overflow check required: The rest of the code assumes that totalSupply never overflows + _totalSupply[ids[i]] += value; + totalMintValue += value; } - _totalSupplyAll += totalMintAmount; + // Overflow check required: The rest of the code assumes that totalSupplyAll never overflows + _totalSupplyAll += totalMintValue; } if (to == address(0)) { - uint256 totalBurnAmount = 0; + uint256 totalBurnValue = 0; for (uint256 i = 0; i < ids.length; ++i) { - uint256 id = ids[i]; - uint256 amount = amounts[i]; - _totalSupply[id] -= amount; + uint256 value = values[i]; + unchecked { - // Overflow not possible: sum(amounts[i]) <= sum(totalSupply(i)) <= totalSupplyAll - totalBurnAmount += amount; + // Overflow not possible: values[i] <= balanceOf(from, ids[i]) <= totalSupply(ids[i]) + _totalSupply[ids[i]] -= value; + // Overflow not possible: sum_i(values[i]) <= sum_i(totalSupply(ids[i])) <= totalSupplyAll + totalBurnValue += value; } } unchecked { - // Overflow not possible: totalBurnAmount = sum(amounts[i]) <= sum(totalSupply(i)) <= totalSupplyAll - _totalSupplyAll -= totalBurnAmount; + // Overflow not possible: totalBurnValue = sum_i(values[i]) <= sum_i(totalSupply(ids[i])) <= totalSupplyAll + _totalSupplyAll -= totalBurnValue; } } - super._update(from, to, ids, amounts, data); } } diff --git a/contracts/token/ERC20/ERC20.sol b/contracts/token/ERC20/ERC20.sol index d43880da6..abaf258c8 100644 --- a/contracts/token/ERC20/ERC20.sol +++ b/contracts/token/ERC20/ERC20.sol @@ -113,11 +113,11 @@ abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors { * Requirements: * * - `to` cannot be the zero address. - * - the caller must have a balance of at least `amount`. + * - the caller must have a balance of at least `value`. */ - function transfer(address to, uint256 amount) public virtual returns (bool) { + function transfer(address to, uint256 value) public virtual returns (bool) { address owner = _msgSender(); - _transfer(owner, to, amount); + _transfer(owner, to, value); return true; } @@ -131,16 +131,16 @@ abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors { /** * @dev See {IERC20-approve}. * - * NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on + * NOTE: If `value` is the maximum `uint256`, the allowance is not updated on * `transferFrom`. This is semantically equivalent to an infinite approval. * * Requirements: * * - `spender` cannot be the zero address. */ - function approve(address spender, uint256 amount) public virtual returns (bool) { + function approve(address spender, uint256 value) public virtual returns (bool) { address owner = _msgSender(); - _approve(owner, spender, amount); + _approve(owner, spender, value); return true; } @@ -156,14 +156,14 @@ abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors { * Requirements: * * - `from` and `to` cannot be the zero address. - * - `from` must have a balance of at least `amount`. + * - `from` must have a balance of at least `value`. * - the caller must have allowance for ``from``'s tokens of at least - * `amount`. + * `value`. */ - function transferFrom(address from, address to, uint256 amount) public virtual returns (bool) { + function transferFrom(address from, address to, uint256 value) public virtual returns (bool) { address spender = _msgSender(); - _spendAllowance(from, spender, amount); - _transfer(from, to, amount); + _spendAllowance(from, spender, value); + _transfer(from, to, value); return true; } @@ -198,6 +198,9 @@ abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors { * - `spender` cannot be the zero address. * - `spender` must have allowance for the caller of at least * `requestedDecrease`. + * + * NOTE: Although this function is designed to avoid double spending with {approval}, + * it can still be frontrunned, preventing any attempt of allowance reduction. */ function decreaseAllowance(address spender, uint256 requestedDecrease) public virtual returns (bool) { address owner = _msgSender(); @@ -213,7 +216,7 @@ abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors { } /** - * @dev Moves `amount` of tokens from `from` to `to`. + * @dev Moves a `value` amount of tokens from `from` to `to`. * * This internal function is equivalent to {transfer}, and can be used to * e.g. implement automatic token fees, slashing mechanisms, etc. @@ -222,83 +225,84 @@ abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors { * * NOTE: This function is not virtual, {_update} should be overridden instead. */ - function _transfer(address from, address to, uint256 amount) internal { + function _transfer(address from, address to, uint256 value) internal { if (from == address(0)) { revert ERC20InvalidSender(address(0)); } if (to == address(0)) { revert ERC20InvalidReceiver(address(0)); } - _update(from, to, amount); + _update(from, to, value); } /** - * @dev Transfers `amount` of tokens from `from` to `to`, or alternatively mints (or burns) if `from` (or `to`) is + * @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from` (or `to`) is * the zero address. All customizations to transfers, mints, and burns should be done by overriding this function. * * Emits a {Transfer} event. */ - function _update(address from, address to, uint256 amount) internal virtual { + function _update(address from, address to, uint256 value) internal virtual { if (from == address(0)) { - _totalSupply += amount; + // Overflow check required: The rest of the code assumes that totalSupply never overflows + _totalSupply += value; } else { uint256 fromBalance = _balances[from]; - if (fromBalance < amount) { - revert ERC20InsufficientBalance(from, fromBalance, amount); + if (fromBalance < value) { + revert ERC20InsufficientBalance(from, fromBalance, value); } unchecked { - // Overflow not possible: amount <= fromBalance <= totalSupply. - _balances[from] = fromBalance - amount; + // Overflow not possible: value <= fromBalance <= totalSupply. + _balances[from] = fromBalance - value; } } if (to == address(0)) { unchecked { - // Overflow not possible: amount <= totalSupply or amount <= fromBalance <= totalSupply. - _totalSupply -= amount; + // Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply. + _totalSupply -= value; } } else { unchecked { - // Overflow not possible: balance + amount is at most totalSupply, which we know fits into a uint256. - _balances[to] += amount; + // Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256. + _balances[to] += value; } } - emit Transfer(from, to, amount); + emit Transfer(from, to, value); } /** - * @dev Creates `amount` tokens and assigns them to `account`, by transferring it from address(0). + * @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0). * Relies on the `_update` mechanism * * Emits a {Transfer} event with `from` set to the zero address. * * NOTE: This function is not virtual, {_update} should be overridden instead. */ - function _mint(address account, uint256 amount) internal { + function _mint(address account, uint256 value) internal { if (account == address(0)) { revert ERC20InvalidReceiver(address(0)); } - _update(address(0), account, amount); + _update(address(0), account, value); } /** - * @dev Destroys `amount` tokens from `account`, by transferring it to address(0). + * @dev Destroys a `value` amount of tokens from `account`, by transferring it to address(0). * Relies on the `_update` mechanism. * * Emits a {Transfer} event with `to` set to the zero address. * * NOTE: This function is not virtual, {_update} should be overridden instead */ - function _burn(address account, uint256 amount) internal { + function _burn(address account, uint256 value) internal { if (account == address(0)) { revert ERC20InvalidSender(address(0)); } - _update(account, address(0), amount); + _update(account, address(0), value); } /** - * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens. + * @dev Sets `value` as the allowance of `spender` over the `owner` s tokens. * * This internal function is equivalent to `approve`, and can be used to * e.g. set automatic allowances for certain subsystems, etc. @@ -310,8 +314,8 @@ abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors { * - `owner` cannot be the zero address. * - `spender` cannot be the zero address. */ - function _approve(address owner, address spender, uint256 amount) internal virtual { - _approve(owner, spender, amount, true); + function _approve(address owner, address spender, uint256 value) internal virtual { + _approve(owner, spender, value, true); } /** @@ -324,42 +328,42 @@ abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors { * Anyone who wishes to continue emitting `Approval` events on the`transferFrom` operation can force the flag to true * using the following override: * ``` - * function _approve(address owner, address spender, uint256 amount, bool) internal virtual override { - * super._approve(owner, spender, amount, true); + * function _approve(address owner, address spender, uint256 value, bool) internal virtual override { + * super._approve(owner, spender, value, true); * } * ``` * * Requirements are the same as {_approve}. */ - function _approve(address owner, address spender, uint256 amount, bool emitEvent) internal virtual { + function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual { if (owner == address(0)) { revert ERC20InvalidApprover(address(0)); } if (spender == address(0)) { revert ERC20InvalidSpender(address(0)); } - _allowances[owner][spender] = amount; + _allowances[owner][spender] = value; if (emitEvent) { - emit Approval(owner, spender, amount); + emit Approval(owner, spender, value); } } /** - * @dev Updates `owner` s allowance for `spender` based on spent `amount`. + * @dev Updates `owner` s allowance for `spender` based on spent `value`. * - * Does not update the allowance amount in case of infinite allowance. + * Does not update the allowance value in case of infinite allowance. * Revert if not enough allowance is available. * * Might emit an {Approval} event. */ - function _spendAllowance(address owner, address spender, uint256 amount) internal virtual { + function _spendAllowance(address owner, address spender, uint256 value) internal virtual { uint256 currentAllowance = allowance(owner, spender); if (currentAllowance != type(uint256).max) { - if (currentAllowance < amount) { - revert ERC20InsufficientAllowance(spender, currentAllowance, amount); + if (currentAllowance < value) { + revert ERC20InsufficientAllowance(spender, currentAllowance, value); } unchecked { - _approve(owner, spender, currentAllowance - amount, false); + _approve(owner, spender, currentAllowance - value, false); } } } diff --git a/contracts/token/ERC20/IERC20.sol b/contracts/token/ERC20/IERC20.sol index a19535a30..eed63a606 100644 --- a/contracts/token/ERC20/IERC20.sol +++ b/contracts/token/ERC20/IERC20.sol @@ -22,23 +22,23 @@ interface IERC20 { event Approval(address indexed owner, address indexed spender, uint256 value); /** - * @dev Returns the amount of tokens in existence. + * @dev Returns the value of tokens in existence. */ function totalSupply() external view returns (uint256); /** - * @dev Returns the amount of tokens owned by `account`. + * @dev Returns the value of tokens owned by `account`. */ function balanceOf(address account) external view returns (uint256); /** - * @dev Moves `amount` tokens from the caller's account to `to`. + * @dev Moves a `value` amount of tokens from the caller's account to `to`. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. */ - function transfer(address to, uint256 amount) external returns (bool); + function transfer(address to, uint256 value) external returns (bool); /** * @dev Returns the remaining number of tokens that `spender` will be @@ -50,7 +50,8 @@ interface IERC20 { function allowance(address owner, address spender) external view returns (uint256); /** - * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. + * @dev Sets a `value` amount of tokens as the allowance of `spender` over the + * caller's tokens. * * Returns a boolean value indicating whether the operation succeeded. * @@ -63,16 +64,16 @@ interface IERC20 { * * Emits an {Approval} event. */ - function approve(address spender, uint256 amount) external returns (bool); + function approve(address spender, uint256 value) external returns (bool); /** - * @dev Moves `amount` tokens from `from` to `to` using the - * allowance mechanism. `amount` is then deducted from the caller's + * @dev Moves a `value` amount of tokens from `from` to `to` using the + * allowance mechanism. `value` is then deducted from the caller's * allowance. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. */ - function transferFrom(address from, address to, uint256 amount) external returns (bool); + function transferFrom(address from, address to, uint256 value) external returns (bool); } diff --git a/contracts/token/ERC20/extensions/ERC20Burnable.sol b/contracts/token/ERC20/extensions/ERC20Burnable.sol index 2a6d28963..e5b43a780 100644 --- a/contracts/token/ERC20/extensions/ERC20Burnable.sol +++ b/contracts/token/ERC20/extensions/ERC20Burnable.sol @@ -13,27 +13,27 @@ import {Context} from "../../../utils/Context.sol"; */ abstract contract ERC20Burnable is Context, ERC20 { /** - * @dev Destroys `amount` tokens from the caller. + * @dev Destroys a `value` amount of tokens from the caller. * * See {ERC20-_burn}. */ - function burn(uint256 amount) public virtual { - _burn(_msgSender(), amount); + function burn(uint256 value) public virtual { + _burn(_msgSender(), value); } /** - * @dev Destroys `amount` tokens from `account`, deducting from the caller's - * allowance. + * @dev Destroys a `value` amount of tokens from `account`, deducting from + * the caller's allowance. * * See {ERC20-_burn} and {ERC20-allowance}. * * Requirements: * * - the caller must have allowance for ``accounts``'s tokens of at least - * `amount`. + * `value`. */ - function burnFrom(address account, uint256 amount) public virtual { - _spendAllowance(account, _msgSender(), amount); - _burn(account, amount); + function burnFrom(address account, uint256 value) public virtual { + _spendAllowance(account, _msgSender(), value); + _burn(account, value); } } diff --git a/contracts/token/ERC20/extensions/ERC20Capped.sol b/contracts/token/ERC20/extensions/ERC20Capped.sol index 98ec71443..943d01644 100644 --- a/contracts/token/ERC20/extensions/ERC20Capped.sol +++ b/contracts/token/ERC20/extensions/ERC20Capped.sol @@ -42,8 +42,8 @@ abstract contract ERC20Capped is ERC20 { /** * @dev See {ERC20-_update}. */ - function _update(address from, address to, uint256 amount) internal virtual override { - super._update(from, to, amount); + function _update(address from, address to, uint256 value) internal virtual override { + super._update(from, to, value); if (from == address(0)) { uint256 maxSupply = cap(); diff --git a/contracts/token/ERC20/extensions/ERC20FlashMint.sol b/contracts/token/ERC20/extensions/ERC20FlashMint.sol index b63c75f71..0583af5df 100644 --- a/contracts/token/ERC20/extensions/ERC20FlashMint.sol +++ b/contracts/token/ERC20/extensions/ERC20FlashMint.sol @@ -14,6 +14,10 @@ import {ERC20} from "../ERC20.sol"; * Adds the {flashLoan} method, which provides flash loan support at the token * level. By default there is no fee, but this can be changed by overriding {flashFee}. * + * NOTE: When this extension is used along with the {ERC20Capped} or {ERC20Votes} extensions, + * {maxFlashLoan} will not correctly reflect the maximum that can be flash minted. We recommend + * overriding {maxFlashLoan} so that it correctly reflects the supply cap. + * * _Available since v4.1._ */ abstract contract ERC20FlashMint is ERC20, IERC3156FlashLender { @@ -25,7 +29,7 @@ abstract contract ERC20FlashMint is ERC20, IERC3156FlashLender { error ERC3156UnsupportedToken(address token); /** - * @dev The requested loan exceeds the max loan amount for `token`. + * @dev The requested loan exceeds the max loan value for `token`. */ error ERC3156ExceededMaxLoan(uint256 maxLoan); @@ -38,6 +42,10 @@ abstract contract ERC20FlashMint is ERC20, IERC3156FlashLender { * @dev Returns the maximum amount of tokens available for loan. * @param token The address of the token that is requested. * @return The amount of token that can be loaned. + * + * NOTE: This function does not consider any form of supply cap, so in case + * it's used in a token with a cap like {ERC20Capped}, make sure to override this + * function to integrate the cap instead of `type(uint256).max`. */ function maxFlashLoan(address token) public view virtual returns (uint256) { return token == address(this) ? type(uint256).max - totalSupply() : 0; @@ -48,14 +56,14 @@ abstract contract ERC20FlashMint is ERC20, IERC3156FlashLender { * the {_flashFee} function which returns the fee applied when doing flash * loans. * @param token The token to be flash loaned. - * @param amount The amount of tokens to be loaned. + * @param value The amount of tokens to be loaned. * @return The fees applied to the corresponding flash loan. */ - function flashFee(address token, uint256 amount) public view virtual returns (uint256) { + function flashFee(address token, uint256 value) public view virtual returns (uint256) { if (token != address(this)) { revert ERC3156UnsupportedToken(token); } - return _flashFee(token, amount); + return _flashFee(token, value); } /** @@ -63,13 +71,13 @@ abstract contract ERC20FlashMint is ERC20, IERC3156FlashLender { * implementation has 0 fees. This function can be overloaded to make * the flash loan mechanism deflationary. * @param token The token to be flash loaned. - * @param amount The amount of tokens to be loaned. + * @param value The amount of tokens to be loaned. * @return The fees applied to the corresponding flash loan. */ - function _flashFee(address token, uint256 amount) internal view virtual returns (uint256) { + function _flashFee(address token, uint256 value) internal view virtual returns (uint256) { // silence warning about unused variable without the addition of bytecode. token; - amount; + value; return 0; } @@ -87,13 +95,13 @@ abstract contract ERC20FlashMint is ERC20, IERC3156FlashLender { * @dev Performs a flash loan. New tokens are minted and sent to the * `receiver`, who is required to implement the {IERC3156FlashBorrower} * interface. By the end of the flash loan, the receiver is expected to own - * amount + fee tokens and have them approved back to the token contract itself so + * value + fee tokens and have them approved back to the token contract itself so * they can be burned. * @param receiver The receiver of the flash loan. Should implement the * {IERC3156FlashBorrower-onFlashLoan} interface. * @param token The token to be flash loaned. Only `address(this)` is * supported. - * @param amount The amount of tokens to be loaned. + * @param value The amount of tokens to be loaned. * @param data An arbitrary datafield that is passed to the receiver. * @return `true` if the flash loan was successful. */ @@ -103,24 +111,24 @@ abstract contract ERC20FlashMint is ERC20, IERC3156FlashLender { function flashLoan( IERC3156FlashBorrower receiver, address token, - uint256 amount, + uint256 value, bytes calldata data ) public virtual returns (bool) { uint256 maxLoan = maxFlashLoan(token); - if (amount > maxLoan) { + if (value > maxLoan) { revert ERC3156ExceededMaxLoan(maxLoan); } - uint256 fee = flashFee(token, amount); - _mint(address(receiver), amount); - if (receiver.onFlashLoan(msg.sender, token, amount, fee, data) != _RETURN_VALUE) { + uint256 fee = flashFee(token, value); + _mint(address(receiver), value); + if (receiver.onFlashLoan(_msgSender(), token, value, fee, data) != _RETURN_VALUE) { revert ERC3156InvalidReceiver(address(receiver)); } address flashFeeReceiver = _flashFeeReceiver(); - _spendAllowance(address(receiver), address(this), amount + fee); + _spendAllowance(address(receiver), address(this), value + fee); if (fee == 0 || flashFeeReceiver == address(0)) { - _burn(address(receiver), amount + fee); + _burn(address(receiver), value + fee); } else { - _burn(address(receiver), amount); + _burn(address(receiver), value); _transfer(address(receiver), flashFeeReceiver, fee); } return true; diff --git a/contracts/token/ERC20/extensions/ERC20Pausable.sol b/contracts/token/ERC20/extensions/ERC20Pausable.sol index 59f451639..6fff5058f 100644 --- a/contracts/token/ERC20/extensions/ERC20Pausable.sol +++ b/contracts/token/ERC20/extensions/ERC20Pausable.sol @@ -17,7 +17,7 @@ import {Pausable} from "../../../security/Pausable.sol"; * addition to inheriting this contract, you must define both functions, invoking the * {Pausable-_pause} and {Pausable-_unpause} internal functions, with appropriate * access control, e.g. using {AccessControl} or {Ownable}. Not doing so will - * make the contract unpausable. + * make the contract pause mechanism of the contract unreachable, and thus unusable. */ abstract contract ERC20Pausable is ERC20, Pausable { /** @@ -27,7 +27,7 @@ abstract contract ERC20Pausable is ERC20, Pausable { * * - the contract must not be paused. */ - function _update(address from, address to, uint256 amount) internal virtual override whenNotPaused { - super._update(from, to, amount); + function _update(address from, address to, uint256 value) internal virtual override whenNotPaused { + super._update(from, to, value); } } diff --git a/contracts/token/ERC20/extensions/ERC20Votes.sol b/contracts/token/ERC20/extensions/ERC20Votes.sol index db5f71241..c27115eaa 100644 --- a/contracts/token/ERC20/extensions/ERC20Votes.sol +++ b/contracts/token/ERC20/extensions/ERC20Votes.sol @@ -41,8 +41,8 @@ abstract contract ERC20Votes is ERC20, Votes { * * Emits a {IVotes-DelegateVotesChanged} event. */ - function _update(address from, address to, uint256 amount) internal virtual override { - super._update(from, to, amount); + function _update(address from, address to, uint256 value) internal virtual override { + super._update(from, to, value); if (from == address(0)) { uint256 supply = totalSupply(); uint256 cap = _maxSupply(); @@ -50,11 +50,14 @@ abstract contract ERC20Votes is ERC20, Votes { revert ERC20ExceededSafeSupply(supply, cap); } } - _transferVotingUnits(from, to, amount); + _transferVotingUnits(from, to, value); } /** - * @dev Returns the balance of `account`. + * @dev Returns the voting units of an `account`. + * + * WARNING: Overriding this function may compromise the internal vote accounting. + * `ERC20Votes` assumes tokens map to voting units 1:1 and this is not easy to change. */ function _getVotingUnits(address account) internal view virtual override returns (uint256) { return balanceOf(account); diff --git a/contracts/token/ERC20/extensions/ERC20Wrapper.sol b/contracts/token/ERC20/extensions/ERC20Wrapper.sol index 2cbff6223..c9e33d00a 100644 --- a/contracts/token/ERC20/extensions/ERC20Wrapper.sol +++ b/contracts/token/ERC20/extensions/ERC20Wrapper.sol @@ -51,22 +51,28 @@ abstract contract ERC20Wrapper is ERC20 { /** * @dev Allow a user to deposit underlying tokens and mint the corresponding number of wrapped tokens. */ - function depositFor(address account, uint256 amount) public virtual returns (bool) { + function depositFor(address account, uint256 value) public virtual returns (bool) { address sender = _msgSender(); if (sender == address(this)) { revert ERC20InvalidSender(address(this)); } - SafeERC20.safeTransferFrom(_underlying, sender, address(this), amount); - _mint(account, amount); + if (account == address(this)) { + revert ERC20InvalidReceiver(account); + } + SafeERC20.safeTransferFrom(_underlying, sender, address(this), value); + _mint(account, value); return true; } /** * @dev Allow a user to burn a number of wrapped tokens and withdraw the corresponding number of underlying tokens. */ - function withdrawTo(address account, uint256 amount) public virtual returns (bool) { - _burn(_msgSender(), amount); - SafeERC20.safeTransfer(_underlying, account, amount); + function withdrawTo(address account, uint256 value) public virtual returns (bool) { + if (account == address(this)) { + revert ERC20InvalidReceiver(account); + } + _burn(_msgSender(), value); + SafeERC20.safeTransfer(_underlying, account, value); return true; } diff --git a/contracts/token/ERC721/ERC721.sol b/contracts/token/ERC721/ERC721.sol index ba2b1573f..25ac69fa9 100644 --- a/contracts/token/ERC721/ERC721.sol +++ b/contracts/token/ERC721/ERC721.sol @@ -486,7 +486,7 @@ abstract contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Er * that `ownerOf(tokenId)` is `a`. */ // solhint-disable-next-line func-name-mixedcase - function __unsafe_increaseBalance(address account, uint256 amount) internal { - _balances[account] += amount; + function __unsafe_increaseBalance(address account, uint256 value) internal { + _balances[account] += value; } } diff --git a/contracts/token/ERC721/extensions/ERC721Pausable.sol b/contracts/token/ERC721/extensions/ERC721Pausable.sol index a4effc549..5777ac36e 100644 --- a/contracts/token/ERC721/extensions/ERC721Pausable.sol +++ b/contracts/token/ERC721/extensions/ERC721Pausable.sol @@ -17,7 +17,7 @@ import {Pausable} from "../../../security/Pausable.sol"; * addition to inheriting this contract, you must define both functions, invoking the * {Pausable-_pause} and {Pausable-_unpause} internal functions, with appropriate * access control, e.g. using {AccessControl} or {Ownable}. Not doing so will - * make the contract unpausable. + * make the contract pause mechanism of the contract unreachable, and thus unusable. */ abstract contract ERC721Pausable is ERC721, Pausable { /** diff --git a/contracts/utils/Nonces.sol b/contracts/utils/Nonces.sol index f8ea1dfd3..d5458b10a 100644 --- a/contracts/utils/Nonces.sol +++ b/contracts/utils/Nonces.sol @@ -13,7 +13,7 @@ abstract contract Nonces { mapping(address => uint256) private _nonces; /** - * @dev Returns an address nonce. + * @dev Returns an the next unused nonce for an address. */ function nonces(address owner) public view virtual returns (uint256) { return _nonces[owner]; diff --git a/contracts/utils/structs/Checkpoints.sol b/contracts/utils/structs/Checkpoints.sol index 47ad91bbf..56b7035b9 100644 --- a/contracts/utils/structs/Checkpoints.sol +++ b/contracts/utils/structs/Checkpoints.sol @@ -5,13 +5,12 @@ pragma solidity ^0.8.19; import {Math} from "../math/Math.sol"; -import {SafeCast} from "../math/SafeCast.sol"; /** - * @dev This library defines the `History` struct, for checkpointing values as they change at different points in + * @dev This library defines the `Trace*` struct, for checkpointing values as they change at different points in * time, and later looking up past values by block number. See {Votes} as an example. * - * To create a history of checkpoints define a variable type `Checkpoints.History` in your contract, and store a new + * To create a history of checkpoints define a variable type `Checkpoints.Trace*` in your contract, and store a new * checkpoint for the current transaction block using the {push} function. * * _Available since v4.5._ @@ -35,6 +34,8 @@ library Checkpoints { * @dev Pushes a (`key`, `value`) pair into a Trace224 so that it is stored as the checkpoint. * * Returns previous value and new value. + * + * IMPORTANT: Never accept `key` as a user input, since an arbitrary `type(uint32).max` key set will disable the library. */ function push(Trace224 storage self, uint32 key, uint224 value) internal returns (uint224, uint224) { return _insert(self._checkpoints, key, value); @@ -220,6 +221,8 @@ library Checkpoints { * @dev Pushes a (`key`, `value`) pair into a Trace160 so that it is stored as the checkpoint. * * Returns previous value and new value. + * + * IMPORTANT: Never accept `key` as a user input, since an arbitrary `type(uint96).max` key set will disable the library. */ function push(Trace160 storage self, uint96 key, uint160 value) internal returns (uint160, uint160) { return _insert(self._checkpoints, key, value); diff --git a/scripts/generate/templates/Checkpoints.js b/scripts/generate/templates/Checkpoints.js index d5ab745a0..d635c8462 100644 --- a/scripts/generate/templates/Checkpoints.js +++ b/scripts/generate/templates/Checkpoints.js @@ -6,13 +6,12 @@ const header = `\ pragma solidity ^0.8.19; import {Math} from "../math/Math.sol"; -import {SafeCast} from "../math/SafeCast.sol"; /** - * @dev This library defines the \`History\` struct, for checkpointing values as they change at different points in + * @dev This library defines the \`Trace*\` struct, for checkpointing values as they change at different points in * time, and later looking up past values by block number. See {Votes} as an example. * - * To create a history of checkpoints define a variable type \`Checkpoints.History\` in your contract, and store a new + * To create a history of checkpoints define a variable type \`Checkpoints.Trace*\` in your contract, and store a new * checkpoint for the current transaction block using the {push} function. * * _Available since v4.5._ @@ -40,6 +39,8 @@ struct ${opts.checkpointTypeName} { * @dev Pushes a (\`key\`, \`value\`) pair into a ${opts.historyTypeName} so that it is stored as the checkpoint. * * Returns previous value and new value. + * + * IMPORTANT: Never accept \`key\` as a user input, since an arbitrary \`type(${opts.keyTypeName}).max\` key set will disable the library. */ function push( ${opts.historyTypeName} storage self, diff --git a/test/governance/utils/Votes.behavior.js b/test/governance/utils/Votes.behavior.js index 20ebdba4f..5836cc351 100644 --- a/test/governance/utils/Votes.behavior.js +++ b/test/governance/utils/Votes.behavior.js @@ -70,8 +70,8 @@ function shouldBehaveLikeVotes(accounts, tokens, { mode = 'blocknumber', fungibl }); expectEvent(receipt, 'DelegateVotesChanged', { delegate: accounts[1], - previousBalance: '0', - newBalance: weight, + previousVotes: '0', + newVotes: weight, }); expect(await this.votes.delegates(accounts[1])).to.be.equal(accounts[1]); @@ -100,13 +100,13 @@ function shouldBehaveLikeVotes(accounts, tokens, { mode = 'blocknumber', fungibl }); expectEvent(receipt, 'DelegateVotesChanged', { delegate: accounts[1], - previousBalance: weight, - newBalance: '0', + previousVotes: weight, + newVotes: '0', }); expectEvent(receipt, 'DelegateVotesChanged', { delegate: accounts[2], - previousBalance: '0', - newBalance: weight, + previousVotes: '0', + newVotes: weight, }); expect(await this.votes.delegates(accounts[1])).to.be.equal(accounts[2]); @@ -152,8 +152,8 @@ function shouldBehaveLikeVotes(accounts, tokens, { mode = 'blocknumber', fungibl }); expectEvent(receipt, 'DelegateVotesChanged', { delegate: delegatee, - previousBalance: '0', - newBalance: weight, + previousVotes: '0', + newVotes: weight, }); expect(await this.votes.delegates(delegator.address)).to.be.equal(delegatee); diff --git a/test/token/ERC1155/ERC1155.behavior.js b/test/token/ERC1155/ERC1155.behavior.js index 4bf4a7319..8df30a814 100644 --- a/test/token/ERC1155/ERC1155.behavior.js +++ b/test/token/ERC1155/ERC1155.behavior.js @@ -14,8 +14,8 @@ function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, m const secondTokenId = new BN(2); const unknownTokenId = new BN(3); - const firstAmount = new BN(1000); - const secondAmount = new BN(2000); + const firstTokenValue = new BN(1000); + const secondTokenValue = new BN(2000); const RECEIVER_SINGLE_MAGIC_VALUE = '0xf23a6e61'; const RECEIVER_BATCH_MAGIC_VALUE = '0xbc197c81'; @@ -38,18 +38,18 @@ function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, m context('when accounts own some tokens', function () { beforeEach(async function () { - await this.token.$_mint(firstTokenHolder, firstTokenId, firstAmount, '0x', { + await this.token.$_mint(firstTokenHolder, firstTokenId, firstTokenValue, '0x', { from: minter, }); - await this.token.$_mint(secondTokenHolder, secondTokenId, secondAmount, '0x', { + await this.token.$_mint(secondTokenHolder, secondTokenId, secondTokenValue, '0x', { from: minter, }); }); it('returns the amount of tokens owned by the given addresses', async function () { - expect(await this.token.balanceOf(firstTokenHolder, firstTokenId)).to.be.bignumber.equal(firstAmount); + expect(await this.token.balanceOf(firstTokenHolder, firstTokenId)).to.be.bignumber.equal(firstTokenValue); - expect(await this.token.balanceOf(secondTokenHolder, secondTokenId)).to.be.bignumber.equal(secondAmount); + expect(await this.token.balanceOf(secondTokenHolder, secondTokenId)).to.be.bignumber.equal(secondTokenValue); expect(await this.token.balanceOf(firstTokenHolder, unknownTokenId)).to.be.bignumber.equal('0'); }); @@ -99,10 +99,10 @@ function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, m context('when accounts own some tokens', function () { beforeEach(async function () { - await this.token.$_mint(firstTokenHolder, firstTokenId, firstAmount, '0x', { + await this.token.$_mint(firstTokenHolder, firstTokenId, firstTokenValue, '0x', { from: minter, }); - await this.token.$_mint(secondTokenHolder, secondTokenId, secondAmount, '0x', { + await this.token.$_mint(secondTokenHolder, secondTokenId, secondTokenValue, '0x', { from: minter, }); }); @@ -113,8 +113,8 @@ function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, m [secondTokenId, firstTokenId, unknownTokenId], ); expect(result).to.be.an('array'); - expect(result[0]).to.be.a.bignumber.equal(secondAmount); - expect(result[1]).to.be.a.bignumber.equal(firstAmount); + expect(result[0]).to.be.a.bignumber.equal(secondTokenValue); + expect(result[1]).to.be.a.bignumber.equal(firstTokenValue); expect(result[2]).to.be.a.bignumber.equal('0'); }); @@ -125,9 +125,9 @@ function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, m ); expect(result).to.be.an('array'); expect(result[0]).to.be.a.bignumber.equal(result[2]); - expect(result[0]).to.be.a.bignumber.equal(firstAmount); - expect(result[1]).to.be.a.bignumber.equal(secondAmount); - expect(result[2]).to.be.a.bignumber.equal(firstAmount); + expect(result[0]).to.be.a.bignumber.equal(firstTokenValue); + expect(result[1]).to.be.a.bignumber.equal(secondTokenValue); + expect(result[2]).to.be.a.bignumber.equal(firstTokenValue); }); }); }); @@ -151,38 +151,38 @@ function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, m expect(await this.token.isApprovedForAll(multiTokenHolder, proxy)).to.be.equal(false); }); - it('reverts if attempting to approve self as an operator', async function () { + it('reverts if attempting to approve zero address as an operator', async function () { await expectRevertCustomError( - this.token.setApprovalForAll(multiTokenHolder, true, { from: multiTokenHolder }), + this.token.setApprovalForAll(constants.ZERO_ADDRESS, true, { from: multiTokenHolder }), 'ERC1155InvalidOperator', - [multiTokenHolder], + [constants.ZERO_ADDRESS], ); }); }); describe('safeTransferFrom', function () { beforeEach(async function () { - await this.token.$_mint(multiTokenHolder, firstTokenId, firstAmount, '0x', { + await this.token.$_mint(multiTokenHolder, firstTokenId, firstTokenValue, '0x', { from: minter, }); - await this.token.$_mint(multiTokenHolder, secondTokenId, secondAmount, '0x', { + await this.token.$_mint(multiTokenHolder, secondTokenId, secondTokenValue, '0x', { from: minter, }); }); it('reverts when transferring more than balance', async function () { await expectRevertCustomError( - this.token.safeTransferFrom(multiTokenHolder, recipient, firstTokenId, firstAmount.addn(1), '0x', { + this.token.safeTransferFrom(multiTokenHolder, recipient, firstTokenId, firstTokenValue.addn(1), '0x', { from: multiTokenHolder, }), 'ERC1155InsufficientBalance', - [multiTokenHolder, firstAmount, firstAmount.addn(1), firstTokenId], + [multiTokenHolder, firstTokenValue, firstTokenValue.addn(1), firstTokenId], ); }); it('reverts when transferring to zero address', async function () { await expectRevertCustomError( - this.token.safeTransferFrom(multiTokenHolder, ZERO_ADDRESS, firstTokenId, firstAmount, '0x', { + this.token.safeTransferFrom(multiTokenHolder, ZERO_ADDRESS, firstTokenId, firstTokenValue, '0x', { from: multiTokenHolder, }), 'ERC1155InvalidReceiver', @@ -219,7 +219,7 @@ function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, m multiTokenHolder, recipient, firstTokenId, - firstAmount, + firstTokenValue, '0x', { from: multiTokenHolder, @@ -231,12 +231,12 @@ function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, m operator: multiTokenHolder, from: multiTokenHolder, id: firstTokenId, - value: firstAmount, + value: firstTokenValue, }); it('preserves existing balances which are not transferred by multiTokenHolder', async function () { const balance1 = await this.token.balanceOf(multiTokenHolder, secondTokenId); - expect(balance1).to.be.a.bignumber.equal(secondAmount); + expect(balance1).to.be.a.bignumber.equal(secondTokenValue); const balance2 = await this.token.balanceOf(recipient, secondTokenId); expect(balance2).to.be.a.bignumber.equal('0'); @@ -251,7 +251,7 @@ function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, m it('reverts', async function () { await expectRevertCustomError( - this.token.safeTransferFrom(multiTokenHolder, recipient, firstTokenId, firstAmount, '0x', { + this.token.safeTransferFrom(multiTokenHolder, recipient, firstTokenId, firstTokenValue, '0x', { from: proxy, }), 'ERC1155MissingApprovalForAll', @@ -268,7 +268,7 @@ function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, m multiTokenHolder, recipient, firstTokenId, - firstAmount, + firstTokenValue, '0x', { from: proxy, @@ -280,7 +280,7 @@ function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, m operator: proxy, from: multiTokenHolder, id: firstTokenId, - value: firstAmount, + value: firstTokenValue, }); it("preserves operator's balances not involved in the transfer", async function () { @@ -309,7 +309,7 @@ function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, m multiTokenHolder, this.receiver.address, firstTokenId, - firstAmount, + firstTokenValue, '0x', { from: multiTokenHolder }, ); @@ -320,7 +320,7 @@ function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, m operator: multiTokenHolder, from: multiTokenHolder, id: firstTokenId, - value: firstAmount, + value: firstTokenValue, }); it('calls onERC1155Received', async function () { @@ -328,7 +328,7 @@ function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, m operator: multiTokenHolder, from: multiTokenHolder, id: firstTokenId, - value: firstAmount, + value: firstTokenValue, data: null, }); }); @@ -342,7 +342,7 @@ function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, m multiTokenHolder, this.receiver.address, firstTokenId, - firstAmount, + firstTokenValue, data, { from: multiTokenHolder }, ); @@ -353,7 +353,7 @@ function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, m operator: multiTokenHolder, from: multiTokenHolder, id: firstTokenId, - value: firstAmount, + value: firstTokenValue, }); it('calls onERC1155Received', async function () { @@ -361,7 +361,7 @@ function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, m operator: multiTokenHolder, from: multiTokenHolder, id: firstTokenId, - value: firstAmount, + value: firstTokenValue, data, }); }); @@ -375,7 +375,7 @@ function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, m it('reverts', async function () { await expectRevertCustomError( - this.token.safeTransferFrom(multiTokenHolder, this.receiver.address, firstTokenId, firstAmount, '0x', { + this.token.safeTransferFrom(multiTokenHolder, this.receiver.address, firstTokenId, firstTokenValue, '0x', { from: multiTokenHolder, }), 'ERC1155InvalidReceiver', @@ -396,9 +396,16 @@ function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, m it('reverts', async function () { await expectRevert( - this.token.safeTransferFrom(multiTokenHolder, this.receiver.address, firstTokenId, firstAmount, '0x', { - from: multiTokenHolder, - }), + this.token.safeTransferFrom( + multiTokenHolder, + this.receiver.address, + firstTokenId, + firstTokenValue, + '0x', + { + from: multiTokenHolder, + }, + ), 'ERC1155ReceiverMock: reverting on receive', ); }); @@ -415,9 +422,16 @@ function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, m it('reverts', async function () { await expectRevertCustomError( - this.token.safeTransferFrom(multiTokenHolder, this.receiver.address, firstTokenId, firstAmount, '0x', { - from: multiTokenHolder, - }), + this.token.safeTransferFrom( + multiTokenHolder, + this.receiver.address, + firstTokenId, + firstTokenValue, + '0x', + { + from: multiTokenHolder, + }, + ), 'ERC1155InvalidReceiver', [this.receiver.address], ); @@ -435,9 +449,16 @@ function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, m it('reverts', async function () { await expectRevertCustomError( - this.token.safeTransferFrom(multiTokenHolder, this.receiver.address, firstTokenId, firstAmount, '0x', { - from: multiTokenHolder, - }), + this.token.safeTransferFrom( + multiTokenHolder, + this.receiver.address, + firstTokenId, + firstTokenValue, + '0x', + { + from: multiTokenHolder, + }, + ), 'CustomError', [RECEIVER_SINGLE_MAGIC_VALUE], ); @@ -455,9 +476,16 @@ function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, m it('reverts', async function () { await expectRevert.unspecified( - this.token.safeTransferFrom(multiTokenHolder, this.receiver.address, firstTokenId, firstAmount, '0x', { - from: multiTokenHolder, - }), + this.token.safeTransferFrom( + multiTokenHolder, + this.receiver.address, + firstTokenId, + firstTokenValue, + '0x', + { + from: multiTokenHolder, + }, + ), ); }); }); @@ -467,9 +495,16 @@ function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, m it('reverts', async function () { const invalidReceiver = this.token; await expectRevert.unspecified( - this.token.safeTransferFrom(multiTokenHolder, invalidReceiver.address, firstTokenId, firstAmount, '0x', { - from: multiTokenHolder, - }), + this.token.safeTransferFrom( + multiTokenHolder, + invalidReceiver.address, + firstTokenId, + firstTokenValue, + '0x', + { + from: multiTokenHolder, + }, + ), ); }); }); @@ -477,49 +512,49 @@ function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, m describe('safeBatchTransferFrom', function () { beforeEach(async function () { - await this.token.$_mint(multiTokenHolder, firstTokenId, firstAmount, '0x', { + await this.token.$_mint(multiTokenHolder, firstTokenId, firstTokenValue, '0x', { from: minter, }); - await this.token.$_mint(multiTokenHolder, secondTokenId, secondAmount, '0x', { + await this.token.$_mint(multiTokenHolder, secondTokenId, secondTokenValue, '0x', { from: minter, }); }); - it('reverts when transferring amount more than any of balances', async function () { + it('reverts when transferring value more than any of balances', async function () { await expectRevertCustomError( this.token.safeBatchTransferFrom( multiTokenHolder, recipient, [firstTokenId, secondTokenId], - [firstAmount, secondAmount.addn(1)], + [firstTokenValue, secondTokenValue.addn(1)], '0x', { from: multiTokenHolder }, ), 'ERC1155InsufficientBalance', - [multiTokenHolder, secondAmount, secondAmount.addn(1), secondTokenId], + [multiTokenHolder, secondTokenValue, secondTokenValue.addn(1), secondTokenId], ); }); - it("reverts when ids array length doesn't match amounts array length", async function () { + it("reverts when ids array length doesn't match values array length", async function () { const ids1 = [firstTokenId]; - const amounts1 = [firstAmount, secondAmount]; + const tokenValues1 = [firstTokenValue, secondTokenValue]; await expectRevertCustomError( - this.token.safeBatchTransferFrom(multiTokenHolder, recipient, ids1, amounts1, '0x', { + this.token.safeBatchTransferFrom(multiTokenHolder, recipient, ids1, tokenValues1, '0x', { from: multiTokenHolder, }), 'ERC1155InvalidArrayLength', - [ids1.length, amounts1.length], + [ids1.length, tokenValues1.length], ); const ids2 = [firstTokenId, secondTokenId]; - const amounts2 = [firstAmount]; + const tokenValues2 = [firstTokenValue]; await expectRevertCustomError( - this.token.safeBatchTransferFrom(multiTokenHolder, recipient, ids2, amounts2, '0x', { + this.token.safeBatchTransferFrom(multiTokenHolder, recipient, ids2, tokenValues2, '0x', { from: multiTokenHolder, }), 'ERC1155InvalidArrayLength', - [ids2.length, amounts2.length], + [ids2.length, tokenValues2.length], ); }); @@ -529,7 +564,7 @@ function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, m multiTokenHolder, ZERO_ADDRESS, [firstTokenId, secondTokenId], - [firstAmount, secondAmount], + [firstTokenValue, secondTokenValue], '0x', { from: multiTokenHolder }, ), @@ -540,7 +575,7 @@ function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, m it('reverts when transferring from zero address', async function () { await expectRevertCustomError( - this.token.$_safeBatchTransferFrom(ZERO_ADDRESS, multiTokenHolder, [firstTokenId], [firstAmount], '0x'), + this.token.$_safeBatchTransferFrom(ZERO_ADDRESS, multiTokenHolder, [firstTokenId], [firstTokenValue], '0x'), 'ERC1155InvalidSender', [ZERO_ADDRESS], ); @@ -579,7 +614,7 @@ function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, m multiTokenHolder, recipient, [firstTokenId, secondTokenId], - [firstAmount, secondAmount], + [firstTokenValue, secondTokenValue], '0x', { from: multiTokenHolder }, ); @@ -589,7 +624,7 @@ function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, m operator: multiTokenHolder, from: multiTokenHolder, ids: [firstTokenId, secondTokenId], - values: [firstAmount, secondAmount], + values: [firstTokenValue, secondTokenValue], }); }); @@ -605,7 +640,7 @@ function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, m multiTokenHolder, recipient, [firstTokenId, secondTokenId], - [firstAmount, secondAmount], + [firstTokenValue, secondTokenValue], '0x', { from: proxy }, ), @@ -623,7 +658,7 @@ function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, m multiTokenHolder, recipient, [firstTokenId, secondTokenId], - [firstAmount, secondAmount], + [firstTokenValue, secondTokenValue], '0x', { from: proxy }, ); @@ -633,7 +668,7 @@ function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, m operator: proxy, from: multiTokenHolder, ids: [firstTokenId, secondTokenId], - values: [firstAmount, secondAmount], + values: [firstTokenValue, secondTokenValue], }); it("preserves operator's balances not involved in the transfer", async function () { @@ -661,7 +696,7 @@ function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, m multiTokenHolder, this.receiver.address, [firstTokenId, secondTokenId], - [firstAmount, secondAmount], + [firstTokenValue, secondTokenValue], '0x', { from: multiTokenHolder }, ); @@ -672,7 +707,7 @@ function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, m operator: multiTokenHolder, from: multiTokenHolder, ids: [firstTokenId, secondTokenId], - values: [firstAmount, secondAmount], + values: [firstTokenValue, secondTokenValue], }); it('calls onERC1155BatchReceived', async function () { @@ -680,7 +715,7 @@ function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, m operator: multiTokenHolder, from: multiTokenHolder, // ids: [firstTokenId, secondTokenId], - // values: [firstAmount, secondAmount], + // values: [firstTokenValue, secondTokenValue], data: null, }); }); @@ -694,7 +729,7 @@ function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, m multiTokenHolder, this.receiver.address, [firstTokenId, secondTokenId], - [firstAmount, secondAmount], + [firstTokenValue, secondTokenValue], data, { from: multiTokenHolder }, ); @@ -705,7 +740,7 @@ function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, m operator: multiTokenHolder, from: multiTokenHolder, ids: [firstTokenId, secondTokenId], - values: [firstAmount, secondAmount], + values: [firstTokenValue, secondTokenValue], }); it('calls onERC1155Received', async function () { @@ -713,7 +748,7 @@ function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, m operator: multiTokenHolder, from: multiTokenHolder, // ids: [firstTokenId, secondTokenId], - // values: [firstAmount, secondAmount], + // values: [firstTokenValue, secondTokenValue], data, }); }); @@ -735,7 +770,7 @@ function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, m multiTokenHolder, this.receiver.address, [firstTokenId, secondTokenId], - [firstAmount, secondAmount], + [firstTokenValue, secondTokenValue], '0x', { from: multiTokenHolder }, ), @@ -761,7 +796,7 @@ function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, m multiTokenHolder, this.receiver.address, [firstTokenId, secondTokenId], - [firstAmount, secondAmount], + [firstTokenValue, secondTokenValue], '0x', { from: multiTokenHolder }, ), @@ -785,7 +820,7 @@ function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, m multiTokenHolder, this.receiver.address, [firstTokenId, secondTokenId], - [firstAmount, secondAmount], + [firstTokenValue, secondTokenValue], '0x', { from: multiTokenHolder }, ), @@ -810,7 +845,7 @@ function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, m multiTokenHolder, this.receiver.address, [firstTokenId, secondTokenId], - [firstAmount, secondAmount], + [firstTokenValue, secondTokenValue], '0x', { from: multiTokenHolder }, ), @@ -835,7 +870,7 @@ function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, m multiTokenHolder, this.receiver.address, [firstTokenId, secondTokenId], - [firstAmount, secondAmount], + [firstTokenValue, secondTokenValue], '0x', { from: multiTokenHolder }, ), @@ -852,7 +887,7 @@ function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, m multiTokenHolder, invalidReceiver.address, [firstTokenId, secondTokenId], - [firstAmount, secondAmount], + [firstTokenValue, secondTokenValue], '0x', { from: multiTokenHolder }, ), diff --git a/test/token/ERC1155/ERC1155.test.js b/test/token/ERC1155/ERC1155.test.js index 23555dd54..58d747a4b 100644 --- a/test/token/ERC1155/ERC1155.test.js +++ b/test/token/ERC1155/ERC1155.test.js @@ -21,19 +21,19 @@ contract('ERC1155', function (accounts) { describe('internal functions', function () { const tokenId = new BN(1990); - const mintAmount = new BN(9001); - const burnAmount = new BN(3000); + const mintValue = new BN(9001); + const burnValue = new BN(3000); const tokenBatchIds = [new BN(2000), new BN(2010), new BN(2020)]; - const mintAmounts = [new BN(5000), new BN(10000), new BN(42195)]; - const burnAmounts = [new BN(5000), new BN(9001), new BN(195)]; + const mintValues = [new BN(5000), new BN(10000), new BN(42195)]; + const burnValues = [new BN(5000), new BN(9001), new BN(195)]; const data = '0x12345678'; describe('_mint', function () { it('reverts with a zero destination address', async function () { await expectRevertCustomError( - this.token.$_mint(ZERO_ADDRESS, tokenId, mintAmount, data), + this.token.$_mint(ZERO_ADDRESS, tokenId, mintValue, data), 'ERC1155InvalidReceiver', [ZERO_ADDRESS], ); @@ -41,7 +41,7 @@ contract('ERC1155', function (accounts) { context('with minted tokens', function () { beforeEach(async function () { - this.receipt = await this.token.$_mint(tokenHolder, tokenId, mintAmount, data, { from: operator }); + this.receipt = await this.token.$_mint(tokenHolder, tokenId, mintValue, data, { from: operator }); }); it('emits a TransferSingle event', function () { @@ -50,12 +50,12 @@ contract('ERC1155', function (accounts) { from: ZERO_ADDRESS, to: tokenHolder, id: tokenId, - value: mintAmount, + value: mintValue, }); }); - it('credits the minted amount of tokens', async function () { - expect(await this.token.balanceOf(tokenHolder, tokenId)).to.be.bignumber.equal(mintAmount); + it('credits the minted token value', async function () { + expect(await this.token.balanceOf(tokenHolder, tokenId)).to.be.bignumber.equal(mintValue); }); }); }); @@ -63,7 +63,7 @@ contract('ERC1155', function (accounts) { describe('_mintBatch', function () { it('reverts with a zero destination address', async function () { await expectRevertCustomError( - this.token.$_mintBatch(ZERO_ADDRESS, tokenBatchIds, mintAmounts, data), + this.token.$_mintBatch(ZERO_ADDRESS, tokenBatchIds, mintValues, data), 'ERC1155InvalidReceiver', [ZERO_ADDRESS], ); @@ -71,21 +71,21 @@ contract('ERC1155', function (accounts) { it('reverts if length of inputs do not match', async function () { await expectRevertCustomError( - this.token.$_mintBatch(tokenBatchHolder, tokenBatchIds, mintAmounts.slice(1), data), + this.token.$_mintBatch(tokenBatchHolder, tokenBatchIds, mintValues.slice(1), data), 'ERC1155InvalidArrayLength', - [tokenBatchIds.length, mintAmounts.length - 1], + [tokenBatchIds.length, mintValues.length - 1], ); await expectRevertCustomError( - this.token.$_mintBatch(tokenBatchHolder, tokenBatchIds.slice(1), mintAmounts, data), + this.token.$_mintBatch(tokenBatchHolder, tokenBatchIds.slice(1), mintValues, data), 'ERC1155InvalidArrayLength', - [tokenBatchIds.length - 1, mintAmounts.length], + [tokenBatchIds.length - 1, mintValues.length], ); }); context('with minted batch of tokens', function () { beforeEach(async function () { - this.receipt = await this.token.$_mintBatch(tokenBatchHolder, tokenBatchIds, mintAmounts, data, { + this.receipt = await this.token.$_mintBatch(tokenBatchHolder, tokenBatchIds, mintValues, data, { from: operator, }); }); @@ -105,7 +105,7 @@ contract('ERC1155', function (accounts) { ); for (let i = 0; i < holderBatchBalances.length; i++) { - expect(holderBatchBalances[i]).to.be.bignumber.equal(mintAmounts[i]); + expect(holderBatchBalances[i]).to.be.bignumber.equal(mintValues[i]); } }); }); @@ -113,33 +113,33 @@ contract('ERC1155', function (accounts) { describe('_burn', function () { it("reverts when burning the zero account's tokens", async function () { - await expectRevertCustomError(this.token.$_burn(ZERO_ADDRESS, tokenId, mintAmount), 'ERC1155InvalidSender', [ + await expectRevertCustomError(this.token.$_burn(ZERO_ADDRESS, tokenId, mintValue), 'ERC1155InvalidSender', [ ZERO_ADDRESS, ]); }); it('reverts when burning a non-existent token id', async function () { await expectRevertCustomError( - this.token.$_burn(tokenHolder, tokenId, mintAmount), + this.token.$_burn(tokenHolder, tokenId, mintValue), 'ERC1155InsufficientBalance', - [tokenHolder, 0, mintAmount, tokenId], + [tokenHolder, 0, mintValue, tokenId], ); }); it('reverts when burning more than available tokens', async function () { - await this.token.$_mint(tokenHolder, tokenId, mintAmount, data, { from: operator }); + await this.token.$_mint(tokenHolder, tokenId, mintValue, data, { from: operator }); await expectRevertCustomError( - this.token.$_burn(tokenHolder, tokenId, mintAmount.addn(1)), + this.token.$_burn(tokenHolder, tokenId, mintValue.addn(1)), 'ERC1155InsufficientBalance', - [tokenHolder, mintAmount, mintAmount.addn(1), tokenId], + [tokenHolder, mintValue, mintValue.addn(1), tokenId], ); }); context('with minted-then-burnt tokens', function () { beforeEach(async function () { - await this.token.$_mint(tokenHolder, tokenId, mintAmount, data); - this.receipt = await this.token.$_burn(tokenHolder, tokenId, burnAmount, { from: operator }); + await this.token.$_mint(tokenHolder, tokenId, mintValue, data); + this.receipt = await this.token.$_burn(tokenHolder, tokenId, burnValue, { from: operator }); }); it('emits a TransferSingle event', function () { @@ -148,12 +148,12 @@ contract('ERC1155', function (accounts) { from: tokenHolder, to: ZERO_ADDRESS, id: tokenId, - value: burnAmount, + value: burnValue, }); }); it('accounts for both minting and burning', async function () { - expect(await this.token.balanceOf(tokenHolder, tokenId)).to.be.bignumber.equal(mintAmount.sub(burnAmount)); + expect(await this.token.balanceOf(tokenHolder, tokenId)).to.be.bignumber.equal(mintValue.sub(burnValue)); }); }); }); @@ -161,7 +161,7 @@ contract('ERC1155', function (accounts) { describe('_burnBatch', function () { it("reverts when burning the zero account's tokens", async function () { await expectRevertCustomError( - this.token.$_burnBatch(ZERO_ADDRESS, tokenBatchIds, burnAmounts), + this.token.$_burnBatch(ZERO_ADDRESS, tokenBatchIds, burnValues), 'ERC1155InvalidSender', [ZERO_ADDRESS], ); @@ -169,30 +169,30 @@ contract('ERC1155', function (accounts) { it('reverts if length of inputs do not match', async function () { await expectRevertCustomError( - this.token.$_burnBatch(tokenBatchHolder, tokenBatchIds, burnAmounts.slice(1)), + this.token.$_burnBatch(tokenBatchHolder, tokenBatchIds, burnValues.slice(1)), 'ERC1155InvalidArrayLength', - [tokenBatchIds.length, burnAmounts.length - 1], + [tokenBatchIds.length, burnValues.length - 1], ); await expectRevertCustomError( - this.token.$_burnBatch(tokenBatchHolder, tokenBatchIds.slice(1), burnAmounts), + this.token.$_burnBatch(tokenBatchHolder, tokenBatchIds.slice(1), burnValues), 'ERC1155InvalidArrayLength', - [tokenBatchIds.length - 1, burnAmounts.length], + [tokenBatchIds.length - 1, burnValues.length], ); }); it('reverts when burning a non-existent token id', async function () { await expectRevertCustomError( - this.token.$_burnBatch(tokenBatchHolder, tokenBatchIds, burnAmounts), + this.token.$_burnBatch(tokenBatchHolder, tokenBatchIds, burnValues), 'ERC1155InsufficientBalance', - [tokenBatchHolder, 0, tokenBatchIds[0], burnAmounts[0]], + [tokenBatchHolder, 0, tokenBatchIds[0], burnValues[0]], ); }); context('with minted-then-burnt tokens', function () { beforeEach(async function () { - await this.token.$_mintBatch(tokenBatchHolder, tokenBatchIds, mintAmounts, data); - this.receipt = await this.token.$_burnBatch(tokenBatchHolder, tokenBatchIds, burnAmounts, { from: operator }); + await this.token.$_mintBatch(tokenBatchHolder, tokenBatchIds, mintValues, data); + this.receipt = await this.token.$_burnBatch(tokenBatchHolder, tokenBatchIds, burnValues, { from: operator }); }); it('emits a TransferBatch event', function () { @@ -201,7 +201,7 @@ contract('ERC1155', function (accounts) { from: tokenBatchHolder, to: ZERO_ADDRESS, // ids: tokenBatchIds, - // values: burnAmounts, + // values: burnValues, }); }); @@ -212,7 +212,7 @@ contract('ERC1155', function (accounts) { ); for (let i = 0; i < holderBatchBalances.length; i++) { - expect(holderBatchBalances[i]).to.be.bignumber.equal(mintAmounts[i].sub(burnAmounts[i])); + expect(holderBatchBalances[i]).to.be.bignumber.equal(mintValues[i].sub(burnValues[i])); } }); }); diff --git a/test/token/ERC1155/extensions/ERC1155Burnable.test.js b/test/token/ERC1155/extensions/ERC1155Burnable.test.js index 65a2f95f4..fc94db052 100644 --- a/test/token/ERC1155/extensions/ERC1155Burnable.test.js +++ b/test/token/ERC1155/extensions/ERC1155Burnable.test.js @@ -12,32 +12,32 @@ contract('ERC1155Burnable', function (accounts) { const uri = 'https://token.com'; const tokenIds = [new BN('42'), new BN('1137')]; - const amounts = [new BN('3000'), new BN('9902')]; + const values = [new BN('3000'), new BN('9902')]; beforeEach(async function () { this.token = await ERC1155Burnable.new(uri); - await this.token.$_mint(holder, tokenIds[0], amounts[0], '0x'); - await this.token.$_mint(holder, tokenIds[1], amounts[1], '0x'); + await this.token.$_mint(holder, tokenIds[0], values[0], '0x'); + await this.token.$_mint(holder, tokenIds[1], values[1], '0x'); }); describe('burn', function () { it('holder can burn their tokens', async function () { - await this.token.burn(holder, tokenIds[0], amounts[0].subn(1), { from: holder }); + await this.token.burn(holder, tokenIds[0], values[0].subn(1), { from: holder }); expect(await this.token.balanceOf(holder, tokenIds[0])).to.be.bignumber.equal('1'); }); it("approved operators can burn the holder's tokens", async function () { await this.token.setApprovalForAll(operator, true, { from: holder }); - await this.token.burn(holder, tokenIds[0], amounts[0].subn(1), { from: operator }); + await this.token.burn(holder, tokenIds[0], values[0].subn(1), { from: operator }); expect(await this.token.balanceOf(holder, tokenIds[0])).to.be.bignumber.equal('1'); }); it("unapproved accounts cannot burn the holder's tokens", async function () { await expectRevertCustomError( - this.token.burn(holder, tokenIds[0], amounts[0].subn(1), { from: other }), + this.token.burn(holder, tokenIds[0], values[0].subn(1), { from: other }), 'ERC1155MissingApprovalForAll', [other, holder], ); @@ -46,7 +46,7 @@ contract('ERC1155Burnable', function (accounts) { describe('burnBatch', function () { it('holder can burn their tokens', async function () { - await this.token.burnBatch(holder, tokenIds, [amounts[0].subn(1), amounts[1].subn(2)], { from: holder }); + await this.token.burnBatch(holder, tokenIds, [values[0].subn(1), values[1].subn(2)], { from: holder }); expect(await this.token.balanceOf(holder, tokenIds[0])).to.be.bignumber.equal('1'); expect(await this.token.balanceOf(holder, tokenIds[1])).to.be.bignumber.equal('2'); @@ -54,7 +54,7 @@ contract('ERC1155Burnable', function (accounts) { it("approved operators can burn the holder's tokens", async function () { await this.token.setApprovalForAll(operator, true, { from: holder }); - await this.token.burnBatch(holder, tokenIds, [amounts[0].subn(1), amounts[1].subn(2)], { from: operator }); + await this.token.burnBatch(holder, tokenIds, [values[0].subn(1), values[1].subn(2)], { from: operator }); expect(await this.token.balanceOf(holder, tokenIds[0])).to.be.bignumber.equal('1'); expect(await this.token.balanceOf(holder, tokenIds[1])).to.be.bignumber.equal('2'); @@ -62,7 +62,7 @@ contract('ERC1155Burnable', function (accounts) { it("unapproved accounts cannot burn the holder's tokens", async function () { await expectRevertCustomError( - this.token.burnBatch(holder, tokenIds, [amounts[0].subn(1), amounts[1].subn(2)], { from: other }), + this.token.burnBatch(holder, tokenIds, [values[0].subn(1), values[1].subn(2)], { from: other }), 'ERC1155MissingApprovalForAll', [other, holder], ); diff --git a/test/token/ERC1155/extensions/ERC1155Pausable.test.js b/test/token/ERC1155/extensions/ERC1155Pausable.test.js index b0ac54bdb..248ea5684 100644 --- a/test/token/ERC1155/extensions/ERC1155Pausable.test.js +++ b/test/token/ERC1155/extensions/ERC1155Pausable.test.js @@ -16,21 +16,21 @@ contract('ERC1155Pausable', function (accounts) { context('when token is paused', function () { const firstTokenId = new BN('37'); - const firstTokenAmount = new BN('42'); + const firstTokenValue = new BN('42'); const secondTokenId = new BN('19842'); - const secondTokenAmount = new BN('23'); + const secondTokenValue = new BN('23'); beforeEach(async function () { await this.token.setApprovalForAll(operator, true, { from: holder }); - await this.token.$_mint(holder, firstTokenId, firstTokenAmount, '0x'); + await this.token.$_mint(holder, firstTokenId, firstTokenValue, '0x'); await this.token.$_pause(); }); it('reverts when trying to safeTransferFrom from holder', async function () { await expectRevertCustomError( - this.token.safeTransferFrom(holder, receiver, firstTokenId, firstTokenAmount, '0x', { from: holder }), + this.token.safeTransferFrom(holder, receiver, firstTokenId, firstTokenValue, '0x', { from: holder }), 'EnforcedPause', [], ); @@ -38,7 +38,7 @@ contract('ERC1155Pausable', function (accounts) { it('reverts when trying to safeTransferFrom from operator', async function () { await expectRevertCustomError( - this.token.safeTransferFrom(holder, receiver, firstTokenId, firstTokenAmount, '0x', { from: operator }), + this.token.safeTransferFrom(holder, receiver, firstTokenId, firstTokenValue, '0x', { from: operator }), 'EnforcedPause', [], ); @@ -46,7 +46,7 @@ contract('ERC1155Pausable', function (accounts) { it('reverts when trying to safeBatchTransferFrom from holder', async function () { await expectRevertCustomError( - this.token.safeBatchTransferFrom(holder, receiver, [firstTokenId], [firstTokenAmount], '0x', { from: holder }), + this.token.safeBatchTransferFrom(holder, receiver, [firstTokenId], [firstTokenValue], '0x', { from: holder }), 'EnforcedPause', [], ); @@ -54,7 +54,7 @@ contract('ERC1155Pausable', function (accounts) { it('reverts when trying to safeBatchTransferFrom from operator', async function () { await expectRevertCustomError( - this.token.safeBatchTransferFrom(holder, receiver, [firstTokenId], [firstTokenAmount], '0x', { + this.token.safeBatchTransferFrom(holder, receiver, [firstTokenId], [firstTokenValue], '0x', { from: operator, }), 'EnforcedPause', @@ -64,7 +64,7 @@ contract('ERC1155Pausable', function (accounts) { it('reverts when trying to mint', async function () { await expectRevertCustomError( - this.token.$_mint(holder, secondTokenId, secondTokenAmount, '0x'), + this.token.$_mint(holder, secondTokenId, secondTokenValue, '0x'), 'EnforcedPause', [], ); @@ -72,19 +72,19 @@ contract('ERC1155Pausable', function (accounts) { it('reverts when trying to mintBatch', async function () { await expectRevertCustomError( - this.token.$_mintBatch(holder, [secondTokenId], [secondTokenAmount], '0x'), + this.token.$_mintBatch(holder, [secondTokenId], [secondTokenValue], '0x'), 'EnforcedPause', [], ); }); it('reverts when trying to burn', async function () { - await expectRevertCustomError(this.token.$_burn(holder, firstTokenId, firstTokenAmount), 'EnforcedPause', []); + await expectRevertCustomError(this.token.$_burn(holder, firstTokenId, firstTokenValue), 'EnforcedPause', []); }); it('reverts when trying to burnBatch', async function () { await expectRevertCustomError( - this.token.$_burnBatch(holder, [firstTokenId], [firstTokenAmount]), + this.token.$_burnBatch(holder, [firstTokenId], [firstTokenValue]), 'EnforcedPause', [], ); @@ -98,9 +98,9 @@ contract('ERC1155Pausable', function (accounts) { }); describe('balanceOf', function () { - it('returns the amount of tokens owned by the given address', async function () { + it('returns the token value owned by the given address', async function () { const balance = await this.token.balanceOf(holder, firstTokenId); - expect(balance).to.be.bignumber.equal(firstTokenAmount); + expect(balance).to.be.bignumber.equal(firstTokenValue); }); }); diff --git a/test/token/ERC1155/extensions/ERC1155Supply.test.js b/test/token/ERC1155/extensions/ERC1155Supply.test.js index 22a75c84f..bf86920f6 100644 --- a/test/token/ERC1155/extensions/ERC1155Supply.test.js +++ b/test/token/ERC1155/extensions/ERC1155Supply.test.js @@ -12,10 +12,10 @@ contract('ERC1155Supply', function (accounts) { const uri = 'https://token.com'; const firstTokenId = new BN('37'); - const firstTokenAmount = new BN('42'); + const firstTokenValue = new BN('42'); const secondTokenId = new BN('19842'); - const secondTokenAmount = new BN('23'); + const secondTokenValue = new BN('23'); beforeEach(async function () { this.token = await ERC1155Supply.new(uri); @@ -35,7 +35,7 @@ contract('ERC1155Supply', function (accounts) { context('after mint', function () { context('single', function () { beforeEach(async function () { - await this.token.$_mint(holder, firstTokenId, firstTokenAmount, '0x'); + await this.token.$_mint(holder, firstTokenId, firstTokenValue, '0x'); }); it('exist', async function () { @@ -43,19 +43,14 @@ contract('ERC1155Supply', function (accounts) { }); it('totalSupply', async function () { - expect(await this.token.methods['totalSupply(uint256)'](firstTokenId)).to.be.bignumber.equal(firstTokenAmount); - expect(await this.token.methods['totalSupply()']()).to.be.bignumber.equal(firstTokenAmount); + expect(await this.token.methods['totalSupply(uint256)'](firstTokenId)).to.be.bignumber.equal(firstTokenValue); + expect(await this.token.methods['totalSupply()']()).to.be.bignumber.equal(firstTokenValue); }); }); context('batch', function () { beforeEach(async function () { - await this.token.$_mintBatch( - holder, - [firstTokenId, secondTokenId], - [firstTokenAmount, secondTokenAmount], - '0x', - ); + await this.token.$_mintBatch(holder, [firstTokenId, secondTokenId], [firstTokenValue, secondTokenValue], '0x'); }); it('exist', async function () { @@ -64,12 +59,10 @@ contract('ERC1155Supply', function (accounts) { }); it('totalSupply', async function () { - expect(await this.token.methods['totalSupply(uint256)'](firstTokenId)).to.be.bignumber.equal(firstTokenAmount); - expect(await this.token.methods['totalSupply(uint256)'](secondTokenId)).to.be.bignumber.equal( - secondTokenAmount, - ); + expect(await this.token.methods['totalSupply(uint256)'](firstTokenId)).to.be.bignumber.equal(firstTokenValue); + expect(await this.token.methods['totalSupply(uint256)'](secondTokenId)).to.be.bignumber.equal(secondTokenValue); expect(await this.token.methods['totalSupply()']()).to.be.bignumber.equal( - firstTokenAmount.add(secondTokenAmount), + firstTokenValue.add(secondTokenValue), ); }); }); @@ -78,8 +71,8 @@ contract('ERC1155Supply', function (accounts) { context('after burn', function () { context('single', function () { beforeEach(async function () { - await this.token.$_mint(holder, firstTokenId, firstTokenAmount, '0x'); - await this.token.$_burn(holder, firstTokenId, firstTokenAmount); + await this.token.$_mint(holder, firstTokenId, firstTokenValue, '0x'); + await this.token.$_burn(holder, firstTokenId, firstTokenValue); }); it('exist', async function () { @@ -94,13 +87,8 @@ contract('ERC1155Supply', function (accounts) { context('batch', function () { beforeEach(async function () { - await this.token.$_mintBatch( - holder, - [firstTokenId, secondTokenId], - [firstTokenAmount, secondTokenAmount], - '0x', - ); - await this.token.$_burnBatch(holder, [firstTokenId, secondTokenId], [firstTokenAmount, secondTokenAmount]); + await this.token.$_mintBatch(holder, [firstTokenId, secondTokenId], [firstTokenValue, secondTokenValue], '0x'); + await this.token.$_burnBatch(holder, [firstTokenId, secondTokenId], [firstTokenValue, secondTokenValue]); }); it('exist', async function () { @@ -118,7 +106,7 @@ contract('ERC1155Supply', function (accounts) { context('other', function () { it('supply unaffected by no-op', async function () { - this.token.safeTransferFrom(ZERO_ADDRESS, ZERO_ADDRESS, firstTokenId, firstTokenAmount, '0x', { + this.token.safeTransferFrom(ZERO_ADDRESS, ZERO_ADDRESS, firstTokenId, firstTokenValue, '0x', { from: ZERO_ADDRESS, }); expect(await this.token.methods['totalSupply(uint256)'](firstTokenId)).to.be.bignumber.equal('0'); diff --git a/test/token/ERC1155/extensions/ERC1155URIStorage.test.js b/test/token/ERC1155/extensions/ERC1155URIStorage.test.js index 95d9c18f7..58ac67bc6 100644 --- a/test/token/ERC1155/extensions/ERC1155URIStorage.test.js +++ b/test/token/ERC1155/extensions/ERC1155URIStorage.test.js @@ -12,14 +12,14 @@ contract(['ERC1155URIStorage'], function (accounts) { const baseUri = 'https://token.com/'; const tokenId = new BN('1'); - const amount = new BN('3000'); + const value = new BN('3000'); describe('with base uri set', function () { beforeEach(async function () { this.token = await ERC1155URIStorage.new(erc1155Uri); await this.token.$_setBaseURI(baseUri); - await this.token.$_mint(holder, tokenId, amount, '0x'); + await this.token.$_mint(holder, tokenId, value, '0x'); }); it('can request the token uri, returning the erc1155 uri if no token uri was set', async function () { @@ -44,7 +44,7 @@ contract(['ERC1155URIStorage'], function (accounts) { beforeEach(async function () { this.token = await ERC1155URIStorage.new(''); - await this.token.$_mint(holder, tokenId, amount, '0x'); + await this.token.$_mint(holder, tokenId, value, '0x'); }); it('can request the token uri, returning an empty string if no token uri was set', async function () { diff --git a/test/token/ERC1155/utils/ERC1155Holder.test.js b/test/token/ERC1155/utils/ERC1155Holder.test.js index 8d8541640..ee818eae8 100644 --- a/test/token/ERC1155/utils/ERC1155Holder.test.js +++ b/test/token/ERC1155/utils/ERC1155Holder.test.js @@ -11,13 +11,13 @@ contract('ERC1155Holder', function (accounts) { const [creator] = accounts; const uri = 'https://token-cdn-domain/{id}.json'; const multiTokenIds = [new BN(1), new BN(2), new BN(3)]; - const multiTokenAmounts = [new BN(1000), new BN(2000), new BN(3000)]; + const multiTokenValues = [new BN(1000), new BN(2000), new BN(3000)]; const transferData = '0x12345678'; beforeEach(async function () { this.multiToken = await ERC1155.new(uri); this.holder = await ERC1155Holder.new(); - await this.multiToken.$_mintBatch(creator, multiTokenIds, multiTokenAmounts, '0x'); + await this.multiToken.$_mintBatch(creator, multiTokenIds, multiTokenValues, '0x'); }); shouldSupportInterfaces(['ERC165', 'ERC1155Receiver']); @@ -27,13 +27,13 @@ contract('ERC1155Holder', function (accounts) { creator, this.holder.address, multiTokenIds[0], - multiTokenAmounts[0], + multiTokenValues[0], transferData, { from: creator }, ); expect(await this.multiToken.balanceOf(this.holder.address, multiTokenIds[0])).to.be.bignumber.equal( - multiTokenAmounts[0], + multiTokenValues[0], ); for (let i = 1; i < multiTokenIds.length; i++) { @@ -50,14 +50,14 @@ contract('ERC1155Holder', function (accounts) { creator, this.holder.address, multiTokenIds, - multiTokenAmounts, + multiTokenValues, transferData, { from: creator }, ); for (let i = 0; i < multiTokenIds.length; i++) { expect(await this.multiToken.balanceOf(this.holder.address, multiTokenIds[i])).to.be.bignumber.equal( - multiTokenAmounts[i], + multiTokenValues[i], ); } }); diff --git a/test/token/ERC20/ERC20.behavior.js b/test/token/ERC20/ERC20.behavior.js index bb2efda89..b6f8617b2 100644 --- a/test/token/ERC20/ERC20.behavior.js +++ b/test/token/ERC20/ERC20.behavior.js @@ -9,7 +9,7 @@ function shouldBehaveLikeERC20(initialSupply, accounts, opts = {}) { const { forcedApproval } = opts; describe('total supply', function () { - it('returns the total amount of tokens', async function () { + it('returns the total token value', async function () { expect(await this.token.totalSupply()).to.be.bignumber.equal(initialSupply); }); }); @@ -22,7 +22,7 @@ function shouldBehaveLikeERC20(initialSupply, accounts, opts = {}) { }); describe('when the requested account has some tokens', function () { - it('returns the total amount of tokens', async function () { + it('returns the total token value', async function () { expect(await this.token.balanceOf(initialHolder)).to.be.bignumber.equal(initialSupply); }); }); @@ -49,33 +49,33 @@ function shouldBehaveLikeERC20(initialSupply, accounts, opts = {}) { }); describe('when the token owner has enough balance', function () { - const amount = initialSupply; + const value = initialSupply; - it('transfers the requested amount', async function () { - await this.token.transferFrom(tokenOwner, to, amount, { from: spender }); + it('transfers the requested value', async function () { + await this.token.transferFrom(tokenOwner, to, value, { from: spender }); expect(await this.token.balanceOf(tokenOwner)).to.be.bignumber.equal('0'); - expect(await this.token.balanceOf(to)).to.be.bignumber.equal(amount); + expect(await this.token.balanceOf(to)).to.be.bignumber.equal(value); }); it('decreases the spender allowance', async function () { - await this.token.transferFrom(tokenOwner, to, amount, { from: spender }); + await this.token.transferFrom(tokenOwner, to, value, { from: spender }); expect(await this.token.allowance(tokenOwner, spender)).to.be.bignumber.equal('0'); }); it('emits a transfer event', async function () { - expectEvent(await this.token.transferFrom(tokenOwner, to, amount, { from: spender }), 'Transfer', { + expectEvent(await this.token.transferFrom(tokenOwner, to, value, { from: spender }), 'Transfer', { from: tokenOwner, to: to, - value: amount, + value: value, }); }); if (forcedApproval) { it('emits an approval event', async function () { - expectEvent(await this.token.transferFrom(tokenOwner, to, amount, { from: spender }), 'Approval', { + expectEvent(await this.token.transferFrom(tokenOwner, to, value, { from: spender }), 'Approval', { owner: tokenOwner, spender: spender, value: await this.token.allowance(tokenOwner, spender), @@ -84,7 +84,7 @@ function shouldBehaveLikeERC20(initialSupply, accounts, opts = {}) { } else { it('does not emit an approval event', async function () { expectEvent.notEmitted( - await this.token.transferFrom(tokenOwner, to, amount, { from: spender }), + await this.token.transferFrom(tokenOwner, to, value, { from: spender }), 'Approval', ); }); @@ -92,7 +92,7 @@ function shouldBehaveLikeERC20(initialSupply, accounts, opts = {}) { }); describe('when the token owner does not have enough balance', function () { - const amount = initialSupply; + const value = initialSupply; beforeEach('reducing balance', async function () { await this.token.transfer(to, 1, { from: tokenOwner }); @@ -100,9 +100,9 @@ function shouldBehaveLikeERC20(initialSupply, accounts, opts = {}) { it('reverts', async function () { await expectRevertCustomError( - this.token.transferFrom(tokenOwner, to, amount, { from: spender }), + this.token.transferFrom(tokenOwner, to, value, { from: spender }), 'ERC20InsufficientBalance', - [tokenOwner, amount - 1, amount], + [tokenOwner, value - 1, value], ); }); }); @@ -116,19 +116,19 @@ function shouldBehaveLikeERC20(initialSupply, accounts, opts = {}) { }); describe('when the token owner has enough balance', function () { - const amount = initialSupply; + const value = initialSupply; it('reverts', async function () { await expectRevertCustomError( - this.token.transferFrom(tokenOwner, to, amount, { from: spender }), + this.token.transferFrom(tokenOwner, to, value, { from: spender }), 'ERC20InsufficientAllowance', - [spender, allowance, amount], + [spender, allowance, value], ); }); }); describe('when the token owner does not have enough balance', function () { - const amount = allowance; + const value = allowance; beforeEach('reducing balance', async function () { await this.token.transfer(to, 2, { from: tokenOwner }); @@ -136,9 +136,9 @@ function shouldBehaveLikeERC20(initialSupply, accounts, opts = {}) { it('reverts', async function () { await expectRevertCustomError( - this.token.transferFrom(tokenOwner, to, amount, { from: spender }), + this.token.transferFrom(tokenOwner, to, value, { from: spender }), 'ERC20InsufficientBalance', - [tokenOwner, amount - 1, amount], + [tokenOwner, value - 1, value], ); }); }); @@ -162,16 +162,16 @@ function shouldBehaveLikeERC20(initialSupply, accounts, opts = {}) { }); describe('when the recipient is the zero address', function () { - const amount = initialSupply; + const value = initialSupply; const to = ZERO_ADDRESS; beforeEach(async function () { - await this.token.approve(spender, amount, { from: tokenOwner }); + await this.token.approve(spender, value, { from: tokenOwner }); }); it('reverts', async function () { await expectRevertCustomError( - this.token.transferFrom(tokenOwner, to, amount, { from: spender }), + this.token.transferFrom(tokenOwner, to, value, { from: spender }), 'ERC20InvalidReceiver', [ZERO_ADDRESS], ); @@ -180,13 +180,13 @@ function shouldBehaveLikeERC20(initialSupply, accounts, opts = {}) { }); describe('when the token owner is the zero address', function () { - const amount = 0; + const value = 0; const tokenOwner = ZERO_ADDRESS; const to = recipient; it('reverts', async function () { await expectRevertCustomError( - this.token.transferFrom(tokenOwner, to, amount, { from: spender }), + this.token.transferFrom(tokenOwner, to, value, { from: spender }), 'ERC20InvalidApprover', [ZERO_ADDRESS], ); @@ -195,8 +195,8 @@ function shouldBehaveLikeERC20(initialSupply, accounts, opts = {}) { }); describe('approve', function () { - shouldBehaveLikeERC20Approve(initialHolder, recipient, initialSupply, function (owner, spender, amount) { - return this.token.approve(spender, amount, { from: owner }); + shouldBehaveLikeERC20Approve(initialHolder, recipient, initialSupply, function (owner, spender, value) { + return this.token.approve(spender, value, { from: owner }); }); }); } @@ -204,38 +204,38 @@ function shouldBehaveLikeERC20(initialSupply, accounts, opts = {}) { function shouldBehaveLikeERC20Transfer(from, to, balance, transfer) { describe('when the recipient is not the zero address', function () { describe('when the sender does not have enough balance', function () { - const amount = balance.addn(1); + const value = balance.addn(1); it('reverts', async function () { - await expectRevertCustomError(transfer.call(this, from, to, amount), 'ERC20InsufficientBalance', [ + await expectRevertCustomError(transfer.call(this, from, to, value), 'ERC20InsufficientBalance', [ from, balance, - amount, + value, ]); }); }); describe('when the sender transfers all balance', function () { - const amount = balance; + const value = balance; - it('transfers the requested amount', async function () { - await transfer.call(this, from, to, amount); + it('transfers the requested value', async function () { + await transfer.call(this, from, to, value); expect(await this.token.balanceOf(from)).to.be.bignumber.equal('0'); - expect(await this.token.balanceOf(to)).to.be.bignumber.equal(amount); + expect(await this.token.balanceOf(to)).to.be.bignumber.equal(value); }); it('emits a transfer event', async function () { - expectEvent(await transfer.call(this, from, to, amount), 'Transfer', { from, to, value: amount }); + expectEvent(await transfer.call(this, from, to, value), 'Transfer', { from, to, value: value }); }); }); describe('when the sender transfers zero tokens', function () { - const amount = new BN('0'); + const value = new BN('0'); - it('transfers the requested amount', async function () { - await transfer.call(this, from, to, amount); + it('transfers the requested value', async function () { + await transfer.call(this, from, to, value); expect(await this.token.balanceOf(from)).to.be.bignumber.equal(balance); @@ -243,7 +243,7 @@ function shouldBehaveLikeERC20Transfer(from, to, balance, transfer) { }); it('emits a transfer event', async function () { - expectEvent(await transfer.call(this, from, to, amount), 'Transfer', { from, to, value: amount }); + expectEvent(await transfer.call(this, from, to, value), 'Transfer', { from, to, value: value }); }); }); }); @@ -260,65 +260,65 @@ function shouldBehaveLikeERC20Transfer(from, to, balance, transfer) { function shouldBehaveLikeERC20Approve(owner, spender, supply, approve) { describe('when the spender is not the zero address', function () { describe('when the sender has enough balance', function () { - const amount = supply; + const value = supply; it('emits an approval event', async function () { - expectEvent(await approve.call(this, owner, spender, amount), 'Approval', { + expectEvent(await approve.call(this, owner, spender, value), 'Approval', { owner: owner, spender: spender, - value: amount, + value: value, }); }); - describe('when there was no approved amount before', function () { - it('approves the requested amount', async function () { - await approve.call(this, owner, spender, amount); + describe('when there was no approved value before', function () { + it('approves the requested value', async function () { + await approve.call(this, owner, spender, value); - expect(await this.token.allowance(owner, spender)).to.be.bignumber.equal(amount); + expect(await this.token.allowance(owner, spender)).to.be.bignumber.equal(value); }); }); - describe('when the spender had an approved amount', function () { + describe('when the spender had an approved value', function () { beforeEach(async function () { await approve.call(this, owner, spender, new BN(1)); }); - it('approves the requested amount and replaces the previous one', async function () { - await approve.call(this, owner, spender, amount); + it('approves the requested value and replaces the previous one', async function () { + await approve.call(this, owner, spender, value); - expect(await this.token.allowance(owner, spender)).to.be.bignumber.equal(amount); + expect(await this.token.allowance(owner, spender)).to.be.bignumber.equal(value); }); }); }); describe('when the sender does not have enough balance', function () { - const amount = supply.addn(1); + const value = supply.addn(1); it('emits an approval event', async function () { - expectEvent(await approve.call(this, owner, spender, amount), 'Approval', { + expectEvent(await approve.call(this, owner, spender, value), 'Approval', { owner: owner, spender: spender, - value: amount, + value: value, }); }); - describe('when there was no approved amount before', function () { - it('approves the requested amount', async function () { - await approve.call(this, owner, spender, amount); + describe('when there was no approved value before', function () { + it('approves the requested value', async function () { + await approve.call(this, owner, spender, value); - expect(await this.token.allowance(owner, spender)).to.be.bignumber.equal(amount); + expect(await this.token.allowance(owner, spender)).to.be.bignumber.equal(value); }); }); - describe('when the spender had an approved amount', function () { + describe('when the spender had an approved value', function () { beforeEach(async function () { await approve.call(this, owner, spender, new BN(1)); }); - it('approves the requested amount and replaces the previous one', async function () { - await approve.call(this, owner, spender, amount); + it('approves the requested value and replaces the previous one', async function () { + await approve.call(this, owner, spender, value); - expect(await this.token.allowance(owner, spender)).to.be.bignumber.equal(amount); + expect(await this.token.allowance(owner, spender)).to.be.bignumber.equal(value); }); }); }); diff --git a/test/token/ERC20/ERC20.test.js b/test/token/ERC20/ERC20.test.js index ef6d82f2b..a63df5239 100644 --- a/test/token/ERC20/ERC20.test.js +++ b/test/token/ERC20/ERC20.test.js @@ -46,145 +46,145 @@ contract('ERC20', function (accounts) { describe('when the spender is not the zero address', function () { const spender = recipient; - function shouldDecreaseApproval(amount) { - describe('when there was no approved amount before', function () { + function shouldDecreaseApproval(value) { + describe('when there was no approved value before', function () { it('reverts', async function () { const allowance = await this.token.allowance(initialHolder, spender); await expectRevertCustomError( - this.token.decreaseAllowance(spender, amount, { from: initialHolder }), + this.token.decreaseAllowance(spender, value, { from: initialHolder }), 'ERC20FailedDecreaseAllowance', - [spender, allowance, amount], + [spender, allowance, value], ); }); }); - describe('when the spender had an approved amount', function () { - const approvedAmount = amount; + describe('when the spender had an approved value', function () { + const approvedValue = value; beforeEach(async function () { - await this.token.approve(spender, approvedAmount, { from: initialHolder }); + await this.token.approve(spender, approvedValue, { from: initialHolder }); }); it('emits an approval event', async function () { expectEvent( - await this.token.decreaseAllowance(spender, approvedAmount, { from: initialHolder }), + await this.token.decreaseAllowance(spender, approvedValue, { from: initialHolder }), 'Approval', { owner: initialHolder, spender: spender, value: new BN(0) }, ); }); - it('decreases the spender allowance subtracting the requested amount', async function () { - await this.token.decreaseAllowance(spender, approvedAmount.subn(1), { from: initialHolder }); + it('decreases the spender allowance subtracting the requested value', async function () { + await this.token.decreaseAllowance(spender, approvedValue.subn(1), { from: initialHolder }); expect(await this.token.allowance(initialHolder, spender)).to.be.bignumber.equal('1'); }); it('sets the allowance to zero when all allowance is removed', async function () { - await this.token.decreaseAllowance(spender, approvedAmount, { from: initialHolder }); + await this.token.decreaseAllowance(spender, approvedValue, { from: initialHolder }); expect(await this.token.allowance(initialHolder, spender)).to.be.bignumber.equal('0'); }); it('reverts when more than the full allowance is removed', async function () { await expectRevertCustomError( - this.token.decreaseAllowance(spender, approvedAmount.addn(1), { from: initialHolder }), + this.token.decreaseAllowance(spender, approvedValue.addn(1), { from: initialHolder }), 'ERC20FailedDecreaseAllowance', - [spender, approvedAmount, approvedAmount.addn(1)], + [spender, approvedValue, approvedValue.addn(1)], ); }); }); } describe('when the sender has enough balance', function () { - const amount = initialSupply; + const value = initialSupply; - shouldDecreaseApproval(amount); + shouldDecreaseApproval(value); }); describe('when the sender does not have enough balance', function () { - const amount = initialSupply.addn(1); + const value = initialSupply.addn(1); - shouldDecreaseApproval(amount); + shouldDecreaseApproval(value); }); }); describe('when the spender is the zero address', function () { - const amount = initialSupply; + const value = initialSupply; const spender = ZERO_ADDRESS; it('reverts', async function () { await expectRevertCustomError( - this.token.decreaseAllowance(spender, amount, { from: initialHolder }), + this.token.decreaseAllowance(spender, value, { from: initialHolder }), 'ERC20FailedDecreaseAllowance', - [spender, 0, amount], + [spender, 0, value], ); }); }); }); describe('increase allowance', function () { - const amount = initialSupply; + const value = initialSupply; describe('when the spender is not the zero address', function () { const spender = recipient; describe('when the sender has enough balance', function () { it('emits an approval event', async function () { - expectEvent(await this.token.increaseAllowance(spender, amount, { from: initialHolder }), 'Approval', { + expectEvent(await this.token.increaseAllowance(spender, value, { from: initialHolder }), 'Approval', { owner: initialHolder, spender: spender, - value: amount, + value: value, }); }); - describe('when there was no approved amount before', function () { - it('approves the requested amount', async function () { - await this.token.increaseAllowance(spender, amount, { from: initialHolder }); + describe('when there was no approved value before', function () { + it('approves the requested value', async function () { + await this.token.increaseAllowance(spender, value, { from: initialHolder }); - expect(await this.token.allowance(initialHolder, spender)).to.be.bignumber.equal(amount); + expect(await this.token.allowance(initialHolder, spender)).to.be.bignumber.equal(value); }); }); - describe('when the spender had an approved amount', function () { + describe('when the spender had an approved value', function () { beforeEach(async function () { await this.token.approve(spender, new BN(1), { from: initialHolder }); }); - it('increases the spender allowance adding the requested amount', async function () { - await this.token.increaseAllowance(spender, amount, { from: initialHolder }); + it('increases the spender allowance adding the requested value', async function () { + await this.token.increaseAllowance(spender, value, { from: initialHolder }); - expect(await this.token.allowance(initialHolder, spender)).to.be.bignumber.equal(amount.addn(1)); + expect(await this.token.allowance(initialHolder, spender)).to.be.bignumber.equal(value.addn(1)); }); }); }); describe('when the sender does not have enough balance', function () { - const amount = initialSupply.addn(1); + const value = initialSupply.addn(1); it('emits an approval event', async function () { - expectEvent(await this.token.increaseAllowance(spender, amount, { from: initialHolder }), 'Approval', { + expectEvent(await this.token.increaseAllowance(spender, value, { from: initialHolder }), 'Approval', { owner: initialHolder, spender: spender, - value: amount, + value: value, }); }); - describe('when there was no approved amount before', function () { - it('approves the requested amount', async function () { - await this.token.increaseAllowance(spender, amount, { from: initialHolder }); + describe('when there was no approved value before', function () { + it('approves the requested value', async function () { + await this.token.increaseAllowance(spender, value, { from: initialHolder }); - expect(await this.token.allowance(initialHolder, spender)).to.be.bignumber.equal(amount); + expect(await this.token.allowance(initialHolder, spender)).to.be.bignumber.equal(value); }); }); - describe('when the spender had an approved amount', function () { + describe('when the spender had an approved value', function () { beforeEach(async function () { await this.token.approve(spender, new BN(1), { from: initialHolder }); }); - it('increases the spender allowance adding the requested amount', async function () { - await this.token.increaseAllowance(spender, amount, { from: initialHolder }); + it('increases the spender allowance adding the requested value', async function () { + await this.token.increaseAllowance(spender, value, { from: initialHolder }); - expect(await this.token.allowance(initialHolder, spender)).to.be.bignumber.equal(amount.addn(1)); + expect(await this.token.allowance(initialHolder, spender)).to.be.bignumber.equal(value.addn(1)); }); }); }); @@ -195,7 +195,7 @@ contract('ERC20', function (accounts) { it('reverts', async function () { await expectRevertCustomError( - this.token.increaseAllowance(spender, amount, { from: initialHolder }), + this.token.increaseAllowance(spender, value, { from: initialHolder }), 'ERC20InvalidSpender', [ZERO_ADDRESS], ); @@ -204,11 +204,9 @@ contract('ERC20', function (accounts) { }); describe('_mint', function () { - const amount = new BN(50); + const value = new BN(50); it('rejects a null account', async function () { - await expectRevertCustomError(this.token.$_mint(ZERO_ADDRESS, amount), 'ERC20InvalidReceiver', [ - ZERO_ADDRESS, - ]); + await expectRevertCustomError(this.token.$_mint(ZERO_ADDRESS, value), 'ERC20InvalidReceiver', [ZERO_ADDRESS]); }); it('rejects overflow', async function () { @@ -221,22 +219,22 @@ contract('ERC20', function (accounts) { describe('for a non zero account', function () { beforeEach('minting', async function () { - this.receipt = await this.token.$_mint(recipient, amount); + this.receipt = await this.token.$_mint(recipient, value); }); it('increments totalSupply', async function () { - const expectedSupply = initialSupply.add(amount); + const expectedSupply = initialSupply.add(value); expect(await this.token.totalSupply()).to.be.bignumber.equal(expectedSupply); }); it('increments recipient balance', async function () { - expect(await this.token.balanceOf(recipient)).to.be.bignumber.equal(amount); + expect(await this.token.balanceOf(recipient)).to.be.bignumber.equal(value); }); it('emits Transfer event', async function () { const event = expectEvent(this.receipt, 'Transfer', { from: ZERO_ADDRESS, to: recipient }); - expect(event.args.value).to.be.bignumber.equal(amount); + expect(event.args.value).to.be.bignumber.equal(value); }); }); }); @@ -257,81 +255,81 @@ contract('ERC20', function (accounts) { ); }); - const describeBurn = function (description, amount) { + const describeBurn = function (description, value) { describe(description, function () { beforeEach('burning', async function () { - this.receipt = await this.token.$_burn(initialHolder, amount); + this.receipt = await this.token.$_burn(initialHolder, value); }); it('decrements totalSupply', async function () { - const expectedSupply = initialSupply.sub(amount); + const expectedSupply = initialSupply.sub(value); expect(await this.token.totalSupply()).to.be.bignumber.equal(expectedSupply); }); it('decrements initialHolder balance', async function () { - const expectedBalance = initialSupply.sub(amount); + const expectedBalance = initialSupply.sub(value); expect(await this.token.balanceOf(initialHolder)).to.be.bignumber.equal(expectedBalance); }); it('emits Transfer event', async function () { const event = expectEvent(this.receipt, 'Transfer', { from: initialHolder, to: ZERO_ADDRESS }); - expect(event.args.value).to.be.bignumber.equal(amount); + expect(event.args.value).to.be.bignumber.equal(value); }); }); }; describeBurn('for entire balance', initialSupply); - describeBurn('for less amount than balance', initialSupply.subn(1)); + describeBurn('for less value than balance', initialSupply.subn(1)); }); }); describe('_update', function () { - const amount = new BN(1); + const value = new BN(1); it('from is the zero address', async function () { const balanceBefore = await this.token.balanceOf(initialHolder); const totalSupply = await this.token.totalSupply(); - expectEvent(await this.token.$_update(ZERO_ADDRESS, initialHolder, amount), 'Transfer', { + expectEvent(await this.token.$_update(ZERO_ADDRESS, initialHolder, value), 'Transfer', { from: ZERO_ADDRESS, to: initialHolder, - value: amount, + value: value, }); - expect(await this.token.totalSupply()).to.be.bignumber.equal(totalSupply.add(amount)); - expect(await this.token.balanceOf(initialHolder)).to.be.bignumber.equal(balanceBefore.add(amount)); + expect(await this.token.totalSupply()).to.be.bignumber.equal(totalSupply.add(value)); + expect(await this.token.balanceOf(initialHolder)).to.be.bignumber.equal(balanceBefore.add(value)); }); it('to is the zero address', async function () { const balanceBefore = await this.token.balanceOf(initialHolder); const totalSupply = await this.token.totalSupply(); - expectEvent(await this.token.$_update(initialHolder, ZERO_ADDRESS, amount), 'Transfer', { + expectEvent(await this.token.$_update(initialHolder, ZERO_ADDRESS, value), 'Transfer', { from: initialHolder, to: ZERO_ADDRESS, - value: amount, + value: value, }); - expect(await this.token.totalSupply()).to.be.bignumber.equal(totalSupply.sub(amount)); - expect(await this.token.balanceOf(initialHolder)).to.be.bignumber.equal(balanceBefore.sub(amount)); + expect(await this.token.totalSupply()).to.be.bignumber.equal(totalSupply.sub(value)); + expect(await this.token.balanceOf(initialHolder)).to.be.bignumber.equal(balanceBefore.sub(value)); }); it('from and to are the zero address', async function () { const totalSupply = await this.token.totalSupply(); - await this.token.$_update(ZERO_ADDRESS, ZERO_ADDRESS, amount); + await this.token.$_update(ZERO_ADDRESS, ZERO_ADDRESS, value); expect(await this.token.totalSupply()).to.be.bignumber.equal(totalSupply); - expectEvent(await this.token.$_update(ZERO_ADDRESS, ZERO_ADDRESS, amount), 'Transfer', { + expectEvent(await this.token.$_update(ZERO_ADDRESS, ZERO_ADDRESS, value), 'Transfer', { from: ZERO_ADDRESS, to: ZERO_ADDRESS, - value: amount, + value: value, }); }); }); describe('_transfer', function () { - shouldBehaveLikeERC20Transfer(initialHolder, recipient, initialSupply, function (from, to, amount) { - return this.token.$_transfer(from, to, amount); + shouldBehaveLikeERC20Transfer(initialHolder, recipient, initialSupply, function (from, to, value) { + return this.token.$_transfer(from, to, value); }); describe('when the sender is the zero address', function () { @@ -346,8 +344,8 @@ contract('ERC20', function (accounts) { }); describe('_approve', function () { - shouldBehaveLikeERC20Approve(initialHolder, recipient, initialSupply, function (owner, spender, amount) { - return this.token.$_approve(owner, spender, amount); + shouldBehaveLikeERC20Approve(initialHolder, recipient, initialSupply, function (owner, spender, value) { + return this.token.$_approve(owner, spender, value); }); describe('when the owner is the zero address', function () { diff --git a/test/token/ERC20/extensions/ERC20Burnable.behavior.js b/test/token/ERC20/extensions/ERC20Burnable.behavior.js index 848e54b79..937491bdf 100644 --- a/test/token/ERC20/extensions/ERC20Burnable.behavior.js +++ b/test/token/ERC20/extensions/ERC20Burnable.behavior.js @@ -6,42 +6,42 @@ const { expectRevertCustomError } = require('../../../helpers/customError'); function shouldBehaveLikeERC20Burnable(owner, initialBalance, [burner]) { describe('burn', function () { - describe('when the given amount is not greater than balance of the sender', function () { - context('for a zero amount', function () { + describe('when the given value is not greater than balance of the sender', function () { + context('for a zero value', function () { shouldBurn(new BN(0)); }); - context('for a non-zero amount', function () { + context('for a non-zero value', function () { shouldBurn(new BN(100)); }); - function shouldBurn(amount) { + function shouldBurn(value) { beforeEach(async function () { - this.receipt = await this.token.burn(amount, { from: owner }); + this.receipt = await this.token.burn(value, { from: owner }); }); - it('burns the requested amount', async function () { - expect(await this.token.balanceOf(owner)).to.be.bignumber.equal(initialBalance.sub(amount)); + it('burns the requested value', async function () { + expect(await this.token.balanceOf(owner)).to.be.bignumber.equal(initialBalance.sub(value)); }); it('emits a transfer event', async function () { expectEvent(this.receipt, 'Transfer', { from: owner, to: ZERO_ADDRESS, - value: amount, + value: value, }); }); } }); - describe('when the given amount is greater than the balance of the sender', function () { - const amount = initialBalance.addn(1); + describe('when the given value is greater than the balance of the sender', function () { + const value = initialBalance.addn(1); it('reverts', async function () { - await expectRevertCustomError(this.token.burn(amount, { from: owner }), 'ERC20InsufficientBalance', [ + await expectRevertCustomError(this.token.burn(value, { from: owner }), 'ERC20InsufficientBalance', [ owner, initialBalance, - amount, + value, ]); }); }); @@ -49,54 +49,54 @@ function shouldBehaveLikeERC20Burnable(owner, initialBalance, [burner]) { describe('burnFrom', function () { describe('on success', function () { - context('for a zero amount', function () { + context('for a zero value', function () { shouldBurnFrom(new BN(0)); }); - context('for a non-zero amount', function () { + context('for a non-zero value', function () { shouldBurnFrom(new BN(100)); }); - function shouldBurnFrom(amount) { - const originalAllowance = amount.muln(3); + function shouldBurnFrom(value) { + const originalAllowance = value.muln(3); beforeEach(async function () { await this.token.approve(burner, originalAllowance, { from: owner }); - this.receipt = await this.token.burnFrom(owner, amount, { from: burner }); + this.receipt = await this.token.burnFrom(owner, value, { from: burner }); }); - it('burns the requested amount', async function () { - expect(await this.token.balanceOf(owner)).to.be.bignumber.equal(initialBalance.sub(amount)); + it('burns the requested value', async function () { + expect(await this.token.balanceOf(owner)).to.be.bignumber.equal(initialBalance.sub(value)); }); it('decrements allowance', async function () { - expect(await this.token.allowance(owner, burner)).to.be.bignumber.equal(originalAllowance.sub(amount)); + expect(await this.token.allowance(owner, burner)).to.be.bignumber.equal(originalAllowance.sub(value)); }); it('emits a transfer event', async function () { expectEvent(this.receipt, 'Transfer', { from: owner, to: ZERO_ADDRESS, - value: amount, + value: value, }); }); } }); - describe('when the given amount is greater than the balance of the sender', function () { - const amount = initialBalance.addn(1); + describe('when the given value is greater than the balance of the sender', function () { + const value = initialBalance.addn(1); it('reverts', async function () { - await this.token.approve(burner, amount, { from: owner }); - await expectRevertCustomError( - this.token.burnFrom(owner, amount, { from: burner }), - 'ERC20InsufficientBalance', - [owner, initialBalance, amount], - ); + await this.token.approve(burner, value, { from: owner }); + await expectRevertCustomError(this.token.burnFrom(owner, value, { from: burner }), 'ERC20InsufficientBalance', [ + owner, + initialBalance, + value, + ]); }); }); - describe('when the given amount is greater than the allowance', function () { + describe('when the given value is greater than the allowance', function () { const allowance = new BN(100); it('reverts', async function () { diff --git a/test/token/ERC20/extensions/ERC20Capped.behavior.js b/test/token/ERC20/extensions/ERC20Capped.behavior.js index c40e4fcc4..5af5c3ddc 100644 --- a/test/token/ERC20/extensions/ERC20Capped.behavior.js +++ b/test/token/ERC20/extensions/ERC20Capped.behavior.js @@ -9,12 +9,12 @@ function shouldBehaveLikeERC20Capped(accounts, cap) { expect(await this.token.cap()).to.be.bignumber.equal(cap); }); - it('mints when amount is less than cap', async function () { + it('mints when value is less than cap', async function () { await this.token.$_mint(user, cap.subn(1)); expect(await this.token.totalSupply()).to.be.bignumber.equal(cap.subn(1)); }); - it('fails to mint if the amount exceeds the cap', async function () { + it('fails to mint if the value exceeds the cap', async function () { await this.token.$_mint(user, cap.subn(1)); await expectRevertCustomError(this.token.$_mint(user, 2), 'ERC20ExceededCap', [cap.addn(1), cap]); }); diff --git a/test/token/ERC20/extensions/ERC20FlashMint.test.js b/test/token/ERC20/extensions/ERC20FlashMint.test.js index a646704e2..13d5b3ef4 100644 --- a/test/token/ERC20/extensions/ERC20FlashMint.test.js +++ b/test/token/ERC20/extensions/ERC20FlashMint.test.js @@ -15,7 +15,7 @@ contract('ERC20FlashMint', function (accounts) { const symbol = 'MTKN'; const initialSupply = new BN(100); - const loanAmount = new BN(10000000000000); + const loanValue = new BN(10000000000000); beforeEach(async function () { this.token = await ERC20FlashMintMock.new(name, symbol); @@ -34,11 +34,11 @@ contract('ERC20FlashMint', function (accounts) { describe('flashFee', function () { it('token match', async function () { - expect(await this.token.flashFee(this.token.address, loanAmount)).to.be.bignumber.equal('0'); + expect(await this.token.flashFee(this.token.address, loanValue)).to.be.bignumber.equal('0'); }); it('token mismatch', async function () { - await expectRevertCustomError(this.token.flashFee(ZERO_ADDRESS, loanAmount), 'ERC3156UnsupportedToken', [ + await expectRevertCustomError(this.token.flashFee(ZERO_ADDRESS, loanValue), 'ERC3156UnsupportedToken', [ ZERO_ADDRESS, ]); }); @@ -53,26 +53,26 @@ contract('ERC20FlashMint', function (accounts) { describe('flashLoan', function () { it('success', async function () { const receiver = await ERC3156FlashBorrowerMock.new(true, true); - const { tx } = await this.token.flashLoan(receiver.address, this.token.address, loanAmount, '0x'); + const { tx } = await this.token.flashLoan(receiver.address, this.token.address, loanValue, '0x'); await expectEvent.inTransaction(tx, this.token, 'Transfer', { from: ZERO_ADDRESS, to: receiver.address, - value: loanAmount, + value: loanValue, }); await expectEvent.inTransaction(tx, this.token, 'Transfer', { from: receiver.address, to: ZERO_ADDRESS, - value: loanAmount, + value: loanValue, }); await expectEvent.inTransaction(tx, receiver, 'BalanceOf', { token: this.token.address, account: receiver.address, - value: loanAmount, + value: loanValue, }); await expectEvent.inTransaction(tx, receiver, 'TotalSupply', { token: this.token.address, - value: initialSupply.add(loanAmount), + value: initialSupply.add(loanValue), }); expect(await this.token.totalSupply()).to.be.bignumber.equal(initialSupply); @@ -83,7 +83,7 @@ contract('ERC20FlashMint', function (accounts) { it('missing return value', async function () { const receiver = await ERC3156FlashBorrowerMock.new(false, true); await expectRevertCustomError( - this.token.flashLoan(receiver.address, this.token.address, loanAmount, '0x'), + this.token.flashLoan(receiver.address, this.token.address, loanValue, '0x'), 'ERC3156InvalidReceiver', [receiver.address], ); @@ -92,9 +92,9 @@ contract('ERC20FlashMint', function (accounts) { it('missing approval', async function () { const receiver = await ERC3156FlashBorrowerMock.new(true, false); await expectRevertCustomError( - this.token.flashLoan(receiver.address, this.token.address, loanAmount, '0x'), + this.token.flashLoan(receiver.address, this.token.address, loanValue, '0x'), 'ERC20InsufficientAllowance', - [this.token.address, 0, loanAmount], + [this.token.address, 0, loanValue], ); }); @@ -102,9 +102,9 @@ contract('ERC20FlashMint', function (accounts) { const receiver = await ERC3156FlashBorrowerMock.new(true, true); const data = this.token.contract.methods.transfer(other, 10).encodeABI(); await expectRevertCustomError( - this.token.flashLoan(receiver.address, this.token.address, loanAmount, data), + this.token.flashLoan(receiver.address, this.token.address, loanValue, data), 'ERC20InsufficientBalance', - [receiver.address, loanAmount - 10, loanAmount], + [receiver.address, loanValue - 10, loanValue], ); }); @@ -130,29 +130,29 @@ contract('ERC20FlashMint', function (accounts) { expect(await this.token.balanceOf(this.receiver.address)).to.be.bignumber.equal(receiverInitialBalance); await this.token.setFlashFee(flashFee); - expect(await this.token.flashFee(this.token.address, loanAmount)).to.be.bignumber.equal(flashFee); + expect(await this.token.flashFee(this.token.address, loanValue)).to.be.bignumber.equal(flashFee); }); it('default flash fee receiver', async function () { - const { tx } = await this.token.flashLoan(this.receiver.address, this.token.address, loanAmount, '0x'); + const { tx } = await this.token.flashLoan(this.receiver.address, this.token.address, loanValue, '0x'); await expectEvent.inTransaction(tx, this.token, 'Transfer', { from: ZERO_ADDRESS, to: this.receiver.address, - value: loanAmount, + value: loanValue, }); await expectEvent.inTransaction(tx, this.token, 'Transfer', { from: this.receiver.address, to: ZERO_ADDRESS, - value: loanAmount.add(flashFee), + value: loanValue.add(flashFee), }); await expectEvent.inTransaction(tx, this.receiver, 'BalanceOf', { token: this.token.address, account: this.receiver.address, - value: receiverInitialBalance.add(loanAmount), + value: receiverInitialBalance.add(loanValue), }); await expectEvent.inTransaction(tx, this.receiver, 'TotalSupply', { token: this.token.address, - value: initialSupply.add(receiverInitialBalance).add(loanAmount), + value: initialSupply.add(receiverInitialBalance).add(loanValue), }); expect(await this.token.totalSupply()).to.be.bignumber.equal( @@ -172,16 +172,16 @@ contract('ERC20FlashMint', function (accounts) { expect(await this.token.balanceOf(flashFeeReceiverAddress)).to.be.bignumber.equal('0'); - const { tx } = await this.token.flashLoan(this.receiver.address, this.token.address, loanAmount, '0x'); + const { tx } = await this.token.flashLoan(this.receiver.address, this.token.address, loanValue, '0x'); await expectEvent.inTransaction(tx, this.token, 'Transfer', { from: ZERO_ADDRESS, to: this.receiver.address, - value: loanAmount, + value: loanValue, }); await expectEvent.inTransaction(tx, this.token, 'Transfer', { from: this.receiver.address, to: ZERO_ADDRESS, - value: loanAmount, + value: loanValue, }); await expectEvent.inTransaction(tx, this.token, 'Transfer', { from: this.receiver.address, @@ -191,11 +191,11 @@ contract('ERC20FlashMint', function (accounts) { await expectEvent.inTransaction(tx, this.receiver, 'BalanceOf', { token: this.token.address, account: this.receiver.address, - value: receiverInitialBalance.add(loanAmount), + value: receiverInitialBalance.add(loanValue), }); await expectEvent.inTransaction(tx, this.receiver, 'TotalSupply', { token: this.token.address, - value: initialSupply.add(receiverInitialBalance).add(loanAmount), + value: initialSupply.add(receiverInitialBalance).add(loanValue), }); expect(await this.token.totalSupply()).to.be.bignumber.equal(initialSupply.add(receiverInitialBalance)); diff --git a/test/token/ERC20/extensions/ERC20Pausable.test.js b/test/token/ERC20/extensions/ERC20Pausable.test.js index 72bfc7769..92c90b9b8 100644 --- a/test/token/ERC20/extensions/ERC20Pausable.test.js +++ b/test/token/ERC20/extensions/ERC20Pausable.test.js @@ -84,52 +84,52 @@ contract('ERC20Pausable', function (accounts) { }); describe('mint', function () { - const amount = new BN('42'); + const value = new BN('42'); it('allows to mint when unpaused', async function () { - await this.token.$_mint(recipient, amount); + await this.token.$_mint(recipient, value); - expect(await this.token.balanceOf(recipient)).to.be.bignumber.equal(amount); + expect(await this.token.balanceOf(recipient)).to.be.bignumber.equal(value); }); it('allows to mint when paused and then unpaused', async function () { await this.token.$_pause(); await this.token.$_unpause(); - await this.token.$_mint(recipient, amount); + await this.token.$_mint(recipient, value); - expect(await this.token.balanceOf(recipient)).to.be.bignumber.equal(amount); + expect(await this.token.balanceOf(recipient)).to.be.bignumber.equal(value); }); it('reverts when trying to mint when paused', async function () { await this.token.$_pause(); - await expectRevertCustomError(this.token.$_mint(recipient, amount), 'EnforcedPause', []); + await expectRevertCustomError(this.token.$_mint(recipient, value), 'EnforcedPause', []); }); }); describe('burn', function () { - const amount = new BN('42'); + const value = new BN('42'); it('allows to burn when unpaused', async function () { - await this.token.$_burn(holder, amount); + await this.token.$_burn(holder, value); - expect(await this.token.balanceOf(holder)).to.be.bignumber.equal(initialSupply.sub(amount)); + expect(await this.token.balanceOf(holder)).to.be.bignumber.equal(initialSupply.sub(value)); }); it('allows to burn when paused and then unpaused', async function () { await this.token.$_pause(); await this.token.$_unpause(); - await this.token.$_burn(holder, amount); + await this.token.$_burn(holder, value); - expect(await this.token.balanceOf(holder)).to.be.bignumber.equal(initialSupply.sub(amount)); + expect(await this.token.balanceOf(holder)).to.be.bignumber.equal(initialSupply.sub(value)); }); it('reverts when trying to burn when paused', async function () { await this.token.$_pause(); - await expectRevertCustomError(this.token.$_burn(holder, amount), 'EnforcedPause', []); + await expectRevertCustomError(this.token.$_burn(holder, value), 'EnforcedPause', []); }); }); }); diff --git a/test/token/ERC20/extensions/ERC20Votes.test.js b/test/token/ERC20/extensions/ERC20Votes.test.js index 714a98adc..a6abb5f9f 100644 --- a/test/token/ERC20/extensions/ERC20Votes.test.js +++ b/test/token/ERC20/extensions/ERC20Votes.test.js @@ -10,7 +10,7 @@ const ethSigUtil = require('eth-sig-util'); const Wallet = require('ethereumjs-wallet').default; const { batchInBlock } = require('../../../helpers/txpool'); -const { getDomain, domainType, domainSeparator } = require('../../../helpers/eip712'); +const { getDomain, domainType } = require('../../../helpers/eip712'); const { clock, clockFromReceipt } = require('../../../helpers/time'); const { expectRevertCustomError } = require('../../../helpers/customError'); @@ -48,10 +48,10 @@ contract('ERC20Votes', function (accounts) { }); it('minting restriction', async function () { - const amount = new BN('2').pow(new BN('224')); - await expectRevertCustomError(this.token.$_mint(holder, amount), 'ERC20ExceededSafeSupply', [ - amount, - amount.subn(1), + const value = web3.utils.toBN(1).shln(224); + await expectRevertCustomError(this.token.$_mint(holder, value), 'ERC20ExceededSafeSupply', [ + value, + value.subn(1), ]); }); @@ -84,8 +84,8 @@ contract('ERC20Votes', function (accounts) { }); expectEvent(receipt, 'DelegateVotesChanged', { delegate: holder, - previousBalance: '0', - newBalance: supply, + previousVotes: '0', + newVotes: supply, }); expect(await this.token.delegates(holder)).to.be.equal(holder); @@ -147,8 +147,8 @@ contract('ERC20Votes', function (accounts) { }); expectEvent(receipt, 'DelegateVotesChanged', { delegate: delegatorAddress, - previousBalance: '0', - newBalance: supply, + previousVotes: '0', + newVotes: supply, }); expect(await this.token.delegates(delegatorAddress)).to.be.equal(delegatorAddress); @@ -248,13 +248,13 @@ contract('ERC20Votes', function (accounts) { }); expectEvent(receipt, 'DelegateVotesChanged', { delegate: holder, - previousBalance: supply, - newBalance: '0', + previousVotes: supply, + newVotes: '0', }); expectEvent(receipt, 'DelegateVotesChanged', { delegate: holderDelegatee, - previousBalance: '0', - newBalance: supply, + previousVotes: '0', + newVotes: supply, }); expect(await this.token.delegates(holder)).to.be.equal(holderDelegatee); @@ -290,8 +290,8 @@ contract('ERC20Votes', function (accounts) { expectEvent(receipt, 'Transfer', { from: holder, to: recipient, value: '1' }); expectEvent(receipt, 'DelegateVotesChanged', { delegate: holder, - previousBalance: supply, - newBalance: supply.subn(1), + previousVotes: supply, + newVotes: supply.subn(1), }); const { logIndex: transferLogIndex } = receipt.logs.find(({ event }) => event == 'Transfer'); @@ -310,7 +310,7 @@ contract('ERC20Votes', function (accounts) { const { receipt } = await this.token.transfer(recipient, 1, { from: holder }); expectEvent(receipt, 'Transfer', { from: holder, to: recipient, value: '1' }); - expectEvent(receipt, 'DelegateVotesChanged', { delegate: recipient, previousBalance: '0', newBalance: '1' }); + expectEvent(receipt, 'DelegateVotesChanged', { delegate: recipient, previousVotes: '0', newVotes: '1' }); const { logIndex: transferLogIndex } = receipt.logs.find(({ event }) => event == 'Transfer'); expect( @@ -331,10 +331,10 @@ contract('ERC20Votes', function (accounts) { expectEvent(receipt, 'Transfer', { from: holder, to: recipient, value: '1' }); expectEvent(receipt, 'DelegateVotesChanged', { delegate: holder, - previousBalance: supply, - newBalance: supply.subn(1), + previousVotes: supply, + newVotes: supply.subn(1), }); - expectEvent(receipt, 'DelegateVotesChanged', { delegate: recipient, previousBalance: '0', newBalance: '1' }); + expectEvent(receipt, 'DelegateVotesChanged', { delegate: recipient, previousVotes: '0', newVotes: '1' }); const { logIndex: transferLogIndex } = receipt.logs.find(({ event }) => event == 'Transfer'); expect( diff --git a/test/token/ERC20/extensions/ERC20Wrapper.test.js b/test/token/ERC20/extensions/ERC20Wrapper.test.js index 94415d088..c54a9e007 100644 --- a/test/token/ERC20/extensions/ERC20Wrapper.test.js +++ b/test/token/ERC20/extensions/ERC20Wrapper.test.js @@ -97,6 +97,15 @@ contract('ERC20Wrapper', function (accounts) { value: initialSupply, }); }); + + it('reverts minting to the wrapper contract', async function () { + await this.underlying.approve(this.token.address, MAX_UINT256, { from: initialHolder }); + await expectRevertCustomError( + this.token.depositFor(this.token.address, MAX_UINT256, { from: initialHolder }), + 'ERC20InvalidReceiver', + [this.token.address], + ); + }); }); describe('withdraw', function () { @@ -156,6 +165,14 @@ contract('ERC20Wrapper', function (accounts) { value: initialSupply, }); }); + + it('reverts withdrawing to the wrapper contract', async function () { + expectRevertCustomError( + this.token.withdrawTo(this.token.address, initialSupply, { from: initialHolder }), + 'ERC20InvalidReceiver', + [this.token.address], + ); + }); }); describe('recover', function () { diff --git a/test/token/ERC20/extensions/ERC4626.test.js b/test/token/ERC20/extensions/ERC4626.test.js index 99d6009e4..9dfc4c3b8 100644 --- a/test/token/ERC20/extensions/ERC4626.test.js +++ b/test/token/ERC20/extensions/ERC4626.test.js @@ -52,8 +52,8 @@ contract('ERC4626', function (accounts) { describe('reentrancy', async function () { const reenterType = Enum('No', 'Before', 'After'); - const amount = web3.utils.toBN(1000000000000000000); - const reenterAmount = web3.utils.toBN(1000000000); + const value = web3.utils.toBN(1000000000000000000); + const reenterValue = web3.utils.toBN(1000000000); let token; let vault; @@ -62,8 +62,8 @@ contract('ERC4626', function (accounts) { // Use offset 1 so the rate is not 1:1 and we can't possibly confuse assets and shares vault = await ERC4626OffsetMock.new('', '', token.address, 1); // Funds and approval for tests - await token.$_mint(holder, amount); - await token.$_mint(other, amount); + await token.$_mint(holder, value); + await token.$_mint(other, value); await token.$_approve(holder, vault.address, constants.MAX_UINT256); await token.$_approve(other, vault.address, constants.MAX_UINT256); await token.$_approve(token.address, vault.address, constants.MAX_UINT256); @@ -75,39 +75,39 @@ contract('ERC4626', function (accounts) { // intermediate state in which the ratio of assets/shares has been decreased (more shares than assets). it('correct share price is observed during reentrancy before deposit', async function () { // mint token for deposit - await token.$_mint(token.address, reenterAmount); + await token.$_mint(token.address, reenterValue); // Schedules a reentrancy from the token contract await token.scheduleReenter( reenterType.Before, vault.address, - vault.contract.methods.deposit(reenterAmount, holder).encodeABI(), + vault.contract.methods.deposit(reenterValue, holder).encodeABI(), ); // Initial share price - const sharesForDeposit = await vault.previewDeposit(amount, { from: holder }); - const sharesForReenter = await vault.previewDeposit(reenterAmount, { from: holder }); + const sharesForDeposit = await vault.previewDeposit(value, { from: holder }); + const sharesForReenter = await vault.previewDeposit(reenterValue, { from: holder }); // Do deposit normally, triggering the _beforeTokenTransfer hook - const receipt = await vault.deposit(amount, holder, { from: holder }); + const receipt = await vault.deposit(value, holder, { from: holder }); // Main deposit event await expectEvent(receipt, 'Deposit', { sender: holder, owner: holder, - assets: amount, + assets: value, shares: sharesForDeposit, }); // Reentrant deposit event → uses the same price await expectEvent(receipt, 'Deposit', { sender: token.address, owner: holder, - assets: reenterAmount, + assets: reenterValue, shares: sharesForReenter, }); // Assert prices is kept - const sharesAfter = await vault.previewDeposit(amount, { from: holder }); + const sharesAfter = await vault.previewDeposit(value, { from: holder }); expect(sharesForDeposit).to.be.bignumber.eq(sharesAfter); }); @@ -116,30 +116,30 @@ contract('ERC4626', function (accounts) { // If the order of burn -> transfer is changed to transfer -> burn, the reentrancy could be triggered on an // intermediate state in which the ratio of shares/assets has been decreased (more assets than shares). it('correct share price is observed during reentrancy after withdraw', async function () { - // Deposit into the vault: holder gets `amount` share, token.address gets `reenterAmount` shares - await vault.deposit(amount, holder, { from: holder }); - await vault.deposit(reenterAmount, token.address, { from: other }); + // Deposit into the vault: holder gets `value` share, token.address gets `reenterValue` shares + await vault.deposit(value, holder, { from: holder }); + await vault.deposit(reenterValue, token.address, { from: other }); // Schedules a reentrancy from the token contract await token.scheduleReenter( reenterType.After, vault.address, - vault.contract.methods.withdraw(reenterAmount, holder, token.address).encodeABI(), + vault.contract.methods.withdraw(reenterValue, holder, token.address).encodeABI(), ); // Initial share price - const sharesForWithdraw = await vault.previewWithdraw(amount, { from: holder }); - const sharesForReenter = await vault.previewWithdraw(reenterAmount, { from: holder }); + const sharesForWithdraw = await vault.previewWithdraw(value, { from: holder }); + const sharesForReenter = await vault.previewWithdraw(reenterValue, { from: holder }); // Do withdraw normally, triggering the _afterTokenTransfer hook - const receipt = await vault.withdraw(amount, holder, holder, { from: holder }); + const receipt = await vault.withdraw(value, holder, holder, { from: holder }); // Main withdraw event await expectEvent(receipt, 'Withdraw', { sender: holder, receiver: holder, owner: holder, - assets: amount, + assets: value, shares: sharesForWithdraw, }); // Reentrant withdraw event → uses the same price @@ -147,76 +147,76 @@ contract('ERC4626', function (accounts) { sender: token.address, receiver: holder, owner: token.address, - assets: reenterAmount, + assets: reenterValue, shares: sharesForReenter, }); // Assert price is kept - const sharesAfter = await vault.previewWithdraw(amount, { from: holder }); + const sharesAfter = await vault.previewWithdraw(value, { from: holder }); expect(sharesForWithdraw).to.be.bignumber.eq(sharesAfter); }); // Donate newly minted tokens to the vault during the reentracy causes the share price to increase. // Still, the deposit that trigger the reentracy is not affected and get the previewed price. - // Further deposits will get a different price (getting fewer shares for the same amount of assets) + // Further deposits will get a different price (getting fewer shares for the same value of assets) it('share price change during reentracy does not affect deposit', async function () { // Schedules a reentrancy from the token contract that mess up the share price await token.scheduleReenter( reenterType.Before, token.address, - token.contract.methods.$_mint(vault.address, reenterAmount).encodeABI(), + token.contract.methods.$_mint(vault.address, reenterValue).encodeABI(), ); // Price before - const sharesBefore = await vault.previewDeposit(amount); + const sharesBefore = await vault.previewDeposit(value); // Deposit, triggering the _beforeTokenTransfer hook - const receipt = await vault.deposit(amount, holder, { from: holder }); + const receipt = await vault.deposit(value, holder, { from: holder }); // Price is as previewed await expectEvent(receipt, 'Deposit', { sender: holder, owner: holder, - assets: amount, + assets: value, shares: sharesBefore, }); // Price was modified during reentrancy - const sharesAfter = await vault.previewDeposit(amount); + const sharesAfter = await vault.previewDeposit(value); expect(sharesAfter).to.be.bignumber.lt(sharesBefore); }); // Burn some tokens from the vault during the reentracy causes the share price to drop. // Still, the withdraw that trigger the reentracy is not affected and get the previewed price. - // Further withdraw will get a different price (needing more shares for the same amount of assets) + // Further withdraw will get a different price (needing more shares for the same value of assets) it('share price change during reentracy does not affect withdraw', async function () { - await vault.deposit(amount, other, { from: other }); - await vault.deposit(amount, holder, { from: holder }); + await vault.deposit(value, other, { from: other }); + await vault.deposit(value, holder, { from: holder }); // Schedules a reentrancy from the token contract that mess up the share price await token.scheduleReenter( reenterType.After, token.address, - token.contract.methods.$_burn(vault.address, reenterAmount).encodeABI(), + token.contract.methods.$_burn(vault.address, reenterValue).encodeABI(), ); // Price before - const sharesBefore = await vault.previewWithdraw(amount); + const sharesBefore = await vault.previewWithdraw(value); // Withdraw, triggering the _afterTokenTransfer hook - const receipt = await vault.withdraw(amount, holder, holder, { from: holder }); + const receipt = await vault.withdraw(value, holder, holder, { from: holder }); // Price is as previewed await expectEvent(receipt, 'Withdraw', { sender: holder, receiver: holder, owner: holder, - assets: amount, + assets: value, shares: sharesBefore, }); // Price was modified during reentrancy - const sharesAfter = await vault.previewWithdraw(amount); + const sharesAfter = await vault.previewWithdraw(value); expect(sharesAfter).to.be.bignumber.gt(sharesBefore); }); }); @@ -738,9 +738,9 @@ contract('ERC4626', function (accounts) { describe('ERC4626Fees', function () { const feeBasePoint = web3.utils.toBN(5e3); - const amountWithoutFees = web3.utils.toBN(10000); - const fees = amountWithoutFees.mul(feeBasePoint).divn(1e5); - const amountWithFees = amountWithoutFees.add(fees); + const valueWithoutFees = web3.utils.toBN(10000); + const fees = valueWithoutFees.mul(feeBasePoint).divn(1e5); + const valueWithFees = valueWithoutFees.add(fees); describe('input fees', function () { beforeEach(async function () { @@ -760,13 +760,13 @@ contract('ERC4626', function (accounts) { }); it('deposit', async function () { - expect(await this.vault.previewDeposit(amountWithFees)).to.be.bignumber.equal(amountWithoutFees); - ({ tx: this.tx } = await this.vault.deposit(amountWithFees, recipient, { from: holder })); + expect(await this.vault.previewDeposit(valueWithFees)).to.be.bignumber.equal(valueWithoutFees); + ({ tx: this.tx } = await this.vault.deposit(valueWithFees, recipient, { from: holder })); }); it('mint', async function () { - expect(await this.vault.previewMint(amountWithoutFees)).to.be.bignumber.equal(amountWithFees); - ({ tx: this.tx } = await this.vault.mint(amountWithoutFees, recipient, { from: holder })); + expect(await this.vault.previewMint(valueWithoutFees)).to.be.bignumber.equal(valueWithFees); + ({ tx: this.tx } = await this.vault.mint(valueWithoutFees, recipient, { from: holder })); }); afterEach(async function () { @@ -774,7 +774,7 @@ contract('ERC4626', function (accounts) { await expectEvent.inTransaction(this.tx, this.token, 'Transfer', { from: holder, to: this.vault.address, - value: amountWithFees, + value: valueWithFees, }); // redirect fees @@ -788,15 +788,15 @@ contract('ERC4626', function (accounts) { await expectEvent.inTransaction(this.tx, this.vault, 'Transfer', { from: constants.ZERO_ADDRESS, to: recipient, - value: amountWithoutFees, + value: valueWithoutFees, }); // deposit event await expectEvent.inTransaction(this.tx, this.vault, 'Deposit', { sender: holder, owner: recipient, - assets: amountWithFees, - shares: amountWithoutFees, + assets: valueWithFees, + shares: valueWithoutFees, }); }); }); @@ -819,13 +819,13 @@ contract('ERC4626', function (accounts) { }); it('redeem', async function () { - expect(await this.vault.previewRedeem(amountWithFees)).to.be.bignumber.equal(amountWithoutFees); - ({ tx: this.tx } = await this.vault.redeem(amountWithFees, recipient, holder, { from: holder })); + expect(await this.vault.previewRedeem(valueWithFees)).to.be.bignumber.equal(valueWithoutFees); + ({ tx: this.tx } = await this.vault.redeem(valueWithFees, recipient, holder, { from: holder })); }); it('withdraw', async function () { - expect(await this.vault.previewWithdraw(amountWithoutFees)).to.be.bignumber.equal(amountWithFees); - ({ tx: this.tx } = await this.vault.withdraw(amountWithoutFees, recipient, holder, { from: holder })); + expect(await this.vault.previewWithdraw(valueWithoutFees)).to.be.bignumber.equal(valueWithFees); + ({ tx: this.tx } = await this.vault.withdraw(valueWithoutFees, recipient, holder, { from: holder })); }); afterEach(async function () { @@ -833,7 +833,7 @@ contract('ERC4626', function (accounts) { await expectEvent.inTransaction(this.tx, this.token, 'Transfer', { from: this.vault.address, to: recipient, - value: amountWithoutFees, + value: valueWithoutFees, }); // redirect fees @@ -847,7 +847,7 @@ contract('ERC4626', function (accounts) { await expectEvent.inTransaction(this.tx, this.vault, 'Transfer', { from: holder, to: constants.ZERO_ADDRESS, - value: amountWithFees, + value: valueWithFees, }); // withdraw event @@ -855,8 +855,8 @@ contract('ERC4626', function (accounts) { sender: holder, receiver: recipient, owner: holder, - assets: amountWithoutFees, - shares: amountWithFees, + assets: valueWithoutFees, + shares: valueWithFees, }); }); }); diff --git a/test/token/ERC721/extensions/ERC721Votes.test.js b/test/token/ERC721/extensions/ERC721Votes.test.js index caa44ea8b..45020baf9 100644 --- a/test/token/ERC721/extensions/ERC721Votes.test.js +++ b/test/token/ERC721/extensions/ERC721Votes.test.js @@ -61,7 +61,7 @@ contract('ERC721Votes', function (accounts) { const { receipt } = await this.votes.transferFrom(account1, account2, tokens[0], { from: account1 }); expectEvent(receipt, 'Transfer', { from: account1, to: account2, tokenId: tokens[0] }); - expectEvent(receipt, 'DelegateVotesChanged', { delegate: account1, previousBalance: '1', newBalance: '0' }); + expectEvent(receipt, 'DelegateVotesChanged', { delegate: account1, previousVotes: '1', newVotes: '0' }); const { logIndex: transferLogIndex } = receipt.logs.find(({ event }) => event == 'Transfer'); expect( @@ -79,7 +79,7 @@ contract('ERC721Votes', function (accounts) { const { receipt } = await this.votes.transferFrom(account1, account2, tokens[0], { from: account1 }); expectEvent(receipt, 'Transfer', { from: account1, to: account2, tokenId: tokens[0] }); - expectEvent(receipt, 'DelegateVotesChanged', { delegate: account2, previousBalance: '0', newBalance: '1' }); + expectEvent(receipt, 'DelegateVotesChanged', { delegate: account2, previousVotes: '0', newVotes: '1' }); const { logIndex: transferLogIndex } = receipt.logs.find(({ event }) => event == 'Transfer'); expect( @@ -98,8 +98,8 @@ contract('ERC721Votes', function (accounts) { const { receipt } = await this.votes.transferFrom(account1, account2, tokens[0], { from: account1 }); expectEvent(receipt, 'Transfer', { from: account1, to: account2, tokenId: tokens[0] }); - expectEvent(receipt, 'DelegateVotesChanged', { delegate: account1, previousBalance: '1', newBalance: '0' }); - expectEvent(receipt, 'DelegateVotesChanged', { delegate: account2, previousBalance: '0', newBalance: '1' }); + expectEvent(receipt, 'DelegateVotesChanged', { delegate: account1, previousVotes: '1', newVotes: '0' }); + expectEvent(receipt, 'DelegateVotesChanged', { delegate: account2, previousVotes: '0', newVotes: '1' }); const { logIndex: transferLogIndex } = receipt.logs.find(({ event }) => event == 'Transfer'); expect( From 6e214227376acd688e60fcdca8cb7f0ae8cef98f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Mon, 3 Jul 2023 14:29:30 -0600 Subject: [PATCH 135/182] Add Governor signature nonces (#4378) Co-authored-by: Hadrien Croubois Co-authored-by: Francisco Co-authored-by: Sergei Tikhomirov Co-authored-by: Renan Souza --- .changeset/sixty-numbers-reply.md | 5 + contracts/governance/Governor.sol | 33 +++-- contracts/governance/IGovernor.sol | 7 + hardhat.config.js | 1 + package-lock.json | 12 +- test/governance/Governor.test.js | 101 +++++++++++++- .../extensions/GovernorWithParams.test.js | 124 +++++++++++++----- test/helpers/governance.js | 46 ++++--- .../SupportsInterface.behavior.js | 6 +- 9 files changed, 263 insertions(+), 72 deletions(-) create mode 100644 .changeset/sixty-numbers-reply.md diff --git a/.changeset/sixty-numbers-reply.md b/.changeset/sixty-numbers-reply.md new file mode 100644 index 000000000..4e6faa837 --- /dev/null +++ b/.changeset/sixty-numbers-reply.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': major +--- + +`Governor`: Add `voter` and `nonce` parameters in signed ballots, to avoid forging signatures for random addresses, prevent signature replay, and allow invalidating signatures. Add `voter` as a new parameter in the `castVoteBySig` and `castVoteWithReasonAndParamsBySig` functions. diff --git a/contracts/governance/Governor.sol b/contracts/governance/Governor.sol index d0ace4f03..1e1526ceb 100644 --- a/contracts/governance/Governor.sol +++ b/contracts/governance/Governor.sol @@ -12,6 +12,7 @@ import {SafeCast} from "../utils/math/SafeCast.sol"; import {DoubleEndedQueue} from "../utils/structs/DoubleEndedQueue.sol"; import {Address} from "../utils/Address.sol"; import {Context} from "../utils/Context.sol"; +import {Nonces} from "../utils/Nonces.sol"; import {IGovernor, IERC6372} from "./IGovernor.sol"; /** @@ -25,12 +26,15 @@ import {IGovernor, IERC6372} from "./IGovernor.sol"; * * _Available since v4.3._ */ -abstract contract Governor is Context, ERC165, EIP712, IGovernor, IERC721Receiver, IERC1155Receiver { +abstract contract Governor is Context, ERC165, EIP712, Nonces, IGovernor, IERC721Receiver, IERC1155Receiver { using DoubleEndedQueue for DoubleEndedQueue.Bytes32Deque; - bytes32 public constant BALLOT_TYPEHASH = keccak256("Ballot(uint256 proposalId,uint8 support)"); + bytes32 public constant BALLOT_TYPEHASH = + keccak256("Ballot(uint256 proposalId,uint8 support,address voter,uint256 nonce)"); bytes32 public constant EXTENDED_BALLOT_TYPEHASH = - keccak256("ExtendedBallot(uint256 proposalId,uint8 support,string reason,bytes params)"); + keccak256( + "ExtendedBallot(uint256 proposalId,uint8 support,address voter,uint256 nonce,string reason,bytes params)" + ); // solhint-disable var-name-mixedcase struct ProposalCore { @@ -514,17 +518,23 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor, IERC721Receive function castVoteBySig( uint256 proposalId, uint8 support, + address voter, uint8 v, bytes32 r, bytes32 s ) public virtual override returns (uint256) { - address voter = ECDSA.recover( - _hashTypedDataV4(keccak256(abi.encode(BALLOT_TYPEHASH, proposalId, support))), + address signer = ECDSA.recover( + _hashTypedDataV4(keccak256(abi.encode(BALLOT_TYPEHASH, proposalId, support, voter, _useNonce(voter)))), v, r, s ); - return _castVote(proposalId, voter, support, ""); + + if (voter != signer) { + revert GovernorInvalidSigner(signer, voter); + } + + return _castVote(proposalId, signer, support, ""); } /** @@ -533,19 +543,22 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor, IERC721Receive function castVoteWithReasonAndParamsBySig( uint256 proposalId, uint8 support, + address voter, string calldata reason, bytes memory params, uint8 v, bytes32 r, bytes32 s ) public virtual override returns (uint256) { - address voter = ECDSA.recover( + address signer = ECDSA.recover( _hashTypedDataV4( keccak256( abi.encode( EXTENDED_BALLOT_TYPEHASH, proposalId, support, + voter, + _useNonce(voter), keccak256(bytes(reason)), keccak256(params) ) @@ -556,7 +569,11 @@ abstract contract Governor is Context, ERC165, EIP712, IGovernor, IERC721Receive s ); - return _castVote(proposalId, voter, support, reason, params); + if (voter != signer) { + revert GovernorInvalidSigner(signer, voter); + } + + return _castVote(proposalId, signer, support, reason, params); } /** diff --git a/contracts/governance/IGovernor.sol b/contracts/governance/IGovernor.sol index 2eeedd313..20636e3ba 100644 --- a/contracts/governance/IGovernor.sol +++ b/contracts/governance/IGovernor.sol @@ -80,6 +80,11 @@ abstract contract IGovernor is IERC165, IERC6372 { */ error GovernorInvalidVoteType(); + /** + * @dev The `voter` doesn't match with the recovered `signer`. + */ + error GovernorInvalidSigner(address signer, address voter); + /** * @dev Emitted when a proposal is created. */ @@ -355,6 +360,7 @@ abstract contract IGovernor is IERC165, IERC6372 { function castVoteBySig( uint256 proposalId, uint8 support, + address voter, uint8 v, bytes32 r, bytes32 s @@ -368,6 +374,7 @@ abstract contract IGovernor is IERC165, IERC6372 { function castVoteWithReasonAndParamsBySig( uint256 proposalId, uint8 support, + address voter, string calldata reason, bytes memory params, uint8 v, diff --git a/hardhat.config.js b/hardhat.config.js index 6cb8b9144..8ee5d05e5 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -78,6 +78,7 @@ module.exports = { warnings: { 'contracts-exposed/**/*': { 'code-size': 'off', + 'initcode-size': 'off', }, '*': { 'code-size': withOptimizations, diff --git a/package-lock.json b/package-lock.json index a85611941..2e6296d24 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7504,9 +7504,9 @@ } }, "node_modules/hardhat-ignore-warnings": { - "version": "0.2.8", - "resolved": "https://registry.npmjs.org/hardhat-ignore-warnings/-/hardhat-ignore-warnings-0.2.8.tgz", - "integrity": "sha512-vPX94rJyTzYsCOzGIYdOcJgn3iQI6qa+CI9ZZfgDhdXJpda8ljpOT7bdUKAYC4LyoP0Z5fWTmupXoPaQrty0gw==", + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/hardhat-ignore-warnings/-/hardhat-ignore-warnings-0.2.9.tgz", + "integrity": "sha512-q1oj6/ixiAx+lgIyGLBajVCSC7qUtAoK7LS9Nr8UVHYo8Iuh5naBiVGo4RDJ6wxbDGYBkeSukUGZrMqzC2DWwA==", "dev": true, "dependencies": { "minimatch": "^5.1.0", @@ -21441,9 +21441,9 @@ } }, "hardhat-ignore-warnings": { - "version": "0.2.8", - "resolved": "https://registry.npmjs.org/hardhat-ignore-warnings/-/hardhat-ignore-warnings-0.2.8.tgz", - "integrity": "sha512-vPX94rJyTzYsCOzGIYdOcJgn3iQI6qa+CI9ZZfgDhdXJpda8ljpOT7bdUKAYC4LyoP0Z5fWTmupXoPaQrty0gw==", + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/hardhat-ignore-warnings/-/hardhat-ignore-warnings-0.2.9.tgz", + "integrity": "sha512-q1oj6/ixiAx+lgIyGLBajVCSC7qUtAoK7LS9Nr8UVHYo8Iuh5naBiVGo4RDJ6wxbDGYBkeSukUGZrMqzC2DWwA==", "dev": true, "requires": { "minimatch": "^5.1.0", diff --git a/test/governance/Governor.test.js b/test/governance/Governor.test.js index 909c38686..f4a352c6d 100644 --- a/test/governance/Governor.test.js +++ b/test/governance/Governor.test.js @@ -2,7 +2,7 @@ const { constants, expectEvent, expectRevert } = require('@openzeppelin/test-hel const { expect } = require('chai'); const ethSigUtil = require('eth-sig-util'); const Wallet = require('ethereumjs-wallet').default; -const { fromRpcSig } = require('ethereumjs-util'); +const { fromRpcSig, toRpcSig } = require('ethereumjs-util'); const Enums = require('../helpers/enums'); const { getDomain, domainType } = require('../helpers/eip712'); @@ -166,7 +166,7 @@ contract('Governor', function (accounts) { expect(await web3.eth.getBalance(this.receiver.address)).to.be.bignumber.equal(value); }); - it('vote with signature', async function () { + it('votes with signature', async function () { const voterBySig = Wallet.generate(); const voterBySigAddress = web3.utils.toChecksumAddress(voterBySig.getAddressString()); @@ -179,6 +179,8 @@ contract('Governor', function (accounts) { Ballot: [ { name: 'proposalId', type: 'uint256' }, { name: 'support', type: 'uint8' }, + { name: 'voter', type: 'address' }, + { name: 'nonce', type: 'uint256' }, ], }, domain, @@ -189,13 +191,19 @@ contract('Governor', function (accounts) { await this.token.delegate(voterBySigAddress, { from: voter1 }); + const nonce = await this.mock.nonces(voterBySigAddress); + // Run proposal await this.helper.propose(); await this.helper.waitForSnapshot(); - expectEvent(await this.helper.vote({ support: Enums.VoteType.For, signature }), 'VoteCast', { - voter: voterBySigAddress, - support: Enums.VoteType.For, - }); + expectEvent( + await this.helper.vote({ support: Enums.VoteType.For, voter: voterBySigAddress, nonce, signature }), + 'VoteCast', + { + voter: voterBySigAddress, + support: Enums.VoteType.For, + }, + ); await this.helper.waitForDeadline(); await this.helper.execute(); @@ -204,6 +212,7 @@ contract('Governor', function (accounts) { expect(await this.mock.hasVoted(this.proposal.id, voter1)).to.be.equal(false); expect(await this.mock.hasVoted(this.proposal.id, voter2)).to.be.equal(false); expect(await this.mock.hasVoted(this.proposal.id, voterBySigAddress)).to.be.equal(true); + expect(await this.mock.nonces(voterBySigAddress)).to.be.bignumber.equal(nonce.addn(1)); }); it('send ethers', async function () { @@ -297,6 +306,86 @@ contract('Governor', function (accounts) { }); }); + describe('on vote by signature', function () { + beforeEach(async function () { + this.voterBySig = Wallet.generate(); + this.voterBySig.address = web3.utils.toChecksumAddress(this.voterBySig.getAddressString()); + + this.data = (contract, message) => + getDomain(contract).then(domain => ({ + primaryType: 'Ballot', + types: { + EIP712Domain: domainType(domain), + Ballot: [ + { name: 'proposalId', type: 'uint256' }, + { name: 'support', type: 'uint8' }, + { name: 'voter', type: 'address' }, + { name: 'nonce', type: 'uint256' }, + ], + }, + domain, + message, + })); + + this.signature = (contract, message) => + this.data(contract, message) + .then(data => ethSigUtil.signTypedMessage(this.voterBySig.getPrivateKey(), { data })) + .then(fromRpcSig); + + await this.token.delegate(this.voterBySig.address, { from: voter1 }); + + // Run proposal + await this.helper.propose(); + await this.helper.waitForSnapshot(); + }); + + it('if signature does not match signer', async function () { + const nonce = await this.mock.nonces(this.voterBySig.address); + + const voteParams = { + support: Enums.VoteType.For, + voter: this.voterBySig.address, + nonce, + signature: async (...params) => { + const sig = await this.signature(...params); + sig.s[12] ^= 0xff; + return sig; + }, + }; + + const { r, s, v } = await this.helper.sign(voteParams); + const message = this.helper.forgeMessage(voteParams); + const data = await this.data(this.mock, message); + + await expectRevertCustomError(this.helper.vote(voteParams), 'GovernorInvalidSigner', [ + ethSigUtil.recoverTypedSignature({ sig: toRpcSig(v, r, s), data }), + voteParams.voter, + ]); + }); + + it('if vote nonce is incorrect', async function () { + const nonce = await this.mock.nonces(this.voterBySig.address); + + const voteParams = { + support: Enums.VoteType.For, + voter: this.voterBySig.address, + nonce: nonce.addn(1), + signature: this.signature, + }; + + const { r, s, v } = await this.helper.sign(voteParams); + const message = this.helper.forgeMessage(voteParams); + const data = await this.data(this.mock, { ...message, nonce }); + + await expectRevertCustomError( + this.helper.vote(voteParams), + // The signature check implies the nonce can't be tampered without changing the signer + 'GovernorInvalidSigner', + [ethSigUtil.recoverTypedSignature({ sig: toRpcSig(v, r, s), data }), voteParams.voter], + ); + }); + }); + describe('on execute', function () { it('if proposal does not exist', async function () { await expectRevertCustomError(this.helper.execute(), 'GovernorNonexistentProposal', [this.proposal.id]); diff --git a/test/governance/extensions/GovernorWithParams.test.js b/test/governance/extensions/GovernorWithParams.test.js index 00936c505..fb58edaaf 100644 --- a/test/governance/extensions/GovernorWithParams.test.js +++ b/test/governance/extensions/GovernorWithParams.test.js @@ -2,11 +2,12 @@ const { expectEvent } = require('@openzeppelin/test-helpers'); const { expect } = require('chai'); const ethSigUtil = require('eth-sig-util'); const Wallet = require('ethereumjs-wallet').default; -const { fromRpcSig } = require('ethereumjs-util'); +const { fromRpcSig, toRpcSig } = require('ethereumjs-util'); const Enums = require('../../helpers/enums'); const { getDomain, domainType } = require('../../helpers/eip712'); const { GovernorHelper } = require('../../helpers/governance'); +const { expectRevertCustomError } = require('../../helpers/customError'); const Governor = artifacts.require('$GovernorWithParamsMock'); const CallReceiver = artifacts.require('CallReceiverMock'); @@ -119,56 +120,119 @@ contract('GovernorWithParams', function (accounts) { expect(votes.forVotes).to.be.bignumber.equal(weight); }); - it('Voting with params by signature is properly supported', async function () { - const voterBySig = Wallet.generate(); - const voterBySigAddress = web3.utils.toChecksumAddress(voterBySig.getAddressString()); + describe('voting by signature', function () { + beforeEach(async function () { + this.voterBySig = Wallet.generate(); + this.voterBySig.address = web3.utils.toChecksumAddress(this.voterBySig.getAddressString()); - const signature = (contract, message) => - getDomain(contract) - .then(domain => ({ + this.data = (contract, message) => + getDomain(contract).then(domain => ({ primaryType: 'ExtendedBallot', types: { EIP712Domain: domainType(domain), ExtendedBallot: [ { name: 'proposalId', type: 'uint256' }, { name: 'support', type: 'uint8' }, + { name: 'voter', type: 'address' }, + { name: 'nonce', type: 'uint256' }, { name: 'reason', type: 'string' }, { name: 'params', type: 'bytes' }, ], }, domain, message, - })) - .then(data => ethSigUtil.signTypedMessage(voterBySig.getPrivateKey(), { data })) - .then(fromRpcSig); + })); - await this.token.delegate(voterBySigAddress, { from: voter2 }); + this.signature = (contract, message) => + this.data(contract, message) + .then(data => ethSigUtil.signTypedMessage(this.voterBySig.getPrivateKey(), { data })) + .then(fromRpcSig); - // Run proposal - await this.helper.propose(); - await this.helper.waitForSnapshot(); + await this.token.delegate(this.voterBySig.address, { from: voter2 }); - const weight = web3.utils.toBN(web3.utils.toWei('7')).sub(rawParams.uintParam); + // Run proposal + await this.helper.propose(); + await this.helper.waitForSnapshot(); + }); - const tx = await this.helper.vote({ - support: Enums.VoteType.For, - reason: 'no particular reason', - params: encodedParams, - signature, + it('is properly supported', async function () { + const weight = web3.utils.toBN(web3.utils.toWei('7')).sub(rawParams.uintParam); + + const nonce = await this.mock.nonces(this.voterBySig.address); + + const tx = await this.helper.vote({ + support: Enums.VoteType.For, + voter: this.voterBySig.address, + nonce, + reason: 'no particular reason', + params: encodedParams, + signature: this.signature, + }); + + expectEvent(tx, 'CountParams', { ...rawParams }); + expectEvent(tx, 'VoteCastWithParams', { + voter: this.voterBySig.address, + proposalId: this.proposal.id, + support: Enums.VoteType.For, + weight, + reason: 'no particular reason', + params: encodedParams, + }); + + const votes = await this.mock.proposalVotes(this.proposal.id); + expect(votes.forVotes).to.be.bignumber.equal(weight); + expect(await this.mock.nonces(this.voterBySig.address)).to.be.bignumber.equal(nonce.addn(1)); }); - expectEvent(tx, 'CountParams', { ...rawParams }); - expectEvent(tx, 'VoteCastWithParams', { - voter: voterBySigAddress, - proposalId: this.proposal.id, - support: Enums.VoteType.For, - weight, - reason: 'no particular reason', - params: encodedParams, + it('reverts if signature does not match signer', async function () { + const nonce = await this.mock.nonces(this.voterBySig.address); + + const voteParams = { + support: Enums.VoteType.For, + voter: this.voterBySig.address, + nonce, + signature: async (...params) => { + const sig = await this.signature(...params); + sig.s[12] ^= 0xff; + return sig; + }, + reason: 'no particular reason', + params: encodedParams, + }; + + const { r, s, v } = await this.helper.sign(voteParams); + const message = this.helper.forgeMessage(voteParams); + const data = await this.data(this.mock, message); + + await expectRevertCustomError(this.helper.vote(voteParams), 'GovernorInvalidSigner', [ + ethSigUtil.recoverTypedSignature({ sig: toRpcSig(v, r, s), data }), + voteParams.voter, + ]); }); - const votes = await this.mock.proposalVotes(this.proposal.id); - expect(votes.forVotes).to.be.bignumber.equal(weight); + it('reverts if vote nonce is incorrect', async function () { + const nonce = await this.mock.nonces(this.voterBySig.address); + + const voteParams = { + support: Enums.VoteType.For, + voter: this.voterBySig.address, + nonce: nonce.addn(1), + signature: this.signature, + reason: 'no particular reason', + params: encodedParams, + }; + + const { r, s, v } = await this.helper.sign(voteParams); + const message = this.helper.forgeMessage(voteParams); + const data = await this.data(this.mock, { ...message, nonce }); + + await expectRevertCustomError( + this.helper.vote(voteParams), + // The signature check implies the nonce can't be tampered without changing the signer + 'GovernorInvalidSigner', + [ethSigUtil.recoverTypedSignature({ sig: toRpcSig(v, r, s), data }), voteParams.voter], + ); + }); }); }); } diff --git a/test/helpers/governance.js b/test/helpers/governance.js index 665c21605..588aeb258 100644 --- a/test/helpers/governance.js +++ b/test/helpers/governance.js @@ -91,26 +91,17 @@ class GovernorHelper { return vote.signature ? // if signature, and either params or reason → vote.params || vote.reason - ? vote - .signature(this.governor, { - proposalId: proposal.id, - support: vote.support, - reason: vote.reason || '', - params: vote.params || '', - }) - .then(({ v, r, s }) => - this.governor.castVoteWithReasonAndParamsBySig( - ...concatOpts([proposal.id, vote.support, vote.reason || '', vote.params || '', v, r, s], opts), + ? this.sign(vote).then(({ v, r, s }) => + this.governor.castVoteWithReasonAndParamsBySig( + ...concatOpts( + [proposal.id, vote.support, vote.voter, vote.reason || '', vote.params || '', v, r, s], + opts, ), - ) - : vote - .signature(this.governor, { - proposalId: proposal.id, - support: vote.support, - }) - .then(({ v, r, s }) => - this.governor.castVoteBySig(...concatOpts([proposal.id, vote.support, v, r, s], opts)), - ) + ), + ) + : this.sign(vote).then(({ v, r, s }) => + this.governor.castVoteBySig(...concatOpts([proposal.id, vote.support, vote.voter, v, r, s], opts)), + ) : vote.params ? // otherwise if params this.governor.castVoteWithReasonAndParams( @@ -122,6 +113,23 @@ class GovernorHelper { : this.governor.castVote(...concatOpts([proposal.id, vote.support], opts)); } + sign(vote = {}) { + return vote.signature(this.governor, this.forgeMessage(vote)); + } + + forgeMessage(vote = {}) { + const proposal = this.currentProposal; + + const message = { proposalId: proposal.id, support: vote.support, voter: vote.voter, nonce: vote.nonce }; + + if (vote.params || vote.reason) { + message.reason = vote.reason || ''; + message.params = vote.params || ''; + } + + return message; + } + async waitForSnapshot(offset = 0) { const proposal = this.currentProposal; const timepoint = await this.governor.proposalSnapshot(proposal.id); diff --git a/test/utils/introspection/SupportsInterface.behavior.js b/test/utils/introspection/SupportsInterface.behavior.js index 201a55f47..71be4313f 100644 --- a/test/utils/introspection/SupportsInterface.behavior.js +++ b/test/utils/introspection/SupportsInterface.behavior.js @@ -66,7 +66,7 @@ const INTERFACES = { 'execute(address[],uint256[],bytes[],bytes32)', 'castVote(uint256,uint8)', 'castVoteWithReason(uint256,uint8,string)', - 'castVoteBySig(uint256,uint8,uint8,bytes32,bytes32)', + 'castVoteBySig(uint256,uint8,address,uint8,bytes32,bytes32)', ], GovernorWithParams: [ 'name()', @@ -87,8 +87,8 @@ const INTERFACES = { 'castVote(uint256,uint8)', 'castVoteWithReason(uint256,uint8,string)', 'castVoteWithReasonAndParams(uint256,uint8,string,bytes)', - 'castVoteBySig(uint256,uint8,uint8,bytes32,bytes32)', - 'castVoteWithReasonAndParamsBySig(uint256,uint8,string,bytes,uint8,bytes32,bytes32)', + 'castVoteBySig(uint256,uint8,address,uint8,bytes32,bytes32)', + 'castVoteWithReasonAndParamsBySig(uint256,uint8,address,string,bytes,uint8,bytes32,bytes32)', ], GovernorCancel: ['proposalProposer(uint256)', 'cancel(address[],uint256[],bytes[],bytes32)'], GovernorTimelock: ['timelock()', 'proposalEta(uint256)', 'queue(address[],uint256[],bytes[],bytes32)'], From e3adf91e505d3125a1ce9a05aae76b36ed800170 Mon Sep 17 00:00:00 2001 From: Renan Souza Date: Tue, 4 Jul 2023 15:23:44 -0300 Subject: [PATCH 136/182] Add state getter in TimelockController using OperationState enum (#4358) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Francisco Co-authored-by: Ernesto García --- .changeset/loud-shrimps-play.md | 5 ++ contracts/governance/README.adoc | 5 +- contracts/governance/TimelockController.sol | 68 +++++++++++++++---- test/governance/TimelockController.test.js | 23 ++++--- .../GovernorTimelockControl.test.js | 4 +- test/helpers/enums.js | 2 +- 6 files changed, 75 insertions(+), 32 deletions(-) create mode 100644 .changeset/loud-shrimps-play.md diff --git a/.changeset/loud-shrimps-play.md b/.changeset/loud-shrimps-play.md new file mode 100644 index 000000000..3de2da080 --- /dev/null +++ b/.changeset/loud-shrimps-play.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': minor +--- + +`TimelockController`: Add a state getter that returns an `OperationState` enum. diff --git a/contracts/governance/README.adoc b/contracts/governance/README.adoc index 00edfe23d..29c3887c7 100644 --- a/contracts/governance/README.adoc +++ b/contracts/governance/README.adoc @@ -52,8 +52,6 @@ NOTE: Functions of the `Governor` contract do not include access control. If you === Core -{{IGovernor}} - {{Governor}} === Modules @@ -92,8 +90,9 @@ In a governance system, the {TimelockController} contract is in charge of introd * *Operation:* A transaction (or a set of transactions) that is the subject of the timelock. It has to be scheduled by a proposer and executed by an executor. The timelock enforces a minimum delay between the proposition and the execution (see xref:access-control.adoc#operation_lifecycle[operation lifecycle]). If the operation contains multiple transactions (batch mode), they are executed atomically. Operations are identified by the hash of their content. * *Operation status:* ** *Unset:* An operation that is not part of the timelock mechanism. -** *Pending:* An operation that has been scheduled, before the timer expires. +** *Waiting:* An operation that has been scheduled, before the timer expires. ** *Ready:* An operation that has been scheduled, after the timer expires. +** *Pending:* An operation that is either waiting or ready. ** *Done:* An operation that has been executed. * *Predecessor*: An (optional) dependency between operations. An operation can depend on another operation (its predecessor), forcing the execution order of these two operations. * *Role*: diff --git a/contracts/governance/TimelockController.sol b/contracts/governance/TimelockController.sol index 56a25fe30..ff8d45595 100644 --- a/contracts/governance/TimelockController.sol +++ b/contracts/governance/TimelockController.sol @@ -35,7 +35,7 @@ contract TimelockController is AccessControl, ERC721Holder, ERC1155Holder { enum OperationState { Unset, - Pending, + Waiting, Ready, Done } @@ -52,8 +52,12 @@ contract TimelockController is AccessControl, ERC721Holder, ERC1155Holder { /** * @dev The current state of an operation is not as required. + * The `expectedStates` is a bitmap with the bits enabled for each OperationState enum position + * counting from right to left. + * + * See {_encodeStateBitmap}. */ - error TimelockUnexpectedOperationState(bytes32 operationId, OperationState expected); + error TimelockUnexpectedOperationState(bytes32 operationId, bytes32 expectedStates); /** * @dev The predecessor to an operation not yet done. @@ -166,30 +170,30 @@ contract TimelockController is AccessControl, ERC721Holder, ERC1155Holder { * @dev Returns whether an id correspond to a registered operation. This * includes both Pending, Ready and Done operations. */ - function isOperation(bytes32 id) public view virtual returns (bool) { - return getTimestamp(id) > 0; + function isOperation(bytes32 id) public view returns (bool) { + return getOperationState(id) != OperationState.Unset; } /** * @dev Returns whether an operation is pending or not. Note that a "pending" operation may also be "ready". */ - function isOperationPending(bytes32 id) public view virtual returns (bool) { - return getTimestamp(id) > _DONE_TIMESTAMP; + function isOperationPending(bytes32 id) public view returns (bool) { + OperationState state = getOperationState(id); + return state == OperationState.Waiting || state == OperationState.Ready; } /** * @dev Returns whether an operation is ready for execution. Note that a "ready" operation is also "pending". */ - function isOperationReady(bytes32 id) public view virtual returns (bool) { - uint256 timestamp = getTimestamp(id); - return timestamp > _DONE_TIMESTAMP && timestamp <= block.timestamp; + function isOperationReady(bytes32 id) public view returns (bool) { + return getOperationState(id) == OperationState.Ready; } /** * @dev Returns whether an operation is done or not. */ - function isOperationDone(bytes32 id) public view virtual returns (bool) { - return getTimestamp(id) == _DONE_TIMESTAMP; + function isOperationDone(bytes32 id) public view returns (bool) { + return getOperationState(id) == OperationState.Done; } /** @@ -200,6 +204,22 @@ contract TimelockController is AccessControl, ERC721Holder, ERC1155Holder { return _timestamps[id]; } + /** + * @dev Returns operation state. + */ + function getOperationState(bytes32 id) public view virtual returns (OperationState) { + uint256 timestamp = getTimestamp(id); + if (timestamp == 0) { + return OperationState.Unset; + } else if (timestamp == _DONE_TIMESTAMP) { + return OperationState.Done; + } else if (timestamp > block.timestamp) { + return OperationState.Waiting; + } else { + return OperationState.Ready; + } + } + /** * @dev Returns the minimum delay for an operation to become valid. * @@ -298,7 +318,7 @@ contract TimelockController is AccessControl, ERC721Holder, ERC1155Holder { */ function _schedule(bytes32 id, uint256 delay) private { if (isOperation(id)) { - revert TimelockUnexpectedOperationState(id, OperationState.Unset); + revert TimelockUnexpectedOperationState(id, _encodeStateBitmap(OperationState.Unset)); } uint256 minDelay = getMinDelay(); if (delay < minDelay) { @@ -316,7 +336,10 @@ contract TimelockController is AccessControl, ERC721Holder, ERC1155Holder { */ function cancel(bytes32 id) public virtual onlyRole(CANCELLER_ROLE) { if (!isOperationPending(id)) { - revert TimelockUnexpectedOperationState(id, OperationState.Pending); + revert TimelockUnexpectedOperationState( + id, + _encodeStateBitmap(OperationState.Waiting) | _encodeStateBitmap(OperationState.Ready) + ); } delete _timestamps[id]; @@ -399,7 +422,7 @@ contract TimelockController is AccessControl, ERC721Holder, ERC1155Holder { */ function _beforeCall(bytes32 id, bytes32 predecessor) private view { if (!isOperationReady(id)) { - revert TimelockUnexpectedOperationState(id, OperationState.Ready); + revert TimelockUnexpectedOperationState(id, _encodeStateBitmap(OperationState.Ready)); } if (predecessor != bytes32(0) && !isOperationDone(predecessor)) { revert TimelockUnexecutedPredecessor(predecessor); @@ -411,7 +434,7 @@ contract TimelockController is AccessControl, ERC721Holder, ERC1155Holder { */ function _afterCall(bytes32 id) private { if (!isOperationReady(id)) { - revert TimelockUnexpectedOperationState(id, OperationState.Ready); + revert TimelockUnexpectedOperationState(id, _encodeStateBitmap(OperationState.Ready)); } _timestamps[id] = _DONE_TIMESTAMP; } @@ -434,4 +457,19 @@ contract TimelockController is AccessControl, ERC721Holder, ERC1155Holder { emit MinDelayChange(_minDelay, newDelay); _minDelay = newDelay; } + + /** + * @dev Encodes a `OperationState` into a `bytes32` representation where each bit enabled corresponds to + * the underlying position in the `OperationState` enum. For example: + * + * 0x000...1000 + * ^^^^^^----- ... + * ^---- Done + * ^--- Ready + * ^-- Waiting + * ^- Unset + */ + function _encodeStateBitmap(OperationState operationState) internal pure returns (bytes32) { + return bytes32(1 << uint8(operationState)); + } } diff --git a/test/governance/TimelockController.test.js b/test/governance/TimelockController.test.js index d8fcdce6c..ce051e787 100644 --- a/test/governance/TimelockController.test.js +++ b/test/governance/TimelockController.test.js @@ -1,5 +1,6 @@ const { BN, constants, expectEvent, expectRevert, time } = require('@openzeppelin/test-helpers'); const { ZERO_ADDRESS, ZERO_BYTES32 } = constants; +const { proposalStatesToBitMap } = require('../helpers/governance'); const { expect } = require('chai'); @@ -195,7 +196,7 @@ contract('TimelockController', function (accounts) { { from: proposer }, ), 'TimelockUnexpectedOperationState', - [this.operation.id, OperationState.Unset], + [this.operation.id, proposalStatesToBitMap(OperationState.Unset)], ); }); @@ -267,7 +268,7 @@ contract('TimelockController', function (accounts) { { from: executor }, ), 'TimelockUnexpectedOperationState', - [this.operation.id, OperationState.Ready], + [this.operation.id, proposalStatesToBitMap(OperationState.Ready)], ); }); @@ -295,7 +296,7 @@ contract('TimelockController', function (accounts) { { from: executor }, ), 'TimelockUnexpectedOperationState', - [this.operation.id, OperationState.Ready], + [this.operation.id, proposalStatesToBitMap(OperationState.Ready)], ); }); @@ -313,7 +314,7 @@ contract('TimelockController', function (accounts) { { from: executor }, ), 'TimelockUnexpectedOperationState', - [this.operation.id, OperationState.Ready], + [this.operation.id, proposalStatesToBitMap(OperationState.Ready)], ); }); @@ -408,7 +409,7 @@ contract('TimelockController', function (accounts) { { from: executor }, ), 'TimelockUnexpectedOperationState', - [reentrantOperation.id, OperationState.Ready], + [reentrantOperation.id, proposalStatesToBitMap(OperationState.Ready)], ); // Disable reentrancy @@ -505,7 +506,7 @@ contract('TimelockController', function (accounts) { { from: proposer }, ), 'TimelockUnexpectedOperationState', - [this.operation.id, OperationState.Unset], + [this.operation.id, proposalStatesToBitMap(OperationState.Unset)], ); }); @@ -596,7 +597,7 @@ contract('TimelockController', function (accounts) { { from: executor }, ), 'TimelockUnexpectedOperationState', - [this.operation.id, OperationState.Ready], + [this.operation.id, proposalStatesToBitMap(OperationState.Ready)], ); }); @@ -624,7 +625,7 @@ contract('TimelockController', function (accounts) { { from: executor }, ), 'TimelockUnexpectedOperationState', - [this.operation.id, OperationState.Ready], + [this.operation.id, proposalStatesToBitMap(OperationState.Ready)], ); }); @@ -642,7 +643,7 @@ contract('TimelockController', function (accounts) { { from: executor }, ), 'TimelockUnexpectedOperationState', - [this.operation.id, OperationState.Ready], + [this.operation.id, proposalStatesToBitMap(OperationState.Ready)], ); }); @@ -784,7 +785,7 @@ contract('TimelockController', function (accounts) { { from: executor }, ), 'TimelockUnexpectedOperationState', - [reentrantBatchOperation.id, OperationState.Ready], + [reentrantBatchOperation.id, proposalStatesToBitMap(OperationState.Ready)], ); // Disable reentrancy @@ -881,7 +882,7 @@ contract('TimelockController', function (accounts) { await expectRevertCustomError( this.mock.cancel(constants.ZERO_BYTES32, { from: canceller }), 'TimelockUnexpectedOperationState', - [constants.ZERO_BYTES32, OperationState.Pending], + [constants.ZERO_BYTES32, proposalStatesToBitMap([OperationState.Waiting, OperationState.Ready])], ); }); diff --git a/test/governance/extensions/GovernorTimelockControl.test.js b/test/governance/extensions/GovernorTimelockControl.test.js index 3265dfa68..5954d4117 100644 --- a/test/governance/extensions/GovernorTimelockControl.test.js +++ b/test/governance/extensions/GovernorTimelockControl.test.js @@ -159,7 +159,7 @@ contract('GovernorTimelockControl', function (accounts) { await expectRevertCustomError(this.helper.execute(), 'TimelockUnexpectedOperationState', [ this.proposal.timelockid, - Enums.OperationState.Ready, + proposalStatesToBitMap(Enums.OperationState.Ready), ]); }); @@ -174,7 +174,7 @@ contract('GovernorTimelockControl', function (accounts) { await expectRevertCustomError(this.helper.execute(), 'TimelockUnexpectedOperationState', [ this.proposal.timelockid, - Enums.OperationState.Ready, + proposalStatesToBitMap(Enums.OperationState.Ready), ]); }); diff --git a/test/helpers/enums.js b/test/helpers/enums.js index d4a4fdbd0..75746e087 100644 --- a/test/helpers/enums.js +++ b/test/helpers/enums.js @@ -7,5 +7,5 @@ module.exports = { ProposalState: Enum('Pending', 'Active', 'Canceled', 'Defeated', 'Succeeded', 'Queued', 'Expired', 'Executed'), VoteType: Enum('Against', 'For', 'Abstain'), Rounding: Enum('Down', 'Up', 'Zero'), - OperationState: Enum('Unset', 'Pending', 'Ready', 'Done'), + OperationState: Enum('Unset', 'Waiting', 'Ready', 'Done'), }; From 90163661df4f7df22294fcbf82dd3f6bb5547417 Mon Sep 17 00:00:00 2001 From: Renan Souza Date: Tue, 4 Jul 2023 15:40:41 -0300 Subject: [PATCH 137/182] Implement ERC165 tests realted to invalidID (#4414) Co-authored-by: ernestognw --- .../SupportsInterface.behavior.js | 33 +++++++++++++------ 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/test/utils/introspection/SupportsInterface.behavior.js b/test/utils/introspection/SupportsInterface.behavior.js index 71be4313f..9c0fd0848 100644 --- a/test/utils/introspection/SupportsInterface.behavior.js +++ b/test/utils/introspection/SupportsInterface.behavior.js @@ -2,6 +2,7 @@ const { makeInterfaceId } = require('@openzeppelin/test-helpers'); const { expect } = require('chai'); +const INVALID_ID = '0xffffffff'; const INTERFACES = { ERC165: ['supportsInterface(bytes4)'], ERC721: [ @@ -111,18 +112,30 @@ function shouldSupportInterfaces(interfaces = []) { this.contractUnderTest = this.mock || this.token || this.holder || this.accessControl; }); - it('supportsInterface uses less than 30k gas', async function () { - for (const k of interfaces) { - const interfaceId = INTERFACE_IDS[k] ?? k; - expect(await this.contractUnderTest.supportsInterface.estimateGas(interfaceId)).to.be.lte(30000); - } + describe('when the interfaceId is supported', function () { + it('uses less than 30k gas', async function () { + for (const k of interfaces) { + const interfaceId = INTERFACE_IDS[k] ?? k; + expect(await this.contractUnderTest.supportsInterface.estimateGas(interfaceId)).to.be.lte(30000); + } + }); + + it('returns true', async function () { + for (const k of interfaces) { + const interfaceId = INTERFACE_IDS[k] ?? k; + expect(await this.contractUnderTest.supportsInterface(interfaceId)).to.equal(true, `does not support ${k}`); + } + }); }); - it('all interfaces are reported as supported', async function () { - for (const k of interfaces) { - const interfaceId = INTERFACE_IDS[k] ?? k; - expect(await this.contractUnderTest.supportsInterface(interfaceId)).to.equal(true, `does not support ${k}`); - } + describe('when the interfaceId is not supported', function () { + it('uses less thank 30k', async function () { + expect(await this.contractUnderTest.supportsInterface.estimateGas(INVALID_ID)).to.be.lte(30000); + }); + + it('returns false', async function () { + expect(await this.contractUnderTest.supportsInterface(INVALID_ID)).to.be.equal(false, `supports ${INVALID_ID}`); + }); }); it('all interface functions are in ABI', async function () { From 63bfab1a0ce40d14f33f6354ff5ebcd67a76f143 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Wed, 5 Jul 2023 07:11:29 -0600 Subject: [PATCH 138/182] Enable ERC-1271 signature checks in Governor `castVoteBySig` (#4418) Co-authored-by: Hadrien Croubois Co-authored-by: Francisco --- .changeset/popular-deers-raise.md | 5 + contracts/governance/Governor.sol | 36 ++-- contracts/governance/IGovernor.sol | 18 +- .../utils/cryptography/SignatureChecker.sol | 2 +- test/governance/Governor.test.js | 179 +++++++++++------- .../extensions/GovernorWithParams.test.js | 96 +++++++--- test/helpers/governance.js | 8 +- .../SupportsInterface.behavior.js | 6 +- 8 files changed, 213 insertions(+), 137 deletions(-) create mode 100644 .changeset/popular-deers-raise.md diff --git a/.changeset/popular-deers-raise.md b/.changeset/popular-deers-raise.md new file mode 100644 index 000000000..ec1fb7466 --- /dev/null +++ b/.changeset/popular-deers-raise.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': major +--- + +`Governor`: Add support for casting votes with ERC-1271 signatures by using a `bytes memory signature` instead of `r`, `s` and `v` arguments in the `castVoteBySig` and `castVoteWithReasonAndParamsBySig` functions. diff --git a/contracts/governance/Governor.sol b/contracts/governance/Governor.sol index 1e1526ceb..ef730d3f7 100644 --- a/contracts/governance/Governor.sol +++ b/contracts/governance/Governor.sol @@ -5,8 +5,8 @@ pragma solidity ^0.8.19; import {IERC721Receiver} from "../token/ERC721/IERC721Receiver.sol"; import {IERC1155Receiver} from "../token/ERC1155/IERC1155Receiver.sol"; -import {ECDSA} from "../utils/cryptography/ECDSA.sol"; import {EIP712} from "../utils/cryptography/EIP712.sol"; +import {SignatureChecker} from "../utils/cryptography/SignatureChecker.sol"; import {IERC165, ERC165} from "../utils/introspection/ERC165.sol"; import {SafeCast} from "../utils/math/SafeCast.sol"; import {DoubleEndedQueue} from "../utils/structs/DoubleEndedQueue.sol"; @@ -519,22 +519,19 @@ abstract contract Governor is Context, ERC165, EIP712, Nonces, IGovernor, IERC72 uint256 proposalId, uint8 support, address voter, - uint8 v, - bytes32 r, - bytes32 s + bytes memory signature ) public virtual override returns (uint256) { - address signer = ECDSA.recover( + bool valid = SignatureChecker.isValidSignatureNow( + voter, _hashTypedDataV4(keccak256(abi.encode(BALLOT_TYPEHASH, proposalId, support, voter, _useNonce(voter)))), - v, - r, - s + signature ); - if (voter != signer) { - revert GovernorInvalidSigner(signer, voter); + if (!valid) { + revert GovernorInvalidSignature(voter); } - return _castVote(proposalId, signer, support, ""); + return _castVote(proposalId, voter, support, ""); } /** @@ -546,11 +543,10 @@ abstract contract Governor is Context, ERC165, EIP712, Nonces, IGovernor, IERC72 address voter, string calldata reason, bytes memory params, - uint8 v, - bytes32 r, - bytes32 s + bytes memory signature ) public virtual override returns (uint256) { - address signer = ECDSA.recover( + bool valid = SignatureChecker.isValidSignatureNow( + voter, _hashTypedDataV4( keccak256( abi.encode( @@ -564,16 +560,14 @@ abstract contract Governor is Context, ERC165, EIP712, Nonces, IGovernor, IERC72 ) ) ), - v, - r, - s + signature ); - if (voter != signer) { - revert GovernorInvalidSigner(signer, voter); + if (!valid) { + revert GovernorInvalidSignature(voter); } - return _castVote(proposalId, signer, support, reason, params); + return _castVote(proposalId, voter, support, reason, params); } /** diff --git a/contracts/governance/IGovernor.sol b/contracts/governance/IGovernor.sol index 20636e3ba..af4698226 100644 --- a/contracts/governance/IGovernor.sol +++ b/contracts/governance/IGovernor.sol @@ -81,9 +81,10 @@ abstract contract IGovernor is IERC165, IERC6372 { error GovernorInvalidVoteType(); /** - * @dev The `voter` doesn't match with the recovered `signer`. + * @dev The provided signature is not valid for the expected `voter`. + * If the `voter` is a contract, the signature is not valid using {IERC1271-isValidSignature}. */ - error GovernorInvalidSigner(address signer, address voter); + error GovernorInvalidSignature(address voter); /** * @dev Emitted when a proposal is created. @@ -353,7 +354,7 @@ abstract contract IGovernor is IERC165, IERC6372 { ) public virtual returns (uint256 balance); /** - * @dev Cast a vote using the user's cryptographic signature. + * @dev Cast a vote using the voter's signature, including ERC-1271 signature support. * * Emits a {VoteCast} event. */ @@ -361,13 +362,12 @@ abstract contract IGovernor is IERC165, IERC6372 { uint256 proposalId, uint8 support, address voter, - uint8 v, - bytes32 r, - bytes32 s + bytes memory signature ) public virtual returns (uint256 balance); /** - * @dev Cast a vote with a reason and additional encoded parameters using the user's cryptographic signature. + * @dev Cast a vote with a reason and additional encoded parameters using the voter's signature, + * including ERC-1271 signature support. * * Emits a {VoteCast} or {VoteCastWithParams} event depending on the length of params. */ @@ -377,8 +377,6 @@ abstract contract IGovernor is IERC165, IERC6372 { address voter, string calldata reason, bytes memory params, - uint8 v, - bytes32 r, - bytes32 s + bytes memory signature ) public virtual returns (uint256 balance); } diff --git a/contracts/utils/cryptography/SignatureChecker.sol b/contracts/utils/cryptography/SignatureChecker.sol index dfc512b39..5caf7bef9 100644 --- a/contracts/utils/cryptography/SignatureChecker.sol +++ b/contracts/utils/cryptography/SignatureChecker.sol @@ -9,7 +9,7 @@ import {IERC1271} from "../../interfaces/IERC1271.sol"; /** * @dev Signature verification helper that can be used instead of `ECDSA.recover` to seamlessly support both ECDSA * signatures from externally owned accounts (EOAs) as well as ERC1271 signatures from smart contract wallets like - * Argent and Gnosis Safe. + * Argent and Safe Wallet (previously Gnosis Safe). * * _Available since v4.1._ */ diff --git a/test/governance/Governor.test.js b/test/governance/Governor.test.js index f4a352c6d..4e47ab9f2 100644 --- a/test/governance/Governor.test.js +++ b/test/governance/Governor.test.js @@ -2,7 +2,6 @@ const { constants, expectEvent, expectRevert } = require('@openzeppelin/test-hel const { expect } = require('chai'); const ethSigUtil = require('eth-sig-util'); const Wallet = require('ethereumjs-wallet').default; -const { fromRpcSig, toRpcSig } = require('ethereumjs-util'); const Enums = require('../helpers/enums'); const { getDomain, domainType } = require('../helpers/eip712'); @@ -18,6 +17,7 @@ const Governor = artifacts.require('$GovernorMock'); const CallReceiver = artifacts.require('CallReceiverMock'); const ERC721 = artifacts.require('$ERC721'); const ERC1155 = artifacts.require('$ERC1155'); +const ERC1271WalletMock = artifacts.require('ERC1271WalletMock'); const TOKENS = [ { Token: artifacts.require('$ERC20Votes'), mode: 'blocknumber' }, @@ -166,55 +166,6 @@ contract('Governor', function (accounts) { expect(await web3.eth.getBalance(this.receiver.address)).to.be.bignumber.equal(value); }); - it('votes with signature', async function () { - const voterBySig = Wallet.generate(); - const voterBySigAddress = web3.utils.toChecksumAddress(voterBySig.getAddressString()); - - const signature = (contract, message) => - getDomain(contract) - .then(domain => ({ - primaryType: 'Ballot', - types: { - EIP712Domain: domainType(domain), - Ballot: [ - { name: 'proposalId', type: 'uint256' }, - { name: 'support', type: 'uint8' }, - { name: 'voter', type: 'address' }, - { name: 'nonce', type: 'uint256' }, - ], - }, - domain, - message, - })) - .then(data => ethSigUtil.signTypedMessage(voterBySig.getPrivateKey(), { data })) - .then(fromRpcSig); - - await this.token.delegate(voterBySigAddress, { from: voter1 }); - - const nonce = await this.mock.nonces(voterBySigAddress); - - // Run proposal - await this.helper.propose(); - await this.helper.waitForSnapshot(); - expectEvent( - await this.helper.vote({ support: Enums.VoteType.For, voter: voterBySigAddress, nonce, signature }), - 'VoteCast', - { - voter: voterBySigAddress, - support: Enums.VoteType.For, - }, - ); - await this.helper.waitForDeadline(); - await this.helper.execute(); - - // After - expect(await this.mock.hasVoted(this.proposal.id, owner)).to.be.equal(false); - expect(await this.mock.hasVoted(this.proposal.id, voter1)).to.be.equal(false); - expect(await this.mock.hasVoted(this.proposal.id, voter2)).to.be.equal(false); - expect(await this.mock.hasVoted(this.proposal.id, voterBySigAddress)).to.be.equal(true); - expect(await this.mock.nonces(voterBySigAddress)).to.be.bignumber.equal(nonce.addn(1)); - }); - it('send ethers', async function () { const empty = web3.utils.toChecksumAddress(web3.utils.randomHex(20)); @@ -244,6 +195,106 @@ contract('Governor', function (accounts) { expect(await web3.eth.getBalance(empty)).to.be.bignumber.equal(value); }); + describe('vote with signature', function () { + const sign = privateKey => async (contract, message) => { + const domain = await getDomain(contract); + return ethSigUtil.signTypedMessage(privateKey, { + data: { + primaryType: 'Ballot', + types: { + EIP712Domain: domainType(domain), + Ballot: [ + { name: 'proposalId', type: 'uint256' }, + { name: 'support', type: 'uint8' }, + { name: 'voter', type: 'address' }, + { name: 'nonce', type: 'uint256' }, + ], + }, + domain, + message, + }, + }); + }; + + afterEach('no other votes are cast for proposalId', async function () { + expect(await this.mock.hasVoted(this.proposal.id, owner)).to.be.equal(false); + expect(await this.mock.hasVoted(this.proposal.id, voter1)).to.be.equal(false); + expect(await this.mock.hasVoted(this.proposal.id, voter2)).to.be.equal(false); + }); + + it('votes with an EOA signature', async function () { + const voterBySig = Wallet.generate(); + const voterBySigAddress = web3.utils.toChecksumAddress(voterBySig.getAddressString()); + + await this.token.delegate(voterBySigAddress, { from: voter1 }); + + const nonce = await this.mock.nonces(voterBySigAddress); + + // Run proposal + await this.helper.propose(); + await this.helper.waitForSnapshot(); + expectEvent( + await this.helper.vote({ + support: Enums.VoteType.For, + voter: voterBySigAddress, + nonce, + signature: sign(voterBySig.getPrivateKey()), + }), + 'VoteCast', + { + voter: voterBySigAddress, + support: Enums.VoteType.For, + }, + ); + await this.helper.waitForDeadline(); + await this.helper.execute(); + + // After + expect(await this.mock.hasVoted(this.proposal.id, voterBySigAddress)).to.be.equal(true); + expect(await this.mock.nonces(voterBySigAddress)).to.be.bignumber.equal(nonce.addn(1)); + }); + + it('votes with a valid EIP-1271 signature', async function () { + const ERC1271WalletOwner = Wallet.generate(); + ERC1271WalletOwner.address = web3.utils.toChecksumAddress(ERC1271WalletOwner.getAddressString()); + + const wallet = await ERC1271WalletMock.new(ERC1271WalletOwner.address); + + await this.token.delegate(wallet.address, { from: voter1 }); + + const nonce = await this.mock.nonces(wallet.address); + + // Run proposal + await this.helper.propose(); + await this.helper.waitForSnapshot(); + expectEvent( + await this.helper.vote({ + support: Enums.VoteType.For, + voter: wallet.address, + nonce, + signature: sign(ERC1271WalletOwner.getPrivateKey()), + }), + 'VoteCast', + { + voter: wallet.address, + support: Enums.VoteType.For, + }, + ); + await this.helper.waitForDeadline(); + await this.helper.execute(); + + // After + expect(await this.mock.hasVoted(this.proposal.id, wallet.address)).to.be.equal(true); + expect(await this.mock.nonces(wallet.address)).to.be.bignumber.equal(nonce.addn(1)); + }); + + afterEach('no other votes are cast', async function () { + expect(await this.mock.hasVoted(this.proposal.id, owner)).to.be.equal(false); + expect(await this.mock.hasVoted(this.proposal.id, voter1)).to.be.equal(false); + expect(await this.mock.hasVoted(this.proposal.id, voter2)).to.be.equal(false); + }); + }); + describe('should revert', function () { describe('on propose', function () { it('if proposal already exists', async function () { @@ -328,9 +379,9 @@ contract('Governor', function (accounts) { })); this.signature = (contract, message) => - this.data(contract, message) - .then(data => ethSigUtil.signTypedMessage(this.voterBySig.getPrivateKey(), { data })) - .then(fromRpcSig); + this.data(contract, message).then(data => + ethSigUtil.signTypedMessage(this.voterBySig.getPrivateKey(), { data }), + ); await this.token.delegate(this.voterBySig.address, { from: voter1 }); @@ -348,19 +399,13 @@ contract('Governor', function (accounts) { nonce, signature: async (...params) => { const sig = await this.signature(...params); - sig.s[12] ^= 0xff; - return sig; + const tamperedSig = web3.utils.hexToBytes(sig); + tamperedSig[42] ^= 0xff; + return web3.utils.bytesToHex(tamperedSig); }, }; - const { r, s, v } = await this.helper.sign(voteParams); - const message = this.helper.forgeMessage(voteParams); - const data = await this.data(this.mock, message); - - await expectRevertCustomError(this.helper.vote(voteParams), 'GovernorInvalidSigner', [ - ethSigUtil.recoverTypedSignature({ sig: toRpcSig(v, r, s), data }), - voteParams.voter, - ]); + await expectRevertCustomError(this.helper.vote(voteParams), 'GovernorInvalidSignature', [voteParams.voter]); }); it('if vote nonce is incorrect', async function () { @@ -373,15 +418,11 @@ contract('Governor', function (accounts) { signature: this.signature, }; - const { r, s, v } = await this.helper.sign(voteParams); - const message = this.helper.forgeMessage(voteParams); - const data = await this.data(this.mock, { ...message, nonce }); - await expectRevertCustomError( this.helper.vote(voteParams), // The signature check implies the nonce can't be tampered without changing the signer - 'GovernorInvalidSigner', - [ethSigUtil.recoverTypedSignature({ sig: toRpcSig(v, r, s), data }), voteParams.voter], + 'GovernorInvalidSignature', + [voteParams.voter], ); }); }); diff --git a/test/governance/extensions/GovernorWithParams.test.js b/test/governance/extensions/GovernorWithParams.test.js index fb58edaaf..896c2e094 100644 --- a/test/governance/extensions/GovernorWithParams.test.js +++ b/test/governance/extensions/GovernorWithParams.test.js @@ -2,7 +2,6 @@ const { expectEvent } = require('@openzeppelin/test-helpers'); const { expect } = require('chai'); const ethSigUtil = require('eth-sig-util'); const Wallet = require('ethereumjs-wallet').default; -const { fromRpcSig, toRpcSig } = require('ethereumjs-util'); const Enums = require('../../helpers/enums'); const { getDomain, domainType } = require('../../helpers/eip712'); @@ -11,6 +10,7 @@ const { expectRevertCustomError } = require('../../helpers/customError'); const Governor = artifacts.require('$GovernorWithParamsMock'); const CallReceiver = artifacts.require('CallReceiverMock'); +const ERC1271WalletMock = artifacts.require('ERC1271WalletMock'); const rawParams = { uintParam: web3.utils.toBN('42'), @@ -143,35 +143,71 @@ contract('GovernorWithParams', function (accounts) { message, })); - this.signature = (contract, message) => - this.data(contract, message) - .then(data => ethSigUtil.signTypedMessage(this.voterBySig.getPrivateKey(), { data })) - .then(fromRpcSig); + this.sign = privateKey => async (contract, message) => + ethSigUtil.signTypedMessage(privateKey, { data: await this.data(contract, message) }); + }); + it('suports EOA signatures', async function () { await this.token.delegate(this.voterBySig.address, { from: voter2 }); + const weight = web3.utils.toBN(web3.utils.toWei('7')).sub(rawParams.uintParam); + + const nonce = await this.mock.nonces(this.voterBySig.address); + // Run proposal await this.helper.propose(); await this.helper.waitForSnapshot(); + const tx = await this.helper.vote({ + support: Enums.VoteType.For, + voter: this.voterBySig.address, + nonce, + reason: 'no particular reason', + params: encodedParams, + signature: this.sign(this.voterBySig.getPrivateKey()), + }); + + expectEvent(tx, 'CountParams', { ...rawParams }); + expectEvent(tx, 'VoteCastWithParams', { + voter: this.voterBySig.address, + proposalId: this.proposal.id, + support: Enums.VoteType.For, + weight, + reason: 'no particular reason', + params: encodedParams, + }); + + const votes = await this.mock.proposalVotes(this.proposal.id); + expect(votes.forVotes).to.be.bignumber.equal(weight); + expect(await this.mock.nonces(this.voterBySig.address)).to.be.bignumber.equal(nonce.addn(1)); }); - it('is properly supported', async function () { + it('supports EIP-1271 signature signatures', async function () { + const ERC1271WalletOwner = Wallet.generate(); + ERC1271WalletOwner.address = web3.utils.toChecksumAddress(ERC1271WalletOwner.getAddressString()); + + const wallet = await ERC1271WalletMock.new(ERC1271WalletOwner.address); + + await this.token.delegate(wallet.address, { from: voter2 }); + const weight = web3.utils.toBN(web3.utils.toWei('7')).sub(rawParams.uintParam); - const nonce = await this.mock.nonces(this.voterBySig.address); + const nonce = await this.mock.nonces(wallet.address); + // Run proposal + await this.helper.propose(); + await this.helper.waitForSnapshot(); const tx = await this.helper.vote({ support: Enums.VoteType.For, - voter: this.voterBySig.address, + voter: wallet.address, nonce, reason: 'no particular reason', params: encodedParams, - signature: this.signature, + signature: this.sign(ERC1271WalletOwner.getPrivateKey()), }); expectEvent(tx, 'CountParams', { ...rawParams }); expectEvent(tx, 'VoteCastWithParams', { - voter: this.voterBySig.address, + voter: wallet.address, proposalId: this.proposal.id, support: Enums.VoteType.For, weight, @@ -181,56 +217,58 @@ contract('GovernorWithParams', function (accounts) { const votes = await this.mock.proposalVotes(this.proposal.id); expect(votes.forVotes).to.be.bignumber.equal(weight); - expect(await this.mock.nonces(this.voterBySig.address)).to.be.bignumber.equal(nonce.addn(1)); + expect(await this.mock.nonces(wallet.address)).to.be.bignumber.equal(nonce.addn(1)); }); it('reverts if signature does not match signer', async function () { + await this.token.delegate(this.voterBySig.address, { from: voter2 }); + const nonce = await this.mock.nonces(this.voterBySig.address); + const signature = this.sign(this.voterBySig.getPrivateKey()); + + // Run proposal + await this.helper.propose(); + await this.helper.waitForSnapshot(); const voteParams = { support: Enums.VoteType.For, voter: this.voterBySig.address, nonce, signature: async (...params) => { - const sig = await this.signature(...params); - sig.s[12] ^= 0xff; - return sig; + const sig = await signature(...params); + const tamperedSig = web3.utils.hexToBytes(sig); + tamperedSig[42] ^= 0xff; + return web3.utils.bytesToHex(tamperedSig); }, reason: 'no particular reason', params: encodedParams, }; - const { r, s, v } = await this.helper.sign(voteParams); - const message = this.helper.forgeMessage(voteParams); - const data = await this.data(this.mock, message); - - await expectRevertCustomError(this.helper.vote(voteParams), 'GovernorInvalidSigner', [ - ethSigUtil.recoverTypedSignature({ sig: toRpcSig(v, r, s), data }), - voteParams.voter, - ]); + await expectRevertCustomError(this.helper.vote(voteParams), 'GovernorInvalidSignature', [voteParams.voter]); }); it('reverts if vote nonce is incorrect', async function () { + await this.token.delegate(this.voterBySig.address, { from: voter2 }); + const nonce = await this.mock.nonces(this.voterBySig.address); + // Run proposal + await this.helper.propose(); + await this.helper.waitForSnapshot(); const voteParams = { support: Enums.VoteType.For, voter: this.voterBySig.address, nonce: nonce.addn(1), - signature: this.signature, + signature: this.sign(this.voterBySig.getPrivateKey()), reason: 'no particular reason', params: encodedParams, }; - const { r, s, v } = await this.helper.sign(voteParams); - const message = this.helper.forgeMessage(voteParams); - const data = await this.data(this.mock, { ...message, nonce }); - await expectRevertCustomError( this.helper.vote(voteParams), // The signature check implies the nonce can't be tampered without changing the signer - 'GovernorInvalidSigner', - [ethSigUtil.recoverTypedSignature({ sig: toRpcSig(v, r, s), data }), voteParams.voter], + 'GovernorInvalidSignature', + [voteParams.voter], ); }); }); diff --git a/test/helpers/governance.js b/test/helpers/governance.js index 588aeb258..3ae0695ac 100644 --- a/test/helpers/governance.js +++ b/test/helpers/governance.js @@ -91,16 +91,16 @@ class GovernorHelper { return vote.signature ? // if signature, and either params or reason → vote.params || vote.reason - ? this.sign(vote).then(({ v, r, s }) => + ? this.sign(vote).then(signature => this.governor.castVoteWithReasonAndParamsBySig( ...concatOpts( - [proposal.id, vote.support, vote.voter, vote.reason || '', vote.params || '', v, r, s], + [proposal.id, vote.support, vote.voter, vote.reason || '', vote.params || '', signature], opts, ), ), ) - : this.sign(vote).then(({ v, r, s }) => - this.governor.castVoteBySig(...concatOpts([proposal.id, vote.support, vote.voter, v, r, s], opts)), + : this.sign(vote).then(signature => + this.governor.castVoteBySig(...concatOpts([proposal.id, vote.support, vote.voter, signature], opts)), ) : vote.params ? // otherwise if params diff --git a/test/utils/introspection/SupportsInterface.behavior.js b/test/utils/introspection/SupportsInterface.behavior.js index 9c0fd0848..80e144f18 100644 --- a/test/utils/introspection/SupportsInterface.behavior.js +++ b/test/utils/introspection/SupportsInterface.behavior.js @@ -67,7 +67,7 @@ const INTERFACES = { 'execute(address[],uint256[],bytes[],bytes32)', 'castVote(uint256,uint8)', 'castVoteWithReason(uint256,uint8,string)', - 'castVoteBySig(uint256,uint8,address,uint8,bytes32,bytes32)', + 'castVoteBySig(uint256,uint8,address,bytes)', ], GovernorWithParams: [ 'name()', @@ -88,8 +88,8 @@ const INTERFACES = { 'castVote(uint256,uint8)', 'castVoteWithReason(uint256,uint8,string)', 'castVoteWithReasonAndParams(uint256,uint8,string,bytes)', - 'castVoteBySig(uint256,uint8,address,uint8,bytes32,bytes32)', - 'castVoteWithReasonAndParamsBySig(uint256,uint8,address,string,bytes,uint8,bytes32,bytes32)', + 'castVoteBySig(uint256,uint8,address,bytes)', + 'castVoteWithReasonAndParamsBySig(uint256,uint8,address,string,bytes,bytes)', ], GovernorCancel: ['proposalProposer(uint256)', 'cancel(address[],uint256[],bytes[],bytes32)'], GovernorTimelock: ['timelock()', 'proposalEta(uint256)', 'queue(address[],uint256[],bytes[],bytes32)'], From 3ff9b42ff57f5bbb25a85481007a7dbac4bbc705 Mon Sep 17 00:00:00 2001 From: Francisco Date: Wed, 5 Jul 2023 18:38:27 -0300 Subject: [PATCH 139/182] Remove retyped and renamed storage layout annotations (#4423) --- contracts/governance/Governor.sol | 1 - .../extensions/GovernorPreventLateQuorum.sol | 1 - .../extensions/GovernorTimelockCompound.sol | 1 - .../GovernorVotesQuorumFraction.sol | 1 - contracts/governance/utils/Votes.sol | 2 - contracts/proxy/utils/Initializable.sol | 1 - scripts/upgradeable/upgradeable.patch | 79 ++----------------- 7 files changed, 7 insertions(+), 79 deletions(-) diff --git a/contracts/governance/Governor.sol b/contracts/governance/Governor.sol index ef730d3f7..a66a1a6bf 100644 --- a/contracts/governance/Governor.sol +++ b/contracts/governance/Governor.sol @@ -49,7 +49,6 @@ abstract contract Governor is Context, ERC165, EIP712, Nonces, IGovernor, IERC72 bytes32 private constant _ALL_PROPOSAL_STATES_BITMAP = bytes32((2 ** (uint8(type(ProposalState).max) + 1)) - 1); string private _name; - /// @custom:oz-retyped-from mapping(uint256 => Governor.ProposalCore) mapping(uint256 => ProposalCore) private _proposals; // This queue keeps track of the governor operating on itself. Calls to functions protected by the diff --git a/contracts/governance/extensions/GovernorPreventLateQuorum.sol b/contracts/governance/extensions/GovernorPreventLateQuorum.sol index 3e034acad..8687b6fd1 100644 --- a/contracts/governance/extensions/GovernorPreventLateQuorum.sol +++ b/contracts/governance/extensions/GovernorPreventLateQuorum.sol @@ -20,7 +20,6 @@ import {Math} from "../../utils/math/Math.sol"; abstract contract GovernorPreventLateQuorum is Governor { uint48 private _voteExtension; - /// @custom:oz-retyped-from mapping(uint256 => Timers.BlockNumber) mapping(uint256 => uint48) private _extendedDeadlines; /// @dev Emitted when a proposal deadline is pushed back due to reaching quorum late in its voting period. diff --git a/contracts/governance/extensions/GovernorTimelockCompound.sol b/contracts/governance/extensions/GovernorTimelockCompound.sol index cd61d1ff8..935600d56 100644 --- a/contracts/governance/extensions/GovernorTimelockCompound.sol +++ b/contracts/governance/extensions/GovernorTimelockCompound.sol @@ -25,7 +25,6 @@ import {Address} from "../../utils/Address.sol"; abstract contract GovernorTimelockCompound is IGovernorTimelock, Governor { ICompoundTimelock private _timelock; - /// @custom:oz-retyped-from mapping(uint256 => GovernorTimelockCompound.ProposalTimelock) mapping(uint256 => uint256) private _proposalTimelocks; /** diff --git a/contracts/governance/extensions/GovernorVotesQuorumFraction.sol b/contracts/governance/extensions/GovernorVotesQuorumFraction.sol index bb1c7d2ea..5d7976b14 100644 --- a/contracts/governance/extensions/GovernorVotesQuorumFraction.sol +++ b/contracts/governance/extensions/GovernorVotesQuorumFraction.sol @@ -16,7 +16,6 @@ import {Checkpoints} from "../../utils/structs/Checkpoints.sol"; abstract contract GovernorVotesQuorumFraction is GovernorVotes { using Checkpoints for Checkpoints.Trace224; - /// @custom:oz-retyped-from Checkpoints.History Checkpoints.Trace224 private _quorumNumeratorHistory; event QuorumNumeratorUpdated(uint256 oldQuorumNumerator, uint256 newQuorumNumerator); diff --git a/contracts/governance/utils/Votes.sol b/contracts/governance/utils/Votes.sol index c0ff69e62..988255189 100644 --- a/contracts/governance/utils/Votes.sol +++ b/contracts/governance/utils/Votes.sol @@ -38,10 +38,8 @@ abstract contract Votes is Context, EIP712, Nonces, IERC5805 { mapping(address => address) private _delegation; - /// @custom:oz-retyped-from mapping(address => Checkpoints.History) mapping(address => Checkpoints.Trace224) private _delegateCheckpoints; - /// @custom:oz-retyped-from Checkpoints.History Checkpoints.Trace224 private _totalCheckpoints; /** diff --git a/contracts/proxy/utils/Initializable.sol b/contracts/proxy/utils/Initializable.sol index 86e99531b..a2e3569c4 100644 --- a/contracts/proxy/utils/Initializable.sol +++ b/contracts/proxy/utils/Initializable.sol @@ -58,7 +58,6 @@ import {Address} from "../../utils/Address.sol"; abstract contract Initializable { /** * @dev Indicates that the contract has been initialized. - * @custom:oz-retyped-from bool */ uint8 private _initialized; diff --git a/scripts/upgradeable/upgradeable.patch b/scripts/upgradeable/upgradeable.patch index 466d1d1ca..8623806f5 100644 --- a/scripts/upgradeable/upgradeable.patch +++ b/scripts/upgradeable/upgradeable.patch @@ -105,32 +105,6 @@ index 27627f43..e42a66e8 100644 } } ``` -diff --git a/contracts/finance/VestingWallet.sol b/contracts/finance/VestingWallet.sol -index f776a7ca..d7a7e0b0 100644 ---- a/contracts/finance/VestingWallet.sol -+++ b/contracts/finance/VestingWallet.sol -@@ -19,6 +19,8 @@ import {Context} from "../utils/Context.sol"; - * - * By setting the duration to 0, one can configure this contract to behave like an asset timelock that hold tokens for - * a beneficiary until a specified time. -+ * -+ * @custom:storage-size 52 - */ - contract VestingWallet is Context { - event EtherReleased(uint256 amount); -diff --git a/contracts/governance/extensions/GovernorVotes.sol b/contracts/governance/extensions/GovernorVotes.sol -index bb14d7fd..0785ebbd 100644 ---- a/contracts/governance/extensions/GovernorVotes.sol -+++ b/contracts/governance/extensions/GovernorVotes.sol -@@ -12,6 +12,8 @@ import {SafeCast} from "../../utils/math/SafeCast.sol"; - * @dev Extension of {Governor} for voting weight extraction from an {ERC20Votes} token, or since v4.5 an {ERC721Votes} token. - * - * _Available since v4.3._ -+ * -+ * @custom:storage-size 51 - */ - abstract contract GovernorVotes is Governor { - IERC5805 public immutable token; diff --git a/contracts/package.json b/contracts/package.json index df141192..1cf90ad1 100644 --- a/contracts/package.json @@ -151,47 +125,8 @@ index df141192..1cf90ad1 100644 }, "keywords": [ "solidity", -diff --git a/contracts/token/ERC20/extensions/ERC20Capped.sol b/contracts/token/ERC20/extensions/ERC20Capped.sol -index 98ec7144..4992115b 100644 ---- a/contracts/token/ERC20/extensions/ERC20Capped.sol -+++ b/contracts/token/ERC20/extensions/ERC20Capped.sol -@@ -7,6 +7,8 @@ import {ERC20} from "../ERC20.sol"; - - /** - * @dev Extension of {ERC20} that adds a cap to the supply of tokens. -+ * -+ * @custom:storage-size 51 - */ - abstract contract ERC20Capped is ERC20 { - uint256 private immutable _cap; -diff --git a/contracts/token/ERC20/extensions/ERC20Permit.sol b/contracts/token/ERC20/extensions/ERC20Permit.sol -index 8778f4ba..825d4e16 100644 ---- a/contracts/token/ERC20/extensions/ERC20Permit.sol -+++ b/contracts/token/ERC20/extensions/ERC20Permit.sol -@@ -18,6 +18,8 @@ import {Nonces} from "../../../utils/Nonces.sol"; - * need to send a transaction, and thus is not required to hold Ether at all. - * - * _Available since v3.4._ -+ * -+ * @custom:storage-size 51 - */ - abstract contract ERC20Permit is ERC20, IERC20Permit, EIP712, Nonces { - // solhint-disable-next-line var-name-mixedcase -diff --git a/contracts/token/ERC20/extensions/ERC20Wrapper.sol b/contracts/token/ERC20/extensions/ERC20Wrapper.sol -index 2cbff622..97f43d7a 100644 ---- a/contracts/token/ERC20/extensions/ERC20Wrapper.sol -+++ b/contracts/token/ERC20/extensions/ERC20Wrapper.sol -@@ -14,6 +14,8 @@ import {SafeERC20} from "../utils/SafeERC20.sol"; - * wrapping of an existing "basic" ERC20 into a governance token. - * - * _Available since v4.2._ -+ * -+ * @custom:storage-size 51 - */ - abstract contract ERC20Wrapper is ERC20 { - IERC20 private immutable _underlying; diff --git a/contracts/utils/cryptography/EIP712.sol b/contracts/utils/cryptography/EIP712.sol -index d94e956a..86bb5713 100644 +index d94e956a..b2d3546f 100644 --- a/contracts/utils/cryptography/EIP712.sol +++ b/contracts/utils/cryptography/EIP712.sol @@ -4,7 +4,6 @@ @@ -202,12 +137,12 @@ index d94e956a..86bb5713 100644 import {IERC5267} from "../../interfaces/IERC5267.sol"; /** -@@ -30,27 +29,19 @@ import {IERC5267} from "../../interfaces/IERC5267.sol"; +@@ -29,28 +28,18 @@ import {IERC5267} from "../../interfaces/IERC5267.sol"; + * separator from the immutable values, which is cheaper than accessing a cached version in cold storage. * * _Available since v3.4._ - * +- * - * @custom:oz-upgrades-unsafe-allow state-variable-immutable state-variable-assignment -+ * @custom:storage-size 52 */ abstract contract EIP712 is IERC5267 { - using ShortStrings for *; @@ -235,7 +170,7 @@ index d94e956a..86bb5713 100644 /** * @dev Initializes the domain separator and parameter caches. -@@ -65,29 +56,23 @@ abstract contract EIP712 is IERC5267 { +@@ -65,29 +54,23 @@ abstract contract EIP712 is IERC5267 { * contract upgrade]. */ constructor(string memory name, string memory version) { @@ -273,7 +208,7 @@ index d94e956a..86bb5713 100644 } /** -@@ -128,6 +113,10 @@ abstract contract EIP712 is IERC5267 { +@@ -128,6 +111,10 @@ abstract contract EIP712 is IERC5267 { uint256[] memory extensions ) { @@ -284,7 +219,7 @@ index d94e956a..86bb5713 100644 return ( hex"0f", // 01111 _EIP712Name(), -@@ -142,26 +131,62 @@ abstract contract EIP712 is IERC5267 { +@@ -142,26 +129,62 @@ abstract contract EIP712 is IERC5267 { /** * @dev The name parameter for the EIP712 domain. * From 3fe28e19af0152782f5e1951ad41c1724bd06f03 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 6 Jul 2023 00:14:20 -0300 Subject: [PATCH 140/182] Update lockfile (#4409) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package-lock.json | 760 +++++++++++++++++++++------------------------- 1 file changed, 353 insertions(+), 407 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2e6296d24..3f9a4f5da 100644 --- a/package-lock.json +++ b/package-lock.json @@ -51,34 +51,43 @@ "yargs": "^17.0.0" } }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/@babel/code-frame": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz", - "integrity": "sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz", + "integrity": "sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==", "dev": true, "dependencies": { - "@babel/highlight": "^7.18.6" + "@babel/highlight": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", - "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", + "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.5.tgz", + "integrity": "sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.18.6", + "@babel/helper-validator-identifier": "^7.22.5", "chalk": "^2.0.0", "js-tokens": "^4.0.0" }, @@ -87,9 +96,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.22.3", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.3.tgz", - "integrity": "sha512-XsDuspWKLUsxwCp6r7EhsExHtYfbe5oAGQ19kqngTdCPUoPQzOPdUbD/pB9PJiwb2ptYKQDjSJT3R6dC+EPqfQ==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.6.tgz", + "integrity": "sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ==", "dev": true, "dependencies": { "regenerator-runtime": "^0.13.11" @@ -125,13 +134,13 @@ } }, "node_modules/@changesets/apply-release-plan": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/@changesets/apply-release-plan/-/apply-release-plan-6.1.3.tgz", - "integrity": "sha512-ECDNeoc3nfeAe1jqJb5aFQX7CqzQhD2klXRez2JDb/aVpGUbX673HgKrnrgJRuQR/9f2TtLoYIzrGB9qwD77mg==", + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/@changesets/apply-release-plan/-/apply-release-plan-6.1.4.tgz", + "integrity": "sha512-FMpKF1fRlJyCZVYHr3CbinpZZ+6MwvOtWUuO8uo+svcATEoc1zRDcj23pAurJ2TZ/uVz1wFHH6K3NlACy0PLew==", "dev": true, "dependencies": { "@babel/runtime": "^7.20.1", - "@changesets/config": "^2.3.0", + "@changesets/config": "^2.3.1", "@changesets/get-version-range-type": "^0.3.2", "@changesets/git": "^2.0.0", "@changesets/types": "^5.2.1", @@ -142,39 +151,21 @@ "outdent": "^0.5.0", "prettier": "^2.7.1", "resolve-from": "^5.0.0", - "semver": "^5.4.1" - } - }, - "node_modules/@changesets/apply-release-plan/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, - "bin": { - "semver": "bin/semver" + "semver": "^7.5.3" } }, "node_modules/@changesets/assemble-release-plan": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/@changesets/assemble-release-plan/-/assemble-release-plan-5.2.3.tgz", - "integrity": "sha512-g7EVZCmnWz3zMBAdrcKhid4hkHT+Ft1n0mLussFMcB1dE2zCuwcvGoy9ec3yOgPGF4hoMtgHaMIk3T3TBdvU9g==", + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/@changesets/assemble-release-plan/-/assemble-release-plan-5.2.4.tgz", + "integrity": "sha512-xJkWX+1/CUaOUWTguXEbCDTyWJFECEhmdtbkjhn5GVBGxdP/JwaHBIU9sW3FR6gD07UwZ7ovpiPclQZs+j+mvg==", "dev": true, "dependencies": { "@babel/runtime": "^7.20.1", "@changesets/errors": "^0.1.4", - "@changesets/get-dependents-graph": "^1.3.5", + "@changesets/get-dependents-graph": "^1.3.6", "@changesets/types": "^5.2.1", "@manypkg/get-packages": "^1.1.3", - "semver": "^5.4.1" - } - }, - "node_modules/@changesets/assemble-release-plan/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, - "bin": { - "semver": "bin/semver" + "semver": "^7.5.3" } }, "node_modules/@changesets/changelog-git": { @@ -198,19 +189,19 @@ } }, "node_modules/@changesets/cli": { - "version": "2.26.1", - "resolved": "https://registry.npmjs.org/@changesets/cli/-/cli-2.26.1.tgz", - "integrity": "sha512-XnTa+b51vt057fyAudvDKGB0Sh72xutQZNAdXkCqPBKO2zvs2yYZx5hFZj1u9cbtpwM6Sxtcr02/FQJfZOzemQ==", + "version": "2.26.2", + "resolved": "https://registry.npmjs.org/@changesets/cli/-/cli-2.26.2.tgz", + "integrity": "sha512-dnWrJTmRR8bCHikJHl9b9HW3gXACCehz4OasrXpMp7sx97ECuBGGNjJhjPhdZNCvMy9mn4BWdplI323IbqsRig==", "dev": true, "dependencies": { "@babel/runtime": "^7.20.1", - "@changesets/apply-release-plan": "^6.1.3", - "@changesets/assemble-release-plan": "^5.2.3", + "@changesets/apply-release-plan": "^6.1.4", + "@changesets/assemble-release-plan": "^5.2.4", "@changesets/changelog-git": "^0.1.14", - "@changesets/config": "^2.3.0", + "@changesets/config": "^2.3.1", "@changesets/errors": "^0.1.4", - "@changesets/get-dependents-graph": "^1.3.5", - "@changesets/get-release-plan": "^3.0.16", + "@changesets/get-dependents-graph": "^1.3.6", + "@changesets/get-release-plan": "^3.0.17", "@changesets/git": "^2.0.0", "@changesets/logger": "^0.0.5", "@changesets/pre": "^1.0.14", @@ -219,7 +210,7 @@ "@changesets/write": "^0.2.3", "@manypkg/get-packages": "^1.1.3", "@types/is-ci": "^3.0.0", - "@types/semver": "^6.0.0", + "@types/semver": "^7.5.0", "ansi-colors": "^4.1.3", "chalk": "^2.1.0", "enquirer": "^2.3.0", @@ -232,7 +223,7 @@ "p-limit": "^2.2.0", "preferred-pm": "^3.0.0", "resolve-from": "^5.0.0", - "semver": "^5.4.1", + "semver": "^7.5.3", "spawndamnit": "^2.0.0", "term-size": "^2.1.0", "tty-table": "^4.1.5" @@ -256,23 +247,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@changesets/cli/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, "node_modules/@changesets/config": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@changesets/config/-/config-2.3.0.tgz", - "integrity": "sha512-EgP/px6mhCx8QeaMAvWtRrgyxW08k/Bx2tpGT+M84jEdX37v3VKfh4Cz1BkwrYKuMV2HZKeHOh8sHvja/HcXfQ==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@changesets/config/-/config-2.3.1.tgz", + "integrity": "sha512-PQXaJl82CfIXddUOppj4zWu+987GCw2M+eQcOepxN5s+kvnsZOwjEJO3DH9eVy+OP6Pg/KFEWdsECFEYTtbg6w==", "dev": true, "dependencies": { "@changesets/errors": "^0.1.4", - "@changesets/get-dependents-graph": "^1.3.5", + "@changesets/get-dependents-graph": "^1.3.6", "@changesets/logger": "^0.0.5", "@changesets/types": "^5.2.1", "@manypkg/get-packages": "^1.1.3", @@ -290,25 +272,16 @@ } }, "node_modules/@changesets/get-dependents-graph": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@changesets/get-dependents-graph/-/get-dependents-graph-1.3.5.tgz", - "integrity": "sha512-w1eEvnWlbVDIY8mWXqWuYE9oKhvIaBhzqzo4ITSJY9hgoqQ3RoBqwlcAzg11qHxv/b8ReDWnMrpjpKrW6m1ZTA==", + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/@changesets/get-dependents-graph/-/get-dependents-graph-1.3.6.tgz", + "integrity": "sha512-Q/sLgBANmkvUm09GgRsAvEtY3p1/5OCzgBE5vX3vgb5CvW0j7CEljocx5oPXeQSNph6FXulJlXV3Re/v3K3P3Q==", "dev": true, "dependencies": { "@changesets/types": "^5.2.1", "@manypkg/get-packages": "^1.1.3", "chalk": "^2.1.0", "fs-extra": "^7.0.1", - "semver": "^5.4.1" - } - }, - "node_modules/@changesets/get-dependents-graph/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, - "bin": { - "semver": "bin/semver" + "semver": "^7.5.3" } }, "node_modules/@changesets/get-github-info": { @@ -322,14 +295,14 @@ } }, "node_modules/@changesets/get-release-plan": { - "version": "3.0.16", - "resolved": "https://registry.npmjs.org/@changesets/get-release-plan/-/get-release-plan-3.0.16.tgz", - "integrity": "sha512-OpP9QILpBp1bY2YNIKFzwigKh7Qe9KizRsZomzLe6pK8IUo8onkAAVUD8+JRKSr8R7d4+JRuQrfSSNlEwKyPYg==", + "version": "3.0.17", + "resolved": "https://registry.npmjs.org/@changesets/get-release-plan/-/get-release-plan-3.0.17.tgz", + "integrity": "sha512-6IwKTubNEgoOZwDontYc2x2cWXfr6IKxP3IhKeK+WjyD6y3M4Gl/jdQvBw+m/5zWILSOCAaGLu2ZF6Q+WiPniw==", "dev": true, "dependencies": { "@babel/runtime": "^7.20.1", - "@changesets/assemble-release-plan": "^5.2.3", - "@changesets/config": "^2.3.0", + "@changesets/assemble-release-plan": "^5.2.4", + "@changesets/config": "^2.3.1", "@changesets/pre": "^1.0.14", "@changesets/read": "^0.5.9", "@changesets/types": "^5.2.1", @@ -549,14 +522,14 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.3.tgz", - "integrity": "sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.0.tgz", + "integrity": "sha512-Lj7DECXqIVCqnqjjHMPna4vn6GJcMgul/wuS0je9OZ9gsL0zzDpKPVtcG1HaDVc+9y+qgXneTeUMbCqXJNpH1A==", "dev": true, "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.5.2", + "espree": "^9.6.0", "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", @@ -590,9 +563,9 @@ } }, "node_modules/@eslint/js": { - "version": "8.41.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.41.0.tgz", - "integrity": "sha512-LxcyMGxwmTh2lY9FwHPGWOHmYFCZvbrFCBZL4FzSSsxsRPuhrYUg/49/0KDfW8tnIEaEHtfmn6+NPN+1DqaNmA==", + "version": "8.44.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.44.0.tgz", + "integrity": "sha512-Ag+9YM4ocKQx9AarydN0KY2j0ErMHNIocPDrVo8zAE44xLTjEtz81OdR68/cydGtk6m6jDb5Za3r2useMzYmSw==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -1369,9 +1342,9 @@ } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.8", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", - "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz", + "integrity": "sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==", "dev": true, "dependencies": { "@humanwhocodes/object-schema": "^1.2.1", @@ -2257,9 +2230,9 @@ } }, "node_modules/@openzeppelin/upgrades-core": { - "version": "1.26.2", - "resolved": "https://registry.npmjs.org/@openzeppelin/upgrades-core/-/upgrades-core-1.26.2.tgz", - "integrity": "sha512-TJORrgyun5qflPos/47P3j61gDw+7W+tEirSBOYRxfVL1WGjX1n8iaLrijPIqzyeS1MKguN1nckAMspQ4SKrxw==", + "version": "1.27.1", + "resolved": "https://registry.npmjs.org/@openzeppelin/upgrades-core/-/upgrades-core-1.27.1.tgz", + "integrity": "sha512-6tLcu6jt0nYdJNr+LRicBgP3jp+//B+dixgB3KsvycSglCHNfmBNDf0ZQ3ZquDdLL0QQmKzIs1EBRVp6lNvPnQ==", "dev": true, "dependencies": { "cbor": "^8.0.0", @@ -2267,8 +2240,12 @@ "compare-versions": "^5.0.0", "debug": "^4.1.1", "ethereumjs-util": "^7.0.3", + "minimist": "^1.2.7", "proper-lockfile": "^4.1.1", "solidity-ast": "^0.4.15" + }, + "bin": { + "openzeppelin-upgrades-core": "dist/cli/cli.js" } }, "node_modules/@openzeppelin/upgrades-core/node_modules/ansi-styles": { @@ -2522,9 +2499,9 @@ } }, "node_modules/@truffle/abi-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@truffle/abi-utils/-/abi-utils-1.0.0.tgz", - "integrity": "sha512-h1wGFB28YfByAWm/uBeMCwqDlGsrcMYTumLC/sB/qYhHisi1LK6tV47FEF7zKyf6Al2CtsO28v02+wfLXbUVRg==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@truffle/abi-utils/-/abi-utils-1.0.1.tgz", + "integrity": "sha512-ZQUY3XUxEPdqxNaoXsOqF0spTtb6f5RNlnN4MUrVsJ64sOh0FJsY7rxZiUI3khfePmNh4i2qcJrQlKT36YcWUA==", "dev": true, "dependencies": { "change-case": "3.0.2", @@ -2533,25 +2510,25 @@ } }, "node_modules/@truffle/blockchain-utils": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/@truffle/blockchain-utils/-/blockchain-utils-0.1.7.tgz", - "integrity": "sha512-1nibqGjEHC7KAyDThEFvbm2+EO8zAHee/VjCtxkYBE3ySwP50joh0QCEBjy7K/9z+icpMoDucfxmgaKToBFUgQ==", + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@truffle/blockchain-utils/-/blockchain-utils-0.1.8.tgz", + "integrity": "sha512-ZskpYDNHkXD3ota4iU3pZz6kLth87RC+wDn66Rp2Or+DqqJCKdnmS9GDctBi1EcMPDEi0BqpkdrfBuzA9uIkGg==", "dev": true }, "node_modules/@truffle/codec": { - "version": "0.15.1", - "resolved": "https://registry.npmjs.org/@truffle/codec/-/codec-0.15.1.tgz", - "integrity": "sha512-OBANcmefxEXLApWl/uU1SOHQJixO8pDaRTybP0YMvxPhgyj7G7+wC+fUvnZdmrTJD2WJFLuoYvZbxILmycEvPg==", + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@truffle/codec/-/codec-0.17.0.tgz", + "integrity": "sha512-0Z7DQNCnvW++JuvNj35v/CuJoaFSAp7/+lXWwe+Zoe++E27V+hzRI88ZYxRJa0/q1HE81epd1r0ipqc7WBotig==", "dev": true, "dependencies": { - "@truffle/abi-utils": "^1.0.0", - "@truffle/compile-common": "^0.9.5", + "@truffle/abi-utils": "^1.0.1", + "@truffle/compile-common": "^0.9.6", "big.js": "^6.0.3", "bn.js": "^5.1.3", "cbor": "^5.2.0", "debug": "^4.3.1", "lodash": "^4.17.21", - "semver": "7.3.7", + "semver": "7.5.2", "utf8": "^3.0.0", "web3-utils": "1.10.0" } @@ -2606,9 +2583,9 @@ } }, "node_modules/@truffle/codec/node_modules/semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "version": "7.5.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.2.tgz", + "integrity": "sha512-SoftuTROv/cRjCze/scjGyiDtcUyxw1rgYQSZY7XTmtR5hX+dm76iDbTH8TkLPHCQmlbQVSSbNZCPM2hb0knnQ==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -2627,33 +2604,33 @@ "dev": true }, "node_modules/@truffle/compile-common": { - "version": "0.9.5", - "resolved": "https://registry.npmjs.org/@truffle/compile-common/-/compile-common-0.9.5.tgz", - "integrity": "sha512-qOIT7nYzQsrnpjk8LurKE6EYYvvJIk3rCHfn+xed88aG6F1l4WYtkUKl+Dcwgxgv3LH0khcQOpjqkXK5kUIJ8A==", + "version": "0.9.6", + "resolved": "https://registry.npmjs.org/@truffle/compile-common/-/compile-common-0.9.6.tgz", + "integrity": "sha512-TCcmr1E0GqMZJ2tOaCRNEllxTBJ/g7TuD6jDJpw5Gt9Bw0YO3Cmp6yPQRynRSO4xMJbHUgiEsSfRgIhswut5UA==", "dev": true, "dependencies": { - "@truffle/error": "^0.2.0", + "@truffle/error": "^0.2.1", "colors": "1.4.0" } }, "node_modules/@truffle/compile-common/node_modules/@truffle/error": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@truffle/error/-/error-0.2.0.tgz", - "integrity": "sha512-Fe0/z4WWb7IP2gBnv3l6zqP87Y0kSMs7oiSLakKJq17q3GUunrHSdioKuNspdggxkXIBhEQLhi8C+LJdwmHKWQ==", + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@truffle/error/-/error-0.2.1.tgz", + "integrity": "sha512-5Qy+z9dg9hP37WNdLnXH4b9MzemWrjTufRq7/DTKqimjyxCP/1zlL8gQEMdiSx1BBtAZz0xypkID/jb7AF/Osg==", "dev": true }, "node_modules/@truffle/contract": { - "version": "4.6.22", - "resolved": "https://registry.npmjs.org/@truffle/contract/-/contract-4.6.22.tgz", - "integrity": "sha512-081tM5CBBLgTQX0Fhzp0nlZHnfgojRXweV7/d6v7LHe6QGrGBmgvUy3EIbO+R3P1uaxeGVijMvB4Ok8md9IpYQ==", + "version": "4.6.26", + "resolved": "https://registry.npmjs.org/@truffle/contract/-/contract-4.6.26.tgz", + "integrity": "sha512-B3KM8fW9dKJCzMRD40r+u+iqQtBGFbcMr2GML31iUkjC77Wn/KlM9cCGZiuXcOquWmBlKrpD4nJCoGirPhyPTQ==", "dev": true, "dependencies": { "@ensdomains/ensjs": "^2.1.0", - "@truffle/blockchain-utils": "^0.1.7", + "@truffle/blockchain-utils": "^0.1.8", "@truffle/contract-schema": "^3.4.14", - "@truffle/debug-utils": "^6.0.50", - "@truffle/error": "^0.2.0", - "@truffle/interface-adapter": "^0.5.33", + "@truffle/debug-utils": "^6.0.54", + "@truffle/error": "^0.2.1", + "@truffle/interface-adapter": "^0.5.34", "bignumber.js": "^7.2.1", "debug": "^4.3.1", "ethers": "^4.0.32", @@ -2675,18 +2652,18 @@ } }, "node_modules/@truffle/contract/node_modules/@truffle/error": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@truffle/error/-/error-0.2.0.tgz", - "integrity": "sha512-Fe0/z4WWb7IP2gBnv3l6zqP87Y0kSMs7oiSLakKJq17q3GUunrHSdioKuNspdggxkXIBhEQLhi8C+LJdwmHKWQ==", + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@truffle/error/-/error-0.2.1.tgz", + "integrity": "sha512-5Qy+z9dg9hP37WNdLnXH4b9MzemWrjTufRq7/DTKqimjyxCP/1zlL8gQEMdiSx1BBtAZz0xypkID/jb7AF/Osg==", "dev": true }, "node_modules/@truffle/debug-utils": { - "version": "6.0.50", - "resolved": "https://registry.npmjs.org/@truffle/debug-utils/-/debug-utils-6.0.50.tgz", - "integrity": "sha512-OWdSoOsPW7/jvcO7ASBRzXDzXQNb7dg8UqwoBAI7j7UpdQoCAhz7JQsusSNiFN6g1qrxEpGzeh5iIMDq9WxO3w==", + "version": "6.0.54", + "resolved": "https://registry.npmjs.org/@truffle/debug-utils/-/debug-utils-6.0.54.tgz", + "integrity": "sha512-ENv5TQQv+CJrwSX9AdXXTDHVNHpPfH+yCpRSnM3Sg0dx7IeWJAjGA66/BiucNBUiAgLhV2EcvkMo+4tEPoR+YQ==", "dev": true, "dependencies": { - "@truffle/codec": "^0.15.1", + "@truffle/codec": "^0.17.0", "@trufflesuite/chromafi": "^3.0.0", "bn.js": "^5.1.3", "chalk": "^2.4.2", @@ -2707,9 +2684,9 @@ "dev": true }, "node_modules/@truffle/interface-adapter": { - "version": "0.5.33", - "resolved": "https://registry.npmjs.org/@truffle/interface-adapter/-/interface-adapter-0.5.33.tgz", - "integrity": "sha512-vbVcH2I8hX+wM0Xj9uAjpgxMHqfT+y6m26zSkOVvZ2wo9Ez1slaOJkK1/TZK+7nJitGZSXeJeB4purMDuADvGA==", + "version": "0.5.34", + "resolved": "https://registry.npmjs.org/@truffle/interface-adapter/-/interface-adapter-0.5.34.tgz", + "integrity": "sha512-gPxabfMi2TueE4VxnNuyeudOfvGJQ1ofVC02PFw14cnRQhzH327JikjjQbZ1bT6S7kWl9H6P3hQPFeYFMHdm1g==", "dev": true, "dependencies": { "bn.js": "^5.1.3", @@ -2917,9 +2894,9 @@ } }, "node_modules/@types/semver": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-6.2.3.tgz", - "integrity": "sha512-KQf+QAMWKMrtBMsB8/24w53tEsxllMj6TuA80TT/5igJalLI/zm0L3oXRbIAl4Ohfc85gyHX/jhMwsVkmhLU4A==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==", "dev": true }, "node_modules/abbrev": { @@ -3002,9 +2979,9 @@ } }, "node_modules/acorn": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", - "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", + "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -3473,9 +3450,9 @@ } }, "node_modules/bigint-crypto-utils": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/bigint-crypto-utils/-/bigint-crypto-utils-3.2.2.tgz", - "integrity": "sha512-U1RbE3aX9ayCUVcIPHuPDPKcK3SFOXf93J1UK/iHlJuQB7bhagPIX06/CLpLEsDThJ7KA4Dhrnzynl+d2weTiw==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/bigint-crypto-utils/-/bigint-crypto-utils-3.3.0.tgz", + "integrity": "sha512-jOTSb+drvEDxEq6OuUybOAv/xxoh3cuYRUIPyu8sSHQNKM303UQ2R1DAo45o1AkcIXw6fzbaFI1+xGGdaXs2lg==", "dev": true, "engines": { "node": ">=14.0.0" @@ -3758,9 +3735,9 @@ } }, "node_modules/cacheable-request": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.2.tgz", - "integrity": "sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", + "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", "dev": true, "dependencies": { "clone-response": "^1.0.2", @@ -4440,9 +4417,9 @@ } }, "node_modules/cosmiconfig": { - "version": "8.1.3", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.1.3.tgz", - "integrity": "sha512-/UkO2JKI18b5jVMJUp0lvKFMpa/Gye+ZgZjKD+DGEN9y7NRcf/nK1A0sp67ONmKtnDCNMS44E6jrk0Yc3bDuUw==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.2.0.tgz", + "integrity": "sha512-3rTMnFJA1tCOPwRxtgF4wd7Ab2qvDbL8jX+3smjIbS4HlZBagTlpERbdN7iAbWlrfxE3M8c27kTwTawQ7st+OQ==", "dev": true, "dependencies": { "import-fresh": "^3.2.1", @@ -4515,12 +4492,12 @@ } }, "node_modules/cross-fetch": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz", - "integrity": "sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==", + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.8.tgz", + "integrity": "sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==", "dev": true, "dependencies": { - "node-fetch": "^2.6.11" + "node-fetch": "^2.6.12" } }, "node_modules/cross-spawn": { @@ -5332,16 +5309,16 @@ } }, "node_modules/eslint": { - "version": "8.41.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.41.0.tgz", - "integrity": "sha512-WQDQpzGBOP5IrXPo4Hc0814r4/v2rrIsB0rhT7jtunIalgg6gYXWhRMOejVO8yH21T/FGaxjmFjBMNqcIlmH1Q==", + "version": "8.44.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.44.0.tgz", + "integrity": "sha512-0wpHoUbDUHgNCyvFB5aXLiQVfK9B0at6gUvzy83k4kAsQ/u769TQDX6iKC+aO4upIHO9WSaA3QoXYQDHbNwf1A==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.4.0", - "@eslint/eslintrc": "^2.0.3", - "@eslint/js": "8.41.0", - "@humanwhocodes/config-array": "^0.11.8", + "@eslint/eslintrc": "^2.1.0", + "@eslint/js": "8.44.0", + "@humanwhocodes/config-array": "^0.11.10", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "ajv": "^6.10.0", @@ -5352,7 +5329,7 @@ "escape-string-regexp": "^4.0.0", "eslint-scope": "^7.2.0", "eslint-visitor-keys": "^3.4.1", - "espree": "^9.5.2", + "espree": "^9.6.0", "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -5372,7 +5349,7 @@ "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.1", + "optionator": "^0.9.3", "strip-ansi": "^6.0.1", "strip-json-comments": "^3.1.0", "text-table": "^0.2.0" @@ -5607,12 +5584,12 @@ } }, "node_modules/espree": { - "version": "9.5.2", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.2.tgz", - "integrity": "sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==", + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.0.tgz", + "integrity": "sha512-1FH/IiruXZ84tpUlm0aCUEwMl2Ho5ilqVh0VvQXw+byAz/4SAciyHLlfmL5WYqsvD38oymdUwBss0LtK8m4s/A==", "dev": true, "dependencies": { - "acorn": "^8.8.0", + "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^3.4.1" }, @@ -6707,9 +6684,9 @@ "dev": true }, "node_modules/fast-glob": { - "version": "3.2.12", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", - "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.0.tgz", + "integrity": "sha512-ChDuvbOypPuNjO8yIDf36x7BlZX1smcUMTTcyoIjycexOxd6DFsKsg21qVBzEmr3G7fUKIRy2/psii+CIUt7FA==", "dev": true, "dependencies": { "@nodelib/fs.stat": "^2.0.2", @@ -7401,9 +7378,9 @@ } }, "node_modules/hardhat": { - "version": "2.14.1", - "resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.14.1.tgz", - "integrity": "sha512-H3Qp/UKyQGmPDDBSfMoSyH18rRnac90rsb0LNer+sKe6at6rxLe4D5j+M+1icqZQF02iLPjNRwc/PA8OPf757A==", + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.16.1.tgz", + "integrity": "sha512-QpBjGXFhhSYoYBGEHyoau/A63crZOP+i3GbNxzLGkL6IklzT+piN14+wGnINNCg5BLSKisQI/RAySPzaWRcx/g==", "dev": true, "dependencies": { "@ethersproject/abi": "^5.1.2", @@ -7445,7 +7422,6 @@ "mnemonist": "^0.38.0", "mocha": "^10.0.0", "p-map": "^4.0.0", - "qs": "^6.7.0", "raw-body": "^2.4.1", "resolve": "1.17.0", "semver": "^6.3.0", @@ -8635,9 +8611,9 @@ "dev": true }, "node_modules/js-sdsl": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.0.tgz", - "integrity": "sha512-FfVSdx6pJ41Oa+CF7RDaFmTnCaFhua+SNYQX74riGOpl96x+2jQCqEfQ2bnXu/5DPCqlRuiqyvTJM0Qjz26IVg==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.1.tgz", + "integrity": "sha512-6Gsx8R0RucyePbWqPssR8DyfuXmLBooYN5cZFZKjHGnQuaf7pEzhtpceagJxVu4LqhYY5EYA7nko3FmeHZ1KbA==", "dev": true, "funding": { "type": "opencollective", @@ -9978,9 +9954,9 @@ } }, "node_modules/node-fetch": { - "version": "2.6.11", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.11.tgz", - "integrity": "sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==", + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.12.tgz", + "integrity": "sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==", "dev": true, "dependencies": { "whatwg-url": "^5.0.0" @@ -10234,17 +10210,17 @@ } }, "node_modules/optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", "dev": true, "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" + "type-check": "^0.4.0" }, "engines": { "node": ">= 0.8.0" @@ -10832,18 +10808,12 @@ ] }, "node_modules/qs": { - "version": "6.11.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", - "integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==", + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", + "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", "dev": true, - "dependencies": { - "side-channel": "^1.0.4" - }, "engines": { "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" } }, "node_modules/query-string": { @@ -11183,15 +11153,6 @@ "request": "^2.34" } }, - "node_modules/request/node_modules/qs": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", - "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", - "dev": true, - "engines": { - "node": ">=0.6" - } - }, "node_modules/request/node_modules/uuid": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", @@ -11596,9 +11557,9 @@ } }, "node_modules/semver": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", - "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", + "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -12796,13 +12757,13 @@ } }, "node_modules/solidity-coverage": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/solidity-coverage/-/solidity-coverage-0.8.2.tgz", - "integrity": "sha512-cv2bWb7lOXPE9/SSleDO6czkFiMHgP4NXPj+iW9W7iEKLBk7Cj0AGBiNmGX3V1totl9wjPrT0gHmABZKZt65rQ==", + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/solidity-coverage/-/solidity-coverage-0.8.4.tgz", + "integrity": "sha512-xeHOfBOjdMF6hWTbt42iH4x+7j1Atmrf5OldDPMxI+i/COdExUxszOswD9qqvcBTaLGiOrrpnh9UZjSpt4rBsg==", "dev": true, "dependencies": { "@ethersproject/abi": "^5.0.9", - "@solidity-parser/parser": "^0.14.1", + "@solidity-parser/parser": "^0.16.0", "chalk": "^2.4.2", "death": "^1.1.0", "detect-port": "^1.3.0", @@ -12829,6 +12790,15 @@ "hardhat": "^2.11.0" } }, + "node_modules/solidity-coverage/node_modules/@solidity-parser/parser": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.16.0.tgz", + "integrity": "sha512-ESipEcHyRHg4Np4SqBCfcXwyxxna1DgFVz69bgpLV8vzl/NP1DtcKsJ4dJZXWQhY/Z4J2LeKBiOkOVZn9ct33Q==", + "dev": true, + "dependencies": { + "antlr4ts": "^0.5.0-alpha.4" + } + }, "node_modules/solidity-coverage/node_modules/ansi-colors": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", @@ -15438,36 +15408,42 @@ } }, "dependencies": { + "@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true + }, "@babel/code-frame": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz", - "integrity": "sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz", + "integrity": "sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==", "dev": true, "requires": { - "@babel/highlight": "^7.18.6" + "@babel/highlight": "^7.22.5" } }, "@babel/helper-validator-identifier": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", - "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", + "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==", "dev": true }, "@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.5.tgz", + "integrity": "sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.18.6", + "@babel/helper-validator-identifier": "^7.22.5", "chalk": "^2.0.0", "js-tokens": "^4.0.0" } }, "@babel/runtime": { - "version": "7.22.3", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.3.tgz", - "integrity": "sha512-XsDuspWKLUsxwCp6r7EhsExHtYfbe5oAGQ19kqngTdCPUoPQzOPdUbD/pB9PJiwb2ptYKQDjSJT3R6dC+EPqfQ==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.6.tgz", + "integrity": "sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ==", "dev": true, "requires": { "regenerator-runtime": "^0.13.11" @@ -15500,13 +15476,13 @@ } }, "@changesets/apply-release-plan": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/@changesets/apply-release-plan/-/apply-release-plan-6.1.3.tgz", - "integrity": "sha512-ECDNeoc3nfeAe1jqJb5aFQX7CqzQhD2klXRez2JDb/aVpGUbX673HgKrnrgJRuQR/9f2TtLoYIzrGB9qwD77mg==", + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/@changesets/apply-release-plan/-/apply-release-plan-6.1.4.tgz", + "integrity": "sha512-FMpKF1fRlJyCZVYHr3CbinpZZ+6MwvOtWUuO8uo+svcATEoc1zRDcj23pAurJ2TZ/uVz1wFHH6K3NlACy0PLew==", "dev": true, "requires": { "@babel/runtime": "^7.20.1", - "@changesets/config": "^2.3.0", + "@changesets/config": "^2.3.1", "@changesets/get-version-range-type": "^0.3.2", "@changesets/git": "^2.0.0", "@changesets/types": "^5.2.1", @@ -15517,37 +15493,21 @@ "outdent": "^0.5.0", "prettier": "^2.7.1", "resolve-from": "^5.0.0", - "semver": "^5.4.1" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } + "semver": "^7.5.3" } }, "@changesets/assemble-release-plan": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/@changesets/assemble-release-plan/-/assemble-release-plan-5.2.3.tgz", - "integrity": "sha512-g7EVZCmnWz3zMBAdrcKhid4hkHT+Ft1n0mLussFMcB1dE2zCuwcvGoy9ec3yOgPGF4hoMtgHaMIk3T3TBdvU9g==", + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/@changesets/assemble-release-plan/-/assemble-release-plan-5.2.4.tgz", + "integrity": "sha512-xJkWX+1/CUaOUWTguXEbCDTyWJFECEhmdtbkjhn5GVBGxdP/JwaHBIU9sW3FR6gD07UwZ7ovpiPclQZs+j+mvg==", "dev": true, "requires": { "@babel/runtime": "^7.20.1", "@changesets/errors": "^0.1.4", - "@changesets/get-dependents-graph": "^1.3.5", + "@changesets/get-dependents-graph": "^1.3.6", "@changesets/types": "^5.2.1", "@manypkg/get-packages": "^1.1.3", - "semver": "^5.4.1" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } + "semver": "^7.5.3" } }, "@changesets/changelog-git": { @@ -15571,19 +15531,19 @@ } }, "@changesets/cli": { - "version": "2.26.1", - "resolved": "https://registry.npmjs.org/@changesets/cli/-/cli-2.26.1.tgz", - "integrity": "sha512-XnTa+b51vt057fyAudvDKGB0Sh72xutQZNAdXkCqPBKO2zvs2yYZx5hFZj1u9cbtpwM6Sxtcr02/FQJfZOzemQ==", + "version": "2.26.2", + "resolved": "https://registry.npmjs.org/@changesets/cli/-/cli-2.26.2.tgz", + "integrity": "sha512-dnWrJTmRR8bCHikJHl9b9HW3gXACCehz4OasrXpMp7sx97ECuBGGNjJhjPhdZNCvMy9mn4BWdplI323IbqsRig==", "dev": true, "requires": { "@babel/runtime": "^7.20.1", - "@changesets/apply-release-plan": "^6.1.3", - "@changesets/assemble-release-plan": "^5.2.3", + "@changesets/apply-release-plan": "^6.1.4", + "@changesets/assemble-release-plan": "^5.2.4", "@changesets/changelog-git": "^0.1.14", - "@changesets/config": "^2.3.0", + "@changesets/config": "^2.3.1", "@changesets/errors": "^0.1.4", - "@changesets/get-dependents-graph": "^1.3.5", - "@changesets/get-release-plan": "^3.0.16", + "@changesets/get-dependents-graph": "^1.3.6", + "@changesets/get-release-plan": "^3.0.17", "@changesets/git": "^2.0.0", "@changesets/logger": "^0.0.5", "@changesets/pre": "^1.0.14", @@ -15592,7 +15552,7 @@ "@changesets/write": "^0.2.3", "@manypkg/get-packages": "^1.1.3", "@types/is-ci": "^3.0.0", - "@types/semver": "^6.0.0", + "@types/semver": "^7.5.0", "ansi-colors": "^4.1.3", "chalk": "^2.1.0", "enquirer": "^2.3.0", @@ -15605,7 +15565,7 @@ "p-limit": "^2.2.0", "preferred-pm": "^3.0.0", "resolve-from": "^5.0.0", - "semver": "^5.4.1", + "semver": "^7.5.3", "spawndamnit": "^2.0.0", "term-size": "^2.1.0", "tty-table": "^4.1.5" @@ -15619,23 +15579,17 @@ "requires": { "p-try": "^2.0.0" } - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true } } }, "@changesets/config": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@changesets/config/-/config-2.3.0.tgz", - "integrity": "sha512-EgP/px6mhCx8QeaMAvWtRrgyxW08k/Bx2tpGT+M84jEdX37v3VKfh4Cz1BkwrYKuMV2HZKeHOh8sHvja/HcXfQ==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@changesets/config/-/config-2.3.1.tgz", + "integrity": "sha512-PQXaJl82CfIXddUOppj4zWu+987GCw2M+eQcOepxN5s+kvnsZOwjEJO3DH9eVy+OP6Pg/KFEWdsECFEYTtbg6w==", "dev": true, "requires": { "@changesets/errors": "^0.1.4", - "@changesets/get-dependents-graph": "^1.3.5", + "@changesets/get-dependents-graph": "^1.3.6", "@changesets/logger": "^0.0.5", "@changesets/types": "^5.2.1", "@manypkg/get-packages": "^1.1.3", @@ -15653,24 +15607,16 @@ } }, "@changesets/get-dependents-graph": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@changesets/get-dependents-graph/-/get-dependents-graph-1.3.5.tgz", - "integrity": "sha512-w1eEvnWlbVDIY8mWXqWuYE9oKhvIaBhzqzo4ITSJY9hgoqQ3RoBqwlcAzg11qHxv/b8ReDWnMrpjpKrW6m1ZTA==", + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/@changesets/get-dependents-graph/-/get-dependents-graph-1.3.6.tgz", + "integrity": "sha512-Q/sLgBANmkvUm09GgRsAvEtY3p1/5OCzgBE5vX3vgb5CvW0j7CEljocx5oPXeQSNph6FXulJlXV3Re/v3K3P3Q==", "dev": true, "requires": { "@changesets/types": "^5.2.1", "@manypkg/get-packages": "^1.1.3", "chalk": "^2.1.0", "fs-extra": "^7.0.1", - "semver": "^5.4.1" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } + "semver": "^7.5.3" } }, "@changesets/get-github-info": { @@ -15684,14 +15630,14 @@ } }, "@changesets/get-release-plan": { - "version": "3.0.16", - "resolved": "https://registry.npmjs.org/@changesets/get-release-plan/-/get-release-plan-3.0.16.tgz", - "integrity": "sha512-OpP9QILpBp1bY2YNIKFzwigKh7Qe9KizRsZomzLe6pK8IUo8onkAAVUD8+JRKSr8R7d4+JRuQrfSSNlEwKyPYg==", + "version": "3.0.17", + "resolved": "https://registry.npmjs.org/@changesets/get-release-plan/-/get-release-plan-3.0.17.tgz", + "integrity": "sha512-6IwKTubNEgoOZwDontYc2x2cWXfr6IKxP3IhKeK+WjyD6y3M4Gl/jdQvBw+m/5zWILSOCAaGLu2ZF6Q+WiPniw==", "dev": true, "requires": { "@babel/runtime": "^7.20.1", - "@changesets/assemble-release-plan": "^5.2.3", - "@changesets/config": "^2.3.0", + "@changesets/assemble-release-plan": "^5.2.4", + "@changesets/config": "^2.3.1", "@changesets/pre": "^1.0.14", "@changesets/read": "^0.5.9", "@changesets/types": "^5.2.1", @@ -15892,14 +15838,14 @@ "dev": true }, "@eslint/eslintrc": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.3.tgz", - "integrity": "sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.0.tgz", + "integrity": "sha512-Lj7DECXqIVCqnqjjHMPna4vn6GJcMgul/wuS0je9OZ9gsL0zzDpKPVtcG1HaDVc+9y+qgXneTeUMbCqXJNpH1A==", "dev": true, "requires": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.5.2", + "espree": "^9.6.0", "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", @@ -15926,9 +15872,9 @@ } }, "@eslint/js": { - "version": "8.41.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.41.0.tgz", - "integrity": "sha512-LxcyMGxwmTh2lY9FwHPGWOHmYFCZvbrFCBZL4FzSSsxsRPuhrYUg/49/0KDfW8tnIEaEHtfmn6+NPN+1DqaNmA==", + "version": "8.44.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.44.0.tgz", + "integrity": "sha512-Ag+9YM4ocKQx9AarydN0KY2j0ErMHNIocPDrVo8zAE44xLTjEtz81OdR68/cydGtk6m6jDb5Za3r2useMzYmSw==", "dev": true }, "@ethereumjs/common": { @@ -16392,9 +16338,9 @@ "dev": true }, "@humanwhocodes/config-array": { - "version": "0.11.8", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", - "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz", + "integrity": "sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==", "dev": true, "requires": { "@humanwhocodes/object-schema": "^1.2.1", @@ -17066,9 +17012,9 @@ } }, "@openzeppelin/upgrades-core": { - "version": "1.26.2", - "resolved": "https://registry.npmjs.org/@openzeppelin/upgrades-core/-/upgrades-core-1.26.2.tgz", - "integrity": "sha512-TJORrgyun5qflPos/47P3j61gDw+7W+tEirSBOYRxfVL1WGjX1n8iaLrijPIqzyeS1MKguN1nckAMspQ4SKrxw==", + "version": "1.27.1", + "resolved": "https://registry.npmjs.org/@openzeppelin/upgrades-core/-/upgrades-core-1.27.1.tgz", + "integrity": "sha512-6tLcu6jt0nYdJNr+LRicBgP3jp+//B+dixgB3KsvycSglCHNfmBNDf0ZQ3ZquDdLL0QQmKzIs1EBRVp6lNvPnQ==", "dev": true, "requires": { "cbor": "^8.0.0", @@ -17076,6 +17022,7 @@ "compare-versions": "^5.0.0", "debug": "^4.1.1", "ethereumjs-util": "^7.0.3", + "minimist": "^1.2.7", "proper-lockfile": "^4.1.1", "solidity-ast": "^0.4.15" }, @@ -17264,9 +17211,9 @@ } }, "@truffle/abi-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@truffle/abi-utils/-/abi-utils-1.0.0.tgz", - "integrity": "sha512-h1wGFB28YfByAWm/uBeMCwqDlGsrcMYTumLC/sB/qYhHisi1LK6tV47FEF7zKyf6Al2CtsO28v02+wfLXbUVRg==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@truffle/abi-utils/-/abi-utils-1.0.1.tgz", + "integrity": "sha512-ZQUY3XUxEPdqxNaoXsOqF0spTtb6f5RNlnN4MUrVsJ64sOh0FJsY7rxZiUI3khfePmNh4i2qcJrQlKT36YcWUA==", "dev": true, "requires": { "change-case": "3.0.2", @@ -17275,25 +17222,25 @@ } }, "@truffle/blockchain-utils": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/@truffle/blockchain-utils/-/blockchain-utils-0.1.7.tgz", - "integrity": "sha512-1nibqGjEHC7KAyDThEFvbm2+EO8zAHee/VjCtxkYBE3ySwP50joh0QCEBjy7K/9z+icpMoDucfxmgaKToBFUgQ==", + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@truffle/blockchain-utils/-/blockchain-utils-0.1.8.tgz", + "integrity": "sha512-ZskpYDNHkXD3ota4iU3pZz6kLth87RC+wDn66Rp2Or+DqqJCKdnmS9GDctBi1EcMPDEi0BqpkdrfBuzA9uIkGg==", "dev": true }, "@truffle/codec": { - "version": "0.15.1", - "resolved": "https://registry.npmjs.org/@truffle/codec/-/codec-0.15.1.tgz", - "integrity": "sha512-OBANcmefxEXLApWl/uU1SOHQJixO8pDaRTybP0YMvxPhgyj7G7+wC+fUvnZdmrTJD2WJFLuoYvZbxILmycEvPg==", + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@truffle/codec/-/codec-0.17.0.tgz", + "integrity": "sha512-0Z7DQNCnvW++JuvNj35v/CuJoaFSAp7/+lXWwe+Zoe++E27V+hzRI88ZYxRJa0/q1HE81epd1r0ipqc7WBotig==", "dev": true, "requires": { - "@truffle/abi-utils": "^1.0.0", - "@truffle/compile-common": "^0.9.5", + "@truffle/abi-utils": "^1.0.1", + "@truffle/compile-common": "^0.9.6", "big.js": "^6.0.3", "bn.js": "^5.1.3", "cbor": "^5.2.0", "debug": "^4.3.1", "lodash": "^4.17.21", - "semver": "7.3.7", + "semver": "7.5.2", "utf8": "^3.0.0", "web3-utils": "1.10.0" }, @@ -17336,9 +17283,9 @@ "dev": true }, "semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "version": "7.5.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.2.tgz", + "integrity": "sha512-SoftuTROv/cRjCze/scjGyiDtcUyxw1rgYQSZY7XTmtR5hX+dm76iDbTH8TkLPHCQmlbQVSSbNZCPM2hb0knnQ==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -17353,35 +17300,35 @@ } }, "@truffle/compile-common": { - "version": "0.9.5", - "resolved": "https://registry.npmjs.org/@truffle/compile-common/-/compile-common-0.9.5.tgz", - "integrity": "sha512-qOIT7nYzQsrnpjk8LurKE6EYYvvJIk3rCHfn+xed88aG6F1l4WYtkUKl+Dcwgxgv3LH0khcQOpjqkXK5kUIJ8A==", + "version": "0.9.6", + "resolved": "https://registry.npmjs.org/@truffle/compile-common/-/compile-common-0.9.6.tgz", + "integrity": "sha512-TCcmr1E0GqMZJ2tOaCRNEllxTBJ/g7TuD6jDJpw5Gt9Bw0YO3Cmp6yPQRynRSO4xMJbHUgiEsSfRgIhswut5UA==", "dev": true, "requires": { - "@truffle/error": "^0.2.0", + "@truffle/error": "^0.2.1", "colors": "1.4.0" }, "dependencies": { "@truffle/error": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@truffle/error/-/error-0.2.0.tgz", - "integrity": "sha512-Fe0/z4WWb7IP2gBnv3l6zqP87Y0kSMs7oiSLakKJq17q3GUunrHSdioKuNspdggxkXIBhEQLhi8C+LJdwmHKWQ==", + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@truffle/error/-/error-0.2.1.tgz", + "integrity": "sha512-5Qy+z9dg9hP37WNdLnXH4b9MzemWrjTufRq7/DTKqimjyxCP/1zlL8gQEMdiSx1BBtAZz0xypkID/jb7AF/Osg==", "dev": true } } }, "@truffle/contract": { - "version": "4.6.22", - "resolved": "https://registry.npmjs.org/@truffle/contract/-/contract-4.6.22.tgz", - "integrity": "sha512-081tM5CBBLgTQX0Fhzp0nlZHnfgojRXweV7/d6v7LHe6QGrGBmgvUy3EIbO+R3P1uaxeGVijMvB4Ok8md9IpYQ==", + "version": "4.6.26", + "resolved": "https://registry.npmjs.org/@truffle/contract/-/contract-4.6.26.tgz", + "integrity": "sha512-B3KM8fW9dKJCzMRD40r+u+iqQtBGFbcMr2GML31iUkjC77Wn/KlM9cCGZiuXcOquWmBlKrpD4nJCoGirPhyPTQ==", "dev": true, "requires": { "@ensdomains/ensjs": "^2.1.0", - "@truffle/blockchain-utils": "^0.1.7", + "@truffle/blockchain-utils": "^0.1.8", "@truffle/contract-schema": "^3.4.14", - "@truffle/debug-utils": "^6.0.50", - "@truffle/error": "^0.2.0", - "@truffle/interface-adapter": "^0.5.33", + "@truffle/debug-utils": "^6.0.54", + "@truffle/error": "^0.2.1", + "@truffle/interface-adapter": "^0.5.34", "bignumber.js": "^7.2.1", "debug": "^4.3.1", "ethers": "^4.0.32", @@ -17393,9 +17340,9 @@ }, "dependencies": { "@truffle/error": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@truffle/error/-/error-0.2.0.tgz", - "integrity": "sha512-Fe0/z4WWb7IP2gBnv3l6zqP87Y0kSMs7oiSLakKJq17q3GUunrHSdioKuNspdggxkXIBhEQLhi8C+LJdwmHKWQ==", + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@truffle/error/-/error-0.2.1.tgz", + "integrity": "sha512-5Qy+z9dg9hP37WNdLnXH4b9MzemWrjTufRq7/DTKqimjyxCP/1zlL8gQEMdiSx1BBtAZz0xypkID/jb7AF/Osg==", "dev": true } } @@ -17411,12 +17358,12 @@ } }, "@truffle/debug-utils": { - "version": "6.0.50", - "resolved": "https://registry.npmjs.org/@truffle/debug-utils/-/debug-utils-6.0.50.tgz", - "integrity": "sha512-OWdSoOsPW7/jvcO7ASBRzXDzXQNb7dg8UqwoBAI7j7UpdQoCAhz7JQsusSNiFN6g1qrxEpGzeh5iIMDq9WxO3w==", + "version": "6.0.54", + "resolved": "https://registry.npmjs.org/@truffle/debug-utils/-/debug-utils-6.0.54.tgz", + "integrity": "sha512-ENv5TQQv+CJrwSX9AdXXTDHVNHpPfH+yCpRSnM3Sg0dx7IeWJAjGA66/BiucNBUiAgLhV2EcvkMo+4tEPoR+YQ==", "dev": true, "requires": { - "@truffle/codec": "^0.15.1", + "@truffle/codec": "^0.17.0", "@trufflesuite/chromafi": "^3.0.0", "bn.js": "^5.1.3", "chalk": "^2.4.2", @@ -17439,9 +17386,9 @@ "dev": true }, "@truffle/interface-adapter": { - "version": "0.5.33", - "resolved": "https://registry.npmjs.org/@truffle/interface-adapter/-/interface-adapter-0.5.33.tgz", - "integrity": "sha512-vbVcH2I8hX+wM0Xj9uAjpgxMHqfT+y6m26zSkOVvZ2wo9Ez1slaOJkK1/TZK+7nJitGZSXeJeB4purMDuADvGA==", + "version": "0.5.34", + "resolved": "https://registry.npmjs.org/@truffle/interface-adapter/-/interface-adapter-0.5.34.tgz", + "integrity": "sha512-gPxabfMi2TueE4VxnNuyeudOfvGJQ1ofVC02PFw14cnRQhzH327JikjjQbZ1bT6S7kWl9H6P3hQPFeYFMHdm1g==", "dev": true, "requires": { "bn.js": "^5.1.3", @@ -17651,9 +17598,9 @@ } }, "@types/semver": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-6.2.3.tgz", - "integrity": "sha512-KQf+QAMWKMrtBMsB8/24w53tEsxllMj6TuA80TT/5igJalLI/zm0L3oXRbIAl4Ohfc85gyHX/jhMwsVkmhLU4A==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==", "dev": true }, "abbrev": { @@ -17715,9 +17662,9 @@ } }, "acorn": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", - "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", + "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", "dev": true }, "acorn-jsx": { @@ -18064,9 +18011,9 @@ "dev": true }, "bigint-crypto-utils": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/bigint-crypto-utils/-/bigint-crypto-utils-3.2.2.tgz", - "integrity": "sha512-U1RbE3aX9ayCUVcIPHuPDPKcK3SFOXf93J1UK/iHlJuQB7bhagPIX06/CLpLEsDThJ7KA4Dhrnzynl+d2weTiw==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/bigint-crypto-utils/-/bigint-crypto-utils-3.3.0.tgz", + "integrity": "sha512-jOTSb+drvEDxEq6OuUybOAv/xxoh3cuYRUIPyu8sSHQNKM303UQ2R1DAo45o1AkcIXw6fzbaFI1+xGGdaXs2lg==", "dev": true }, "bignumber.js": { @@ -18302,9 +18249,9 @@ "dev": true }, "cacheable-request": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.2.tgz", - "integrity": "sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", + "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", "dev": true, "requires": { "clone-response": "^1.0.2", @@ -18845,9 +18792,9 @@ } }, "cosmiconfig": { - "version": "8.1.3", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.1.3.tgz", - "integrity": "sha512-/UkO2JKI18b5jVMJUp0lvKFMpa/Gye+ZgZjKD+DGEN9y7NRcf/nK1A0sp67ONmKtnDCNMS44E6jrk0Yc3bDuUw==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.2.0.tgz", + "integrity": "sha512-3rTMnFJA1tCOPwRxtgF4wd7Ab2qvDbL8jX+3smjIbS4HlZBagTlpERbdN7iAbWlrfxE3M8c27kTwTawQ7st+OQ==", "dev": true, "requires": { "import-fresh": "^3.2.1", @@ -18907,12 +18854,12 @@ } }, "cross-fetch": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz", - "integrity": "sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==", + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.8.tgz", + "integrity": "sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==", "dev": true, "requires": { - "node-fetch": "^2.6.11" + "node-fetch": "^2.6.12" } }, "cross-spawn": { @@ -19538,16 +19485,16 @@ } }, "eslint": { - "version": "8.41.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.41.0.tgz", - "integrity": "sha512-WQDQpzGBOP5IrXPo4Hc0814r4/v2rrIsB0rhT7jtunIalgg6gYXWhRMOejVO8yH21T/FGaxjmFjBMNqcIlmH1Q==", + "version": "8.44.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.44.0.tgz", + "integrity": "sha512-0wpHoUbDUHgNCyvFB5aXLiQVfK9B0at6gUvzy83k4kAsQ/u769TQDX6iKC+aO4upIHO9WSaA3QoXYQDHbNwf1A==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.4.0", - "@eslint/eslintrc": "^2.0.3", - "@eslint/js": "8.41.0", - "@humanwhocodes/config-array": "^0.11.8", + "@eslint/eslintrc": "^2.1.0", + "@eslint/js": "8.44.0", + "@humanwhocodes/config-array": "^0.11.10", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "ajv": "^6.10.0", @@ -19558,7 +19505,7 @@ "escape-string-regexp": "^4.0.0", "eslint-scope": "^7.2.0", "eslint-visitor-keys": "^3.4.1", - "espree": "^9.5.2", + "espree": "^9.6.0", "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -19578,7 +19525,7 @@ "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.1", + "optionator": "^0.9.3", "strip-ansi": "^6.0.1", "strip-json-comments": "^3.1.0", "text-table": "^0.2.0" @@ -19732,12 +19679,12 @@ "dev": true }, "espree": { - "version": "9.5.2", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.2.tgz", - "integrity": "sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==", + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.0.tgz", + "integrity": "sha512-1FH/IiruXZ84tpUlm0aCUEwMl2Ho5ilqVh0VvQXw+byAz/4SAciyHLlfmL5WYqsvD38oymdUwBss0LtK8m4s/A==", "dev": true, "requires": { - "acorn": "^8.8.0", + "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^3.4.1" } @@ -20668,9 +20615,9 @@ "dev": true }, "fast-glob": { - "version": "3.2.12", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", - "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.0.tgz", + "integrity": "sha512-ChDuvbOypPuNjO8yIDf36x7BlZX1smcUMTTcyoIjycexOxd6DFsKsg21qVBzEmr3G7fUKIRy2/psii+CIUt7FA==", "dev": true, "requires": { "@nodelib/fs.stat": "^2.0.2", @@ -21205,9 +21152,9 @@ "dev": true }, "hardhat": { - "version": "2.14.1", - "resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.14.1.tgz", - "integrity": "sha512-H3Qp/UKyQGmPDDBSfMoSyH18rRnac90rsb0LNer+sKe6at6rxLe4D5j+M+1icqZQF02iLPjNRwc/PA8OPf757A==", + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.16.1.tgz", + "integrity": "sha512-QpBjGXFhhSYoYBGEHyoau/A63crZOP+i3GbNxzLGkL6IklzT+piN14+wGnINNCg5BLSKisQI/RAySPzaWRcx/g==", "dev": true, "requires": { "@ethersproject/abi": "^5.1.2", @@ -21249,7 +21196,6 @@ "mnemonist": "^0.38.0", "mocha": "^10.0.0", "p-map": "^4.0.0", - "qs": "^6.7.0", "raw-body": "^2.4.1", "resolve": "1.17.0", "semver": "^6.3.0", @@ -22126,9 +22072,9 @@ "dev": true }, "js-sdsl": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.0.tgz", - "integrity": "sha512-FfVSdx6pJ41Oa+CF7RDaFmTnCaFhua+SNYQX74riGOpl96x+2jQCqEfQ2bnXu/5DPCqlRuiqyvTJM0Qjz26IVg==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.1.tgz", + "integrity": "sha512-6Gsx8R0RucyePbWqPssR8DyfuXmLBooYN5cZFZKjHGnQuaf7pEzhtpceagJxVu4LqhYY5EYA7nko3FmeHZ1KbA==", "dev": true }, "js-sha3": { @@ -23190,9 +23136,9 @@ } }, "node-fetch": { - "version": "2.6.11", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.11.tgz", - "integrity": "sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==", + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.12.tgz", + "integrity": "sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==", "dev": true, "requires": { "whatwg-url": "^5.0.0" @@ -23376,17 +23322,17 @@ } }, "optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", "dev": true, "requires": { + "@aashutoshrathi/word-wrap": "^1.2.3", "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" + "type-check": "^0.4.0" } }, "os-locale": { @@ -23825,13 +23771,10 @@ "dev": true }, "qs": { - "version": "6.11.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", - "integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==", - "dev": true, - "requires": { - "side-channel": "^1.0.4" - } + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", + "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", + "dev": true }, "query-string": { "version": "5.1.1", @@ -24064,12 +24007,6 @@ "uuid": "^3.3.2" }, "dependencies": { - "qs": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", - "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", - "dev": true - }, "uuid": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", @@ -24382,9 +24319,9 @@ } }, "semver": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", - "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", + "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -25290,13 +25227,13 @@ "optional": true }, "solidity-coverage": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/solidity-coverage/-/solidity-coverage-0.8.2.tgz", - "integrity": "sha512-cv2bWb7lOXPE9/SSleDO6czkFiMHgP4NXPj+iW9W7iEKLBk7Cj0AGBiNmGX3V1totl9wjPrT0gHmABZKZt65rQ==", + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/solidity-coverage/-/solidity-coverage-0.8.4.tgz", + "integrity": "sha512-xeHOfBOjdMF6hWTbt42iH4x+7j1Atmrf5OldDPMxI+i/COdExUxszOswD9qqvcBTaLGiOrrpnh9UZjSpt4rBsg==", "dev": true, "requires": { "@ethersproject/abi": "^5.0.9", - "@solidity-parser/parser": "^0.14.1", + "@solidity-parser/parser": "^0.16.0", "chalk": "^2.4.2", "death": "^1.1.0", "detect-port": "^1.3.0", @@ -25317,6 +25254,15 @@ "web3-utils": "^1.3.6" }, "dependencies": { + "@solidity-parser/parser": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.16.0.tgz", + "integrity": "sha512-ESipEcHyRHg4Np4SqBCfcXwyxxna1DgFVz69bgpLV8vzl/NP1DtcKsJ4dJZXWQhY/Z4J2LeKBiOkOVZn9ct33Q==", + "dev": true, + "requires": { + "antlr4ts": "^0.5.0-alpha.4" + } + }, "ansi-colors": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", From 6bf68a41d19f3d6e364d8c207cb7d1a9a8e542e1 Mon Sep 17 00:00:00 2001 From: Francisco Date: Thu, 6 Jul 2023 05:00:34 -0300 Subject: [PATCH 141/182] Remove "available since" comments (#4424) Co-authored-by: Hadrien Croubois --- contracts/access/AccessControl.sol | 4 - .../access/AccessControlDefaultAdminRules.sol | 2 - contracts/access/IAccessControl.sol | 2 - .../IAccessControlDefaultAdminRules.sol | 2 - contracts/governance/Governor.sol | 2 - contracts/governance/IGovernor.sol | 2 - contracts/governance/TimelockController.sol | 2 - .../GovernorCompatibilityBravo.sol | 2 - .../IGovernorCompatibilityBravo.sol | 2 - .../extensions/GovernorCountingSimple.sol | 2 - .../extensions/GovernorPreventLateQuorum.sol | 2 - .../extensions/GovernorSettings.sol | 2 - .../extensions/GovernorTimelockCompound.sol | 2 - .../extensions/GovernorTimelockControl.sol | 2 - .../governance/extensions/GovernorVotes.sol | 2 - .../GovernorVotesQuorumFraction.sol | 2 - .../extensions/IGovernorTimelock.sol | 2 - contracts/governance/utils/IVotes.sol | 2 - contracts/governance/utils/Votes.sol | 2 - contracts/interfaces/IERC1271.sol | 2 - contracts/interfaces/IERC1967.sol | 2 - contracts/interfaces/IERC2309.sol | 2 - contracts/interfaces/IERC2981.sol | 2 - .../interfaces/IERC3156FlashBorrower.sol | 2 - contracts/interfaces/IERC3156FlashLender.sol | 2 - contracts/interfaces/IERC4626.sol | 2 - contracts/interfaces/IERC5313.sol | 2 - contracts/proxy/Clones.sol | 2 - contracts/proxy/ERC1967/ERC1967Utils.sol | 2 - contracts/proxy/beacon/BeaconProxy.sol | 2 - contracts/proxy/utils/UUPSUpgradeable.sol | 2 - contracts/token/ERC1155/ERC1155.sol | 2 - contracts/token/ERC1155/IERC1155.sol | 2 - contracts/token/ERC1155/IERC1155Receiver.sol | 3 +- .../ERC1155/extensions/ERC1155Burnable.sol | 2 - .../ERC1155/extensions/ERC1155Pausable.sol | 2 - .../ERC1155/extensions/ERC1155URIStorage.sol | 2 - .../extensions/IERC1155MetadataURI.sol | 2 - .../token/ERC1155/utils/ERC1155Holder.sol | 4 +- .../token/ERC1155/utils/ERC1155Receiver.sol | 4 +- .../token/ERC20/extensions/ERC20FlashMint.sol | 2 - .../token/ERC20/extensions/ERC20Permit.sol | 2 - .../token/ERC20/extensions/ERC20Votes.sol | 2 - .../token/ERC20/extensions/ERC20Wrapper.sol | 2 - contracts/token/ERC20/extensions/ERC4626.sol | 2 - .../token/ERC20/extensions/IERC20Metadata.sol | 2 - .../ERC721/extensions/ERC721Consecutive.sol | 2 - .../token/ERC721/extensions/ERC721Royalty.sol | 2 - .../token/ERC721/extensions/ERC721Votes.sol | 2 - .../token/ERC721/extensions/ERC721Wrapper.sol | 2 - contracts/token/common/ERC2981.sol | 2 - contracts/utils/Address.sol | 22 --- contracts/utils/Base64.sol | 2 - contracts/utils/Multicall.sol | 2 - contracts/utils/StorageSlot.sol | 3 - contracts/utils/cryptography/ECDSA.sol | 8 -- contracts/utils/cryptography/EIP712.sol | 8 -- contracts/utils/cryptography/MerkleProof.sol | 14 -- .../utils/cryptography/SignatureChecker.sol | 2 - .../utils/introspection/ERC165Checker.sol | 2 - contracts/utils/math/Math.sol | 10 -- contracts/utils/math/SafeCast.sol | 128 ------------------ contracts/utils/structs/Checkpoints.sol | 2 - contracts/utils/structs/DoubleEndedQueue.sol | 2 - scripts/generate/templates/Checkpoints.js | 2 - scripts/generate/templates/SafeCast.js | 65 --------- scripts/generate/templates/StorageSlot.js | 23 +--- scripts/upgradeable/upgradeable.patch | 29 ++-- 68 files changed, 26 insertions(+), 407 deletions(-) diff --git a/contracts/access/AccessControl.sol b/contracts/access/AccessControl.sol index 8465fefbd..ec30e81e7 100644 --- a/contracts/access/AccessControl.sol +++ b/contracts/access/AccessControl.sol @@ -64,8 +64,6 @@ abstract contract AccessControl is Context, IAccessControl, ERC165 { * The format of the revert reason is given by the following regular expression: * * /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/ - * - * _Available since v4.1._ */ modifier onlyRole(bytes32 role) { _checkRole(role); @@ -91,8 +89,6 @@ abstract contract AccessControl is Context, IAccessControl, ERC165 { * Overriding this function changes the behavior of the {onlyRole} modifier. * * Format of the revert message is described in {_checkRole}. - * - * _Available since v4.6._ */ function _checkRole(bytes32 role) internal view virtual { _checkRole(role, _msgSender()); diff --git a/contracts/access/AccessControlDefaultAdminRules.sol b/contracts/access/AccessControlDefaultAdminRules.sol index ebc40a5bf..4572a328d 100644 --- a/contracts/access/AccessControlDefaultAdminRules.sol +++ b/contracts/access/AccessControlDefaultAdminRules.sol @@ -35,8 +35,6 @@ import {IERC5313} from "../interfaces/IERC5313.sol"; * ) {} * } * ``` - * - * _Available since v4.9._ */ abstract contract AccessControlDefaultAdminRules is IAccessControlDefaultAdminRules, IERC5313, AccessControl { // pending admin pair read/written together frequently diff --git a/contracts/access/IAccessControl.sol b/contracts/access/IAccessControl.sol index 9abc2b735..38facbfb1 100644 --- a/contracts/access/IAccessControl.sol +++ b/contracts/access/IAccessControl.sol @@ -24,8 +24,6 @@ interface IAccessControl { * * `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite * {RoleAdminChanged} not being emitted signaling this. - * - * _Available since v3.1._ */ event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole); diff --git a/contracts/access/IAccessControlDefaultAdminRules.sol b/contracts/access/IAccessControlDefaultAdminRules.sol index 5e61f965d..a1afb3d22 100644 --- a/contracts/access/IAccessControlDefaultAdminRules.sol +++ b/contracts/access/IAccessControlDefaultAdminRules.sol @@ -7,8 +7,6 @@ import {IAccessControl} from "./IAccessControl.sol"; /** * @dev External interface of AccessControlDefaultAdminRules declared to support ERC165 detection. - * - * _Available since v4.9._ */ interface IAccessControlDefaultAdminRules is IAccessControl { /** diff --git a/contracts/governance/Governor.sol b/contracts/governance/Governor.sol index a66a1a6bf..94f29e95f 100644 --- a/contracts/governance/Governor.sol +++ b/contracts/governance/Governor.sol @@ -23,8 +23,6 @@ import {IGovernor, IERC6372} from "./IGovernor.sol"; * - A counting module must implement {quorum}, {_quorumReached}, {_voteSucceeded} and {_countVote} * - A voting module must implement {_getVotes} * - Additionally, {votingPeriod} must also be implemented - * - * _Available since v4.3._ */ abstract contract Governor is Context, ERC165, EIP712, Nonces, IGovernor, IERC721Receiver, IERC1155Receiver { using DoubleEndedQueue for DoubleEndedQueue.Bytes32Deque; diff --git a/contracts/governance/IGovernor.sol b/contracts/governance/IGovernor.sol index af4698226..b954ed205 100644 --- a/contracts/governance/IGovernor.sol +++ b/contracts/governance/IGovernor.sol @@ -8,8 +8,6 @@ import {IERC6372} from "../interfaces/IERC6372.sol"; /** * @dev Interface of the {Governor} core. - * - * _Available since v4.3._ */ abstract contract IGovernor is IERC165, IERC6372 { enum ProposalState { diff --git a/contracts/governance/TimelockController.sol b/contracts/governance/TimelockController.sol index ff8d45595..2cf954d12 100644 --- a/contracts/governance/TimelockController.sol +++ b/contracts/governance/TimelockController.sol @@ -21,8 +21,6 @@ import {Address} from "../utils/Address.sol"; * is in charge of proposing (resp executing) operations. A common use case is * to position this {TimelockController} as the owner of a smart contract, with * a multisig or a DAO as the sole proposer. - * - * _Available since v3.3._ */ contract TimelockController is AccessControl, ERC721Holder, ERC1155Holder { bytes32 public constant PROPOSER_ROLE = keccak256("PROPOSER_ROLE"); diff --git a/contracts/governance/compatibility/GovernorCompatibilityBravo.sol b/contracts/governance/compatibility/GovernorCompatibilityBravo.sol index 445f71be0..40284c578 100644 --- a/contracts/governance/compatibility/GovernorCompatibilityBravo.sol +++ b/contracts/governance/compatibility/GovernorCompatibilityBravo.sol @@ -15,8 +15,6 @@ import {IGovernorCompatibilityBravo} from "./IGovernorCompatibilityBravo.sol"; * through inheritance. It does not include token bindings, nor does it include any variable upgrade patterns. * * NOTE: When using this module, you may need to enable the Solidity optimizer to avoid hitting the contract size limit. - * - * _Available since v4.3._ */ abstract contract GovernorCompatibilityBravo is IGovernorTimelock, IGovernorCompatibilityBravo, Governor { enum VoteType { diff --git a/contracts/governance/compatibility/IGovernorCompatibilityBravo.sol b/contracts/governance/compatibility/IGovernorCompatibilityBravo.sol index 4bd593077..5476edf4f 100644 --- a/contracts/governance/compatibility/IGovernorCompatibilityBravo.sol +++ b/contracts/governance/compatibility/IGovernorCompatibilityBravo.sol @@ -7,8 +7,6 @@ import {IGovernor} from "../IGovernor.sol"; /** * @dev Interface extension that adds missing functions to the {Governor} core to provide `GovernorBravo` compatibility. - * - * _Available since v4.3._ */ abstract contract IGovernorCompatibilityBravo is IGovernor { /** diff --git a/contracts/governance/extensions/GovernorCountingSimple.sol b/contracts/governance/extensions/GovernorCountingSimple.sol index 8934995de..6496c3acc 100644 --- a/contracts/governance/extensions/GovernorCountingSimple.sol +++ b/contracts/governance/extensions/GovernorCountingSimple.sol @@ -7,8 +7,6 @@ import {Governor} from "../Governor.sol"; /** * @dev Extension of {Governor} for simple, 3 options, vote counting. - * - * _Available since v4.3._ */ abstract contract GovernorCountingSimple is Governor { /** diff --git a/contracts/governance/extensions/GovernorPreventLateQuorum.sol b/contracts/governance/extensions/GovernorPreventLateQuorum.sol index 8687b6fd1..89c02fce6 100644 --- a/contracts/governance/extensions/GovernorPreventLateQuorum.sol +++ b/contracts/governance/extensions/GovernorPreventLateQuorum.sol @@ -14,8 +14,6 @@ import {Math} from "../../utils/math/Math.sol"; * If a vote causes quorum to be reached, the proposal's voting period may be extended so that it does not end before at * least a specified time has passed (the "vote extension" parameter). This parameter can be set through a governance * proposal. - * - * _Available since v4.5._ */ abstract contract GovernorPreventLateQuorum is Governor { uint48 private _voteExtension; diff --git a/contracts/governance/extensions/GovernorSettings.sol b/contracts/governance/extensions/GovernorSettings.sol index 5d98fbf34..834555654 100644 --- a/contracts/governance/extensions/GovernorSettings.sol +++ b/contracts/governance/extensions/GovernorSettings.sol @@ -7,8 +7,6 @@ import {Governor} from "../Governor.sol"; /** * @dev Extension of {Governor} for settings updatable through governance. - * - * _Available since v4.4._ */ abstract contract GovernorSettings is Governor { // amount of token diff --git a/contracts/governance/extensions/GovernorTimelockCompound.sol b/contracts/governance/extensions/GovernorTimelockCompound.sol index 935600d56..4b609723d 100644 --- a/contracts/governance/extensions/GovernorTimelockCompound.sol +++ b/contracts/governance/extensions/GovernorTimelockCompound.sol @@ -19,8 +19,6 @@ import {Address} from "../../utils/Address.sol"; * Using this model means the proposal will be operated by the {TimelockController} and not by the {Governor}. Thus, * the assets and permissions must be attached to the {TimelockController}. Any asset sent to the {Governor} will be * inaccessible. - * - * _Available since v4.3._ */ abstract contract GovernorTimelockCompound is IGovernorTimelock, Governor { ICompoundTimelock private _timelock; diff --git a/contracts/governance/extensions/GovernorTimelockControl.sol b/contracts/governance/extensions/GovernorTimelockControl.sol index fefe31555..111569408 100644 --- a/contracts/governance/extensions/GovernorTimelockControl.sol +++ b/contracts/governance/extensions/GovernorTimelockControl.sol @@ -21,8 +21,6 @@ import {IERC165} from "../../interfaces/IERC165.sol"; * grants them powers that they must be trusted or known not to use: 1) {onlyGovernance} functions like {relay} are * available to them through the timelock, and 2) approved governance proposals can be blocked by them, effectively * executing a Denial of Service attack. This risk will be mitigated in a future release. - * - * _Available since v4.3._ */ abstract contract GovernorTimelockControl is IGovernorTimelock, Governor { TimelockController private _timelock; diff --git a/contracts/governance/extensions/GovernorVotes.sol b/contracts/governance/extensions/GovernorVotes.sol index bb14d7fde..eea43290e 100644 --- a/contracts/governance/extensions/GovernorVotes.sol +++ b/contracts/governance/extensions/GovernorVotes.sol @@ -10,8 +10,6 @@ import {SafeCast} from "../../utils/math/SafeCast.sol"; /** * @dev Extension of {Governor} for voting weight extraction from an {ERC20Votes} token, or since v4.5 an {ERC721Votes} token. - * - * _Available since v4.3._ */ abstract contract GovernorVotes is Governor { IERC5805 public immutable token; diff --git a/contracts/governance/extensions/GovernorVotesQuorumFraction.sol b/contracts/governance/extensions/GovernorVotesQuorumFraction.sol index 5d7976b14..7782130fa 100644 --- a/contracts/governance/extensions/GovernorVotesQuorumFraction.sol +++ b/contracts/governance/extensions/GovernorVotesQuorumFraction.sol @@ -10,8 +10,6 @@ import {Checkpoints} from "../../utils/structs/Checkpoints.sol"; /** * @dev Extension of {Governor} for voting weight extraction from an {ERC20Votes} token and a quorum expressed as a * fraction of the total supply. - * - * _Available since v4.3._ */ abstract contract GovernorVotesQuorumFraction is GovernorVotes { using Checkpoints for Checkpoints.Trace224; diff --git a/contracts/governance/extensions/IGovernorTimelock.sol b/contracts/governance/extensions/IGovernorTimelock.sol index 00254853a..7d9c3c554 100644 --- a/contracts/governance/extensions/IGovernorTimelock.sol +++ b/contracts/governance/extensions/IGovernorTimelock.sol @@ -7,8 +7,6 @@ import {IGovernor} from "../IGovernor.sol"; /** * @dev Extension of the {IGovernor} for timelock supporting modules. - * - * _Available since v4.3._ */ abstract contract IGovernorTimelock is IGovernor { /** diff --git a/contracts/governance/utils/IVotes.sol b/contracts/governance/utils/IVotes.sol index ee17721ff..c664d1255 100644 --- a/contracts/governance/utils/IVotes.sol +++ b/contracts/governance/utils/IVotes.sol @@ -4,8 +4,6 @@ pragma solidity ^0.8.19; /** * @dev Common interface for {ERC20Votes}, {ERC721Votes}, and other {Votes}-enabled contracts. - * - * _Available since v4.5._ */ interface IVotes { /** diff --git a/contracts/governance/utils/Votes.sol b/contracts/governance/utils/Votes.sol index 988255189..1c0bb3289 100644 --- a/contracts/governance/utils/Votes.sol +++ b/contracts/governance/utils/Votes.sol @@ -27,8 +27,6 @@ import {ECDSA} from "../../utils/cryptography/ECDSA.sol"; * When using this module the derived contract must implement {_getVotingUnits} (for example, make it return * {ERC721-balanceOf}), and can use {_transferVotingUnits} to track a change in the distribution of those units (in the * previous example, it would be included in {ERC721-_beforeTokenTransfer}). - * - * _Available since v4.5._ */ abstract contract Votes is Context, EIP712, Nonces, IERC5805 { using Checkpoints for Checkpoints.Trace224; diff --git a/contracts/interfaces/IERC1271.sol b/contracts/interfaces/IERC1271.sol index e7fca3079..1523f988e 100644 --- a/contracts/interfaces/IERC1271.sol +++ b/contracts/interfaces/IERC1271.sol @@ -6,8 +6,6 @@ pragma solidity ^0.8.19; /** * @dev Interface of the ERC1271 standard signature validation method for * contracts as defined in https://eips.ethereum.org/EIPS/eip-1271[ERC-1271]. - * - * _Available since v4.1._ */ interface IERC1271 { /** diff --git a/contracts/interfaces/IERC1967.sol b/contracts/interfaces/IERC1967.sol index 190d86b2c..a57f767f1 100644 --- a/contracts/interfaces/IERC1967.sol +++ b/contracts/interfaces/IERC1967.sol @@ -5,8 +5,6 @@ pragma solidity ^0.8.19; /** * @dev ERC-1967: Proxy Storage Slots. This interface contains the events defined in the ERC. - * - * _Available since v4.8.3._ */ interface IERC1967 { /** diff --git a/contracts/interfaces/IERC2309.sol b/contracts/interfaces/IERC2309.sol index 9d8886994..cd967e41b 100644 --- a/contracts/interfaces/IERC2309.sol +++ b/contracts/interfaces/IERC2309.sol @@ -5,8 +5,6 @@ pragma solidity ^0.8.19; /** * @dev ERC-2309: ERC-721 Consecutive Transfer Extension. - * - * _Available since v4.8._ */ interface IERC2309 { /** diff --git a/contracts/interfaces/IERC2981.sol b/contracts/interfaces/IERC2981.sol index ba0f98c99..18cfa7135 100644 --- a/contracts/interfaces/IERC2981.sol +++ b/contracts/interfaces/IERC2981.sol @@ -10,8 +10,6 @@ import {IERC165} from "../utils/introspection/IERC165.sol"; * * A standardized way to retrieve royalty payment information for non-fungible tokens (NFTs) to enable universal * support for royalty payments across all NFT marketplaces and ecosystem participants. - * - * _Available since v4.5._ */ interface IERC2981 is IERC165 { /** diff --git a/contracts/interfaces/IERC3156FlashBorrower.sol b/contracts/interfaces/IERC3156FlashBorrower.sol index 3f216194b..5c0b88de4 100644 --- a/contracts/interfaces/IERC3156FlashBorrower.sol +++ b/contracts/interfaces/IERC3156FlashBorrower.sol @@ -6,8 +6,6 @@ pragma solidity ^0.8.19; /** * @dev Interface of the ERC3156 FlashBorrower, as defined in * https://eips.ethereum.org/EIPS/eip-3156[ERC-3156]. - * - * _Available since v4.1._ */ interface IERC3156FlashBorrower { /** diff --git a/contracts/interfaces/IERC3156FlashLender.sol b/contracts/interfaces/IERC3156FlashLender.sol index e5dd9c523..b52428a06 100644 --- a/contracts/interfaces/IERC3156FlashLender.sol +++ b/contracts/interfaces/IERC3156FlashLender.sol @@ -8,8 +8,6 @@ import {IERC3156FlashBorrower} from "./IERC3156FlashBorrower.sol"; /** * @dev Interface of the ERC3156 FlashLender, as defined in * https://eips.ethereum.org/EIPS/eip-3156[ERC-3156]. - * - * _Available since v4.1._ */ interface IERC3156FlashLender { /** diff --git a/contracts/interfaces/IERC4626.sol b/contracts/interfaces/IERC4626.sol index 27735f6b9..8854b0022 100644 --- a/contracts/interfaces/IERC4626.sol +++ b/contracts/interfaces/IERC4626.sol @@ -9,8 +9,6 @@ import {IERC20Metadata} from "../token/ERC20/extensions/IERC20Metadata.sol"; /** * @dev Interface of the ERC4626 "Tokenized Vault Standard", as defined in * https://eips.ethereum.org/EIPS/eip-4626[ERC-4626]. - * - * _Available since v4.7._ */ interface IERC4626 is IERC20, IERC20Metadata { event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares); diff --git a/contracts/interfaces/IERC5313.sol b/contracts/interfaces/IERC5313.sol index 1d17080a1..1150c322c 100644 --- a/contracts/interfaces/IERC5313.sol +++ b/contracts/interfaces/IERC5313.sol @@ -7,8 +7,6 @@ pragma solidity ^0.8.19; * @dev Interface for the Light Contract Ownership Standard. * * A standardized minimal interface required to identify an account that controls a contract - * - * _Available since v4.9._ */ interface IERC5313 { /** diff --git a/contracts/proxy/Clones.sol b/contracts/proxy/Clones.sol index d859d5645..fea1db13a 100644 --- a/contracts/proxy/Clones.sol +++ b/contracts/proxy/Clones.sol @@ -13,8 +13,6 @@ pragma solidity ^0.8.19; * The library includes functions to deploy a proxy using either `create` (traditional deployment) or `create2` * (salted deterministic deployment). It also includes functions to predict the addresses of clones deployed using the * deterministic method. - * - * _Available since v3.4._ */ library Clones { /** diff --git a/contracts/proxy/ERC1967/ERC1967Utils.sol b/contracts/proxy/ERC1967/ERC1967Utils.sol index 843fa6584..f7caaa684 100644 --- a/contracts/proxy/ERC1967/ERC1967Utils.sol +++ b/contracts/proxy/ERC1967/ERC1967Utils.sol @@ -10,8 +10,6 @@ import {StorageSlot} from "../../utils/StorageSlot.sol"; /** * @dev This abstract contract provides getters and event emitting update functions for * https://eips.ethereum.org/EIPS/eip-1967[EIP1967] slots. - * - * _Available since v4.1._ */ library ERC1967Utils { // We re-declare ERC-1967 events here because they can't be used directly from IERC1967. diff --git a/contracts/proxy/beacon/BeaconProxy.sol b/contracts/proxy/beacon/BeaconProxy.sol index 57883b692..94e697ca4 100644 --- a/contracts/proxy/beacon/BeaconProxy.sol +++ b/contracts/proxy/beacon/BeaconProxy.sol @@ -12,8 +12,6 @@ import {ERC1967Utils} from "../ERC1967/ERC1967Utils.sol"; * * The beacon address is stored in storage slot `uint256(keccak256('eip1967.proxy.beacon')) - 1`, so that it doesn't * conflict with the storage layout of the implementation behind the proxy. - * - * _Available since v3.4._ */ contract BeaconProxy is Proxy { /** diff --git a/contracts/proxy/utils/UUPSUpgradeable.sol b/contracts/proxy/utils/UUPSUpgradeable.sol index 7d1515bbb..f4045aa6f 100644 --- a/contracts/proxy/utils/UUPSUpgradeable.sol +++ b/contracts/proxy/utils/UUPSUpgradeable.sol @@ -15,8 +15,6 @@ import {ERC1967Utils} from "../ERC1967/ERC1967Utils.sol"; * `UUPSUpgradeable` with a custom implementation of upgrades. * * The {_authorizeUpgrade} function must be overridden to include access restriction to the upgrade mechanism. - * - * _Available since v4.1._ */ abstract contract UUPSUpgradeable is IERC1822Proxiable { /// @custom:oz-upgrades-unsafe-allow state-variable-immutable state-variable-assignment diff --git a/contracts/token/ERC1155/ERC1155.sol b/contracts/token/ERC1155/ERC1155.sol index a9028f323..7b1c9cfff 100644 --- a/contracts/token/ERC1155/ERC1155.sol +++ b/contracts/token/ERC1155/ERC1155.sol @@ -15,8 +15,6 @@ import {IERC1155Errors} from "../../interfaces/draft-IERC6093.sol"; * @dev Implementation of the basic standard multi-token. * See https://eips.ethereum.org/EIPS/eip-1155 * Originally based on code by Enjin: https://github.com/enjin/erc-1155 - * - * _Available since v3.1._ */ abstract contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI, IERC1155Errors { using Arrays for uint256[]; diff --git a/contracts/token/ERC1155/IERC1155.sol b/contracts/token/ERC1155/IERC1155.sol index 1b0b0ae9d..19b626c9a 100644 --- a/contracts/token/ERC1155/IERC1155.sol +++ b/contracts/token/ERC1155/IERC1155.sol @@ -8,8 +8,6 @@ import {IERC165} from "../../utils/introspection/IERC165.sol"; /** * @dev Required interface of an ERC1155 compliant contract, as defined in the * https://eips.ethereum.org/EIPS/eip-1155[EIP]. - * - * _Available since v3.1._ */ interface IERC1155 is IERC165 { /** diff --git a/contracts/token/ERC1155/IERC1155Receiver.sol b/contracts/token/ERC1155/IERC1155Receiver.sol index 04ef783f1..ce3265246 100644 --- a/contracts/token/ERC1155/IERC1155Receiver.sol +++ b/contracts/token/ERC1155/IERC1155Receiver.sol @@ -6,7 +6,8 @@ pragma solidity ^0.8.19; import {IERC165} from "../../utils/introspection/IERC165.sol"; /** - * @dev _Available since v3.1._ + * @dev Interface that must be implemented by smart contracts in order to receive + * ERC-1155 token transfers. */ interface IERC1155Receiver is IERC165 { /** diff --git a/contracts/token/ERC1155/extensions/ERC1155Burnable.sol b/contracts/token/ERC1155/extensions/ERC1155Burnable.sol index 5ebfcf6c4..3696d1bbe 100644 --- a/contracts/token/ERC1155/extensions/ERC1155Burnable.sol +++ b/contracts/token/ERC1155/extensions/ERC1155Burnable.sol @@ -8,8 +8,6 @@ import {ERC1155} from "../ERC1155.sol"; /** * @dev Extension of {ERC1155} that allows token holders to destroy both their * own tokens and those that they have been approved to use. - * - * _Available since v3.1._ */ abstract contract ERC1155Burnable is ERC1155 { function burn(address account, uint256 id, uint256 value) public virtual { diff --git a/contracts/token/ERC1155/extensions/ERC1155Pausable.sol b/contracts/token/ERC1155/extensions/ERC1155Pausable.sol index 960cd3b6e..a51f21599 100644 --- a/contracts/token/ERC1155/extensions/ERC1155Pausable.sol +++ b/contracts/token/ERC1155/extensions/ERC1155Pausable.sol @@ -18,8 +18,6 @@ import {Pausable} from "../../../security/Pausable.sol"; * {Pausable-_pause} and {Pausable-_unpause} internal functions, with appropriate * access control, e.g. using {AccessControl} or {Ownable}. Not doing so will * make the contract pause mechanism of the contract unreachable, and thus unusable. - * - * _Available since v3.1._ */ abstract contract ERC1155Pausable is ERC1155, Pausable { /** diff --git a/contracts/token/ERC1155/extensions/ERC1155URIStorage.sol b/contracts/token/ERC1155/extensions/ERC1155URIStorage.sol index 303b452b8..2e502fd35 100644 --- a/contracts/token/ERC1155/extensions/ERC1155URIStorage.sol +++ b/contracts/token/ERC1155/extensions/ERC1155URIStorage.sol @@ -9,8 +9,6 @@ import {ERC1155} from "../ERC1155.sol"; /** * @dev ERC1155 token with storage based token URI management. * Inspired by the ERC721URIStorage extension - * - * _Available since v4.6._ */ abstract contract ERC1155URIStorage is ERC1155 { using Strings for uint256; diff --git a/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol b/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol index 88462ef87..d0391d371 100644 --- a/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol +++ b/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol @@ -8,8 +8,6 @@ import {IERC1155} from "../IERC1155.sol"; /** * @dev Interface of the optional ERC1155MetadataExtension interface, as defined * in the https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions[EIP]. - * - * _Available since v3.1._ */ interface IERC1155MetadataURI is IERC1155 { /** diff --git a/contracts/token/ERC1155/utils/ERC1155Holder.sol b/contracts/token/ERC1155/utils/ERC1155Holder.sol index 87c42b02f..e288f8692 100644 --- a/contracts/token/ERC1155/utils/ERC1155Holder.sol +++ b/contracts/token/ERC1155/utils/ERC1155Holder.sol @@ -6,12 +6,10 @@ pragma solidity ^0.8.19; import {ERC1155Receiver} from "./ERC1155Receiver.sol"; /** - * Simple implementation of `ERC1155Receiver` that will allow a contract to hold ERC1155 tokens. + * @dev Simple implementation of `ERC1155Receiver` that will allow a contract to hold ERC1155 tokens. * * IMPORTANT: When inheriting this contract, you must include a way to use the received tokens, otherwise they will be * stuck. - * - * @dev _Available since v3.1._ */ abstract contract ERC1155Holder is ERC1155Receiver { function onERC1155Received( diff --git a/contracts/token/ERC1155/utils/ERC1155Receiver.sol b/contracts/token/ERC1155/utils/ERC1155Receiver.sol index cf948ced7..02f922cf8 100644 --- a/contracts/token/ERC1155/utils/ERC1155Receiver.sol +++ b/contracts/token/ERC1155/utils/ERC1155Receiver.sol @@ -7,7 +7,9 @@ import {IERC1155Receiver} from "../IERC1155Receiver.sol"; import {IERC165, ERC165} from "../../../utils/introspection/ERC165.sol"; /** - * @dev _Available since v3.1._ + * @dev Basic contract implementing the ERC-165 interface for {IERC1155Receiver}. + * + * NOTE: This contract does not suffice to receive tokens. See {ERC1155Holder}. */ abstract contract ERC1155Receiver is ERC165, IERC1155Receiver { /** diff --git a/contracts/token/ERC20/extensions/ERC20FlashMint.sol b/contracts/token/ERC20/extensions/ERC20FlashMint.sol index 0583af5df..5d3e11f76 100644 --- a/contracts/token/ERC20/extensions/ERC20FlashMint.sol +++ b/contracts/token/ERC20/extensions/ERC20FlashMint.sol @@ -17,8 +17,6 @@ import {ERC20} from "../ERC20.sol"; * NOTE: When this extension is used along with the {ERC20Capped} or {ERC20Votes} extensions, * {maxFlashLoan} will not correctly reflect the maximum that can be flash minted. We recommend * overriding {maxFlashLoan} so that it correctly reflects the supply cap. - * - * _Available since v4.1._ */ abstract contract ERC20FlashMint is ERC20, IERC3156FlashLender { bytes32 private constant _RETURN_VALUE = keccak256("ERC3156FlashBorrower.onFlashLoan"); diff --git a/contracts/token/ERC20/extensions/ERC20Permit.sol b/contracts/token/ERC20/extensions/ERC20Permit.sol index 8778f4baf..c83c7dae7 100644 --- a/contracts/token/ERC20/extensions/ERC20Permit.sol +++ b/contracts/token/ERC20/extensions/ERC20Permit.sol @@ -16,8 +16,6 @@ import {Nonces} from "../../../utils/Nonces.sol"; * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by * presenting a message signed by the account. By not relying on `{IERC20-approve}`, the token holder account doesn't * need to send a transaction, and thus is not required to hold Ether at all. - * - * _Available since v3.4._ */ abstract contract ERC20Permit is ERC20, IERC20Permit, EIP712, Nonces { // solhint-disable-next-line var-name-mixedcase diff --git a/contracts/token/ERC20/extensions/ERC20Votes.sol b/contracts/token/ERC20/extensions/ERC20Votes.sol index c27115eaa..e0d675b2a 100644 --- a/contracts/token/ERC20/extensions/ERC20Votes.sol +++ b/contracts/token/ERC20/extensions/ERC20Votes.sol @@ -20,8 +20,6 @@ import {Checkpoints} from "../../../utils/structs/Checkpoints.sol"; * * By default, token balance does not account for voting power. This makes transfers cheaper. The downside is that it * requires users to delegate to themselves in order to activate checkpoints and have their voting power tracked. - * - * _Available since v4.2._ */ abstract contract ERC20Votes is ERC20, Votes { /** diff --git a/contracts/token/ERC20/extensions/ERC20Wrapper.sol b/contracts/token/ERC20/extensions/ERC20Wrapper.sol index c9e33d00a..fb5f314d7 100644 --- a/contracts/token/ERC20/extensions/ERC20Wrapper.sol +++ b/contracts/token/ERC20/extensions/ERC20Wrapper.sol @@ -12,8 +12,6 @@ import {SafeERC20} from "../utils/SafeERC20.sol"; * Users can deposit and withdraw "underlying tokens" and receive a matching number of "wrapped tokens". This is useful * in conjunction with other modules. For example, combining this wrapping mechanism with {ERC20Votes} will allow the * wrapping of an existing "basic" ERC20 into a governance token. - * - * _Available since v4.2._ */ abstract contract ERC20Wrapper is ERC20 { IERC20 private immutable _underlying; diff --git a/contracts/token/ERC20/extensions/ERC4626.sol b/contracts/token/ERC20/extensions/ERC4626.sol index cb5e03da8..11f1cd593 100644 --- a/contracts/token/ERC20/extensions/ERC4626.sol +++ b/contracts/token/ERC20/extensions/ERC4626.sol @@ -44,8 +44,6 @@ import {Math} from "../../../utils/math/Math.sol"; * * To learn more, check out our xref:ROOT:erc4626.adoc[ERC-4626 guide]. * ==== - * - * _Available since v4.7._ */ abstract contract ERC4626 is ERC20, IERC4626 { using Math for uint256; diff --git a/contracts/token/ERC20/extensions/IERC20Metadata.sol b/contracts/token/ERC20/extensions/IERC20Metadata.sol index bdfe81145..d79bbaa39 100644 --- a/contracts/token/ERC20/extensions/IERC20Metadata.sol +++ b/contracts/token/ERC20/extensions/IERC20Metadata.sol @@ -7,8 +7,6 @@ import {IERC20} from "../IERC20.sol"; /** * @dev Interface for the optional metadata functions from the ERC20 standard. - * - * _Available since v4.1._ */ interface IERC20Metadata is IERC20 { /** diff --git a/contracts/token/ERC721/extensions/ERC721Consecutive.sol b/contracts/token/ERC721/extensions/ERC721Consecutive.sol index e25edfcd9..7c37e0764 100644 --- a/contracts/token/ERC721/extensions/ERC721Consecutive.sol +++ b/contracts/token/ERC721/extensions/ERC721Consecutive.sol @@ -26,8 +26,6 @@ import {Checkpoints} from "../../../utils/structs/Checkpoints.sol"; * IMPORTANT: When overriding {_afterTokenTransfer}, be careful about call ordering. {ownerOf} may return invalid * values during the {_afterTokenTransfer} execution if the super call is not called first. To be safe, execute the * super call before your custom logic. - * - * _Available since v4.8._ */ abstract contract ERC721Consecutive is IERC2309, ERC721 { using BitMaps for BitMaps.BitMap; diff --git a/contracts/token/ERC721/extensions/ERC721Royalty.sol b/contracts/token/ERC721/extensions/ERC721Royalty.sol index b4518dc24..eb128ac58 100644 --- a/contracts/token/ERC721/extensions/ERC721Royalty.sol +++ b/contracts/token/ERC721/extensions/ERC721Royalty.sol @@ -17,8 +17,6 @@ import {ERC165} from "../../../utils/introspection/ERC165.sol"; * IMPORTANT: ERC-2981 only specifies a way to signal royalty information and does not enforce its payment. See * https://eips.ethereum.org/EIPS/eip-2981#optional-royalty-payments[Rationale] in the EIP. Marketplaces are expected to * voluntarily pay royalties together with sales, but note that this standard is not yet widely supported. - * - * _Available since v4.5._ */ abstract contract ERC721Royalty is ERC2981, ERC721 { /** diff --git a/contracts/token/ERC721/extensions/ERC721Votes.sol b/contracts/token/ERC721/extensions/ERC721Votes.sol index 1e694e4c7..0838010eb 100644 --- a/contracts/token/ERC721/extensions/ERC721Votes.sol +++ b/contracts/token/ERC721/extensions/ERC721Votes.sol @@ -13,8 +13,6 @@ import {Votes} from "../../../governance/utils/Votes.sol"; * Tokens do not count as votes until they are delegated, because votes must be tracked which incurs an additional cost * on every transfer. Token holders can either delegate to a trusted representative who will decide how to make use of * the votes in governance decisions, or they can delegate to themselves to be their own representative. - * - * _Available since v4.5._ */ abstract contract ERC721Votes is ERC721, Votes { /** diff --git a/contracts/token/ERC721/extensions/ERC721Wrapper.sol b/contracts/token/ERC721/extensions/ERC721Wrapper.sol index b8c396c3e..f204c1079 100644 --- a/contracts/token/ERC721/extensions/ERC721Wrapper.sol +++ b/contracts/token/ERC721/extensions/ERC721Wrapper.sol @@ -12,8 +12,6 @@ import {IERC721Receiver} from "../IERC721Receiver.sol"; * Users can deposit and withdraw an "underlying token" and receive a "wrapped token" with a matching tokenId. This is useful * in conjunction with other modules. For example, combining this wrapping mechanism with {ERC721Votes} will allow the * wrapping of an existing "basic" ERC721 into a governance token. - * - * _Available since v4.9.0_ */ abstract contract ERC721Wrapper is ERC721, IERC721Receiver { IERC721 private immutable _underlying; diff --git a/contracts/token/common/ERC2981.sol b/contracts/token/common/ERC2981.sol index b9183e34e..e683b41c5 100644 --- a/contracts/token/common/ERC2981.sol +++ b/contracts/token/common/ERC2981.sol @@ -18,8 +18,6 @@ import {IERC165, ERC165} from "../../utils/introspection/ERC165.sol"; * IMPORTANT: ERC-2981 only specifies a way to signal royalty information and does not enforce its payment. See * https://eips.ethereum.org/EIPS/eip-2981#optional-royalty-payments[Rationale] in the EIP. Marketplaces are expected to * voluntarily pay royalties together with sales, but note that this standard is not yet widely supported. - * - * _Available since v4.5._ */ abstract contract ERC2981 is IERC2981, ERC165 { struct RoyaltyInfo { diff --git a/contracts/utils/Address.sol b/contracts/utils/Address.sol index 859332b39..3f5d0a55b 100644 --- a/contracts/utils/Address.sol +++ b/contracts/utils/Address.sol @@ -64,8 +64,6 @@ library Address { * * - `target` must be a contract. * - calling `target` with `data` must not revert. - * - * _Available since v3.1._ */ function functionCall(address target, bytes memory data) internal returns (bytes memory) { return functionCallWithValue(target, data, 0, defaultRevert); @@ -78,8 +76,6 @@ library Address { * Requirements: * * - `customRevert` must be a reverting function. - * - * _Available since v5.0._ */ function functionCall( address target, @@ -97,8 +93,6 @@ library Address { * * - the calling contract must have an ETH balance of at least `value`. * - the called Solidity function must be `payable`. - * - * _Available since v3.1._ */ function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) { return functionCallWithValue(target, data, value, defaultRevert); @@ -111,8 +105,6 @@ library Address { * Requirements: * * - `customRevert` must be a reverting function. - * - * _Available since v5.0._ */ function functionCallWithValue( address target, @@ -130,8 +122,6 @@ library Address { /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but performing a static call. - * - * _Available since v3.3._ */ function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { return functionStaticCall(target, data, defaultRevert); @@ -140,8 +130,6 @@ library Address { /** * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], * but performing a static call. - * - * _Available since v3.3._ */ function functionStaticCall( address target, @@ -155,8 +143,6 @@ library Address { /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but performing a delegate call. - * - * _Available since v3.4._ */ function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { return functionDelegateCall(target, data, defaultRevert); @@ -165,8 +151,6 @@ library Address { /** * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], * but performing a delegate call. - * - * _Available since v3.4._ */ function functionDelegateCall( address target, @@ -180,8 +164,6 @@ library Address { /** * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling * the revert reason or using the provided `customRevert`) in case of unsuccessful call or if target was not a contract. - * - * _Available since v5.0._ */ function verifyCallResultFromTarget( address target, @@ -206,8 +188,6 @@ library Address { /** * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the * revert reason or with a default revert error. - * - * _Available since v5.0._ */ function verifyCallResult(bool success, bytes memory returndata) internal view returns (bytes memory) { return verifyCallResult(success, returndata, defaultRevert); @@ -220,8 +200,6 @@ library Address { * Requirements: * * - `customRevert` must be a reverting function. - * - * _Available since v5.0._ */ function verifyCallResult( bool success, diff --git a/contracts/utils/Base64.sol b/contracts/utils/Base64.sol index 9ba6defe0..ae73ec4af 100644 --- a/contracts/utils/Base64.sol +++ b/contracts/utils/Base64.sol @@ -5,8 +5,6 @@ pragma solidity ^0.8.19; /** * @dev Provides a set of functions to operate with Base64 strings. - * - * _Available since v4.5._ */ library Base64 { /** diff --git a/contracts/utils/Multicall.sol b/contracts/utils/Multicall.sol index fed9844c4..12c01ef11 100644 --- a/contracts/utils/Multicall.sol +++ b/contracts/utils/Multicall.sol @@ -7,8 +7,6 @@ import {Address} from "./Address.sol"; /** * @dev Provides a function to batch together multiple calls in a single external call. - * - * _Available since v4.1._ */ abstract contract Multicall { /** diff --git a/contracts/utils/StorageSlot.sol b/contracts/utils/StorageSlot.sol index b0e918967..cae1c8b68 100644 --- a/contracts/utils/StorageSlot.sol +++ b/contracts/utils/StorageSlot.sol @@ -27,9 +27,6 @@ pragma solidity ^0.8.19; * } * } * ``` - * - * _Available since v4.1 for `address`, `bool`, `bytes32`, `uint256`._ - * _Available since v4.9 for `string`, `bytes`._ */ library StorageSlot { struct AddressSlot { diff --git a/contracts/utils/cryptography/ECDSA.sol b/contracts/utils/cryptography/ECDSA.sol index 5e67a7591..aabfb5ca2 100644 --- a/contracts/utils/cryptography/ECDSA.sol +++ b/contracts/utils/cryptography/ECDSA.sol @@ -63,8 +63,6 @@ library ECDSA { * Documentation for signature generation: * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js] * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers] - * - * _Available since v4.3._ */ function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError, bytes32) { if (signature.length == 65) { @@ -109,8 +107,6 @@ library ECDSA { * @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately. * * See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures] - * - * _Available since v4.3._ */ function tryRecover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address, RecoverError, bytes32) { unchecked { @@ -123,8 +119,6 @@ library ECDSA { /** * @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately. - * - * _Available since v4.2._ */ function recover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address) { (address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, r, vs); @@ -135,8 +129,6 @@ library ECDSA { /** * @dev Overload of {ECDSA-tryRecover} that receives the `v`, * `r` and `s` signature fields separately. - * - * _Available since v4.3._ */ function tryRecover( bytes32 hash, diff --git a/contracts/utils/cryptography/EIP712.sol b/contracts/utils/cryptography/EIP712.sol index d94e956af..ff34e8146 100644 --- a/contracts/utils/cryptography/EIP712.sol +++ b/contracts/utils/cryptography/EIP712.sol @@ -28,8 +28,6 @@ import {IERC5267} from "../../interfaces/IERC5267.sol"; * separator of the implementation contract. This will cause the `_domainSeparatorV4` function to always rebuild the * separator from the immutable values, which is cheaper than accessing a cached version in cold storage. * - * _Available since v3.4._ - * * @custom:oz-upgrades-unsafe-allow state-variable-immutable state-variable-assignment */ abstract contract EIP712 is IERC5267 { @@ -111,8 +109,6 @@ abstract contract EIP712 is IERC5267 { /** * @dev See {IERC-5267}. - * - * _Available since v4.9._ */ function eip712Domain() public @@ -144,8 +140,6 @@ abstract contract EIP712 is IERC5267 { * * NOTE: By default this function reads _name which is an immutable value. * It only reads from storage if necessary (in case the value is too large to fit in a ShortString). - * - * _Available since v5.0._ */ // solhint-disable-next-line func-name-mixedcase function _EIP712Name() internal view returns (string memory) { @@ -157,8 +151,6 @@ abstract contract EIP712 is IERC5267 { * * NOTE: By default this function reads _version which is an immutable value. * It only reads from storage if necessary (in case the value is too large to fit in a ShortString). - * - * _Available since v5.0._ */ // solhint-disable-next-line func-name-mixedcase function _EIP712Version() internal view returns (string memory) { diff --git a/contracts/utils/cryptography/MerkleProof.sol b/contracts/utils/cryptography/MerkleProof.sol index 94586ff7b..17a6384fe 100644 --- a/contracts/utils/cryptography/MerkleProof.sol +++ b/contracts/utils/cryptography/MerkleProof.sol @@ -35,8 +35,6 @@ library MerkleProof { /** * @dev Calldata version of {verify} - * - * _Available since v4.7._ */ function verifyCalldata(bytes32[] calldata proof, bytes32 root, bytes32 leaf) internal pure returns (bool) { return processProofCalldata(proof, leaf) == root; @@ -47,8 +45,6 @@ library MerkleProof { * from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt * hash matches the root of the tree. When processing the proof, the pairs * of leafs & pre-images are assumed to be sorted. - * - * _Available since v4.4._ */ function processProof(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32) { bytes32 computedHash = leaf; @@ -60,8 +56,6 @@ library MerkleProof { /** * @dev Calldata version of {processProof} - * - * _Available since v4.7._ */ function processProofCalldata(bytes32[] calldata proof, bytes32 leaf) internal pure returns (bytes32) { bytes32 computedHash = leaf; @@ -76,8 +70,6 @@ library MerkleProof { * `root`, according to `proof` and `proofFlags` as described in {processMultiProof}. * * CAUTION: Not all merkle trees admit multiproofs. See {processMultiProof} for details. - * - * _Available since v4.7._ */ function multiProofVerify( bytes32[] memory proof, @@ -92,8 +84,6 @@ library MerkleProof { * @dev Calldata version of {multiProofVerify} * * CAUTION: Not all merkle trees admit multiproofs. See {processMultiProof} for details. - * - * _Available since v4.7._ */ function multiProofVerifyCalldata( bytes32[] calldata proof, @@ -113,8 +103,6 @@ library MerkleProof { * CAUTION: Not all merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree * is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the * tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer). - * - * _Available since v4.7._ */ function processMultiProof( bytes32[] memory proof, @@ -171,8 +159,6 @@ library MerkleProof { * @dev Calldata version of {processMultiProof}. * * CAUTION: Not all merkle trees admit multiproofs. See {processMultiProof} for details. - * - * _Available since v4.7._ */ function processMultiProofCalldata( bytes32[] calldata proof, diff --git a/contracts/utils/cryptography/SignatureChecker.sol b/contracts/utils/cryptography/SignatureChecker.sol index 5caf7bef9..f2cc2c4ed 100644 --- a/contracts/utils/cryptography/SignatureChecker.sol +++ b/contracts/utils/cryptography/SignatureChecker.sol @@ -10,8 +10,6 @@ import {IERC1271} from "../../interfaces/IERC1271.sol"; * @dev Signature verification helper that can be used instead of `ECDSA.recover` to seamlessly support both ECDSA * signatures from externally owned accounts (EOAs) as well as ERC1271 signatures from smart contract wallets like * Argent and Safe Wallet (previously Gnosis Safe). - * - * _Available since v4.1._ */ library SignatureChecker { /** diff --git a/contracts/utils/introspection/ERC165Checker.sol b/contracts/utils/introspection/ERC165Checker.sol index d614c6755..86212695a 100644 --- a/contracts/utils/introspection/ERC165Checker.sol +++ b/contracts/utils/introspection/ERC165Checker.sol @@ -45,8 +45,6 @@ library ERC165Checker { * is that some interfaces may not be supported. * * See {IERC165-supportsInterface}. - * - * _Available since v3.4._ */ function getSupportedInterfaces( address account, diff --git a/contracts/utils/math/Math.sol b/contracts/utils/math/Math.sol index d372295d7..f55b69afc 100644 --- a/contracts/utils/math/Math.sol +++ b/contracts/utils/math/Math.sol @@ -20,8 +20,6 @@ library Math { /** * @dev Returns the addition of two unsigned integers, with an overflow flag. - * - * _Available since v5.0._ */ function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) { unchecked { @@ -33,8 +31,6 @@ library Math { /** * @dev Returns the subtraction of two unsigned integers, with an overflow flag. - * - * _Available since v5.0._ */ function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) { unchecked { @@ -45,8 +41,6 @@ library Math { /** * @dev Returns the multiplication of two unsigned integers, with an overflow flag. - * - * _Available since v5.0._ */ function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) { unchecked { @@ -62,8 +56,6 @@ library Math { /** * @dev Returns the division of two unsigned integers, with a division by zero flag. - * - * _Available since v5.0._ */ function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) { unchecked { @@ -74,8 +66,6 @@ library Math { /** * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag. - * - * _Available since v5.0._ */ function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) { unchecked { diff --git a/contracts/utils/math/SafeCast.sol b/contracts/utils/math/SafeCast.sol index d3b86b088..64f180cd2 100644 --- a/contracts/utils/math/SafeCast.sol +++ b/contracts/utils/math/SafeCast.sol @@ -46,8 +46,6 @@ library SafeCast { * Requirements: * * - input must fit into 248 bits - * - * _Available since v4.7._ */ function toUint248(uint256 value) internal pure returns (uint248) { if (value > type(uint248).max) { @@ -65,8 +63,6 @@ library SafeCast { * Requirements: * * - input must fit into 240 bits - * - * _Available since v4.7._ */ function toUint240(uint256 value) internal pure returns (uint240) { if (value > type(uint240).max) { @@ -84,8 +80,6 @@ library SafeCast { * Requirements: * * - input must fit into 232 bits - * - * _Available since v4.7._ */ function toUint232(uint256 value) internal pure returns (uint232) { if (value > type(uint232).max) { @@ -103,8 +97,6 @@ library SafeCast { * Requirements: * * - input must fit into 224 bits - * - * _Available since v4.2._ */ function toUint224(uint256 value) internal pure returns (uint224) { if (value > type(uint224).max) { @@ -122,8 +114,6 @@ library SafeCast { * Requirements: * * - input must fit into 216 bits - * - * _Available since v4.7._ */ function toUint216(uint256 value) internal pure returns (uint216) { if (value > type(uint216).max) { @@ -141,8 +131,6 @@ library SafeCast { * Requirements: * * - input must fit into 208 bits - * - * _Available since v4.7._ */ function toUint208(uint256 value) internal pure returns (uint208) { if (value > type(uint208).max) { @@ -160,8 +148,6 @@ library SafeCast { * Requirements: * * - input must fit into 200 bits - * - * _Available since v4.7._ */ function toUint200(uint256 value) internal pure returns (uint200) { if (value > type(uint200).max) { @@ -179,8 +165,6 @@ library SafeCast { * Requirements: * * - input must fit into 192 bits - * - * _Available since v4.7._ */ function toUint192(uint256 value) internal pure returns (uint192) { if (value > type(uint192).max) { @@ -198,8 +182,6 @@ library SafeCast { * Requirements: * * - input must fit into 184 bits - * - * _Available since v4.7._ */ function toUint184(uint256 value) internal pure returns (uint184) { if (value > type(uint184).max) { @@ -217,8 +199,6 @@ library SafeCast { * Requirements: * * - input must fit into 176 bits - * - * _Available since v4.7._ */ function toUint176(uint256 value) internal pure returns (uint176) { if (value > type(uint176).max) { @@ -236,8 +216,6 @@ library SafeCast { * Requirements: * * - input must fit into 168 bits - * - * _Available since v4.7._ */ function toUint168(uint256 value) internal pure returns (uint168) { if (value > type(uint168).max) { @@ -255,8 +233,6 @@ library SafeCast { * Requirements: * * - input must fit into 160 bits - * - * _Available since v4.7._ */ function toUint160(uint256 value) internal pure returns (uint160) { if (value > type(uint160).max) { @@ -274,8 +250,6 @@ library SafeCast { * Requirements: * * - input must fit into 152 bits - * - * _Available since v4.7._ */ function toUint152(uint256 value) internal pure returns (uint152) { if (value > type(uint152).max) { @@ -293,8 +267,6 @@ library SafeCast { * Requirements: * * - input must fit into 144 bits - * - * _Available since v4.7._ */ function toUint144(uint256 value) internal pure returns (uint144) { if (value > type(uint144).max) { @@ -312,8 +284,6 @@ library SafeCast { * Requirements: * * - input must fit into 136 bits - * - * _Available since v4.7._ */ function toUint136(uint256 value) internal pure returns (uint136) { if (value > type(uint136).max) { @@ -331,8 +301,6 @@ library SafeCast { * Requirements: * * - input must fit into 128 bits - * - * _Available since v2.5._ */ function toUint128(uint256 value) internal pure returns (uint128) { if (value > type(uint128).max) { @@ -350,8 +318,6 @@ library SafeCast { * Requirements: * * - input must fit into 120 bits - * - * _Available since v4.7._ */ function toUint120(uint256 value) internal pure returns (uint120) { if (value > type(uint120).max) { @@ -369,8 +335,6 @@ library SafeCast { * Requirements: * * - input must fit into 112 bits - * - * _Available since v4.7._ */ function toUint112(uint256 value) internal pure returns (uint112) { if (value > type(uint112).max) { @@ -388,8 +352,6 @@ library SafeCast { * Requirements: * * - input must fit into 104 bits - * - * _Available since v4.7._ */ function toUint104(uint256 value) internal pure returns (uint104) { if (value > type(uint104).max) { @@ -407,8 +369,6 @@ library SafeCast { * Requirements: * * - input must fit into 96 bits - * - * _Available since v4.2._ */ function toUint96(uint256 value) internal pure returns (uint96) { if (value > type(uint96).max) { @@ -426,8 +386,6 @@ library SafeCast { * Requirements: * * - input must fit into 88 bits - * - * _Available since v4.7._ */ function toUint88(uint256 value) internal pure returns (uint88) { if (value > type(uint88).max) { @@ -445,8 +403,6 @@ library SafeCast { * Requirements: * * - input must fit into 80 bits - * - * _Available since v4.7._ */ function toUint80(uint256 value) internal pure returns (uint80) { if (value > type(uint80).max) { @@ -464,8 +420,6 @@ library SafeCast { * Requirements: * * - input must fit into 72 bits - * - * _Available since v4.7._ */ function toUint72(uint256 value) internal pure returns (uint72) { if (value > type(uint72).max) { @@ -483,8 +437,6 @@ library SafeCast { * Requirements: * * - input must fit into 64 bits - * - * _Available since v2.5._ */ function toUint64(uint256 value) internal pure returns (uint64) { if (value > type(uint64).max) { @@ -502,8 +454,6 @@ library SafeCast { * Requirements: * * - input must fit into 56 bits - * - * _Available since v4.7._ */ function toUint56(uint256 value) internal pure returns (uint56) { if (value > type(uint56).max) { @@ -521,8 +471,6 @@ library SafeCast { * Requirements: * * - input must fit into 48 bits - * - * _Available since v4.7._ */ function toUint48(uint256 value) internal pure returns (uint48) { if (value > type(uint48).max) { @@ -540,8 +488,6 @@ library SafeCast { * Requirements: * * - input must fit into 40 bits - * - * _Available since v4.7._ */ function toUint40(uint256 value) internal pure returns (uint40) { if (value > type(uint40).max) { @@ -559,8 +505,6 @@ library SafeCast { * Requirements: * * - input must fit into 32 bits - * - * _Available since v2.5._ */ function toUint32(uint256 value) internal pure returns (uint32) { if (value > type(uint32).max) { @@ -578,8 +522,6 @@ library SafeCast { * Requirements: * * - input must fit into 24 bits - * - * _Available since v4.7._ */ function toUint24(uint256 value) internal pure returns (uint24) { if (value > type(uint24).max) { @@ -597,8 +539,6 @@ library SafeCast { * Requirements: * * - input must fit into 16 bits - * - * _Available since v2.5._ */ function toUint16(uint256 value) internal pure returns (uint16) { if (value > type(uint16).max) { @@ -616,8 +556,6 @@ library SafeCast { * Requirements: * * - input must fit into 8 bits - * - * _Available since v2.5._ */ function toUint8(uint256 value) internal pure returns (uint8) { if (value > type(uint8).max) { @@ -632,8 +570,6 @@ library SafeCast { * Requirements: * * - input must be greater than or equal to 0. - * - * _Available since v3.0._ */ function toUint256(int256 value) internal pure returns (uint256) { if (value < 0) { @@ -652,8 +588,6 @@ library SafeCast { * Requirements: * * - input must fit into 248 bits - * - * _Available since v4.7._ */ function toInt248(int256 value) internal pure returns (int248 downcasted) { downcasted = int248(value); @@ -672,8 +606,6 @@ library SafeCast { * Requirements: * * - input must fit into 240 bits - * - * _Available since v4.7._ */ function toInt240(int256 value) internal pure returns (int240 downcasted) { downcasted = int240(value); @@ -692,8 +624,6 @@ library SafeCast { * Requirements: * * - input must fit into 232 bits - * - * _Available since v4.7._ */ function toInt232(int256 value) internal pure returns (int232 downcasted) { downcasted = int232(value); @@ -712,8 +642,6 @@ library SafeCast { * Requirements: * * - input must fit into 224 bits - * - * _Available since v4.7._ */ function toInt224(int256 value) internal pure returns (int224 downcasted) { downcasted = int224(value); @@ -732,8 +660,6 @@ library SafeCast { * Requirements: * * - input must fit into 216 bits - * - * _Available since v4.7._ */ function toInt216(int256 value) internal pure returns (int216 downcasted) { downcasted = int216(value); @@ -752,8 +678,6 @@ library SafeCast { * Requirements: * * - input must fit into 208 bits - * - * _Available since v4.7._ */ function toInt208(int256 value) internal pure returns (int208 downcasted) { downcasted = int208(value); @@ -772,8 +696,6 @@ library SafeCast { * Requirements: * * - input must fit into 200 bits - * - * _Available since v4.7._ */ function toInt200(int256 value) internal pure returns (int200 downcasted) { downcasted = int200(value); @@ -792,8 +714,6 @@ library SafeCast { * Requirements: * * - input must fit into 192 bits - * - * _Available since v4.7._ */ function toInt192(int256 value) internal pure returns (int192 downcasted) { downcasted = int192(value); @@ -812,8 +732,6 @@ library SafeCast { * Requirements: * * - input must fit into 184 bits - * - * _Available since v4.7._ */ function toInt184(int256 value) internal pure returns (int184 downcasted) { downcasted = int184(value); @@ -832,8 +750,6 @@ library SafeCast { * Requirements: * * - input must fit into 176 bits - * - * _Available since v4.7._ */ function toInt176(int256 value) internal pure returns (int176 downcasted) { downcasted = int176(value); @@ -852,8 +768,6 @@ library SafeCast { * Requirements: * * - input must fit into 168 bits - * - * _Available since v4.7._ */ function toInt168(int256 value) internal pure returns (int168 downcasted) { downcasted = int168(value); @@ -872,8 +786,6 @@ library SafeCast { * Requirements: * * - input must fit into 160 bits - * - * _Available since v4.7._ */ function toInt160(int256 value) internal pure returns (int160 downcasted) { downcasted = int160(value); @@ -892,8 +804,6 @@ library SafeCast { * Requirements: * * - input must fit into 152 bits - * - * _Available since v4.7._ */ function toInt152(int256 value) internal pure returns (int152 downcasted) { downcasted = int152(value); @@ -912,8 +822,6 @@ library SafeCast { * Requirements: * * - input must fit into 144 bits - * - * _Available since v4.7._ */ function toInt144(int256 value) internal pure returns (int144 downcasted) { downcasted = int144(value); @@ -932,8 +840,6 @@ library SafeCast { * Requirements: * * - input must fit into 136 bits - * - * _Available since v4.7._ */ function toInt136(int256 value) internal pure returns (int136 downcasted) { downcasted = int136(value); @@ -952,8 +858,6 @@ library SafeCast { * Requirements: * * - input must fit into 128 bits - * - * _Available since v3.1._ */ function toInt128(int256 value) internal pure returns (int128 downcasted) { downcasted = int128(value); @@ -972,8 +876,6 @@ library SafeCast { * Requirements: * * - input must fit into 120 bits - * - * _Available since v4.7._ */ function toInt120(int256 value) internal pure returns (int120 downcasted) { downcasted = int120(value); @@ -992,8 +894,6 @@ library SafeCast { * Requirements: * * - input must fit into 112 bits - * - * _Available since v4.7._ */ function toInt112(int256 value) internal pure returns (int112 downcasted) { downcasted = int112(value); @@ -1012,8 +912,6 @@ library SafeCast { * Requirements: * * - input must fit into 104 bits - * - * _Available since v4.7._ */ function toInt104(int256 value) internal pure returns (int104 downcasted) { downcasted = int104(value); @@ -1032,8 +930,6 @@ library SafeCast { * Requirements: * * - input must fit into 96 bits - * - * _Available since v4.7._ */ function toInt96(int256 value) internal pure returns (int96 downcasted) { downcasted = int96(value); @@ -1052,8 +948,6 @@ library SafeCast { * Requirements: * * - input must fit into 88 bits - * - * _Available since v4.7._ */ function toInt88(int256 value) internal pure returns (int88 downcasted) { downcasted = int88(value); @@ -1072,8 +966,6 @@ library SafeCast { * Requirements: * * - input must fit into 80 bits - * - * _Available since v4.7._ */ function toInt80(int256 value) internal pure returns (int80 downcasted) { downcasted = int80(value); @@ -1092,8 +984,6 @@ library SafeCast { * Requirements: * * - input must fit into 72 bits - * - * _Available since v4.7._ */ function toInt72(int256 value) internal pure returns (int72 downcasted) { downcasted = int72(value); @@ -1112,8 +1002,6 @@ library SafeCast { * Requirements: * * - input must fit into 64 bits - * - * _Available since v3.1._ */ function toInt64(int256 value) internal pure returns (int64 downcasted) { downcasted = int64(value); @@ -1132,8 +1020,6 @@ library SafeCast { * Requirements: * * - input must fit into 56 bits - * - * _Available since v4.7._ */ function toInt56(int256 value) internal pure returns (int56 downcasted) { downcasted = int56(value); @@ -1152,8 +1038,6 @@ library SafeCast { * Requirements: * * - input must fit into 48 bits - * - * _Available since v4.7._ */ function toInt48(int256 value) internal pure returns (int48 downcasted) { downcasted = int48(value); @@ -1172,8 +1056,6 @@ library SafeCast { * Requirements: * * - input must fit into 40 bits - * - * _Available since v4.7._ */ function toInt40(int256 value) internal pure returns (int40 downcasted) { downcasted = int40(value); @@ -1192,8 +1074,6 @@ library SafeCast { * Requirements: * * - input must fit into 32 bits - * - * _Available since v3.1._ */ function toInt32(int256 value) internal pure returns (int32 downcasted) { downcasted = int32(value); @@ -1212,8 +1092,6 @@ library SafeCast { * Requirements: * * - input must fit into 24 bits - * - * _Available since v4.7._ */ function toInt24(int256 value) internal pure returns (int24 downcasted) { downcasted = int24(value); @@ -1232,8 +1110,6 @@ library SafeCast { * Requirements: * * - input must fit into 16 bits - * - * _Available since v3.1._ */ function toInt16(int256 value) internal pure returns (int16 downcasted) { downcasted = int16(value); @@ -1252,8 +1128,6 @@ library SafeCast { * Requirements: * * - input must fit into 8 bits - * - * _Available since v3.1._ */ function toInt8(int256 value) internal pure returns (int8 downcasted) { downcasted = int8(value); @@ -1268,8 +1142,6 @@ library SafeCast { * Requirements: * * - input must be less than or equal to maxInt256. - * - * _Available since v3.0._ */ function toInt256(uint256 value) internal pure returns (int256) { // Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive diff --git a/contracts/utils/structs/Checkpoints.sol b/contracts/utils/structs/Checkpoints.sol index 56b7035b9..6c73a08d8 100644 --- a/contracts/utils/structs/Checkpoints.sol +++ b/contracts/utils/structs/Checkpoints.sol @@ -12,8 +12,6 @@ import {Math} from "../math/Math.sol"; * * To create a history of checkpoints define a variable type `Checkpoints.Trace*` in your contract, and store a new * checkpoint for the current transaction block using the {push} function. - * - * _Available since v4.5._ */ library Checkpoints { /** diff --git a/contracts/utils/structs/DoubleEndedQueue.sol b/contracts/utils/structs/DoubleEndedQueue.sol index 5183bad8c..30d953239 100644 --- a/contracts/utils/structs/DoubleEndedQueue.sol +++ b/contracts/utils/structs/DoubleEndedQueue.sol @@ -15,8 +15,6 @@ import {SafeCast} from "../math/SafeCast.sol"; * ```solidity * DoubleEndedQueue.Bytes32Deque queue; * ``` - * - * _Available since v4.6._ */ library DoubleEndedQueue { /** diff --git a/scripts/generate/templates/Checkpoints.js b/scripts/generate/templates/Checkpoints.js index d635c8462..3bd4589d2 100644 --- a/scripts/generate/templates/Checkpoints.js +++ b/scripts/generate/templates/Checkpoints.js @@ -13,8 +13,6 @@ import {Math} from "../math/Math.sol"; * * To create a history of checkpoints define a variable type \`Checkpoints.Trace*\` in your contract, and store a new * checkpoint for the current transaction block using the {push} function. - * - * _Available since v4.5._ */ `; diff --git a/scripts/generate/templates/SafeCast.js b/scripts/generate/templates/SafeCast.js index 6a4a80c2b..afc31a641 100644 --- a/scripts/generate/templates/SafeCast.js +++ b/scripts/generate/templates/SafeCast.js @@ -1,65 +1,8 @@ -const assert = require('assert'); const format = require('../format-lines'); const { range } = require('../../helpers'); const LENGTHS = range(8, 256, 8).reverse(); // 248 → 8 (in steps of 8) -// Returns the version of OpenZeppelin Contracts in which a particular function was introduced. -// This is used in the docs for each function. -const version = (selector, length) => { - switch (selector) { - case 'toUint(uint)': { - switch (length) { - case 8: - case 16: - case 32: - case 64: - case 128: - return '2.5'; - case 96: - case 224: - return '4.2'; - default: - assert(LENGTHS.includes(length)); - return '4.7'; - } - } - case 'toInt(int)': { - switch (length) { - case 8: - case 16: - case 32: - case 64: - case 128: - return '3.1'; - default: - assert(LENGTHS.includes(length)); - return '4.7'; - } - } - case 'toUint(int)': { - switch (length) { - case 256: - return '3.0'; - default: - assert(false); - return; - } - } - case 'toInt(uint)': { - switch (length) { - case 256: - return '3.0'; - default: - assert(false); - return; - } - } - default: - assert(false); - } -}; - const header = `\ pragma solidity ^0.8.19; @@ -109,8 +52,6 @@ const toUintDownCast = length => `\ * Requirements: * * - input must fit into ${length} bits - * - * _Available since v${version('toUint(uint)', length)}._ */ function toUint${length}(uint256 value) internal pure returns (uint${length}) { if (value > type(uint${length}).max) { @@ -132,8 +73,6 @@ const toIntDownCast = length => `\ * Requirements: * * - input must fit into ${length} bits - * - * _Available since v${version('toInt(int)', length)}._ */ function toInt${length}(int256 value) internal pure returns (int${length} downcasted) { downcasted = int${length}(value); @@ -151,8 +90,6 @@ const toInt = length => `\ * Requirements: * * - input must be less than or equal to maxInt${length}. - * - * _Available since v${version('toInt(uint)', length)}._ */ function toInt${length}(uint${length} value) internal pure returns (int${length}) { // Note: Unsafe cast below is okay because \`type(int${length}).max\` is guaranteed to be positive @@ -170,8 +107,6 @@ const toUint = length => `\ * Requirements: * * - input must be greater than or equal to 0. - * - * _Available since v${version('toUint(int)', length)}._ */ function toUint${length}(int${length} value) internal pure returns (uint${length}) { if (value < 0) { diff --git a/scripts/generate/templates/StorageSlot.js b/scripts/generate/templates/StorageSlot.js index 3e2263a0c..a9fa11fe3 100644 --- a/scripts/generate/templates/StorageSlot.js +++ b/scripts/generate/templates/StorageSlot.js @@ -1,22 +1,15 @@ const format = require('../format-lines'); -const { capitalize, unique } = require('../../helpers'); +const { capitalize } = require('../../helpers'); const TYPES = [ - { type: 'address', isValueType: true, version: '4.1' }, - { type: 'bool', isValueType: true, name: 'Boolean', version: '4.1' }, - { type: 'bytes32', isValueType: true, version: '4.1' }, - { type: 'uint256', isValueType: true, version: '4.1' }, - { type: 'string', isValueType: false, version: '4.9' }, - { type: 'bytes', isValueType: false, version: '4.9' }, + { type: 'address', isValueType: true }, + { type: 'bool', isValueType: true, name: 'Boolean' }, + { type: 'bytes32', isValueType: true }, + { type: 'uint256', isValueType: true }, + { type: 'string', isValueType: false }, + { type: 'bytes', isValueType: false }, ].map(type => Object.assign(type, { struct: (type.name ?? capitalize(type.type)) + 'Slot' })); -const VERSIONS = unique(TYPES.map(t => t.version)).map( - version => - `_Available since v${version} for ${TYPES.filter(t => t.version == version) - .map(t => `\`${t.type}\``) - .join(', ')}._`, -); - const header = `\ pragma solidity ^0.8.19; @@ -43,8 +36,6 @@ pragma solidity ^0.8.19; * } * } * \`\`\` - * -${VERSIONS.map(s => ` * ${s}`).join('\n')} */ `; diff --git a/scripts/upgradeable/upgradeable.patch b/scripts/upgradeable/upgradeable.patch index 8623806f5..ebf3aa4e2 100644 --- a/scripts/upgradeable/upgradeable.patch +++ b/scripts/upgradeable/upgradeable.patch @@ -126,7 +126,7 @@ index df141192..1cf90ad1 100644 "keywords": [ "solidity", diff --git a/contracts/utils/cryptography/EIP712.sol b/contracts/utils/cryptography/EIP712.sol -index d94e956a..b2d3546f 100644 +index ff34e814..a9d08d5c 100644 --- a/contracts/utils/cryptography/EIP712.sol +++ b/contracts/utils/cryptography/EIP712.sol @@ -4,7 +4,6 @@ @@ -137,10 +137,10 @@ index d94e956a..b2d3546f 100644 import {IERC5267} from "../../interfaces/IERC5267.sol"; /** -@@ -29,28 +28,18 @@ import {IERC5267} from "../../interfaces/IERC5267.sol"; +@@ -27,28 +26,18 @@ import {IERC5267} from "../../interfaces/IERC5267.sol"; + * NOTE: In the upgradeable version of this contract, the cached values will correspond to the address, and the domain + * separator of the implementation contract. This will cause the `_domainSeparatorV4` function to always rebuild the * separator from the immutable values, which is cheaper than accessing a cached version in cold storage. - * - * _Available since v3.4._ - * - * @custom:oz-upgrades-unsafe-allow state-variable-immutable state-variable-assignment */ @@ -170,7 +170,7 @@ index d94e956a..b2d3546f 100644 /** * @dev Initializes the domain separator and parameter caches. -@@ -65,29 +54,23 @@ abstract contract EIP712 is IERC5267 { +@@ -63,29 +52,23 @@ abstract contract EIP712 is IERC5267 { * contract upgrade]. */ constructor(string memory name, string memory version) { @@ -208,7 +208,7 @@ index d94e956a..b2d3546f 100644 } /** -@@ -128,6 +111,10 @@ abstract contract EIP712 is IERC5267 { +@@ -124,6 +107,10 @@ abstract contract EIP712 is IERC5267 { uint256[] memory extensions ) { @@ -219,14 +219,12 @@ index d94e956a..b2d3546f 100644 return ( hex"0f", // 01111 _EIP712Name(), -@@ -142,26 +129,62 @@ abstract contract EIP712 is IERC5267 { +@@ -138,22 +125,62 @@ abstract contract EIP712 is IERC5267 { /** * @dev The name parameter for the EIP712 domain. * - * NOTE: By default this function reads _name which is an immutable value. - * It only reads from storage if necessary (in case the value is too large to fit in a ShortString). -- * -- * _Available since v5.0._ + * NOTE: This function reads from storage by default, but can be redefined to return a constant value if gas costs + * are a concern. */ @@ -244,7 +242,10 @@ index d94e956a..b2d3546f 100644 - * It only reads from storage if necessary (in case the value is too large to fit in a ShortString). + * NOTE: This function reads from storage by default, but can be redefined to return a constant value if gas costs + * are a concern. -+ */ + */ +- // solhint-disable-next-line func-name-mixedcase +- function _EIP712Version() internal view returns (string memory) { +- return _version.toStringWithFallback(_versionFallback); + function _EIP712Version() internal view virtual returns (string memory) { + return _version; + } @@ -272,13 +273,9 @@ index d94e956a..b2d3546f 100644 + + /** + * @dev The hash of the version parameter for the EIP712 domain. - * -- * _Available since v5.0._ ++ * + * NOTE: In previous versions this function was virtual. In this version you should override `_EIP712Version` instead. - */ -- // solhint-disable-next-line func-name-mixedcase -- function _EIP712Version() internal view returns (string memory) { -- return _version.toStringWithFallback(_versionFallback); ++ */ + function _EIP712VersionHash() internal view returns (bytes32) { + string memory version = _EIP712Version(); + if (bytes(version).length > 0) { From 7ccea54dc15856d0e6c3b61b829b85d9e52195cd Mon Sep 17 00:00:00 2001 From: Francisco Date: Thu, 6 Jul 2023 18:33:38 -0300 Subject: [PATCH 142/182] Add back IGovernor to docs (#4421) --- contracts/governance/README.adoc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/contracts/governance/README.adoc b/contracts/governance/README.adoc index 29c3887c7..35f324b7e 100644 --- a/contracts/governance/README.adoc +++ b/contracts/governance/README.adoc @@ -52,6 +52,8 @@ NOTE: Functions of the `Governor` contract do not include access control. If you === Core +{{IGovernor}} + {{Governor}} === Modules From 996168f1f114645b01a1c2e6909c9d98ec451203 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Fri, 7 Jul 2023 08:29:21 -0600 Subject: [PATCH 143/182] Remove slither hardcoded version (#4431) --- .github/workflows/checks.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 15cc88e96..122d39564 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -100,7 +100,6 @@ jobs: - uses: crytic/slither-action@v0.3.0 with: node-version: 18.15 - slither-version: 0.9.3 codespell: runs-on: ubuntu-latest From 0053ee040a7ff1dbc39691c9e67a69f564930a88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Fri, 7 Jul 2023 14:01:35 -0600 Subject: [PATCH 144/182] Move `ECDSA` message hash methods to its own `MessageHashUtils` library (#4430) Co-authored-by: Hadrien Croubois Co-authored-by: Francisco --- .changeset/hot-dingos-kiss.md | 5 ++ contracts/utils/README.adoc | 2 + contracts/utils/cryptography/ECDSA.sol | 84 +++--------------- contracts/utils/cryptography/EIP712.sol | 13 +-- .../utils/cryptography/MessageHashUtils.sol | 87 +++++++++++++++++++ docs/modules/ROOT/pages/utilities.adoc | 5 +- scripts/upgradeable/upgradeable.patch | 14 +-- test/utils/cryptography/ECDSA.test.js | 25 +----- .../cryptography/MessageHashUtils.test.js | 55 ++++++++++++ 9 files changed, 179 insertions(+), 111 deletions(-) create mode 100644 .changeset/hot-dingos-kiss.md create mode 100644 contracts/utils/cryptography/MessageHashUtils.sol create mode 100644 test/utils/cryptography/MessageHashUtils.test.js diff --git a/.changeset/hot-dingos-kiss.md b/.changeset/hot-dingos-kiss.md new file mode 100644 index 000000000..fb213cd64 --- /dev/null +++ b/.changeset/hot-dingos-kiss.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': major +--- + +`MessageHashUtils`: Add a new library for creating message digest to be used along with signing or recovery such as ECDSA or ERC-1271. These functions are moved from the `ECDSA` library. diff --git a/contracts/utils/README.adoc b/contracts/utils/README.adoc index aa5182bd1..d95f4dad4 100644 --- a/contracts/utils/README.adoc +++ b/contracts/utils/README.adoc @@ -34,6 +34,8 @@ Finally, {Create2} contains all necessary utilities to safely use the https://bl {{ECDSA}} +{{MessageHashUtils}} + {{SignatureChecker}} {{MerkleProof}} diff --git a/contracts/utils/cryptography/ECDSA.sol b/contracts/utils/cryptography/ECDSA.sol index aabfb5ca2..74064c889 100644 --- a/contracts/utils/cryptography/ECDSA.sol +++ b/contracts/utils/cryptography/ECDSA.sol @@ -3,8 +3,6 @@ pragma solidity ^0.8.19; -import {Strings} from "../Strings.sol"; - /** * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations. * @@ -34,18 +32,6 @@ library ECDSA { */ error ECDSAInvalidSignatureS(bytes32 s); - function _throwError(RecoverError error, bytes32 errorArg) private pure { - if (error == RecoverError.NoError) { - return; // no error: do nothing - } else if (error == RecoverError.InvalidSignature) { - revert ECDSAInvalidSignature(); - } else if (error == RecoverError.InvalidSignatureLength) { - revert ECDSAInvalidSignatureLength(uint256(errorArg)); - } else if (error == RecoverError.InvalidSignatureS) { - revert ECDSAInvalidSignatureS(errorArg); - } - } - /** * @dev Returns the address that signed a hashed message (`hash`) with * `signature` or error string. This address can then be used for verification purposes. @@ -58,7 +44,7 @@ library ECDSA { * verification to be secure: it is possible to craft signatures that * recover to arbitrary addresses for non-hashed data. A safe way to ensure * this is by receiving a hash of the original message (which may otherwise - * be too long), and then calling {toEthSignedMessageHash} on it. + * be too long), and then calling {MessageHashUtils-toEthSignedMessageHash} on it. * * Documentation for signature generation: * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js] @@ -95,7 +81,7 @@ library ECDSA { * verification to be secure: it is possible to craft signatures that * recover to arbitrary addresses for non-hashed data. A safe way to ensure * this is by receiving a hash of the original message (which may otherwise - * be too long), and then calling {toEthSignedMessageHash} on it. + * be too long), and then calling {MessageHashUtils-toEthSignedMessageHash} on it. */ function recover(bytes32 hash, bytes memory signature) internal pure returns (address) { (address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, signature); @@ -169,63 +155,17 @@ library ECDSA { } /** - * @dev Returns an Ethereum Signed Message, created from a `hash`. This - * produces hash corresponding to the one signed with the - * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`] - * JSON-RPC method as part of EIP-191. - * - * See {recover}. - */ - function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32 message) { - // 32 is the length in bytes of hash, - // enforced by the type signature above - /// @solidity memory-safe-assembly - assembly { - mstore(0x00, "\x19Ethereum Signed Message:\n32") - mstore(0x1c, hash) - message := keccak256(0x00, 0x3c) - } - } - - /** - * @dev Returns an Ethereum Signed Message, created from `s`. This - * produces hash corresponding to the one signed with the - * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`] - * JSON-RPC method as part of EIP-191. - * - * See {recover}. + * @dev Optionally reverts with the corresponding custom error according to the `error` argument provided. */ - function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32) { - return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n", Strings.toString(s.length), s)); - } - - /** - * @dev Returns an Ethereum Signed Typed Data, created from a - * `domainSeparator` and a `structHash`. This produces hash corresponding - * to the one signed with the - * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`] - * JSON-RPC method as part of EIP-712. - * - * See {recover}. - */ - function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32 data) { - /// @solidity memory-safe-assembly - assembly { - let ptr := mload(0x40) - mstore(ptr, hex"19_01") - mstore(add(ptr, 0x02), domainSeparator) - mstore(add(ptr, 0x22), structHash) - data := keccak256(ptr, 0x42) + function _throwError(RecoverError error, bytes32 errorArg) private pure { + if (error == RecoverError.NoError) { + return; // no error: do nothing + } else if (error == RecoverError.InvalidSignature) { + revert ECDSAInvalidSignature(); + } else if (error == RecoverError.InvalidSignatureLength) { + revert ECDSAInvalidSignatureLength(uint256(errorArg)); + } else if (error == RecoverError.InvalidSignatureS) { + revert ECDSAInvalidSignatureS(errorArg); } } - - /** - * @dev Returns an Ethereum Signed Data with intended validator, created from a - * `validator` and `data` according to the version 0 of EIP-191. - * - * See {recover}. - */ - function toDataWithIntendedValidatorHash(address validator, bytes memory data) internal pure returns (bytes32) { - return keccak256(abi.encodePacked(hex"19_00", validator, data)); - } } diff --git a/contracts/utils/cryptography/EIP712.sol b/contracts/utils/cryptography/EIP712.sol index ff34e8146..36f076e55 100644 --- a/contracts/utils/cryptography/EIP712.sol +++ b/contracts/utils/cryptography/EIP712.sol @@ -3,16 +3,17 @@ pragma solidity ^0.8.19; -import {ECDSA} from "./ECDSA.sol"; +import {MessageHashUtils} from "./MessageHashUtils.sol"; import {ShortStrings, ShortString} from "../ShortStrings.sol"; import {IERC5267} from "../../interfaces/IERC5267.sol"; /** * @dev https://eips.ethereum.org/EIPS/eip-712[EIP 712] is a standard for hashing and signing of typed structured data. * - * The encoding specified in the EIP is very generic, and such a generic implementation in Solidity is not feasible, - * thus this contract does not implement the encoding itself. Protocols need to implement the type-specific encoding - * they need in their contracts using a combination of `abi.encode` and `keccak256`. + * The encoding scheme specified in the EIP requires a domain separator and a hash of the typed structured data, whose + * encoding is very generic and therefore its implementation in Solidity is not feasible, thus this contract + * does not implement the encoding itself. Protocols need to implement the type-specific encoding they need in order to + * produce the hash of their typed data using a combination of `abi.encode` and `keccak256`. * * This contract implements the EIP 712 domain separator ({_domainSeparatorV4}) that is used as part of the encoding * scheme, and the final step of the encoding to obtain the message digest that is then signed via ECDSA @@ -25,7 +26,7 @@ import {IERC5267} from "../../interfaces/IERC5267.sol"; * https://docs.metamask.io/guide/signing-data.html[`eth_signTypedDataV4` in MetaMask]. * * NOTE: In the upgradeable version of this contract, the cached values will correspond to the address, and the domain - * separator of the implementation contract. This will cause the `_domainSeparatorV4` function to always rebuild the + * separator of the implementation contract. This will cause the {_domainSeparatorV4} function to always rebuild the * separator from the immutable values, which is cheaper than accessing a cached version in cold storage. * * @custom:oz-upgrades-unsafe-allow state-variable-immutable state-variable-assignment @@ -104,7 +105,7 @@ abstract contract EIP712 is IERC5267 { * ``` */ function _hashTypedDataV4(bytes32 structHash) internal view virtual returns (bytes32) { - return ECDSA.toTypedDataHash(_domainSeparatorV4(), structHash); + return MessageHashUtils.toTypedDataHash(_domainSeparatorV4(), structHash); } /** diff --git a/contracts/utils/cryptography/MessageHashUtils.sol b/contracts/utils/cryptography/MessageHashUtils.sol new file mode 100644 index 000000000..05ce35af8 --- /dev/null +++ b/contracts/utils/cryptography/MessageHashUtils.sol @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.19; + +import {Strings} from "../Strings.sol"; + +/** + * @dev Signature message hash utilities for producing digests to be consumed by {ECDSA} recovery or signing. + * + * The library provides methods for generating a hash of a message that conforms to the + * https://eips.ethereum.org/EIPS/eip-191[EIP 191] and https://eips.ethereum.org/EIPS/eip-712[EIP 712] + * specifications. + */ +library MessageHashUtils { + /** + * @dev Returns the keccak256 digest of an EIP-191 signed data with version + * `0x45` (`personal_sign` messages). + * + * The digest is calculated by prefixing a bytes32 `messageHash` with + * `"\x19Ethereum Signed Message:\n32"` and hashing the result. It corresponds with the + * hash signed when using the https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`] JSON-RPC method. + * + * NOTE: The `hash` parameter is intended to be the result of hashing a raw message with + * keccak256, althoguh any bytes32 value can be safely used because the final digest will + * be re-hashed. + * + * See {ECDSA-recover}. + */ + function toEthSignedMessageHash(bytes32 messageHash) internal pure returns (bytes32 digest) { + /// @solidity memory-safe-assembly + assembly { + mstore(0x00, "\x19Ethereum Signed Message:\n32") // 32 is the bytes-length of messageHash + mstore(0x1c, messageHash) // 0x1c (28) is the length of the prefix + digest := keccak256(0x00, 0x3c) // 0x3c is the length of the prefix (0x1c) + messageHash (0x20) + } + } + + /** + * @dev Returns the keccak256 digest of an EIP-191 signed data with version + * `0x45` (`personal_sign` messages). + * + * The digest is calculated by prefixing an arbitrary `message` with + * `"\x19Ethereum Signed Message:\n" + len(message)` and hashing the result. It corresponds with the + * hash signed when using the https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`] JSON-RPC method. + * + * See {ECDSA-recover}. + */ + function toEthSignedMessageHash(bytes memory message) internal pure returns (bytes32 digest) { + return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n", Strings.toString(message.length), message)); + } + + /** + * @dev Returns the keccak256 digest of an EIP-191 signed data with version + * `0x00` (data with intended validator). + * + * The digest is calculated by prefixing an arbitrary `data` with `"\x19\x00"` and the intended + * `validator` address. Then hashing the result. + * + * See {ECDSA-recover}. + */ + function toDataWithIntendedValidatorHash( + address validator, + bytes memory data + ) internal pure returns (bytes32 digest) { + return keccak256(abi.encodePacked(hex"19_00", validator, data)); + } + + /** + * @dev Returns the keccak256 digest of an EIP-712 typed data (EIP-191 version `0x01`). + * + * The digest is calculated from a `domainSeparator` and a `structHash`, by prefixing them with + * `\x19\x01` and hashing the result. It corresponds to the hash signed by the + * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`] JSON-RPC method as part of EIP-712. + * + * See {ECDSA-recover}. + */ + function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32 digest) { + /// @solidity memory-safe-assembly + assembly { + let ptr := mload(0x40) + mstore(ptr, hex"19_01") + mstore(add(ptr, 0x02), domainSeparator) + mstore(add(ptr, 0x22), structHash) + digest := keccak256(ptr, 0x42) + } + } +} diff --git a/docs/modules/ROOT/pages/utilities.adoc b/docs/modules/ROOT/pages/utilities.adoc index 3a1ba5420..2ac7b63c9 100644 --- a/docs/modules/ROOT/pages/utilities.adoc +++ b/docs/modules/ROOT/pages/utilities.adoc @@ -9,11 +9,12 @@ The OpenZeppelin Contracts provide a ton of useful utilities that you can use in xref:api:utils.adoc#ECDSA[`ECDSA`] provides functions for recovering and managing Ethereum account ECDSA signatures. These are often generated via https://web3js.readthedocs.io/en/v1.7.3/web3-eth.html#sign[`web3.eth.sign`], and are a 65 byte array (of type `bytes` in Solidity) arranged the following way: `[[v (1)], [r (32)], [s (32)]]`. -The data signer can be recovered with xref:api:utils.adoc#ECDSA-recover-bytes32-bytes-[`ECDSA.recover`], and its address compared to verify the signature. Most wallets will hash the data to sign and add the prefix '\x19Ethereum Signed Message:\n', so when attempting to recover the signer of an Ethereum signed message hash, you'll want to use xref:api:utils.adoc#ECDSA-toEthSignedMessageHash-bytes32-[`toEthSignedMessageHash`]. +The data signer can be recovered with xref:api:utils.adoc#ECDSA-recover-bytes32-bytes-[`ECDSA.recover`], and its address compared to verify the signature. Most wallets will hash the data to sign and add the prefix '\x19Ethereum Signed Message:\n', so when attempting to recover the signer of an Ethereum signed message hash, you'll want to use xref:api:utils.adoc#MessageHashUtils-toEthSignedMessageHash-bytes32-[`toEthSignedMessageHash`]. [source,solidity] ---- using ECDSA for bytes32; +using MessageHashUtils for bytes32; function _verify(bytes32 data, bytes memory signature, address account) internal pure returns (bool) { return data @@ -22,7 +23,7 @@ function _verify(bytes32 data, bytes memory signature, address account) internal } ---- -WARNING: Getting signature verification right is not trivial: make sure you fully read and understand xref:api:utils.adoc#ECDSA[`ECDSA`]'s documentation. +WARNING: Getting signature verification right is not trivial: make sure you fully read and understand xref:api:utils.adoc#MessageHashUtils[`MessageHashUtils`]'s and xref:api:utils.adoc#ECDSA[`ECDSA`]'s documentation. === Verifying Merkle Proofs diff --git a/scripts/upgradeable/upgradeable.patch b/scripts/upgradeable/upgradeable.patch index ebf3aa4e2..71a76d78c 100644 --- a/scripts/upgradeable/upgradeable.patch +++ b/scripts/upgradeable/upgradeable.patch @@ -126,20 +126,20 @@ index df141192..1cf90ad1 100644 "keywords": [ "solidity", diff --git a/contracts/utils/cryptography/EIP712.sol b/contracts/utils/cryptography/EIP712.sol -index ff34e814..a9d08d5c 100644 +index 36f076e5..90c1db78 100644 --- a/contracts/utils/cryptography/EIP712.sol +++ b/contracts/utils/cryptography/EIP712.sol @@ -4,7 +4,6 @@ pragma solidity ^0.8.19; - import {ECDSA} from "./ECDSA.sol"; + import {MessageHashUtils} from "./MessageHashUtils.sol"; -import {ShortStrings, ShortString} from "../ShortStrings.sol"; import {IERC5267} from "../../interfaces/IERC5267.sol"; /** -@@ -27,28 +26,18 @@ import {IERC5267} from "../../interfaces/IERC5267.sol"; +@@ -28,28 +27,18 @@ import {IERC5267} from "../../interfaces/IERC5267.sol"; * NOTE: In the upgradeable version of this contract, the cached values will correspond to the address, and the domain - * separator of the implementation contract. This will cause the `_domainSeparatorV4` function to always rebuild the + * separator of the implementation contract. This will cause the {_domainSeparatorV4} function to always rebuild the * separator from the immutable values, which is cheaper than accessing a cached version in cold storage. - * - * @custom:oz-upgrades-unsafe-allow state-variable-immutable state-variable-assignment @@ -170,7 +170,7 @@ index ff34e814..a9d08d5c 100644 /** * @dev Initializes the domain separator and parameter caches. -@@ -63,29 +52,23 @@ abstract contract EIP712 is IERC5267 { +@@ -64,29 +53,23 @@ abstract contract EIP712 is IERC5267 { * contract upgrade]. */ constructor(string memory name, string memory version) { @@ -208,7 +208,7 @@ index ff34e814..a9d08d5c 100644 } /** -@@ -124,6 +107,10 @@ abstract contract EIP712 is IERC5267 { +@@ -125,6 +108,10 @@ abstract contract EIP712 is IERC5267 { uint256[] memory extensions ) { @@ -219,7 +219,7 @@ index ff34e814..a9d08d5c 100644 return ( hex"0f", // 01111 _EIP712Name(), -@@ -138,22 +125,62 @@ abstract contract EIP712 is IERC5267 { +@@ -139,22 +126,62 @@ abstract contract EIP712 is IERC5267 { /** * @dev The name parameter for the EIP712 domain. * diff --git a/test/utils/cryptography/ECDSA.test.js b/test/utils/cryptography/ECDSA.test.js index 3fd112a18..f164ef196 100644 --- a/test/utils/cryptography/ECDSA.test.js +++ b/test/utils/cryptography/ECDSA.test.js @@ -1,6 +1,6 @@ require('@openzeppelin/test-helpers'); const { expectRevertCustomError } = require('../../helpers/customError'); -const { toEthSignedMessageHash, toDataWithIntendedValidatorHash } = require('../../helpers/sign'); +const { toEthSignedMessageHash } = require('../../helpers/sign'); const { expect } = require('chai'); @@ -9,7 +9,6 @@ const ECDSA = artifacts.require('$ECDSA'); const TEST_MESSAGE = web3.utils.sha3('OpenZeppelin'); const WRONG_MESSAGE = web3.utils.sha3('Nope'); const NON_HASH_MESSAGE = '0x' + Buffer.from('abcd').toString('hex'); -const RANDOM_ADDRESS = web3.utils.toChecksumAddress(web3.utils.randomHex(20)); function to2098Format(signature) { const long = web3.utils.hexToBytes(signature); @@ -243,26 +242,4 @@ contract('ECDSA', function (accounts) { expect(() => to2098Format(highSSignature)).to.throw("invalid signature 's' value"); }); }); - - context('toEthSignedMessageHash', function () { - it('prefixes bytes32 data correctly', async function () { - expect(await this.ecdsa.methods['$toEthSignedMessageHash(bytes32)'](TEST_MESSAGE)).to.equal( - toEthSignedMessageHash(TEST_MESSAGE), - ); - }); - - it('prefixes dynamic length data correctly', async function () { - expect(await this.ecdsa.methods['$toEthSignedMessageHash(bytes)'](NON_HASH_MESSAGE)).to.equal( - toEthSignedMessageHash(NON_HASH_MESSAGE), - ); - }); - }); - - context('toDataWithIntendedValidatorHash', function () { - it('returns the hash correctly', async function () { - expect( - await this.ecdsa.methods['$toDataWithIntendedValidatorHash(address,bytes)'](RANDOM_ADDRESS, NON_HASH_MESSAGE), - ).to.equal(toDataWithIntendedValidatorHash(RANDOM_ADDRESS, NON_HASH_MESSAGE)); - }); - }); }); diff --git a/test/utils/cryptography/MessageHashUtils.test.js b/test/utils/cryptography/MessageHashUtils.test.js new file mode 100644 index 000000000..b38e945da --- /dev/null +++ b/test/utils/cryptography/MessageHashUtils.test.js @@ -0,0 +1,55 @@ +require('@openzeppelin/test-helpers'); +const { toEthSignedMessageHash, toDataWithIntendedValidatorHash } = require('../../helpers/sign'); +const { domainSeparator, hashTypedData } = require('../../helpers/eip712'); + +const { expect } = require('chai'); + +const MessageHashUtils = artifacts.require('$MessageHashUtils'); + +contract('MessageHashUtils', function () { + beforeEach(async function () { + this.messageHashUtils = await MessageHashUtils.new(); + + this.message = '0x' + Buffer.from('abcd').toString('hex'); + this.messageHash = web3.utils.sha3(this.message); + this.verifyingAddress = web3.utils.toChecksumAddress(web3.utils.randomHex(20)); + }); + + context('toEthSignedMessageHash', function () { + it('prefixes bytes32 data correctly', async function () { + expect(await this.messageHashUtils.methods['$toEthSignedMessageHash(bytes32)'](this.messageHash)).to.equal( + toEthSignedMessageHash(this.messageHash), + ); + }); + + it('prefixes dynamic length data correctly', async function () { + expect(await this.messageHashUtils.methods['$toEthSignedMessageHash(bytes)'](this.message)).to.equal( + toEthSignedMessageHash(this.message), + ); + }); + }); + + context('toDataWithIntendedValidatorHash', function () { + it('returns the digest correctly', async function () { + expect( + await this.messageHashUtils.$toDataWithIntendedValidatorHash(this.verifyingAddress, this.message), + ).to.equal(toDataWithIntendedValidatorHash(this.verifyingAddress, this.message)); + }); + }); + + context('toTypedDataHash', function () { + it('returns the digest correctly', async function () { + const domain = { + name: 'Test', + version: 1, + chainId: 1, + verifyingContract: this.verifyingAddress, + }; + const structhash = web3.utils.randomHex(32); + const expectedDomainSeparator = await domainSeparator(domain); + expect(await this.messageHashUtils.$toTypedDataHash(expectedDomainSeparator, structhash)).to.equal( + hashTypedData(domain, structhash), + ); + }); + }); +}); From f5bf7233cb32b28d29a0ff5f1911d4c5118836a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Fri, 7 Jul 2023 18:56:49 -0600 Subject: [PATCH 145/182] Add `ERC2771Forwarder` fuzz tests for avoiding loss of unused ETH (#4396) Co-authored-by: Hadrien Croubois Co-authored-by: Francisco --- contracts/metatx/ERC2771Forwarder.sol | 12 +- test/metatx/ERC2771Forwarder.t.sol | 165 ++++++++++++++++++++++++++ 2 files changed, 171 insertions(+), 6 deletions(-) create mode 100644 test/metatx/ERC2771Forwarder.t.sol diff --git a/contracts/metatx/ERC2771Forwarder.sol b/contracts/metatx/ERC2771Forwarder.sol index f271d5d3b..84d736c74 100644 --- a/contracts/metatx/ERC2771Forwarder.sol +++ b/contracts/metatx/ERC2771Forwarder.sol @@ -53,7 +53,7 @@ contract ERC2771Forwarder is EIP712, Nonces { bytes signature; } - bytes32 private constant _FORWARD_REQUEST_TYPEHASH = + bytes32 internal constant _FORWARD_REQUEST_TYPEHASH = keccak256( "ForwardRequest(address from,address to,uint256 value,uint256 gas,uint256 nonce,uint48 deadline,bytes data)" ); @@ -255,7 +255,7 @@ contract ERC2771Forwarder is EIP712, Nonces { abi.encodePacked(request.data, request.from) ); - _checkForwardedGas(request); + _checkForwardedGas(gasleft(), request); emit ExecutedForwardRequest(signer, currentNonce, success); } @@ -270,10 +270,10 @@ contract ERC2771Forwarder is EIP712, Nonces { * * It reverts consuming all the available gas if the forwarded gas is not the requested gas. * - * IMPORTANT: This function should be called exactly the end of the forwarded call. Any gas consumed - * in between will make room for bypassing this check. + * IMPORTANT: The `gasLeft` parameter should be measured exactly at the end of the forwarded call. + * Any gas consumed in between will make room for bypassing this check. */ - function _checkForwardedGas(ForwardRequestData calldata request) private view { + function _checkForwardedGas(uint256 gasLeft, ForwardRequestData calldata request) private pure { // To avoid insufficient gas griefing attacks, as referenced in https://ronan.eth.limo/blog/ethereum-gas-dangers/ // // A malicious relayer can attempt to shrink the gas forwarded so that the underlying call reverts out-of-gas @@ -295,7 +295,7 @@ contract ERC2771Forwarder is EIP712, Nonces { // - req.gas >= X * 63 / 64 // In other words if req.gas < X * 63 / 64 then req.gas / 63 <= gasleft(), thus if the relayer behaves honestly // the forwarding does not revert. - if (gasleft() < request.gas / 63) { + if (gasLeft < request.gas / 63) { // We explicitly trigger invalid opcode to consume all gas and bubble-up the effects, since // neither revert or assert consume all gas since Solidity 0.8.0 // https://docs.soliditylang.org/en/v0.8.0/control-structures.html#panic-via-assert-and-error-via-require diff --git a/test/metatx/ERC2771Forwarder.t.sol b/test/metatx/ERC2771Forwarder.t.sol new file mode 100644 index 000000000..946599cc9 --- /dev/null +++ b/test/metatx/ERC2771Forwarder.t.sol @@ -0,0 +1,165 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.19; + +import {Test} from "forge-std/Test.sol"; +import {ERC2771Forwarder} from "contracts/metatx/ERC2771Forwarder.sol"; +import {CallReceiverMock} from "contracts/mocks/CallReceiverMock.sol"; + +struct ForwardRequest { + address from; + address to; + uint256 value; + uint256 gas; + uint256 nonce; + uint48 deadline; + bytes data; +} + +contract ERC2771ForwarderMock is ERC2771Forwarder { + constructor(string memory name) ERC2771Forwarder(name) {} + + function structHash(ForwardRequest calldata request) external view returns (bytes32) { + return + _hashTypedDataV4( + keccak256( + abi.encode( + _FORWARD_REQUEST_TYPEHASH, + request.from, + request.to, + request.value, + request.gas, + request.nonce, + request.deadline, + keccak256(request.data) + ) + ) + ); + } +} + +contract ERC2771ForwarderTest is Test { + ERC2771ForwarderMock internal _erc2771Forwarder; + CallReceiverMock internal _receiver; + + uint256 internal _signerPrivateKey; + uint256 internal _relayerPrivateKey; + + address internal _signer; + address internal _relayer; + + uint256 internal constant _MAX_ETHER = 10_000_000; // To avoid overflow + + function setUp() public { + _erc2771Forwarder = new ERC2771ForwarderMock("ERC2771Forwarder"); + _receiver = new CallReceiverMock(); + + _signerPrivateKey = 0xA11CE; + _relayerPrivateKey = 0xB0B; + + _signer = vm.addr(_signerPrivateKey); + _relayer = vm.addr(_relayerPrivateKey); + } + + function _forgeRequestData( + uint256 value, + uint256 nonce, + uint48 deadline, + bytes memory data + ) private view returns (ERC2771Forwarder.ForwardRequestData memory) { + ForwardRequest memory request = ForwardRequest({ + from: _signer, + to: address(_receiver), + value: value, + gas: 30000, + nonce: nonce, + deadline: deadline, + data: data + }); + + bytes32 digest = _erc2771Forwarder.structHash(request); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(_signerPrivateKey, digest); + bytes memory signature = abi.encodePacked(r, s, v); + + return + ERC2771Forwarder.ForwardRequestData({ + from: request.from, + to: request.to, + value: request.value, + gas: request.gas, + deadline: request.deadline, + data: request.data, + signature: signature + }); + } + + function testExecuteAvoidsETHStuck(uint256 initialBalance, uint256 value, bool targetReverts) public { + initialBalance = bound(initialBalance, 0, _MAX_ETHER); + value = bound(value, 0, _MAX_ETHER); + + vm.deal(address(_erc2771Forwarder), initialBalance); + + uint256 nonce = _erc2771Forwarder.nonces(_signer); + + vm.deal(address(this), value); + + ERC2771Forwarder.ForwardRequestData memory requestData = _forgeRequestData({ + value: value, + nonce: nonce, + deadline: uint48(block.timestamp + 1), + data: targetReverts + ? abi.encodeCall(CallReceiverMock.mockFunctionRevertsNoReason, ()) + : abi.encodeCall(CallReceiverMock.mockFunction, ()) + }); + + if (targetReverts) { + vm.expectRevert(); + } + + _erc2771Forwarder.execute{value: value}(requestData); + assertEq(address(_erc2771Forwarder).balance, initialBalance); + } + + function testExecuteBatchAvoidsETHStuck(uint256 initialBalance, uint256 batchSize, uint256 value) public { + batchSize = bound(batchSize, 1, 10); + initialBalance = bound(initialBalance, 0, _MAX_ETHER); + value = bound(value, 0, _MAX_ETHER); + + vm.deal(address(_erc2771Forwarder), initialBalance); + uint256 nonce = _erc2771Forwarder.nonces(_signer); + + ERC2771Forwarder.ForwardRequestData[] memory batchRequestDatas = new ERC2771Forwarder.ForwardRequestData[]( + batchSize + ); + + uint256 expectedRefund; + + for (uint256 i = 0; i < batchSize; ++i) { + bytes memory data; + bool succeed = uint256(keccak256(abi.encodePacked(initialBalance, i))) % 2 == 0; + + if (succeed) { + data = abi.encodeCall(CallReceiverMock.mockFunction, ()); + } else { + expectedRefund += value; + data = abi.encodeCall(CallReceiverMock.mockFunctionRevertsNoReason, ()); + } + + batchRequestDatas[i] = _forgeRequestData({ + value: value, + nonce: nonce + i, + deadline: uint48(block.timestamp + 1), + data: data + }); + } + + address payable refundReceiver = payable(address(0xebe)); + uint256 totalValue = value * batchSize; + + vm.deal(address(this), totalValue); + _erc2771Forwarder.executeBatch{value: totalValue}(batchRequestDatas, refundReceiver); + + assertEq(address(_erc2771Forwarder).balance, initialBalance); + assertEq(refundReceiver.balance, expectedRefund); + } +} From 6d74b913885d729b5c72209aa03c4b68a33c794c Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Sat, 8 Jul 2023 03:23:28 +0200 Subject: [PATCH 146/182] Remove superfluous receive() function from Proxy.sol (#4434) Co-authored-by: Francisco Giordano --- .changeset/eight-peaches-guess.md | 5 +++++ contracts/proxy/Proxy.sol | 8 -------- 2 files changed, 5 insertions(+), 8 deletions(-) create mode 100644 .changeset/eight-peaches-guess.md diff --git a/.changeset/eight-peaches-guess.md b/.changeset/eight-peaches-guess.md new file mode 100644 index 000000000..ba4e87c17 --- /dev/null +++ b/.changeset/eight-peaches-guess.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': minor +--- + +`Proxy`: Removed redundant `receive` function. diff --git a/contracts/proxy/Proxy.sol b/contracts/proxy/Proxy.sol index c2875aeac..8c8925b9b 100644 --- a/contracts/proxy/Proxy.sol +++ b/contracts/proxy/Proxy.sol @@ -68,14 +68,6 @@ abstract contract Proxy { _fallback(); } - /** - * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if call data - * is empty. - */ - receive() external payable virtual { - _fallback(); - } - /** * @dev Hook that is called before falling back to the implementation. Can happen as part of a manual `_fallback` * call, or as part of the Solidity `fallback` or `receive` functions. From 5229b75785213541e93fb0a466a3d102a3bf5dbe Mon Sep 17 00:00:00 2001 From: Eric Lau Date: Sat, 8 Jul 2023 18:24:12 -0400 Subject: [PATCH 147/182] Use immutable beacon address in BeaconProxy (#4435) Co-authored-by: Hadrien Croubois Co-authored-by: Francisco Giordano --- .changeset/proud-seals-complain.md | 5 +++++ contracts/proxy/ERC1967/ERC1967Utils.sol | 7 +++++-- contracts/proxy/beacon/BeaconProxy.sol | 16 +++++++++++++++- 3 files changed, 25 insertions(+), 3 deletions(-) create mode 100644 .changeset/proud-seals-complain.md diff --git a/.changeset/proud-seals-complain.md b/.changeset/proud-seals-complain.md new file mode 100644 index 000000000..35df4777e --- /dev/null +++ b/.changeset/proud-seals-complain.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': patch +--- + +`BeaconProxy`: Use an immutable variable to store the address of the beacon. It is no longer possible for a `BeaconProxy` to upgrade by changing to another beacon. diff --git a/contracts/proxy/ERC1967/ERC1967Utils.sol b/contracts/proxy/ERC1967/ERC1967Utils.sol index f7caaa684..3ad93ff3c 100644 --- a/contracts/proxy/ERC1967/ERC1967Utils.sol +++ b/contracts/proxy/ERC1967/ERC1967Utils.sol @@ -161,10 +161,13 @@ library ERC1967Utils { } /** - * @dev Perform beacon upgrade with additional setup call. Note: This upgrades the address of the beacon, it does - * not upgrade the implementation contained in the beacon (see {UpgradeableBeacon-_setImplementation} for that). + * @dev Change the beacon and trigger a setup call. * * Emits an {IERC1967-BeaconUpgraded} event. + * + * CAUTION: Invoking this function has no effect on an instance of {BeaconProxy} since v5, since + * it uses an immutable beacon without looking at the value of the ERC-1967 beacon slot for + * efficiency. */ function upgradeBeaconToAndCall(address newBeacon, bytes memory data, bool forceCall) internal { _setBeacon(newBeacon); diff --git a/contracts/proxy/beacon/BeaconProxy.sol b/contracts/proxy/beacon/BeaconProxy.sol index 94e697ca4..93e306884 100644 --- a/contracts/proxy/beacon/BeaconProxy.sol +++ b/contracts/proxy/beacon/BeaconProxy.sol @@ -12,8 +12,14 @@ import {ERC1967Utils} from "../ERC1967/ERC1967Utils.sol"; * * The beacon address is stored in storage slot `uint256(keccak256('eip1967.proxy.beacon')) - 1`, so that it doesn't * conflict with the storage layout of the implementation behind the proxy. + * + * CAUTION: The beacon address can only be set once during construction, and cannot be changed afterwards. + * You must ensure that you either control the beacon, or trust the beacon to not upgrade the implementation maliciously. */ contract BeaconProxy is Proxy { + // An immutable address for the beacon to avoid unnecessary SLOADs before each delegate call. + address private immutable _beacon; + /** * @dev Initializes the proxy with `beacon`. * @@ -27,12 +33,20 @@ contract BeaconProxy is Proxy { */ constructor(address beacon, bytes memory data) payable { ERC1967Utils.upgradeBeaconToAndCall(beacon, data, false); + _beacon = beacon; } /** * @dev Returns the current implementation address of the associated beacon. */ function _implementation() internal view virtual override returns (address) { - return IBeacon(ERC1967Utils.getBeacon()).implementation(); + return IBeacon(_getBeacon()).implementation(); + } + + /** + * @dev Returns the beacon. + */ + function _getBeacon() internal view virtual returns (address) { + return _beacon; } } From e47b53bce4991a3eb9ecdca59a82b9956a3f0699 Mon Sep 17 00:00:00 2001 From: Eric Lau Date: Sun, 9 Jul 2023 11:33:23 -0400 Subject: [PATCH 148/182] Improve BeaconProxy documentation for storage slot (#4438) --- contracts/proxy/beacon/BeaconProxy.sol | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/contracts/proxy/beacon/BeaconProxy.sol b/contracts/proxy/beacon/BeaconProxy.sol index 93e306884..14a8d1b66 100644 --- a/contracts/proxy/beacon/BeaconProxy.sol +++ b/contracts/proxy/beacon/BeaconProxy.sol @@ -10,11 +10,15 @@ import {ERC1967Utils} from "../ERC1967/ERC1967Utils.sol"; /** * @dev This contract implements a proxy that gets the implementation address for each call from an {UpgradeableBeacon}. * - * The beacon address is stored in storage slot `uint256(keccak256('eip1967.proxy.beacon')) - 1`, so that it doesn't - * conflict with the storage layout of the implementation behind the proxy. + * The beacon address can only be set once during construction, and cannot be changed afterwards. It is stored in an immutable + * variable to avoid unnecessary storage reads, and also in the beacon storage slot specified by + * https://eips.ethereum.org/EIPS/eip-1967[EIP1967] so that it can be accessed externally. * - * CAUTION: The beacon address can only be set once during construction, and cannot be changed afterwards. - * You must ensure that you either control the beacon, or trust the beacon to not upgrade the implementation maliciously. + * CAUTION: Since the beacon address can never be changed, you must ensure that you either control the beacon, or trust the + * beacon to not upgrade the implementation maliciously. + * + * IMPORTANT: Do not use the implementation logic to modify the beacon storage slot. Doing so would leave the proxy in an + * inconsistent state where the beacon storage slot does not match the beacon address. */ contract BeaconProxy is Proxy { // An immutable address for the beacon to avoid unnecessary SLOADs before each delegate call. From 4bac6fa310f85a439b8e269e17265e7f5cf85540 Mon Sep 17 00:00:00 2001 From: Francisco Date: Sun, 9 Jul 2023 18:36:23 -0300 Subject: [PATCH 149/182] Improve custom error helper when there is no match (#4437) --- test/helpers/customError.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/test/helpers/customError.js b/test/helpers/customError.js index e38170b78..ea5c36820 100644 --- a/test/helpers/customError.js +++ b/test/helpers/customError.js @@ -13,13 +13,10 @@ async function expectRevertCustomError(promise, expectedErrorName, args) { // VM Exception while processing transaction: // reverted with custom error 'InvalidAccountNonce("0x70997970C51812dc3A010C7d01b50e0d17dc79C8", 0)' - // We trim out anything inside the single quotes as comma-separated values - const [, error] = message.match(/'(.*)'/); - - // Attempt to parse as an error - const match = error.match(/(?\w+)\((?.*)\)/); + // Attempt to parse as a custom error + const match = message.match(/custom error '(?\w+)\((?.*)\)'/); if (!match) { - expect.fail(`Couldn't parse "${error}" as a custom error`); + expect.fail(`Could not parse as custom error. ${message}`); } // Extract the error name and parameters const errorName = match.groups.name; From 2a4396c9dd281222e38fa4b1136d291ff77a0b16 Mon Sep 17 00:00:00 2001 From: Prince Allwin <127643894+worksofallwin@users.noreply.github.com> Date: Mon, 10 Jul 2023 03:12:23 +0530 Subject: [PATCH 150/182] Add suggested remappings in readme (#4440) Co-authored-by: Francisco --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 27627f439..1befa024a 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,8 @@ OpenZeppelin Contracts features a [stable API](https://docs.openzeppelin.com/con $ forge install OpenZeppelin/openzeppelin-contracts ``` +Add `@openzeppelin/=lib/openzeppelin-contracts/` in `remappings.txt.` + ### Usage Once installed, you can use the contracts in the library by importing them: From cd981f6521e22f2b74ee9d1cea27c6b16d318528 Mon Sep 17 00:00:00 2001 From: Luiz Hemerly Date: Mon, 10 Jul 2023 17:26:02 -0300 Subject: [PATCH 151/182] Add custom linting rules (#4132) Co-authored-by: Francisco Giordano Co-authored-by: Hadrien Croubois --- .solhint.json | 15 ------ package-lock.json | 18 +++++++ package.json | 1 + scripts/solhint-custom/index.js | 84 +++++++++++++++++++++++++++++ scripts/solhint-custom/package.json | 4 ++ solhint.config.js | 20 +++++++ 6 files changed, 127 insertions(+), 15 deletions(-) delete mode 100644 .solhint.json create mode 100644 scripts/solhint-custom/index.js create mode 100644 scripts/solhint-custom/package.json create mode 100644 solhint.config.js diff --git a/.solhint.json b/.solhint.json deleted file mode 100644 index cb8a8af6d..000000000 --- a/.solhint.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "rules": { - "no-unused-vars": "error", - "const-name-snakecase": "error", - "contract-name-camelcase": "error", - "event-name-camelcase": "error", - "func-name-mixedcase": "error", - "func-param-name-mixedcase": "error", - "modifier-name-mixedcase": "error", - "private-vars-leading-underscore": "error", - "var-name-mixedcase": "error", - "imports-on-top": "error", - "no-global-import": "error" - } -} diff --git a/package-lock.json b/package-lock.json index 3f9a4f5da..d02ae3baf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43,6 +43,7 @@ "rimraf": "^3.0.2", "semver": "^7.3.5", "solhint": "^3.3.6", + "solhint-plugin-openzeppelin": "file:scripts/solhint-custom", "solidity-ast": "^0.4.25", "solidity-coverage": "^0.8.0", "solidity-docgen": "^0.6.0-beta.29", @@ -12436,6 +12437,10 @@ "prettier": "^2.8.3" } }, + "node_modules/solhint-plugin-openzeppelin": { + "resolved": "scripts/solhint-custom", + "link": true + }, "node_modules/solhint/node_modules/@solidity-parser/parser": { "version": "0.16.0", "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.16.0.tgz", @@ -15405,6 +15410,16 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "scripts/lints": { + "version": "1.0.2", + "extraneous": true, + "license": "MIT" + }, + "scripts/solhint-custom": { + "version": "1.0.2", + "dev": true, + "license": "MIT" } }, "dependencies": { @@ -25126,6 +25141,9 @@ } } }, + "solhint-plugin-openzeppelin": { + "version": "file:scripts/solhint-custom" + }, "solidity-ast": { "version": "0.4.49", "resolved": "https://registry.npmjs.org/solidity-ast/-/solidity-ast-0.4.49.tgz", diff --git a/package.json b/package.json index 9eae67320..ffa868ac7 100644 --- a/package.json +++ b/package.json @@ -84,6 +84,7 @@ "rimraf": "^3.0.2", "semver": "^7.3.5", "solhint": "^3.3.6", + "solhint-plugin-openzeppelin": "file:scripts/solhint-custom", "solidity-ast": "^0.4.25", "solidity-coverage": "^0.8.0", "solidity-docgen": "^0.6.0-beta.29", diff --git a/scripts/solhint-custom/index.js b/scripts/solhint-custom/index.js new file mode 100644 index 000000000..a4cba1a93 --- /dev/null +++ b/scripts/solhint-custom/index.js @@ -0,0 +1,84 @@ +const path = require('path'); +const minimatch = require('minimatch'); + +// Files matching these patterns will be ignored unless a rule has `static global = true` +const ignore = ['contracts/mocks/**/*', 'test/**/*']; + +class Base { + constructor(reporter, config, source, fileName) { + this.reporter = reporter; + this.ignored = this.constructor.global || ignore.some(p => minimatch(path.normalize(fileName), p)); + this.ruleId = this.constructor.ruleId; + if (this.ruleId === undefined) { + throw Error('missing ruleId static property'); + } + } + + error(node, message) { + if (!this.ignored) { + this.reporter.error(node, this.ruleId, message); + } + } +} + +module.exports = [ + class extends Base { + static ruleId = 'interface-names'; + + ContractDefinition(node) { + if (node.kind === 'interface' && !/^I[A-Z]/.test(node.name)) { + this.error(node, 'Interface names should have a capital I prefix'); + } + } + }, + + class extends Base { + static ruleId = 'private-variables'; + + VariableDeclaration(node) { + const constantOrImmutable = node.isDeclaredConst || node.isImmutable; + if (node.isStateVar && !constantOrImmutable && node.visibility !== 'private') { + this.error(node, 'State variables must be private'); + } + } + }, + + class extends Base { + static ruleId = 'leading-underscore'; + + VariableDeclaration(node) { + if (node.isDeclaredConst) { + if (/^_/.test(node.name)) { + // TODO: re-enable and fix + // this.error(node, 'Constant variables should not have leading underscore'); + } + } else if (node.visibility === 'private' && !/^_/.test(node.name)) { + this.error(node, 'Non-constant private variables must have leading underscore'); + } + } + + FunctionDefinition(node) { + if (node.visibility === 'private' || (node.visibility === 'internal' && node.parent.kind !== 'library')) { + if (!/^_/.test(node.name)) { + this.error(node, 'Private and internal functions must have leading underscore'); + } + } + if (node.visibility === 'internal' && node.parent.kind === 'library') { + if (/^_/.test(node.name)) { + this.error(node, 'Library internal functions should not have leading underscore'); + } + } + } + }, + + // TODO: re-enable and fix + // class extends Base { + // static ruleId = 'no-external-virtual'; + // + // FunctionDefinition(node) { + // if (node.visibility == 'external' && node.isVirtual) { + // this.error(node, 'Functions should not be external and virtual'); + // } + // } + // }, +]; diff --git a/scripts/solhint-custom/package.json b/scripts/solhint-custom/package.json new file mode 100644 index 000000000..d91e327a4 --- /dev/null +++ b/scripts/solhint-custom/package.json @@ -0,0 +1,4 @@ +{ + "name": "solhint-plugin-openzeppelin", + "private": true +} diff --git a/solhint.config.js b/solhint.config.js new file mode 100644 index 000000000..123ff91fa --- /dev/null +++ b/solhint.config.js @@ -0,0 +1,20 @@ +const customRules = require('./scripts/solhint-custom'); + +const rules = [ + 'no-unused-vars', + 'const-name-snakecase', + 'contract-name-camelcase', + 'event-name-camelcase', + 'func-name-mixedcase', + 'func-param-name-mixedcase', + 'modifier-name-mixedcase', + 'var-name-mixedcase', + 'imports-on-top', + 'no-global-import', + ...customRules.map(r => `openzeppelin/${r.ruleId}`), +]; + +module.exports = { + plugins: ['openzeppelin'], + rules: Object.fromEntries(rules.map(r => [r, 'error'])), +}; From 3d0edbecf1ce98d0f0960094c9ec7ee2b3947cc2 Mon Sep 17 00:00:00 2001 From: Francisco Date: Tue, 11 Jul 2023 14:49:58 -0300 Subject: [PATCH 152/182] Remove ERC1155Receiver in favor of ERC1155Holder (#4450) --- .changeset/afraid-walls-smell.md | 5 +++++ contracts/governance/TimelockController.sol | 3 +-- contracts/token/ERC1155/README.adoc | 2 -- .../token/ERC1155/utils/ERC1155Holder.sol | 14 ++++++++++--- .../token/ERC1155/utils/ERC1155Receiver.sol | 21 ------------------- 5 files changed, 17 insertions(+), 28 deletions(-) create mode 100644 .changeset/afraid-walls-smell.md delete mode 100644 contracts/token/ERC1155/utils/ERC1155Receiver.sol diff --git a/.changeset/afraid-walls-smell.md b/.changeset/afraid-walls-smell.md new file mode 100644 index 000000000..682fdde5e --- /dev/null +++ b/.changeset/afraid-walls-smell.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': major +--- + +`ERC1155Receiver`: Removed in favor of `ERC1155Holder`. diff --git a/contracts/governance/TimelockController.sol b/contracts/governance/TimelockController.sol index 2cf954d12..be86f7b88 100644 --- a/contracts/governance/TimelockController.sol +++ b/contracts/governance/TimelockController.sol @@ -6,7 +6,6 @@ pragma solidity ^0.8.19; import {AccessControl} from "../access/AccessControl.sol"; import {ERC721Holder} from "../token/ERC721/utils/ERC721Holder.sol"; import {ERC1155Holder} from "../token/ERC1155/utils/ERC1155Holder.sol"; -import {ERC1155Receiver} from "../token/ERC1155/utils/ERC1155Receiver.sol"; import {Address} from "../utils/Address.sol"; /** @@ -160,7 +159,7 @@ contract TimelockController is AccessControl, ERC721Holder, ERC1155Holder { */ function supportsInterface( bytes4 interfaceId - ) public view virtual override(AccessControl, ERC1155Receiver) returns (bool) { + ) public view virtual override(AccessControl, ERC1155Holder) returns (bool) { return super.supportsInterface(interfaceId); } diff --git a/contracts/token/ERC1155/README.adoc b/contracts/token/ERC1155/README.adoc index 48c7faa0a..1a56358ef 100644 --- a/contracts/token/ERC1155/README.adoc +++ b/contracts/token/ERC1155/README.adoc @@ -26,8 +26,6 @@ NOTE: This core set of contracts is designed to be unopinionated, allowing devel {{IERC1155Receiver}} -{{ERC1155Receiver}} - == Extensions {{ERC1155Pausable}} diff --git a/contracts/token/ERC1155/utils/ERC1155Holder.sol b/contracts/token/ERC1155/utils/ERC1155Holder.sol index e288f8692..908ad82c5 100644 --- a/contracts/token/ERC1155/utils/ERC1155Holder.sol +++ b/contracts/token/ERC1155/utils/ERC1155Holder.sol @@ -3,15 +3,23 @@ pragma solidity ^0.8.19; -import {ERC1155Receiver} from "./ERC1155Receiver.sol"; +import {IERC165, ERC165} from "../../../utils/introspection/ERC165.sol"; +import {IERC1155Receiver} from "../IERC1155Receiver.sol"; /** - * @dev Simple implementation of `ERC1155Receiver` that will allow a contract to hold ERC1155 tokens. + * @dev Simple implementation of `IERC1155Receiver` that will allow a contract to hold ERC1155 tokens. * * IMPORTANT: When inheriting this contract, you must include a way to use the received tokens, otherwise they will be * stuck. */ -abstract contract ERC1155Holder is ERC1155Receiver { +abstract contract ERC1155Holder is ERC165, IERC1155Receiver { + /** + * @dev See {IERC165-supportsInterface}. + */ + function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) { + return interfaceId == type(IERC1155Receiver).interfaceId || super.supportsInterface(interfaceId); + } + function onERC1155Received( address, address, diff --git a/contracts/token/ERC1155/utils/ERC1155Receiver.sol b/contracts/token/ERC1155/utils/ERC1155Receiver.sol deleted file mode 100644 index 02f922cf8..000000000 --- a/contracts/token/ERC1155/utils/ERC1155Receiver.sol +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts v4.4.1 (token/ERC1155/utils/ERC1155Receiver.sol) - -pragma solidity ^0.8.19; - -import {IERC1155Receiver} from "../IERC1155Receiver.sol"; -import {IERC165, ERC165} from "../../../utils/introspection/ERC165.sol"; - -/** - * @dev Basic contract implementing the ERC-165 interface for {IERC1155Receiver}. - * - * NOTE: This contract does not suffice to receive tokens. See {ERC1155Holder}. - */ -abstract contract ERC1155Receiver is ERC165, IERC1155Receiver { - /** - * @dev See {IERC165-supportsInterface}. - */ - function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) { - return interfaceId == type(IERC1155Receiver).interfaceId || super.supportsInterface(interfaceId); - } -} From 24ebff5ae947dca60101b1ba0cfe4a5e727b926d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Tue, 11 Jul 2023 11:51:40 -0600 Subject: [PATCH 153/182] Remove unused imports (#4436) Co-authored-by: Francisco --- contracts/access/AccessControl.sol | 1 - contracts/governance/extensions/GovernorTimelockCompound.sol | 1 - contracts/mocks/token/ERC20PermitNoRevertMock.sol | 1 - contracts/proxy/transparent/ProxyAdmin.sol | 2 +- contracts/proxy/utils/Initializable.sol | 2 -- contracts/token/ERC20/extensions/ERC20Votes.sol | 1 - contracts/token/ERC721/extensions/ERC721Royalty.sol | 1 - scripts/upgradeable/transpile.sh | 3 +++ test/token/ERC20/extensions/ERC4626.t.sol | 1 - 9 files changed, 4 insertions(+), 9 deletions(-) diff --git a/contracts/access/AccessControl.sol b/contracts/access/AccessControl.sol index ec30e81e7..d934eddaf 100644 --- a/contracts/access/AccessControl.sol +++ b/contracts/access/AccessControl.sol @@ -5,7 +5,6 @@ pragma solidity ^0.8.19; import {IAccessControl} from "./IAccessControl.sol"; import {Context} from "../utils/Context.sol"; -import {Strings} from "../utils/Strings.sol"; import {ERC165} from "../utils/introspection/ERC165.sol"; /** diff --git a/contracts/governance/extensions/GovernorTimelockCompound.sol b/contracts/governance/extensions/GovernorTimelockCompound.sol index 4b609723d..c50c82c57 100644 --- a/contracts/governance/extensions/GovernorTimelockCompound.sol +++ b/contracts/governance/extensions/GovernorTimelockCompound.sol @@ -5,7 +5,6 @@ pragma solidity ^0.8.19; import {IGovernorTimelock} from "./IGovernorTimelock.sol"; import {IGovernor, Governor} from "../Governor.sol"; -import {SafeCast} from "../../utils/math/SafeCast.sol"; import {ICompoundTimelock} from "../../vendor/compound/ICompoundTimelock.sol"; import {IERC165} from "../../interfaces/IERC165.sol"; import {Address} from "../../utils/Address.sol"; diff --git a/contracts/mocks/token/ERC20PermitNoRevertMock.sol b/contracts/mocks/token/ERC20PermitNoRevertMock.sol index 23395d75d..63ae363fa 100644 --- a/contracts/mocks/token/ERC20PermitNoRevertMock.sol +++ b/contracts/mocks/token/ERC20PermitNoRevertMock.sol @@ -2,7 +2,6 @@ pragma solidity ^0.8.19; -import {ERC20} from "../../token/ERC20/ERC20.sol"; import {ERC20Permit} from "../../token/ERC20/extensions/ERC20Permit.sol"; abstract contract ERC20PermitNoRevertMock is ERC20Permit { diff --git a/contracts/proxy/transparent/ProxyAdmin.sol b/contracts/proxy/transparent/ProxyAdmin.sol index 344c942f2..12d80a9c4 100644 --- a/contracts/proxy/transparent/ProxyAdmin.sol +++ b/contracts/proxy/transparent/ProxyAdmin.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.19; -import {ITransparentUpgradeableProxy, TransparentUpgradeableProxy} from "./TransparentUpgradeableProxy.sol"; +import {ITransparentUpgradeableProxy} from "./TransparentUpgradeableProxy.sol"; import {Ownable} from "../../access/Ownable.sol"; /** diff --git a/contracts/proxy/utils/Initializable.sol b/contracts/proxy/utils/Initializable.sol index a2e3569c4..08171015d 100644 --- a/contracts/proxy/utils/Initializable.sol +++ b/contracts/proxy/utils/Initializable.sol @@ -3,8 +3,6 @@ pragma solidity ^0.8.19; -import {Address} from "../../utils/Address.sol"; - /** * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an diff --git a/contracts/token/ERC20/extensions/ERC20Votes.sol b/contracts/token/ERC20/extensions/ERC20Votes.sol index e0d675b2a..74953d3d6 100644 --- a/contracts/token/ERC20/extensions/ERC20Votes.sol +++ b/contracts/token/ERC20/extensions/ERC20Votes.sol @@ -5,7 +5,6 @@ pragma solidity ^0.8.19; import {ERC20} from "../ERC20.sol"; import {Votes} from "../../../governance/utils/Votes.sol"; -import {SafeCast} from "../../../utils/math/SafeCast.sol"; import {Checkpoints} from "../../../utils/structs/Checkpoints.sol"; /** diff --git a/contracts/token/ERC721/extensions/ERC721Royalty.sol b/contracts/token/ERC721/extensions/ERC721Royalty.sol index eb128ac58..c4b8d371e 100644 --- a/contracts/token/ERC721/extensions/ERC721Royalty.sol +++ b/contracts/token/ERC721/extensions/ERC721Royalty.sol @@ -5,7 +5,6 @@ pragma solidity ^0.8.19; import {ERC721} from "../ERC721.sol"; import {ERC2981} from "../../common/ERC2981.sol"; -import {ERC165} from "../../../utils/introspection/ERC165.sol"; /** * @dev Extension of ERC721 with the ERC2981 NFT Royalty Standard, a standardized way to retrieve royalty payment diff --git a/scripts/upgradeable/transpile.sh b/scripts/upgradeable/transpile.sh index fbffe844e..c0cb9ff5e 100644 --- a/scripts/upgradeable/transpile.sh +++ b/scripts/upgradeable/transpile.sh @@ -33,3 +33,6 @@ npx @openzeppelin/upgrade-safe-transpiler@latest -D \ -x '!contracts/proxy/utils/UUPSUpgradeable.sol' \ -x '!contracts/proxy/beacon/IBeacon.sol' \ -p 'contracts/**/presets/**/*' + +# delete compilation artifacts of vanilla code +npm run clean diff --git a/test/token/ERC20/extensions/ERC4626.t.sol b/test/token/ERC20/extensions/ERC4626.t.sol index da01c7d81..b5f94fcb1 100644 --- a/test/token/ERC20/extensions/ERC4626.t.sol +++ b/test/token/ERC20/extensions/ERC4626.t.sol @@ -3,7 +3,6 @@ pragma solidity ^0.8.19; import {ERC4626Test} from "erc4626-tests/ERC4626.test.sol"; -import {SafeCast} from "openzeppelin/utils/math/SafeCast.sol"; import {ERC20} from "openzeppelin/token/ERC20/ERC20.sol"; import {ERC4626} from "openzeppelin/token/ERC20/extensions/ERC4626.sol"; From 8b72e20e326078029b92d526ff5a44add2671df1 Mon Sep 17 00:00:00 2001 From: Francisco Date: Tue, 11 Jul 2023 16:35:56 -0300 Subject: [PATCH 154/182] Remove unnecessary explicit assignment override (#4443) --- contracts/proxy/utils/UUPSUpgradeable.sol | 2 +- contracts/utils/cryptography/EIP712.sol | 2 +- scripts/upgradeable/upgradeable.patch | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/proxy/utils/UUPSUpgradeable.sol b/contracts/proxy/utils/UUPSUpgradeable.sol index f4045aa6f..286eafe8e 100644 --- a/contracts/proxy/utils/UUPSUpgradeable.sol +++ b/contracts/proxy/utils/UUPSUpgradeable.sol @@ -17,7 +17,7 @@ import {ERC1967Utils} from "../ERC1967/ERC1967Utils.sol"; * The {_authorizeUpgrade} function must be overridden to include access restriction to the upgrade mechanism. */ abstract contract UUPSUpgradeable is IERC1822Proxiable { - /// @custom:oz-upgrades-unsafe-allow state-variable-immutable state-variable-assignment + /// @custom:oz-upgrades-unsafe-allow state-variable-immutable address private immutable __self = address(this); /** diff --git a/contracts/utils/cryptography/EIP712.sol b/contracts/utils/cryptography/EIP712.sol index 36f076e55..3800804ab 100644 --- a/contracts/utils/cryptography/EIP712.sol +++ b/contracts/utils/cryptography/EIP712.sol @@ -29,7 +29,7 @@ import {IERC5267} from "../../interfaces/IERC5267.sol"; * separator of the implementation contract. This will cause the {_domainSeparatorV4} function to always rebuild the * separator from the immutable values, which is cheaper than accessing a cached version in cold storage. * - * @custom:oz-upgrades-unsafe-allow state-variable-immutable state-variable-assignment + * @custom:oz-upgrades-unsafe-allow state-variable-immutable */ abstract contract EIP712 is IERC5267 { using ShortStrings for *; diff --git a/scripts/upgradeable/upgradeable.patch b/scripts/upgradeable/upgradeable.patch index 71a76d78c..e50d3a70e 100644 --- a/scripts/upgradeable/upgradeable.patch +++ b/scripts/upgradeable/upgradeable.patch @@ -142,7 +142,7 @@ index 36f076e5..90c1db78 100644 * separator of the implementation contract. This will cause the {_domainSeparatorV4} function to always rebuild the * separator from the immutable values, which is cheaper than accessing a cached version in cold storage. - * -- * @custom:oz-upgrades-unsafe-allow state-variable-immutable state-variable-assignment +- * @custom:oz-upgrades-unsafe-allow state-variable-immutable */ abstract contract EIP712 is IERC5267 { - using ShortStrings for *; From 921ac49ccb898fb0bd7c57dc5c8156b96f5624a6 Mon Sep 17 00:00:00 2001 From: Pierre Grimaud Date: Wed, 12 Jul 2023 22:05:21 +0200 Subject: [PATCH 155/182] Fix typos in the tests (#4452) --- test/governance/extensions/GovernorWithParams.test.js | 2 +- test/helpers/governance.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/governance/extensions/GovernorWithParams.test.js b/test/governance/extensions/GovernorWithParams.test.js index 896c2e094..35da3a0f3 100644 --- a/test/governance/extensions/GovernorWithParams.test.js +++ b/test/governance/extensions/GovernorWithParams.test.js @@ -147,7 +147,7 @@ contract('GovernorWithParams', function (accounts) { ethSigUtil.signTypedMessage(privateKey, { data: await this.data(contract, message) }); }); - it('suports EOA signatures', async function () { + it('supports EOA signatures', async function () { await this.token.delegate(this.voterBySig.address, { from: voter2 }); const weight = web3.utils.toBN(web3.utils.toWei('7')).sub(rawParams.uintParam); diff --git a/test/helpers/governance.js b/test/helpers/governance.js index 3ae0695ac..9b3349cf8 100644 --- a/test/helpers/governance.js +++ b/test/helpers/governance.js @@ -81,7 +81,7 @@ class GovernorHelper { ...concatOpts(proposal.shortProposal, opts), ); default: - throw new Error(`unsuported visibility "${visibility}"`); + throw new Error(`unsupported visibility "${visibility}"`); } } From 0abf18f305e0066279231e930889cf8ae30a673f Mon Sep 17 00:00:00 2001 From: Molly Date: Wed, 12 Jul 2023 16:07:35 -0400 Subject: [PATCH 156/182] Remove duplicated SLOAD in Arrays.findUpperBound (#4442) Co-authored-by: Hadrien Croubois Co-authored-by: Francisco Giordano --- .changeset/fluffy-countries-buy.md | 5 +++++ contracts/utils/Arrays.sol | 8 ++++---- 2 files changed, 9 insertions(+), 4 deletions(-) create mode 100644 .changeset/fluffy-countries-buy.md diff --git a/.changeset/fluffy-countries-buy.md b/.changeset/fluffy-countries-buy.md new file mode 100644 index 000000000..0cc7de370 --- /dev/null +++ b/.changeset/fluffy-countries-buy.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': minor +--- + +`Arrays`: Optimize `findUpperBound` by removing redundant SLOAD. diff --git a/contracts/utils/Arrays.sol b/contracts/utils/Arrays.sol index 7dbc86fdd..0c16b07cf 100644 --- a/contracts/utils/Arrays.sol +++ b/contracts/utils/Arrays.sol @@ -22,13 +22,13 @@ library Arrays { * repeated elements. */ function findUpperBound(uint256[] storage array, uint256 element) internal view returns (uint256) { - if (array.length == 0) { - return 0; - } - uint256 low = 0; uint256 high = array.length; + if (high == 0) { + return 0; + } + while (low < high) { uint256 mid = Math.average(low, high); From b6c5abbde5b9ad2b5f12b26a862061d883aa744e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Wed, 12 Jul 2023 16:36:10 -0600 Subject: [PATCH 157/182] Add `GovernorTimelockControl` address to `TimelockController` salt (#4432) Co-authored-by: Francisco Giordano --- .changeset/purple-cats-cheer.md | 5 +++++ .../extensions/GovernorTimelockControl.sol | 17 ++++++++++++++--- .../extensions/GovernorTimelockControl.test.js | 8 ++++++-- 3 files changed, 25 insertions(+), 5 deletions(-) create mode 100644 .changeset/purple-cats-cheer.md diff --git a/.changeset/purple-cats-cheer.md b/.changeset/purple-cats-cheer.md new file mode 100644 index 000000000..7e9dc1c4d --- /dev/null +++ b/.changeset/purple-cats-cheer.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': major +--- + +`GovernorTimelockControl`: Add the Governor instance address as part of the TimelockController operation `salt` to avoid operation id collisions between governors using the same TimelockController. diff --git a/contracts/governance/extensions/GovernorTimelockControl.sol b/contracts/governance/extensions/GovernorTimelockControl.sol index 111569408..e6dea036f 100644 --- a/contracts/governance/extensions/GovernorTimelockControl.sol +++ b/contracts/governance/extensions/GovernorTimelockControl.sol @@ -106,8 +106,9 @@ abstract contract GovernorTimelockControl is IGovernorTimelock, Governor { } uint256 delay = _timelock.getMinDelay(); - _timelockIds[proposalId] = _timelock.hashOperationBatch(targets, values, calldatas, 0, descriptionHash); - _timelock.scheduleBatch(targets, values, calldatas, 0, descriptionHash, delay); + bytes32 salt = _timelockSalt(descriptionHash); + _timelockIds[proposalId] = _timelock.hashOperationBatch(targets, values, calldatas, 0, salt); + _timelock.scheduleBatch(targets, values, calldatas, 0, salt, delay); emit ProposalQueued(proposalId, block.timestamp + delay); @@ -125,7 +126,7 @@ abstract contract GovernorTimelockControl is IGovernorTimelock, Governor { bytes32 descriptionHash ) internal virtual override { // execute - _timelock.executeBatch{value: msg.value}(targets, values, calldatas, 0, descriptionHash); + _timelock.executeBatch{value: msg.value}(targets, values, calldatas, 0, _timelockSalt(descriptionHash)); // cleanup for refund delete _timelockIds[proposalId]; } @@ -177,4 +178,14 @@ abstract contract GovernorTimelockControl is IGovernorTimelock, Governor { emit TimelockChange(address(_timelock), address(newTimelock)); _timelock = newTimelock; } + + /** + * @dev Computes the {TimelockController} operation salt. + * + * It is computed with the governor address itself to avoid collisions across governor instances using the + * same timelock. + */ + function _timelockSalt(bytes32 descriptionHash) private view returns (bytes32) { + return bytes20(address(this)) ^ descriptionHash; + } } diff --git a/test/governance/extensions/GovernorTimelockControl.test.js b/test/governance/extensions/GovernorTimelockControl.test.js index 5954d4117..8dde8acb9 100644 --- a/test/governance/extensions/GovernorTimelockControl.test.js +++ b/test/governance/extensions/GovernorTimelockControl.test.js @@ -37,6 +37,9 @@ contract('GovernorTimelockControl', function (accounts) { for (const { mode, Token } of TOKENS) { describe(`using ${Token._json.contractName}`, function () { + const timelockSalt = (address, descriptionHash) => + '0x' + web3.utils.toBN(address).shln(96).xor(web3.utils.toBN(descriptionHash)).toString(16, 64); + beforeEach(async function () { const [deployer] = await web3.eth.getAccounts(); @@ -86,10 +89,11 @@ contract('GovernorTimelockControl', function (accounts) { ], '', ); + this.proposal.timelockid = await this.timelock.hashOperationBatch( ...this.proposal.shortProposal.slice(0, 3), '0x0', - this.proposal.shortProposal[3], + timelockSalt(this.mock.address, this.proposal.shortProposal[3]), ); }); @@ -204,7 +208,7 @@ contract('GovernorTimelockControl', function (accounts) { await this.timelock.executeBatch( ...this.proposal.shortProposal.slice(0, 3), '0x0', - this.proposal.shortProposal[3], + timelockSalt(this.mock.address, this.proposal.shortProposal[3]), ); await expectRevertCustomError(this.helper.execute(), 'GovernorUnexpectedProposalState', [ From a55af77c75e8e5ce5205d2787856ee52055adf11 Mon Sep 17 00:00:00 2001 From: Benjamin Date: Thu, 13 Jul 2023 05:11:12 +0200 Subject: [PATCH 159/182] Natspec update for TimelockController (#4454) --- contracts/governance/TimelockController.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/governance/TimelockController.sol b/contracts/governance/TimelockController.sol index be86f7b88..aaaeb255b 100644 --- a/contracts/governance/TimelockController.sol +++ b/contracts/governance/TimelockController.sol @@ -102,7 +102,7 @@ contract TimelockController is AccessControl, ERC721Holder, ERC1155Holder { /** * @dev Initializes the contract with the following parameters: * - * - `minDelay`: initial minimum delay for operations + * - `minDelay`: initial minimum delay in seconds for operations * - `proposers`: accounts to be granted proposer and canceller roles * - `executors`: accounts to be granted executor role * - `admin`: optional account to be granted admin role; disable with zero address @@ -218,7 +218,7 @@ contract TimelockController is AccessControl, ERC721Holder, ERC1155Holder { } /** - * @dev Returns the minimum delay for an operation to become valid. + * @dev Returns the minimum delay in seconds for an operation to become valid. * * This value can be changed by executing an operation that calls `updateDelay`. */ From 84db204a4155b6a058a6e51ea2e053c4d29d29c8 Mon Sep 17 00:00:00 2001 From: Francisco Date: Thu, 13 Jul 2023 17:52:03 -0300 Subject: [PATCH 160/182] Rename rounding modes and complete with fourth (#4455) Co-authored-by: ernestognw --- .changeset/wild-rockets-rush.md | 5 + contracts/mocks/docs/ERC4626Fees.sol | 4 +- contracts/token/ERC20/extensions/ERC4626.sol | 14 +- contracts/utils/Arrays.sol | 2 +- contracts/utils/math/Math.sol | 37 ++- docs/modules/ROOT/pages/erc4626.adoc | 2 +- test/helpers/enums.js | 2 +- test/token/ERC20/extensions/ERC4626.test.js | 6 +- test/utils/math/Math.t.sol | 16 +- test/utils/math/Math.test.js | 311 ++++++++++--------- 10 files changed, 218 insertions(+), 181 deletions(-) create mode 100644 .changeset/wild-rockets-rush.md diff --git a/.changeset/wild-rockets-rush.md b/.changeset/wild-rockets-rush.md new file mode 100644 index 000000000..7fc6f598d --- /dev/null +++ b/.changeset/wild-rockets-rush.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': major +--- + +`Math`: Renamed members of `Rounding` enum, and added a new rounding mode for "away from zero". diff --git a/contracts/mocks/docs/ERC4626Fees.sol b/contracts/mocks/docs/ERC4626Fees.sol index c82279a60..49c1095ab 100644 --- a/contracts/mocks/docs/ERC4626Fees.sol +++ b/contracts/mocks/docs/ERC4626Fees.sol @@ -81,10 +81,10 @@ abstract contract ERC4626Fees is ERC4626 { } function _feeOnRaw(uint256 assets, uint256 feeBasePoint) private pure returns (uint256) { - return assets.mulDiv(feeBasePoint, 1e5, Math.Rounding.Up); + return assets.mulDiv(feeBasePoint, 1e5, Math.Rounding.Ceil); } function _feeOnTotal(uint256 assets, uint256 feeBasePoint) private pure returns (uint256) { - return assets.mulDiv(feeBasePoint, feeBasePoint + 1e5, Math.Rounding.Up); + return assets.mulDiv(feeBasePoint, feeBasePoint + 1e5, Math.Rounding.Ceil); } } diff --git a/contracts/token/ERC20/extensions/ERC4626.sol b/contracts/token/ERC20/extensions/ERC4626.sol index 11f1cd593..ad3b5a170 100644 --- a/contracts/token/ERC20/extensions/ERC4626.sol +++ b/contracts/token/ERC20/extensions/ERC4626.sol @@ -119,12 +119,12 @@ abstract contract ERC4626 is ERC20, IERC4626 { /** @dev See {IERC4626-convertToShares}. */ function convertToShares(uint256 assets) public view virtual returns (uint256) { - return _convertToShares(assets, Math.Rounding.Down); + return _convertToShares(assets, Math.Rounding.Floor); } /** @dev See {IERC4626-convertToAssets}. */ function convertToAssets(uint256 shares) public view virtual returns (uint256) { - return _convertToAssets(shares, Math.Rounding.Down); + return _convertToAssets(shares, Math.Rounding.Floor); } /** @dev See {IERC4626-maxDeposit}. */ @@ -139,7 +139,7 @@ abstract contract ERC4626 is ERC20, IERC4626 { /** @dev See {IERC4626-maxWithdraw}. */ function maxWithdraw(address owner) public view virtual returns (uint256) { - return _convertToAssets(balanceOf(owner), Math.Rounding.Down); + return _convertToAssets(balanceOf(owner), Math.Rounding.Floor); } /** @dev See {IERC4626-maxRedeem}. */ @@ -149,22 +149,22 @@ abstract contract ERC4626 is ERC20, IERC4626 { /** @dev See {IERC4626-previewDeposit}. */ function previewDeposit(uint256 assets) public view virtual returns (uint256) { - return _convertToShares(assets, Math.Rounding.Down); + return _convertToShares(assets, Math.Rounding.Floor); } /** @dev See {IERC4626-previewMint}. */ function previewMint(uint256 shares) public view virtual returns (uint256) { - return _convertToAssets(shares, Math.Rounding.Up); + return _convertToAssets(shares, Math.Rounding.Ceil); } /** @dev See {IERC4626-previewWithdraw}. */ function previewWithdraw(uint256 assets) public view virtual returns (uint256) { - return _convertToShares(assets, Math.Rounding.Up); + return _convertToShares(assets, Math.Rounding.Ceil); } /** @dev See {IERC4626-previewRedeem}. */ function previewRedeem(uint256 shares) public view virtual returns (uint256) { - return _convertToAssets(shares, Math.Rounding.Down); + return _convertToAssets(shares, Math.Rounding.Floor); } /** @dev See {IERC4626-deposit}. */ diff --git a/contracts/utils/Arrays.sol b/contracts/utils/Arrays.sol index 0c16b07cf..b2eeaac50 100644 --- a/contracts/utils/Arrays.sol +++ b/contracts/utils/Arrays.sol @@ -33,7 +33,7 @@ library Arrays { uint256 mid = Math.average(low, high); // Note that mid will always be strictly less than high (i.e. it will be a valid array index) - // because Math.average rounds down (it does integer division with truncation). + // because Math.average rounds towards zero (it does integer division with truncation). if (unsafeAccess(array, mid).value > element) { high = mid; } else { diff --git a/contracts/utils/math/Math.sol b/contracts/utils/math/Math.sol index f55b69afc..e3d7f799d 100644 --- a/contracts/utils/math/Math.sol +++ b/contracts/utils/math/Math.sol @@ -13,9 +13,10 @@ library Math { error MathOverflowedMulDiv(); enum Rounding { - Down, // Toward negative infinity - Up, // Toward infinity - Zero // Toward zero + Floor, // Toward negative infinity + Ceil, // Toward positive infinity + Trunc, // Toward zero + Expand // Away from zero } /** @@ -100,8 +101,8 @@ library Math { /** * @dev Returns the ceiling of the division of two numbers. * - * This differs from standard division with `/` in that it rounds up instead - * of rounding down. + * This differs from standard division with `/` in that it rounds towards infinity instead + * of rounding towards zero. */ function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) { if (b == 0) { @@ -206,14 +207,15 @@ library Math { */ function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) { uint256 result = mulDiv(x, y, denominator); - if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) { + if (unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0) { result += 1; } return result; } /** - * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down. + * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded + * towards zero. * * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11). */ @@ -256,12 +258,12 @@ library Math { function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) { unchecked { uint256 result = sqrt(a); - return result + (rounding == Rounding.Up && result * result < a ? 1 : 0); + return result + (unsignedRoundsUp(rounding) && result * result < a ? 1 : 0); } } /** - * @dev Return the log in base 2, rounded down, of a positive value. + * @dev Return the log in base 2 of a positive value rounded towards zero. * Returns 0 if given 0. */ function log2(uint256 value) internal pure returns (uint256) { @@ -309,12 +311,12 @@ library Math { function log2(uint256 value, Rounding rounding) internal pure returns (uint256) { unchecked { uint256 result = log2(value); - return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0); + return result + (unsignedRoundsUp(rounding) && 1 << result < value ? 1 : 0); } } /** - * @dev Return the log in base 10, rounded down, of a positive value. + * @dev Return the log in base 10 of a positive value rounded towards zero. * Returns 0 if given 0. */ function log10(uint256 value) internal pure returns (uint256) { @@ -358,12 +360,12 @@ library Math { function log10(uint256 value, Rounding rounding) internal pure returns (uint256) { unchecked { uint256 result = log10(value); - return result + (rounding == Rounding.Up && 10 ** result < value ? 1 : 0); + return result + (unsignedRoundsUp(rounding) && 10 ** result < value ? 1 : 0); } } /** - * @dev Return the log in base 256, rounded down, of a positive value. + * @dev Return the log in base 256 of a positive value rounded towards zero. * Returns 0 if given 0. * * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string. @@ -401,7 +403,14 @@ library Math { function log256(uint256 value, Rounding rounding) internal pure returns (uint256) { unchecked { uint256 result = log256(value); - return result + (rounding == Rounding.Up && 1 << (result << 3) < value ? 1 : 0); + return result + (unsignedRoundsUp(rounding) && 1 << (result << 3) < value ? 1 : 0); } } + + /** + * @dev Returns whether a provided rounding mode is considered rounding up for unsigned integers. + */ + function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) { + return uint8(rounding) % 2 == 1; + } } diff --git a/docs/modules/ROOT/pages/erc4626.adoc b/docs/modules/ROOT/pages/erc4626.adoc index c8adce736..43ec3ebb9 100644 --- a/docs/modules/ROOT/pages/erc4626.adoc +++ b/docs/modules/ROOT/pages/erc4626.adoc @@ -29,7 +29,7 @@ image::erc4626-rate-loglogext.png[More exchange rates in logarithmic scale] === The attack -When depositing tokens, the number of shares a user gets is rounded down. This rounding takes away value from the user in favor or the vault (i.e. in favor of all the current share holders). This rounding is often negligible because of the amount at stake. If you deposit 1e9 shares worth of tokens, the rounding will have you lose at most 0.0000001% of your deposit. However if you deposit 10 shares worth of tokens, you could lose 10% of your deposit. Even worse, if you deposit <1 share worth of tokens, then you get 0 shares, and you basically made a donation. +When depositing tokens, the number of shares a user gets is rounded towards zero. This rounding takes away value from the user in favor or the vault (i.e. in favor of all the current share holders). This rounding is often negligible because of the amount at stake. If you deposit 1e9 shares worth of tokens, the rounding will have you lose at most 0.0000001% of your deposit. However if you deposit 10 shares worth of tokens, you could lose 10% of your deposit. Even worse, if you deposit <1 share worth of tokens, then you get 0 shares, and you basically made a donation. For a given amount of assets, the more shares you receive the safer you are. If you want to limit your losses to at most 1%, you need to receive at least 100 shares. diff --git a/test/helpers/enums.js b/test/helpers/enums.js index 75746e087..6280e0f31 100644 --- a/test/helpers/enums.js +++ b/test/helpers/enums.js @@ -6,6 +6,6 @@ module.exports = { Enum, ProposalState: Enum('Pending', 'Active', 'Canceled', 'Defeated', 'Succeeded', 'Queued', 'Expired', 'Executed'), VoteType: Enum('Against', 'For', 'Abstain'), - Rounding: Enum('Down', 'Up', 'Zero'), + Rounding: Enum('Floor', 'Ceil', 'Trunc', 'Expand'), OperationState: Enum('Unset', 'Waiting', 'Ready', 'Done'), }; diff --git a/test/token/ERC20/extensions/ERC4626.test.js b/test/token/ERC20/extensions/ERC4626.test.js index 9dfc4c3b8..aead1d50a 100644 --- a/test/token/ERC20/extensions/ERC4626.test.js +++ b/test/token/ERC20/extensions/ERC4626.test.js @@ -965,8 +965,8 @@ contract('ERC4626', function (accounts) { } // 5. Bob mints 2000 shares (costs 3001 assets) - // NOTE: Bob's assets spent got rounded up - // NOTE: Alices's vault assets got rounded up + // NOTE: Bob's assets spent got rounded towards infinity + // NOTE: Alices's vault assets got rounded towards infinity { const { tx } = await this.vault.mint(2000, user2, { from: user2 }); await expectEvent.inTransaction(tx, this.token, 'Transfer', { @@ -1056,7 +1056,7 @@ contract('ERC4626', function (accounts) { } // 9. Alice withdraws 3643 assets (2000 shares) - // NOTE: Bob's assets have been rounded back up + // NOTE: Bob's assets have been rounded back towards infinity { const { tx } = await this.vault.withdraw(3643, user1, user1, { from: user1 }); await expectEvent.inTransaction(tx, this.vault, 'Transfer', { diff --git a/test/utils/math/Math.t.sol b/test/utils/math/Math.t.sol index 5a6be476f..d6b0c5d03 100644 --- a/test/utils/math/Math.t.sol +++ b/test/utils/math/Math.t.sol @@ -31,12 +31,12 @@ contract MathTest is Test { // square of result is bigger than input if (_squareBigger(result, input)) { - assertTrue(rounding == Math.Rounding.Up); + assertTrue(Math.unsignedRoundsUp(rounding)); assertTrue(_squareSmaller(result - 1, input)); } // square of result is smaller than input else if (_squareSmaller(result, input)) { - assertFalse(rounding == Math.Rounding.Up); + assertFalse(Math.unsignedRoundsUp(rounding)); assertTrue(_squareBigger(result + 1, input)); } // input is perfect square @@ -63,10 +63,10 @@ contract MathTest is Test { if (input == 0) { assertEq(result, 0); } else if (_powerOf2Bigger(result, input)) { - assertTrue(rounding == Math.Rounding.Up); + assertTrue(Math.unsignedRoundsUp(rounding)); assertTrue(_powerOf2Smaller(result - 1, input)); } else if (_powerOf2Smaller(result, input)) { - assertFalse(rounding == Math.Rounding.Up); + assertFalse(Math.unsignedRoundsUp(rounding)); assertTrue(_powerOf2Bigger(result + 1, input)); } else { assertEq(2 ** result, input); @@ -90,10 +90,10 @@ contract MathTest is Test { if (input == 0) { assertEq(result, 0); } else if (_powerOf10Bigger(result, input)) { - assertTrue(rounding == Math.Rounding.Up); + assertTrue(Math.unsignedRoundsUp(rounding)); assertTrue(_powerOf10Smaller(result - 1, input)); } else if (_powerOf10Smaller(result, input)) { - assertFalse(rounding == Math.Rounding.Up); + assertFalse(Math.unsignedRoundsUp(rounding)); assertTrue(_powerOf10Bigger(result + 1, input)); } else { assertEq(10 ** result, input); @@ -117,10 +117,10 @@ contract MathTest is Test { if (input == 0) { assertEq(result, 0); } else if (_powerOf256Bigger(result, input)) { - assertTrue(rounding == Math.Rounding.Up); + assertTrue(Math.unsignedRoundsUp(rounding)); assertTrue(_powerOf256Smaller(result - 1, input)); } else if (_powerOf256Smaller(result, input)) { - assertFalse(rounding == Math.Rounding.Up); + assertFalse(Math.unsignedRoundsUp(rounding)); assertTrue(_powerOf256Bigger(result + 1, input)); } else { assertEq(256 ** result, input); diff --git a/test/utils/math/Math.test.js b/test/utils/math/Math.test.js index afd822b17..7d4a58c81 100644 --- a/test/utils/math/Math.test.js +++ b/test/utils/math/Math.test.js @@ -6,6 +6,9 @@ const { expectRevertCustomError } = require('../../helpers/customError.js'); const Math = artifacts.require('$Math'); +const RoundingDown = [Rounding.Floor, Rounding.Trunc]; +const RoundingUp = [Rounding.Ceil, Rounding.Expand]; + function expectStruct(value, expected) { for (const key in expected) { if (BN.isBN(value[key])) { @@ -244,202 +247,222 @@ contract('Math', function () { describe('muldiv', function () { it('divide by 0', async function () { - await expectRevert.unspecified(this.math.$mulDiv(1, 1, 0, Rounding.Down)); + await expectRevert.unspecified(this.math.$mulDiv(1, 1, 0, Rounding.Floor)); }); it('reverts with result higher than 2 ^ 256', async function () { - await expectRevertCustomError(this.math.$mulDiv(5, MAX_UINT256, 2, Rounding.Down), 'MathOverflowedMulDiv', []); + await expectRevertCustomError(this.math.$mulDiv(5, MAX_UINT256, 2, Rounding.Floor), 'MathOverflowedMulDiv', []); }); describe('does round down', async function () { it('small values', async function () { - expect(await this.math.$mulDiv('3', '4', '5', Rounding.Down)).to.be.bignumber.equal('2'); - expect(await this.math.$mulDiv('3', '5', '5', Rounding.Down)).to.be.bignumber.equal('3'); + for (const rounding of RoundingDown) { + expect(await this.math.$mulDiv('3', '4', '5', rounding)).to.be.bignumber.equal('2'); + expect(await this.math.$mulDiv('3', '5', '5', rounding)).to.be.bignumber.equal('3'); + } }); it('large values', async function () { - expect( - await this.math.$mulDiv(new BN('42'), MAX_UINT256_SUB1, MAX_UINT256, Rounding.Down), - ).to.be.bignumber.equal(new BN('41')); - - expect(await this.math.$mulDiv(new BN('17'), MAX_UINT256, MAX_UINT256, Rounding.Down)).to.be.bignumber.equal( - new BN('17'), - ); - - expect( - await this.math.$mulDiv(MAX_UINT256_SUB1, MAX_UINT256_SUB1, MAX_UINT256, Rounding.Down), - ).to.be.bignumber.equal(MAX_UINT256_SUB2); - - expect( - await this.math.$mulDiv(MAX_UINT256, MAX_UINT256_SUB1, MAX_UINT256, Rounding.Down), - ).to.be.bignumber.equal(MAX_UINT256_SUB1); - - expect(await this.math.$mulDiv(MAX_UINT256, MAX_UINT256, MAX_UINT256, Rounding.Down)).to.be.bignumber.equal( - MAX_UINT256, - ); + for (const rounding of RoundingDown) { + expect(await this.math.$mulDiv(new BN('42'), MAX_UINT256_SUB1, MAX_UINT256, rounding)).to.be.bignumber.equal( + new BN('41'), + ); + + expect(await this.math.$mulDiv(new BN('17'), MAX_UINT256, MAX_UINT256, rounding)).to.be.bignumber.equal( + new BN('17'), + ); + + expect( + await this.math.$mulDiv(MAX_UINT256_SUB1, MAX_UINT256_SUB1, MAX_UINT256, rounding), + ).to.be.bignumber.equal(MAX_UINT256_SUB2); + + expect(await this.math.$mulDiv(MAX_UINT256, MAX_UINT256_SUB1, MAX_UINT256, rounding)).to.be.bignumber.equal( + MAX_UINT256_SUB1, + ); + + expect(await this.math.$mulDiv(MAX_UINT256, MAX_UINT256, MAX_UINT256, rounding)).to.be.bignumber.equal( + MAX_UINT256, + ); + } }); }); describe('does round up', async function () { it('small values', async function () { - expect(await this.math.$mulDiv('3', '4', '5', Rounding.Up)).to.be.bignumber.equal('3'); - expect(await this.math.$mulDiv('3', '5', '5', Rounding.Up)).to.be.bignumber.equal('3'); + for (const rounding of RoundingUp) { + expect(await this.math.$mulDiv('3', '4', '5', rounding)).to.be.bignumber.equal('3'); + expect(await this.math.$mulDiv('3', '5', '5', rounding)).to.be.bignumber.equal('3'); + } }); it('large values', async function () { - expect(await this.math.$mulDiv(new BN('42'), MAX_UINT256_SUB1, MAX_UINT256, Rounding.Up)).to.be.bignumber.equal( - new BN('42'), - ); - - expect(await this.math.$mulDiv(new BN('17'), MAX_UINT256, MAX_UINT256, Rounding.Up)).to.be.bignumber.equal( - new BN('17'), - ); - - expect( - await this.math.$mulDiv(MAX_UINT256_SUB1, MAX_UINT256_SUB1, MAX_UINT256, Rounding.Up), - ).to.be.bignumber.equal(MAX_UINT256_SUB1); - - expect(await this.math.$mulDiv(MAX_UINT256, MAX_UINT256_SUB1, MAX_UINT256, Rounding.Up)).to.be.bignumber.equal( - MAX_UINT256_SUB1, - ); - - expect(await this.math.$mulDiv(MAX_UINT256, MAX_UINT256, MAX_UINT256, Rounding.Up)).to.be.bignumber.equal( - MAX_UINT256, - ); + for (const rounding of RoundingUp) { + expect(await this.math.$mulDiv(new BN('42'), MAX_UINT256_SUB1, MAX_UINT256, rounding)).to.be.bignumber.equal( + new BN('42'), + ); + + expect(await this.math.$mulDiv(new BN('17'), MAX_UINT256, MAX_UINT256, rounding)).to.be.bignumber.equal( + new BN('17'), + ); + + expect( + await this.math.$mulDiv(MAX_UINT256_SUB1, MAX_UINT256_SUB1, MAX_UINT256, rounding), + ).to.be.bignumber.equal(MAX_UINT256_SUB1); + + expect(await this.math.$mulDiv(MAX_UINT256, MAX_UINT256_SUB1, MAX_UINT256, rounding)).to.be.bignumber.equal( + MAX_UINT256_SUB1, + ); + + expect(await this.math.$mulDiv(MAX_UINT256, MAX_UINT256, MAX_UINT256, rounding)).to.be.bignumber.equal( + MAX_UINT256, + ); + } }); }); }); describe('sqrt', function () { it('rounds down', async function () { - expect(await this.math.$sqrt('0', Rounding.Down)).to.be.bignumber.equal('0'); - expect(await this.math.$sqrt('1', Rounding.Down)).to.be.bignumber.equal('1'); - expect(await this.math.$sqrt('2', Rounding.Down)).to.be.bignumber.equal('1'); - expect(await this.math.$sqrt('3', Rounding.Down)).to.be.bignumber.equal('1'); - expect(await this.math.$sqrt('4', Rounding.Down)).to.be.bignumber.equal('2'); - expect(await this.math.$sqrt('144', Rounding.Down)).to.be.bignumber.equal('12'); - expect(await this.math.$sqrt('999999', Rounding.Down)).to.be.bignumber.equal('999'); - expect(await this.math.$sqrt('1000000', Rounding.Down)).to.be.bignumber.equal('1000'); - expect(await this.math.$sqrt('1000001', Rounding.Down)).to.be.bignumber.equal('1000'); - expect(await this.math.$sqrt('1002000', Rounding.Down)).to.be.bignumber.equal('1000'); - expect(await this.math.$sqrt('1002001', Rounding.Down)).to.be.bignumber.equal('1001'); - expect(await this.math.$sqrt(MAX_UINT256, Rounding.Down)).to.be.bignumber.equal( - '340282366920938463463374607431768211455', - ); + for (const rounding of RoundingDown) { + expect(await this.math.$sqrt('0', rounding)).to.be.bignumber.equal('0'); + expect(await this.math.$sqrt('1', rounding)).to.be.bignumber.equal('1'); + expect(await this.math.$sqrt('2', rounding)).to.be.bignumber.equal('1'); + expect(await this.math.$sqrt('3', rounding)).to.be.bignumber.equal('1'); + expect(await this.math.$sqrt('4', rounding)).to.be.bignumber.equal('2'); + expect(await this.math.$sqrt('144', rounding)).to.be.bignumber.equal('12'); + expect(await this.math.$sqrt('999999', rounding)).to.be.bignumber.equal('999'); + expect(await this.math.$sqrt('1000000', rounding)).to.be.bignumber.equal('1000'); + expect(await this.math.$sqrt('1000001', rounding)).to.be.bignumber.equal('1000'); + expect(await this.math.$sqrt('1002000', rounding)).to.be.bignumber.equal('1000'); + expect(await this.math.$sqrt('1002001', rounding)).to.be.bignumber.equal('1001'); + expect(await this.math.$sqrt(MAX_UINT256, rounding)).to.be.bignumber.equal( + '340282366920938463463374607431768211455', + ); + } }); it('rounds up', async function () { - expect(await this.math.$sqrt('0', Rounding.Up)).to.be.bignumber.equal('0'); - expect(await this.math.$sqrt('1', Rounding.Up)).to.be.bignumber.equal('1'); - expect(await this.math.$sqrt('2', Rounding.Up)).to.be.bignumber.equal('2'); - expect(await this.math.$sqrt('3', Rounding.Up)).to.be.bignumber.equal('2'); - expect(await this.math.$sqrt('4', Rounding.Up)).to.be.bignumber.equal('2'); - expect(await this.math.$sqrt('144', Rounding.Up)).to.be.bignumber.equal('12'); - expect(await this.math.$sqrt('999999', Rounding.Up)).to.be.bignumber.equal('1000'); - expect(await this.math.$sqrt('1000000', Rounding.Up)).to.be.bignumber.equal('1000'); - expect(await this.math.$sqrt('1000001', Rounding.Up)).to.be.bignumber.equal('1001'); - expect(await this.math.$sqrt('1002000', Rounding.Up)).to.be.bignumber.equal('1001'); - expect(await this.math.$sqrt('1002001', Rounding.Up)).to.be.bignumber.equal('1001'); - expect(await this.math.$sqrt(MAX_UINT256, Rounding.Up)).to.be.bignumber.equal( - '340282366920938463463374607431768211456', - ); + for (const rounding of RoundingUp) { + expect(await this.math.$sqrt('0', rounding)).to.be.bignumber.equal('0'); + expect(await this.math.$sqrt('1', rounding)).to.be.bignumber.equal('1'); + expect(await this.math.$sqrt('2', rounding)).to.be.bignumber.equal('2'); + expect(await this.math.$sqrt('3', rounding)).to.be.bignumber.equal('2'); + expect(await this.math.$sqrt('4', rounding)).to.be.bignumber.equal('2'); + expect(await this.math.$sqrt('144', rounding)).to.be.bignumber.equal('12'); + expect(await this.math.$sqrt('999999', rounding)).to.be.bignumber.equal('1000'); + expect(await this.math.$sqrt('1000000', rounding)).to.be.bignumber.equal('1000'); + expect(await this.math.$sqrt('1000001', rounding)).to.be.bignumber.equal('1001'); + expect(await this.math.$sqrt('1002000', rounding)).to.be.bignumber.equal('1001'); + expect(await this.math.$sqrt('1002001', rounding)).to.be.bignumber.equal('1001'); + expect(await this.math.$sqrt(MAX_UINT256, rounding)).to.be.bignumber.equal( + '340282366920938463463374607431768211456', + ); + } }); }); describe('log', function () { describe('log2', function () { it('rounds down', async function () { - // For some reason calling .$log2() directly fails - expect(await this.math.methods['$log2(uint256,uint8)']('0', Rounding.Down)).to.be.bignumber.equal('0'); - expect(await this.math.methods['$log2(uint256,uint8)']('1', Rounding.Down)).to.be.bignumber.equal('0'); - expect(await this.math.methods['$log2(uint256,uint8)']('2', Rounding.Down)).to.be.bignumber.equal('1'); - expect(await this.math.methods['$log2(uint256,uint8)']('3', Rounding.Down)).to.be.bignumber.equal('1'); - expect(await this.math.methods['$log2(uint256,uint8)']('4', Rounding.Down)).to.be.bignumber.equal('2'); - expect(await this.math.methods['$log2(uint256,uint8)']('5', Rounding.Down)).to.be.bignumber.equal('2'); - expect(await this.math.methods['$log2(uint256,uint8)']('6', Rounding.Down)).to.be.bignumber.equal('2'); - expect(await this.math.methods['$log2(uint256,uint8)']('7', Rounding.Down)).to.be.bignumber.equal('2'); - expect(await this.math.methods['$log2(uint256,uint8)']('8', Rounding.Down)).to.be.bignumber.equal('3'); - expect(await this.math.methods['$log2(uint256,uint8)']('9', Rounding.Down)).to.be.bignumber.equal('3'); - expect(await this.math.methods['$log2(uint256,uint8)'](MAX_UINT256, Rounding.Down)).to.be.bignumber.equal( - '255', - ); + for (const rounding of RoundingDown) { + expect(await this.math.methods['$log2(uint256,uint8)']('0', rounding)).to.be.bignumber.equal('0'); + expect(await this.math.methods['$log2(uint256,uint8)']('1', rounding)).to.be.bignumber.equal('0'); + expect(await this.math.methods['$log2(uint256,uint8)']('2', rounding)).to.be.bignumber.equal('1'); + expect(await this.math.methods['$log2(uint256,uint8)']('3', rounding)).to.be.bignumber.equal('1'); + expect(await this.math.methods['$log2(uint256,uint8)']('4', rounding)).to.be.bignumber.equal('2'); + expect(await this.math.methods['$log2(uint256,uint8)']('5', rounding)).to.be.bignumber.equal('2'); + expect(await this.math.methods['$log2(uint256,uint8)']('6', rounding)).to.be.bignumber.equal('2'); + expect(await this.math.methods['$log2(uint256,uint8)']('7', rounding)).to.be.bignumber.equal('2'); + expect(await this.math.methods['$log2(uint256,uint8)']('8', rounding)).to.be.bignumber.equal('3'); + expect(await this.math.methods['$log2(uint256,uint8)']('9', rounding)).to.be.bignumber.equal('3'); + expect(await this.math.methods['$log2(uint256,uint8)'](MAX_UINT256, rounding)).to.be.bignumber.equal('255'); + } }); it('rounds up', async function () { - // For some reason calling .$log2() directly fails - expect(await this.math.methods['$log2(uint256,uint8)']('0', Rounding.Up)).to.be.bignumber.equal('0'); - expect(await this.math.methods['$log2(uint256,uint8)']('1', Rounding.Up)).to.be.bignumber.equal('0'); - expect(await this.math.methods['$log2(uint256,uint8)']('2', Rounding.Up)).to.be.bignumber.equal('1'); - expect(await this.math.methods['$log2(uint256,uint8)']('3', Rounding.Up)).to.be.bignumber.equal('2'); - expect(await this.math.methods['$log2(uint256,uint8)']('4', Rounding.Up)).to.be.bignumber.equal('2'); - expect(await this.math.methods['$log2(uint256,uint8)']('5', Rounding.Up)).to.be.bignumber.equal('3'); - expect(await this.math.methods['$log2(uint256,uint8)']('6', Rounding.Up)).to.be.bignumber.equal('3'); - expect(await this.math.methods['$log2(uint256,uint8)']('7', Rounding.Up)).to.be.bignumber.equal('3'); - expect(await this.math.methods['$log2(uint256,uint8)']('8', Rounding.Up)).to.be.bignumber.equal('3'); - expect(await this.math.methods['$log2(uint256,uint8)']('9', Rounding.Up)).to.be.bignumber.equal('4'); - expect(await this.math.methods['$log2(uint256,uint8)'](MAX_UINT256, Rounding.Up)).to.be.bignumber.equal('256'); + for (const rounding of RoundingUp) { + expect(await this.math.methods['$log2(uint256,uint8)']('0', rounding)).to.be.bignumber.equal('0'); + expect(await this.math.methods['$log2(uint256,uint8)']('1', rounding)).to.be.bignumber.equal('0'); + expect(await this.math.methods['$log2(uint256,uint8)']('2', rounding)).to.be.bignumber.equal('1'); + expect(await this.math.methods['$log2(uint256,uint8)']('3', rounding)).to.be.bignumber.equal('2'); + expect(await this.math.methods['$log2(uint256,uint8)']('4', rounding)).to.be.bignumber.equal('2'); + expect(await this.math.methods['$log2(uint256,uint8)']('5', rounding)).to.be.bignumber.equal('3'); + expect(await this.math.methods['$log2(uint256,uint8)']('6', rounding)).to.be.bignumber.equal('3'); + expect(await this.math.methods['$log2(uint256,uint8)']('7', rounding)).to.be.bignumber.equal('3'); + expect(await this.math.methods['$log2(uint256,uint8)']('8', rounding)).to.be.bignumber.equal('3'); + expect(await this.math.methods['$log2(uint256,uint8)']('9', rounding)).to.be.bignumber.equal('4'); + expect(await this.math.methods['$log2(uint256,uint8)'](MAX_UINT256, rounding)).to.be.bignumber.equal('256'); + } }); }); describe('log10', function () { it('rounds down', async function () { - expect(await this.math.$log10('0', Rounding.Down)).to.be.bignumber.equal('0'); - expect(await this.math.$log10('1', Rounding.Down)).to.be.bignumber.equal('0'); - expect(await this.math.$log10('2', Rounding.Down)).to.be.bignumber.equal('0'); - expect(await this.math.$log10('9', Rounding.Down)).to.be.bignumber.equal('0'); - expect(await this.math.$log10('10', Rounding.Down)).to.be.bignumber.equal('1'); - expect(await this.math.$log10('11', Rounding.Down)).to.be.bignumber.equal('1'); - expect(await this.math.$log10('99', Rounding.Down)).to.be.bignumber.equal('1'); - expect(await this.math.$log10('100', Rounding.Down)).to.be.bignumber.equal('2'); - expect(await this.math.$log10('101', Rounding.Down)).to.be.bignumber.equal('2'); - expect(await this.math.$log10('999', Rounding.Down)).to.be.bignumber.equal('2'); - expect(await this.math.$log10('1000', Rounding.Down)).to.be.bignumber.equal('3'); - expect(await this.math.$log10('1001', Rounding.Down)).to.be.bignumber.equal('3'); - expect(await this.math.$log10(MAX_UINT256, Rounding.Down)).to.be.bignumber.equal('77'); + for (const rounding of RoundingDown) { + expect(await this.math.$log10('0', rounding)).to.be.bignumber.equal('0'); + expect(await this.math.$log10('1', rounding)).to.be.bignumber.equal('0'); + expect(await this.math.$log10('2', rounding)).to.be.bignumber.equal('0'); + expect(await this.math.$log10('9', rounding)).to.be.bignumber.equal('0'); + expect(await this.math.$log10('10', rounding)).to.be.bignumber.equal('1'); + expect(await this.math.$log10('11', rounding)).to.be.bignumber.equal('1'); + expect(await this.math.$log10('99', rounding)).to.be.bignumber.equal('1'); + expect(await this.math.$log10('100', rounding)).to.be.bignumber.equal('2'); + expect(await this.math.$log10('101', rounding)).to.be.bignumber.equal('2'); + expect(await this.math.$log10('999', rounding)).to.be.bignumber.equal('2'); + expect(await this.math.$log10('1000', rounding)).to.be.bignumber.equal('3'); + expect(await this.math.$log10('1001', rounding)).to.be.bignumber.equal('3'); + expect(await this.math.$log10(MAX_UINT256, rounding)).to.be.bignumber.equal('77'); + } }); it('rounds up', async function () { - expect(await this.math.$log10('0', Rounding.Up)).to.be.bignumber.equal('0'); - expect(await this.math.$log10('1', Rounding.Up)).to.be.bignumber.equal('0'); - expect(await this.math.$log10('2', Rounding.Up)).to.be.bignumber.equal('1'); - expect(await this.math.$log10('9', Rounding.Up)).to.be.bignumber.equal('1'); - expect(await this.math.$log10('10', Rounding.Up)).to.be.bignumber.equal('1'); - expect(await this.math.$log10('11', Rounding.Up)).to.be.bignumber.equal('2'); - expect(await this.math.$log10('99', Rounding.Up)).to.be.bignumber.equal('2'); - expect(await this.math.$log10('100', Rounding.Up)).to.be.bignumber.equal('2'); - expect(await this.math.$log10('101', Rounding.Up)).to.be.bignumber.equal('3'); - expect(await this.math.$log10('999', Rounding.Up)).to.be.bignumber.equal('3'); - expect(await this.math.$log10('1000', Rounding.Up)).to.be.bignumber.equal('3'); - expect(await this.math.$log10('1001', Rounding.Up)).to.be.bignumber.equal('4'); - expect(await this.math.$log10(MAX_UINT256, Rounding.Up)).to.be.bignumber.equal('78'); + for (const rounding of RoundingUp) { + expect(await this.math.$log10('0', rounding)).to.be.bignumber.equal('0'); + expect(await this.math.$log10('1', rounding)).to.be.bignumber.equal('0'); + expect(await this.math.$log10('2', rounding)).to.be.bignumber.equal('1'); + expect(await this.math.$log10('9', rounding)).to.be.bignumber.equal('1'); + expect(await this.math.$log10('10', rounding)).to.be.bignumber.equal('1'); + expect(await this.math.$log10('11', rounding)).to.be.bignumber.equal('2'); + expect(await this.math.$log10('99', rounding)).to.be.bignumber.equal('2'); + expect(await this.math.$log10('100', rounding)).to.be.bignumber.equal('2'); + expect(await this.math.$log10('101', rounding)).to.be.bignumber.equal('3'); + expect(await this.math.$log10('999', rounding)).to.be.bignumber.equal('3'); + expect(await this.math.$log10('1000', rounding)).to.be.bignumber.equal('3'); + expect(await this.math.$log10('1001', rounding)).to.be.bignumber.equal('4'); + expect(await this.math.$log10(MAX_UINT256, rounding)).to.be.bignumber.equal('78'); + } }); }); describe('log256', function () { it('rounds down', async function () { - expect(await this.math.$log256('0', Rounding.Down)).to.be.bignumber.equal('0'); - expect(await this.math.$log256('1', Rounding.Down)).to.be.bignumber.equal('0'); - expect(await this.math.$log256('2', Rounding.Down)).to.be.bignumber.equal('0'); - expect(await this.math.$log256('255', Rounding.Down)).to.be.bignumber.equal('0'); - expect(await this.math.$log256('256', Rounding.Down)).to.be.bignumber.equal('1'); - expect(await this.math.$log256('257', Rounding.Down)).to.be.bignumber.equal('1'); - expect(await this.math.$log256('65535', Rounding.Down)).to.be.bignumber.equal('1'); - expect(await this.math.$log256('65536', Rounding.Down)).to.be.bignumber.equal('2'); - expect(await this.math.$log256('65537', Rounding.Down)).to.be.bignumber.equal('2'); - expect(await this.math.$log256(MAX_UINT256, Rounding.Down)).to.be.bignumber.equal('31'); + for (const rounding of RoundingDown) { + expect(await this.math.$log256('0', rounding)).to.be.bignumber.equal('0'); + expect(await this.math.$log256('1', rounding)).to.be.bignumber.equal('0'); + expect(await this.math.$log256('2', rounding)).to.be.bignumber.equal('0'); + expect(await this.math.$log256('255', rounding)).to.be.bignumber.equal('0'); + expect(await this.math.$log256('256', rounding)).to.be.bignumber.equal('1'); + expect(await this.math.$log256('257', rounding)).to.be.bignumber.equal('1'); + expect(await this.math.$log256('65535', rounding)).to.be.bignumber.equal('1'); + expect(await this.math.$log256('65536', rounding)).to.be.bignumber.equal('2'); + expect(await this.math.$log256('65537', rounding)).to.be.bignumber.equal('2'); + expect(await this.math.$log256(MAX_UINT256, rounding)).to.be.bignumber.equal('31'); + } }); it('rounds up', async function () { - expect(await this.math.$log256('0', Rounding.Up)).to.be.bignumber.equal('0'); - expect(await this.math.$log256('1', Rounding.Up)).to.be.bignumber.equal('0'); - expect(await this.math.$log256('2', Rounding.Up)).to.be.bignumber.equal('1'); - expect(await this.math.$log256('255', Rounding.Up)).to.be.bignumber.equal('1'); - expect(await this.math.$log256('256', Rounding.Up)).to.be.bignumber.equal('1'); - expect(await this.math.$log256('257', Rounding.Up)).to.be.bignumber.equal('2'); - expect(await this.math.$log256('65535', Rounding.Up)).to.be.bignumber.equal('2'); - expect(await this.math.$log256('65536', Rounding.Up)).to.be.bignumber.equal('2'); - expect(await this.math.$log256('65537', Rounding.Up)).to.be.bignumber.equal('3'); - expect(await this.math.$log256(MAX_UINT256, Rounding.Up)).to.be.bignumber.equal('32'); + for (const rounding of RoundingUp) { + expect(await this.math.$log256('0', rounding)).to.be.bignumber.equal('0'); + expect(await this.math.$log256('1', rounding)).to.be.bignumber.equal('0'); + expect(await this.math.$log256('2', rounding)).to.be.bignumber.equal('1'); + expect(await this.math.$log256('255', rounding)).to.be.bignumber.equal('1'); + expect(await this.math.$log256('256', rounding)).to.be.bignumber.equal('1'); + expect(await this.math.$log256('257', rounding)).to.be.bignumber.equal('2'); + expect(await this.math.$log256('65535', rounding)).to.be.bignumber.equal('2'); + expect(await this.math.$log256('65536', rounding)).to.be.bignumber.equal('2'); + expect(await this.math.$log256('65537', rounding)).to.be.bignumber.equal('3'); + expect(await this.math.$log256(MAX_UINT256, rounding)).to.be.bignumber.equal('32'); + } }); }); }); From 9cf873ea1481764c3be51e203861475d6b302450 Mon Sep 17 00:00:00 2001 From: Renan Souza Date: Thu, 13 Jul 2023 18:54:22 -0300 Subject: [PATCH 161/182] Change access folder structure (#4359) Co-authored-by: Hadrien Croubois Co-authored-by: Francisco --- .changeset/spicy-sheep-eat.md | 5 +++++ contracts/access/README.adoc | 6 +++++- .../{ => extensions}/AccessControlDefaultAdminRules.sol | 8 ++++---- .../access/{ => extensions}/AccessControlEnumerable.sol | 4 ++-- .../{ => extensions}/IAccessControlDefaultAdminRules.sol | 2 +- .../access/{ => extensions}/IAccessControlEnumerable.sol | 2 +- .../AccessControlDefaultAdminRules.test.js | 2 +- .../{ => extensions}/AccessControlEnumerable.test.js | 2 +- 8 files changed, 20 insertions(+), 11 deletions(-) create mode 100644 .changeset/spicy-sheep-eat.md rename contracts/access/{ => extensions}/AccessControlDefaultAdminRules.sol (98%) rename contracts/access/{ => extensions}/AccessControlEnumerable.sol (95%) rename contracts/access/{ => extensions}/IAccessControlDefaultAdminRules.sol (99%) rename contracts/access/{ => extensions}/IAccessControlEnumerable.sol (95%) rename test/access/{ => extensions}/AccessControlDefaultAdminRules.test.js (95%) rename test/access/{ => extensions}/AccessControlEnumerable.test.js (92%) diff --git a/.changeset/spicy-sheep-eat.md b/.changeset/spicy-sheep-eat.md new file mode 100644 index 000000000..17b6d5585 --- /dev/null +++ b/.changeset/spicy-sheep-eat.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': major +--- + +`access`: Move `AccessControl` extensions to a dedicated directory. diff --git a/contracts/access/README.adoc b/contracts/access/README.adoc index 117cd7c97..b03753d96 100644 --- a/contracts/access/README.adoc +++ b/contracts/access/README.adoc @@ -8,7 +8,7 @@ This directory provides ways to restrict who can access the functions of a contr - {AccessControl} provides a general role based access control mechanism. Multiple hierarchical roles can be created and assigned each to multiple accounts. - {Ownable} is a simpler mechanism with a single owner "role" that can be assigned to a single account. This simpler mechanism can be useful for quick tests but projects with production concerns are likely to outgrow it. -== Authorization +== Core {{Ownable}} @@ -18,8 +18,12 @@ This directory provides ways to restrict who can access the functions of a contr {{AccessControl}} +== Extensions + {{IAccessControlEnumerable}} {{AccessControlEnumerable}} +{{IAccessControlDefaultAdminRules}} + {{AccessControlDefaultAdminRules}} diff --git a/contracts/access/AccessControlDefaultAdminRules.sol b/contracts/access/extensions/AccessControlDefaultAdminRules.sol similarity index 98% rename from contracts/access/AccessControlDefaultAdminRules.sol rename to contracts/access/extensions/AccessControlDefaultAdminRules.sol index 4572a328d..e8820cb4b 100644 --- a/contracts/access/AccessControlDefaultAdminRules.sol +++ b/contracts/access/extensions/AccessControlDefaultAdminRules.sol @@ -3,11 +3,11 @@ pragma solidity ^0.8.19; -import {AccessControl, IAccessControl} from "./AccessControl.sol"; import {IAccessControlDefaultAdminRules} from "./IAccessControlDefaultAdminRules.sol"; -import {SafeCast} from "../utils/math/SafeCast.sol"; -import {Math} from "../utils/math/Math.sol"; -import {IERC5313} from "../interfaces/IERC5313.sol"; +import {AccessControl, IAccessControl} from "../AccessControl.sol"; +import {SafeCast} from "../../utils/math/SafeCast.sol"; +import {Math} from "../../utils/math/Math.sol"; +import {IERC5313} from "../../interfaces/IERC5313.sol"; /** * @dev Extension of {AccessControl} that allows specifying special rules to manage diff --git a/contracts/access/AccessControlEnumerable.sol b/contracts/access/extensions/AccessControlEnumerable.sol similarity index 95% rename from contracts/access/AccessControlEnumerable.sol rename to contracts/access/extensions/AccessControlEnumerable.sol index 6f160e4e8..2512c4379 100644 --- a/contracts/access/AccessControlEnumerable.sol +++ b/contracts/access/extensions/AccessControlEnumerable.sol @@ -4,8 +4,8 @@ pragma solidity ^0.8.19; import {IAccessControlEnumerable} from "./IAccessControlEnumerable.sol"; -import {AccessControl} from "./AccessControl.sol"; -import {EnumerableSet} from "../utils/structs/EnumerableSet.sol"; +import {AccessControl} from "../AccessControl.sol"; +import {EnumerableSet} from "../../utils/structs/EnumerableSet.sol"; /** * @dev Extension of {AccessControl} that allows enumerating the members of each role. diff --git a/contracts/access/IAccessControlDefaultAdminRules.sol b/contracts/access/extensions/IAccessControlDefaultAdminRules.sol similarity index 99% rename from contracts/access/IAccessControlDefaultAdminRules.sol rename to contracts/access/extensions/IAccessControlDefaultAdminRules.sol index a1afb3d22..7dac1c1f2 100644 --- a/contracts/access/IAccessControlDefaultAdminRules.sol +++ b/contracts/access/extensions/IAccessControlDefaultAdminRules.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.19; -import {IAccessControl} from "./IAccessControl.sol"; +import {IAccessControl} from "../IAccessControl.sol"; /** * @dev External interface of AccessControlDefaultAdminRules declared to support ERC165 detection. diff --git a/contracts/access/IAccessControlEnumerable.sol b/contracts/access/extensions/IAccessControlEnumerable.sol similarity index 95% rename from contracts/access/IAccessControlEnumerable.sol rename to contracts/access/extensions/IAccessControlEnumerable.sol index 1bd88a4f4..bc59ed586 100644 --- a/contracts/access/IAccessControlEnumerable.sol +++ b/contracts/access/extensions/IAccessControlEnumerable.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.19; -import {IAccessControl} from "./IAccessControl.sol"; +import {IAccessControl} from "../IAccessControl.sol"; /** * @dev External interface of AccessControlEnumerable declared to support ERC165 detection. diff --git a/test/access/AccessControlDefaultAdminRules.test.js b/test/access/extensions/AccessControlDefaultAdminRules.test.js similarity index 95% rename from test/access/AccessControlDefaultAdminRules.test.js rename to test/access/extensions/AccessControlDefaultAdminRules.test.js index b8eae3220..af34143f1 100644 --- a/test/access/AccessControlDefaultAdminRules.test.js +++ b/test/access/extensions/AccessControlDefaultAdminRules.test.js @@ -2,7 +2,7 @@ const { time, constants, expectRevert } = require('@openzeppelin/test-helpers'); const { shouldBehaveLikeAccessControl, shouldBehaveLikeAccessControlDefaultAdminRules, -} = require('./AccessControl.behavior.js'); +} = require('../AccessControl.behavior.js'); const AccessControlDefaultAdminRules = artifacts.require('$AccessControlDefaultAdminRules'); diff --git a/test/access/AccessControlEnumerable.test.js b/test/access/extensions/AccessControlEnumerable.test.js similarity index 92% rename from test/access/AccessControlEnumerable.test.js rename to test/access/extensions/AccessControlEnumerable.test.js index 429f22f8f..7dfb0bce8 100644 --- a/test/access/AccessControlEnumerable.test.js +++ b/test/access/extensions/AccessControlEnumerable.test.js @@ -2,7 +2,7 @@ const { DEFAULT_ADMIN_ROLE, shouldBehaveLikeAccessControl, shouldBehaveLikeAccessControlEnumerable, -} = require('./AccessControl.behavior.js'); +} = require('../AccessControl.behavior.js'); const AccessControlEnumerable = artifacts.require('$AccessControlEnumerable'); From 121be5dd09caa2f7ce731f0806b0e14761023bd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Thu, 13 Jul 2023 16:25:22 -0600 Subject: [PATCH 162/182] Make `TransparentUpgradeableProxy` deploy its `ProxyAdmin` and optimize proxy interfaces (#4382) Co-authored-by: Francisco Co-authored-by: Eric Lau Co-authored-by: Hadrien Croubois --- .changeset/empty-taxis-kiss.md | 5 + .changeset/tender-shirts-turn.md | 5 + contracts/mocks/UpgreadeableBeaconMock.sol | 12 ++ .../mocks/proxy/ClashingImplementation.sol | 2 +- contracts/mocks/proxy/UUPSUpgradeableMock.sol | 6 +- contracts/proxy/ERC1967/ERC1967Proxy.sol | 6 +- contracts/proxy/ERC1967/ERC1967Utils.sol | 43 ++-- contracts/proxy/beacon/BeaconProxy.sol | 3 +- contracts/proxy/transparent/ProxyAdmin.sol | 24 +-- .../TransparentUpgradeableProxy.sol | 97 +++------ contracts/proxy/utils/UUPSUpgradeable.sol | 34 ++- .../GovernorCompatibilityBravo.test.js | 13 +- .../GovernorTimelockCompound.test.js | 13 +- test/helpers/account.js | 14 ++ test/helpers/create.js | 23 ++ test/helpers/create2.js | 11 - test/helpers/erc1967.js | 17 +- test/proxy/Clones.test.js | 2 +- test/proxy/ERC1967/ERC1967Proxy.test.js | 9 +- test/proxy/ERC1967/ERC1967Utils.test.js | 160 ++++++++++++++ test/proxy/Proxy.behaviour.js | 83 ++----- test/proxy/beacon/BeaconProxy.test.js | 16 +- test/proxy/transparent/ProxyAdmin.test.js | 29 ++- .../TransparentUpgradeableProxy.behaviour.js | 203 +++++++++--------- .../TransparentUpgradeableProxy.test.js | 19 +- test/proxy/utils/UUPSUpgradeable.test.js | 24 ++- test/utils/Create2.test.js | 2 +- 27 files changed, 520 insertions(+), 355 deletions(-) create mode 100644 .changeset/empty-taxis-kiss.md create mode 100644 .changeset/tender-shirts-turn.md create mode 100644 contracts/mocks/UpgreadeableBeaconMock.sol create mode 100644 test/helpers/account.js create mode 100644 test/helpers/create.js delete mode 100644 test/helpers/create2.js create mode 100644 test/proxy/ERC1967/ERC1967Utils.test.js diff --git a/.changeset/empty-taxis-kiss.md b/.changeset/empty-taxis-kiss.md new file mode 100644 index 000000000..b01c92bd0 --- /dev/null +++ b/.changeset/empty-taxis-kiss.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': major +--- + +`UUPSUpgradeable`, `TransparentUpgradeableProxy` and `ProxyAdmin`: Removed `upgradeTo` and `upgrade` functions, and made `upgradeToAndCall` and `upgradeAndCall` ignore the data argument if it is empty. It is no longer possible to invoke the receive function (or send value with empty data) along with an upgrade. diff --git a/.changeset/tender-shirts-turn.md b/.changeset/tender-shirts-turn.md new file mode 100644 index 000000000..9c98e6e2b --- /dev/null +++ b/.changeset/tender-shirts-turn.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': major +--- + +`BeaconProxy`: Reject value in initialization unless a payable function is explicitly invoked. diff --git a/contracts/mocks/UpgreadeableBeaconMock.sol b/contracts/mocks/UpgreadeableBeaconMock.sol new file mode 100644 index 000000000..02c138d4d --- /dev/null +++ b/contracts/mocks/UpgreadeableBeaconMock.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import {IBeacon} from "../proxy/beacon/IBeacon.sol"; + +contract UpgradeableBeaconMock is IBeacon { + address public implementation; + + constructor(address impl) { + implementation = impl; + } +} diff --git a/contracts/mocks/proxy/ClashingImplementation.sol b/contracts/mocks/proxy/ClashingImplementation.sol index 89904b91f..4cb5d2326 100644 --- a/contracts/mocks/proxy/ClashingImplementation.sol +++ b/contracts/mocks/proxy/ClashingImplementation.sol @@ -9,7 +9,7 @@ pragma solidity ^0.8.19; contract ClashingImplementation { event ClashingImplementationCall(); - function upgradeTo(address) external payable { + function upgradeToAndCall(address, bytes calldata) external payable { emit ClashingImplementationCall(); } diff --git a/contracts/mocks/proxy/UUPSUpgradeableMock.sol b/contracts/mocks/proxy/UUPSUpgradeableMock.sol index 4333e63e8..769c89946 100644 --- a/contracts/mocks/proxy/UUPSUpgradeableMock.sol +++ b/contracts/mocks/proxy/UUPSUpgradeableMock.sol @@ -23,12 +23,8 @@ contract UUPSUpgradeableMock is NonUpgradeableMock, UUPSUpgradeable { } contract UUPSUpgradeableUnsafeMock is UUPSUpgradeableMock { - function upgradeTo(address newImplementation) public override { - ERC1967Utils.upgradeToAndCall(newImplementation, bytes(""), false); - } - function upgradeToAndCall(address newImplementation, bytes memory data) public payable override { - ERC1967Utils.upgradeToAndCall(newImplementation, data, false); + ERC1967Utils.upgradeToAndCall(newImplementation, data); } } diff --git a/contracts/proxy/ERC1967/ERC1967Proxy.sol b/contracts/proxy/ERC1967/ERC1967Proxy.sol index e7c90c706..d2a927d58 100644 --- a/contracts/proxy/ERC1967/ERC1967Proxy.sol +++ b/contracts/proxy/ERC1967/ERC1967Proxy.sol @@ -18,9 +18,13 @@ contract ERC1967Proxy is Proxy { * * If `_data` is nonempty, it's used as data in a delegate call to `_logic`. This will typically be an encoded * function call, and allows initializing the storage of the proxy like a Solidity constructor. + * + * Requirements: + * + * - If `data` is empty, `msg.value` must be zero. */ constructor(address _logic, bytes memory _data) payable { - ERC1967Utils.upgradeToAndCall(_logic, _data, false); + ERC1967Utils.upgradeToAndCall(_logic, _data); } /** diff --git a/contracts/proxy/ERC1967/ERC1967Utils.sol b/contracts/proxy/ERC1967/ERC1967Utils.sol index 3ad93ff3c..bd2108096 100644 --- a/contracts/proxy/ERC1967/ERC1967Utils.sol +++ b/contracts/proxy/ERC1967/ERC1967Utils.sol @@ -52,6 +52,11 @@ library ERC1967Utils { */ error ERC1967InvalidBeacon(address beacon); + /** + * @dev An upgrade function sees `msg.value > 0` that may be lost. + */ + error ERC1967NonPayable(); + /** * @dev Returns the current implementation address. */ @@ -70,24 +75,20 @@ library ERC1967Utils { } /** - * @dev Perform implementation upgrade + * @dev Performs implementation upgrade with additional setup call if data is nonempty. + * This function is payable only if the setup call is performed, otherwise `msg.value` is rejected + * to avoid stuck value in the contract. * * Emits an {IERC1967-Upgraded} event. */ - function upgradeTo(address newImplementation) internal { + function upgradeToAndCall(address newImplementation, bytes memory data) internal { _setImplementation(newImplementation); emit Upgraded(newImplementation); - } - /** - * @dev Perform implementation upgrade with additional setup call. - * - * Emits an {IERC1967-Upgraded} event. - */ - function upgradeToAndCall(address newImplementation, bytes memory data, bool forceCall) internal { - upgradeTo(newImplementation); - if (data.length > 0 || forceCall) { + if (data.length > 0) { Address.functionDelegateCall(newImplementation, data); + } else { + _checkNonPayable(); } } @@ -161,7 +162,9 @@ library ERC1967Utils { } /** - * @dev Change the beacon and trigger a setup call. + * @dev Change the beacon and trigger a setup call if data is nonempty. + * This function is payable only if the setup call is performed, otherwise `msg.value` is rejected + * to avoid stuck value in the contract. * * Emits an {IERC1967-BeaconUpgraded} event. * @@ -169,11 +172,23 @@ library ERC1967Utils { * it uses an immutable beacon without looking at the value of the ERC-1967 beacon slot for * efficiency. */ - function upgradeBeaconToAndCall(address newBeacon, bytes memory data, bool forceCall) internal { + function upgradeBeaconToAndCall(address newBeacon, bytes memory data) internal { _setBeacon(newBeacon); emit BeaconUpgraded(newBeacon); - if (data.length > 0 || forceCall) { + + if (data.length > 0) { Address.functionDelegateCall(IBeacon(newBeacon).implementation(), data); + } else { + _checkNonPayable(); + } + } + + /** + * @dev Reverts if `msg.value` is not zero. + */ + function _checkNonPayable() private { + if (msg.value > 0) { + revert ERC1967NonPayable(); } } } diff --git a/contracts/proxy/beacon/BeaconProxy.sol b/contracts/proxy/beacon/BeaconProxy.sol index 14a8d1b66..b519b9caf 100644 --- a/contracts/proxy/beacon/BeaconProxy.sol +++ b/contracts/proxy/beacon/BeaconProxy.sol @@ -34,9 +34,10 @@ contract BeaconProxy is Proxy { * Requirements: * * - `beacon` must be a contract with the interface {IBeacon}. + * - If `data` is empty, `msg.value` must be zero. */ constructor(address beacon, bytes memory data) payable { - ERC1967Utils.upgradeBeaconToAndCall(beacon, data, false); + ERC1967Utils.upgradeBeaconToAndCall(beacon, data); _beacon = beacon; } diff --git a/contracts/proxy/transparent/ProxyAdmin.sol b/contracts/proxy/transparent/ProxyAdmin.sol index 12d80a9c4..fd4a82d12 100644 --- a/contracts/proxy/transparent/ProxyAdmin.sol +++ b/contracts/proxy/transparent/ProxyAdmin.sol @@ -12,28 +12,28 @@ import {Ownable} from "../../access/Ownable.sol"; */ contract ProxyAdmin is Ownable { /** - * @dev Sets the initial owner who can perform upgrades. + * @dev The version of the upgrade interface of the contract. If this getter is missing, both `upgrade(address)` + * and `upgradeAndCall(address,bytes)` are present, and `upgradeTo` must be used if no function should be called, + * while `upgradeAndCall` will invoke the `receive` function if the second argument is the empty byte string. + * If the getter returns `"5.0.0"`, only `upgradeAndCall(address,bytes)` is present, and the second argument must + * be the empty byte string if no function should be called, being impossible to invoke the `receive` function + * during an upgrade. */ - constructor(address initialOwner) Ownable(initialOwner) {} + string public constant UPGRADE_INTERFACE_VERSION = "5.0.0"; /** - * @dev Upgrades `proxy` to `implementation`. See {TransparentUpgradeableProxy-upgradeTo}. - * - * Requirements: - * - * - This contract must be the admin of `proxy`. + * @dev Sets the initial owner who can perform upgrades. */ - function upgrade(ITransparentUpgradeableProxy proxy, address implementation) public virtual onlyOwner { - proxy.upgradeTo(implementation); - } + constructor(address initialOwner) Ownable(initialOwner) {} /** - * @dev Upgrades `proxy` to `implementation` and calls a function on the new implementation. See - * {TransparentUpgradeableProxy-upgradeToAndCall}. + * @dev Upgrades `proxy` to `implementation` and calls a function on the new implementation. + * See {TransparentUpgradeableProxy-_dispatchUpgradeToAndCall}. * * Requirements: * * - This contract must be the admin of `proxy`. + * - If `data` is empty, `msg.value` must be zero. */ function upgradeAndCall( ITransparentUpgradeableProxy proxy, diff --git a/contracts/proxy/transparent/TransparentUpgradeableProxy.sol b/contracts/proxy/transparent/TransparentUpgradeableProxy.sol index 3d6e11f38..fc3574642 100644 --- a/contracts/proxy/transparent/TransparentUpgradeableProxy.sol +++ b/contracts/proxy/transparent/TransparentUpgradeableProxy.sol @@ -6,21 +6,20 @@ pragma solidity ^0.8.19; import {ERC1967Utils} from "../ERC1967/ERC1967Utils.sol"; import {ERC1967Proxy} from "../ERC1967/ERC1967Proxy.sol"; import {IERC1967} from "../../interfaces/IERC1967.sol"; +import {ProxyAdmin} from "./ProxyAdmin.sol"; /** * @dev Interface for {TransparentUpgradeableProxy}. In order to implement transparency, {TransparentUpgradeableProxy} - * does not implement this interface directly, and some of its functions are implemented by an internal dispatch + * does not implement this interface directly, and its upgradeability mechanism is implemented by an internal dispatch * mechanism. The compiler is unaware that these functions are implemented by {TransparentUpgradeableProxy} and will not * include them in the ABI so this interface must be used to interact with it. */ interface ITransparentUpgradeableProxy is IERC1967 { - function upgradeTo(address) external; - - function upgradeToAndCall(address, bytes memory) external payable; + function upgradeToAndCall(address, bytes calldata) external payable; } /** - * @dev This contract implements a proxy that is upgradeable by an immutable admin. + * @dev This contract implements a proxy that is upgradeable through an associated {ProxyAdmin} instance. * * To avoid https://medium.com/nomic-labs-blog/malicious-backdoors-in-ethereum-proxies-62629adf3357[proxy selector * clashing], which can potentially be used in an attack, this contract uses the @@ -28,23 +27,22 @@ interface ITransparentUpgradeableProxy is IERC1967 { * things that go hand in hand: * * 1. If any account other than the admin calls the proxy, the call will be forwarded to the implementation, even if - * that call matches one of the admin functions exposed by the proxy itself. - * 2. If the admin calls the proxy, it can access the admin functions, but its calls will never be forwarded to the + * that call matches the {ITransparentUpgradeableProxy-upgradeToAndCall} function exposed by the proxy itself. + * 2. If the admin calls the proxy, it can call the `upgradeToAndCall` function but any other call won't be forwarded to the * implementation. If the admin tries to call a function on the implementation it will fail with an error indicating the * proxy admin cannot fallback to the target implementation. * * These properties mean that the admin account can only be used for upgrading the proxy, so it's best if it's a dedicated * account that is not used for anything else. This will avoid headaches due to sudden errors when trying to call a function - * from the proxy implementation. - * - * Our recommendation is for the dedicated account to be an instance of the {ProxyAdmin} contract. If set up this way, - * you should think of the `ProxyAdmin` instance as the real administrative interface of your proxy, which extends from the - * {Ownable} contract to allow for changing the proxy's admin owner. + * from the proxy implementation. For this reason, the proxy deploys an instance of {ProxyAdmin} and allows upgrades + * only if they come through it. + * You should think of the `ProxyAdmin` instance as the administrative interface of the proxy, including the ability to + * change who can trigger upgrades by transferring ownership. * * NOTE: The real interface of this proxy is that defined in `ITransparentUpgradeableProxy`. This contract does not - * inherit from that interface, and instead the admin functions are implicitly implemented using a custom dispatch - * mechanism in `_fallback`. Consequently, the compiler will not produce an ABI for this contract. This is necessary to - * fully implement transparency without decoding reverts caused by selector clashes between the proxy and the + * inherit from that interface, and instead `upgradeToAndCall` is implicitly implemented using a custom dispatch mechanism + * in `_fallback`. Consequently, the compiler will not produce an ABI for this contract. This is necessary to fully + * implement transparency without decoding reverts caused by selector clashes between the proxy and the * implementation. * * IMPORTANT: This contract avoids unnecessary storage reads by setting the admin only during construction as an immutable variable, @@ -55,10 +53,10 @@ interface ITransparentUpgradeableProxy is IERC1967 { * WARNING: It is not recommended to extend this contract to add additional external functions. If you do so, the compiler * will not check that there are no selector conflicts, due to the note above. A selector clash between any new function * and the functions declared in {ITransparentUpgradeableProxy} will be resolved in favor of the new one. This could - * render the admin operations inaccessible, which could prevent upgradeability. Transparency may also be compromised. + * render the `upgradeToAndCall` function inaccessible, preventing upgradeability and compromising transparency. */ contract TransparentUpgradeableProxy is ERC1967Proxy { - // An immutable address for the admin avoid unnecessary SLOADs before each call + // An immutable address for the admin to avoid unnecessary SLOADs before each call // at the expense of removing the ability to change the admin once it's set. // This is acceptable if the admin is always a ProxyAdmin instance or similar contract // with its own ability to transfer the permissions to another account. @@ -70,73 +68,40 @@ contract TransparentUpgradeableProxy is ERC1967Proxy { error ProxyDeniedAdminAccess(); /** - * @dev msg.value is not 0. - */ - error ProxyNonPayableFunction(); - - /** - * @dev Initializes an upgradeable proxy managed by `_admin`, backed by the implementation at `_logic`, and - * optionally initialized with `_data` as explained in {ERC1967Proxy-constructor}. + * @dev Initializes an upgradeable proxy managed by an instance of a {ProxyAdmin} with an `initialOwner`, + * backed by the implementation at `_logic`, and optionally initialized with `_data` as explained in + * {ERC1967Proxy-constructor}. */ - constructor(address _logic, address admin_, bytes memory _data) payable ERC1967Proxy(_logic, _data) { - _admin = admin_; + constructor(address _logic, address initialOwner, bytes memory _data) payable ERC1967Proxy(_logic, _data) { + _admin = address(new ProxyAdmin(initialOwner)); // Set the storage value and emit an event for ERC-1967 compatibility - ERC1967Utils.changeAdmin(admin_); + ERC1967Utils.changeAdmin(_admin); } /** - * @dev If caller is the admin process the call internally, otherwise transparently fallback to the proxy behavior + * @dev If caller is the admin process the call internally, otherwise transparently fallback to the proxy behavior. */ function _fallback() internal virtual override { if (msg.sender == _admin) { - bytes memory ret; - bytes4 selector = msg.sig; - if (selector == ITransparentUpgradeableProxy.upgradeTo.selector) { - ret = _dispatchUpgradeTo(); - } else if (selector == ITransparentUpgradeableProxy.upgradeToAndCall.selector) { - ret = _dispatchUpgradeToAndCall(); + if (msg.sig == ITransparentUpgradeableProxy.upgradeToAndCall.selector) { + _dispatchUpgradeToAndCall(); } else { revert ProxyDeniedAdminAccess(); } - assembly { - return(add(ret, 0x20), mload(ret)) - } } else { super._fallback(); } } /** - * @dev Upgrade the implementation of the proxy. - */ - function _dispatchUpgradeTo() private returns (bytes memory) { - _requireZeroValue(); - - address newImplementation = abi.decode(msg.data[4:], (address)); - ERC1967Utils.upgradeToAndCall(newImplementation, bytes(""), false); - - return ""; - } - - /** - * @dev Upgrade the implementation of the proxy, and then call a function from the new implementation as specified - * by `data`, which should be an encoded function call. This is useful to initialize new storage variables in the - * proxied contract. + * @dev Upgrade the implementation of the proxy. See {ERC1967Utils-upgradeToAndCall}. + * + * Requirements: + * + * - If `data` is empty, `msg.value` must be zero. */ - function _dispatchUpgradeToAndCall() private returns (bytes memory) { + function _dispatchUpgradeToAndCall() private { (address newImplementation, bytes memory data) = abi.decode(msg.data[4:], (address, bytes)); - ERC1967Utils.upgradeToAndCall(newImplementation, data, true); - - return ""; - } - - /** - * @dev To keep this contract fully transparent, the fallback is payable. This helper is here to enforce - * non-payability of function implemented through dispatchers while still allowing value to pass through. - */ - function _requireZeroValue() private { - if (msg.value != 0) { - revert ProxyNonPayableFunction(); - } + ERC1967Utils.upgradeToAndCall(newImplementation, data); } } diff --git a/contracts/proxy/utils/UUPSUpgradeable.sol b/contracts/proxy/utils/UUPSUpgradeable.sol index 286eafe8e..982e1e58f 100644 --- a/contracts/proxy/utils/UUPSUpgradeable.sol +++ b/contracts/proxy/utils/UUPSUpgradeable.sol @@ -20,6 +20,16 @@ abstract contract UUPSUpgradeable is IERC1822Proxiable { /// @custom:oz-upgrades-unsafe-allow state-variable-immutable address private immutable __self = address(this); + /** + * @dev The version of the upgrade interface of the contract. If this getter is missing, both `upgradeTo(address)` + * and `upgradeToAndCall(address,bytes)` are present, and `upgradeTo` must be used if no function should be called, + * while `upgradeToAndCall` will invoke the `receive` function if the second argument is the empty byte string. + * If the getter returns `"5.0.0"`, only `upgradeToAndCall(address,bytes)` is present, and the second argument must + * be the empty byte string if no function should be called, being impossible to invoke the `receive` function + * during an upgrade. + */ + string public constant UPGRADE_INTERFACE_VERSION = "5.0.0"; + /** * @dev The call is from an unauthorized context. */ @@ -71,20 +81,6 @@ abstract contract UUPSUpgradeable is IERC1822Proxiable { return ERC1967Utils.IMPLEMENTATION_SLOT; } - /** - * @dev Upgrade the implementation of the proxy to `newImplementation`. - * - * Calls {_authorizeUpgrade}. - * - * Emits an {Upgraded} event. - * - * @custom:oz-upgrades-unsafe-allow-reachable delegatecall - */ - function upgradeTo(address newImplementation) public virtual onlyProxy { - _authorizeUpgrade(newImplementation); - _upgradeToAndCallUUPS(newImplementation, new bytes(0), false); - } - /** * @dev Upgrade the implementation of the proxy to `newImplementation`, and subsequently execute the function call * encoded in `data`. @@ -97,17 +93,17 @@ abstract contract UUPSUpgradeable is IERC1822Proxiable { */ function upgradeToAndCall(address newImplementation, bytes memory data) public payable virtual onlyProxy { _authorizeUpgrade(newImplementation); - _upgradeToAndCallUUPS(newImplementation, data, true); + _upgradeToAndCallUUPS(newImplementation, data); } /** * @dev Function that should revert when `msg.sender` is not authorized to upgrade the contract. Called by - * {upgradeTo} and {upgradeToAndCall}. + * {upgradeToAndCall}. * * Normally, this function will use an xref:access.adoc[access control] modifier such as {Ownable-onlyOwner}. * * ```solidity - * function _authorizeUpgrade(address) internal onlyOwner {} + * function _authorizeUpgrade(address) internal onlyOwner {} * ``` */ function _authorizeUpgrade(address newImplementation) internal virtual; @@ -117,12 +113,12 @@ abstract contract UUPSUpgradeable is IERC1822Proxiable { * * Emits an {IERC1967-Upgraded} event. */ - function _upgradeToAndCallUUPS(address newImplementation, bytes memory data, bool forceCall) private { + function _upgradeToAndCallUUPS(address newImplementation, bytes memory data) private { try IERC1822Proxiable(newImplementation).proxiableUUID() returns (bytes32 slot) { if (slot != ERC1967Utils.IMPLEMENTATION_SLOT) { revert UUPSUnsupportedProxiableUUID(slot); } - ERC1967Utils.upgradeToAndCall(newImplementation, data, forceCall); + ERC1967Utils.upgradeToAndCall(newImplementation, data); } catch { // The implementation is not UUPS revert ERC1967Utils.ERC1967InvalidImplementation(newImplementation); diff --git a/test/governance/compatibility/GovernorCompatibilityBravo.test.js b/test/governance/compatibility/GovernorCompatibilityBravo.test.js index 4182dfb4e..26255342f 100644 --- a/test/governance/compatibility/GovernorCompatibilityBravo.test.js +++ b/test/governance/compatibility/GovernorCompatibilityBravo.test.js @@ -1,6 +1,6 @@ const { expectEvent } = require('@openzeppelin/test-helpers'); const { expect } = require('chai'); -const RLP = require('rlp'); +const { computeCreateAddress } = require('../../helpers/create'); const Enums = require('../../helpers/enums'); const { GovernorHelper } = require('../../helpers/governance'); const { clockFromReceipt } = require('../../helpers/time'); @@ -12,15 +12,6 @@ const CallReceiver = artifacts.require('CallReceiverMock'); const { shouldBehaveLikeEIP6372 } = require('../utils/EIP6372.behavior'); -function makeContractAddress(creator, nonce) { - return web3.utils.toChecksumAddress( - web3.utils - .sha3(RLP.encode([creator, nonce])) - .slice(12) - .substring(14), - ); -} - const TOKENS = [ { Token: artifacts.require('$ERC20Votes'), mode: 'blocknumber' }, { Token: artifacts.require('$ERC20VotesTimestampMock'), mode: 'timestamp' }, @@ -58,7 +49,7 @@ contract('GovernorCompatibilityBravo', function (accounts) { // Need to predict governance address to set it as timelock admin with a delayed transfer const nonce = await web3.eth.getTransactionCount(deployer); - const predictGovernor = makeContractAddress(deployer, nonce + 1); + const predictGovernor = computeCreateAddress(deployer, nonce + 1); this.timelock = await Timelock.new(predictGovernor, 2 * 86400); this.mock = await Governor.new( diff --git a/test/governance/extensions/GovernorTimelockCompound.test.js b/test/governance/extensions/GovernorTimelockCompound.test.js index c406baf8d..7a2cb0abd 100644 --- a/test/governance/extensions/GovernorTimelockCompound.test.js +++ b/test/governance/extensions/GovernorTimelockCompound.test.js @@ -1,10 +1,10 @@ const { constants, expectEvent, expectRevert } = require('@openzeppelin/test-helpers'); const { expect } = require('chai'); -const RLP = require('rlp'); const Enums = require('../../helpers/enums'); const { GovernorHelper, proposalStatesToBitMap } = require('../../helpers/governance'); const { expectRevertCustomError } = require('../../helpers/customError'); +const { computeCreateAddress } = require('../../helpers/create'); const { shouldSupportInterfaces } = require('../../utils/introspection/SupportsInterface.behavior'); @@ -14,15 +14,6 @@ const CallReceiver = artifacts.require('CallReceiverMock'); const ERC721 = artifacts.require('$ERC721'); const ERC1155 = artifacts.require('$ERC1155'); -function makeContractAddress(creator, nonce) { - return web3.utils.toChecksumAddress( - web3.utils - .sha3(RLP.encode([creator, nonce])) - .slice(12) - .substring(14), - ); -} - const TOKENS = [ { Token: artifacts.require('$ERC20Votes'), mode: 'blocknumber' }, { Token: artifacts.require('$ERC20VotesTimestampMock'), mode: 'timestamp' }, @@ -49,7 +40,7 @@ contract('GovernorTimelockCompound', function (accounts) { // Need to predict governance address to set it as timelock admin with a delayed transfer const nonce = await web3.eth.getTransactionCount(deployer); - const predictGovernor = makeContractAddress(deployer, nonce + 1); + const predictGovernor = computeCreateAddress(deployer, nonce + 1); this.timelock = await Timelock.new(predictGovernor, 2 * 86400); this.mock = await Governor.new( diff --git a/test/helpers/account.js b/test/helpers/account.js new file mode 100644 index 000000000..1b01a7214 --- /dev/null +++ b/test/helpers/account.js @@ -0,0 +1,14 @@ +const { web3 } = require('hardhat'); +const { impersonateAccount, setBalance } = require('@nomicfoundation/hardhat-network-helpers'); + +// Hardhat default balance +const DEFAULT_BALANCE = web3.utils.toBN('10000000000000000000000'); + +async function impersonate(account, balance = DEFAULT_BALANCE) { + await impersonateAccount(account); + await setBalance(account, balance); +} + +module.exports = { + impersonate, +}; diff --git a/test/helpers/create.js b/test/helpers/create.js new file mode 100644 index 000000000..e0cea66e2 --- /dev/null +++ b/test/helpers/create.js @@ -0,0 +1,23 @@ +const { rlp } = require('ethereumjs-util'); + +function computeCreateAddress(deployer, nonce) { + return web3.utils.toChecksumAddress(web3.utils.sha3(rlp.encode([deployer.address ?? deployer, nonce])).slice(-40)); +} + +function computeCreate2Address(saltHex, bytecode, deployer) { + return web3.utils.toChecksumAddress( + web3.utils + .sha3( + '0x' + + ['ff', deployer.address ?? deployer, saltHex, web3.utils.soliditySha3(bytecode)] + .map(x => x.replace(/0x/, '')) + .join(''), + ) + .slice(-40), + ); +} + +module.exports = { + computeCreateAddress, + computeCreate2Address, +}; diff --git a/test/helpers/create2.js b/test/helpers/create2.js deleted file mode 100644 index afe07dae3..000000000 --- a/test/helpers/create2.js +++ /dev/null @@ -1,11 +0,0 @@ -function computeCreate2Address(saltHex, bytecode, deployer) { - return web3.utils.toChecksumAddress( - `0x${web3.utils - .sha3(`0x${['ff', deployer, saltHex, web3.utils.soliditySha3(bytecode)].map(x => x.replace(/0x/, '')).join('')}`) - .slice(-40)}`, - ); -} - -module.exports = { - computeCreate2Address, -}; diff --git a/test/helpers/erc1967.js b/test/helpers/erc1967.js index ac263d0c4..4ad92c55c 100644 --- a/test/helpers/erc1967.js +++ b/test/helpers/erc1967.js @@ -1,3 +1,5 @@ +const { getStorageAt, setStorageAt } = require('@nomicfoundation/hardhat-network-helpers'); + const ImplementationLabel = 'eip1967.proxy.implementation'; const AdminLabel = 'eip1967.proxy.admin'; const BeaconLabel = 'eip1967.proxy.beacon'; @@ -7,15 +9,25 @@ function labelToSlot(label) { } function getSlot(address, slot) { - return web3.eth.getStorageAt( + return getStorageAt( + web3.utils.isAddress(address) ? address : address.address, + web3.utils.isHex(slot) ? slot : labelToSlot(slot), + ); +} + +function setSlot(address, slot, value) { + const hexValue = web3.utils.isHex(value) ? value : web3.utils.toHex(value); + + return setStorageAt( web3.utils.isAddress(address) ? address : address.address, web3.utils.isHex(slot) ? slot : labelToSlot(slot), + web3.utils.padLeft(hexValue, 64), ); } async function getAddressInSlot(address, slot) { const slotValue = await getSlot(address, slot); - return web3.utils.toChecksumAddress(slotValue.substr(-40)); + return web3.utils.toChecksumAddress(slotValue.substring(slotValue.length - 40)); } module.exports = { @@ -25,6 +37,7 @@ module.exports = { ImplementationSlot: labelToSlot(ImplementationLabel), AdminSlot: labelToSlot(AdminLabel), BeaconSlot: labelToSlot(BeaconLabel), + setSlot, getSlot, getAddressInSlot, }; diff --git a/test/proxy/Clones.test.js b/test/proxy/Clones.test.js index 2edd1999c..14a70e20b 100644 --- a/test/proxy/Clones.test.js +++ b/test/proxy/Clones.test.js @@ -1,5 +1,5 @@ const { expectEvent } = require('@openzeppelin/test-helpers'); -const { computeCreate2Address } = require('../helpers/create2'); +const { computeCreate2Address } = require('../helpers/create'); const { expect } = require('chai'); const { expectRevertCustomError } = require('../helpers/customError'); diff --git a/test/proxy/ERC1967/ERC1967Proxy.test.js b/test/proxy/ERC1967/ERC1967Proxy.test.js index 22df960ff..81cc43507 100644 --- a/test/proxy/ERC1967/ERC1967Proxy.test.js +++ b/test/proxy/ERC1967/ERC1967Proxy.test.js @@ -3,11 +3,10 @@ const shouldBehaveLikeProxy = require('../Proxy.behaviour'); const ERC1967Proxy = artifacts.require('ERC1967Proxy'); contract('ERC1967Proxy', function (accounts) { - const [proxyAdminOwner] = accounts; - - const createProxy = async function (implementation, _admin, initData, opts) { - return ERC1967Proxy.new(implementation, initData, opts); + // `undefined`, `null` and other false-ish opts will not be forwarded. + const createProxy = async function (implementation, initData, opts) { + return ERC1967Proxy.new(implementation, initData, ...[opts].filter(Boolean)); }; - shouldBehaveLikeProxy(createProxy, undefined, proxyAdminOwner); + shouldBehaveLikeProxy(createProxy, accounts); }); diff --git a/test/proxy/ERC1967/ERC1967Utils.test.js b/test/proxy/ERC1967/ERC1967Utils.test.js new file mode 100644 index 000000000..cce874cd9 --- /dev/null +++ b/test/proxy/ERC1967/ERC1967Utils.test.js @@ -0,0 +1,160 @@ +const { expectEvent, constants } = require('@openzeppelin/test-helpers'); +const { expectRevertCustomError } = require('../../helpers/customError'); +const { getAddressInSlot, setSlot, ImplementationSlot, AdminSlot, BeaconSlot } = require('../../helpers/erc1967'); + +const { ZERO_ADDRESS } = constants; + +const ERC1967Utils = artifacts.require('$ERC1967Utils'); + +const V1 = artifacts.require('DummyImplementation'); +const V2 = artifacts.require('CallReceiverMock'); +const UpgradeableBeaconMock = artifacts.require('UpgradeableBeaconMock'); + +contract('ERC1967Utils', function (accounts) { + const [, admin, anotherAccount] = accounts; + const EMPTY_DATA = '0x'; + + beforeEach('setup', async function () { + this.utils = await ERC1967Utils.new(); + this.v1 = await V1.new(); + this.v2 = await V2.new(); + }); + + describe('IMPLEMENTATION_SLOT', function () { + beforeEach('set v1 implementation', async function () { + await setSlot(this.utils, ImplementationSlot, this.v1.address); + }); + + describe('getImplementation', function () { + it('returns current implementation and matches implementation slot value', async function () { + expect(await this.utils.$getImplementation()).to.equal(this.v1.address); + expect(await getAddressInSlot(this.utils.address, ImplementationSlot)).to.equal(this.v1.address); + }); + }); + + describe('upgradeToAndCall', function () { + it('sets implementation in storage and emits event', async function () { + const newImplementation = this.v2.address; + const receipt = await this.utils.$upgradeToAndCall(newImplementation, EMPTY_DATA); + + expect(await getAddressInSlot(this.utils.address, ImplementationSlot)).to.equal(newImplementation); + expectEvent(receipt, 'Upgraded', { implementation: newImplementation }); + }); + + it('reverts when implementation does not contain code', async function () { + await expectRevertCustomError( + this.utils.$upgradeToAndCall(anotherAccount, EMPTY_DATA), + 'ERC1967InvalidImplementation', + [anotherAccount], + ); + }); + + describe('when data is empty', function () { + it('reverts when value is sent', async function () { + await expectRevertCustomError( + this.utils.$upgradeToAndCall(this.v2.address, EMPTY_DATA, { value: 1 }), + 'ERC1967NonPayable', + [], + ); + }); + }); + + describe('when data is not empty', function () { + it('delegates a call to the new implementation', async function () { + const initializeData = this.v2.contract.methods.mockFunction().encodeABI(); + const receipt = await this.utils.$upgradeToAndCall(this.v2.address, initializeData); + await expectEvent.inTransaction(receipt.tx, await V2.at(this.utils.address), 'MockFunctionCalled'); + }); + }); + }); + }); + + describe('ADMIN_SLOT', function () { + beforeEach('set admin', async function () { + await setSlot(this.utils, AdminSlot, admin); + }); + + describe('getAdmin', function () { + it('returns current admin and matches admin slot value', async function () { + expect(await this.utils.$getAdmin()).to.equal(admin); + expect(await getAddressInSlot(this.utils.address, AdminSlot)).to.equal(admin); + }); + }); + + describe('changeAdmin', function () { + it('sets admin in storage and emits event', async function () { + const newAdmin = anotherAccount; + const receipt = await this.utils.$changeAdmin(newAdmin); + + expect(await getAddressInSlot(this.utils.address, AdminSlot)).to.equal(newAdmin); + expectEvent(receipt, 'AdminChanged', { previousAdmin: admin, newAdmin: newAdmin }); + }); + + it('reverts when setting the address zero as admin', async function () { + await expectRevertCustomError(this.utils.$changeAdmin(ZERO_ADDRESS), 'ERC1967InvalidAdmin', [ZERO_ADDRESS]); + }); + }); + }); + + describe('BEACON_SLOT', function () { + beforeEach('set beacon', async function () { + this.beacon = await UpgradeableBeaconMock.new(this.v1.address); + await setSlot(this.utils, BeaconSlot, this.beacon.address); + }); + + describe('getBeacon', function () { + it('returns current beacon and matches beacon slot value', async function () { + expect(await this.utils.$getBeacon()).to.equal(this.beacon.address); + expect(await getAddressInSlot(this.utils.address, BeaconSlot)).to.equal(this.beacon.address); + }); + }); + + describe('upgradeBeaconToAndCall', function () { + it('sets beacon in storage and emits event', async function () { + const newBeacon = await UpgradeableBeaconMock.new(this.v2.address); + const receipt = await this.utils.$upgradeBeaconToAndCall(newBeacon.address, EMPTY_DATA); + + expect(await getAddressInSlot(this.utils.address, BeaconSlot)).to.equal(newBeacon.address); + expectEvent(receipt, 'BeaconUpgraded', { beacon: newBeacon.address }); + }); + + it('reverts when beacon does not contain code', async function () { + await expectRevertCustomError( + this.utils.$upgradeBeaconToAndCall(anotherAccount, EMPTY_DATA), + 'ERC1967InvalidBeacon', + [anotherAccount], + ); + }); + + it("reverts when beacon's implementation does not contain code", async function () { + const newBeacon = await UpgradeableBeaconMock.new(anotherAccount); + + await expectRevertCustomError( + this.utils.$upgradeBeaconToAndCall(newBeacon.address, EMPTY_DATA), + 'ERC1967InvalidImplementation', + [anotherAccount], + ); + }); + + describe('when data is empty', function () { + it('reverts when value is sent', async function () { + const newBeacon = await UpgradeableBeaconMock.new(this.v2.address); + await expectRevertCustomError( + this.utils.$upgradeBeaconToAndCall(newBeacon.address, EMPTY_DATA, { value: 1 }), + 'ERC1967NonPayable', + [], + ); + }); + }); + + describe('when data is not empty', function () { + it('delegates a call to the new implementation', async function () { + const initializeData = this.v2.contract.methods.mockFunction().encodeABI(); + const newBeacon = await UpgradeableBeaconMock.new(this.v2.address); + const receipt = await this.utils.$upgradeBeaconToAndCall(newBeacon.address, initializeData); + await expectEvent.inTransaction(receipt.tx, await V2.at(this.utils.address), 'MockFunctionCalled'); + }); + }); + }); + }); +}); diff --git a/test/proxy/Proxy.behaviour.js b/test/proxy/Proxy.behaviour.js index 0867b93c9..acce6d188 100644 --- a/test/proxy/Proxy.behaviour.js +++ b/test/proxy/Proxy.behaviour.js @@ -2,18 +2,15 @@ const { expectRevert } = require('@openzeppelin/test-helpers'); const { getSlot, ImplementationSlot } = require('../helpers/erc1967'); const { expect } = require('chai'); +const { expectRevertCustomError } = require('../helpers/customError'); const DummyImplementation = artifacts.require('DummyImplementation'); -module.exports = function shouldBehaveLikeProxy(createProxy, proxyAdminAddress, proxyCreator) { +module.exports = function shouldBehaveLikeProxy(createProxy, accounts) { it('cannot be initialized with a non-contract address', async function () { - const nonContractAddress = proxyCreator; + const nonContractAddress = accounts[0]; const initializeData = Buffer.from(''); - await expectRevert.unspecified( - createProxy(nonContractAddress, proxyAdminAddress, initializeData, { - from: proxyCreator, - }), - ); + await expectRevert.unspecified(createProxy(nonContractAddress, initializeData)); }); before('deploy implementation', async function () { @@ -42,11 +39,7 @@ module.exports = function shouldBehaveLikeProxy(createProxy, proxyAdminAddress, describe('when not sending balance', function () { beforeEach('creating proxy', async function () { - this.proxy = ( - await createProxy(this.implementation, proxyAdminAddress, initializeData, { - from: proxyCreator, - }) - ).address; + this.proxy = (await createProxy(this.implementation, initializeData)).address; }); assertProxyInitialization({ value: 0, balance: 0 }); @@ -55,16 +48,13 @@ module.exports = function shouldBehaveLikeProxy(createProxy, proxyAdminAddress, describe('when sending some balance', function () { const value = 10e5; - beforeEach('creating proxy', async function () { - this.proxy = ( - await createProxy(this.implementation, proxyAdminAddress, initializeData, { - from: proxyCreator, - value, - }) - ).address; + it('reverts', async function () { + await expectRevertCustomError( + createProxy(this.implementation, initializeData, { value }), + 'ERC1967NonPayable', + [], + ); }); - - assertProxyInitialization({ value: 0, balance: value }); }); }); @@ -75,11 +65,7 @@ module.exports = function shouldBehaveLikeProxy(createProxy, proxyAdminAddress, describe('when not sending balance', function () { beforeEach('creating proxy', async function () { - this.proxy = ( - await createProxy(this.implementation, proxyAdminAddress, initializeData, { - from: proxyCreator, - }) - ).address; + this.proxy = (await createProxy(this.implementation, initializeData)).address; }); assertProxyInitialization({ @@ -92,9 +78,7 @@ module.exports = function shouldBehaveLikeProxy(createProxy, proxyAdminAddress, const value = 10e5; it('reverts', async function () { - await expectRevert.unspecified( - createProxy(this.implementation, proxyAdminAddress, initializeData, { from: proxyCreator, value }), - ); + await expectRevert.unspecified(createProxy(this.implementation, initializeData, { value })); }); }); }); @@ -105,11 +89,7 @@ module.exports = function shouldBehaveLikeProxy(createProxy, proxyAdminAddress, describe('when not sending balance', function () { beforeEach('creating proxy', async function () { - this.proxy = ( - await createProxy(this.implementation, proxyAdminAddress, initializeData, { - from: proxyCreator, - }) - ).address; + this.proxy = (await createProxy(this.implementation, initializeData)).address; }); assertProxyInitialization({ @@ -122,12 +102,7 @@ module.exports = function shouldBehaveLikeProxy(createProxy, proxyAdminAddress, const value = 10e5; beforeEach('creating proxy', async function () { - this.proxy = ( - await createProxy(this.implementation, proxyAdminAddress, initializeData, { - from: proxyCreator, - value, - }) - ).address; + this.proxy = (await createProxy(this.implementation, initializeData, { value })).address; }); assertProxyInitialization({ @@ -147,11 +122,7 @@ module.exports = function shouldBehaveLikeProxy(createProxy, proxyAdminAddress, describe('when not sending balance', function () { beforeEach('creating proxy', async function () { - this.proxy = ( - await createProxy(this.implementation, proxyAdminAddress, initializeData, { - from: proxyCreator, - }) - ).address; + this.proxy = (await createProxy(this.implementation, initializeData)).address; }); assertProxyInitialization({ @@ -164,9 +135,7 @@ module.exports = function shouldBehaveLikeProxy(createProxy, proxyAdminAddress, const value = 10e5; it('reverts', async function () { - await expectRevert.unspecified( - createProxy(this.implementation, proxyAdminAddress, initializeData, { from: proxyCreator, value }), - ); + await expectRevert.unspecified(createProxy(this.implementation, initializeData, { value })); }); }); }); @@ -179,11 +148,7 @@ module.exports = function shouldBehaveLikeProxy(createProxy, proxyAdminAddress, describe('when not sending balance', function () { beforeEach('creating proxy', async function () { - this.proxy = ( - await createProxy(this.implementation, proxyAdminAddress, initializeData, { - from: proxyCreator, - }) - ).address; + this.proxy = (await createProxy(this.implementation, initializeData)).address; }); assertProxyInitialization({ @@ -196,12 +161,7 @@ module.exports = function shouldBehaveLikeProxy(createProxy, proxyAdminAddress, const value = 10e5; beforeEach('creating proxy', async function () { - this.proxy = ( - await createProxy(this.implementation, proxyAdminAddress, initializeData, { - from: proxyCreator, - value, - }) - ).address; + this.proxy = (await createProxy(this.implementation, initializeData, { value })).address; }); assertProxyInitialization({ @@ -215,10 +175,7 @@ module.exports = function shouldBehaveLikeProxy(createProxy, proxyAdminAddress, const initializeData = new DummyImplementation('').contract.methods.reverts().encodeABI(); it('reverts', async function () { - await expectRevert( - createProxy(this.implementation, proxyAdminAddress, initializeData, { from: proxyCreator }), - 'DummyImplementation reverted', - ); + await expectRevert(createProxy(this.implementation, initializeData), 'DummyImplementation reverted'); }); }); }); diff --git a/test/proxy/beacon/BeaconProxy.test.js b/test/proxy/beacon/BeaconProxy.test.js index 63d982397..d583d0ffb 100644 --- a/test/proxy/beacon/BeaconProxy.test.js +++ b/test/proxy/beacon/BeaconProxy.test.js @@ -59,9 +59,8 @@ contract('BeaconProxy', function (accounts) { it('no initialization', async function () { const data = Buffer.from(''); - const balance = '10'; - this.proxy = await BeaconProxy.new(this.beacon.address, data, { value: balance }); - await this.assertInitialized({ value: '0', balance }); + this.proxy = await BeaconProxy.new(this.beacon.address, data); + await this.assertInitialized({ value: '0', balance: '0' }); }); it('non-payable initialization', async function () { @@ -79,7 +78,16 @@ contract('BeaconProxy', function (accounts) { await this.assertInitialized({ value, balance }); }); - it('reverting initialization', async function () { + it('reverting initialization due to value', async function () { + const data = Buffer.from(''); + await expectRevertCustomError( + BeaconProxy.new(this.beacon.address, data, { value: '1' }), + 'ERC1967NonPayable', + [], + ); + }); + + it('reverting initialization function', async function () { const data = this.implementationV0.contract.methods.reverts().encodeABI(); await expectRevert(BeaconProxy.new(this.beacon.address, data), 'DummyImplementation reverted'); }); diff --git a/test/proxy/transparent/ProxyAdmin.test.js b/test/proxy/transparent/ProxyAdmin.test.js index 23f7ce9b2..4d1a54f6a 100644 --- a/test/proxy/transparent/ProxyAdmin.test.js +++ b/test/proxy/transparent/ProxyAdmin.test.js @@ -8,6 +8,7 @@ const ITransparentUpgradeableProxy = artifacts.require('ITransparentUpgradeableP const { getAddressInSlot, ImplementationSlot } = require('../../helpers/erc1967'); const { expectRevertCustomError } = require('../../helpers/customError'); +const { computeCreateAddress } = require('../../helpers/create'); contract('ProxyAdmin', function (accounts) { const [proxyAdminOwner, anotherAccount] = accounts; @@ -19,12 +20,12 @@ contract('ProxyAdmin', function (accounts) { beforeEach(async function () { const initializeData = Buffer.from(''); - this.proxyAdmin = await ProxyAdmin.new(proxyAdminOwner); - const proxy = await TransparentUpgradeableProxy.new( - this.implementationV1.address, - this.proxyAdmin.address, - initializeData, - ); + const proxy = await TransparentUpgradeableProxy.new(this.implementationV1.address, proxyAdminOwner, initializeData); + + const proxyNonce = await web3.eth.getTransactionCount(proxy.address); + const proxyAdminAddress = computeCreateAddress(proxy.address, proxyNonce - 1); // Nonce already used + this.proxyAdmin = await ProxyAdmin.at(proxyAdminAddress); + this.proxy = await ITransparentUpgradeableProxy.at(proxy.address); }); @@ -32,11 +33,17 @@ contract('ProxyAdmin', function (accounts) { expect(await this.proxyAdmin.owner()).to.equal(proxyAdminOwner); }); - describe('#upgrade', function () { + it('has an interface version', async function () { + expect(await this.proxyAdmin.UPGRADE_INTERFACE_VERSION()).to.equal('5.0.0'); + }); + + describe('without data', function () { context('with unauthorized account', function () { it('fails to upgrade', async function () { await expectRevertCustomError( - this.proxyAdmin.upgrade(this.proxy.address, this.implementationV2.address, { from: anotherAccount }), + this.proxyAdmin.upgradeAndCall(this.proxy.address, this.implementationV2.address, '0x', { + from: anotherAccount, + }), 'OwnableUnauthorizedAccount', [anotherAccount], ); @@ -45,7 +52,9 @@ contract('ProxyAdmin', function (accounts) { context('with authorized account', function () { it('upgrades implementation', async function () { - await this.proxyAdmin.upgrade(this.proxy.address, this.implementationV2.address, { from: proxyAdminOwner }); + await this.proxyAdmin.upgradeAndCall(this.proxy.address, this.implementationV2.address, '0x', { + from: proxyAdminOwner, + }); const implementationAddress = await getAddressInSlot(this.proxy, ImplementationSlot); expect(implementationAddress).to.be.equal(this.implementationV2.address); @@ -53,7 +62,7 @@ contract('ProxyAdmin', function (accounts) { }); }); - describe('#upgradeAndCall', function () { + describe('with data', function () { context('with unauthorized account', function () { it('fails to upgrade', async function () { const callData = new ImplV1('').contract.methods.initializeNonPayableWithValue(1337).encodeABI(); diff --git a/test/proxy/transparent/TransparentUpgradeableProxy.behaviour.js b/test/proxy/transparent/TransparentUpgradeableProxy.behaviour.js index 1a03b84db..103af7fc3 100644 --- a/test/proxy/transparent/TransparentUpgradeableProxy.behaviour.js +++ b/test/proxy/transparent/TransparentUpgradeableProxy.behaviour.js @@ -5,6 +5,8 @@ const { expectRevertCustomError } = require('../../helpers/customError'); const { expect } = require('chai'); const { web3 } = require('hardhat'); +const { computeCreateAddress } = require('../../helpers/create'); +const { impersonate } = require('../../helpers/account'); const Implementation1 = artifacts.require('Implementation1'); const Implementation2 = artifacts.require('Implementation2'); @@ -16,9 +18,23 @@ const MigratableMockV3 = artifacts.require('MigratableMockV3'); const InitializableMock = artifacts.require('InitializableMock'); const DummyImplementation = artifacts.require('DummyImplementation'); const ClashingImplementation = artifacts.require('ClashingImplementation'); +const Ownable = artifacts.require('Ownable'); -module.exports = function shouldBehaveLikeTransparentUpgradeableProxy(createProxy, accounts) { - const [proxyAdminAddress, proxyAdminOwner, anotherAccount] = accounts; +module.exports = function shouldBehaveLikeTransparentUpgradeableProxy(createProxy, initialOwner, accounts) { + const [anotherAccount] = accounts; + + async function createProxyWithImpersonatedProxyAdmin(logic, initData, opts = undefined) { + const proxy = await createProxy(logic, initData, opts); + + // Expect proxy admin to be the first and only contract created by the proxy + const proxyAdminAddress = computeCreateAddress(proxy.address, 1); + await impersonate(proxyAdminAddress); + + return { + proxy, + proxyAdminAddress, + }; + } before(async function () { this.implementationV0 = (await DummyImplementation.new()).address; @@ -27,10 +43,12 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy(createProx beforeEach(async function () { const initializeData = Buffer.from(''); - this.proxy = await createProxy(this.implementationV0, proxyAdminAddress, initializeData, { - from: proxyAdminOwner, - }); - this.proxyAddress = this.proxy.address; + const { proxy, proxyAdminAddress } = await createProxyWithImpersonatedProxyAdmin( + this.implementationV0, + initializeData, + ); + this.proxy = proxy; + this.proxyAdminAddress = proxyAdminAddress; }); describe('implementation', function () { @@ -40,7 +58,7 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy(createProx }); it('delegates to the implementation', async function () { - const dummy = new DummyImplementation(this.proxyAddress); + const dummy = new DummyImplementation(this.proxy.address); const value = await dummy.get(); expect(value).to.equal(true); @@ -49,64 +67,33 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy(createProx describe('proxy admin', function () { it('emits AdminChanged event during construction', async function () { - expectEvent.inConstruction(this.proxy, 'AdminChanged', { + await expectEvent.inConstruction(this.proxy, 'AdminChanged', { previousAdmin: ZERO_ADDRESS, - newAdmin: proxyAdminAddress, + newAdmin: this.proxyAdminAddress, }); }); - it('sets the admin in the storage', async function () { - expect(await getAddressInSlot(this.proxy, AdminSlot)).to.be.equal(proxyAdminAddress); + it('sets the proxy admin in storage with the correct initial owner', async function () { + expect(await getAddressInSlot(this.proxy, AdminSlot)).to.be.equal(this.proxyAdminAddress); + const proxyAdmin = await Ownable.at(this.proxyAdminAddress); + expect(await proxyAdmin.owner()).to.be.equal(initialOwner); }); it('can overwrite the admin by the implementation', async function () { - const dummy = new DummyImplementation(this.proxyAddress); + const dummy = new DummyImplementation(this.proxy.address); await dummy.unsafeOverrideAdmin(anotherAccount); const ERC1967AdminSlotValue = await getAddressInSlot(this.proxy, AdminSlot); expect(ERC1967AdminSlotValue).to.be.equal(anotherAccount); // Still allows previous admin to execute admin operations - expect(ERC1967AdminSlotValue).to.not.equal(proxyAdminAddress); - expectEvent(await this.proxy.upgradeTo(this.implementationV1, { from: proxyAdminAddress }), 'Upgraded', { - implementation: this.implementationV1, - }); - }); - }); - - describe('upgradeTo', function () { - describe('when the sender is the admin', function () { - const from = proxyAdminAddress; - - describe('when the given implementation is different from the current one', function () { - it('upgrades to the requested implementation', async function () { - await this.proxy.upgradeTo(this.implementationV1, { from }); - - const implementationAddress = await getAddressInSlot(this.proxy, ImplementationSlot); - expect(implementationAddress).to.be.equal(this.implementationV1); - }); - - it('emits an event', async function () { - expectEvent(await this.proxy.upgradeTo(this.implementationV1, { from }), 'Upgraded', { - implementation: this.implementationV1, - }); - }); - }); - - describe('when the given implementation is the zero address', function () { - it('reverts', async function () { - await expectRevertCustomError(this.proxy.upgradeTo(ZERO_ADDRESS, { from }), 'ERC1967InvalidImplementation', [ - ZERO_ADDRESS, - ]); - }); - }); - }); - - describe('when the sender is not the admin', function () { - const from = anotherAccount; - - it('reverts', async function () { - await expectRevert.unspecified(this.proxy.upgradeTo(this.implementationV1, { from })); - }); + expect(ERC1967AdminSlotValue).to.not.equal(this.proxyAdminAddress); + expectEvent( + await this.proxy.upgradeToAndCall(this.implementationV1, '0x', { from: this.proxyAdminAddress }), + 'Upgraded', + { + implementation: this.implementationV1, + }, + ); }); }); @@ -120,11 +107,13 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy(createProx const initializeData = new InitializableMock('').contract.methods['initializeWithX(uint256)'](42).encodeABI(); describe('when the sender is the admin', function () { - const from = proxyAdminAddress; const value = 1e5; beforeEach(async function () { - this.receipt = await this.proxy.upgradeToAndCall(this.behavior.address, initializeData, { from, value }); + this.receipt = await this.proxy.upgradeToAndCall(this.behavior.address, initializeData, { + from: this.proxyAdminAddress, + value, + }); }); it('upgrades to the requested implementation', async function () { @@ -137,13 +126,13 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy(createProx }); it('calls the initializer function', async function () { - const migratable = new InitializableMock(this.proxyAddress); + const migratable = new InitializableMock(this.proxy.address); const x = await migratable.x(); expect(x).to.be.bignumber.equal('42'); }); it('sends given value to the proxy', async function () { - const balance = await web3.eth.getBalance(this.proxyAddress); + const balance = await web3.eth.getBalance(this.proxy.address); expect(balance.toString()).to.be.bignumber.equal(value.toString()); }); @@ -151,7 +140,7 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy(createProx // storage layout should look as follows: // - 0: Initializable storage ++ initializerRan ++ onlyInitializingRan // - 1: x - const storedValue = await web3.eth.getStorageAt(this.proxyAddress, 1); + const storedValue = await web3.eth.getStorageAt(this.proxy.address, 1); expect(parseInt(storedValue)).to.eq(42); }); }); @@ -170,7 +159,7 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy(createProx it('reverts', async function () { await expectRevert.unspecified( - this.proxy.upgradeToAndCall(this.behavior.address, initializeData, { from: proxyAdminAddress }), + this.proxy.upgradeToAndCall(this.behavior.address, initializeData, { from: this.proxyAdminAddress }), ); }); }); @@ -178,7 +167,6 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy(createProx describe('with migrations', function () { describe('when the sender is the admin', function () { - const from = proxyAdminAddress; const value = 1e5; describe('when upgrading to V1', function () { @@ -186,8 +174,11 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy(createProx beforeEach(async function () { this.behaviorV1 = await MigratableMockV1.new(); - this.balancePreviousV1 = new BN(await web3.eth.getBalance(this.proxyAddress)); - this.receipt = await this.proxy.upgradeToAndCall(this.behaviorV1.address, v1MigrationData, { from, value }); + this.balancePreviousV1 = new BN(await web3.eth.getBalance(this.proxy.address)); + this.receipt = await this.proxy.upgradeToAndCall(this.behaviorV1.address, v1MigrationData, { + from: this.proxyAdminAddress, + value, + }); }); it('upgrades to the requested version and emits an event', async function () { @@ -197,12 +188,12 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy(createProx }); it("calls the 'initialize' function and sends given value to the proxy", async function () { - const migratable = new MigratableMockV1(this.proxyAddress); + const migratable = new MigratableMockV1(this.proxy.address); const x = await migratable.x(); expect(x).to.be.bignumber.equal('42'); - const balance = await web3.eth.getBalance(this.proxyAddress); + const balance = await web3.eth.getBalance(this.proxy.address); expect(new BN(balance)).to.be.bignumber.equal(this.balancePreviousV1.addn(value)); }); @@ -211,9 +202,9 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy(createProx beforeEach(async function () { this.behaviorV2 = await MigratableMockV2.new(); - this.balancePreviousV2 = new BN(await web3.eth.getBalance(this.proxyAddress)); + this.balancePreviousV2 = new BN(await web3.eth.getBalance(this.proxy.address)); this.receipt = await this.proxy.upgradeToAndCall(this.behaviorV2.address, v2MigrationData, { - from, + from: this.proxyAdminAddress, value, }); }); @@ -225,7 +216,7 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy(createProx }); it("calls the 'migrate' function and sends given value to the proxy", async function () { - const migratable = new MigratableMockV2(this.proxyAddress); + const migratable = new MigratableMockV2(this.proxy.address); const x = await migratable.x(); expect(x).to.be.bignumber.equal('10'); @@ -233,7 +224,7 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy(createProx const y = await migratable.y(); expect(y).to.be.bignumber.equal('42'); - const balance = new BN(await web3.eth.getBalance(this.proxyAddress)); + const balance = new BN(await web3.eth.getBalance(this.proxy.address)); expect(balance).to.be.bignumber.equal(this.balancePreviousV2.addn(value)); }); @@ -242,9 +233,9 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy(createProx beforeEach(async function () { this.behaviorV3 = await MigratableMockV3.new(); - this.balancePreviousV3 = new BN(await web3.eth.getBalance(this.proxyAddress)); + this.balancePreviousV3 = new BN(await web3.eth.getBalance(this.proxy.address)); this.receipt = await this.proxy.upgradeToAndCall(this.behaviorV3.address, v3MigrationData, { - from, + from: this.proxyAdminAddress, value, }); }); @@ -256,7 +247,7 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy(createProx }); it("calls the 'migrate' function and sends given value to the proxy", async function () { - const migratable = new MigratableMockV3(this.proxyAddress); + const migratable = new MigratableMockV3(this.proxy.address); const x = await migratable.x(); expect(x).to.be.bignumber.equal('42'); @@ -264,7 +255,7 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy(createProx const y = await migratable.y(); expect(y).to.be.bignumber.equal('10'); - const balance = new BN(await web3.eth.getBalance(this.proxyAddress)); + const balance = new BN(await web3.eth.getBalance(this.proxy.address)); expect(balance).to.be.bignumber.equal(this.balancePreviousV3.addn(value)); }); }); @@ -289,15 +280,18 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy(createProx const initializeData = Buffer.from(''); this.clashingImplV0 = (await ClashingImplementation.new()).address; this.clashingImplV1 = (await ClashingImplementation.new()).address; - this.proxy = await createProxy(this.clashingImplV0, proxyAdminAddress, initializeData, { - from: proxyAdminOwner, - }); + const { proxy, proxyAdminAddress } = await createProxyWithImpersonatedProxyAdmin( + this.clashingImplV0, + initializeData, + ); + this.proxy = proxy; + this.proxyAdminAddress = proxyAdminAddress; this.clashing = new ClashingImplementation(this.proxy.address); }); it('proxy admin cannot call delegated functions', async function () { await expectRevertCustomError( - this.clashing.delegatedFunction({ from: proxyAdminAddress }), + this.clashing.delegatedFunction({ from: this.proxyAdminAddress }), 'ProxyDeniedAdminAccess', [], ); @@ -305,26 +299,16 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy(createProx describe('when function names clash', function () { it('executes the proxy function if the sender is the admin', async function () { - const receipt = await this.proxy.upgradeTo(this.clashingImplV1, { from: proxyAdminAddress, value: 0 }); + const receipt = await this.proxy.upgradeToAndCall(this.clashingImplV1, '0x', { + from: this.proxyAdminAddress, + }); expectEvent(receipt, 'Upgraded', { implementation: this.clashingImplV1 }); }); it('delegates the call to implementation when sender is not the admin', async function () { - const receipt = await this.proxy.upgradeTo(this.clashingImplV1, { from: anotherAccount, value: 0 }); - expectEvent.notEmitted(receipt, 'Upgraded'); - expectEvent.inTransaction(receipt.tx, this.clashing, 'ClashingImplementationCall'); - }); - - it('requires 0 value calling upgradeTo by proxy admin', async function () { - await expectRevertCustomError( - this.proxy.upgradeTo(this.clashingImplV1, { from: proxyAdminAddress, value: 1 }), - 'ProxyNonPayableFunction', - [], - ); - }); - - it('allows calling with value if sender is not the admin', async function () { - const receipt = await this.proxy.upgradeTo(this.clashingImplV1, { from: anotherAccount, value: 1 }); + const receipt = await this.proxy.upgradeToAndCall(this.clashingImplV1, '0x', { + from: anotherAccount, + }); expectEvent.notEmitted(receipt, 'Upgraded'); expectEvent.inTransaction(receipt.tx, this.clashing, 'ClashingImplementationCall'); }); @@ -336,13 +320,16 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy(createProx it('should add new function', async () => { const instance1 = await Implementation1.new(); - const proxy = await createProxy(instance1.address, proxyAdminAddress, initializeData, { from: proxyAdminOwner }); + const { proxy, proxyAdminAddress } = await createProxyWithImpersonatedProxyAdmin( + instance1.address, + initializeData, + ); const proxyInstance1 = new Implementation1(proxy.address); await proxyInstance1.setValue(42); const instance2 = await Implementation2.new(); - await proxy.upgradeTo(instance2.address, { from: proxyAdminAddress }); + await proxy.upgradeToAndCall(instance2.address, '0x', { from: proxyAdminAddress }); const proxyInstance2 = new Implementation2(proxy.address); const res = await proxyInstance2.getValue(); @@ -351,7 +338,10 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy(createProx it('should remove function', async () => { const instance2 = await Implementation2.new(); - const proxy = await createProxy(instance2.address, proxyAdminAddress, initializeData, { from: proxyAdminOwner }); + const { proxy, proxyAdminAddress } = await createProxyWithImpersonatedProxyAdmin( + instance2.address, + initializeData, + ); const proxyInstance2 = new Implementation2(proxy.address); await proxyInstance2.setValue(42); @@ -359,7 +349,7 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy(createProx expect(res.toString()).to.eq('42'); const instance1 = await Implementation1.new(); - await proxy.upgradeTo(instance1.address, { from: proxyAdminAddress }); + await proxy.upgradeToAndCall(instance1.address, '0x', { from: proxyAdminAddress }); const proxyInstance1 = new Implementation2(proxy.address); await expectRevert.unspecified(proxyInstance1.getValue()); @@ -367,13 +357,16 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy(createProx it('should change function signature', async () => { const instance1 = await Implementation1.new(); - const proxy = await createProxy(instance1.address, proxyAdminAddress, initializeData, { from: proxyAdminOwner }); + const { proxy, proxyAdminAddress } = await createProxyWithImpersonatedProxyAdmin( + instance1.address, + initializeData, + ); const proxyInstance1 = new Implementation1(proxy.address); await proxyInstance1.setValue(42); const instance3 = await Implementation3.new(); - await proxy.upgradeTo(instance3.address, { from: proxyAdminAddress }); + await proxy.upgradeToAndCall(instance3.address, '0x', { from: proxyAdminAddress }); const proxyInstance3 = new Implementation3(proxy.address); const res = await proxyInstance3.getValue(8); @@ -383,10 +376,13 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy(createProx it('should add fallback function', async () => { const initializeData = Buffer.from(''); const instance1 = await Implementation1.new(); - const proxy = await createProxy(instance1.address, proxyAdminAddress, initializeData, { from: proxyAdminOwner }); + const { proxy, proxyAdminAddress } = await createProxyWithImpersonatedProxyAdmin( + instance1.address, + initializeData, + ); const instance4 = await Implementation4.new(); - await proxy.upgradeTo(instance4.address, { from: proxyAdminAddress }); + await proxy.upgradeToAndCall(instance4.address, '0x', { from: proxyAdminAddress }); const proxyInstance4 = new Implementation4(proxy.address); const data = '0x'; @@ -398,10 +394,13 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy(createProx it('should remove fallback function', async () => { const instance4 = await Implementation4.new(); - const proxy = await createProxy(instance4.address, proxyAdminAddress, initializeData, { from: proxyAdminOwner }); + const { proxy, proxyAdminAddress } = await createProxyWithImpersonatedProxyAdmin( + instance4.address, + initializeData, + ); const instance2 = await Implementation2.new(); - await proxy.upgradeTo(instance2.address, { from: proxyAdminAddress }); + await proxy.upgradeToAndCall(instance2.address, '0x', { from: proxyAdminAddress }); const data = '0x'; await expectRevert.unspecified(web3.eth.sendTransaction({ to: proxy.address, from: anotherAccount, data })); diff --git a/test/proxy/transparent/TransparentUpgradeableProxy.test.js b/test/proxy/transparent/TransparentUpgradeableProxy.test.js index d60a31a21..f45e392f6 100644 --- a/test/proxy/transparent/TransparentUpgradeableProxy.test.js +++ b/test/proxy/transparent/TransparentUpgradeableProxy.test.js @@ -5,13 +5,20 @@ const TransparentUpgradeableProxy = artifacts.require('TransparentUpgradeablePro const ITransparentUpgradeableProxy = artifacts.require('ITransparentUpgradeableProxy'); contract('TransparentUpgradeableProxy', function (accounts) { - const [proxyAdminAddress, proxyAdminOwner] = accounts; + const [owner, ...otherAccounts] = accounts; - const createProxy = async function (logic, admin, initData, opts) { - const { address } = await TransparentUpgradeableProxy.new(logic, admin, initData, opts); - return ITransparentUpgradeableProxy.at(address); + // `undefined`, `null` and other false-ish opts will not be forwarded. + const createProxy = async function (logic, initData, opts = undefined) { + const { address, transactionHash } = await TransparentUpgradeableProxy.new( + logic, + owner, + initData, + ...[opts].filter(Boolean), + ); + const instance = await ITransparentUpgradeableProxy.at(address); + return { ...instance, transactionHash }; }; - shouldBehaveLikeProxy(createProxy, proxyAdminAddress, proxyAdminOwner); - shouldBehaveLikeTransparentUpgradeableProxy(createProxy, accounts); + shouldBehaveLikeProxy(createProxy, otherAccounts); + shouldBehaveLikeTransparentUpgradeableProxy(createProxy, owner, otherAccounts); }); diff --git a/test/proxy/utils/UUPSUpgradeable.test.js b/test/proxy/utils/UUPSUpgradeable.test.js index 6a8104248..0baa90520 100644 --- a/test/proxy/utils/UUPSUpgradeable.test.js +++ b/test/proxy/utils/UUPSUpgradeable.test.js @@ -25,8 +25,12 @@ contract('UUPSUpgradeable', function () { this.instance = await UUPSUpgradeableMock.at(address); }); + it('has an interface version', async function () { + expect(await this.instance.UPGRADE_INTERFACE_VERSION()).to.equal('5.0.0'); + }); + it('upgrade to upgradeable implementation', async function () { - const { receipt } = await this.instance.upgradeTo(this.implUpgradeOk.address); + const { receipt } = await this.instance.upgradeToAndCall(this.implUpgradeOk.address, '0x'); expect(receipt.logs.filter(({ event }) => event === 'Upgraded').length).to.be.equal(1); expectEvent(receipt, 'Upgraded', { implementation: this.implUpgradeOk.address }); expect(await getAddressInSlot(this.instance, ImplementationSlot)).to.be.equal(this.implUpgradeOk.address); @@ -48,7 +52,7 @@ contract('UUPSUpgradeable', function () { it('calling upgradeTo on the implementation reverts', async function () { await expectRevertCustomError( - this.implInitial.upgradeTo(this.implUpgradeOk.address), + this.implInitial.upgradeToAndCall(this.implUpgradeOk.address, '0x'), 'UUPSUnauthorizedCallContext', [], ); @@ -72,7 +76,7 @@ contract('UUPSUpgradeable', function () { ); await expectRevertCustomError( - instance.upgradeTo(this.implUpgradeUnsafe.address), + instance.upgradeToAndCall(this.implUpgradeUnsafe.address, '0x'), 'UUPSUnauthorizedCallContext', [], ); @@ -93,14 +97,14 @@ contract('UUPSUpgradeable', function () { it('rejects upgrading to an unsupported UUID', async function () { await expectRevertCustomError( - this.instance.upgradeTo(this.implUnsupportedUUID.address), + this.instance.upgradeToAndCall(this.implUnsupportedUUID.address, '0x'), 'UUPSUnsupportedProxiableUUID', [web3.utils.keccak256('invalid UUID')], ); }); it('upgrade to and unsafe upgradeable implementation', async function () { - const { receipt } = await this.instance.upgradeTo(this.implUpgradeUnsafe.address); + const { receipt } = await this.instance.upgradeToAndCall(this.implUpgradeUnsafe.address, '0x'); expectEvent(receipt, 'Upgraded', { implementation: this.implUpgradeUnsafe.address }); expect(await getAddressInSlot(this.instance, ImplementationSlot)).to.be.equal(this.implUpgradeUnsafe.address); }); @@ -108,7 +112,7 @@ contract('UUPSUpgradeable', function () { // delegate to a non existing upgradeTo function causes a low level revert it('reject upgrade to non uups implementation', async function () { await expectRevertCustomError( - this.instance.upgradeTo(this.implUpgradeNonUUPS.address), + this.instance.upgradeToAndCall(this.implUpgradeNonUUPS.address, '0x'), 'ERC1967InvalidImplementation', [this.implUpgradeNonUUPS.address], ); @@ -118,8 +122,10 @@ contract('UUPSUpgradeable', function () { const { address } = await ERC1967Proxy.new(this.implInitial.address, '0x'); const otherInstance = await UUPSUpgradeableMock.at(address); - await expectRevertCustomError(this.instance.upgradeTo(otherInstance.address), 'ERC1967InvalidImplementation', [ - otherInstance.address, - ]); + await expectRevertCustomError( + this.instance.upgradeToAndCall(otherInstance.address, '0x'), + 'ERC1967InvalidImplementation', + [otherInstance.address], + ); }); }); diff --git a/test/utils/Create2.test.js b/test/utils/Create2.test.js index f88d5504c..afbcc3db9 100644 --- a/test/utils/Create2.test.js +++ b/test/utils/Create2.test.js @@ -1,5 +1,5 @@ const { balance, ether, expectEvent, expectRevert, send } = require('@openzeppelin/test-helpers'); -const { computeCreate2Address } = require('../helpers/create2'); +const { computeCreate2Address } = require('../helpers/create'); const { expect } = require('chai'); const { expectRevertCustomError } = require('../helpers/customError'); From 21bb89ef5bfc789b9333eb05e3ba2b7b284ac77c Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Mon, 17 Jul 2023 21:26:31 +0200 Subject: [PATCH 163/182] Fix typo in MessageHashUtils.sol (#4462) --- contracts/utils/cryptography/MessageHashUtils.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/utils/cryptography/MessageHashUtils.sol b/contracts/utils/cryptography/MessageHashUtils.sol index 05ce35af8..558e5e793 100644 --- a/contracts/utils/cryptography/MessageHashUtils.sol +++ b/contracts/utils/cryptography/MessageHashUtils.sol @@ -21,7 +21,7 @@ library MessageHashUtils { * hash signed when using the https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`] JSON-RPC method. * * NOTE: The `hash` parameter is intended to be the result of hashing a raw message with - * keccak256, althoguh any bytes32 value can be safely used because the final digest will + * keccak256, although any bytes32 value can be safely used because the final digest will * be re-hashed. * * See {ECDSA-recover}. From f347b410cf6aeeaaf5197e1fece139c793c03b2b Mon Sep 17 00:00:00 2001 From: Eric Lau Date: Tue, 18 Jul 2023 13:08:38 -0400 Subject: [PATCH 164/182] Update recommended Foundry remapping (#4468) Co-authored-by: ernestognw --- README.md | 2 +- scripts/upgradeable/upgradeable.patch | 14 +++++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 1befa024a..38197f3af 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ OpenZeppelin Contracts features a [stable API](https://docs.openzeppelin.com/con $ forge install OpenZeppelin/openzeppelin-contracts ``` -Add `@openzeppelin/=lib/openzeppelin-contracts/` in `remappings.txt.` +Add `@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/` in `remappings.txt.` ### Usage diff --git a/scripts/upgradeable/upgradeable.patch b/scripts/upgradeable/upgradeable.patch index e50d3a70e..ac9eca821 100644 --- a/scripts/upgradeable/upgradeable.patch +++ b/scripts/upgradeable/upgradeable.patch @@ -59,7 +59,7 @@ index ff596b0c..00000000 - - diff --git a/README.md b/README.md -index 27627f43..e42a66e8 100644 +index 38197f3a..bc934d1c 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,9 @@ @@ -81,7 +81,7 @@ index 27627f43..e42a66e8 100644 ``` OpenZeppelin Contracts features a [stable API](https://docs.openzeppelin.com/contracts/releases-stability#api-stability), which means that your contracts won't break unexpectedly when upgrading to a newer minor version. -@@ -38,7 +41,7 @@ OpenZeppelin Contracts features a [stable API](https://docs.openzeppelin.com/con +@@ -38,10 +41,10 @@ OpenZeppelin Contracts features a [stable API](https://docs.openzeppelin.com/con > **Warning** Foundry installs the latest version initially, but subsequent `forge update` commands will use the `master` branch. ``` @@ -89,8 +89,12 @@ index 27627f43..e42a66e8 100644 +$ forge install OpenZeppelin/openzeppelin-contracts-upgradeable ``` +-Add `@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/` in `remappings.txt.` ++Add `@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/` in `remappings.txt.` + ### Usage -@@ -48,10 +51,11 @@ Once installed, you can use the contracts in the library by importing them: + +@@ -50,10 +53,11 @@ Once installed, you can use the contracts in the library by importing them: ```solidity pragma solidity ^0.8.19; @@ -126,7 +130,7 @@ index df141192..1cf90ad1 100644 "keywords": [ "solidity", diff --git a/contracts/utils/cryptography/EIP712.sol b/contracts/utils/cryptography/EIP712.sol -index 36f076e5..90c1db78 100644 +index 3800804a..90c1db78 100644 --- a/contracts/utils/cryptography/EIP712.sol +++ b/contracts/utils/cryptography/EIP712.sol @@ -4,7 +4,6 @@ @@ -293,7 +297,7 @@ index 36f076e5..90c1db78 100644 } } diff --git a/package.json b/package.json -index 9eae6732..b3a56366 100644 +index ffa868ac..e254d962 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ From 19293f3ecdb20a7f44d54279b5c1ddbb84de4a2e Mon Sep 17 00:00:00 2001 From: Prince Allwin Date: Tue, 25 Jul 2023 07:30:30 +0530 Subject: [PATCH 165/182] Remove outdated comments in AccessControl.sol (#4475) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ernesto García --- contracts/access/AccessControl.sol | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/contracts/access/AccessControl.sol b/contracts/access/AccessControl.sol index d934eddaf..99099dd1f 100644 --- a/contracts/access/AccessControl.sol +++ b/contracts/access/AccessControl.sol @@ -58,11 +58,8 @@ abstract contract AccessControl is Context, IAccessControl, ERC165 { /** * @dev Modifier that checks that an account has a specific role. Reverts - * with a standardized message including the required role. + * with a custom error including the required role. * - * The format of the revert reason is given by the following regular expression: - * - * /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/ */ modifier onlyRole(bytes32 role) { _checkRole(role); @@ -84,21 +81,15 @@ abstract contract AccessControl is Context, IAccessControl, ERC165 { } /** - * @dev Revert with a standard message if `_msgSender()` is missing `role`. + * @dev Revert with a custom error if `_msgSender()` is missing `role`. * Overriding this function changes the behavior of the {onlyRole} modifier. - * - * Format of the revert message is described in {_checkRole}. */ function _checkRole(bytes32 role) internal view virtual { _checkRole(role, _msgSender()); } /** - * @dev Revert with a standard message if `account` is missing `role`. - * - * The format of the revert reason is given by the following regular expression: - * - * /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/ + * @dev Revert with a custom error if `account` is missing `role`. */ function _checkRole(bytes32 role, address account) internal view virtual { if (!hasRole(role, account)) { From 28d9ac2bdb321b24fe06b8b916ee2962889f772b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Tue, 25 Jul 2023 15:48:23 -0600 Subject: [PATCH 166/182] Make ERC2771Context return original sender address if `msg.data.length <= 20` (#4481) --- .changeset/unlucky-beans-obey.md | 5 +++++ contracts/metatx/ERC2771Context.sol | 2 +- test/metatx/ERC2771Context.test.js | 11 +++++++++++ 3 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 .changeset/unlucky-beans-obey.md diff --git a/.changeset/unlucky-beans-obey.md b/.changeset/unlucky-beans-obey.md new file mode 100644 index 000000000..e472d3c6c --- /dev/null +++ b/.changeset/unlucky-beans-obey.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': patch +--- + +`ERC2771Context`: Return the forwarder address whenever the `msg.data` of a call originating from a trusted forwarder is not long enough to contain the request signer address (i.e. `msg.data.length` is less than 20 bytes), as specified by ERC-2771. diff --git a/contracts/metatx/ERC2771Context.sol b/contracts/metatx/ERC2771Context.sol index 35de9f7ba..9ade6148d 100644 --- a/contracts/metatx/ERC2771Context.sol +++ b/contracts/metatx/ERC2771Context.sol @@ -22,7 +22,7 @@ abstract contract ERC2771Context is Context { } function _msgSender() internal view virtual override returns (address sender) { - if (isTrustedForwarder(msg.sender)) { + if (isTrustedForwarder(msg.sender) && msg.data.length >= 20) { // The assembly code is more direct than the Solidity version using `abi.decode`. /// @solidity memory-safe-assembly assembly { diff --git a/test/metatx/ERC2771Context.test.js b/test/metatx/ERC2771Context.test.js index 3dd4b4153..a907e502c 100644 --- a/test/metatx/ERC2771Context.test.js +++ b/test/metatx/ERC2771Context.test.js @@ -12,6 +12,8 @@ const ContextMockCaller = artifacts.require('ContextMockCaller'); const { shouldBehaveLikeRegularContext } = require('../utils/Context.behavior'); contract('ERC2771Context', function (accounts) { + const [, anotherAccount] = accounts; + const MAX_UINT48 = web3.utils.toBN(1).shln(48).subn(1).toString(); beforeEach(async function () { @@ -79,6 +81,15 @@ contract('ERC2771Context', function (accounts) { const { tx } = await this.forwarder.execute(req); await expectEvent.inTransaction(tx, ERC2771ContextMock, 'Sender', { sender: this.sender }); }); + + it('returns the original sender when calldata length is less than 20 bytes (address length)', async function () { + // The forwarder doesn't produce calls with calldata length less than 20 bytes + const recipient = await ERC2771ContextMock.new(anotherAccount); + + const { receipt } = await recipient.msgSender({ from: anotherAccount }); + + await expectEvent(receipt, 'Sender', { sender: anotherAccount }); + }); }); describe('msgData', function () { From 7222a31d548695998a475c9661fa159ef45a0e88 Mon Sep 17 00:00:00 2001 From: Prince Allwin Date: Thu, 27 Jul 2023 03:27:50 +0530 Subject: [PATCH 167/182] Add internal functions inside modifiers (#4472) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ernesto García Co-authored-by: Hadrien Croubois Co-authored-by: Francisco --- .changeset/swift-numbers-cry.md | 5 +++ contracts/governance/Governor.sol | 25 ++++++++++----- contracts/proxy/utils/Initializable.sol | 9 +++++- contracts/proxy/utils/UUPSUpgradeable.sol | 37 +++++++++++++++++------ 4 files changed, 57 insertions(+), 19 deletions(-) create mode 100644 .changeset/swift-numbers-cry.md diff --git a/.changeset/swift-numbers-cry.md b/.changeset/swift-numbers-cry.md new file mode 100644 index 000000000..48afbd245 --- /dev/null +++ b/.changeset/swift-numbers-cry.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': minor +--- + +`Governor`, `Initializable`, and `UUPSUpgradeable`: Use internal functions in modifiers to optimize bytecode size. diff --git a/contracts/governance/Governor.sol b/contracts/governance/Governor.sol index 94f29e95f..a94b4b305 100644 --- a/contracts/governance/Governor.sol +++ b/contracts/governance/Governor.sol @@ -66,14 +66,7 @@ abstract contract Governor is Context, ERC165, EIP712, Nonces, IGovernor, IERC72 * governance protocol (since v4.6). */ modifier onlyGovernance() { - if (_executor() != _msgSender()) { - revert GovernorOnlyExecutor(_msgSender()); - } - if (_executor() != address(this)) { - bytes32 msgDataHash = keccak256(_msgData()); - // loop until popping the expected operation - throw if deque is empty (operation not authorized) - while (_governanceCall.popFront() != msgDataHash) {} - } + _checkGovernance(); _; } @@ -227,6 +220,22 @@ abstract contract Governor is Context, ERC165, EIP712, Nonces, IGovernor, IERC72 return _proposals[proposalId].proposer; } + /** + * @dev Reverts if the `msg.sender` is not the executor. In case the executor is not this contract + * itself, the function reverts if `msg.data` is not whitelisted as a result of an {execute} + * operation. See {onlyGovernance}. + */ + function _checkGovernance() internal virtual { + if (_executor() != _msgSender()) { + revert GovernorOnlyExecutor(_msgSender()); + } + if (_executor() != address(this)) { + bytes32 msgDataHash = keccak256(_msgData()); + // loop until popping the expected operation - throw if deque is empty (operation not authorized) + while (_governanceCall.popFront() != msgDataHash) {} + } + } + /** * @dev Amount of votes already cast passes the threshold limit. */ diff --git a/contracts/proxy/utils/Initializable.sol b/contracts/proxy/utils/Initializable.sol index 08171015d..43f82feca 100644 --- a/contracts/proxy/utils/Initializable.sol +++ b/contracts/proxy/utils/Initializable.sol @@ -138,10 +138,17 @@ abstract contract Initializable { * {initializer} and {reinitializer} modifiers, directly or indirectly. */ modifier onlyInitializing() { + _checkInitializing(); + _; + } + + /** + * @dev Reverts if the contract is not in an initializing state. See {onlyInitializing}. + */ + function _checkInitializing() internal view virtual { if (!_initializing) { revert NotInitializing(); } - _; } /** diff --git a/contracts/proxy/utils/UUPSUpgradeable.sol b/contracts/proxy/utils/UUPSUpgradeable.sol index 982e1e58f..fd41e37ec 100644 --- a/contracts/proxy/utils/UUPSUpgradeable.sol +++ b/contracts/proxy/utils/UUPSUpgradeable.sol @@ -48,12 +48,7 @@ abstract contract UUPSUpgradeable is IERC1822Proxiable { * fail. */ modifier onlyProxy() { - if ( - address(this) == __self || // Must be called through delegatecall - ERC1967Utils.getImplementation() != __self // Must be called through an active proxy - ) { - revert UUPSUnauthorizedCallContext(); - } + _checkProxy(); _; } @@ -62,10 +57,7 @@ abstract contract UUPSUpgradeable is IERC1822Proxiable { * callable on the implementing contract but not through proxies. */ modifier notDelegated() { - if (address(this) != __self) { - // Must not be called through delegatecall - revert UUPSUnauthorizedCallContext(); - } + _checkNotDelegated(); _; } @@ -96,6 +88,31 @@ abstract contract UUPSUpgradeable is IERC1822Proxiable { _upgradeToAndCallUUPS(newImplementation, data); } + /** + * @dev Reverts if the execution is not performed via delegatecall or the execution + * context is not of a proxy with an ERC1967-compliant implementation pointing to self. + * See {_onlyProxy}. + */ + function _checkProxy() internal view virtual { + if ( + address(this) == __self || // Must be called through delegatecall + ERC1967Utils.getImplementation() != __self // Must be called through an active proxy + ) { + revert UUPSUnauthorizedCallContext(); + } + } + + /** + * @dev Reverts if the execution is performed via delegatecall. + * See {notDelegated}. + */ + function _checkNotDelegated() internal view virtual { + if (address(this) != __self) { + // Must not be called through delegatecall + revert UUPSUnauthorizedCallContext(); + } + } + /** * @dev Function that should revert when `msg.sender` is not authorized to upgrade the contract. Called by * {upgradeToAndCall}. From 7c02b5cab2ccca42ccb612e56d3942c6dae382cd Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Thu, 27 Jul 2023 20:37:31 +0200 Subject: [PATCH 168/182] Refactor DoubleEndedQueue (#4150) Co-authored-by: Francisco --- .changeset/strong-poems-thank.md | 5 + certora/harnesses/DoubleEndedQueueHarness.sol | 4 +- certora/run.js | 8 + certora/specs/DoubleEndedQueue.spec | 174 ++++++------------ certora/specs/helpers/helpers.spec | 3 - contracts/utils/structs/DoubleEndedQueue.sol | 68 +++---- requirements.txt | 2 +- 7 files changed, 104 insertions(+), 160 deletions(-) create mode 100644 .changeset/strong-poems-thank.md diff --git a/.changeset/strong-poems-thank.md b/.changeset/strong-poems-thank.md new file mode 100644 index 000000000..5f496de7f --- /dev/null +++ b/.changeset/strong-poems-thank.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': major +--- + +`DoubleEndedQueue`: refactor internal structure to use `uint128` instead of `int128`. This has no effect on the library interface. diff --git a/certora/harnesses/DoubleEndedQueueHarness.sol b/certora/harnesses/DoubleEndedQueueHarness.sol index 35dd4a54a..c6a800726 100644 --- a/certora/harnesses/DoubleEndedQueueHarness.sol +++ b/certora/harnesses/DoubleEndedQueueHarness.sol @@ -29,11 +29,11 @@ contract DoubleEndedQueueHarness { _deque.clear(); } - function begin() external view returns (int128) { + function begin() external view returns (uint128) { return _deque._begin; } - function end() external view returns (int128) { + function end() external view returns (uint128) { return _deque._end; } diff --git a/certora/run.js b/certora/run.js index fdee42d2e..68f34aab2 100755 --- a/certora/run.js +++ b/certora/run.js @@ -28,6 +28,11 @@ const argv = require('yargs') type: 'number', default: 4, }, + verbose: { + alias: 'v', + type: 'count', + default: 0, + }, options: { alias: 'o', type: 'array', @@ -65,6 +70,9 @@ for (const { spec, contract, files, options = [] } of specs) { // Run certora, aggregate the output and print it at the end async function runCertora(spec, contract, files, options = []) { const args = [...files, '--verify', `${contract}:certora/specs/${spec}.spec`, ...options]; + if (argv.verbose) { + console.log('Running:', args.join(' ')); + } const child = proc.spawn('certoraRun', args); const stream = new PassThrough(); diff --git a/certora/specs/DoubleEndedQueue.spec b/certora/specs/DoubleEndedQueue.spec index 2a196772d..3b71bb4c7 100644 --- a/certora/specs/DoubleEndedQueue.spec +++ b/certora/specs/DoubleEndedQueue.spec @@ -1,64 +1,25 @@ -import "helpers/helpers.spec" +import "helpers/helpers.spec"; methods { - pushFront(bytes32) envfree - pushBack(bytes32) envfree - popFront() returns (bytes32) envfree - popBack() returns (bytes32) envfree - clear() envfree + function pushFront(bytes32) external envfree; + function pushBack(bytes32) external envfree; + function popFront() external returns (bytes32) envfree; + function popBack() external returns (bytes32) envfree; + function clear() external envfree; // exposed for FV - begin() returns (int128) envfree - end() returns (int128) envfree + function begin() external returns (uint128) envfree; + function end() external returns (uint128) envfree; // view - length() returns (uint256) envfree - empty() returns (bool) envfree - front() returns (bytes32) envfree - back() returns (bytes32) envfree - at_(uint256) returns (bytes32) envfree // at is a reserved word + function length() external returns (uint256) envfree; + function empty() external returns (bool) envfree; + function front() external returns (bytes32) envfree; + function back() external returns (bytes32) envfree; + function at_(uint256) external returns (bytes32) envfree; // at is a reserved word } -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Helpers │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ - -function min_int128() returns mathint { - return -(1 << 127); -} - -function max_int128() returns mathint { - return (1 << 127) - 1; -} - -// Could be broken in theory, but not in practice -function boundedQueue() returns bool { - return - max_int128() > to_mathint(end()) && - min_int128() < to_mathint(begin()); -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Invariant: end is larger or equal than begin │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -invariant boundariesConsistency() - end() >= begin() - filtered { f -> !f.isView } - { preserved { require boundedQueue(); } } - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Invariant: length is end minus begin │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -invariant lengthConsistency() - length() == to_mathint(end()) - to_mathint(begin()) - filtered { f -> !f.isView } - { preserved { require boundedQueue(); } } +definition full() returns bool = length() == max_uint128; /* ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ @@ -68,22 +29,19 @@ invariant lengthConsistency() invariant emptiness() empty() <=> length() == 0 filtered { f -> !f.isView } - { preserved { require boundedQueue(); } } /* ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ │ Invariant: front points to the first index and back points to the last one │ └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ */ -invariant queueEndings() - at_(length() - 1) == back() && at_(0) == front() +invariant queueFront() + at_(0) == front() + filtered { f -> !f.isView } + +invariant queueBack() + at_(require_uint256(length() - 1)) == back() filtered { f -> !f.isView } - { - preserved { - requireInvariant boundariesConsistency(); - require boundedQueue(); - } - } /* ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ @@ -91,18 +49,18 @@ invariant queueEndings() └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ */ rule pushFront(bytes32 value) { - require boundedQueue(); - uint256 lengthBefore = length(); + bool fullBefore = full(); pushFront@withrevert(value); + bool success = !lastReverted; // liveness - assert !lastReverted, "never reverts"; + assert success <=> !fullBefore, "never revert if not previously full"; // effect - assert front() == value, "front set to value"; - assert length() == lengthBefore + 1, "queue extended"; + assert success => front() == value, "front set to value"; + assert success => to_mathint(length()) == lengthBefore + 1, "queue extended"; } /* @@ -111,15 +69,13 @@ rule pushFront(bytes32 value) { └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ */ rule pushFrontConsistency(uint256 index) { - require boundedQueue(); - bytes32 beforeAt = at_(index); bytes32 value; pushFront(value); // try to read value - bytes32 afterAt = at_@withrevert(index + 1); + bytes32 afterAt = at_@withrevert(require_uint256(index + 1)); assert !lastReverted, "value still there"; assert afterAt == beforeAt, "data is preserved"; @@ -131,18 +87,18 @@ rule pushFrontConsistency(uint256 index) { └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ */ rule pushBack(bytes32 value) { - require boundedQueue(); - uint256 lengthBefore = length(); + bool fullBefore = full(); pushBack@withrevert(value); + bool success = !lastReverted; // liveness - assert !lastReverted, "never reverts"; + assert success <=> !fullBefore, "never revert if not previously full"; // effect - assert back() == value, "back set to value"; - assert length() == lengthBefore + 1, "queue increased"; + assert success => back() == value, "back set to value"; + assert success => to_mathint(length()) == lengthBefore + 1, "queue increased"; } /* @@ -151,8 +107,6 @@ rule pushBack(bytes32 value) { └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ */ rule pushBackConsistency(uint256 index) { - require boundedQueue(); - bytes32 beforeAt = at_(index); bytes32 value; @@ -171,9 +125,6 @@ rule pushBackConsistency(uint256 index) { └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ */ rule popFront { - requireInvariant boundariesConsistency(); - require boundedQueue(); - uint256 lengthBefore = length(); bytes32 frontBefore = front@withrevert(); @@ -185,7 +136,7 @@ rule popFront { // effect assert success => frontBefore == popped, "previous front is returned"; - assert success => length() == lengthBefore - 1, "queue decreased"; + assert success => to_mathint(length()) == lengthBefore - 1, "queue decreased"; } /* @@ -194,9 +145,6 @@ rule popFront { └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ */ rule popFrontConsistency(uint256 index) { - requireInvariant boundariesConsistency(); - require boundedQueue(); - // Read (any) value that is not the front (this asserts the value exists / the queue is long enough) require index > 1; bytes32 before = at_(index); @@ -204,7 +152,7 @@ rule popFrontConsistency(uint256 index) { popFront(); // try to read value - bytes32 after = at_@withrevert(index - 1); + bytes32 after = at_@withrevert(require_uint256(index - 1)); assert !lastReverted, "value still exists in the queue"; assert before == after, "values are offset and not modified"; @@ -216,9 +164,6 @@ rule popFrontConsistency(uint256 index) { └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ */ rule popBack { - requireInvariant boundariesConsistency(); - require boundedQueue(); - uint256 lengthBefore = length(); bytes32 backBefore = back@withrevert(); @@ -230,7 +175,7 @@ rule popBack { // effect assert success => backBefore == popped, "previous back is returned"; - assert success => length() == lengthBefore - 1, "queue decreased"; + assert success => to_mathint(length()) == lengthBefore - 1, "queue decreased"; } /* @@ -239,11 +184,8 @@ rule popBack { └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ */ rule popBackConsistency(uint256 index) { - requireInvariant boundariesConsistency(); - require boundedQueue(); - // Read (any) value that is not the back (this asserts the value exists / the queue is long enough) - require index < length() - 1; + require to_mathint(index) < length() - 1; bytes32 before = at_(index); popBack(); @@ -275,24 +217,25 @@ rule clear { │ Rule: front/back access reverts only if the queue is empty or querying out of bounds │ └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ */ -rule onlyEmptyRevert(env e) { +rule onlyEmptyOrFullRevert(env e) { require nonpayable(e); - requireInvariant boundariesConsistency(); - require boundedQueue(); method f; calldataarg args; bool emptyBefore = empty(); + bool fullBefore = full(); f@withrevert(e, args); assert lastReverted => ( - (f.selector == front().selector && emptyBefore) || - (f.selector == back().selector && emptyBefore) || - (f.selector == popFront().selector && emptyBefore) || - (f.selector == popBack().selector && emptyBefore) || - f.selector == at_(uint256).selector // revert conditions are verified in onlyOutOfBoundsRevert + (f.selector == sig:front().selector && emptyBefore) || + (f.selector == sig:back().selector && emptyBefore) || + (f.selector == sig:popFront().selector && emptyBefore) || + (f.selector == sig:popBack().selector && emptyBefore) || + (f.selector == sig:pushFront(bytes32).selector && fullBefore ) || + (f.selector == sig:pushBack(bytes32).selector && fullBefore ) || + f.selector == sig:at_(uint256).selector // revert conditions are verified in onlyOutOfBoundsRevert ), "only revert if empty or out of bounds"; } @@ -302,9 +245,6 @@ rule onlyEmptyRevert(env e) { └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ */ rule onlyOutOfBoundsRevert(uint256 index) { - requireInvariant boundariesConsistency(); - require boundedQueue(); - at_@withrevert(index); assert lastReverted <=> index >= length(), "only reverts if index is out of bounds"; @@ -316,9 +256,6 @@ rule onlyOutOfBoundsRevert(uint256 index) { └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ */ rule noLengthChange(env e) { - requireInvariant boundariesConsistency(); - require boundedQueue(); - method f; calldataarg args; @@ -327,11 +264,11 @@ rule noLengthChange(env e) { uint256 lengthAfter = length(); assert lengthAfter != lengthBefore => ( - (f.selector == pushFront(bytes32).selector && lengthAfter == lengthBefore + 1) || - (f.selector == pushBack(bytes32).selector && lengthAfter == lengthBefore + 1) || - (f.selector == popBack().selector && lengthAfter == lengthBefore - 1) || - (f.selector == popFront().selector && lengthAfter == lengthBefore - 1) || - (f.selector == clear().selector && lengthAfter == 0) + (f.selector == sig:pushFront(bytes32).selector && to_mathint(lengthAfter) == lengthBefore + 1) || + (f.selector == sig:pushBack(bytes32).selector && to_mathint(lengthAfter) == lengthBefore + 1) || + (f.selector == sig:popBack().selector && to_mathint(lengthAfter) == lengthBefore - 1) || + (f.selector == sig:popFront().selector && to_mathint(lengthAfter) == lengthBefore - 1) || + (f.selector == sig:clear().selector && lengthAfter == 0) ), "length is only affected by clear/pop/push operations"; } @@ -341,9 +278,6 @@ rule noLengthChange(env e) { └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ */ rule noDataChange(env e) { - requireInvariant boundariesConsistency(); - require boundedQueue(); - method f; calldataarg args; @@ -354,13 +288,13 @@ rule noDataChange(env e) { bool atAfterSuccess = !lastReverted; assert !atAfterSuccess <=> ( - f.selector == clear().selector || - (f.selector == popBack().selector && index == length()) || - (f.selector == popFront().selector && index == length()) + (f.selector == sig:clear().selector ) || + (f.selector == sig:popBack().selector && index == length()) || + (f.selector == sig:popFront().selector && index == length()) ), "indexes of the queue are only removed by clear or pop"; assert atAfterSuccess && atAfter != atBefore => ( - f.selector == popFront().selector || - f.selector == pushFront(bytes32).selector + f.selector == sig:popFront().selector || + f.selector == sig:pushFront(bytes32).selector ), "values of the queue are only changed by popFront or pushFront"; } diff --git a/certora/specs/helpers/helpers.spec b/certora/specs/helpers/helpers.spec index 04e76df94..a6c1e2302 100644 --- a/certora/specs/helpers/helpers.spec +++ b/certora/specs/helpers/helpers.spec @@ -2,9 +2,6 @@ definition nonpayable(env e) returns bool = e.msg.value == 0; definition nonzerosender(env e) returns bool = e.msg.sender != 0; -// constants -definition max_uint48() returns mathint = (1 << 48) - 1; - // math definition min(mathint a, mathint b) returns mathint = a < b ? a : b; definition max(mathint a, mathint b) returns mathint = a > b ? a : b; diff --git a/contracts/utils/structs/DoubleEndedQueue.sol b/contracts/utils/structs/DoubleEndedQueue.sol index 30d953239..928665bee 100644 --- a/contracts/utils/structs/DoubleEndedQueue.sol +++ b/contracts/utils/structs/DoubleEndedQueue.sol @@ -2,8 +2,6 @@ // OpenZeppelin Contracts (last updated v4.9.0) (utils/structs/DoubleEndedQueue.sol) pragma solidity ^0.8.19; -import {SafeCast} from "../math/SafeCast.sol"; - /** * @dev A sequence of items with the ability to efficiently push and pop items (i.e. insert and remove) on both ends of * the sequence (called front and back). Among other access patterns, it can be used to implement efficient LIFO and @@ -22,6 +20,11 @@ library DoubleEndedQueue { */ error QueueEmpty(); + /** + * @dev A push operation couldn't be completed due to the queue being full. + */ + error QueueFull(); + /** * @dev An operation (e.g. {at}) couldn't be completed due to an index being out of bounds. */ @@ -40,18 +43,19 @@ library DoubleEndedQueue { * data[end - 1]. */ struct Bytes32Deque { - int128 _begin; - int128 _end; - mapping(int128 => bytes32) _data; + uint128 _begin; + uint128 _end; + mapping(uint128 => bytes32) _data; } /** * @dev Inserts an item at the end of the queue. */ function pushBack(Bytes32Deque storage deque, bytes32 value) internal { - int128 backIndex = deque._end; - deque._data[backIndex] = value; unchecked { + uint128 backIndex = deque._end; + if (backIndex + 1 == deque._begin) revert QueueFull(); + deque._data[backIndex] = value; deque._end = backIndex + 1; } } @@ -62,26 +66,26 @@ library DoubleEndedQueue { * Reverts with `QueueEmpty` if the queue is empty. */ function popBack(Bytes32Deque storage deque) internal returns (bytes32 value) { - if (empty(deque)) revert QueueEmpty(); - int128 backIndex; unchecked { - backIndex = deque._end - 1; + uint128 backIndex = deque._end; + if (backIndex == deque._begin) revert QueueEmpty(); + --backIndex; + value = deque._data[backIndex]; + delete deque._data[backIndex]; + deque._end = backIndex; } - value = deque._data[backIndex]; - delete deque._data[backIndex]; - deque._end = backIndex; } /** * @dev Inserts an item at the beginning of the queue. */ function pushFront(Bytes32Deque storage deque, bytes32 value) internal { - int128 frontIndex; unchecked { - frontIndex = deque._begin - 1; + uint128 frontIndex = deque._begin - 1; + if (frontIndex == deque._end) revert QueueFull(); + deque._data[frontIndex] = value; + deque._begin = frontIndex; } - deque._data[frontIndex] = value; - deque._begin = frontIndex; } /** @@ -90,11 +94,11 @@ library DoubleEndedQueue { * Reverts with `QueueEmpty` if the queue is empty. */ function popFront(Bytes32Deque storage deque) internal returns (bytes32 value) { - if (empty(deque)) revert QueueEmpty(); - int128 frontIndex = deque._begin; - value = deque._data[frontIndex]; - delete deque._data[frontIndex]; unchecked { + uint128 frontIndex = deque._begin; + if (frontIndex == deque._end) revert QueueEmpty(); + value = deque._data[frontIndex]; + delete deque._data[frontIndex]; deque._begin = frontIndex + 1; } } @@ -106,8 +110,7 @@ library DoubleEndedQueue { */ function front(Bytes32Deque storage deque) internal view returns (bytes32 value) { if (empty(deque)) revert QueueEmpty(); - int128 frontIndex = deque._begin; - return deque._data[frontIndex]; + return deque._data[deque._begin]; } /** @@ -117,11 +120,9 @@ library DoubleEndedQueue { */ function back(Bytes32Deque storage deque) internal view returns (bytes32 value) { if (empty(deque)) revert QueueEmpty(); - int128 backIndex; unchecked { - backIndex = deque._end - 1; + return deque._data[deque._end - 1]; } - return deque._data[backIndex]; } /** @@ -131,10 +132,11 @@ library DoubleEndedQueue { * Reverts with `QueueOutOfBounds` if the index is out of bounds. */ function at(Bytes32Deque storage deque, uint256 index) internal view returns (bytes32 value) { - // int256(deque._begin) is a safe upcast - int128 idx = SafeCast.toInt128(int256(deque._begin) + SafeCast.toInt256(index)); - if (idx >= deque._end) revert QueueOutOfBounds(); - return deque._data[idx]; + if (index >= length(deque)) revert QueueOutOfBounds(); + // By construction, length is a uint128, so the check above ensures that index can be safely downcast to uint128. + unchecked { + return deque._data[deque._begin + uint128(index)]; + } } /** @@ -152,10 +154,8 @@ library DoubleEndedQueue { * @dev Returns the number of items in the queue. */ function length(Bytes32Deque storage deque) internal view returns (uint256) { - // The interface preserves the invariant that begin <= end so we assume this will not overflow. - // We also assume there are at most int256.max items in the queue. unchecked { - return uint256(int256(deque._end) - int256(deque._begin)); + return uint256(deque._end - deque._begin); } } @@ -163,6 +163,6 @@ library DoubleEndedQueue { * @dev Returns true if the queue is empty. */ function empty(Bytes32Deque storage deque) internal view returns (bool) { - return deque._end <= deque._begin; + return deque._end == deque._begin; } } diff --git a/requirements.txt b/requirements.txt index da3e95766..b92a2728d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1 @@ -certora-cli==3.6.4 +certora-cli==4.3.1 From 9445f96223041abf2bf08daa56f8da50b674cbcd Mon Sep 17 00:00:00 2001 From: Francisco Date: Thu, 27 Jul 2023 22:30:41 +0200 Subject: [PATCH 169/182] Adjust ERC2771Context._msgData for msg.data.length < 20 (#4484) --- .changeset/warm-guests-rule.md | 5 +++++ contracts/metatx/ERC2771Context.sol | 2 +- contracts/mocks/ContextMock.sol | 6 ++++++ test/metatx/ERC2771Context.test.js | 18 ++++++++++++++---- 4 files changed, 26 insertions(+), 5 deletions(-) create mode 100644 .changeset/warm-guests-rule.md diff --git a/.changeset/warm-guests-rule.md b/.changeset/warm-guests-rule.md new file mode 100644 index 000000000..049da4dd5 --- /dev/null +++ b/.changeset/warm-guests-rule.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': patch +--- + +`ERC2771Context`: Prevent revert in `_msgData()` when a call originating from a trusted forwarder is not long enough to contain the request signer address (i.e. `msg.data.length` is less than 20 bytes). Return the full calldata in that case. diff --git a/contracts/metatx/ERC2771Context.sol b/contracts/metatx/ERC2771Context.sol index 9ade6148d..2c4d89f94 100644 --- a/contracts/metatx/ERC2771Context.sol +++ b/contracts/metatx/ERC2771Context.sol @@ -34,7 +34,7 @@ abstract contract ERC2771Context is Context { } function _msgData() internal view virtual override returns (bytes calldata) { - if (isTrustedForwarder(msg.sender)) { + if (isTrustedForwarder(msg.sender) && msg.data.length >= 20) { return msg.data[:msg.data.length - 20]; } else { return super._msgData(); diff --git a/contracts/mocks/ContextMock.sol b/contracts/mocks/ContextMock.sol index fd105aa91..fb57535f7 100644 --- a/contracts/mocks/ContextMock.sol +++ b/contracts/mocks/ContextMock.sol @@ -16,6 +16,12 @@ contract ContextMock is Context { function msgData(uint256 integerValue, string memory stringValue) public { emit Data(_msgData(), integerValue, stringValue); } + + event DataShort(bytes data); + + function msgDataShort() public { + emit DataShort(_msgData()); + } } contract ContextMockCaller { diff --git a/test/metatx/ERC2771Context.test.js b/test/metatx/ERC2771Context.test.js index a907e502c..0ec8d98dd 100644 --- a/test/metatx/ERC2771Context.test.js +++ b/test/metatx/ERC2771Context.test.js @@ -12,7 +12,7 @@ const ContextMockCaller = artifacts.require('ContextMockCaller'); const { shouldBehaveLikeRegularContext } = require('../utils/Context.behavior'); contract('ERC2771Context', function (accounts) { - const [, anotherAccount] = accounts; + const [, trustedForwarder] = accounts; const MAX_UINT48 = web3.utils.toBN(1).shln(48).subn(1).toString(); @@ -84,11 +84,11 @@ contract('ERC2771Context', function (accounts) { it('returns the original sender when calldata length is less than 20 bytes (address length)', async function () { // The forwarder doesn't produce calls with calldata length less than 20 bytes - const recipient = await ERC2771ContextMock.new(anotherAccount); + const recipient = await ERC2771ContextMock.new(trustedForwarder); - const { receipt } = await recipient.msgSender({ from: anotherAccount }); + const { receipt } = await recipient.msgSender({ from: trustedForwarder }); - await expectEvent(receipt, 'Sender', { sender: anotherAccount }); + await expectEvent(receipt, 'Sender', { sender: trustedForwarder }); }); }); @@ -117,5 +117,15 @@ contract('ERC2771Context', function (accounts) { await expectEvent.inTransaction(tx, ERC2771ContextMock, 'Data', { data, integerValue, stringValue }); }); }); + + it('returns the full original data when calldata length is less than 20 bytes (address length)', async function () { + // The forwarder doesn't produce calls with calldata length less than 20 bytes + const recipient = await ERC2771ContextMock.new(trustedForwarder); + + const { receipt } = await recipient.msgDataShort({ from: trustedForwarder }); + + const data = recipient.contract.methods.msgDataShort().encodeABI(); + await expectEvent(receipt, 'DataShort', { data }); + }); }); }); From 02ea01765a9964541dd9cdcffc4a7f8b403c2ff6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Thu, 27 Jul 2023 17:18:45 -0600 Subject: [PATCH 170/182] Add custom errors to docs (#4480) --- contracts/access/AccessControl.sol | 10 +++++----- contracts/interfaces/README.adoc | 9 +++++++++ contracts/interfaces/draft-IERC6093.sol | 9 +++------ docs/templates/contract.hbs | 26 +++++++++++++++++++++++++ docs/templates/properties.js | 4 ++++ 5 files changed, 47 insertions(+), 11 deletions(-) diff --git a/contracts/access/AccessControl.sol b/contracts/access/AccessControl.sol index 99099dd1f..bc81c4609 100644 --- a/contracts/access/AccessControl.sol +++ b/contracts/access/AccessControl.sol @@ -58,8 +58,7 @@ abstract contract AccessControl is Context, IAccessControl, ERC165 { /** * @dev Modifier that checks that an account has a specific role. Reverts - * with a custom error including the required role. - * + * with an {AccessControlUnauthorizedAccount} error including the required role. */ modifier onlyRole(bytes32 role) { _checkRole(role); @@ -81,15 +80,16 @@ abstract contract AccessControl is Context, IAccessControl, ERC165 { } /** - * @dev Revert with a custom error if `_msgSender()` is missing `role`. - * Overriding this function changes the behavior of the {onlyRole} modifier. + * @dev Reverts with an {AccessControlUnauthorizedAccount} error if `_msgSender()` + * is missing `role`. Overriding this function changes the behavior of the {onlyRole} modifier. */ function _checkRole(bytes32 role) internal view virtual { _checkRole(role, _msgSender()); } /** - * @dev Revert with a custom error if `account` is missing `role`. + * @dev Reverts with an {AccessControlUnauthorizedAccount} error if `account` + * is missing `role`. */ function _checkRole(bytes32 role, address account) internal view virtual { if (!hasRole(role, account)) { diff --git a/contracts/interfaces/README.adoc b/contracts/interfaces/README.adoc index 4525bc9a2..379a24a1e 100644 --- a/contracts/interfaces/README.adoc +++ b/contracts/interfaces/README.adoc @@ -8,18 +8,21 @@ These interfaces are available as `.sol` files, and also as compiler `.json` ABI are useful to interact with third party contracts that implement them. - {IERC20} +- {IERC20Errors} - {IERC20Metadata} - {IERC165} - {IERC721} - {IERC721Receiver} - {IERC721Enumerable} - {IERC721Metadata} +- {IERC721Errors} - {IERC777} - {IERC777Recipient} - {IERC777Sender} - {IERC1155} - {IERC1155Receiver} - {IERC1155MetadataURI} +- {IERC1155Errors} - {IERC1271} - {IERC1363} - {IERC1363Receiver} @@ -40,6 +43,12 @@ are useful to interact with third party contracts that implement them. == Detailed ABI +{{IERC20Errors}} + +{{IERC721Errors}} + +{{IERC1155Errors}} + {{IERC1271}} {{IERC1363}} diff --git a/contracts/interfaces/draft-IERC6093.sol b/contracts/interfaces/draft-IERC6093.sol index 08e77553c..3c3900852 100644 --- a/contracts/interfaces/draft-IERC6093.sol +++ b/contracts/interfaces/draft-IERC6093.sol @@ -3,8 +3,7 @@ pragma solidity ^0.8.19; /** * @dev Standard ERC20 Errors - * Interface of the ERC6093 custom errors for ERC20 tokens - * as defined in https://eips.ethereum.org/EIPS/eip-6093 + * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC20 tokens. */ interface IERC20Errors { /** @@ -50,8 +49,7 @@ interface IERC20Errors { /** * @dev Standard ERC721 Errors - * Interface of the ERC6093 custom errors for ERC721 tokens - * as defined in https://eips.ethereum.org/EIPS/eip-6093 + * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC721 tokens. */ interface IERC721Errors { /** @@ -109,8 +107,7 @@ interface IERC721Errors { /** * @dev Standard ERC1155 Errors - * Interface of the ERC6093 custom errors for ERC1155 tokens - * as defined in https://eips.ethereum.org/EIPS/eip-6093 + * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC1155 tokens. */ interface IERC1155Errors { /** diff --git a/docs/templates/contract.hbs b/docs/templates/contract.hbs index d97e7fd0d..a4716825a 100644 --- a/docs/templates/contract.hbs +++ b/docs/templates/contract.hbs @@ -57,6 +57,23 @@ import "@openzeppelin/{{__item_context.file.absolutePath}}"; -- {{/if}} +{{#if has-errors}} +[.contract-index] +.Errors +-- +{{#each inheritance}} +{{#unless @first}} +[.contract-subindex-inherited] +.{{name}} +{{/unless}} +{{#each errors}} +* {xref-{{anchor~}} }[`++{{name}}({{names params}})++`] +{{/each}} + +{{/each}} +-- +{{/if}} + {{#each modifiers}} [.contract-item] [[{{anchor}}]] @@ -83,3 +100,12 @@ import "@openzeppelin/{{__item_context.file.absolutePath}}"; {{{natspec.dev}}} {{/each}} + +{{#each errors}} +[.contract-item] +[[{{anchor}}]] +==== `[.contract-item-name]#++{{name}}++#++({{typed-params params}})++` [.item-kind]#error# + +{{{natspec.dev}}} + +{{/each}} diff --git a/docs/templates/properties.js b/docs/templates/properties.js index 99bdf88b2..584c0abca 100644 --- a/docs/templates/properties.js +++ b/docs/templates/properties.js @@ -35,6 +35,10 @@ module.exports['has-events'] = function ({ item }) { return item.inheritance.some(c => c.events.length > 0); }; +module.exports['has-errors'] = function ({ item }) { + return item.inheritance.some(c => c.errors.length > 0); +}; + module.exports['inherited-functions'] = function ({ item }) { const { inheritance } = item; const baseFunctions = new Set(inheritance.flatMap(c => c.functions.flatMap(f => f.baseFunctions ?? []))); From aed5720a0177ddd36e2923ecedb1759205ee6014 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Fri, 28 Jul 2023 18:58:30 -0600 Subject: [PATCH 171/182] Avoid `returndatacopy` in ERC2771Forwarder by calling via assembly (#4458) --- contracts/metatx/ERC2771Context.sol | 6 ++ contracts/metatx/ERC2771Forwarder.sol | 16 +++- test/metatx/ERC2771Forwarder.test.js | 112 +++++++++++++++++++++++--- 3 files changed, 117 insertions(+), 17 deletions(-) diff --git a/contracts/metatx/ERC2771Context.sol b/contracts/metatx/ERC2771Context.sol index 2c4d89f94..406a62bd6 100644 --- a/contracts/metatx/ERC2771Context.sol +++ b/contracts/metatx/ERC2771Context.sol @@ -7,6 +7,12 @@ import {Context} from "../utils/Context.sol"; /** * @dev Context variant with ERC2771 support. + * + * WARNING: Avoid using this pattern in contracts that rely in a specific calldata length as they'll + * be affected by any forwarder whose `msg.data` is suffixed with the `from` address according to the ERC2771 + * specification adding the address size in bytes (20) to the calldata size. An example of an unexpected + * behavior could be an unintended fallback (or another function) invocation while trying to invoke the `receive` + * function only accessible if `msg.data.length == 0`. */ abstract contract ERC2771Context is Context { /// @custom:oz-upgrades-unsafe-allow state-variable-immutable diff --git a/contracts/metatx/ERC2771Forwarder.sol b/contracts/metatx/ERC2771Forwarder.sol index 84d736c74..125f80076 100644 --- a/contracts/metatx/ERC2771Forwarder.sol +++ b/contracts/metatx/ERC2771Forwarder.sol @@ -251,11 +251,19 @@ contract ERC2771Forwarder is EIP712, Nonces { // Nonce should be used before the call to prevent reusing by reentrancy uint256 currentNonce = _useNonce(signer); - (success, ) = request.to.call{gas: request.gas, value: request.value}( - abi.encodePacked(request.data, request.from) - ); + uint256 reqGas = request.gas; + address to = request.to; + uint256 value = request.value; + bytes memory data = abi.encodePacked(request.data, request.from); - _checkForwardedGas(gasleft(), request); + uint256 gasLeft; + + assembly { + success := call(reqGas, to, value, add(data, 0x20), mload(data), 0, 0) + gasLeft := gas() + } + + _checkForwardedGas(gasLeft, request); emit ExecutedForwardRequest(signer, currentNonce, success); } diff --git a/test/metatx/ERC2771Forwarder.test.js b/test/metatx/ERC2771Forwarder.test.js index fa84ccdc3..26726e8df 100644 --- a/test/metatx/ERC2771Forwarder.test.js +++ b/test/metatx/ERC2771Forwarder.test.js @@ -41,12 +41,13 @@ contract('ERC2771Forwarder', function (accounts) { this.alice.address = web3.utils.toChecksumAddress(this.alice.getAddressString()); this.timestamp = await time.latest(); + this.receiver = await CallReceiverMock.new(); this.request = { from: this.alice.address, - to: constants.ZERO_ADDRESS, + to: this.receiver.address, value: '0', gas: '100000', - data: '0x', + data: this.receiver.contract.methods.mockFunction().encodeABI(), deadline: this.timestamp.toNumber() + 60, // 1 minute }; this.requestData = { @@ -64,6 +65,14 @@ contract('ERC2771Forwarder', function (accounts) { ethSigUtil.signTypedMessage(privateKey, { data: this.forgeData(request), }); + this.estimateRequest = request => + web3.eth.estimateGas({ + from: this.forwarder.address, + to: request.to, + data: web3.utils.encodePacked({ value: request.data, type: 'bytes' }, { value: request.from, type: 'address' }), + value: request.value, + gas: request.gas, + }); this.requestData.signature = this.sign(this.alice.getPrivateKey()); }); @@ -125,7 +134,8 @@ contract('ERC2771Forwarder', function (accounts) { it('emits an event and consumes nonce for a successful request', async function () { const receipt = await this.forwarder.execute(this.requestData); - expectEvent(receipt, 'ExecutedForwardRequest', { + await expectEvent.inTransaction(receipt.tx, this.receiver, 'MockFunctionCalled'); + await expectEvent.inTransaction(receipt.tx, this.forwarder, 'ExecutedForwardRequest', { signer: this.requestData.from, nonce: web3.utils.toBN(this.requestData.nonce), success: true, @@ -136,11 +146,9 @@ contract('ERC2771Forwarder', function (accounts) { }); it('reverts with an unsuccessful request', async function () { - const receiver = await CallReceiverMock.new(); const req = { ...this.requestData, - to: receiver.address, - data: receiver.contract.methods.mockFunctionRevertsNoReason().encodeABI(), + data: this.receiver.contract.methods.mockFunctionRevertsNoReason().encodeABI(), }; req.signature = this.sign(this.alice.getPrivateKey(), req); await expectRevertCustomError(this.forwarder.execute(req), 'FailedInnerCall', []); @@ -208,14 +216,11 @@ contract('ERC2771Forwarder', function (accounts) { }); it('bubbles out of gas', async function () { - const receiver = await CallReceiverMock.new(); - const gasAvailable = 100000; - this.requestData.to = receiver.address; - this.requestData.data = receiver.contract.methods.mockFunctionOutOfGas().encodeABI(); - this.requestData.gas = 1000000; - + this.requestData.data = this.receiver.contract.methods.mockFunctionOutOfGas().encodeABI(); + this.requestData.gas = 1_000_000; this.requestData.signature = this.sign(this.alice.getPrivateKey()); + const gasAvailable = 100_000; await expectRevert.assertion(this.forwarder.execute(this.requestData, { gas: gasAvailable })); const { transactions } = await web3.eth.getBlock('latest'); @@ -223,6 +228,32 @@ contract('ERC2771Forwarder', function (accounts) { expect(gasUsed).to.be.equal(gasAvailable); }); + + it('bubbles out of gas forced by the relayer', async function () { + // If there's an incentive behind executing requests, a malicious relayer could grief + // the forwarder by executing requests and providing a top-level call gas limit that + // is too low to successfully finish the request after the 63/64 rule. + + // We set the baseline to the gas limit consumed by a successful request if it was executed + // normally. Note this includes the 21000 buffer that also the relayer will be charged to + // start a request execution. + const estimate = await this.estimateRequest(this.request); + + // Because the relayer call consumes gas until the `CALL` opcode, the gas left after failing + // the subcall won't enough to finish the top level call (after testing), so we add a + // moderated buffer. + const gasAvailable = estimate + 2_000; + + // The subcall out of gas should be caught by the contract and then bubbled up consuming + // the available gas with an `invalid` opcode. + await expectRevert.outOfGas(this.forwarder.execute(this.requestData, { gas: gasAvailable })); + + const { transactions } = await web3.eth.getBlock('latest'); + const { gasUsed } = await web3.eth.getTransactionReceipt(transactions[0]); + + // We assert that indeed the gas was totally consumed. + expect(gasUsed).to.be.equal(gasAvailable); + }); }); context('executeBatch', function () { @@ -252,6 +283,14 @@ contract('ERC2771Forwarder', function (accounts) { })); this.msgValue = batchValue(this.requestDatas); + + this.gasUntil = async reqIdx => { + const gas = 0; + const estimations = await Promise.all( + new Array(reqIdx + 1).fill().map((_, idx) => this.estimateRequest(this.requestDatas[idx])), + ); + return estimations.reduce((acc, estimation) => acc + estimation, gas); + }; }); context('with valid requests', function () { @@ -265,7 +304,8 @@ contract('ERC2771Forwarder', function (accounts) { it('emits events', async function () { for (const request of this.requestDatas) { - expectEvent(this.receipt, 'ExecutedForwardRequest', { + await expectEvent.inTransaction(this.receipt.tx, this.receiver, 'MockFunctionCalled'); + await expectEvent.inTransaction(this.receipt.tx, this.forwarder, 'ExecutedForwardRequest', { signer: request.from, nonce: web3.utils.toBN(request.nonce), success: true, @@ -428,6 +468,52 @@ contract('ERC2771Forwarder', function (accounts) { ); }); }); + + it('bubbles out of gas', async function () { + this.requestDatas[this.idx].data = this.receiver.contract.methods.mockFunctionOutOfGas().encodeABI(); + this.requestDatas[this.idx].gas = 1_000_000; + this.requestDatas[this.idx].signature = this.sign( + this.signers[this.idx].getPrivateKey(), + this.requestDatas[this.idx], + ); + + const gasAvailable = 300_000; + await expectRevert.assertion( + this.forwarder.executeBatch(this.requestDatas, constants.ZERO_ADDRESS, { + gas: gasAvailable, + value: this.requestDatas.reduce((acc, { value }) => acc + Number(value), 0), + }), + ); + + const { transactions } = await web3.eth.getBlock('latest'); + const { gasUsed } = await web3.eth.getTransactionReceipt(transactions[0]); + + expect(gasUsed).to.be.equal(gasAvailable); + }); + + it('bubbles out of gas forced by the relayer', async function () { + // Similarly to the single execute, a malicious relayer could grief requests. + + // We estimate until the selected request as if they were executed normally + const estimate = await this.gasUntil(this.requestDatas, this.idx); + + // We add a Buffer to account for all the gas that's used before the selected call. + // Note is slightly bigger because the selected request is not the index 0 and it affects + // the buffer needed. + const gasAvailable = estimate + 10_000; + + // The subcall out of gas should be caught by the contract and then bubbled up consuming + // the available gas with an `invalid` opcode. + await expectRevert.outOfGas( + this.forwarder.executeBatch(this.requestDatas, constants.ZERO_ADDRESS, { gas: gasAvailable }), + ); + + const { transactions } = await web3.eth.getBlock('latest'); + const { gasUsed } = await web3.eth.getTransactionReceipt(transactions[0]); + + // We assert that indeed the gas was totally consumed. + expect(gasUsed).to.be.equal(gasAvailable); + }); }); }); }); From f631d8a5f00d06d60912bcf03181aa1e700d42f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Fri, 28 Jul 2023 19:16:14 -0600 Subject: [PATCH 172/182] Improve ERC4626 fees example (#4476) Co-authored-by: Francisco Co-authored-by: Hadrien Croubois --- contracts/mocks/docs/ERC4626Fees.sol | 59 +++++++++++++-------- contracts/mocks/token/ERC4646FeesMock.sol | 20 +++---- test/token/ERC20/extensions/ERC4626.test.js | 6 +-- 3 files changed, 49 insertions(+), 36 deletions(-) diff --git a/contracts/mocks/docs/ERC4626Fees.sol b/contracts/mocks/docs/ERC4626Fees.sol index 49c1095ab..ab1151cd7 100644 --- a/contracts/mocks/docs/ERC4626Fees.sol +++ b/contracts/mocks/docs/ERC4626Fees.sol @@ -7,36 +7,41 @@ import {ERC4626} from "../../token/ERC20/extensions/ERC4626.sol"; import {SafeERC20} from "../../token/ERC20/utils/SafeERC20.sol"; import {Math} from "../../utils/math/Math.sol"; +/// @dev ERC4626 vault with entry/exit fees expressed in https://en.wikipedia.org/wiki/Basis_point[basis point (bp)]. abstract contract ERC4626Fees is ERC4626 { using Math for uint256; - /** @dev See {IERC4626-previewDeposit}. */ + uint256 private constant _BASIS_POINT_SCALE = 1e4; + + // === Overrides === /// + + /// @dev Preview taking an entry fee on deposit. See {IERC4626-previewDeposit}. function previewDeposit(uint256 assets) public view virtual override returns (uint256) { - uint256 fee = _feeOnTotal(assets, _entryFeeBasePoint()); + uint256 fee = _feeOnTotal(assets, _entryFeeBasisPoints()); return super.previewDeposit(assets - fee); } - /** @dev See {IERC4626-previewMint}. */ + /// @dev Preview adding an entry fee on mint. See {IERC4626-previewMint}. function previewMint(uint256 shares) public view virtual override returns (uint256) { uint256 assets = super.previewMint(shares); - return assets + _feeOnRaw(assets, _entryFeeBasePoint()); + return assets + _feeOnRaw(assets, _entryFeeBasisPoints()); } - /** @dev See {IERC4626-previewWithdraw}. */ + /// @dev Preview adding an exit fee on withdraw. See {IERC4626-previewWithdraw}. function previewWithdraw(uint256 assets) public view virtual override returns (uint256) { - uint256 fee = _feeOnRaw(assets, _exitFeeBasePoint()); + uint256 fee = _feeOnRaw(assets, _exitFeeBasisPoints()); return super.previewWithdraw(assets + fee); } - /** @dev See {IERC4626-previewRedeem}. */ + /// @dev Preview taking an exit fee on redeem. See {IERC4626-previewRedeem}. function previewRedeem(uint256 shares) public view virtual override returns (uint256) { uint256 assets = super.previewRedeem(shares); - return assets - _feeOnTotal(assets, _exitFeeBasePoint()); + return assets - _feeOnTotal(assets, _exitFeeBasisPoints()); } - /** @dev See {IERC4626-_deposit}. */ + /// @dev Send entry fee to {_entryFeeRecipient}. See {IERC4626-_deposit}. function _deposit(address caller, address receiver, uint256 assets, uint256 shares) internal virtual override { - uint256 fee = _feeOnTotal(assets, _entryFeeBasePoint()); + uint256 fee = _feeOnTotal(assets, _entryFeeBasisPoints()); address recipient = _entryFeeRecipient(); super._deposit(caller, receiver, assets, shares); @@ -46,7 +51,7 @@ abstract contract ERC4626Fees is ERC4626 { } } - /** @dev See {IERC4626-_deposit}. */ + /// @dev Send exit fee to {_exitFeeRecipient}. See {IERC4626-_deposit}. function _withdraw( address caller, address receiver, @@ -54,7 +59,7 @@ abstract contract ERC4626Fees is ERC4626 { uint256 assets, uint256 shares ) internal virtual override { - uint256 fee = _feeOnRaw(assets, _exitFeeBasePoint()); + uint256 fee = _feeOnRaw(assets, _exitFeeBasisPoints()); address recipient = _exitFeeRecipient(); super._withdraw(caller, receiver, owner, assets, shares); @@ -64,27 +69,35 @@ abstract contract ERC4626Fees is ERC4626 { } } - function _entryFeeBasePoint() internal view virtual returns (uint256) { - return 0; + // === Fee configuration === /// + + function _entryFeeBasisPoints() internal view virtual returns (uint256) { + return 0; // replace with e.g. 1_000 for 1% } - function _entryFeeRecipient() internal view virtual returns (address) { - return address(0); + function _exitFeeBasisPoints() internal view virtual returns (uint256) { + return 0; // replace with e.g. 1_000 for 1% } - function _exitFeeBasePoint() internal view virtual returns (uint256) { - return 0; + function _entryFeeRecipient() internal view virtual returns (address) { + return address(0); // replace with e.g. a treasury address } function _exitFeeRecipient() internal view virtual returns (address) { - return address(0); + return address(0); // replace with e.g. a treasury address } - function _feeOnRaw(uint256 assets, uint256 feeBasePoint) private pure returns (uint256) { - return assets.mulDiv(feeBasePoint, 1e5, Math.Rounding.Ceil); + // === Fee operations === /// + + /// @dev Calculates the fees that should be added to an amount `assets` that does not already include fees. + /// Used in {IERC4626-mint} and {IERC4626-withdraw} operations. + function _feeOnRaw(uint256 assets, uint256 feeBasisPoints) private pure returns (uint256) { + return assets.mulDiv(feeBasisPoints, _BASIS_POINT_SCALE, Math.Rounding.Ceil); } - function _feeOnTotal(uint256 assets, uint256 feeBasePoint) private pure returns (uint256) { - return assets.mulDiv(feeBasePoint, feeBasePoint + 1e5, Math.Rounding.Ceil); + /// @dev Calculates the fee part of an amount `assets` that already includes fees. + /// Used in {IERC4626-deposit} and {IERC4626-redeem} operations. + function _feeOnTotal(uint256 assets, uint256 feeBasisPoints) private pure returns (uint256) { + return assets.mulDiv(feeBasisPoints, feeBasisPoints + _BASIS_POINT_SCALE, Math.Rounding.Ceil); } } diff --git a/contracts/mocks/token/ERC4646FeesMock.sol b/contracts/mocks/token/ERC4646FeesMock.sol index f8fde293c..081b03a61 100644 --- a/contracts/mocks/token/ERC4646FeesMock.sol +++ b/contracts/mocks/token/ERC4646FeesMock.sol @@ -5,33 +5,33 @@ pragma solidity ^0.8.19; import {ERC4626Fees} from "../docs/ERC4626Fees.sol"; abstract contract ERC4626FeesMock is ERC4626Fees { - uint256 private immutable _entryFeeBasePointValue; + uint256 private immutable _entryFeeBasisPointValue; address private immutable _entryFeeRecipientValue; - uint256 private immutable _exitFeeBasePointValue; + uint256 private immutable _exitFeeBasisPointValue; address private immutable _exitFeeRecipientValue; constructor( - uint256 entryFeeBasePoint, + uint256 entryFeeBasisPoints, address entryFeeRecipient, - uint256 exitFeeBasePoint, + uint256 exitFeeBasisPoints, address exitFeeRecipient ) { - _entryFeeBasePointValue = entryFeeBasePoint; + _entryFeeBasisPointValue = entryFeeBasisPoints; _entryFeeRecipientValue = entryFeeRecipient; - _exitFeeBasePointValue = exitFeeBasePoint; + _exitFeeBasisPointValue = exitFeeBasisPoints; _exitFeeRecipientValue = exitFeeRecipient; } - function _entryFeeBasePoint() internal view virtual override returns (uint256) { - return _entryFeeBasePointValue; + function _entryFeeBasisPoints() internal view virtual override returns (uint256) { + return _entryFeeBasisPointValue; } function _entryFeeRecipient() internal view virtual override returns (address) { return _entryFeeRecipientValue; } - function _exitFeeBasePoint() internal view virtual override returns (uint256) { - return _exitFeeBasePointValue; + function _exitFeeBasisPoints() internal view virtual override returns (uint256) { + return _exitFeeBasisPointValue; } function _exitFeeRecipient() internal view virtual override returns (address) { diff --git a/test/token/ERC20/extensions/ERC4626.test.js b/test/token/ERC20/extensions/ERC4626.test.js index aead1d50a..499cf0628 100644 --- a/test/token/ERC20/extensions/ERC4626.test.js +++ b/test/token/ERC20/extensions/ERC4626.test.js @@ -737,9 +737,9 @@ contract('ERC4626', function (accounts) { } describe('ERC4626Fees', function () { - const feeBasePoint = web3.utils.toBN(5e3); + const feeBasisPoints = web3.utils.toBN(5e3); const valueWithoutFees = web3.utils.toBN(10000); - const fees = valueWithoutFees.mul(feeBasePoint).divn(1e5); + const fees = valueWithoutFees.mul(feeBasisPoints).divn(1e4); const valueWithFees = valueWithoutFees.add(fees); describe('input fees', function () { @@ -749,7 +749,7 @@ contract('ERC4626', function (accounts) { name + ' Vault', symbol + 'V', this.token.address, - feeBasePoint, + feeBasisPoints, other, 0, constants.ZERO_ADDRESS, From d6b63a48ba440ad8d551383697db6e5b0ef84137 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Sat, 29 Jul 2023 17:30:47 +0200 Subject: [PATCH 173/182] Fix issues in the ERC4646Fee documentation (#4487) --- contracts/mocks/docs/ERC4626Fees.sol | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/contracts/mocks/docs/ERC4626Fees.sol b/contracts/mocks/docs/ERC4626Fees.sol index ab1151cd7..e7501e9b4 100644 --- a/contracts/mocks/docs/ERC4626Fees.sol +++ b/contracts/mocks/docs/ERC4626Fees.sol @@ -13,7 +13,7 @@ abstract contract ERC4626Fees is ERC4626 { uint256 private constant _BASIS_POINT_SCALE = 1e4; - // === Overrides === /// + // === Overrides === /// @dev Preview taking an entry fee on deposit. See {IERC4626-previewDeposit}. function previewDeposit(uint256 assets) public view virtual override returns (uint256) { @@ -69,14 +69,14 @@ abstract contract ERC4626Fees is ERC4626 { } } - // === Fee configuration === /// + // === Fee configuration === function _entryFeeBasisPoints() internal view virtual returns (uint256) { - return 0; // replace with e.g. 1_000 for 1% + return 0; // replace with e.g. 100 for 1% } function _exitFeeBasisPoints() internal view virtual returns (uint256) { - return 0; // replace with e.g. 1_000 for 1% + return 0; // replace with e.g. 100 for 1% } function _entryFeeRecipient() internal view virtual returns (address) { @@ -87,7 +87,7 @@ abstract contract ERC4626Fees is ERC4626 { return address(0); // replace with e.g. a treasury address } - // === Fee operations === /// + // === Fee operations === /// @dev Calculates the fees that should be added to an amount `assets` that does not already include fees. /// Used in {IERC4626-mint} and {IERC4626-withdraw} operations. From 00cbf5a236564c3b7aacdad1f378cae22d890ca6 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Mon, 31 Jul 2023 22:25:45 +0200 Subject: [PATCH 174/182] Update pragma to 0.8.20 (#4489) Co-authored-by: Francisco Giordano Co-authored-by: ernestognw --- .changeset/short-eels-enjoy.md | 2 +- .github/workflows/formal-verification.yml | 2 +- README.md | 2 +- .../harnesses/AccessControlDefaultAdminRulesHarness.sol | 2 +- certora/harnesses/AccessControlHarness.sol | 2 +- certora/harnesses/DoubleEndedQueueHarness.sol | 2 +- certora/harnesses/ERC20FlashMintHarness.sol | 2 +- certora/harnesses/ERC20PermitHarness.sol | 2 +- certora/harnesses/ERC20WrapperHarness.sol | 2 +- certora/harnesses/ERC3156FlashBorrowerHarness.sol | 2 +- certora/harnesses/ERC721Harness.sol | 2 +- certora/harnesses/ERC721ReceiverHarness.sol | 2 +- certora/harnesses/EnumerableMapHarness.sol | 2 +- certora/harnesses/EnumerableSetHarness.sol | 2 +- certora/harnesses/InitializableHarness.sol | 2 +- certora/harnesses/Ownable2StepHarness.sol | 2 +- certora/harnesses/OwnableHarness.sol | 2 +- certora/harnesses/PausableHarness.sol | 2 +- certora/harnesses/TimelockControllerHarness.sol | 2 +- contracts/access/AccessControl.sol | 2 +- contracts/access/IAccessControl.sol | 2 +- contracts/access/Ownable.sol | 2 +- contracts/access/Ownable2Step.sol | 2 +- .../access/extensions/AccessControlDefaultAdminRules.sol | 2 +- contracts/access/extensions/AccessControlEnumerable.sol | 2 +- .../access/extensions/IAccessControlDefaultAdminRules.sol | 2 +- contracts/access/extensions/IAccessControlEnumerable.sol | 2 +- contracts/finance/VestingWallet.sol | 2 +- contracts/governance/Governor.sol | 2 +- contracts/governance/IGovernor.sol | 2 +- contracts/governance/TimelockController.sol | 2 +- .../compatibility/GovernorCompatibilityBravo.sol | 2 +- .../compatibility/IGovernorCompatibilityBravo.sol | 2 +- .../governance/extensions/GovernorCountingSimple.sol | 2 +- .../governance/extensions/GovernorPreventLateQuorum.sol | 2 +- contracts/governance/extensions/GovernorSettings.sol | 2 +- .../governance/extensions/GovernorTimelockCompound.sol | 2 +- .../governance/extensions/GovernorTimelockControl.sol | 2 +- contracts/governance/extensions/GovernorVotes.sol | 2 +- .../governance/extensions/GovernorVotesQuorumFraction.sol | 2 +- contracts/governance/extensions/IGovernorTimelock.sol | 2 +- contracts/governance/utils/IVotes.sol | 2 +- contracts/governance/utils/Votes.sol | 2 +- contracts/interfaces/IERC1155.sol | 2 +- contracts/interfaces/IERC1155MetadataURI.sol | 2 +- contracts/interfaces/IERC1155Receiver.sol | 2 +- contracts/interfaces/IERC1271.sol | 2 +- contracts/interfaces/IERC1363.sol | 2 +- contracts/interfaces/IERC1363Receiver.sol | 2 +- contracts/interfaces/IERC1363Spender.sol | 2 +- contracts/interfaces/IERC165.sol | 2 +- contracts/interfaces/IERC1820Implementer.sol | 2 +- contracts/interfaces/IERC1820Registry.sol | 2 +- contracts/interfaces/IERC1967.sol | 2 +- contracts/interfaces/IERC20.sol | 2 +- contracts/interfaces/IERC20Metadata.sol | 2 +- contracts/interfaces/IERC2309.sol | 2 +- contracts/interfaces/IERC2612.sol | 2 +- contracts/interfaces/IERC2981.sol | 2 +- contracts/interfaces/IERC3156.sol | 2 +- contracts/interfaces/IERC3156FlashBorrower.sol | 2 +- contracts/interfaces/IERC3156FlashLender.sol | 2 +- contracts/interfaces/IERC4626.sol | 2 +- contracts/interfaces/IERC4906.sol | 2 +- contracts/interfaces/IERC5267.sol | 2 +- contracts/interfaces/IERC5313.sol | 2 +- contracts/interfaces/IERC5805.sol | 2 +- contracts/interfaces/IERC6372.sol | 2 +- contracts/interfaces/IERC721.sol | 2 +- contracts/interfaces/IERC721Enumerable.sol | 2 +- contracts/interfaces/IERC721Metadata.sol | 2 +- contracts/interfaces/IERC721Receiver.sol | 2 +- contracts/interfaces/IERC777.sol | 2 +- contracts/interfaces/IERC777Recipient.sol | 2 +- contracts/interfaces/IERC777Sender.sol | 2 +- contracts/interfaces/draft-IERC1822.sol | 2 +- contracts/interfaces/draft-IERC6093.sol | 2 +- contracts/metatx/ERC2771Context.sol | 2 +- contracts/metatx/ERC2771Forwarder.sol | 6 +++--- contracts/mocks/AddressFnPointersMock.sol | 2 +- contracts/mocks/ArraysMock.sol | 2 +- contracts/mocks/CallReceiverMock.sol | 2 +- contracts/mocks/ContextMock.sol | 2 +- contracts/mocks/DummyImplementation.sol | 2 +- contracts/mocks/EIP712Verifier.sol | 2 +- contracts/mocks/ERC1271WalletMock.sol | 2 +- contracts/mocks/ERC165/ERC165InterfacesSupported.sol | 2 +- contracts/mocks/ERC165/ERC165MaliciousData.sol | 2 +- contracts/mocks/ERC165/ERC165MissingData.sol | 2 +- contracts/mocks/ERC165/ERC165NotSupported.sol | 2 +- contracts/mocks/ERC165/ERC165ReturnBomb.sol | 2 +- contracts/mocks/ERC2771ContextMock.sol | 2 +- contracts/mocks/ERC3156FlashBorrowerMock.sol | 2 +- contracts/mocks/EtherReceiverMock.sol | 2 +- contracts/mocks/InitializableMock.sol | 2 +- contracts/mocks/MulticallTest.sol | 2 +- contracts/mocks/MultipleInheritanceInitializableMocks.sol | 2 +- contracts/mocks/PausableMock.sol | 2 +- contracts/mocks/ReentrancyAttack.sol | 2 +- contracts/mocks/ReentrancyMock.sol | 2 +- contracts/mocks/RegressionImplementation.sol | 2 +- contracts/mocks/SingleInheritanceInitializableMocks.sol | 2 +- contracts/mocks/StorageSlotMock.sol | 2 +- contracts/mocks/TimelockReentrant.sol | 2 +- contracts/mocks/UpgreadeableBeaconMock.sol | 2 +- contracts/mocks/VotesMock.sol | 2 +- contracts/mocks/compound/CompTimelock.sol | 2 +- contracts/mocks/docs/ERC4626Fees.sol | 2 +- contracts/mocks/docs/governance/MyGovernor.sol | 2 +- contracts/mocks/docs/governance/MyToken.sol | 2 +- contracts/mocks/docs/governance/MyTokenTimestampBased.sol | 2 +- contracts/mocks/docs/governance/MyTokenWrapped.sol | 2 +- .../mocks/governance/GovernorCompatibilityBravoMock.sol | 2 +- contracts/mocks/governance/GovernorMock.sol | 2 +- .../mocks/governance/GovernorPreventLateQuorumMock.sol | 2 +- .../mocks/governance/GovernorTimelockCompoundMock.sol | 2 +- .../mocks/governance/GovernorTimelockControlMock.sol | 2 +- contracts/mocks/governance/GovernorVoteMock.sol | 2 +- contracts/mocks/governance/GovernorWithParamsMock.sol | 2 +- contracts/mocks/proxy/BadBeacon.sol | 2 +- contracts/mocks/proxy/ClashingImplementation.sol | 2 +- contracts/mocks/proxy/UUPSUpgradeableMock.sol | 2 +- contracts/mocks/token/ERC1155ReceiverMock.sol | 2 +- contracts/mocks/token/ERC20ApprovalMock.sol | 2 +- contracts/mocks/token/ERC20DecimalsMock.sol | 2 +- contracts/mocks/token/ERC20ExcessDecimalsMock.sol | 2 +- contracts/mocks/token/ERC20FlashMintMock.sol | 2 +- contracts/mocks/token/ERC20ForceApproveMock.sol | 2 +- contracts/mocks/token/ERC20Mock.sol | 2 +- contracts/mocks/token/ERC20MulticallMock.sol | 2 +- contracts/mocks/token/ERC20NoReturnMock.sol | 2 +- contracts/mocks/token/ERC20PermitNoRevertMock.sol | 2 +- contracts/mocks/token/ERC20Reentrant.sol | 2 +- contracts/mocks/token/ERC20ReturnFalseMock.sol | 2 +- contracts/mocks/token/ERC20VotesLegacyMock.sol | 2 +- contracts/mocks/token/ERC4626LimitsMock.sol | 2 +- contracts/mocks/token/ERC4626Mock.sol | 2 +- contracts/mocks/token/ERC4626OffsetMock.sol | 2 +- contracts/mocks/token/ERC4646FeesMock.sol | 2 +- contracts/mocks/token/ERC721ConsecutiveEnumerableMock.sol | 2 +- contracts/mocks/token/ERC721ConsecutiveMock.sol | 2 +- contracts/mocks/token/ERC721ReceiverMock.sol | 2 +- contracts/mocks/token/ERC721URIStorageMock.sol | 2 +- contracts/mocks/token/VotesTimestamp.sol | 2 +- contracts/proxy/Clones.sol | 2 +- contracts/proxy/ERC1967/ERC1967Proxy.sol | 2 +- contracts/proxy/Proxy.sol | 2 +- contracts/proxy/beacon/BeaconProxy.sol | 2 +- contracts/proxy/beacon/IBeacon.sol | 2 +- contracts/proxy/beacon/UpgradeableBeacon.sol | 2 +- contracts/proxy/transparent/ProxyAdmin.sol | 2 +- .../proxy/transparent/TransparentUpgradeableProxy.sol | 2 +- contracts/proxy/utils/Initializable.sol | 2 +- contracts/proxy/utils/UUPSUpgradeable.sol | 2 +- contracts/security/Pausable.sol | 2 +- contracts/security/ReentrancyGuard.sol | 2 +- contracts/token/ERC1155/ERC1155.sol | 2 +- contracts/token/ERC1155/IERC1155.sol | 2 +- contracts/token/ERC1155/IERC1155Receiver.sol | 2 +- contracts/token/ERC1155/extensions/ERC1155Burnable.sol | 2 +- contracts/token/ERC1155/extensions/ERC1155Pausable.sol | 2 +- contracts/token/ERC1155/extensions/ERC1155Supply.sol | 2 +- contracts/token/ERC1155/extensions/ERC1155URIStorage.sol | 2 +- .../token/ERC1155/extensions/IERC1155MetadataURI.sol | 2 +- contracts/token/ERC1155/utils/ERC1155Holder.sol | 2 +- contracts/token/ERC20/ERC20.sol | 2 +- contracts/token/ERC20/IERC20.sol | 2 +- contracts/token/ERC20/extensions/ERC20Burnable.sol | 2 +- contracts/token/ERC20/extensions/ERC20Capped.sol | 2 +- contracts/token/ERC20/extensions/ERC20FlashMint.sol | 2 +- contracts/token/ERC20/extensions/ERC20Pausable.sol | 2 +- contracts/token/ERC20/extensions/ERC20Permit.sol | 2 +- contracts/token/ERC20/extensions/ERC20Votes.sol | 2 +- contracts/token/ERC20/extensions/ERC20Wrapper.sol | 2 +- contracts/token/ERC20/extensions/ERC4626.sol | 2 +- contracts/token/ERC20/extensions/IERC20Metadata.sol | 2 +- contracts/token/ERC20/extensions/IERC20Permit.sol | 2 +- contracts/token/ERC20/utils/SafeERC20.sol | 2 +- contracts/token/ERC721/ERC721.sol | 2 +- contracts/token/ERC721/IERC721.sol | 2 +- contracts/token/ERC721/IERC721Receiver.sol | 2 +- contracts/token/ERC721/extensions/ERC721Burnable.sol | 2 +- contracts/token/ERC721/extensions/ERC721Consecutive.sol | 2 +- contracts/token/ERC721/extensions/ERC721Enumerable.sol | 2 +- contracts/token/ERC721/extensions/ERC721Pausable.sol | 2 +- contracts/token/ERC721/extensions/ERC721Royalty.sol | 2 +- contracts/token/ERC721/extensions/ERC721URIStorage.sol | 2 +- contracts/token/ERC721/extensions/ERC721Votes.sol | 2 +- contracts/token/ERC721/extensions/ERC721Wrapper.sol | 2 +- contracts/token/ERC721/extensions/IERC721Enumerable.sol | 2 +- contracts/token/ERC721/extensions/IERC721Metadata.sol | 2 +- contracts/token/ERC721/utils/ERC721Holder.sol | 2 +- contracts/token/common/ERC2981.sol | 2 +- contracts/utils/Address.sol | 4 ++-- contracts/utils/Arrays.sol | 8 ++++---- contracts/utils/Base64.sol | 2 +- contracts/utils/Context.sol | 2 +- contracts/utils/Create2.sol | 2 +- contracts/utils/Multicall.sol | 2 +- contracts/utils/Nonces.sol | 2 +- contracts/utils/ShortStrings.sol | 2 +- contracts/utils/StorageSlot.sol | 2 +- contracts/utils/Strings.sol | 2 +- contracts/utils/cryptography/ECDSA.sol | 2 +- contracts/utils/cryptography/EIP712.sol | 2 +- contracts/utils/cryptography/MerkleProof.sol | 2 +- contracts/utils/cryptography/MessageHashUtils.sol | 2 +- contracts/utils/cryptography/SignatureChecker.sol | 2 +- contracts/utils/introspection/ERC165.sol | 2 +- contracts/utils/introspection/ERC165Checker.sol | 2 +- contracts/utils/introspection/IERC165.sol | 2 +- contracts/utils/math/Math.sol | 2 +- contracts/utils/math/SafeCast.sol | 2 +- contracts/utils/math/SignedMath.sol | 2 +- contracts/utils/structs/BitMaps.sol | 2 +- contracts/utils/structs/Checkpoints.sol | 2 +- contracts/utils/structs/DoubleEndedQueue.sol | 2 +- contracts/utils/structs/EnumerableMap.sol | 2 +- contracts/utils/structs/EnumerableSet.sol | 2 +- contracts/vendor/compound/ICompoundTimelock.sol | 2 +- docs/modules/ROOT/pages/access-control.adoc | 8 ++++---- docs/modules/ROOT/pages/erc1155.adoc | 4 ++-- docs/modules/ROOT/pages/erc20.adoc | 2 +- docs/modules/ROOT/pages/erc721.adoc | 2 +- docs/modules/ROOT/pages/extending-contracts.adoc | 6 +++--- docs/modules/ROOT/pages/governance.adoc | 2 +- docs/modules/ROOT/pages/index.adoc | 2 +- docs/modules/ROOT/pages/utilities.adoc | 2 +- hardhat.config.js | 2 +- scripts/generate/templates/Checkpoints.js | 2 +- scripts/generate/templates/Checkpoints.t.js | 2 +- scripts/generate/templates/EnumerableMap.js | 2 +- scripts/generate/templates/EnumerableSet.js | 2 +- scripts/generate/templates/SafeCast.js | 2 +- scripts/generate/templates/StorageSlot.js | 2 +- scripts/upgradeable/upgradeable.patch | 4 ++-- test/governance/Governor.t.sol | 2 +- test/metatx/ERC2771Forwarder.t.sol | 2 +- test/token/ERC20/extensions/ERC4626.t.sol | 2 +- test/token/ERC721/extensions/ERC721Consecutive.t.sol | 2 +- test/utils/ShortStrings.t.sol | 2 +- test/utils/math/Math.t.sol | 2 +- test/utils/structs/Checkpoints.t.sol | 2 +- 243 files changed, 256 insertions(+), 256 deletions(-) diff --git a/.changeset/short-eels-enjoy.md b/.changeset/short-eels-enjoy.md index b7d74c1a2..e826c6d19 100644 --- a/.changeset/short-eels-enjoy.md +++ b/.changeset/short-eels-enjoy.md @@ -2,4 +2,4 @@ 'openzeppelin-solidity': major --- -Bump minimum compiler version required to 0.8.19 +Bump minimum compiler version required to 0.8.20 diff --git a/.github/workflows/formal-verification.yml b/.github/workflows/formal-verification.yml index ae5eba006..a86f255fb 100644 --- a/.github/workflows/formal-verification.yml +++ b/.github/workflows/formal-verification.yml @@ -12,7 +12,7 @@ on: env: PIP_VERSION: '3.10' JAVA_VERSION: '11' - SOLC_VERSION: '0.8.19' + SOLC_VERSION: '0.8.20' concurrency: ${{ github.workflow }}-${{ github.ref }} diff --git a/README.md b/README.md index 38197f3af..825c6ea7d 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ Add `@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/` in `remappi Once installed, you can use the contracts in the library by importing them: ```solidity -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; diff --git a/certora/harnesses/AccessControlDefaultAdminRulesHarness.sol b/certora/harnesses/AccessControlDefaultAdminRulesHarness.sol index 3483c558f..145f65b76 100644 --- a/certora/harnesses/AccessControlDefaultAdminRulesHarness.sol +++ b/certora/harnesses/AccessControlDefaultAdminRulesHarness.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import "../patched/access/AccessControlDefaultAdminRules.sol"; diff --git a/certora/harnesses/AccessControlHarness.sol b/certora/harnesses/AccessControlHarness.sol index 3363f3590..42a536af1 100644 --- a/certora/harnesses/AccessControlHarness.sol +++ b/certora/harnesses/AccessControlHarness.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import "../patched/access/AccessControl.sol"; diff --git a/certora/harnesses/DoubleEndedQueueHarness.sol b/certora/harnesses/DoubleEndedQueueHarness.sol index c6a800726..54852a739 100644 --- a/certora/harnesses/DoubleEndedQueueHarness.sol +++ b/certora/harnesses/DoubleEndedQueueHarness.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import "../patched/utils/structs/DoubleEndedQueue.sol"; diff --git a/certora/harnesses/ERC20FlashMintHarness.sol b/certora/harnesses/ERC20FlashMintHarness.sol index 3599d8494..2f989b243 100644 --- a/certora/harnesses/ERC20FlashMintHarness.sol +++ b/certora/harnesses/ERC20FlashMintHarness.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import "../patched/token/ERC20/ERC20.sol"; import "../patched/token/ERC20/extensions/ERC20Permit.sol"; diff --git a/certora/harnesses/ERC20PermitHarness.sol b/certora/harnesses/ERC20PermitHarness.sol index dccc9cb91..1041c1715 100644 --- a/certora/harnesses/ERC20PermitHarness.sol +++ b/certora/harnesses/ERC20PermitHarness.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import "../patched/token/ERC20/extensions/ERC20Permit.sol"; diff --git a/certora/harnesses/ERC20WrapperHarness.sol b/certora/harnesses/ERC20WrapperHarness.sol index 191c46cc4..5e55e4b72 100644 --- a/certora/harnesses/ERC20WrapperHarness.sol +++ b/certora/harnesses/ERC20WrapperHarness.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import "../patched/token/ERC20/extensions/ERC20Wrapper.sol"; diff --git a/certora/harnesses/ERC3156FlashBorrowerHarness.sol b/certora/harnesses/ERC3156FlashBorrowerHarness.sol index d07d12e47..81dfdaf31 100644 --- a/certora/harnesses/ERC3156FlashBorrowerHarness.sol +++ b/certora/harnesses/ERC3156FlashBorrowerHarness.sol @@ -2,7 +2,7 @@ import "../patched/interfaces/IERC3156FlashBorrower.sol"; -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; contract ERC3156FlashBorrowerHarness is IERC3156FlashBorrower { bytes32 somethingToReturn; diff --git a/certora/harnesses/ERC721Harness.sol b/certora/harnesses/ERC721Harness.sol index f22a6811d..b0afb589c 100644 --- a/certora/harnesses/ERC721Harness.sol +++ b/certora/harnesses/ERC721Harness.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import "../patched/token/ERC721/ERC721.sol"; diff --git a/certora/harnesses/ERC721ReceiverHarness.sol b/certora/harnesses/ERC721ReceiverHarness.sol index a0e9e247a..3843ef4a2 100644 --- a/certora/harnesses/ERC721ReceiverHarness.sol +++ b/certora/harnesses/ERC721ReceiverHarness.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import "../patched/interfaces/IERC721Receiver.sol"; diff --git a/certora/harnesses/EnumerableMapHarness.sol b/certora/harnesses/EnumerableMapHarness.sol index 40b752487..2b9a8e47c 100644 --- a/certora/harnesses/EnumerableMapHarness.sol +++ b/certora/harnesses/EnumerableMapHarness.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import "../patched/utils/structs/EnumerableMap.sol"; diff --git a/certora/harnesses/EnumerableSetHarness.sol b/certora/harnesses/EnumerableSetHarness.sol index 21c7cc7c4..1f4cac7d9 100644 --- a/certora/harnesses/EnumerableSetHarness.sol +++ b/certora/harnesses/EnumerableSetHarness.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import "../patched/utils/structs/EnumerableSet.sol"; diff --git a/certora/harnesses/InitializableHarness.sol b/certora/harnesses/InitializableHarness.sol index 52b48b82d..0d0c0a4e4 100644 --- a/certora/harnesses/InitializableHarness.sol +++ b/certora/harnesses/InitializableHarness.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import "../patched/proxy/utils/Initializable.sol"; diff --git a/certora/harnesses/Ownable2StepHarness.sol b/certora/harnesses/Ownable2StepHarness.sol index 1a9ed76e5..aed6f5854 100644 --- a/certora/harnesses/Ownable2StepHarness.sol +++ b/certora/harnesses/Ownable2StepHarness.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import "../patched/access/Ownable2Step.sol"; diff --git a/certora/harnesses/OwnableHarness.sol b/certora/harnesses/OwnableHarness.sol index 3113fa1bb..45666772a 100644 --- a/certora/harnesses/OwnableHarness.sol +++ b/certora/harnesses/OwnableHarness.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import "../patched/access/Ownable.sol"; diff --git a/certora/harnesses/PausableHarness.sol b/certora/harnesses/PausableHarness.sol index 34d6a82d8..b9c15cf54 100644 --- a/certora/harnesses/PausableHarness.sol +++ b/certora/harnesses/PausableHarness.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import "../patched/security/Pausable.sol"; diff --git a/certora/harnesses/TimelockControllerHarness.sol b/certora/harnesses/TimelockControllerHarness.sol index f75dcfa45..476a8376c 100644 --- a/certora/harnesses/TimelockControllerHarness.sol +++ b/certora/harnesses/TimelockControllerHarness.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import "../patched/governance/TimelockController.sol"; diff --git a/contracts/access/AccessControl.sol b/contracts/access/AccessControl.sol index bc81c4609..cb7650806 100644 --- a/contracts/access/AccessControl.sol +++ b/contracts/access/AccessControl.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (access/AccessControl.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {IAccessControl} from "./IAccessControl.sol"; import {Context} from "../utils/Context.sol"; diff --git a/contracts/access/IAccessControl.sol b/contracts/access/IAccessControl.sol index 38facbfb1..ebb01f2c8 100644 --- a/contracts/access/IAccessControl.sol +++ b/contracts/access/IAccessControl.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; /** * @dev External interface of AccessControl declared to support ERC165 detection. diff --git a/contracts/access/Ownable.sol b/contracts/access/Ownable.sol index ca09e0350..aa495ff4d 100644 --- a/contracts/access/Ownable.sol +++ b/contracts/access/Ownable.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {Context} from "../utils/Context.sol"; diff --git a/contracts/access/Ownable2Step.sol b/contracts/access/Ownable2Step.sol index 3cbab1798..e6bfb1f84 100644 --- a/contracts/access/Ownable2Step.sol +++ b/contracts/access/Ownable2Step.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable2Step.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {Ownable} from "./Ownable.sol"; diff --git a/contracts/access/extensions/AccessControlDefaultAdminRules.sol b/contracts/access/extensions/AccessControlDefaultAdminRules.sol index e8820cb4b..dc4dbfbcf 100644 --- a/contracts/access/extensions/AccessControlDefaultAdminRules.sol +++ b/contracts/access/extensions/AccessControlDefaultAdminRules.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (access/AccessControlDefaultAdminRules.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {IAccessControlDefaultAdminRules} from "./IAccessControlDefaultAdminRules.sol"; import {AccessControl, IAccessControl} from "../AccessControl.sol"; diff --git a/contracts/access/extensions/AccessControlEnumerable.sol b/contracts/access/extensions/AccessControlEnumerable.sol index 2512c4379..f07aca637 100644 --- a/contracts/access/extensions/AccessControlEnumerable.sol +++ b/contracts/access/extensions/AccessControlEnumerable.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.5.0) (access/AccessControlEnumerable.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {IAccessControlEnumerable} from "./IAccessControlEnumerable.sol"; import {AccessControl} from "../AccessControl.sol"; diff --git a/contracts/access/extensions/IAccessControlDefaultAdminRules.sol b/contracts/access/extensions/IAccessControlDefaultAdminRules.sol index 7dac1c1f2..0e6fa2b28 100644 --- a/contracts/access/extensions/IAccessControlDefaultAdminRules.sol +++ b/contracts/access/extensions/IAccessControlDefaultAdminRules.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (access/IAccessControlDefaultAdminRules.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {IAccessControl} from "../IAccessControl.sol"; diff --git a/contracts/access/extensions/IAccessControlEnumerable.sol b/contracts/access/extensions/IAccessControlEnumerable.sol index bc59ed586..1dd28f1a0 100644 --- a/contracts/access/extensions/IAccessControlEnumerable.sol +++ b/contracts/access/extensions/IAccessControlEnumerable.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (access/IAccessControlEnumerable.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {IAccessControl} from "../IAccessControl.sol"; diff --git a/contracts/finance/VestingWallet.sol b/contracts/finance/VestingWallet.sol index f776a7ca4..840837d27 100644 --- a/contracts/finance/VestingWallet.sol +++ b/contracts/finance/VestingWallet.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (finance/VestingWallet.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {IERC20} from "../token/ERC20/IERC20.sol"; import {SafeERC20} from "../token/ERC20/utils/SafeERC20.sol"; diff --git a/contracts/governance/Governor.sol b/contracts/governance/Governor.sol index a94b4b305..9e76bfea9 100644 --- a/contracts/governance/Governor.sol +++ b/contracts/governance/Governor.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.1) (governance/Governor.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {IERC721Receiver} from "../token/ERC721/IERC721Receiver.sol"; import {IERC1155Receiver} from "../token/ERC1155/IERC1155Receiver.sol"; diff --git a/contracts/governance/IGovernor.sol b/contracts/governance/IGovernor.sol index b954ed205..c4a28fd19 100644 --- a/contracts/governance/IGovernor.sol +++ b/contracts/governance/IGovernor.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (governance/IGovernor.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {IERC165} from "../interfaces/IERC165.sol"; import {IERC6372} from "../interfaces/IERC6372.sol"; diff --git a/contracts/governance/TimelockController.sol b/contracts/governance/TimelockController.sol index aaaeb255b..798f84026 100644 --- a/contracts/governance/TimelockController.sol +++ b/contracts/governance/TimelockController.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (governance/TimelockController.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {AccessControl} from "../access/AccessControl.sol"; import {ERC721Holder} from "../token/ERC721/utils/ERC721Holder.sol"; diff --git a/contracts/governance/compatibility/GovernorCompatibilityBravo.sol b/contracts/governance/compatibility/GovernorCompatibilityBravo.sol index 40284c578..27c34de29 100644 --- a/contracts/governance/compatibility/GovernorCompatibilityBravo.sol +++ b/contracts/governance/compatibility/GovernorCompatibilityBravo.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (governance/compatibility/GovernorCompatibilityBravo.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {SafeCast} from "../../utils/math/SafeCast.sol"; import {IGovernorTimelock} from "../extensions/IGovernorTimelock.sol"; diff --git a/contracts/governance/compatibility/IGovernorCompatibilityBravo.sol b/contracts/governance/compatibility/IGovernorCompatibilityBravo.sol index 5476edf4f..26fdb31ea 100644 --- a/contracts/governance/compatibility/IGovernorCompatibilityBravo.sol +++ b/contracts/governance/compatibility/IGovernorCompatibilityBravo.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (governance/compatibility/IGovernorCompatibilityBravo.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {IGovernor} from "../IGovernor.sol"; diff --git a/contracts/governance/extensions/GovernorCountingSimple.sol b/contracts/governance/extensions/GovernorCountingSimple.sol index 6496c3acc..26f97c8b9 100644 --- a/contracts/governance/extensions/GovernorCountingSimple.sol +++ b/contracts/governance/extensions/GovernorCountingSimple.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (governance/extensions/GovernorCountingSimple.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {Governor} from "../Governor.sol"; diff --git a/contracts/governance/extensions/GovernorPreventLateQuorum.sol b/contracts/governance/extensions/GovernorPreventLateQuorum.sol index 89c02fce6..eda22fbee 100644 --- a/contracts/governance/extensions/GovernorPreventLateQuorum.sol +++ b/contracts/governance/extensions/GovernorPreventLateQuorum.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (governance/extensions/GovernorPreventLateQuorum.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {Governor} from "../Governor.sol"; import {Math} from "../../utils/math/Math.sol"; diff --git a/contracts/governance/extensions/GovernorSettings.sol b/contracts/governance/extensions/GovernorSettings.sol index 834555654..f6aba7d37 100644 --- a/contracts/governance/extensions/GovernorSettings.sol +++ b/contracts/governance/extensions/GovernorSettings.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (governance/extensions/GovernorSettings.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {Governor} from "../Governor.sol"; diff --git a/contracts/governance/extensions/GovernorTimelockCompound.sol b/contracts/governance/extensions/GovernorTimelockCompound.sol index c50c82c57..30181b6d2 100644 --- a/contracts/governance/extensions/GovernorTimelockCompound.sol +++ b/contracts/governance/extensions/GovernorTimelockCompound.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (governance/extensions/GovernorTimelockCompound.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {IGovernorTimelock} from "./IGovernorTimelock.sol"; import {IGovernor, Governor} from "../Governor.sol"; diff --git a/contracts/governance/extensions/GovernorTimelockControl.sol b/contracts/governance/extensions/GovernorTimelockControl.sol index e6dea036f..615b9330a 100644 --- a/contracts/governance/extensions/GovernorTimelockControl.sol +++ b/contracts/governance/extensions/GovernorTimelockControl.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (governance/extensions/GovernorTimelockControl.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {IGovernorTimelock} from "./IGovernorTimelock.sol"; import {IGovernor, Governor} from "../Governor.sol"; diff --git a/contracts/governance/extensions/GovernorVotes.sol b/contracts/governance/extensions/GovernorVotes.sol index eea43290e..45447da51 100644 --- a/contracts/governance/extensions/GovernorVotes.sol +++ b/contracts/governance/extensions/GovernorVotes.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (governance/extensions/GovernorVotes.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {Governor} from "../Governor.sol"; import {IVotes} from "../utils/IVotes.sol"; diff --git a/contracts/governance/extensions/GovernorVotesQuorumFraction.sol b/contracts/governance/extensions/GovernorVotesQuorumFraction.sol index 7782130fa..f7def8529 100644 --- a/contracts/governance/extensions/GovernorVotesQuorumFraction.sol +++ b/contracts/governance/extensions/GovernorVotesQuorumFraction.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (governance/extensions/GovernorVotesQuorumFraction.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {GovernorVotes} from "./GovernorVotes.sol"; import {SafeCast} from "../../utils/math/SafeCast.sol"; diff --git a/contracts/governance/extensions/IGovernorTimelock.sol b/contracts/governance/extensions/IGovernorTimelock.sol index 7d9c3c554..28a9f9330 100644 --- a/contracts/governance/extensions/IGovernorTimelock.sol +++ b/contracts/governance/extensions/IGovernorTimelock.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (governance/extensions/IGovernorTimelock.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {IGovernor} from "../IGovernor.sol"; diff --git a/contracts/governance/utils/IVotes.sol b/contracts/governance/utils/IVotes.sol index c664d1255..fe47bcf36 100644 --- a/contracts/governance/utils/IVotes.sol +++ b/contracts/governance/utils/IVotes.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (governance/utils/IVotes.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; /** * @dev Common interface for {ERC20Votes}, {ERC721Votes}, and other {Votes}-enabled contracts. diff --git a/contracts/governance/utils/Votes.sol b/contracts/governance/utils/Votes.sol index 1c0bb3289..69ef48d7d 100644 --- a/contracts/governance/utils/Votes.sol +++ b/contracts/governance/utils/Votes.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (governance/utils/Votes.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {IERC5805} from "../../interfaces/IERC5805.sol"; import {Context} from "../../utils/Context.sol"; diff --git a/contracts/interfaces/IERC1155.sol b/contracts/interfaces/IERC1155.sol index 6c9ef7f6c..783c897be 100644 --- a/contracts/interfaces/IERC1155.sol +++ b/contracts/interfaces/IERC1155.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (interfaces/IERC1155.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {IERC1155} from "../token/ERC1155/IERC1155.sol"; diff --git a/contracts/interfaces/IERC1155MetadataURI.sol b/contracts/interfaces/IERC1155MetadataURI.sol index 7c202be48..3c8cdb553 100644 --- a/contracts/interfaces/IERC1155MetadataURI.sol +++ b/contracts/interfaces/IERC1155MetadataURI.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (interfaces/IERC1155MetadataURI.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {IERC1155MetadataURI} from "../token/ERC1155/extensions/IERC1155MetadataURI.sol"; diff --git a/contracts/interfaces/IERC1155Receiver.sol b/contracts/interfaces/IERC1155Receiver.sol index c67ed38bd..1b5cd4b8d 100644 --- a/contracts/interfaces/IERC1155Receiver.sol +++ b/contracts/interfaces/IERC1155Receiver.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (interfaces/IERC1155Receiver.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {IERC1155Receiver} from "../token/ERC1155/IERC1155Receiver.sol"; diff --git a/contracts/interfaces/IERC1271.sol b/contracts/interfaces/IERC1271.sol index 1523f988e..49c7df1c8 100644 --- a/contracts/interfaces/IERC1271.sol +++ b/contracts/interfaces/IERC1271.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (interfaces/IERC1271.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; /** * @dev Interface of the ERC1271 standard signature validation method for diff --git a/contracts/interfaces/IERC1363.sol b/contracts/interfaces/IERC1363.sol index 2bbba32b6..d1b555a11 100644 --- a/contracts/interfaces/IERC1363.sol +++ b/contracts/interfaces/IERC1363.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC1363.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {IERC20} from "./IERC20.sol"; import {IERC165} from "./IERC165.sol"; diff --git a/contracts/interfaces/IERC1363Receiver.sol b/contracts/interfaces/IERC1363Receiver.sol index 70ed857e9..61f32ba34 100644 --- a/contracts/interfaces/IERC1363Receiver.sol +++ b/contracts/interfaces/IERC1363Receiver.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC1363Receiver.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; /** * @dev Interface for any contract that wants to support {IERC1363-transferAndCall} diff --git a/contracts/interfaces/IERC1363Spender.sol b/contracts/interfaces/IERC1363Spender.sol index 0c89e58eb..ab9e62140 100644 --- a/contracts/interfaces/IERC1363Spender.sol +++ b/contracts/interfaces/IERC1363Spender.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC1363Spender.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; /** * @dev Interface for any contract that wants to support {IERC1363-approveAndCall} diff --git a/contracts/interfaces/IERC165.sol b/contracts/interfaces/IERC165.sol index 3ac6e69dd..53945fcb3 100644 --- a/contracts/interfaces/IERC165.sol +++ b/contracts/interfaces/IERC165.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (interfaces/IERC165.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {IERC165} from "../utils/introspection/IERC165.sol"; diff --git a/contracts/interfaces/IERC1820Implementer.sol b/contracts/interfaces/IERC1820Implementer.sol index 6f0ec661d..03b6245e0 100644 --- a/contracts/interfaces/IERC1820Implementer.sol +++ b/contracts/interfaces/IERC1820Implementer.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC1820Implementer.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; /** * @dev Interface for an ERC1820 implementer, as defined in the diff --git a/contracts/interfaces/IERC1820Registry.sol b/contracts/interfaces/IERC1820Registry.sol index d9dd49355..a88e4ba8e 100644 --- a/contracts/interfaces/IERC1820Registry.sol +++ b/contracts/interfaces/IERC1820Registry.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.8.0) (utils/introspection/IERC1820Registry.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; /** * @dev Interface of the global ERC1820 Registry, as defined in the diff --git a/contracts/interfaces/IERC1967.sol b/contracts/interfaces/IERC1967.sol index a57f767f1..6cbcb5a50 100644 --- a/contracts/interfaces/IERC1967.sol +++ b/contracts/interfaces/IERC1967.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC1967.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; /** * @dev ERC-1967: Proxy Storage Slots. This interface contains the events defined in the ERC. diff --git a/contracts/interfaces/IERC20.sol b/contracts/interfaces/IERC20.sol index 3ff660480..9f72f9e0b 100644 --- a/contracts/interfaces/IERC20.sol +++ b/contracts/interfaces/IERC20.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (interfaces/IERC20.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {IERC20} from "../token/ERC20/IERC20.sol"; diff --git a/contracts/interfaces/IERC20Metadata.sol b/contracts/interfaces/IERC20Metadata.sol index edb680446..fce1d8deb 100644 --- a/contracts/interfaces/IERC20Metadata.sol +++ b/contracts/interfaces/IERC20Metadata.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (interfaces/IERC20Metadata.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {IERC20Metadata} from "../token/ERC20/extensions/IERC20Metadata.sol"; diff --git a/contracts/interfaces/IERC2309.sol b/contracts/interfaces/IERC2309.sol index cd967e41b..d4e80c82c 100644 --- a/contracts/interfaces/IERC2309.sol +++ b/contracts/interfaces/IERC2309.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.8.0) (interfaces/IERC2309.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; /** * @dev ERC-2309: ERC-721 Consecutive Transfer Extension. diff --git a/contracts/interfaces/IERC2612.sol b/contracts/interfaces/IERC2612.sol index 3a1b35266..e27ba7ffe 100644 --- a/contracts/interfaces/IERC2612.sol +++ b/contracts/interfaces/IERC2612.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC2612.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {IERC20Permit} from "../token/ERC20/extensions/IERC20Permit.sol"; diff --git a/contracts/interfaces/IERC2981.sol b/contracts/interfaces/IERC2981.sol index 18cfa7135..eabe34929 100644 --- a/contracts/interfaces/IERC2981.sol +++ b/contracts/interfaces/IERC2981.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC2981.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {IERC165} from "../utils/introspection/IERC165.sol"; diff --git a/contracts/interfaces/IERC3156.sol b/contracts/interfaces/IERC3156.sol index 6bd56088b..8721e3082 100644 --- a/contracts/interfaces/IERC3156.sol +++ b/contracts/interfaces/IERC3156.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (interfaces/IERC3156.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {IERC3156FlashBorrower} from "./IERC3156FlashBorrower.sol"; import {IERC3156FlashLender} from "./IERC3156FlashLender.sol"; diff --git a/contracts/interfaces/IERC3156FlashBorrower.sol b/contracts/interfaces/IERC3156FlashBorrower.sol index 5c0b88de4..c3f9d6158 100644 --- a/contracts/interfaces/IERC3156FlashBorrower.sol +++ b/contracts/interfaces/IERC3156FlashBorrower.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC3156FlashBorrower.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; /** * @dev Interface of the ERC3156 FlashBorrower, as defined in diff --git a/contracts/interfaces/IERC3156FlashLender.sol b/contracts/interfaces/IERC3156FlashLender.sol index b52428a06..5a18adb40 100644 --- a/contracts/interfaces/IERC3156FlashLender.sol +++ b/contracts/interfaces/IERC3156FlashLender.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (interfaces/IERC3156FlashLender.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {IERC3156FlashBorrower} from "./IERC3156FlashBorrower.sol"; diff --git a/contracts/interfaces/IERC4626.sol b/contracts/interfaces/IERC4626.sol index 8854b0022..e1b778e65 100644 --- a/contracts/interfaces/IERC4626.sol +++ b/contracts/interfaces/IERC4626.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC4626.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {IERC20} from "../token/ERC20/IERC20.sol"; import {IERC20Metadata} from "../token/ERC20/extensions/IERC20Metadata.sol"; diff --git a/contracts/interfaces/IERC4906.sol b/contracts/interfaces/IERC4906.sol index fecdb0c52..1f129450e 100644 --- a/contracts/interfaces/IERC4906.sol +++ b/contracts/interfaces/IERC4906.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC4906.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {IERC165} from "./IERC165.sol"; import {IERC721} from "./IERC721.sol"; diff --git a/contracts/interfaces/IERC5267.sol b/contracts/interfaces/IERC5267.sol index c19b4a6da..eeda3ea53 100644 --- a/contracts/interfaces/IERC5267.sol +++ b/contracts/interfaces/IERC5267.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC5267.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; interface IERC5267 { /** diff --git a/contracts/interfaces/IERC5313.sol b/contracts/interfaces/IERC5313.sol index 1150c322c..86adb8ef2 100644 --- a/contracts/interfaces/IERC5313.sol +++ b/contracts/interfaces/IERC5313.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC5313.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; /** * @dev Interface for the Light Contract Ownership Standard. diff --git a/contracts/interfaces/IERC5805.sol b/contracts/interfaces/IERC5805.sol index 6eb89919f..7b562b18f 100644 --- a/contracts/interfaces/IERC5805.sol +++ b/contracts/interfaces/IERC5805.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC5805.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {IVotes} from "../governance/utils/IVotes.sol"; import {IERC6372} from "./IERC6372.sol"; diff --git a/contracts/interfaces/IERC6372.sol b/contracts/interfaces/IERC6372.sol index c6c78d10a..0627254a6 100644 --- a/contracts/interfaces/IERC6372.sol +++ b/contracts/interfaces/IERC6372.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC6372.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; interface IERC6372 { /** diff --git a/contracts/interfaces/IERC721.sol b/contracts/interfaces/IERC721.sol index f086ab6fb..d9b8070f3 100644 --- a/contracts/interfaces/IERC721.sol +++ b/contracts/interfaces/IERC721.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (interfaces/IERC721.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {IERC721} from "../token/ERC721/IERC721.sol"; diff --git a/contracts/interfaces/IERC721Enumerable.sol b/contracts/interfaces/IERC721Enumerable.sol index e5ce68d99..216139911 100644 --- a/contracts/interfaces/IERC721Enumerable.sol +++ b/contracts/interfaces/IERC721Enumerable.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (interfaces/IERC721Enumerable.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {IERC721Enumerable} from "../token/ERC721/extensions/IERC721Enumerable.sol"; diff --git a/contracts/interfaces/IERC721Metadata.sol b/contracts/interfaces/IERC721Metadata.sol index 5b2ad5687..4dc4becf8 100644 --- a/contracts/interfaces/IERC721Metadata.sol +++ b/contracts/interfaces/IERC721Metadata.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (interfaces/IERC721Metadata.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {IERC721Metadata} from "../token/ERC721/extensions/IERC721Metadata.sol"; diff --git a/contracts/interfaces/IERC721Receiver.sol b/contracts/interfaces/IERC721Receiver.sol index 6ee3d3d80..0987da198 100644 --- a/contracts/interfaces/IERC721Receiver.sol +++ b/contracts/interfaces/IERC721Receiver.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (interfaces/IERC721Receiver.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {IERC721Receiver} from "../token/ERC721/IERC721Receiver.sol"; diff --git a/contracts/interfaces/IERC777.sol b/contracts/interfaces/IERC777.sol index f868701ba..651909047 100644 --- a/contracts/interfaces/IERC777.sol +++ b/contracts/interfaces/IERC777.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; /** * @dev Interface of the ERC777Token standard as defined in the EIP. diff --git a/contracts/interfaces/IERC777Recipient.sol b/contracts/interfaces/IERC777Recipient.sol index be2ab871d..65a60feac 100644 --- a/contracts/interfaces/IERC777Recipient.sol +++ b/contracts/interfaces/IERC777Recipient.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; /** * @dev Interface of the ERC777TokensRecipient standard as defined in the EIP. diff --git a/contracts/interfaces/IERC777Sender.sol b/contracts/interfaces/IERC777Sender.sol index d4172b107..99e508688 100644 --- a/contracts/interfaces/IERC777Sender.sol +++ b/contracts/interfaces/IERC777Sender.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; /** * @dev Interface of the ERC777TokensSender standard as defined in the EIP. diff --git a/contracts/interfaces/draft-IERC1822.sol b/contracts/interfaces/draft-IERC1822.sol index f452b5e34..52023a5f9 100644 --- a/contracts/interfaces/draft-IERC1822.sol +++ b/contracts/interfaces/draft-IERC1822.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.5.0) (interfaces/draft-IERC1822.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; /** * @dev ERC1822: Universal Upgradeable Proxy Standard (UUPS) documents a method for upgradeability through a simplified diff --git a/contracts/interfaces/draft-IERC6093.sol b/contracts/interfaces/draft-IERC6093.sol index 3c3900852..c38379ac4 100644 --- a/contracts/interfaces/draft-IERC6093.sol +++ b/contracts/interfaces/draft-IERC6093.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; /** * @dev Standard ERC20 Errors diff --git a/contracts/metatx/ERC2771Context.sol b/contracts/metatx/ERC2771Context.sol index 406a62bd6..4a2df6e3d 100644 --- a/contracts/metatx/ERC2771Context.sol +++ b/contracts/metatx/ERC2771Context.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.7.0) (metatx/ERC2771Context.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {Context} from "../utils/Context.sol"; diff --git a/contracts/metatx/ERC2771Forwarder.sol b/contracts/metatx/ERC2771Forwarder.sol index 125f80076..93e334622 100644 --- a/contracts/metatx/ERC2771Forwarder.sol +++ b/contracts/metatx/ERC2771Forwarder.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (metatx/ERC2771Forwarder.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {ECDSA} from "../utils/cryptography/ECDSA.sol"; import {EIP712} from "../utils/cryptography/EIP712.sol"; @@ -305,8 +305,8 @@ contract ERC2771Forwarder is EIP712, Nonces { // the forwarding does not revert. if (gasLeft < request.gas / 63) { // We explicitly trigger invalid opcode to consume all gas and bubble-up the effects, since - // neither revert or assert consume all gas since Solidity 0.8.0 - // https://docs.soliditylang.org/en/v0.8.0/control-structures.html#panic-via-assert-and-error-via-require + // neither revert or assert consume all gas since Solidity 0.8.20 + // https://docs.soliditylang.org/en/v0.8.20/control-structures.html#panic-via-assert-and-error-via-require /// @solidity memory-safe-assembly assembly { invalid() diff --git a/contracts/mocks/AddressFnPointersMock.sol b/contracts/mocks/AddressFnPointersMock.sol index 9d16a5263..dc4edc075 100644 --- a/contracts/mocks/AddressFnPointersMock.sol +++ b/contracts/mocks/AddressFnPointersMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.20; import {Address} from "../utils/Address.sol"; diff --git a/contracts/mocks/ArraysMock.sol b/contracts/mocks/ArraysMock.sol index 7c049b7df..a00def29c 100644 --- a/contracts/mocks/ArraysMock.sol +++ b/contracts/mocks/ArraysMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {Arrays} from "../utils/Arrays.sol"; diff --git a/contracts/mocks/CallReceiverMock.sol b/contracts/mocks/CallReceiverMock.sol index b87f4e59e..281afacfe 100644 --- a/contracts/mocks/CallReceiverMock.sol +++ b/contracts/mocks/CallReceiverMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; contract CallReceiverMock { event MockFunctionCalled(); diff --git a/contracts/mocks/ContextMock.sol b/contracts/mocks/ContextMock.sol index fb57535f7..199b2a978 100644 --- a/contracts/mocks/ContextMock.sol +++ b/contracts/mocks/ContextMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {Context} from "../utils/Context.sol"; diff --git a/contracts/mocks/DummyImplementation.sol b/contracts/mocks/DummyImplementation.sol index 4d3254602..4925c89df 100644 --- a/contracts/mocks/DummyImplementation.sol +++ b/contracts/mocks/DummyImplementation.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {ERC1967Utils} from "../proxy/ERC1967/ERC1967Utils.sol"; import {StorageSlot} from "../utils/StorageSlot.sol"; diff --git a/contracts/mocks/EIP712Verifier.sol b/contracts/mocks/EIP712Verifier.sol index 91a1150f6..fe32a2189 100644 --- a/contracts/mocks/EIP712Verifier.sol +++ b/contracts/mocks/EIP712Verifier.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {ECDSA} from "../utils/cryptography/ECDSA.sol"; import {EIP712} from "../utils/cryptography/EIP712.sol"; diff --git a/contracts/mocks/ERC1271WalletMock.sol b/contracts/mocks/ERC1271WalletMock.sol index e142fe73e..cba7d47d7 100644 --- a/contracts/mocks/ERC1271WalletMock.sol +++ b/contracts/mocks/ERC1271WalletMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {Ownable} from "../access/Ownable.sol"; import {IERC1271} from "../interfaces/IERC1271.sol"; diff --git a/contracts/mocks/ERC165/ERC165InterfacesSupported.sol b/contracts/mocks/ERC165/ERC165InterfacesSupported.sol index c9a1df485..b1efbd3e8 100644 --- a/contracts/mocks/ERC165/ERC165InterfacesSupported.sol +++ b/contracts/mocks/ERC165/ERC165InterfacesSupported.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {IERC165} from "../../utils/introspection/IERC165.sol"; diff --git a/contracts/mocks/ERC165/ERC165MaliciousData.sol b/contracts/mocks/ERC165/ERC165MaliciousData.sol index 2f3b57d66..35427567d 100644 --- a/contracts/mocks/ERC165/ERC165MaliciousData.sol +++ b/contracts/mocks/ERC165/ERC165MaliciousData.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; contract ERC165MaliciousData { function supportsInterface(bytes4) public pure returns (bool) { diff --git a/contracts/mocks/ERC165/ERC165MissingData.sol b/contracts/mocks/ERC165/ERC165MissingData.sol index c58ca8fd0..fec43391b 100644 --- a/contracts/mocks/ERC165/ERC165MissingData.sol +++ b/contracts/mocks/ERC165/ERC165MissingData.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; contract ERC165MissingData { function supportsInterface(bytes4 interfaceId) public view {} // missing return diff --git a/contracts/mocks/ERC165/ERC165NotSupported.sol b/contracts/mocks/ERC165/ERC165NotSupported.sol index 9cd21bc2d..78ef9c8e0 100644 --- a/contracts/mocks/ERC165/ERC165NotSupported.sol +++ b/contracts/mocks/ERC165/ERC165NotSupported.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; contract ERC165NotSupported {} diff --git a/contracts/mocks/ERC165/ERC165ReturnBomb.sol b/contracts/mocks/ERC165/ERC165ReturnBomb.sol index 99f4685cf..4bfacfd66 100644 --- a/contracts/mocks/ERC165/ERC165ReturnBomb.sol +++ b/contracts/mocks/ERC165/ERC165ReturnBomb.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {IERC165} from "../../utils/introspection/IERC165.sol"; diff --git a/contracts/mocks/ERC2771ContextMock.sol b/contracts/mocks/ERC2771ContextMock.sol index 3cdc211d8..22b9203e7 100644 --- a/contracts/mocks/ERC2771ContextMock.sol +++ b/contracts/mocks/ERC2771ContextMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {ContextMock} from "./ContextMock.sol"; import {Context} from "../utils/Context.sol"; diff --git a/contracts/mocks/ERC3156FlashBorrowerMock.sol b/contracts/mocks/ERC3156FlashBorrowerMock.sol index c426a7ef7..261fea177 100644 --- a/contracts/mocks/ERC3156FlashBorrowerMock.sol +++ b/contracts/mocks/ERC3156FlashBorrowerMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {IERC20} from "../token/ERC20/IERC20.sol"; import {IERC3156FlashBorrower} from "../interfaces/IERC3156.sol"; diff --git a/contracts/mocks/EtherReceiverMock.sol b/contracts/mocks/EtherReceiverMock.sol index d06d35b21..1b1c9363a 100644 --- a/contracts/mocks/EtherReceiverMock.sol +++ b/contracts/mocks/EtherReceiverMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; contract EtherReceiverMock { bool private _acceptEther; diff --git a/contracts/mocks/InitializableMock.sol b/contracts/mocks/InitializableMock.sol index 155aaefb7..38959a5f5 100644 --- a/contracts/mocks/InitializableMock.sol +++ b/contracts/mocks/InitializableMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {Initializable} from "../proxy/utils/Initializable.sol"; diff --git a/contracts/mocks/MulticallTest.sol b/contracts/mocks/MulticallTest.sol index 89a18b3c9..74be7d8b4 100644 --- a/contracts/mocks/MulticallTest.sol +++ b/contracts/mocks/MulticallTest.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {ERC20MulticallMock} from "./token/ERC20MulticallMock.sol"; diff --git a/contracts/mocks/MultipleInheritanceInitializableMocks.sol b/contracts/mocks/MultipleInheritanceInitializableMocks.sol index d5982f0e3..51030acd6 100644 --- a/contracts/mocks/MultipleInheritanceInitializableMocks.sol +++ b/contracts/mocks/MultipleInheritanceInitializableMocks.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {Initializable} from "../proxy/utils/Initializable.sol"; diff --git a/contracts/mocks/PausableMock.sol b/contracts/mocks/PausableMock.sol index 56bd4f45f..abe50c6c9 100644 --- a/contracts/mocks/PausableMock.sol +++ b/contracts/mocks/PausableMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {Pausable} from "../security/Pausable.sol"; diff --git a/contracts/mocks/ReentrancyAttack.sol b/contracts/mocks/ReentrancyAttack.sol index d0ba74c31..3df2d1c2b 100644 --- a/contracts/mocks/ReentrancyAttack.sol +++ b/contracts/mocks/ReentrancyAttack.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {Context} from "../utils/Context.sol"; diff --git a/contracts/mocks/ReentrancyMock.sol b/contracts/mocks/ReentrancyMock.sol index 1754c1557..f275c88e2 100644 --- a/contracts/mocks/ReentrancyMock.sol +++ b/contracts/mocks/ReentrancyMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {ReentrancyGuard} from "../security/ReentrancyGuard.sol"; import {ReentrancyAttack} from "./ReentrancyAttack.sol"; diff --git a/contracts/mocks/RegressionImplementation.sol b/contracts/mocks/RegressionImplementation.sol index bfcf52c44..19b9706d4 100644 --- a/contracts/mocks/RegressionImplementation.sol +++ b/contracts/mocks/RegressionImplementation.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {Initializable} from "../proxy/utils/Initializable.sol"; diff --git a/contracts/mocks/SingleInheritanceInitializableMocks.sol b/contracts/mocks/SingleInheritanceInitializableMocks.sol index d755826d9..0bd3c614f 100644 --- a/contracts/mocks/SingleInheritanceInitializableMocks.sol +++ b/contracts/mocks/SingleInheritanceInitializableMocks.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {Initializable} from "../proxy/utils/Initializable.sol"; diff --git a/contracts/mocks/StorageSlotMock.sol b/contracts/mocks/StorageSlotMock.sol index 7bbd7b232..7de9a732c 100644 --- a/contracts/mocks/StorageSlotMock.sol +++ b/contracts/mocks/StorageSlotMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {StorageSlot} from "../utils/StorageSlot.sol"; diff --git a/contracts/mocks/TimelockReentrant.sol b/contracts/mocks/TimelockReentrant.sol index de55a87f3..aab676a50 100644 --- a/contracts/mocks/TimelockReentrant.sol +++ b/contracts/mocks/TimelockReentrant.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {Address} from "../utils/Address.sol"; diff --git a/contracts/mocks/UpgreadeableBeaconMock.sol b/contracts/mocks/UpgreadeableBeaconMock.sol index 02c138d4d..4bee5c0f2 100644 --- a/contracts/mocks/UpgreadeableBeaconMock.sol +++ b/contracts/mocks/UpgreadeableBeaconMock.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {IBeacon} from "../proxy/beacon/IBeacon.sol"; diff --git a/contracts/mocks/VotesMock.sol b/contracts/mocks/VotesMock.sol index 0bac0087c..db9e9f52c 100644 --- a/contracts/mocks/VotesMock.sol +++ b/contracts/mocks/VotesMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {Votes} from "../governance/utils/Votes.sol"; diff --git a/contracts/mocks/compound/CompTimelock.sol b/contracts/mocks/compound/CompTimelock.sol index 9dc586ddb..c72ed0833 100644 --- a/contracts/mocks/compound/CompTimelock.sol +++ b/contracts/mocks/compound/CompTimelock.sol @@ -24,7 +24,7 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; contract CompTimelock { event NewAdmin(address indexed newAdmin); diff --git a/contracts/mocks/docs/ERC4626Fees.sol b/contracts/mocks/docs/ERC4626Fees.sol index e7501e9b4..17bc92d7c 100644 --- a/contracts/mocks/docs/ERC4626Fees.sol +++ b/contracts/mocks/docs/ERC4626Fees.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {IERC20} from "../../token/ERC20/IERC20.sol"; import {ERC4626} from "../../token/ERC20/extensions/ERC4626.sol"; diff --git a/contracts/mocks/docs/governance/MyGovernor.sol b/contracts/mocks/docs/governance/MyGovernor.sol index f788f04a6..2ac6d2cef 100644 --- a/contracts/mocks/docs/governance/MyGovernor.sol +++ b/contracts/mocks/docs/governance/MyGovernor.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {IGovernor, Governor} from "../../../governance/Governor.sol"; import {GovernorCompatibilityBravo} from "../../../governance/compatibility/GovernorCompatibilityBravo.sol"; diff --git a/contracts/mocks/docs/governance/MyToken.sol b/contracts/mocks/docs/governance/MyToken.sol index 7a700212b..cfb167557 100644 --- a/contracts/mocks/docs/governance/MyToken.sol +++ b/contracts/mocks/docs/governance/MyToken.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {ERC20} from "../../../token/ERC20/ERC20.sol"; import {ERC20Permit} from "../../../token/ERC20/extensions/ERC20Permit.sol"; diff --git a/contracts/mocks/docs/governance/MyTokenTimestampBased.sol b/contracts/mocks/docs/governance/MyTokenTimestampBased.sol index 100d10f51..7c0d32956 100644 --- a/contracts/mocks/docs/governance/MyTokenTimestampBased.sol +++ b/contracts/mocks/docs/governance/MyTokenTimestampBased.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {ERC20} from "../../../token/ERC20/ERC20.sol"; import {ERC20Permit} from "../../../token/ERC20/extensions/ERC20Permit.sol"; diff --git a/contracts/mocks/docs/governance/MyTokenWrapped.sol b/contracts/mocks/docs/governance/MyTokenWrapped.sol index 94182187a..c9d567dc5 100644 --- a/contracts/mocks/docs/governance/MyTokenWrapped.sol +++ b/contracts/mocks/docs/governance/MyTokenWrapped.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {IERC20, ERC20} from "../../../token/ERC20/ERC20.sol"; import {ERC20Permit} from "../../../token/ERC20/extensions/ERC20Permit.sol"; diff --git a/contracts/mocks/governance/GovernorCompatibilityBravoMock.sol b/contracts/mocks/governance/GovernorCompatibilityBravoMock.sol index 516dd8ff2..041c85f28 100644 --- a/contracts/mocks/governance/GovernorCompatibilityBravoMock.sol +++ b/contracts/mocks/governance/GovernorCompatibilityBravoMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {IGovernor, Governor} from "../../governance/Governor.sol"; import {GovernorCompatibilityBravo} from "../../governance/compatibility/GovernorCompatibilityBravo.sol"; diff --git a/contracts/mocks/governance/GovernorMock.sol b/contracts/mocks/governance/GovernorMock.sol index 64024b2ce..69668d285 100644 --- a/contracts/mocks/governance/GovernorMock.sol +++ b/contracts/mocks/governance/GovernorMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {Governor} from "../../governance/Governor.sol"; import {GovernorSettings} from "../../governance/extensions/GovernorSettings.sol"; diff --git a/contracts/mocks/governance/GovernorPreventLateQuorumMock.sol b/contracts/mocks/governance/GovernorPreventLateQuorumMock.sol index 785e5df5c..fde0863ce 100644 --- a/contracts/mocks/governance/GovernorPreventLateQuorumMock.sol +++ b/contracts/mocks/governance/GovernorPreventLateQuorumMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {Governor} from "../../governance/Governor.sol"; import {GovernorPreventLateQuorum} from "../../governance/extensions/GovernorPreventLateQuorum.sol"; diff --git a/contracts/mocks/governance/GovernorTimelockCompoundMock.sol b/contracts/mocks/governance/GovernorTimelockCompoundMock.sol index 102dffae8..d17d4428a 100644 --- a/contracts/mocks/governance/GovernorTimelockCompoundMock.sol +++ b/contracts/mocks/governance/GovernorTimelockCompoundMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {IGovernor, Governor} from "../../governance/Governor.sol"; import {GovernorTimelockCompound} from "../../governance/extensions/GovernorTimelockCompound.sol"; diff --git a/contracts/mocks/governance/GovernorTimelockControlMock.sol b/contracts/mocks/governance/GovernorTimelockControlMock.sol index 5c83acd64..7182a38be 100644 --- a/contracts/mocks/governance/GovernorTimelockControlMock.sol +++ b/contracts/mocks/governance/GovernorTimelockControlMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {IGovernor, Governor} from "../../governance/Governor.sol"; import {GovernorTimelockControl} from "../../governance/extensions/GovernorTimelockControl.sol"; diff --git a/contracts/mocks/governance/GovernorVoteMock.sol b/contracts/mocks/governance/GovernorVoteMock.sol index ecb80a4fb..e6949b5b2 100644 --- a/contracts/mocks/governance/GovernorVoteMock.sol +++ b/contracts/mocks/governance/GovernorVoteMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {GovernorCountingSimple} from "../../governance/extensions/GovernorCountingSimple.sol"; import {GovernorVotes} from "../../governance/extensions/GovernorVotes.sol"; diff --git a/contracts/mocks/governance/GovernorWithParamsMock.sol b/contracts/mocks/governance/GovernorWithParamsMock.sol index f67bd4b71..d535f811c 100644 --- a/contracts/mocks/governance/GovernorWithParamsMock.sol +++ b/contracts/mocks/governance/GovernorWithParamsMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {Governor} from "../../governance/Governor.sol"; import {GovernorCountingSimple} from "../../governance/extensions/GovernorCountingSimple.sol"; diff --git a/contracts/mocks/proxy/BadBeacon.sol b/contracts/mocks/proxy/BadBeacon.sol index 2c8ffe291..f3153a843 100644 --- a/contracts/mocks/proxy/BadBeacon.sol +++ b/contracts/mocks/proxy/BadBeacon.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; contract BadBeaconNoImpl {} diff --git a/contracts/mocks/proxy/ClashingImplementation.sol b/contracts/mocks/proxy/ClashingImplementation.sol index 4cb5d2326..43d5a34f2 100644 --- a/contracts/mocks/proxy/ClashingImplementation.sol +++ b/contracts/mocks/proxy/ClashingImplementation.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; /** * @dev Implementation contract with a payable changeAdmin(address) function made to clash with diff --git a/contracts/mocks/proxy/UUPSUpgradeableMock.sol b/contracts/mocks/proxy/UUPSUpgradeableMock.sol index 769c89946..a5f2d4a25 100644 --- a/contracts/mocks/proxy/UUPSUpgradeableMock.sol +++ b/contracts/mocks/proxy/UUPSUpgradeableMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {UUPSUpgradeable} from "../../proxy/utils/UUPSUpgradeable.sol"; import {ERC1967Utils} from "../../proxy/ERC1967/ERC1967Utils.sol"; diff --git a/contracts/mocks/token/ERC1155ReceiverMock.sol b/contracts/mocks/token/ERC1155ReceiverMock.sol index b02617333..2a85d1dfa 100644 --- a/contracts/mocks/token/ERC1155ReceiverMock.sol +++ b/contracts/mocks/token/ERC1155ReceiverMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {IERC1155Receiver} from "../../token/ERC1155/IERC1155Receiver.sol"; import {ERC165} from "../../utils/introspection/ERC165.sol"; diff --git a/contracts/mocks/token/ERC20ApprovalMock.sol b/contracts/mocks/token/ERC20ApprovalMock.sol index 3caa7d0d2..ff33a36df 100644 --- a/contracts/mocks/token/ERC20ApprovalMock.sol +++ b/contracts/mocks/token/ERC20ApprovalMock.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {ERC20} from "../../token/ERC20/ERC20.sol"; diff --git a/contracts/mocks/token/ERC20DecimalsMock.sol b/contracts/mocks/token/ERC20DecimalsMock.sol index 3ecc77f4e..a26e1f52b 100644 --- a/contracts/mocks/token/ERC20DecimalsMock.sol +++ b/contracts/mocks/token/ERC20DecimalsMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {ERC20} from "../../token/ERC20/ERC20.sol"; diff --git a/contracts/mocks/token/ERC20ExcessDecimalsMock.sol b/contracts/mocks/token/ERC20ExcessDecimalsMock.sol index bed8c5fcf..4627efd37 100644 --- a/contracts/mocks/token/ERC20ExcessDecimalsMock.sol +++ b/contracts/mocks/token/ERC20ExcessDecimalsMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; contract ERC20ExcessDecimalsMock { function decimals() public pure returns (uint256) { diff --git a/contracts/mocks/token/ERC20FlashMintMock.sol b/contracts/mocks/token/ERC20FlashMintMock.sol index 1831f53bd..508573c2b 100644 --- a/contracts/mocks/token/ERC20FlashMintMock.sol +++ b/contracts/mocks/token/ERC20FlashMintMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {ERC20FlashMint} from "../../token/ERC20/extensions/ERC20FlashMint.sol"; diff --git a/contracts/mocks/token/ERC20ForceApproveMock.sol b/contracts/mocks/token/ERC20ForceApproveMock.sol index f6f71a7e8..9fbca9727 100644 --- a/contracts/mocks/token/ERC20ForceApproveMock.sol +++ b/contracts/mocks/token/ERC20ForceApproveMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {ERC20} from "../../token/ERC20/ERC20.sol"; diff --git a/contracts/mocks/token/ERC20Mock.sol b/contracts/mocks/token/ERC20Mock.sol index cd5f9d444..39ab12952 100644 --- a/contracts/mocks/token/ERC20Mock.sol +++ b/contracts/mocks/token/ERC20Mock.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {ERC20} from "../../token/ERC20/ERC20.sol"; diff --git a/contracts/mocks/token/ERC20MulticallMock.sol b/contracts/mocks/token/ERC20MulticallMock.sol index 87aaf8923..dce3e7056 100644 --- a/contracts/mocks/token/ERC20MulticallMock.sol +++ b/contracts/mocks/token/ERC20MulticallMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {ERC20} from "../../token/ERC20/ERC20.sol"; import {Multicall} from "../../utils/Multicall.sol"; diff --git a/contracts/mocks/token/ERC20NoReturnMock.sol b/contracts/mocks/token/ERC20NoReturnMock.sol index e7f234cff..2129537b5 100644 --- a/contracts/mocks/token/ERC20NoReturnMock.sol +++ b/contracts/mocks/token/ERC20NoReturnMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {ERC20} from "../../token/ERC20/ERC20.sol"; diff --git a/contracts/mocks/token/ERC20PermitNoRevertMock.sol b/contracts/mocks/token/ERC20PermitNoRevertMock.sol index 63ae363fa..64e82b6f3 100644 --- a/contracts/mocks/token/ERC20PermitNoRevertMock.sol +++ b/contracts/mocks/token/ERC20PermitNoRevertMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {ERC20Permit} from "../../token/ERC20/extensions/ERC20Permit.sol"; diff --git a/contracts/mocks/token/ERC20Reentrant.sol b/contracts/mocks/token/ERC20Reentrant.sol index 063e9f3d5..813913f75 100644 --- a/contracts/mocks/token/ERC20Reentrant.sol +++ b/contracts/mocks/token/ERC20Reentrant.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {ERC20} from "../../token/ERC20/ERC20.sol"; import {Address} from "../../utils/Address.sol"; diff --git a/contracts/mocks/token/ERC20ReturnFalseMock.sol b/contracts/mocks/token/ERC20ReturnFalseMock.sol index e8014b87b..94bff32f1 100644 --- a/contracts/mocks/token/ERC20ReturnFalseMock.sol +++ b/contracts/mocks/token/ERC20ReturnFalseMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {ERC20} from "../../token/ERC20/ERC20.sol"; diff --git a/contracts/mocks/token/ERC20VotesLegacyMock.sol b/contracts/mocks/token/ERC20VotesLegacyMock.sol index 6a2078510..2ddc25c53 100644 --- a/contracts/mocks/token/ERC20VotesLegacyMock.sol +++ b/contracts/mocks/token/ERC20VotesLegacyMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {ERC20Permit} from "../../token/ERC20/extensions/ERC20Permit.sol"; import {Math} from "../../utils/math/Math.sol"; diff --git a/contracts/mocks/token/ERC4626LimitsMock.sol b/contracts/mocks/token/ERC4626LimitsMock.sol index fe5d724c8..a845365af 100644 --- a/contracts/mocks/token/ERC4626LimitsMock.sol +++ b/contracts/mocks/token/ERC4626LimitsMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {ERC4626} from "../../token/ERC20/extensions/ERC4626.sol"; diff --git a/contracts/mocks/token/ERC4626Mock.sol b/contracts/mocks/token/ERC4626Mock.sol index 151606ca5..22ac5e8c7 100644 --- a/contracts/mocks/token/ERC4626Mock.sol +++ b/contracts/mocks/token/ERC4626Mock.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {IERC20, ERC20} from "../../token/ERC20/ERC20.sol"; import {ERC4626} from "../../token/ERC20/extensions/ERC4626.sol"; diff --git a/contracts/mocks/token/ERC4626OffsetMock.sol b/contracts/mocks/token/ERC4626OffsetMock.sol index b1dac7d5d..3dde0952c 100644 --- a/contracts/mocks/token/ERC4626OffsetMock.sol +++ b/contracts/mocks/token/ERC4626OffsetMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {ERC4626} from "../../token/ERC20/extensions/ERC4626.sol"; diff --git a/contracts/mocks/token/ERC4646FeesMock.sol b/contracts/mocks/token/ERC4646FeesMock.sol index 081b03a61..368b078ec 100644 --- a/contracts/mocks/token/ERC4646FeesMock.sol +++ b/contracts/mocks/token/ERC4646FeesMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {ERC4626Fees} from "../docs/ERC4626Fees.sol"; diff --git a/contracts/mocks/token/ERC721ConsecutiveEnumerableMock.sol b/contracts/mocks/token/ERC721ConsecutiveEnumerableMock.sol index 081b8d99d..9c81549be 100644 --- a/contracts/mocks/token/ERC721ConsecutiveEnumerableMock.sol +++ b/contracts/mocks/token/ERC721ConsecutiveEnumerableMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {ERC721} from "../../token/ERC721/ERC721.sol"; import {ERC721Consecutive} from "../../token/ERC721/extensions/ERC721Consecutive.sol"; diff --git a/contracts/mocks/token/ERC721ConsecutiveMock.sol b/contracts/mocks/token/ERC721ConsecutiveMock.sol index 166959e92..0054d7511 100644 --- a/contracts/mocks/token/ERC721ConsecutiveMock.sol +++ b/contracts/mocks/token/ERC721ConsecutiveMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {ERC721} from "../../token/ERC721/ERC721.sol"; import {ERC721Consecutive} from "../../token/ERC721/extensions/ERC721Consecutive.sol"; diff --git a/contracts/mocks/token/ERC721ReceiverMock.sol b/contracts/mocks/token/ERC721ReceiverMock.sol index f1c842e49..14120f5d1 100644 --- a/contracts/mocks/token/ERC721ReceiverMock.sol +++ b/contracts/mocks/token/ERC721ReceiverMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {IERC721Receiver} from "../../token/ERC721/IERC721Receiver.sol"; diff --git a/contracts/mocks/token/ERC721URIStorageMock.sol b/contracts/mocks/token/ERC721URIStorageMock.sol index 569a1c0f4..254435e07 100644 --- a/contracts/mocks/token/ERC721URIStorageMock.sol +++ b/contracts/mocks/token/ERC721URIStorageMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {ERC721URIStorage} from "../../token/ERC721/extensions/ERC721URIStorage.sol"; diff --git a/contracts/mocks/token/VotesTimestamp.sol b/contracts/mocks/token/VotesTimestamp.sol index b54dbda97..78fdfae9c 100644 --- a/contracts/mocks/token/VotesTimestamp.sol +++ b/contracts/mocks/token/VotesTimestamp.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {ERC20Votes} from "../../token/ERC20/extensions/ERC20Votes.sol"; import {ERC721Votes} from "../../token/ERC721/extensions/ERC721Votes.sol"; diff --git a/contracts/proxy/Clones.sol b/contracts/proxy/Clones.sol index fea1db13a..ae7525fe4 100644 --- a/contracts/proxy/Clones.sol +++ b/contracts/proxy/Clones.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (proxy/Clones.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; /** * @dev https://eips.ethereum.org/EIPS/eip-1167[EIP 1167] is a standard for diff --git a/contracts/proxy/ERC1967/ERC1967Proxy.sol b/contracts/proxy/ERC1967/ERC1967Proxy.sol index d2a927d58..d4104ccfb 100644 --- a/contracts/proxy/ERC1967/ERC1967Proxy.sol +++ b/contracts/proxy/ERC1967/ERC1967Proxy.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.7.0) (proxy/ERC1967/ERC1967Proxy.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {Proxy} from "../Proxy.sol"; import {ERC1967Utils} from "./ERC1967Utils.sol"; diff --git a/contracts/proxy/Proxy.sol b/contracts/proxy/Proxy.sol index 8c8925b9b..3644197fa 100644 --- a/contracts/proxy/Proxy.sol +++ b/contracts/proxy/Proxy.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.6.0) (proxy/Proxy.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; /** * @dev This abstract contract provides a fallback function that delegates all calls to another contract using the EVM diff --git a/contracts/proxy/beacon/BeaconProxy.sol b/contracts/proxy/beacon/BeaconProxy.sol index b519b9caf..9cf8dbf70 100644 --- a/contracts/proxy/beacon/BeaconProxy.sol +++ b/contracts/proxy/beacon/BeaconProxy.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.7.0) (proxy/beacon/BeaconProxy.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {IBeacon} from "./IBeacon.sol"; import {Proxy} from "../Proxy.sol"; diff --git a/contracts/proxy/beacon/IBeacon.sol b/contracts/proxy/beacon/IBeacon.sol index fcd655d6c..f5e9f7981 100644 --- a/contracts/proxy/beacon/IBeacon.sol +++ b/contracts/proxy/beacon/IBeacon.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (proxy/beacon/IBeacon.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; /** * @dev This is the interface that {BeaconProxy} expects of its beacon. diff --git a/contracts/proxy/beacon/UpgradeableBeacon.sol b/contracts/proxy/beacon/UpgradeableBeacon.sol index 0c72ddf63..81ce50902 100644 --- a/contracts/proxy/beacon/UpgradeableBeacon.sol +++ b/contracts/proxy/beacon/UpgradeableBeacon.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (proxy/beacon/UpgradeableBeacon.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {IBeacon} from "./IBeacon.sol"; import {Ownable} from "../../access/Ownable.sol"; diff --git a/contracts/proxy/transparent/ProxyAdmin.sol b/contracts/proxy/transparent/ProxyAdmin.sol index fd4a82d12..c0e6780f4 100644 --- a/contracts/proxy/transparent/ProxyAdmin.sol +++ b/contracts/proxy/transparent/ProxyAdmin.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.8.3) (proxy/transparent/ProxyAdmin.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {ITransparentUpgradeableProxy} from "./TransparentUpgradeableProxy.sol"; import {Ownable} from "../../access/Ownable.sol"; diff --git a/contracts/proxy/transparent/TransparentUpgradeableProxy.sol b/contracts/proxy/transparent/TransparentUpgradeableProxy.sol index fc3574642..b51d2973e 100644 --- a/contracts/proxy/transparent/TransparentUpgradeableProxy.sol +++ b/contracts/proxy/transparent/TransparentUpgradeableProxy.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (proxy/transparent/TransparentUpgradeableProxy.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {ERC1967Utils} from "../ERC1967/ERC1967Utils.sol"; import {ERC1967Proxy} from "../ERC1967/ERC1967Proxy.sol"; diff --git a/contracts/proxy/utils/Initializable.sol b/contracts/proxy/utils/Initializable.sol index 43f82feca..53a34b3fe 100644 --- a/contracts/proxy/utils/Initializable.sol +++ b/contracts/proxy/utils/Initializable.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (proxy/utils/Initializable.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; /** * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed diff --git a/contracts/proxy/utils/UUPSUpgradeable.sol b/contracts/proxy/utils/UUPSUpgradeable.sol index fd41e37ec..7ad0c9abc 100644 --- a/contracts/proxy/utils/UUPSUpgradeable.sol +++ b/contracts/proxy/utils/UUPSUpgradeable.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (proxy/utils/UUPSUpgradeable.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {IERC1822Proxiable} from "../../interfaces/draft-IERC1822.sol"; import {ERC1967Utils} from "../ERC1967/ERC1967Utils.sol"; diff --git a/contracts/security/Pausable.sol b/contracts/security/Pausable.sol index 7a54b2511..96f80eccc 100644 --- a/contracts/security/Pausable.sol +++ b/contracts/security/Pausable.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.7.0) (security/Pausable.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {Context} from "../utils/Context.sol"; diff --git a/contracts/security/ReentrancyGuard.sol b/contracts/security/ReentrancyGuard.sol index 40ae5b050..37a63d763 100644 --- a/contracts/security/ReentrancyGuard.sol +++ b/contracts/security/ReentrancyGuard.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (security/ReentrancyGuard.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; /** * @dev Contract module that helps prevent reentrant calls to a function. diff --git a/contracts/token/ERC1155/ERC1155.sol b/contracts/token/ERC1155/ERC1155.sol index 7b1c9cfff..6a65a3296 100644 --- a/contracts/token/ERC1155/ERC1155.sol +++ b/contracts/token/ERC1155/ERC1155.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC1155/ERC1155.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {IERC1155} from "./IERC1155.sol"; import {IERC1155Receiver} from "./IERC1155Receiver.sol"; diff --git a/contracts/token/ERC1155/IERC1155.sol b/contracts/token/ERC1155/IERC1155.sol index 19b626c9a..ac931704f 100644 --- a/contracts/token/ERC1155/IERC1155.sol +++ b/contracts/token/ERC1155/IERC1155.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC1155/IERC1155.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {IERC165} from "../../utils/introspection/IERC165.sol"; diff --git a/contracts/token/ERC1155/IERC1155Receiver.sol b/contracts/token/ERC1155/IERC1155Receiver.sol index ce3265246..6517621b7 100644 --- a/contracts/token/ERC1155/IERC1155Receiver.sol +++ b/contracts/token/ERC1155/IERC1155Receiver.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.5.0) (token/ERC1155/IERC1155Receiver.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {IERC165} from "../../utils/introspection/IERC165.sol"; diff --git a/contracts/token/ERC1155/extensions/ERC1155Burnable.sol b/contracts/token/ERC1155/extensions/ERC1155Burnable.sol index 3696d1bbe..57f03f699 100644 --- a/contracts/token/ERC1155/extensions/ERC1155Burnable.sol +++ b/contracts/token/ERC1155/extensions/ERC1155Burnable.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC1155/extensions/ERC1155Burnable.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {ERC1155} from "../ERC1155.sol"; diff --git a/contracts/token/ERC1155/extensions/ERC1155Pausable.sol b/contracts/token/ERC1155/extensions/ERC1155Pausable.sol index a51f21599..914420ccc 100644 --- a/contracts/token/ERC1155/extensions/ERC1155Pausable.sol +++ b/contracts/token/ERC1155/extensions/ERC1155Pausable.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.8.2) (token/ERC1155/extensions/ERC1155Pausable.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {ERC1155} from "../ERC1155.sol"; import {Pausable} from "../../../security/Pausable.sol"; diff --git a/contracts/token/ERC1155/extensions/ERC1155Supply.sol b/contracts/token/ERC1155/extensions/ERC1155Supply.sol index ef2736339..8553b356a 100644 --- a/contracts/token/ERC1155/extensions/ERC1155Supply.sol +++ b/contracts/token/ERC1155/extensions/ERC1155Supply.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.6.0) (token/ERC1155/extensions/ERC1155Supply.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {ERC1155} from "../ERC1155.sol"; diff --git a/contracts/token/ERC1155/extensions/ERC1155URIStorage.sol b/contracts/token/ERC1155/extensions/ERC1155URIStorage.sol index 2e502fd35..09f777082 100644 --- a/contracts/token/ERC1155/extensions/ERC1155URIStorage.sol +++ b/contracts/token/ERC1155/extensions/ERC1155URIStorage.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.6.0) (token/ERC1155/extensions/ERC1155URIStorage.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {Strings} from "../../../utils/Strings.sol"; import {ERC1155} from "../ERC1155.sol"; diff --git a/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol b/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol index d0391d371..9b06fb6ad 100644 --- a/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol +++ b/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (token/ERC1155/extensions/IERC1155MetadataURI.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {IERC1155} from "../IERC1155.sol"; diff --git a/contracts/token/ERC1155/utils/ERC1155Holder.sol b/contracts/token/ERC1155/utils/ERC1155Holder.sol index 908ad82c5..7c8d470e0 100644 --- a/contracts/token/ERC1155/utils/ERC1155Holder.sol +++ b/contracts/token/ERC1155/utils/ERC1155Holder.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.5.0) (token/ERC1155/utils/ERC1155Holder.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {IERC165, ERC165} from "../../../utils/introspection/ERC165.sol"; import {IERC1155Receiver} from "../IERC1155Receiver.sol"; diff --git a/contracts/token/ERC20/ERC20.sol b/contracts/token/ERC20/ERC20.sol index abaf258c8..8d4d60465 100644 --- a/contracts/token/ERC20/ERC20.sol +++ b/contracts/token/ERC20/ERC20.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/ERC20.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {IERC20} from "./IERC20.sol"; import {IERC20Metadata} from "./extensions/IERC20Metadata.sol"; diff --git a/contracts/token/ERC20/IERC20.sol b/contracts/token/ERC20/IERC20.sol index eed63a606..77ca716bd 100644 --- a/contracts/token/ERC20/IERC20.sol +++ b/contracts/token/ERC20/IERC20.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; /** * @dev Interface of the ERC20 standard as defined in the EIP. diff --git a/contracts/token/ERC20/extensions/ERC20Burnable.sol b/contracts/token/ERC20/extensions/ERC20Burnable.sol index e5b43a780..6233e8c10 100644 --- a/contracts/token/ERC20/extensions/ERC20Burnable.sol +++ b/contracts/token/ERC20/extensions/ERC20Burnable.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.5.0) (token/ERC20/extensions/ERC20Burnable.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {ERC20} from "../ERC20.sol"; import {Context} from "../../../utils/Context.sol"; diff --git a/contracts/token/ERC20/extensions/ERC20Capped.sol b/contracts/token/ERC20/extensions/ERC20Capped.sol index 943d01644..523c2670c 100644 --- a/contracts/token/ERC20/extensions/ERC20Capped.sol +++ b/contracts/token/ERC20/extensions/ERC20Capped.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/ERC20Capped.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {ERC20} from "../ERC20.sol"; diff --git a/contracts/token/ERC20/extensions/ERC20FlashMint.sol b/contracts/token/ERC20/extensions/ERC20FlashMint.sol index 5d3e11f76..d98dcf397 100644 --- a/contracts/token/ERC20/extensions/ERC20FlashMint.sol +++ b/contracts/token/ERC20/extensions/ERC20FlashMint.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.8.0) (token/ERC20/extensions/ERC20FlashMint.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {IERC3156FlashBorrower} from "../../../interfaces/IERC3156FlashBorrower.sol"; import {IERC3156FlashLender} from "../../../interfaces/IERC3156FlashLender.sol"; diff --git a/contracts/token/ERC20/extensions/ERC20Pausable.sol b/contracts/token/ERC20/extensions/ERC20Pausable.sol index 6fff5058f..6ac0db2e2 100644 --- a/contracts/token/ERC20/extensions/ERC20Pausable.sol +++ b/contracts/token/ERC20/extensions/ERC20Pausable.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/ERC20Pausable.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {ERC20} from "../ERC20.sol"; import {Pausable} from "../../../security/Pausable.sol"; diff --git a/contracts/token/ERC20/extensions/ERC20Permit.sol b/contracts/token/ERC20/extensions/ERC20Permit.sol index c83c7dae7..8acb23ddb 100644 --- a/contracts/token/ERC20/extensions/ERC20Permit.sol +++ b/contracts/token/ERC20/extensions/ERC20Permit.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/ERC20Permit.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {IERC20Permit} from "./IERC20Permit.sol"; import {ERC20} from "../ERC20.sol"; diff --git a/contracts/token/ERC20/extensions/ERC20Votes.sol b/contracts/token/ERC20/extensions/ERC20Votes.sol index 74953d3d6..a4ded6b24 100644 --- a/contracts/token/ERC20/extensions/ERC20Votes.sol +++ b/contracts/token/ERC20/extensions/ERC20Votes.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/ERC20Votes.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {ERC20} from "../ERC20.sol"; import {Votes} from "../../../governance/utils/Votes.sol"; diff --git a/contracts/token/ERC20/extensions/ERC20Wrapper.sol b/contracts/token/ERC20/extensions/ERC20Wrapper.sol index fb5f314d7..0dfbbd4aa 100644 --- a/contracts/token/ERC20/extensions/ERC20Wrapper.sol +++ b/contracts/token/ERC20/extensions/ERC20Wrapper.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/ERC20Wrapper.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {IERC20, IERC20Metadata, ERC20} from "../ERC20.sol"; import {SafeERC20} from "../utils/SafeERC20.sol"; diff --git a/contracts/token/ERC20/extensions/ERC4626.sol b/contracts/token/ERC20/extensions/ERC4626.sol index ad3b5a170..adc4f661b 100644 --- a/contracts/token/ERC20/extensions/ERC4626.sol +++ b/contracts/token/ERC20/extensions/ERC4626.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/ERC4626.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {IERC20, IERC20Metadata, ERC20} from "../ERC20.sol"; import {SafeERC20} from "../utils/SafeERC20.sol"; diff --git a/contracts/token/ERC20/extensions/IERC20Metadata.sol b/contracts/token/ERC20/extensions/IERC20Metadata.sol index d79bbaa39..9056e34ed 100644 --- a/contracts/token/ERC20/extensions/IERC20Metadata.sol +++ b/contracts/token/ERC20/extensions/IERC20Metadata.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {IERC20} from "../IERC20.sol"; diff --git a/contracts/token/ERC20/extensions/IERC20Permit.sol b/contracts/token/ERC20/extensions/IERC20Permit.sol index eb3e3f005..237041006 100644 --- a/contracts/token/ERC20/extensions/IERC20Permit.sol +++ b/contracts/token/ERC20/extensions/IERC20Permit.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/IERC20Permit.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; /** * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in diff --git a/contracts/token/ERC20/utils/SafeERC20.sol b/contracts/token/ERC20/utils/SafeERC20.sol index 51468368f..fcdbbae76 100644 --- a/contracts/token/ERC20/utils/SafeERC20.sol +++ b/contracts/token/ERC20/utils/SafeERC20.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/utils/SafeERC20.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {IERC20} from "../IERC20.sol"; import {IERC20Permit} from "../extensions/IERC20Permit.sol"; diff --git a/contracts/token/ERC721/ERC721.sol b/contracts/token/ERC721/ERC721.sol index 25ac69fa9..932671bbd 100644 --- a/contracts/token/ERC721/ERC721.sol +++ b/contracts/token/ERC721/ERC721.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC721/ERC721.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {IERC721} from "./IERC721.sol"; import {IERC721Receiver} from "./IERC721Receiver.sol"; diff --git a/contracts/token/ERC721/IERC721.sol b/contracts/token/ERC721/IERC721.sol index 3b2db67cb..49471fe77 100644 --- a/contracts/token/ERC721/IERC721.sol +++ b/contracts/token/ERC721/IERC721.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC721/IERC721.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {IERC165} from "../../utils/introspection/IERC165.sol"; diff --git a/contracts/token/ERC721/IERC721Receiver.sol b/contracts/token/ERC721/IERC721Receiver.sol index 3839b6f6c..914f5995c 100644 --- a/contracts/token/ERC721/IERC721Receiver.sol +++ b/contracts/token/ERC721/IERC721Receiver.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.6.0) (token/ERC721/IERC721Receiver.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; /** * @title ERC721 token receiver interface diff --git a/contracts/token/ERC721/extensions/ERC721Burnable.sol b/contracts/token/ERC721/extensions/ERC721Burnable.sol index 607265328..299a12536 100644 --- a/contracts/token/ERC721/extensions/ERC721Burnable.sol +++ b/contracts/token/ERC721/extensions/ERC721Burnable.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.8.0) (token/ERC721/extensions/ERC721Burnable.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {ERC721} from "../ERC721.sol"; import {Context} from "../../../utils/Context.sol"; diff --git a/contracts/token/ERC721/extensions/ERC721Consecutive.sol b/contracts/token/ERC721/extensions/ERC721Consecutive.sol index 7c37e0764..4540d295b 100644 --- a/contracts/token/ERC721/extensions/ERC721Consecutive.sol +++ b/contracts/token/ERC721/extensions/ERC721Consecutive.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC721/extensions/ERC721Consecutive.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {ERC721} from "../ERC721.sol"; import {IERC2309} from "../../../interfaces/IERC2309.sol"; diff --git a/contracts/token/ERC721/extensions/ERC721Enumerable.sol b/contracts/token/ERC721/extensions/ERC721Enumerable.sol index 2e33123b6..acb57a7be 100644 --- a/contracts/token/ERC721/extensions/ERC721Enumerable.sol +++ b/contracts/token/ERC721/extensions/ERC721Enumerable.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.8.0) (token/ERC721/extensions/ERC721Enumerable.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {ERC721} from "../ERC721.sol"; import {IERC721Enumerable} from "./IERC721Enumerable.sol"; diff --git a/contracts/token/ERC721/extensions/ERC721Pausable.sol b/contracts/token/ERC721/extensions/ERC721Pausable.sol index 5777ac36e..50fb7d1ea 100644 --- a/contracts/token/ERC721/extensions/ERC721Pausable.sol +++ b/contracts/token/ERC721/extensions/ERC721Pausable.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.8.2) (token/ERC721/extensions/ERC721Pausable.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {ERC721} from "../ERC721.sol"; import {Pausable} from "../../../security/Pausable.sol"; diff --git a/contracts/token/ERC721/extensions/ERC721Royalty.sol b/contracts/token/ERC721/extensions/ERC721Royalty.sol index c4b8d371e..30906e605 100644 --- a/contracts/token/ERC721/extensions/ERC721Royalty.sol +++ b/contracts/token/ERC721/extensions/ERC721Royalty.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.8.0) (token/ERC721/extensions/ERC721Royalty.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {ERC721} from "../ERC721.sol"; import {ERC2981} from "../../common/ERC2981.sol"; diff --git a/contracts/token/ERC721/extensions/ERC721URIStorage.sol b/contracts/token/ERC721/extensions/ERC721URIStorage.sol index 737d28e27..f32babe0f 100644 --- a/contracts/token/ERC721/extensions/ERC721URIStorage.sol +++ b/contracts/token/ERC721/extensions/ERC721URIStorage.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC721/extensions/ERC721URIStorage.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {ERC721} from "../ERC721.sol"; import {Strings} from "../../../utils/Strings.sol"; diff --git a/contracts/token/ERC721/extensions/ERC721Votes.sol b/contracts/token/ERC721/extensions/ERC721Votes.sol index 0838010eb..4bef6ac5a 100644 --- a/contracts/token/ERC721/extensions/ERC721Votes.sol +++ b/contracts/token/ERC721/extensions/ERC721Votes.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC721/extensions/ERC721Votes.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {ERC721} from "../ERC721.sol"; import {Votes} from "../../../governance/utils/Votes.sol"; diff --git a/contracts/token/ERC721/extensions/ERC721Wrapper.sol b/contracts/token/ERC721/extensions/ERC721Wrapper.sol index f204c1079..aea8e58d7 100644 --- a/contracts/token/ERC721/extensions/ERC721Wrapper.sol +++ b/contracts/token/ERC721/extensions/ERC721Wrapper.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC721/extensions/ERC721Wrapper.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {IERC721, ERC721} from "../ERC721.sol"; import {IERC721Receiver} from "../IERC721Receiver.sol"; diff --git a/contracts/token/ERC721/extensions/IERC721Enumerable.sol b/contracts/token/ERC721/extensions/IERC721Enumerable.sol index 75b04581f..d490998e4 100644 --- a/contracts/token/ERC721/extensions/IERC721Enumerable.sol +++ b/contracts/token/ERC721/extensions/IERC721Enumerable.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.5.0) (token/ERC721/extensions/IERC721Enumerable.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {IERC721} from "../IERC721.sol"; diff --git a/contracts/token/ERC721/extensions/IERC721Metadata.sol b/contracts/token/ERC721/extensions/IERC721Metadata.sol index 3d2846231..9a0a67774 100644 --- a/contracts/token/ERC721/extensions/IERC721Metadata.sol +++ b/contracts/token/ERC721/extensions/IERC721Metadata.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (token/ERC721/extensions/IERC721Metadata.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {IERC721} from "../IERC721.sol"; diff --git a/contracts/token/ERC721/utils/ERC721Holder.sol b/contracts/token/ERC721/utils/ERC721Holder.sol index e1fad1e23..740e29c5f 100644 --- a/contracts/token/ERC721/utils/ERC721Holder.sol +++ b/contracts/token/ERC721/utils/ERC721Holder.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC721/utils/ERC721Holder.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {IERC721Receiver} from "../IERC721Receiver.sol"; diff --git a/contracts/token/common/ERC2981.sol b/contracts/token/common/ERC2981.sol index e683b41c5..b56e5f956 100644 --- a/contracts/token/common/ERC2981.sol +++ b/contracts/token/common/ERC2981.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (token/common/ERC2981.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {IERC2981} from "../../interfaces/IERC2981.sol"; import {IERC165, ERC165} from "../../utils/introspection/ERC165.sol"; diff --git a/contracts/utils/Address.sol b/contracts/utils/Address.sol index 3f5d0a55b..e3a71313c 100644 --- a/contracts/utils/Address.sol +++ b/contracts/utils/Address.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; /** * @dev Collection of functions related to the address type @@ -36,7 +36,7 @@ library Address { * IMPORTANT: because control is transferred to `recipient`, care must be * taken to not create reentrancy vulnerabilities. Consider using * {ReentrancyGuard} or the - * https://solidity.readthedocs.io/en/v0.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. + * https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. */ function sendValue(address payable recipient, uint256 amount) internal { if (address(this).balance < amount) { diff --git a/contracts/utils/Arrays.sol b/contracts/utils/Arrays.sol index b2eeaac50..e051d4b71 100644 --- a/contracts/utils/Arrays.sol +++ b/contracts/utils/Arrays.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (utils/Arrays.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {StorageSlot} from "./StorageSlot.sol"; import {Math} from "./math/Math.sol"; @@ -57,7 +57,7 @@ library Arrays { function unsafeAccess(address[] storage arr, uint256 pos) internal pure returns (StorageSlot.AddressSlot storage) { bytes32 slot; // We use assembly to calculate the storage slot of the element at index `pos` of the dynamic array `arr` - // following https://docs.soliditylang.org/en/v0.8.17/internals/layout_in_storage.html#mappings-and-dynamic-arrays. + // following https://docs.soliditylang.org/en/v0.8.20/internals/layout_in_storage.html#mappings-and-dynamic-arrays. /// @solidity memory-safe-assembly assembly { @@ -75,7 +75,7 @@ library Arrays { function unsafeAccess(bytes32[] storage arr, uint256 pos) internal pure returns (StorageSlot.Bytes32Slot storage) { bytes32 slot; // We use assembly to calculate the storage slot of the element at index `pos` of the dynamic array `arr` - // following https://docs.soliditylang.org/en/v0.8.17/internals/layout_in_storage.html#mappings-and-dynamic-arrays. + // following https://docs.soliditylang.org/en/v0.8.20/internals/layout_in_storage.html#mappings-and-dynamic-arrays. /// @solidity memory-safe-assembly assembly { @@ -93,7 +93,7 @@ library Arrays { function unsafeAccess(uint256[] storage arr, uint256 pos) internal pure returns (StorageSlot.Uint256Slot storage) { bytes32 slot; // We use assembly to calculate the storage slot of the element at index `pos` of the dynamic array `arr` - // following https://docs.soliditylang.org/en/v0.8.17/internals/layout_in_storage.html#mappings-and-dynamic-arrays. + // following https://docs.soliditylang.org/en/v0.8.20/internals/layout_in_storage.html#mappings-and-dynamic-arrays. /// @solidity memory-safe-assembly assembly { diff --git a/contracts/utils/Base64.sol b/contracts/utils/Base64.sol index ae73ec4af..bd3562bd5 100644 --- a/contracts/utils/Base64.sol +++ b/contracts/utils/Base64.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.7.0) (utils/Base64.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; /** * @dev Provides a set of functions to operate with Base64 strings. diff --git a/contracts/utils/Context.sol b/contracts/utils/Context.sol index 2d517987d..25e115925 100644 --- a/contracts/utils/Context.sol +++ b/contracts/utils/Context.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (utils/Context.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; /** * @dev Provides information about the current execution context, including the diff --git a/contracts/utils/Create2.sol b/contracts/utils/Create2.sol index 24d27ea0b..73bf43ddb 100644 --- a/contracts/utils/Create2.sol +++ b/contracts/utils/Create2.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (utils/Create2.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; /** * @dev Helper to make usage of the `CREATE2` EVM opcode easier and safer. diff --git a/contracts/utils/Multicall.sol b/contracts/utils/Multicall.sol index 12c01ef11..a9a3d3acf 100644 --- a/contracts/utils/Multicall.sol +++ b/contracts/utils/Multicall.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (utils/Multicall.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {Address} from "./Address.sol"; diff --git a/contracts/utils/Nonces.sol b/contracts/utils/Nonces.sol index d5458b10a..eb756b5e6 100644 --- a/contracts/utils/Nonces.sol +++ b/contracts/utils/Nonces.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; /** * @dev Provides tracking nonces for addresses. Nonces will only increment. diff --git a/contracts/utils/ShortStrings.sol b/contracts/utils/ShortStrings.sol index f90ccd77a..2fb97824d 100644 --- a/contracts/utils/ShortStrings.sol +++ b/contracts/utils/ShortStrings.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (utils/ShortStrings.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {StorageSlot} from "./StorageSlot.sol"; diff --git a/contracts/utils/StorageSlot.sol b/contracts/utils/StorageSlot.sol index cae1c8b68..c853c0e5f 100644 --- a/contracts/utils/StorageSlot.sol +++ b/contracts/utils/StorageSlot.sol @@ -2,7 +2,7 @@ // OpenZeppelin Contracts (last updated v4.9.0) (utils/StorageSlot.sol) // This file was procedurally generated from scripts/generate/templates/StorageSlot.js. -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; /** * @dev Library for reading and writing primitive types to specific storage slots. diff --git a/contracts/utils/Strings.sol b/contracts/utils/Strings.sol index fd8d66229..0037eee1b 100644 --- a/contracts/utils/Strings.sol +++ b/contracts/utils/Strings.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (utils/Strings.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {Math} from "./math/Math.sol"; import {SignedMath} from "./math/SignedMath.sol"; diff --git a/contracts/utils/cryptography/ECDSA.sol b/contracts/utils/cryptography/ECDSA.sol index 74064c889..4e8259472 100644 --- a/contracts/utils/cryptography/ECDSA.sol +++ b/contracts/utils/cryptography/ECDSA.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (utils/cryptography/ECDSA.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; /** * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations. diff --git a/contracts/utils/cryptography/EIP712.sol b/contracts/utils/cryptography/EIP712.sol index 3800804ab..58f8a2180 100644 --- a/contracts/utils/cryptography/EIP712.sol +++ b/contracts/utils/cryptography/EIP712.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (utils/cryptography/EIP712.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {MessageHashUtils} from "./MessageHashUtils.sol"; import {ShortStrings, ShortString} from "../ShortStrings.sol"; diff --git a/contracts/utils/cryptography/MerkleProof.sol b/contracts/utils/cryptography/MerkleProof.sol index 17a6384fe..b42a080c8 100644 --- a/contracts/utils/cryptography/MerkleProof.sol +++ b/contracts/utils/cryptography/MerkleProof.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.2) (utils/cryptography/MerkleProof.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; /** * @dev These functions deal with verification of Merkle Tree proofs. diff --git a/contracts/utils/cryptography/MessageHashUtils.sol b/contracts/utils/cryptography/MessageHashUtils.sol index 558e5e793..1a1bea731 100644 --- a/contracts/utils/cryptography/MessageHashUtils.sol +++ b/contracts/utils/cryptography/MessageHashUtils.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {Strings} from "../Strings.sol"; diff --git a/contracts/utils/cryptography/SignatureChecker.sol b/contracts/utils/cryptography/SignatureChecker.sol index f2cc2c4ed..a7f1750cf 100644 --- a/contracts/utils/cryptography/SignatureChecker.sol +++ b/contracts/utils/cryptography/SignatureChecker.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (utils/cryptography/SignatureChecker.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {ECDSA} from "./ECDSA.sol"; import {IERC1271} from "../../interfaces/IERC1271.sol"; diff --git a/contracts/utils/introspection/ERC165.sol b/contracts/utils/introspection/ERC165.sol index 56fe1b80a..71c8e4a4f 100644 --- a/contracts/utils/introspection/ERC165.sol +++ b/contracts/utils/introspection/ERC165.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {IERC165} from "./IERC165.sol"; diff --git a/contracts/utils/introspection/ERC165Checker.sol b/contracts/utils/introspection/ERC165Checker.sol index 86212695a..4d3948f0d 100644 --- a/contracts/utils/introspection/ERC165Checker.sol +++ b/contracts/utils/introspection/ERC165Checker.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (utils/introspection/ERC165Checker.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {IERC165} from "./IERC165.sol"; diff --git a/contracts/utils/introspection/IERC165.sol b/contracts/utils/introspection/IERC165.sol index da6f186d4..87e7490cd 100644 --- a/contracts/utils/introspection/IERC165.sol +++ b/contracts/utils/introspection/IERC165.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; /** * @dev Interface of the ERC165 standard, as defined in the diff --git a/contracts/utils/math/Math.sol b/contracts/utils/math/Math.sol index e3d7f799d..3e6f61bd1 100644 --- a/contracts/utils/math/Math.sol +++ b/contracts/utils/math/Math.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (utils/math/Math.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; /** * @dev Standard math utilities missing in the Solidity language. diff --git a/contracts/utils/math/SafeCast.sol b/contracts/utils/math/SafeCast.sol index 64f180cd2..cfb99eb75 100644 --- a/contracts/utils/math/SafeCast.sol +++ b/contracts/utils/math/SafeCast.sol @@ -2,7 +2,7 @@ // OpenZeppelin Contracts (last updated v4.8.0) (utils/math/SafeCast.sol) // This file was procedurally generated from scripts/generate/templates/SafeCast.js. -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; /** * @dev Wrappers over Solidity's uintXX/intXX casting operators with added overflow diff --git a/contracts/utils/math/SignedMath.sol b/contracts/utils/math/SignedMath.sol index 80413531d..cd8c88f25 100644 --- a/contracts/utils/math/SignedMath.sol +++ b/contracts/utils/math/SignedMath.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.8.0) (utils/math/SignedMath.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; /** * @dev Standard signed math utilities missing in the Solidity language. diff --git a/contracts/utils/structs/BitMaps.sol b/contracts/utils/structs/BitMaps.sol index 5b62d2f07..413b47e0c 100644 --- a/contracts/utils/structs/BitMaps.sol +++ b/contracts/utils/structs/BitMaps.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (utils/structs/BitMaps.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; /** * @dev Library for managing uint256 to bool mapping in a compact and efficient way, provided the keys are sequential. diff --git a/contracts/utils/structs/Checkpoints.sol b/contracts/utils/structs/Checkpoints.sol index 6c73a08d8..383f01af8 100644 --- a/contracts/utils/structs/Checkpoints.sol +++ b/contracts/utils/structs/Checkpoints.sol @@ -2,7 +2,7 @@ // OpenZeppelin Contracts (last updated v4.9.0) (utils/structs/Checkpoints.sol) // This file was procedurally generated from scripts/generate/templates/Checkpoints.js. -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {Math} from "../math/Math.sol"; diff --git a/contracts/utils/structs/DoubleEndedQueue.sol b/contracts/utils/structs/DoubleEndedQueue.sol index 928665bee..93f1b2d01 100644 --- a/contracts/utils/structs/DoubleEndedQueue.sol +++ b/contracts/utils/structs/DoubleEndedQueue.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (utils/structs/DoubleEndedQueue.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; /** * @dev A sequence of items with the ability to efficiently push and pop items (i.e. insert and remove) on both ends of diff --git a/contracts/utils/structs/EnumerableMap.sol b/contracts/utils/structs/EnumerableMap.sol index c0ad9a2c9..017072075 100644 --- a/contracts/utils/structs/EnumerableMap.sol +++ b/contracts/utils/structs/EnumerableMap.sol @@ -2,7 +2,7 @@ // OpenZeppelin Contracts (last updated v4.9.0) (utils/structs/EnumerableMap.sol) // This file was procedurally generated from scripts/generate/templates/EnumerableMap.js. -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {EnumerableSet} from "./EnumerableSet.sol"; diff --git a/contracts/utils/structs/EnumerableSet.sol b/contracts/utils/structs/EnumerableSet.sol index b10b3cdcd..272851b2f 100644 --- a/contracts/utils/structs/EnumerableSet.sol +++ b/contracts/utils/structs/EnumerableSet.sol @@ -2,7 +2,7 @@ // OpenZeppelin Contracts (last updated v4.9.0) (utils/structs/EnumerableSet.sol) // This file was procedurally generated from scripts/generate/templates/EnumerableSet.js. -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; /** * @dev Library for managing diff --git a/contracts/vendor/compound/ICompoundTimelock.sol b/contracts/vendor/compound/ICompoundTimelock.sol index 1b04290b4..edefc4e04 100644 --- a/contracts/vendor/compound/ICompoundTimelock.sol +++ b/contracts/vendor/compound/ICompoundTimelock.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.6.0) (vendor/compound/ICompoundTimelock.sol) -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; /** * https://github.com/compound-finance/compound-protocol/blob/master/contracts/Timelock.sol[Compound's timelock] interface diff --git a/docs/modules/ROOT/pages/access-control.adoc b/docs/modules/ROOT/pages/access-control.adoc index aa28376ad..a60a34388 100644 --- a/docs/modules/ROOT/pages/access-control.adoc +++ b/docs/modules/ROOT/pages/access-control.adoc @@ -13,7 +13,7 @@ OpenZeppelin Contracts provides xref:api:access.adoc#Ownable[`Ownable`] for impl ---- // contracts/MyContract.sol // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; @@ -62,7 +62,7 @@ Here's a simple example of using `AccessControl` in an xref:tokens.adoc#ERC20[`E ---- // contracts/MyToken.sol // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; @@ -94,7 +94,7 @@ Let's augment our ERC20 token example by also defining a 'burner' role, which le ---- // contracts/MyToken.sol // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; @@ -139,7 +139,7 @@ Let's take a look at the ERC20 token example, this time taking advantage of the ---- // contracts/MyToken.sol // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; diff --git a/docs/modules/ROOT/pages/erc1155.adoc b/docs/modules/ROOT/pages/erc1155.adoc index f1d2f88ce..5a4c91670 100644 --- a/docs/modules/ROOT/pages/erc1155.adoc +++ b/docs/modules/ROOT/pages/erc1155.adoc @@ -34,7 +34,7 @@ Here's what a contract for tokenized items might look like: ---- // contracts/GameItems.sol // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {ERC1155} from "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; @@ -134,7 +134,7 @@ In order for our contract to receive ERC1155 tokens we can inherit from the conv ---- // contracts/MyContract.sol // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {ERC1155Holder} from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol"; diff --git a/docs/modules/ROOT/pages/erc20.adoc b/docs/modules/ROOT/pages/erc20.adoc index 2ffa60f2a..2b85070a6 100644 --- a/docs/modules/ROOT/pages/erc20.adoc +++ b/docs/modules/ROOT/pages/erc20.adoc @@ -15,7 +15,7 @@ Here's what our GLD token might look like. ---- // contracts/GLDToken.sol // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; diff --git a/docs/modules/ROOT/pages/erc721.adoc b/docs/modules/ROOT/pages/erc721.adoc index cd4d14b8f..7481c6b62 100644 --- a/docs/modules/ROOT/pages/erc721.adoc +++ b/docs/modules/ROOT/pages/erc721.adoc @@ -14,7 +14,7 @@ Here's what a contract for tokenized items might look like: ---- // contracts/GameItem.sol // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {ERC721URIStorage} from "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; diff --git a/docs/modules/ROOT/pages/extending-contracts.adoc b/docs/modules/ROOT/pages/extending-contracts.adoc index ac6730984..1c13d6b4b 100644 --- a/docs/modules/ROOT/pages/extending-contracts.adoc +++ b/docs/modules/ROOT/pages/extending-contracts.adoc @@ -20,7 +20,7 @@ For example, imagine you want to change xref:api:access.adoc#AccessControl[`Acce ```solidity // contracts/ModifiedAccessControl.sol // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; @@ -48,7 +48,7 @@ Here is a modified version of xref:api:access.adoc#AccessControl[`AccessControl` ```solidity // contracts/ModifiedAccessControl.sol // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; @@ -80,7 +80,7 @@ Hooks are simply functions that are called before or after some action takes pla Here's how you would implement the `IERC721Receiver` pattern in `ERC20`, using the xref:api:token/ERC20.adoc#ERC20-_beforeTokenTransfer-address-address-uint256-[`_beforeTokenTransfer`] hook: ```solidity -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; diff --git a/docs/modules/ROOT/pages/governance.adoc b/docs/modules/ROOT/pages/governance.adoc index cae2f14f6..ce5a9c340 100644 --- a/docs/modules/ROOT/pages/governance.adoc +++ b/docs/modules/ROOT/pages/governance.adoc @@ -198,7 +198,7 @@ The Governor will automatically detect the clock mode used by the token and adap ```solidity // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {Governor} from "@openzeppelin/contracts/governance/Governor.sol"; import {GovernorCompatibilityBravo} from "@openzeppelin/contracts/governance/compatibility/GovernorCompatibilityBravo.sol"; diff --git a/docs/modules/ROOT/pages/index.adoc b/docs/modules/ROOT/pages/index.adoc index b24412ae7..ff720f93c 100644 --- a/docs/modules/ROOT/pages/index.adoc +++ b/docs/modules/ROOT/pages/index.adoc @@ -26,7 +26,7 @@ Once installed, you can use the contracts in the library by importing them: ---- // contracts/MyNFT.sol // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; diff --git a/docs/modules/ROOT/pages/utilities.adoc b/docs/modules/ROOT/pages/utilities.adoc index 2ac7b63c9..487b47a80 100644 --- a/docs/modules/ROOT/pages/utilities.adoc +++ b/docs/modules/ROOT/pages/utilities.adoc @@ -155,7 +155,7 @@ Consider this dummy contract: ---- // contracts/Box.sol // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import "@openzeppelin/contracts/utils/Multicall.sol"; diff --git a/hardhat.config.js b/hardhat.config.js index 8ee5d05e5..1c87aac94 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -3,7 +3,7 @@ // - COVERAGE: enable coverage report // - ENABLE_GAS_REPORT: enable gas report // - COMPILE_MODE: production modes enables optimizations (default: development) -// - COMPILE_VERSION: compiler version (default: 0.8.9) +// - COMPILE_VERSION: compiler version (default: 0.8.20) // - COINMARKETCAP: coinmarkercat api key for USD value in gas report const fs = require('fs'); diff --git a/scripts/generate/templates/Checkpoints.js b/scripts/generate/templates/Checkpoints.js index 3bd4589d2..73c9ab53e 100644 --- a/scripts/generate/templates/Checkpoints.js +++ b/scripts/generate/templates/Checkpoints.js @@ -3,7 +3,7 @@ const { OPTS } = require('./Checkpoints.opts.js'); // TEMPLATE const header = `\ -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {Math} from "../math/Math.sol"; diff --git a/scripts/generate/templates/Checkpoints.t.js b/scripts/generate/templates/Checkpoints.t.js index 492e5f8c2..d21beb53e 100644 --- a/scripts/generate/templates/Checkpoints.t.js +++ b/scripts/generate/templates/Checkpoints.t.js @@ -4,7 +4,7 @@ const { OPTS } = require('./Checkpoints.opts.js'); // TEMPLATE const header = `\ -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {Test} from "forge-std/Test.sol"; import {SafeCast} from "../../../contracts/utils/math/SafeCast.sol"; diff --git a/scripts/generate/templates/EnumerableMap.js b/scripts/generate/templates/EnumerableMap.js index 032f08681..2582b1206 100644 --- a/scripts/generate/templates/EnumerableMap.js +++ b/scripts/generate/templates/EnumerableMap.js @@ -10,7 +10,7 @@ const TYPES = [ /* eslint-disable max-len */ const header = `\ -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {EnumerableSet} from "./EnumerableSet.sol"; diff --git a/scripts/generate/templates/EnumerableSet.js b/scripts/generate/templates/EnumerableSet.js index 1edab9162..4079b3718 100644 --- a/scripts/generate/templates/EnumerableSet.js +++ b/scripts/generate/templates/EnumerableSet.js @@ -9,7 +9,7 @@ const TYPES = [ /* eslint-disable max-len */ const header = `\ -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; /** * @dev Library for managing diff --git a/scripts/generate/templates/SafeCast.js b/scripts/generate/templates/SafeCast.js index afc31a641..f1954a753 100644 --- a/scripts/generate/templates/SafeCast.js +++ b/scripts/generate/templates/SafeCast.js @@ -4,7 +4,7 @@ const { range } = require('../../helpers'); const LENGTHS = range(8, 256, 8).reverse(); // 248 → 8 (in steps of 8) const header = `\ -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; /** * @dev Wrappers over Solidity's uintXX/intXX casting operators with added overflow diff --git a/scripts/generate/templates/StorageSlot.js b/scripts/generate/templates/StorageSlot.js index a9fa11fe3..1c90b5e75 100644 --- a/scripts/generate/templates/StorageSlot.js +++ b/scripts/generate/templates/StorageSlot.js @@ -11,7 +11,7 @@ const TYPES = [ ].map(type => Object.assign(type, { struct: (type.name ?? capitalize(type.type)) + 'Slot' })); const header = `\ -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; /** * @dev Library for reading and writing primitive types to specific storage slots. diff --git a/scripts/upgradeable/upgradeable.patch b/scripts/upgradeable/upgradeable.patch index ac9eca821..6508212c5 100644 --- a/scripts/upgradeable/upgradeable.patch +++ b/scripts/upgradeable/upgradeable.patch @@ -96,7 +96,7 @@ index 38197f3a..bc934d1c 100644 @@ -50,10 +53,11 @@ Once installed, you can use the contracts in the library by importing them: ```solidity - pragma solidity ^0.8.19; + pragma solidity ^0.8.20; -import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; +import {ERC721Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol"; @@ -134,7 +134,7 @@ index 3800804a..90c1db78 100644 --- a/contracts/utils/cryptography/EIP712.sol +++ b/contracts/utils/cryptography/EIP712.sol @@ -4,7 +4,6 @@ - pragma solidity ^0.8.19; + pragma solidity ^0.8.20; import {MessageHashUtils} from "./MessageHashUtils.sol"; -import {ShortStrings, ShortString} from "../ShortStrings.sol"; diff --git a/test/governance/Governor.t.sol b/test/governance/Governor.t.sol index 1732fa2f9..eaa4eefe5 100644 --- a/test/governance/Governor.t.sol +++ b/test/governance/Governor.t.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {Test} from "forge-std/Test.sol"; import {Strings} from "../../contracts/utils/Strings.sol"; diff --git a/test/metatx/ERC2771Forwarder.t.sol b/test/metatx/ERC2771Forwarder.t.sol index 946599cc9..189ed6ac5 100644 --- a/test/metatx/ERC2771Forwarder.t.sol +++ b/test/metatx/ERC2771Forwarder.t.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {Test} from "forge-std/Test.sol"; import {ERC2771Forwarder} from "contracts/metatx/ERC2771Forwarder.sol"; diff --git a/test/token/ERC20/extensions/ERC4626.t.sol b/test/token/ERC20/extensions/ERC4626.t.sol index b5f94fcb1..d4b9e2af1 100644 --- a/test/token/ERC20/extensions/ERC4626.t.sol +++ b/test/token/ERC20/extensions/ERC4626.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {ERC4626Test} from "erc4626-tests/ERC4626.test.sol"; diff --git a/test/token/ERC721/extensions/ERC721Consecutive.t.sol b/test/token/ERC721/extensions/ERC721Consecutive.t.sol index 617b17a46..fc164558b 100644 --- a/test/token/ERC721/extensions/ERC721Consecutive.t.sol +++ b/test/token/ERC721/extensions/ERC721Consecutive.t.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; // solhint-disable func-name-mixedcase diff --git a/test/utils/ShortStrings.t.sol b/test/utils/ShortStrings.t.sol index c6aa5355b..e7e6b1960 100644 --- a/test/utils/ShortStrings.t.sol +++ b/test/utils/ShortStrings.t.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {Test} from "forge-std/Test.sol"; diff --git a/test/utils/math/Math.t.sol b/test/utils/math/Math.t.sol index d6b0c5d03..d5c7e5c32 100644 --- a/test/utils/math/Math.t.sol +++ b/test/utils/math/Math.t.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {Test} from "forge-std/Test.sol"; diff --git a/test/utils/structs/Checkpoints.t.sol b/test/utils/structs/Checkpoints.t.sol index bbc309226..afda2423e 100644 --- a/test/utils/structs/Checkpoints.t.sol +++ b/test/utils/structs/Checkpoints.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // This file was procedurally generated from scripts/generate/templates/Checkpoints.t.js. -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {Test} from "forge-std/Test.sol"; import {SafeCast} from "../../../contracts/utils/math/SafeCast.sol"; From fa680739e9c29e18670d44ae47dc1bc49de95629 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 31 Jul 2023 18:48:47 -0300 Subject: [PATCH 175/182] Bump word-wrap from 1.2.3 to 1.2.4 (#4471) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index d02ae3baf..22fe93cb7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15039,9 +15039,9 @@ } }, "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz", + "integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==", "dev": true, "engines": { "node": ">=0.10.0" @@ -15417,9 +15417,8 @@ "license": "MIT" }, "scripts/solhint-custom": { - "version": "1.0.2", - "dev": true, - "license": "MIT" + "name": "solhint-plugin-openzeppelin", + "dev": true } }, "dependencies": { @@ -27079,9 +27078,9 @@ "dev": true }, "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz", + "integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==", "dev": true }, "wordwrap": { From 48cc8a92f59b4a66ae1104c2383e2a04fcacd0c7 Mon Sep 17 00:00:00 2001 From: Vladislav Date: Tue, 1 Aug 2023 13:52:36 +0200 Subject: [PATCH 176/182] Optimize muldiv (#4494) Co-authored-by: Francisco --- .changeset/pink-suns-mix.md | 5 +++++ contracts/utils/math/Math.sol | 6 ++---- 2 files changed, 7 insertions(+), 4 deletions(-) create mode 100644 .changeset/pink-suns-mix.md diff --git a/.changeset/pink-suns-mix.md b/.changeset/pink-suns-mix.md new file mode 100644 index 000000000..eb7aaac46 --- /dev/null +++ b/.changeset/pink-suns-mix.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': patch +--- + +`Math`: Optimized stack operations in `mulDiv`. diff --git a/contracts/utils/math/Math.sol b/contracts/utils/math/Math.sol index 3e6f61bd1..17ce4c8ab 100644 --- a/contracts/utils/math/Math.sol +++ b/contracts/utils/math/Math.sol @@ -124,11 +124,10 @@ library Math { // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256 // variables such that product = prod1 * 2^256 + prod0. - uint256 prod0; // Least significant 256 bits of the product + uint256 prod0 = x * y; // Least significant 256 bits of the product uint256 prod1; // Most significant 256 bits of the product assembly { let mm := mulmod(x, y, not(0)) - prod0 := mul(x, y) prod1 := sub(sub(mm, prod0), lt(mm, prod0)) } @@ -163,8 +162,7 @@ library Math { // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1. // See https://cs.stackexchange.com/q/138556/92363. - // Does not overflow because the denominator cannot be zero at this stage in the function. - uint256 twos = denominator & (~denominator + 1); + uint256 twos = denominator & (0 - denominator); assembly { // Divide denominator by twos. denominator := div(denominator, twos) From 5ae630684a0f57de400ef69499addab4c32ac8fb Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Tue, 1 Aug 2023 13:58:47 +0200 Subject: [PATCH 177/182] Add a bool return to _grantRole and _revokeRole (#4241) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ernesto García --- .changeset/two-wasps-punch.md | 5 ++++ contracts/access/AccessControl.sol | 14 ++++++--- .../AccessControlDefaultAdminRules.sol | 8 ++--- .../extensions/AccessControlEnumerable.sol | 22 +++++++++----- test/access/AccessControl.behavior.js | 30 +++++++++++++++++++ 5 files changed, 63 insertions(+), 16 deletions(-) create mode 100644 .changeset/two-wasps-punch.md diff --git a/.changeset/two-wasps-punch.md b/.changeset/two-wasps-punch.md new file mode 100644 index 000000000..d382ab6e9 --- /dev/null +++ b/.changeset/two-wasps-punch.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': minor +--- + +`AccessControl`: Add a boolean return value to the internal `_grantRole` and `_revokeRole` functions indicating whether the role was granted or revoked. diff --git a/contracts/access/AccessControl.sol b/contracts/access/AccessControl.sol index cb7650806..a2be30de3 100644 --- a/contracts/access/AccessControl.sol +++ b/contracts/access/AccessControl.sol @@ -174,30 +174,36 @@ abstract contract AccessControl is Context, IAccessControl, ERC165 { } /** - * @dev Grants `role` to `account`. + * @dev Attempts to grant `role` to `account` and returns a boolean indicating if `role` was granted. * * Internal function without access restriction. * * May emit a {RoleGranted} event. */ - function _grantRole(bytes32 role, address account) internal virtual { + function _grantRole(bytes32 role, address account) internal virtual returns (bool) { if (!hasRole(role, account)) { _roles[role].members[account] = true; emit RoleGranted(role, account, _msgSender()); + return true; + } else { + return false; } } /** - * @dev Revokes `role` from `account`. + * @dev Attempts to revoke `role` to `account` and returns a boolean indicating if `role` was revoked. * * Internal function without access restriction. * * May emit a {RoleRevoked} event. */ - function _revokeRole(bytes32 role, address account) internal virtual { + function _revokeRole(bytes32 role, address account) internal virtual returns (bool) { if (hasRole(role, account)) { _roles[role].members[account] = false; emit RoleRevoked(role, account, _msgSender()); + return true; + } else { + return false; } } } diff --git a/contracts/access/extensions/AccessControlDefaultAdminRules.sol b/contracts/access/extensions/AccessControlDefaultAdminRules.sol index dc4dbfbcf..454608407 100644 --- a/contracts/access/extensions/AccessControlDefaultAdminRules.sol +++ b/contracts/access/extensions/AccessControlDefaultAdminRules.sol @@ -130,24 +130,24 @@ abstract contract AccessControlDefaultAdminRules is IAccessControlDefaultAdminRu * NOTE: Exposing this function through another mechanism may make the `DEFAULT_ADMIN_ROLE` * assignable again. Make sure to guarantee this is the expected behavior in your implementation. */ - function _grantRole(bytes32 role, address account) internal virtual override { + function _grantRole(bytes32 role, address account) internal virtual override returns (bool) { if (role == DEFAULT_ADMIN_ROLE) { if (defaultAdmin() != address(0)) { revert AccessControlEnforcedDefaultAdminRules(); } _currentDefaultAdmin = account; } - super._grantRole(role, account); + return super._grantRole(role, account); } /** * @dev See {AccessControl-_revokeRole}. */ - function _revokeRole(bytes32 role, address account) internal virtual override { + function _revokeRole(bytes32 role, address account) internal virtual override returns (bool) { if (role == DEFAULT_ADMIN_ROLE && account == defaultAdmin()) { delete _currentDefaultAdmin; } - super._revokeRole(role, account); + return super._revokeRole(role, account); } /** diff --git a/contracts/access/extensions/AccessControlEnumerable.sol b/contracts/access/extensions/AccessControlEnumerable.sol index f07aca637..707c5759b 100644 --- a/contracts/access/extensions/AccessControlEnumerable.sol +++ b/contracts/access/extensions/AccessControlEnumerable.sol @@ -47,18 +47,24 @@ abstract contract AccessControlEnumerable is IAccessControlEnumerable, AccessCon } /** - * @dev Overload {_grantRole} to track enumerable memberships + * @dev Overload {AccessControl-_grantRole} to track enumerable memberships */ - function _grantRole(bytes32 role, address account) internal virtual override { - super._grantRole(role, account); - _roleMembers[role].add(account); + function _grantRole(bytes32 role, address account) internal virtual override returns (bool) { + bool granted = super._grantRole(role, account); + if (granted) { + _roleMembers[role].add(account); + } + return granted; } /** - * @dev Overload {_revokeRole} to track enumerable memberships + * @dev Overload {AccessControl-_revokeRole} to track enumerable memberships */ - function _revokeRole(bytes32 role, address account) internal virtual override { - super._revokeRole(role, account); - _roleMembers[role].remove(account); + function _revokeRole(bytes32 role, address account) internal virtual override returns (bool) { + bool revoked = super._revokeRole(role, account); + if (revoked) { + _roleMembers[role].remove(account); + } + return revoked; } } diff --git a/test/access/AccessControl.behavior.js b/test/access/AccessControl.behavior.js index 20804f049..cc3e8d63f 100644 --- a/test/access/AccessControl.behavior.js +++ b/test/access/AccessControl.behavior.js @@ -191,6 +191,36 @@ function shouldBehaveLikeAccessControl(admin, authorized, other, otherAdmin) { ); }); }); + + describe('internal functions', function () { + describe('_grantRole', function () { + it('return true if the account does not have the role', async function () { + const receipt = await this.accessControl.$_grantRole(ROLE, authorized); + expectEvent(receipt, 'return$_grantRole', { ret0: true }); + }); + + it('return false if the account has the role', async function () { + await this.accessControl.$_grantRole(ROLE, authorized); + + const receipt = await this.accessControl.$_grantRole(ROLE, authorized); + expectEvent(receipt, 'return$_grantRole', { ret0: false }); + }); + }); + + describe('_revokeRole', function () { + it('return true if the account has the role', async function () { + await this.accessControl.$_grantRole(ROLE, authorized); + + const receipt = await this.accessControl.$_revokeRole(ROLE, authorized); + expectEvent(receipt, 'return$_revokeRole', { ret0: true }); + }); + + it('return false if the account does not have the role', async function () { + const receipt = await this.accessControl.$_revokeRole(ROLE, authorized); + expectEvent(receipt, 'return$_revokeRole', { ret0: false }); + }); + }); + }); } function shouldBehaveLikeAccessControlEnumerable(admin, authorized, other, otherAdmin, otherAuthorized) { From d39df78f6cf00fee453830ce4500600eabfdbc60 Mon Sep 17 00:00:00 2001 From: Francisco Date: Wed, 2 Aug 2023 15:45:39 -0300 Subject: [PATCH 178/182] Use bytes.concat in MessageHashUtils (#4504) --- .changeset/flat-bottles-wonder.md | 4 +++- contracts/utils/cryptography/MessageHashUtils.sol | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.changeset/flat-bottles-wonder.md b/.changeset/flat-bottles-wonder.md index 099ea8339..f7ee7dd5d 100644 --- a/.changeset/flat-bottles-wonder.md +++ b/.changeset/flat-bottles-wonder.md @@ -2,4 +2,6 @@ 'openzeppelin-solidity': minor --- -Replace some uses of `abi.encodePacked` with clearer alternatives (e.g. `bytes.concat`, `string.concat`). +Replace some uses of `abi.encodePacked` with clearer alternatives (e.g. `bytes.concat`, `string.concat`). (#4504)[https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4504] + +pr: #4296 diff --git a/contracts/utils/cryptography/MessageHashUtils.sol b/contracts/utils/cryptography/MessageHashUtils.sol index 1a1bea731..3cf0ce9d9 100644 --- a/contracts/utils/cryptography/MessageHashUtils.sol +++ b/contracts/utils/cryptography/MessageHashUtils.sol @@ -46,7 +46,8 @@ library MessageHashUtils { * See {ECDSA-recover}. */ function toEthSignedMessageHash(bytes memory message) internal pure returns (bytes32 digest) { - return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n", Strings.toString(message.length), message)); + return + keccak256(bytes.concat("\x19Ethereum Signed Message:\n", bytes(Strings.toString(message.length)), message)); } /** From 21716722ad78ee3e9bf21a5b47ff420323c8fe27 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Thu, 3 Aug 2023 17:51:07 +0200 Subject: [PATCH 179/182] Remove GovernorCompatibilyBravo and add simpler GovernorStorage (#4360) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ernesto García Co-authored-by: Francisco Giordano --- .changeset/brave-lobsters-punch.md | 5 + .changeset/wild-beds-visit.md | 5 + contracts/governance/Governor.sol | 313 +++++++++------- contracts/governance/IGovernor.sol | 123 ++++--- contracts/governance/README.adoc | 4 +- .../GovernorCompatibilityBravo.sol | 333 ------------------ .../IGovernorCompatibilityBravo.sol | 121 ------- .../governance/extensions/GovernorStorage.sol | 113 ++++++ .../extensions/GovernorTimelockCompound.sol | 75 +--- .../extensions/GovernorTimelockControl.sol | 60 +--- .../extensions/IGovernorTimelock.sol | 34 -- .../mocks/docs/governance/MyGovernor.sol | 34 +- .../GovernorCompatibilityBravoMock.sol | 102 ------ .../mocks/governance/GovernorStorageMock.sol | 73 ++++ .../GovernorTimelockCompoundMock.sol | 30 +- .../GovernorTimelockControlMock.sol | 26 +- docs/modules/ROOT/pages/governance.adoc | 11 +- test/governance/Governor.test.js | 27 +- .../GovernorCompatibilityBravo.test.js | 297 ---------------- .../extensions/GovernorStorage.test.js | 150 ++++++++ .../GovernorTimelockCompound.test.js | 4 - .../GovernorTimelockControl.test.js | 9 +- test/helpers/create.js | 11 +- test/helpers/governance.js | 5 + test/proxy/Clones.test.js | 3 +- test/utils/Create2.test.js | 2 +- .../SupportsInterface.behavior.js | 26 +- 27 files changed, 746 insertions(+), 1250 deletions(-) create mode 100644 .changeset/brave-lobsters-punch.md create mode 100644 .changeset/wild-beds-visit.md delete mode 100644 contracts/governance/compatibility/GovernorCompatibilityBravo.sol delete mode 100644 contracts/governance/compatibility/IGovernorCompatibilityBravo.sol create mode 100644 contracts/governance/extensions/GovernorStorage.sol delete mode 100644 contracts/governance/extensions/IGovernorTimelock.sol delete mode 100644 contracts/mocks/governance/GovernorCompatibilityBravoMock.sol create mode 100644 contracts/mocks/governance/GovernorStorageMock.sol delete mode 100644 test/governance/compatibility/GovernorCompatibilityBravo.test.js create mode 100644 test/governance/extensions/GovernorStorage.test.js diff --git a/.changeset/brave-lobsters-punch.md b/.changeset/brave-lobsters-punch.md new file mode 100644 index 000000000..60f04e430 --- /dev/null +++ b/.changeset/brave-lobsters-punch.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': major +--- + +`Governor`: Refactored internals to implement common queuing logic in the core module of the Governor. Added `queue` and `_queueOperations` functions that act at different levels. Modules that implement queuing via timelocks are expected to override `_queueOperations` to implement the timelock-specific logic. Added `_executeOperations` as the equivalent for execution. diff --git a/.changeset/wild-beds-visit.md b/.changeset/wild-beds-visit.md new file mode 100644 index 000000000..e97dee284 --- /dev/null +++ b/.changeset/wild-beds-visit.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': major +--- + +`GovernorStorage`: Added a new governor extension that stores the proposal details in storage, with an interface that operates on `proposalId`, as well as proposal enumerability. This replaces the old `GovernorCompatibilityBravo` module. diff --git a/contracts/governance/Governor.sol b/contracts/governance/Governor.sol index 9e76bfea9..b9874c820 100644 --- a/contracts/governance/Governor.sol +++ b/contracts/governance/Governor.sol @@ -34,7 +34,6 @@ abstract contract Governor is Context, ERC165, EIP712, Nonces, IGovernor, IERC72 "ExtendedBallot(uint256 proposalId,uint8 support,address voter,uint256 nonce,string reason,bytes params)" ); - // solhint-disable var-name-mixedcase struct ProposalCore { address proposer; uint48 voteStart; @@ -42,12 +41,21 @@ abstract contract Governor is Context, ERC165, EIP712, Nonces, IGovernor, IERC72 bool executed; bool canceled; } - // solhint-enable var-name-mixedcase + + struct ProposalExtra { + uint48 eta; + } + + // Each object in this should fit into a single slot so it can be cached efficiently + struct ProposalFull { + ProposalCore core; + ProposalExtra extra; + } bytes32 private constant _ALL_PROPOSAL_STATES_BITMAP = bytes32((2 ** (uint8(type(ProposalState).max) + 1)) - 1); string private _name; - mapping(uint256 => ProposalCore) private _proposals; + mapping(uint256 => ProposalFull) private _proposals; // This queue keeps track of the governor operating on itself. Calls to functions protected by the // {onlyGovernance} modifier needs to be whitelisted in this queue. Whitelisting is set in {_beforeExecute}, @@ -90,27 +98,8 @@ abstract contract Governor is Context, ERC165, EIP712, Nonces, IGovernor, IERC72 * @dev See {IERC165-supportsInterface}. */ function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC165) returns (bool) { - bytes4 governorCancelId = this.cancel.selector ^ this.proposalProposer.selector; - - bytes4 governorParamsId = this.castVoteWithReasonAndParams.selector ^ - this.castVoteWithReasonAndParamsBySig.selector ^ - this.getVotesWithParams.selector; - - // The original interface id in v4.3. - bytes4 governor43Id = type(IGovernor).interfaceId ^ - type(IERC6372).interfaceId ^ - governorCancelId ^ - governorParamsId; - - // An updated interface id in v4.6, with params added. - bytes4 governor46Id = type(IGovernor).interfaceId ^ type(IERC6372).interfaceId ^ governorCancelId; - - // For the updated interface id in v4.9, we use governorCancelId directly. - return - interfaceId == governor43Id || - interfaceId == governor46Id || - interfaceId == governorCancelId || + interfaceId == type(IGovernor).interfaceId || interfaceId == type(IERC1155Receiver).interfaceId || super.supportsInterface(interfaceId); } @@ -157,13 +146,11 @@ abstract contract Governor is Context, ERC165, EIP712, Nonces, IGovernor, IERC72 function state(uint256 proposalId) public view virtual override returns (ProposalState) { // ProposalCore is just one slot. We can load it from storage to memory with a single sload and use memory // object as a cache. This avoid duplicating expensive sloads. - ProposalCore memory proposal = _proposals[proposalId]; + ProposalCore memory core = _proposals[proposalId].core; - if (proposal.executed) { + if (core.executed) { return ProposalState.Executed; - } - - if (proposal.canceled) { + } else if (core.canceled) { return ProposalState.Canceled; } @@ -183,19 +170,19 @@ abstract contract Governor is Context, ERC165, EIP712, Nonces, IGovernor, IERC72 if (deadline >= currentTimepoint) { return ProposalState.Active; - } - - if (_quorumReached(proposalId) && _voteSucceeded(proposalId)) { + } else if (!_quorumReached(proposalId) || !_voteSucceeded(proposalId)) { + return ProposalState.Defeated; + } else if (proposalEta(proposalId) == 0) { return ProposalState.Succeeded; } else { - return ProposalState.Defeated; + return ProposalState.Queued; } } /** - * @dev Part of the Governor Bravo's interface: _"The number of votes required in order for a voter to become a proposer"_. + * @dev See {IGovernor-proposalThreshold}. */ - function proposalThreshold() public view virtual returns (uint256) { + function proposalThreshold() public view virtual override returns (uint256) { return 0; } @@ -203,21 +190,28 @@ abstract contract Governor is Context, ERC165, EIP712, Nonces, IGovernor, IERC72 * @dev See {IGovernor-proposalSnapshot}. */ function proposalSnapshot(uint256 proposalId) public view virtual override returns (uint256) { - return _proposals[proposalId].voteStart; + return _proposals[proposalId].core.voteStart; } /** * @dev See {IGovernor-proposalDeadline}. */ function proposalDeadline(uint256 proposalId) public view virtual override returns (uint256) { - return _proposals[proposalId].voteStart + _proposals[proposalId].voteDuration; + return _proposals[proposalId].core.voteStart + _proposals[proposalId].core.voteDuration; } /** - * @dev Returns the account that created a given proposal. + * @dev See {IGovernor-proposalProposer}. */ function proposalProposer(uint256 proposalId) public view virtual override returns (address) { - return _proposals[proposalId].proposer; + return _proposals[proposalId].core.proposer; + } + + /** + * @dev See {IGovernor-proposalEta}. + */ + function proposalEta(uint256 proposalId) public view virtual override returns (uint256) { + return _proposals[proposalId].extra.eta; } /** @@ -284,32 +278,47 @@ abstract contract Governor is Context, ERC165, EIP712, Nonces, IGovernor, IERC72 string memory description ) public virtual override returns (uint256) { address proposer = _msgSender(); - require(_isValidDescriptionForProposer(proposer, description), "Governor: proposer restricted"); - uint256 currentTimepoint = clock(); + // check description restriction + if (!_isValidDescriptionForProposer(proposer, description)) { + revert GovernorRestrictedProposer(proposer); + } - // Avoid stack too deep - { - uint256 proposerVotes = getVotes(proposer, currentTimepoint - 1); - uint256 votesThreshold = proposalThreshold(); - if (proposerVotes < votesThreshold) { - revert GovernorInsufficientProposerVotes(proposer, proposerVotes, votesThreshold); - } + // check proposal threshold + uint256 proposerVotes = getVotes(proposer, clock() - 1); + uint256 votesThreshold = proposalThreshold(); + if (proposerVotes < votesThreshold) { + revert GovernorInsufficientProposerVotes(proposer, proposerVotes, votesThreshold); } + return _propose(targets, values, calldatas, description, proposer); + } + + /** + * @dev Internal propose mechanism. Can be overridden to add more logic on proposal creation. + * + * Emits a {IGovernor-ProposalCreated} event. + */ + function _propose( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + string memory description, + address proposer + ) internal virtual returns (uint256) { uint256 proposalId = hashProposal(targets, values, calldatas, keccak256(bytes(description))); if (targets.length != values.length || targets.length != calldatas.length || targets.length == 0) { revert GovernorInvalidProposalLength(targets.length, calldatas.length, values.length); } - if (_proposals[proposalId].voteStart != 0) { + if (_proposals[proposalId].core.voteStart != 0) { revert GovernorUnexpectedProposalState(proposalId, state(proposalId), bytes32(0)); } - uint256 snapshot = currentTimepoint + votingDelay(); + uint256 snapshot = clock() + votingDelay(); uint256 duration = votingPeriod(); - _proposals[proposalId] = ProposalCore({ + _proposals[proposalId].core = ProposalCore({ proposer: proposer, voteStart: SafeCast.toUint48(snapshot), voteDuration: SafeCast.toUint32(duration), @@ -333,59 +342,101 @@ abstract contract Governor is Context, ERC165, EIP712, Nonces, IGovernor, IERC72 } /** - * @dev See {IGovernor-execute}. + * @dev See {IGovernor-queue}. */ - function execute( + function queue( address[] memory targets, uint256[] memory values, bytes[] memory calldatas, bytes32 descriptionHash - ) public payable virtual override returns (uint256) { + ) public virtual override returns (uint256) { uint256 proposalId = hashProposal(targets, values, calldatas, descriptionHash); - ProposalState currentState = state(proposalId); - if (currentState != ProposalState.Succeeded && currentState != ProposalState.Queued) { - revert GovernorUnexpectedProposalState( - proposalId, - currentState, - _encodeStateBitmap(ProposalState.Succeeded) | _encodeStateBitmap(ProposalState.Queued) - ); - } - _proposals[proposalId].executed = true; + _validateStateBitmap(proposalId, _encodeStateBitmap(ProposalState.Succeeded)); - emit ProposalExecuted(proposalId); + uint48 eta = _queueOperations(proposalId, targets, values, calldatas, descriptionHash); - _beforeExecute(proposalId, targets, values, calldatas, descriptionHash); - _execute(proposalId, targets, values, calldatas, descriptionHash); - _afterExecute(proposalId, targets, values, calldatas, descriptionHash); + if (eta != 0) { + _proposals[proposalId].extra.eta = eta; + emit ProposalQueued(proposalId, eta); + } else { + revert GovernorQueueNotImplemented(); + } return proposalId; } /** - * @dev See {IGovernor-cancel}. + * @dev Internal queuing mechanism. Can be overridden (without a super call) to modify the way queuing is + * performed (for example adding a vault/timelock). + * + * This is empty by default, and must be overridden to implement queuing. + * + * This function returns a timestamp that describes the expected eta for execution. If the returned value is 0 + * (which is the default value), the core will consider queueing did not succeed, and the public {queue} function + * will revert. + * + * NOTE: Calling this function directly will NOT check the current state of the proposal, or emit the + * `ProposalQueued` event. Queuing a proposal should be done using {queue} or {_queue}. + */ + function _queueOperations( + uint256 /*proposalId*/, + address[] memory /*targets*/, + uint256[] memory /*values*/, + bytes[] memory /*calldatas*/, + bytes32 /*descriptionHash*/ + ) internal virtual returns (uint48) { + return 0; + } + + /** + * @dev See {IGovernor-execute}. */ - function cancel( + function execute( address[] memory targets, uint256[] memory values, bytes[] memory calldatas, bytes32 descriptionHash - ) public virtual override returns (uint256) { + ) public payable virtual override returns (uint256) { uint256 proposalId = hashProposal(targets, values, calldatas, descriptionHash); - ProposalState currentState = state(proposalId); - if (currentState != ProposalState.Pending) { - revert GovernorUnexpectedProposalState(proposalId, currentState, _encodeStateBitmap(ProposalState.Pending)); + + _validateStateBitmap( + proposalId, + _encodeStateBitmap(ProposalState.Succeeded) | _encodeStateBitmap(ProposalState.Queued) + ); + + // mark as executed before calls to avoid reentrancy + _proposals[proposalId].core.executed = true; + + // before execute: register governance call in queue. + if (_executor() != address(this)) { + for (uint256 i = 0; i < targets.length; ++i) { + if (targets[i] == address(this)) { + _governanceCall.pushBack(keccak256(calldatas[i])); + } + } } - if (_msgSender() != proposalProposer(proposalId)) { - revert GovernorOnlyProposer(_msgSender()); + + _executeOperations(proposalId, targets, values, calldatas, descriptionHash); + + // after execute: cleanup governance call queue. + if (_executor() != address(this) && !_governanceCall.empty()) { + _governanceCall.clear(); } - return _cancel(targets, values, calldatas, descriptionHash); + + emit ProposalExecuted(proposalId); + + return proposalId; } /** - * @dev Internal execution mechanism. Can be overridden to implement different execution mechanism + * @dev Internal execution mechanism. Can be overridden (without a super call) to modify the way execution is + * performed (for example adding a vault/timelock). + * + * NOTE: Calling this function directly will NOT check the current state of the proposal, set the executed flag to + * true or emit the `ProposalExecuted` event. Executing a proposal should be done using {execute} or {_execute}. */ - function _execute( + function _executeOperations( uint256 /* proposalId */, address[] memory targets, uint256[] memory values, @@ -399,44 +450,31 @@ abstract contract Governor is Context, ERC165, EIP712, Nonces, IGovernor, IERC72 } /** - * @dev Hook before execution is triggered. + * @dev See {IGovernor-cancel}. */ - function _beforeExecute( - uint256 /* proposalId */, + function cancel( address[] memory targets, - uint256[] memory /* values */, + uint256[] memory values, bytes[] memory calldatas, - bytes32 /*descriptionHash*/ - ) internal virtual { - if (_executor() != address(this)) { - for (uint256 i = 0; i < targets.length; ++i) { - if (targets[i] == address(this)) { - _governanceCall.pushBack(keccak256(calldatas[i])); - } - } - } - } + bytes32 descriptionHash + ) public virtual override returns (uint256) { + // The proposalId will be recomputed in the `_cancel` call further down. However we need the value before we + // do the internal call, because we need to check the proposal state BEFORE the internal `_cancel` call + // changes it. The `hashProposal` duplication has a cost that is limited, and that we accept. + uint256 proposalId = hashProposal(targets, values, calldatas, descriptionHash); - /** - * @dev Hook after execution is triggered. - */ - function _afterExecute( - uint256 /* proposalId */, - address[] memory /* targets */, - uint256[] memory /* values */, - bytes[] memory /* calldatas */, - bytes32 /*descriptionHash*/ - ) internal virtual { - if (_executor() != address(this)) { - if (!_governanceCall.empty()) { - _governanceCall.clear(); - } + // public cancel restrictions (on top of existing _cancel restrictions). + _validateStateBitmap(proposalId, _encodeStateBitmap(ProposalState.Pending)); + if (_msgSender() != proposalProposer(proposalId)) { + revert GovernorOnlyProposer(_msgSender()); } + + return _cancel(targets, values, calldatas, descriptionHash); } /** - * @dev Internal cancel mechanism: locks up the proposal timer, preventing it from being re-submitted. Marks it as - * canceled to allow distinguishing it from executed proposals. + * @dev Internal cancel mechanism with minimal restrictions. A proposal can be cancelled in any state other than + * Canceled, Expired, or Executed. Once cancelled a proposal can't be re-submitted. * * Emits a {IGovernor-ProposalCanceled} event. */ @@ -448,20 +486,15 @@ abstract contract Governor is Context, ERC165, EIP712, Nonces, IGovernor, IERC72 ) internal virtual returns (uint256) { uint256 proposalId = hashProposal(targets, values, calldatas, descriptionHash); - ProposalState currentState = state(proposalId); - - bytes32 forbiddenStates = _encodeStateBitmap(ProposalState.Canceled) | - _encodeStateBitmap(ProposalState.Expired) | - _encodeStateBitmap(ProposalState.Executed); - if (forbiddenStates & _encodeStateBitmap(currentState) != 0) { - revert GovernorUnexpectedProposalState( - proposalId, - currentState, - _ALL_PROPOSAL_STATES_BITMAP ^ forbiddenStates - ); - } - _proposals[proposalId].canceled = true; + _validateStateBitmap( + proposalId, + _ALL_PROPOSAL_STATES_BITMAP ^ + _encodeStateBitmap(ProposalState.Canceled) ^ + _encodeStateBitmap(ProposalState.Expired) ^ + _encodeStateBitmap(ProposalState.Executed) + ); + _proposals[proposalId].core.canceled = true; emit ProposalCanceled(proposalId); return proposalId; @@ -695,6 +728,20 @@ abstract contract Governor is Context, ERC165, EIP712, Nonces, IGovernor, IERC72 return bytes32(1 << uint8(proposalState)); } + /** + * @dev Check that the current state of a proposal matches the requirements described by the `allowedStates` bitmap. + * This bitmap should be built using `_encodeStateBitmap`. + * + * If requirements are not met, reverts with a {GovernorUnexpectedProposalState} error. + */ + function _validateStateBitmap(uint256 proposalId, bytes32 allowedStates) private view returns (ProposalState) { + ProposalState currentState = state(proposalId); + if (_encodeStateBitmap(currentState) & allowedStates == bytes32(0)) { + revert GovernorUnexpectedProposalState(proposalId, currentState, allowedStates); + } + return currentState; + } + /* * @dev Check if the proposer is authorized to submit a proposal with the given description. * @@ -779,4 +826,30 @@ abstract contract Governor is Context, ERC165, EIP712, Nonces, IGovernor, IERC72 } } } + + /** + * @inheritdoc IERC6372 + */ + function clock() public view virtual returns (uint48); + + /** + * @inheritdoc IERC6372 + */ + // solhint-disable-next-line func-name-mixedcase + function CLOCK_MODE() public view virtual returns (string memory); + + /** + * @inheritdoc IGovernor + */ + function votingDelay() public view virtual returns (uint256); + + /** + * @inheritdoc IGovernor + */ + function votingPeriod() public view virtual returns (uint256); + + /** + * @inheritdoc IGovernor + */ + function quorum(uint256 timepoint) public view virtual returns (uint256); } diff --git a/contracts/governance/IGovernor.sol b/contracts/governance/IGovernor.sol index c4a28fd19..3b31bc502 100644 --- a/contracts/governance/IGovernor.sol +++ b/contracts/governance/IGovernor.sol @@ -9,7 +9,7 @@ import {IERC6372} from "../interfaces/IERC6372.sol"; /** * @dev Interface of the {Governor} core. */ -abstract contract IGovernor is IERC165, IERC6372 { +interface IGovernor is IERC165, IERC6372 { enum ProposalState { Pending, Active, @@ -69,15 +69,35 @@ abstract contract IGovernor is IERC165, IERC6372 { error GovernorInvalidVotingPeriod(uint256 votingPeriod); /** - * @dev The `proposer` does not have the required votes to operate on a proposal. + * @dev The `proposer` does not have the required votes to create a proposal. */ error GovernorInsufficientProposerVotes(address proposer, uint256 votes, uint256 threshold); + /** + * @dev The `proposer` is not allowed to create a proposal. + */ + error GovernorRestrictedProposer(address proposer); + /** * @dev The vote type used is not valid for the corresponding counting module. */ error GovernorInvalidVoteType(); + /** + * @dev Queue operation is not implemented for this governor. Execute should be called directly. + */ + error GovernorQueueNotImplemented(); + + /** + * @dev The proposal hasn't been queued yet. + */ + error GovernorNotQueuedProposal(uint256 proposalId); + + /** + * @dev The proposal has already been queued. + */ + error GovernorAlreadyQueuedProposal(uint256 proposalId); + /** * @dev The provided signature is not valid for the expected `voter`. * If the `voter` is a contract, the signature is not valid using {IERC1271-isValidSignature}. @@ -100,15 +120,20 @@ abstract contract IGovernor is IERC165, IERC6372 { ); /** - * @dev Emitted when a proposal is canceled. + * @dev Emitted when a proposal is queued. */ - event ProposalCanceled(uint256 proposalId); + event ProposalQueued(uint256 proposalId, uint256 eta); /** * @dev Emitted when a proposal is executed. */ event ProposalExecuted(uint256 proposalId); + /** + * @dev Emitted when a proposal is canceled. + */ + event ProposalCanceled(uint256 proposalId); + /** * @dev Emitted when a vote is cast without params. * @@ -135,26 +160,13 @@ abstract contract IGovernor is IERC165, IERC6372 { * @notice module:core * @dev Name of the governor instance (used in building the ERC712 domain separator). */ - function name() public view virtual returns (string memory); + function name() external view returns (string memory); /** * @notice module:core * @dev Version of the governor instance (used in building the ERC712 domain separator). Default: "1" */ - function version() public view virtual returns (string memory); - - /** - * @notice module:core - * @dev See {IERC6372} - */ - function clock() public view virtual returns (uint48); - - /** - * @notice module:core - * @dev See EIP-6372. - */ - // solhint-disable-next-line func-name-mixedcase - function CLOCK_MODE() public view virtual returns (string memory); + function version() external view returns (string memory); /** * @notice module:voting @@ -179,7 +191,7 @@ abstract contract IGovernor is IERC165, IERC6372 { * JavaScript class. */ // solhint-disable-next-line func-name-mixedcase - function COUNTING_MODE() public view virtual returns (string memory); + function COUNTING_MODE() external view returns (string memory); /** * @notice module:core @@ -190,13 +202,19 @@ abstract contract IGovernor is IERC165, IERC6372 { uint256[] memory values, bytes[] memory calldatas, bytes32 descriptionHash - ) public pure virtual returns (uint256); + ) external pure returns (uint256); /** * @notice module:core * @dev Current state of a proposal, following Compound's convention */ - function state(uint256 proposalId) public view virtual returns (ProposalState); + function state(uint256 proposalId) external view returns (ProposalState); + + /** + * @notice module:core + * @dev The number of votes required in order for a voter to become a proposer. + */ + function proposalThreshold() external view returns (uint256); /** * @notice module:core @@ -204,20 +222,28 @@ abstract contract IGovernor is IERC165, IERC6372 { * snapshot is performed at the end of this block. Hence, voting for this proposal starts at the beginning of the * following block. */ - function proposalSnapshot(uint256 proposalId) public view virtual returns (uint256); + function proposalSnapshot(uint256 proposalId) external view returns (uint256); /** * @notice module:core * @dev Timepoint at which votes close. If using block number, votes close at the end of this block, so it is * possible to cast a vote during this block. */ - function proposalDeadline(uint256 proposalId) public view virtual returns (uint256); + function proposalDeadline(uint256 proposalId) external view returns (uint256); /** * @notice module:core * @dev The account that created a proposal. */ - function proposalProposer(uint256 proposalId) public view virtual returns (address); + function proposalProposer(uint256 proposalId) external view returns (address); + + /** + * @notice module:core + * @dev The time when a queued proposal becomes executable ("ETA"). Unlike {proposalSnapshot} and + * {proposalDeadline}, this doesn't use the governor clock, and instead relies on the executor's clock which may be + * different. In most cases this will be a timestamp. + */ + function proposalEta(uint256 proposalId) external view returns (uint256); /** * @notice module:user-config @@ -230,7 +256,7 @@ abstract contract IGovernor is IERC165, IERC6372 { * NOTE: While this interface returns a uint256, timepoints are stored as uint48 following the ERC-6372 clock type. * Consequently this value must fit in a uint48 (when added to the current clock). See {IERC6372-clock}. */ - function votingDelay() public view virtual returns (uint256); + function votingDelay() external view returns (uint256); /** * @notice module:user-config @@ -244,7 +270,7 @@ abstract contract IGovernor is IERC165, IERC6372 { * proposals that have already been submitted. The type used to save it is a uint32. Consequently, while this * interface returns a uint256, the value it returns should fit in a uint32. */ - function votingPeriod() public view virtual returns (uint256); + function votingPeriod() external view returns (uint256); /** * @notice module:user-config @@ -253,7 +279,7 @@ abstract contract IGovernor is IERC165, IERC6372 { * NOTE: The `timepoint` parameter corresponds to the snapshot used for counting vote. This allows to scale the * quorum depending on values such as the totalSupply of a token at this timepoint (see {ERC20Votes}). */ - function quorum(uint256 timepoint) public view virtual returns (uint256); + function quorum(uint256 timepoint) external view returns (uint256); /** * @notice module:reputation @@ -262,7 +288,7 @@ abstract contract IGovernor is IERC165, IERC6372 { * Note: this can be implemented in a number of ways, for example by reading the delegated balance from one (or * multiple), {ERC20Votes} tokens. */ - function getVotes(address account, uint256 timepoint) public view virtual returns (uint256); + function getVotes(address account, uint256 timepoint) external view returns (uint256); /** * @notice module:reputation @@ -272,13 +298,13 @@ abstract contract IGovernor is IERC165, IERC6372 { address account, uint256 timepoint, bytes memory params - ) public view virtual returns (uint256); + ) external view returns (uint256); /** * @notice module:voting * @dev Returns whether `account` has cast a vote on `proposalId`. */ - function hasVoted(uint256 proposalId, address account) public view virtual returns (bool); + function hasVoted(uint256 proposalId, address account) external view returns (bool); /** * @dev Create a new proposal. Vote start after a delay specified by {IGovernor-votingDelay} and lasts for a @@ -291,22 +317,37 @@ abstract contract IGovernor is IERC165, IERC6372 { uint256[] memory values, bytes[] memory calldatas, string memory description - ) public virtual returns (uint256 proposalId); + ) external returns (uint256 proposalId); + + /** + * @dev Queue a proposal. Some governors require this step to be performed before execution can happen. If queuing + * is not necessary, this function may revert. + * Queuing a proposal requires the quorum to be reached, the vote to be successful, and the deadline to be reached. + * + * Emits a {ProposalQueued} event. + */ + function queue( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 descriptionHash + ) external returns (uint256 proposalId); /** * @dev Execute a successful proposal. This requires the quorum to be reached, the vote to be successful, and the - * deadline to be reached. + * deadline to be reached. Depending on the governor it might also be required that the proposal was queued and + * that some delay passed. * * Emits a {ProposalExecuted} event. * - * Note: some module can modify the requirements for execution, for example by adding an additional timelock. + * NOTE: Some modules can modify the requirements for execution, for example by adding an additional timelock. */ function execute( address[] memory targets, uint256[] memory values, bytes[] memory calldatas, bytes32 descriptionHash - ) public payable virtual returns (uint256 proposalId); + ) external payable returns (uint256 proposalId); /** * @dev Cancel a proposal. A proposal is cancellable by the proposer, but only while it is Pending state, i.e. @@ -319,14 +360,14 @@ abstract contract IGovernor is IERC165, IERC6372 { uint256[] memory values, bytes[] memory calldatas, bytes32 descriptionHash - ) public virtual returns (uint256 proposalId); + ) external returns (uint256 proposalId); /** * @dev Cast a vote * * Emits a {VoteCast} event. */ - function castVote(uint256 proposalId, uint8 support) public virtual returns (uint256 balance); + function castVote(uint256 proposalId, uint8 support) external returns (uint256 balance); /** * @dev Cast a vote with a reason @@ -337,7 +378,7 @@ abstract contract IGovernor is IERC165, IERC6372 { uint256 proposalId, uint8 support, string calldata reason - ) public virtual returns (uint256 balance); + ) external returns (uint256 balance); /** * @dev Cast a vote with a reason and additional encoded parameters @@ -349,7 +390,7 @@ abstract contract IGovernor is IERC165, IERC6372 { uint8 support, string calldata reason, bytes memory params - ) public virtual returns (uint256 balance); + ) external returns (uint256 balance); /** * @dev Cast a vote using the voter's signature, including ERC-1271 signature support. @@ -361,7 +402,7 @@ abstract contract IGovernor is IERC165, IERC6372 { uint8 support, address voter, bytes memory signature - ) public virtual returns (uint256 balance); + ) external returns (uint256 balance); /** * @dev Cast a vote with a reason and additional encoded parameters using the voter's signature, @@ -376,5 +417,5 @@ abstract contract IGovernor is IERC165, IERC6372 { string calldata reason, bytes memory params, bytes memory signature - ) public virtual returns (uint256 balance); + ) external returns (uint256 balance); } diff --git a/contracts/governance/README.adoc b/contracts/governance/README.adoc index 35f324b7e..5b38c4d53 100644 --- a/contracts/governance/README.adoc +++ b/contracts/governance/README.adoc @@ -36,7 +36,7 @@ Timelock extensions add a delay for governance decisions to be executed. The wor Other extensions can customize the behavior or interface in multiple ways. -* {GovernorCompatibilityBravo}: Extends the interface to be fully `GovernorBravo`-compatible. Note that events are compatible regardless of whether this extension is included or not. +* {GovernorStorage}: Stores the proposal details onchain and provides enumerability of the proposals. This can be useful for some L2 chains where storage is cheap compared to calldata. * {GovernorSettings}: Manages some of the settings (voting delay, voting period duration, and proposal threshold) in a way that can be updated through a governance proposal, without requiring an upgrade. @@ -74,7 +74,7 @@ NOTE: Functions of the `Governor` contract do not include access control. If you {{GovernorPreventLateQuorum}} -{{GovernorCompatibilityBravo}} +{{GovernorStorage}} == Utils diff --git a/contracts/governance/compatibility/GovernorCompatibilityBravo.sol b/contracts/governance/compatibility/GovernorCompatibilityBravo.sol deleted file mode 100644 index 27c34de29..000000000 --- a/contracts/governance/compatibility/GovernorCompatibilityBravo.sol +++ /dev/null @@ -1,333 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.9.0) (governance/compatibility/GovernorCompatibilityBravo.sol) - -pragma solidity ^0.8.20; - -import {SafeCast} from "../../utils/math/SafeCast.sol"; -import {IGovernorTimelock} from "../extensions/IGovernorTimelock.sol"; -import {IGovernor, Governor} from "../Governor.sol"; -import {IGovernorCompatibilityBravo} from "./IGovernorCompatibilityBravo.sol"; - -/** - * @dev Compatibility layer that implements GovernorBravo compatibility on top of {Governor}. - * - * This compatibility layer includes a voting system and requires a {IGovernorTimelock} compatible module to be added - * through inheritance. It does not include token bindings, nor does it include any variable upgrade patterns. - * - * NOTE: When using this module, you may need to enable the Solidity optimizer to avoid hitting the contract size limit. - */ -abstract contract GovernorCompatibilityBravo is IGovernorTimelock, IGovernorCompatibilityBravo, Governor { - enum VoteType { - Against, - For, - Abstain - } - - struct ProposalDetails { - address[] targets; - uint256[] values; - string[] signatures; - bytes[] calldatas; - uint256 forVotes; - uint256 againstVotes; - uint256 abstainVotes; - mapping(address => Receipt) receipts; - bytes32 descriptionHash; - } - - mapping(uint256 => ProposalDetails) private _proposalDetails; - - // solhint-disable-next-line func-name-mixedcase - function COUNTING_MODE() public pure virtual override returns (string memory) { - return "support=bravo&quorum=bravo"; - } - - // ============================================== Proposal lifecycle ============================================== - /** - * @dev See {IGovernor-propose}. - */ - function propose( - address[] memory targets, - uint256[] memory values, - bytes[] memory calldatas, - string memory description - ) public virtual override(IGovernor, Governor) returns (uint256) { - // Stores the proposal details (if not already present) and executes the propose logic from the core. - _storeProposal(targets, values, new string[](calldatas.length), calldatas, description); - return super.propose(targets, values, calldatas, description); - } - - /** - * @dev See {IGovernorCompatibilityBravo-propose}. - */ - function propose( - address[] memory targets, - uint256[] memory values, - string[] memory signatures, - bytes[] memory calldatas, - string memory description - ) public virtual override returns (uint256) { - if (signatures.length != calldatas.length) { - revert GovernorInvalidSignaturesLength(signatures.length, calldatas.length); - } - // Stores the full proposal and fallback to the public (possibly overridden) propose. The fallback is done - // after the full proposal is stored, so the store operation included in the fallback will be skipped. Here we - // call `propose` and not `super.propose` to make sure if a child contract override `propose`, whatever code - // is added there is also executed when calling this alternative interface. - _storeProposal(targets, values, signatures, calldatas, description); - return propose(targets, values, _encodeCalldata(signatures, calldatas), description); - } - - /** - * @dev See {IGovernorCompatibilityBravo-queue}. - */ - function queue(uint256 proposalId) public virtual override { - ( - address[] memory targets, - uint256[] memory values, - bytes[] memory calldatas, - bytes32 descriptionHash - ) = _getProposalParameters(proposalId); - - queue(targets, values, calldatas, descriptionHash); - } - - /** - * @dev See {IGovernorCompatibilityBravo-execute}. - */ - function execute(uint256 proposalId) public payable virtual override { - ( - address[] memory targets, - uint256[] memory values, - bytes[] memory calldatas, - bytes32 descriptionHash - ) = _getProposalParameters(proposalId); - - execute(targets, values, calldatas, descriptionHash); - } - - /** - * @dev Cancel a proposal with GovernorBravo logic. - */ - function cancel(uint256 proposalId) public virtual override { - ( - address[] memory targets, - uint256[] memory values, - bytes[] memory calldatas, - bytes32 descriptionHash - ) = _getProposalParameters(proposalId); - - cancel(targets, values, calldatas, descriptionHash); - } - - /** - * @dev Cancel a proposal with GovernorBravo logic. At any moment a proposal can be cancelled, either by the - * proposer, or by third parties if the proposer's voting power has dropped below the proposal threshold. - */ - function cancel( - address[] memory targets, - uint256[] memory values, - bytes[] memory calldatas, - bytes32 descriptionHash - ) public virtual override(IGovernor, Governor) returns (uint256) { - uint256 proposalId = hashProposal(targets, values, calldatas, descriptionHash); - address proposer = proposalProposer(proposalId); - - uint256 proposerVotes = getVotes(proposer, clock() - 1); - uint256 votesThreshold = proposalThreshold(); - if (_msgSender() != proposer && proposerVotes >= votesThreshold) { - revert GovernorInsufficientProposerVotes(proposer, proposerVotes, votesThreshold); - } - - return _cancel(targets, values, calldatas, descriptionHash); - } - - /** - * @dev Encodes calldatas with optional function signature. - */ - function _encodeCalldata( - string[] memory signatures, - bytes[] memory calldatas - ) private pure returns (bytes[] memory) { - bytes[] memory fullcalldatas = new bytes[](calldatas.length); - for (uint256 i = 0; i < fullcalldatas.length; ++i) { - fullcalldatas[i] = bytes(signatures[i]).length == 0 - ? calldatas[i] - : bytes.concat(abi.encodeWithSignature(signatures[i]), calldatas[i]); - } - - return fullcalldatas; - } - - /** - * @dev Retrieve proposal parameters by id, with fully encoded calldatas. - */ - function _getProposalParameters( - uint256 proposalId - ) - private - view - returns (address[] memory targets, uint256[] memory values, bytes[] memory calldatas, bytes32 descriptionHash) - { - ProposalDetails storage details = _proposalDetails[proposalId]; - return ( - details.targets, - details.values, - _encodeCalldata(details.signatures, details.calldatas), - details.descriptionHash - ); - } - - /** - * @dev Store proposal metadata (if not already present) for later lookup. - */ - function _storeProposal( - address[] memory targets, - uint256[] memory values, - string[] memory signatures, - bytes[] memory calldatas, - string memory description - ) private { - bytes32 descriptionHash = keccak256(bytes(description)); - uint256 proposalId = hashProposal(targets, values, _encodeCalldata(signatures, calldatas), descriptionHash); - - ProposalDetails storage details = _proposalDetails[proposalId]; - if (details.descriptionHash == bytes32(0)) { - details.targets = targets; - details.values = values; - details.signatures = signatures; - details.calldatas = calldatas; - details.descriptionHash = descriptionHash; - } - } - - // ==================================================== Views ===================================================== - /** - * @dev See {IGovernorCompatibilityBravo-proposals}. - */ - function proposals( - uint256 proposalId - ) - public - view - virtual - override - returns ( - uint256 id, - address proposer, - uint256 eta, - uint256 startBlock, - uint256 endBlock, - uint256 forVotes, - uint256 againstVotes, - uint256 abstainVotes, - bool canceled, - bool executed - ) - { - id = proposalId; - proposer = proposalProposer(proposalId); - eta = proposalEta(proposalId); - startBlock = proposalSnapshot(proposalId); - endBlock = proposalDeadline(proposalId); - - ProposalDetails storage details = _proposalDetails[proposalId]; - forVotes = details.forVotes; - againstVotes = details.againstVotes; - abstainVotes = details.abstainVotes; - - ProposalState currentState = state(proposalId); - canceled = currentState == ProposalState.Canceled; - executed = currentState == ProposalState.Executed; - } - - /** - * @dev See {IGovernorCompatibilityBravo-getActions}. - */ - function getActions( - uint256 proposalId - ) - public - view - virtual - override - returns ( - address[] memory targets, - uint256[] memory values, - string[] memory signatures, - bytes[] memory calldatas - ) - { - ProposalDetails storage details = _proposalDetails[proposalId]; - return (details.targets, details.values, details.signatures, details.calldatas); - } - - /** - * @dev See {IGovernorCompatibilityBravo-getReceipt}. - */ - function getReceipt(uint256 proposalId, address voter) public view virtual override returns (Receipt memory) { - return _proposalDetails[proposalId].receipts[voter]; - } - - /** - * @dev See {IGovernorCompatibilityBravo-quorumVotes}. - */ - function quorumVotes() public view virtual override returns (uint256) { - return quorum(clock() - 1); - } - - // ==================================================== Voting ==================================================== - /** - * @dev See {IGovernor-hasVoted}. - */ - function hasVoted(uint256 proposalId, address account) public view virtual override returns (bool) { - return _proposalDetails[proposalId].receipts[account].hasVoted; - } - - /** - * @dev See {Governor-_quorumReached}. In this module, only forVotes count toward the quorum. - */ - function _quorumReached(uint256 proposalId) internal view virtual override returns (bool) { - ProposalDetails storage details = _proposalDetails[proposalId]; - return quorum(proposalSnapshot(proposalId)) <= details.forVotes; - } - - /** - * @dev See {Governor-_voteSucceeded}. In this module, the forVotes must be strictly over the againstVotes. - */ - function _voteSucceeded(uint256 proposalId) internal view virtual override returns (bool) { - ProposalDetails storage details = _proposalDetails[proposalId]; - return details.forVotes > details.againstVotes; - } - - /** - * @dev See {Governor-_countVote}. In this module, the support follows Governor Bravo. - */ - function _countVote( - uint256 proposalId, - address account, - uint8 support, - uint256 weight, - bytes memory // params - ) internal virtual override { - ProposalDetails storage details = _proposalDetails[proposalId]; - Receipt storage receipt = details.receipts[account]; - - if (receipt.hasVoted) { - revert GovernorAlreadyCastVote(account); - } - receipt.hasVoted = true; - receipt.support = support; - receipt.votes = SafeCast.toUint96(weight); - - if (support == uint8(VoteType.Against)) { - details.againstVotes += weight; - } else if (support == uint8(VoteType.For)) { - details.forVotes += weight; - } else if (support == uint8(VoteType.Abstain)) { - details.abstainVotes += weight; - } else { - revert GovernorInvalidVoteType(); - } - } -} diff --git a/contracts/governance/compatibility/IGovernorCompatibilityBravo.sol b/contracts/governance/compatibility/IGovernorCompatibilityBravo.sol deleted file mode 100644 index 26fdb31ea..000000000 --- a/contracts/governance/compatibility/IGovernorCompatibilityBravo.sol +++ /dev/null @@ -1,121 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.9.0) (governance/compatibility/IGovernorCompatibilityBravo.sol) - -pragma solidity ^0.8.20; - -import {IGovernor} from "../IGovernor.sol"; - -/** - * @dev Interface extension that adds missing functions to the {Governor} core to provide `GovernorBravo` compatibility. - */ -abstract contract IGovernorCompatibilityBravo is IGovernor { - /** - * @dev Mismatch between the parameters length for a proposal call. - */ - error GovernorInvalidSignaturesLength(uint256 signatures, uint256 calldatas); - - /** - * @dev Proposal structure from Compound Governor Bravo. Not actually used by the compatibility layer, as - * {{proposal}} returns a very different structure. - */ - struct Proposal { - uint256 id; - address proposer; - uint256 eta; - address[] targets; - uint256[] values; - string[] signatures; - bytes[] calldatas; - uint256 startBlock; - uint256 endBlock; - uint256 forVotes; - uint256 againstVotes; - uint256 abstainVotes; - bool canceled; - bool executed; - mapping(address => Receipt) receipts; - } - - /** - * @dev Receipt structure from Compound Governor Bravo - */ - struct Receipt { - bool hasVoted; - uint8 support; - uint96 votes; - } - - /** - * @dev Part of the Governor Bravo's interface. - */ - function quorumVotes() public view virtual returns (uint256); - - /** - * @dev Part of the Governor Bravo's interface: _"The official record of all proposals ever proposed"_. - */ - function proposals( - uint256 - ) - public - view - virtual - returns ( - uint256 id, - address proposer, - uint256 eta, - uint256 startBlock, - uint256 endBlock, - uint256 forVotes, - uint256 againstVotes, - uint256 abstainVotes, - bool canceled, - bool executed - ); - - /** - * @dev Part of the Governor Bravo's interface: _"Function used to propose a new proposal"_. - */ - function propose( - address[] memory targets, - uint256[] memory values, - string[] memory signatures, - bytes[] memory calldatas, - string memory description - ) public virtual returns (uint256); - - /** - * @dev Part of the Governor Bravo's interface: _"Queues a proposal of state succeeded"_. - */ - function queue(uint256 proposalId) public virtual; - - /** - * @dev Part of the Governor Bravo's interface: _"Executes a queued proposal if eta has passed"_. - */ - function execute(uint256 proposalId) public payable virtual; - - /** - * @dev Cancels a proposal only if the sender is the proposer or the proposer delegates' voting power dropped below the proposal threshold. - */ - function cancel(uint256 proposalId) public virtual; - - /** - * @dev Part of the Governor Bravo's interface: _"Gets actions of a proposal"_. - */ - function getActions( - uint256 proposalId - ) - public - view - virtual - returns ( - address[] memory targets, - uint256[] memory values, - string[] memory signatures, - bytes[] memory calldatas - ); - - /** - * @dev Part of the Governor Bravo's interface: _"Gets the receipt for a voter on a given proposal"_. - */ - function getReceipt(uint256 proposalId, address voter) public view virtual returns (Receipt memory); -} diff --git a/contracts/governance/extensions/GovernorStorage.sol b/contracts/governance/extensions/GovernorStorage.sol new file mode 100644 index 000000000..6443b7895 --- /dev/null +++ b/contracts/governance/extensions/GovernorStorage.sol @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.19; + +import {Governor} from "../Governor.sol"; + +/** + * @dev Extension of {Governor} that implements storage of proposal details. This modules also provides primitives for + * the enumerability of proposals. + * + * Use cases for this module include: + * - UIs that explore the proposal state without relying on event indexing. + * - Using only the proposalId as an argument in the {Governor-queue} and {Governor-execute} functions for L2 chains where storage is cheap compared to calldata. + */ +abstract contract GovernorStorage is Governor { + struct ProposalDetails { + address[] targets; + uint256[] values; + bytes[] calldatas; + bytes32 descriptionHash; + } + + uint256[] private _proposalIds; + mapping(uint256 => ProposalDetails) private _proposalDetails; + + /** + * @dev Hook into the proposing mechanism + */ + function _propose( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + string memory description, + address proposer + ) internal virtual override returns (uint256) { + uint256 proposalId = super._propose(targets, values, calldatas, description, proposer); + + // store + _proposalIds.push(proposalId); + _proposalDetails[proposalId] = ProposalDetails({ + targets: targets, + values: values, + calldatas: calldatas, + descriptionHash: keccak256(bytes(description)) + }); + + return proposalId; + } + + /** + * @dev Version of {IGovernorTimelock-queue} with only `proposalId` as an argument. + */ + function queue(uint256 proposalId) public virtual { + // here, using storage is more efficient than memory + ProposalDetails storage details = _proposalDetails[proposalId]; + queue(details.targets, details.values, details.calldatas, details.descriptionHash); + } + + /** + * @dev Version of {IGovernor-execute} with only `proposalId` as an argument. + */ + function execute(uint256 proposalId) public payable virtual { + // here, using storage is more efficient than memory + ProposalDetails storage details = _proposalDetails[proposalId]; + execute(details.targets, details.values, details.calldatas, details.descriptionHash); + } + + /** + * @dev ProposalId version of {IGovernor-cancel}. + */ + function cancel(uint256 proposalId) public virtual { + // here, using storage is more efficient than memory + ProposalDetails storage details = _proposalDetails[proposalId]; + cancel(details.targets, details.values, details.calldatas, details.descriptionHash); + } + + /** + * @dev Returns the number of stored proposals. + */ + function proposalCount() public view virtual returns (uint256) { + return _proposalIds.length; + } + + /** + * @dev Returns the details of a proposalId. Reverts if `proposalId` is not a known proposal. + */ + function proposalDetails( + uint256 proposalId + ) public view virtual returns (address[] memory, uint256[] memory, bytes[] memory, bytes32) { + // here, using memory is more efficient than storage + ProposalDetails memory details = _proposalDetails[proposalId]; + if (details.descriptionHash == 0) { + revert GovernorNonexistentProposal(proposalId); + } + return (details.targets, details.values, details.calldatas, details.descriptionHash); + } + + /** + * @dev Returns the details (including the proposalId) of a proposal given its sequential index. + */ + function proposalDetailsAt( + uint256 index + ) public view virtual returns (uint256, address[] memory, uint256[] memory, bytes[] memory, bytes32) { + uint256 proposalId = _proposalIds[index]; + ( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 descriptionHash + ) = proposalDetails(proposalId); + return (proposalId, targets, values, calldatas, descriptionHash); + } +} diff --git a/contracts/governance/extensions/GovernorTimelockCompound.sol b/contracts/governance/extensions/GovernorTimelockCompound.sol index 30181b6d2..fe554dad5 100644 --- a/contracts/governance/extensions/GovernorTimelockCompound.sol +++ b/contracts/governance/extensions/GovernorTimelockCompound.sol @@ -3,11 +3,11 @@ pragma solidity ^0.8.20; -import {IGovernorTimelock} from "./IGovernorTimelock.sol"; import {IGovernor, Governor} from "../Governor.sol"; import {ICompoundTimelock} from "../../vendor/compound/ICompoundTimelock.sol"; import {IERC165} from "../../interfaces/IERC165.sol"; import {Address} from "../../utils/Address.sol"; +import {SafeCast} from "../../utils/math/SafeCast.sol"; /** * @dev Extension of {Governor} that binds the execution process to a Compound Timelock. This adds a delay, enforced by @@ -19,11 +19,9 @@ import {Address} from "../../utils/Address.sol"; * the assets and permissions must be attached to the {TimelockController}. Any asset sent to the {Governor} will be * inaccessible. */ -abstract contract GovernorTimelockCompound is IGovernorTimelock, Governor { +abstract contract GovernorTimelockCompound is Governor { ICompoundTimelock private _timelock; - mapping(uint256 => uint256) private _proposalTimelocks; - /** * @dev Emitted when the timelock controller used for proposal execution is modified. */ @@ -37,68 +35,36 @@ abstract contract GovernorTimelockCompound is IGovernorTimelock, Governor { } /** - * @dev See {IERC165-supportsInterface}. - */ - function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, Governor) returns (bool) { - return interfaceId == type(IGovernorTimelock).interfaceId || super.supportsInterface(interfaceId); - } - - /** - * @dev Overridden version of the {Governor-state} function with added support for the `Queued` and `Expired` state. + * @dev Overridden version of the {Governor-state} function with added support for the `Expired` state. */ - function state(uint256 proposalId) public view virtual override(IGovernor, Governor) returns (ProposalState) { + function state(uint256 proposalId) public view virtual override returns (ProposalState) { ProposalState currentState = super.state(proposalId); - if (currentState != ProposalState.Succeeded) { - return currentState; - } - - uint256 eta = proposalEta(proposalId); - if (eta == 0) { - return currentState; - } else if (block.timestamp >= eta + _timelock.GRACE_PERIOD()) { - return ProposalState.Expired; - } else { - return ProposalState.Queued; - } + return + (currentState == ProposalState.Queued && + block.timestamp >= proposalEta(proposalId) + _timelock.GRACE_PERIOD()) + ? ProposalState.Expired + : currentState; } /** * @dev Public accessor to check the address of the timelock */ - function timelock() public view virtual override returns (address) { + function timelock() public view virtual returns (address) { return address(_timelock); } - /** - * @dev Public accessor to check the eta of a queued proposal - */ - function proposalEta(uint256 proposalId) public view virtual override returns (uint256) { - return _proposalTimelocks[proposalId]; - } - /** * @dev Function to queue a proposal to the timelock. */ - function queue( + function _queueOperations( + uint256 proposalId, address[] memory targets, uint256[] memory values, bytes[] memory calldatas, - bytes32 descriptionHash - ) public virtual override returns (uint256) { - uint256 proposalId = hashProposal(targets, values, calldatas, descriptionHash); - - ProposalState currentState = state(proposalId); - if (currentState != ProposalState.Succeeded) { - revert GovernorUnexpectedProposalState( - proposalId, - currentState, - _encodeStateBitmap(ProposalState.Succeeded) - ); - } - - uint256 eta = block.timestamp + _timelock.delay(); - _proposalTimelocks[proposalId] = eta; + bytes32 /*descriptionHash*/ + ) internal virtual override returns (uint48) { + uint48 eta = SafeCast.toUint48(block.timestamp + _timelock.delay()); for (uint256 i = 0; i < targets.length; ++i) { if (_timelock.queuedTransactions(keccak256(abi.encode(targets[i], values[i], "", calldatas[i], eta)))) { @@ -107,15 +73,14 @@ abstract contract GovernorTimelockCompound is IGovernorTimelock, Governor { _timelock.queueTransaction(targets[i], values[i], "", calldatas[i], eta); } - emit ProposalQueued(proposalId, eta); - - return proposalId; + return eta; } /** - * @dev Overridden execute function that run the already queued proposal through the timelock. + * @dev Overridden version of the {Governor-_executeOperations} function that run the already queued proposal through + * the timelock. */ - function _execute( + function _executeOperations( uint256 proposalId, address[] memory targets, uint256[] memory values, @@ -146,8 +111,6 @@ abstract contract GovernorTimelockCompound is IGovernorTimelock, Governor { uint256 eta = proposalEta(proposalId); if (eta > 0) { - // update state first - delete _proposalTimelocks[proposalId]; // do external call later for (uint256 i = 0; i < targets.length; ++i) { _timelock.cancelTransaction(targets[i], values[i], "", calldatas[i], eta); diff --git a/contracts/governance/extensions/GovernorTimelockControl.sol b/contracts/governance/extensions/GovernorTimelockControl.sol index 615b9330a..c468f328b 100644 --- a/contracts/governance/extensions/GovernorTimelockControl.sol +++ b/contracts/governance/extensions/GovernorTimelockControl.sol @@ -3,10 +3,10 @@ pragma solidity ^0.8.20; -import {IGovernorTimelock} from "./IGovernorTimelock.sol"; import {IGovernor, Governor} from "../Governor.sol"; import {TimelockController} from "../TimelockController.sol"; import {IERC165} from "../../interfaces/IERC165.sol"; +import {SafeCast} from "../../utils/math/SafeCast.sol"; /** * @dev Extension of {Governor} that binds the execution process to an instance of {TimelockController}. This adds a @@ -22,7 +22,7 @@ import {IERC165} from "../../interfaces/IERC165.sol"; * available to them through the timelock, and 2) approved governance proposals can be blocked by them, effectively * executing a Denial of Service attack. This risk will be mitigated in a future release. */ -abstract contract GovernorTimelockControl is IGovernorTimelock, Governor { +abstract contract GovernorTimelockControl is Governor { TimelockController private _timelock; mapping(uint256 => bytes32) private _timelockIds; @@ -39,27 +39,17 @@ abstract contract GovernorTimelockControl is IGovernorTimelock, Governor { } /** - * @dev See {IERC165-supportsInterface}. + * @dev Overridden version of the {Governor-state} function that considers the status reported by the timelock. */ - function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, Governor) returns (bool) { - return interfaceId == type(IGovernorTimelock).interfaceId || super.supportsInterface(interfaceId); - } - - /** - * @dev Overridden version of the {Governor-state} function with added support for the `Queued` state. - */ - function state(uint256 proposalId) public view virtual override(IGovernor, Governor) returns (ProposalState) { + function state(uint256 proposalId) public view virtual override returns (ProposalState) { ProposalState currentState = super.state(proposalId); - if (currentState != ProposalState.Succeeded) { + if (currentState != ProposalState.Queued) { return currentState; } - // core tracks execution, so we just have to check if successful proposal have been queued. bytes32 queueid = _timelockIds[proposalId]; - if (queueid == bytes32(0)) { - return currentState; - } else if (_timelock.isOperationPending(queueid)) { + if (_timelock.isOperationPending(queueid)) { return ProposalState.Queued; } else if (_timelock.isOperationDone(queueid)) { // This can happen if the proposal is executed directly on the timelock. @@ -73,52 +63,34 @@ abstract contract GovernorTimelockControl is IGovernorTimelock, Governor { /** * @dev Public accessor to check the address of the timelock */ - function timelock() public view virtual override returns (address) { + function timelock() public view virtual returns (address) { return address(_timelock); } - /** - * @dev Public accessor to check the eta of a queued proposal - */ - function proposalEta(uint256 proposalId) public view virtual override returns (uint256) { - uint256 eta = _timelock.getTimestamp(_timelockIds[proposalId]); - return eta == 1 ? 0 : eta; // _DONE_TIMESTAMP (1) should be replaced with a 0 value - } - /** * @dev Function to queue a proposal to the timelock. */ - function queue( + function _queueOperations( + uint256 proposalId, address[] memory targets, uint256[] memory values, bytes[] memory calldatas, bytes32 descriptionHash - ) public virtual override returns (uint256) { - uint256 proposalId = hashProposal(targets, values, calldatas, descriptionHash); - - ProposalState currentState = state(proposalId); - if (currentState != ProposalState.Succeeded) { - revert GovernorUnexpectedProposalState( - proposalId, - currentState, - _encodeStateBitmap(ProposalState.Succeeded) - ); - } - + ) internal virtual override returns (uint48) { uint256 delay = _timelock.getMinDelay(); + bytes32 salt = _timelockSalt(descriptionHash); _timelockIds[proposalId] = _timelock.hashOperationBatch(targets, values, calldatas, 0, salt); _timelock.scheduleBatch(targets, values, calldatas, 0, salt, delay); - emit ProposalQueued(proposalId, block.timestamp + delay); - - return proposalId; + return SafeCast.toUint48(block.timestamp + delay); } /** - * @dev Overridden execute function that run the already queued proposal through the timelock. + * @dev Overridden version of the {Governor-_executeOperations} function that runs the already queued proposal through + * the timelock. */ - function _execute( + function _executeOperations( uint256 proposalId, address[] memory targets, uint256[] memory values, @@ -145,8 +117,8 @@ abstract contract GovernorTimelockControl is IGovernorTimelock, Governor { bytes32 descriptionHash ) internal virtual override returns (uint256) { uint256 proposalId = super._cancel(targets, values, calldatas, descriptionHash); - bytes32 timelockId = _timelockIds[proposalId]; + bytes32 timelockId = _timelockIds[proposalId]; if (timelockId != 0) { // cancel _timelock.cancel(timelockId); diff --git a/contracts/governance/extensions/IGovernorTimelock.sol b/contracts/governance/extensions/IGovernorTimelock.sol deleted file mode 100644 index 28a9f9330..000000000 --- a/contracts/governance/extensions/IGovernorTimelock.sol +++ /dev/null @@ -1,34 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts v4.4.1 (governance/extensions/IGovernorTimelock.sol) - -pragma solidity ^0.8.20; - -import {IGovernor} from "../IGovernor.sol"; - -/** - * @dev Extension of the {IGovernor} for timelock supporting modules. - */ -abstract contract IGovernorTimelock is IGovernor { - /** - * @dev The proposal hasn't been queued yet. - */ - error GovernorNotQueuedProposal(uint256 proposalId); - - /** - * @dev The proposal has already been queued. - */ - error GovernorAlreadyQueuedProposal(uint256 proposalId); - - event ProposalQueued(uint256 proposalId, uint256 eta); - - function timelock() public view virtual returns (address); - - function proposalEta(uint256 proposalId) public view virtual returns (uint256); - - function queue( - address[] memory targets, - uint256[] memory values, - bytes[] memory calldatas, - bytes32 descriptionHash - ) public virtual returns (uint256 proposalId); -} diff --git a/contracts/mocks/docs/governance/MyGovernor.sol b/contracts/mocks/docs/governance/MyGovernor.sol index 2ac6d2cef..7cd5f3c2c 100644 --- a/contracts/mocks/docs/governance/MyGovernor.sol +++ b/contracts/mocks/docs/governance/MyGovernor.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.20; import {IGovernor, Governor} from "../../../governance/Governor.sol"; -import {GovernorCompatibilityBravo} from "../../../governance/compatibility/GovernorCompatibilityBravo.sol"; +import {GovernorCountingSimple} from "../../../governance/extensions/GovernorCountingSimple.sol"; import {GovernorVotes} from "../../../governance/extensions/GovernorVotes.sol"; import {GovernorVotesQuorumFraction} from "../../../governance/extensions/GovernorVotesQuorumFraction.sol"; import {GovernorTimelockControl} from "../../../governance/extensions/GovernorTimelockControl.sol"; @@ -12,7 +12,7 @@ import {IERC165} from "../../../interfaces/IERC165.sol"; contract MyGovernor is Governor, - GovernorCompatibilityBravo, + GovernorCountingSimple, GovernorVotes, GovernorVotesQuorumFraction, GovernorTimelockControl @@ -36,38 +36,28 @@ contract MyGovernor is // The functions below are overrides required by Solidity. - function state( - uint256 proposalId - ) public view override(Governor, IGovernor, GovernorTimelockControl) returns (ProposalState) { + function state(uint256 proposalId) public view override(Governor, GovernorTimelockControl) returns (ProposalState) { return super.state(proposalId); } - function propose( - address[] memory targets, - uint256[] memory values, - bytes[] memory calldatas, - string memory description - ) public override(Governor, GovernorCompatibilityBravo, IGovernor) returns (uint256) { - return super.propose(targets, values, calldatas, description); - } - - function cancel( + function _queueOperations( + uint256 proposalId, address[] memory targets, uint256[] memory values, bytes[] memory calldatas, bytes32 descriptionHash - ) public override(Governor, GovernorCompatibilityBravo, IGovernor) returns (uint256) { - return super.cancel(targets, values, calldatas, descriptionHash); + ) internal override(Governor, GovernorTimelockControl) returns (uint48) { + return super._queueOperations(proposalId, targets, values, calldatas, descriptionHash); } - function _execute( + function _executeOperations( uint256 proposalId, address[] memory targets, uint256[] memory values, bytes[] memory calldatas, bytes32 descriptionHash ) internal override(Governor, GovernorTimelockControl) { - super._execute(proposalId, targets, values, calldatas, descriptionHash); + super._executeOperations(proposalId, targets, values, calldatas, descriptionHash); } function _cancel( @@ -82,10 +72,4 @@ contract MyGovernor is function _executor() internal view override(Governor, GovernorTimelockControl) returns (address) { return super._executor(); } - - function supportsInterface( - bytes4 interfaceId - ) public view override(Governor, IERC165, GovernorTimelockControl) returns (bool) { - return super.supportsInterface(interfaceId); - } } diff --git a/contracts/mocks/governance/GovernorCompatibilityBravoMock.sol b/contracts/mocks/governance/GovernorCompatibilityBravoMock.sol deleted file mode 100644 index 041c85f28..000000000 --- a/contracts/mocks/governance/GovernorCompatibilityBravoMock.sol +++ /dev/null @@ -1,102 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.20; - -import {IGovernor, Governor} from "../../governance/Governor.sol"; -import {GovernorCompatibilityBravo} from "../../governance/compatibility/GovernorCompatibilityBravo.sol"; -import {IGovernorTimelock, GovernorTimelockCompound} from "../../governance/extensions/GovernorTimelockCompound.sol"; -import {GovernorSettings} from "../../governance/extensions/GovernorSettings.sol"; -import {GovernorVotes} from "../../governance/extensions/GovernorVotes.sol"; -import {IERC165} from "../../interfaces/IERC165.sol"; - -abstract contract GovernorCompatibilityBravoMock is - GovernorCompatibilityBravo, - GovernorSettings, - GovernorTimelockCompound, - GovernorVotes -{ - function quorum(uint256) public pure override returns (uint256) { - return 0; - } - - function supportsInterface( - bytes4 interfaceId - ) public view override(IERC165, Governor, GovernorTimelockCompound) returns (bool) { - return super.supportsInterface(interfaceId); - } - - function state( - uint256 proposalId - ) public view override(IGovernor, Governor, GovernorTimelockCompound) returns (ProposalState) { - return super.state(proposalId); - } - - function proposalEta( - uint256 proposalId - ) public view override(IGovernorTimelock, GovernorTimelockCompound) returns (uint256) { - return super.proposalEta(proposalId); - } - - function proposalThreshold() public view override(Governor, GovernorSettings) returns (uint256) { - return super.proposalThreshold(); - } - - function propose( - address[] memory targets, - uint256[] memory values, - bytes[] memory calldatas, - string memory description - ) public override(IGovernor, Governor, GovernorCompatibilityBravo) returns (uint256) { - return super.propose(targets, values, calldatas, description); - } - - function queue( - address[] memory targets, - uint256[] memory values, - bytes[] memory calldatas, - bytes32 salt - ) public override(IGovernorTimelock, GovernorTimelockCompound) returns (uint256) { - return super.queue(targets, values, calldatas, salt); - } - - function execute( - address[] memory targets, - uint256[] memory values, - bytes[] memory calldatas, - bytes32 salt - ) public payable override(IGovernor, Governor) returns (uint256) { - return super.execute(targets, values, calldatas, salt); - } - - function cancel( - address[] memory targets, - uint256[] memory values, - bytes[] memory calldatas, - bytes32 descriptionHash - ) public override(Governor, GovernorCompatibilityBravo, IGovernor) returns (uint256) { - return super.cancel(targets, values, calldatas, descriptionHash); - } - - function _execute( - uint256 proposalId, - address[] memory targets, - uint256[] memory values, - bytes[] memory calldatas, - bytes32 descriptionHash - ) internal override(Governor, GovernorTimelockCompound) { - super._execute(proposalId, targets, values, calldatas, descriptionHash); - } - - function _cancel( - address[] memory targets, - uint256[] memory values, - bytes[] memory calldatas, - bytes32 salt - ) internal override(Governor, GovernorTimelockCompound) returns (uint256 proposalId) { - return super._cancel(targets, values, calldatas, salt); - } - - function _executor() internal view override(Governor, GovernorTimelockCompound) returns (address) { - return super._executor(); - } -} diff --git a/contracts/mocks/governance/GovernorStorageMock.sol b/contracts/mocks/governance/GovernorStorageMock.sol new file mode 100644 index 000000000..3d08a0031 --- /dev/null +++ b/contracts/mocks/governance/GovernorStorageMock.sol @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.19; + +import {IGovernor, Governor} from "../../governance/Governor.sol"; +import {GovernorTimelockControl} from "../../governance/extensions/GovernorTimelockControl.sol"; +import {GovernorSettings} from "../../governance/extensions/GovernorSettings.sol"; +import {GovernorCountingSimple} from "../../governance/extensions/GovernorCountingSimple.sol"; +import {GovernorVotesQuorumFraction} from "../../governance/extensions/GovernorVotesQuorumFraction.sol"; +import {GovernorStorage} from "../../governance/extensions/GovernorStorage.sol"; + +abstract contract GovernorStorageMock is + GovernorSettings, + GovernorTimelockControl, + GovernorVotesQuorumFraction, + GovernorCountingSimple, + GovernorStorage +{ + function quorum(uint256 blockNumber) public view override(Governor, GovernorVotesQuorumFraction) returns (uint256) { + return super.quorum(blockNumber); + } + + function state(uint256 proposalId) public view override(Governor, GovernorTimelockControl) returns (ProposalState) { + return super.state(proposalId); + } + + function proposalThreshold() public view override(Governor, GovernorSettings) returns (uint256) { + return super.proposalThreshold(); + } + + function _propose( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + string memory description, + address proposer + ) internal virtual override(Governor, GovernorStorage) returns (uint256) { + return super._propose(targets, values, calldatas, description, proposer); + } + + function _queueOperations( + uint256 proposalId, + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 descriptionHash + ) internal override(Governor, GovernorTimelockControl) returns (uint48) { + return super._queueOperations(proposalId, targets, values, calldatas, descriptionHash); + } + + function _executeOperations( + uint256 proposalId, + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 descriptionHash + ) internal override(Governor, GovernorTimelockControl) { + super._executeOperations(proposalId, targets, values, calldatas, descriptionHash); + } + + function _cancel( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 descriptionHash + ) internal override(Governor, GovernorTimelockControl) returns (uint256) { + return super._cancel(targets, values, calldatas, descriptionHash); + } + + function _executor() internal view override(Governor, GovernorTimelockControl) returns (address) { + return super._executor(); + } +} diff --git a/contracts/mocks/governance/GovernorTimelockCompoundMock.sol b/contracts/mocks/governance/GovernorTimelockCompoundMock.sol index d17d4428a..124b0346f 100644 --- a/contracts/mocks/governance/GovernorTimelockCompoundMock.sol +++ b/contracts/mocks/governance/GovernorTimelockCompoundMock.sol @@ -14,15 +14,7 @@ abstract contract GovernorTimelockCompoundMock is GovernorVotesQuorumFraction, GovernorCountingSimple { - function supportsInterface( - bytes4 interfaceId - ) public view override(Governor, GovernorTimelockCompound) returns (bool) { - return super.supportsInterface(interfaceId); - } - - function quorum( - uint256 blockNumber - ) public view override(IGovernor, GovernorVotesQuorumFraction) returns (uint256) { + function quorum(uint256 blockNumber) public view override(Governor, GovernorVotesQuorumFraction) returns (uint256) { return super.quorum(blockNumber); } @@ -36,23 +28,33 @@ abstract contract GovernorTimelockCompoundMock is return super.proposalThreshold(); } - function _execute( + function _queueOperations( + uint256 proposalId, + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 descriptionHash + ) internal override(Governor, GovernorTimelockCompound) returns (uint48) { + return super._queueOperations(proposalId, targets, values, calldatas, descriptionHash); + } + + function _executeOperations( uint256 proposalId, address[] memory targets, uint256[] memory values, bytes[] memory calldatas, bytes32 descriptionHash ) internal override(Governor, GovernorTimelockCompound) { - super._execute(proposalId, targets, values, calldatas, descriptionHash); + super._executeOperations(proposalId, targets, values, calldatas, descriptionHash); } function _cancel( address[] memory targets, uint256[] memory values, bytes[] memory calldatas, - bytes32 salt - ) internal override(Governor, GovernorTimelockCompound) returns (uint256 proposalId) { - return super._cancel(targets, values, calldatas, salt); + bytes32 descriptionHash + ) internal override(Governor, GovernorTimelockCompound) returns (uint256) { + return super._cancel(targets, values, calldatas, descriptionHash); } function _executor() internal view override(Governor, GovernorTimelockCompound) returns (address) { diff --git a/contracts/mocks/governance/GovernorTimelockControlMock.sol b/contracts/mocks/governance/GovernorTimelockControlMock.sol index 7182a38be..125aa37df 100644 --- a/contracts/mocks/governance/GovernorTimelockControlMock.sol +++ b/contracts/mocks/governance/GovernorTimelockControlMock.sol @@ -14,15 +14,7 @@ abstract contract GovernorTimelockControlMock is GovernorVotesQuorumFraction, GovernorCountingSimple { - function supportsInterface( - bytes4 interfaceId - ) public view override(Governor, GovernorTimelockControl) returns (bool) { - return super.supportsInterface(interfaceId); - } - - function quorum( - uint256 blockNumber - ) public view override(IGovernor, GovernorVotesQuorumFraction) returns (uint256) { + function quorum(uint256 blockNumber) public view override(Governor, GovernorVotesQuorumFraction) returns (uint256) { return super.quorum(blockNumber); } @@ -34,14 +26,24 @@ abstract contract GovernorTimelockControlMock is return super.proposalThreshold(); } - function _execute( + function _queueOperations( + uint256 proposalId, + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 descriptionHash + ) internal override(Governor, GovernorTimelockControl) returns (uint48) { + return super._queueOperations(proposalId, targets, values, calldatas, descriptionHash); + } + + function _executeOperations( uint256 proposalId, address[] memory targets, uint256[] memory values, bytes[] memory calldatas, bytes32 descriptionHash ) internal override(Governor, GovernorTimelockControl) { - super._execute(proposalId, targets, values, calldatas, descriptionHash); + super._executeOperations(proposalId, targets, values, calldatas, descriptionHash); } function _cancel( @@ -49,7 +51,7 @@ abstract contract GovernorTimelockControlMock is uint256[] memory values, bytes[] memory calldatas, bytes32 descriptionHash - ) internal override(Governor, GovernorTimelockControl) returns (uint256 proposalId) { + ) internal override(Governor, GovernorTimelockControl) returns (uint256) { return super._cancel(targets, values, calldatas, descriptionHash); } diff --git a/docs/modules/ROOT/pages/governance.adoc b/docs/modules/ROOT/pages/governance.adoc index ce5a9c340..b8db64230 100644 --- a/docs/modules/ROOT/pages/governance.adoc +++ b/docs/modules/ROOT/pages/governance.adoc @@ -18,11 +18,11 @@ OpenZeppelin’s Governor system was designed with a concern for compatibility w The ERC20 extension to keep track of votes and vote delegation is one such case. The shorter one is the more generic version because it can support token supplies greater than 2^96, while the “Comp” variant is limited in that regard, but exactly fits the interface of the COMP token that is used by GovernorAlpha and Bravo. Both contract variants share the same events, so they are fully compatible when looking at events only. -=== Governor & GovernorCompatibilityBravo +=== Governor & GovernorStorage -An OpenZeppelin Governor contract is by default not interface-compatible with Compound's GovernorAlpha or Bravo. Even though events are fully compatible, proposal lifecycle functions (creation, execution, etc.) have different signatures that are meant to optimize storage use. Other functions from GovernorAlpha are Bravo are likewise not available. It’s possible to opt in to a higher level of compatibility by inheriting from the GovernorCompatibilityBravo module, which covers the proposal lifecycle functions such as `propose` and `execute`. +An OpenZeppelin Governor contract is not interface-compatible with Compound's GovernorAlpha or Bravo. Even though events are fully compatible, proposal lifecycle functions (creation, execution, etc.) have different signatures that are meant to optimize storage use. Other functions from GovernorAlpha are Bravo are likewise not available. It’s possible to opt in some Bravo-like behavior by inheriting from the GovernorStorage module. This module provides proposal enumerability and alternate versions of the `queue`, `execute` and `cancel` function that only take the proposal id. This module reduces the calldata needed by some operations in exchange for an increased the storage footprint. This might be a good trade-off for some L2 chains. It also provides primitives for indexer-free frontends. -Note that even with the use of this module, there will still be differences in the way that `proposalId`s are calculated. Governor uses the hash of the proposal parameters with the purpose of keeping its data off-chain by event indexing, while the original Bravo implementation uses sequential `proposalId`s. Due to this and other differences, several of the functions from GovernorBravo are not included in the compatibility module. +Note that even with the use of this module, one important difference with Compound's GovernorBravo is the way that `proposalId`s are calculated. Governor uses the hash of the proposal parameters with the purpose of keeping its data off-chain by event indexing, while the original Bravo implementation uses sequential `proposalId`s. === GovernorTimelockControl & GovernorTimelockCompound @@ -201,13 +201,14 @@ The Governor will automatically detect the clock mode used by the token and adap pragma solidity ^0.8.20; import {Governor} from "@openzeppelin/contracts/governance/Governor.sol"; -import {GovernorCompatibilityBravo} from "@openzeppelin/contracts/governance/compatibility/GovernorCompatibilityBravo.sol"; +import {GovernorCountingSimple} from "@openzeppelin/contracts/governance/compatibility/GovernorCountingSimple.sol"; import {GovernorVotes} from "@openzeppelin/contracts/governance/extensions/GovernorVotes.sol"; import {GovernorVotesQuorumFraction} from "@openzeppelin/contracts/governance/extensions/GovernorVotesQuorumFraction.sol"; import {GovernorTimelockControl} from "@openzeppelin/contracts/governance/extensions/GovernorTimelockControl.sol"; +import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol"; import {IVotes} from "@openzeppelin/contracts/governance/utils/IVotes.sol"; -contract MyGovernor is Governor, GovernorCompatibilityBravo, GovernorVotes, GovernorVotesQuorumFraction, GovernorTimelockControl { +contract MyGovernor is Governor, GovernorCountingSimple, GovernorVotes, GovernorVotesQuorumFraction, GovernorTimelockControl { constructor(IVotes _token, TimelockController _timelock) Governor("MyGovernor") GovernorVotes(_token) diff --git a/test/governance/Governor.test.js b/test/governance/Governor.test.js index 4e47ab9f2..9dfa53e7b 100644 --- a/test/governance/Governor.test.js +++ b/test/governance/Governor.test.js @@ -79,7 +79,7 @@ contract('Governor', function (accounts) { ); }); - shouldSupportInterfaces(['ERC165', 'ERC1155Receiver', 'Governor', 'GovernorWithParams', 'GovernorCancel']); + shouldSupportInterfaces(['ERC165', 'ERC1155Receiver', 'Governor']); shouldBehaveLikeEIP6372(mode); it('deployment check', async function () { @@ -305,6 +305,17 @@ contract('Governor', function (accounts) { ZERO_BYTES32, ]); }); + + it('if proposer has below threshold votes', async function () { + const votes = web3.utils.toWei('10'); + const threshold = web3.utils.toWei('1000'); + await this.mock.$_setProposalThreshold(threshold); + await expectRevertCustomError(this.helper.propose({ from: voter1 }), 'GovernorInsufficientProposerVotes', [ + voter1, + votes, + threshold, + ]); + }); }); describe('on vote', function () { @@ -427,6 +438,16 @@ contract('Governor', function (accounts) { }); }); + describe('on queue', function () { + it('always', async function () { + await this.helper.propose({ from: proposer }); + await this.helper.waitForSnapshot(); + await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }); + await this.helper.waitForDeadline(); + await expectRevertCustomError(this.helper.queue(), 'GovernorQueueNotImplemented', []); + }); + }); + describe('on execute', function () { it('if proposal does not exist', async function () { await expectRevertCustomError(this.helper.execute(), 'GovernorNonexistentProposal', [this.proposal.id]); @@ -826,7 +847,9 @@ contract('Governor', function (accounts) { }); it('someone else cannot propose', async function () { - await expectRevert(this.helper.propose({ from: voter1 }), 'Governor: proposer restricted'); + await expectRevertCustomError(this.helper.propose({ from: voter1 }), 'GovernorRestrictedProposer', [ + voter1, + ]); }); }); }); diff --git a/test/governance/compatibility/GovernorCompatibilityBravo.test.js b/test/governance/compatibility/GovernorCompatibilityBravo.test.js deleted file mode 100644 index 26255342f..000000000 --- a/test/governance/compatibility/GovernorCompatibilityBravo.test.js +++ /dev/null @@ -1,297 +0,0 @@ -const { expectEvent } = require('@openzeppelin/test-helpers'); -const { expect } = require('chai'); -const { computeCreateAddress } = require('../../helpers/create'); -const Enums = require('../../helpers/enums'); -const { GovernorHelper } = require('../../helpers/governance'); -const { clockFromReceipt } = require('../../helpers/time'); -const { expectRevertCustomError } = require('../../helpers/customError'); - -const Timelock = artifacts.require('CompTimelock'); -const Governor = artifacts.require('$GovernorCompatibilityBravoMock'); -const CallReceiver = artifacts.require('CallReceiverMock'); - -const { shouldBehaveLikeEIP6372 } = require('../utils/EIP6372.behavior'); - -const TOKENS = [ - { Token: artifacts.require('$ERC20Votes'), mode: 'blocknumber' }, - { Token: artifacts.require('$ERC20VotesTimestampMock'), mode: 'timestamp' }, -]; - -contract('GovernorCompatibilityBravo', function (accounts) { - const [owner, proposer, voter1, voter2, voter3, voter4, other] = accounts; - - const name = 'OZ-Governor'; - const version = '1'; - const tokenName = 'MockToken'; - const tokenSymbol = 'MTKN'; - const tokenSupply = web3.utils.toWei('100'); - const votingDelay = web3.utils.toBN(4); - const votingPeriod = web3.utils.toBN(16); - const proposalThreshold = web3.utils.toWei('10'); - const value = web3.utils.toWei('1'); - - const votes = { - [owner]: tokenSupply, - [proposer]: proposalThreshold, - [voter1]: web3.utils.toWei('10'), - [voter2]: web3.utils.toWei('7'), - [voter3]: web3.utils.toWei('5'), - [voter4]: web3.utils.toWei('2'), - [other]: 0, - }; - - for (const { mode, Token } of TOKENS) { - describe(`using ${Token._json.contractName}`, function () { - beforeEach(async function () { - const [deployer] = await web3.eth.getAccounts(); - - this.token = await Token.new(tokenName, tokenSymbol, tokenName, version); - - // Need to predict governance address to set it as timelock admin with a delayed transfer - const nonce = await web3.eth.getTransactionCount(deployer); - const predictGovernor = computeCreateAddress(deployer, nonce + 1); - - this.timelock = await Timelock.new(predictGovernor, 2 * 86400); - this.mock = await Governor.new( - name, - votingDelay, - votingPeriod, - proposalThreshold, - this.timelock.address, - this.token.address, - ); - this.receiver = await CallReceiver.new(); - - this.helper = new GovernorHelper(this.mock, mode); - - await web3.eth.sendTransaction({ from: owner, to: this.timelock.address, value }); - - await this.token.$_mint(owner, tokenSupply); - await this.helper.delegate({ token: this.token, to: proposer, value: votes[proposer] }, { from: owner }); - await this.helper.delegate({ token: this.token, to: voter1, value: votes[voter1] }, { from: owner }); - await this.helper.delegate({ token: this.token, to: voter2, value: votes[voter2] }, { from: owner }); - await this.helper.delegate({ token: this.token, to: voter3, value: votes[voter3] }, { from: owner }); - await this.helper.delegate({ token: this.token, to: voter4, value: votes[voter4] }, { from: owner }); - - // default proposal - this.proposal = this.helper.setProposal( - [ - { - target: this.receiver.address, - value, - signature: 'mockFunction()', - }, - ], - '', - ); - }); - - shouldBehaveLikeEIP6372(mode); - - it('deployment check', async function () { - expect(await this.mock.name()).to.be.equal(name); - expect(await this.mock.token()).to.be.equal(this.token.address); - expect(await this.mock.votingDelay()).to.be.bignumber.equal(votingDelay); - expect(await this.mock.votingPeriod()).to.be.bignumber.equal(votingPeriod); - expect(await this.mock.quorum(0)).to.be.bignumber.equal('0'); - expect(await this.mock.quorumVotes()).to.be.bignumber.equal('0'); - expect(await this.mock.COUNTING_MODE()).to.be.equal('support=bravo&quorum=bravo'); - }); - - it('nominal workflow', async function () { - // Before - expect(await this.mock.hasVoted(this.proposal.id, owner)).to.be.equal(false); - expect(await this.mock.hasVoted(this.proposal.id, voter1)).to.be.equal(false); - expect(await this.mock.hasVoted(this.proposal.id, voter2)).to.be.equal(false); - expect(await web3.eth.getBalance(this.mock.address)).to.be.bignumber.equal('0'); - expect(await web3.eth.getBalance(this.timelock.address)).to.be.bignumber.equal(value); - expect(await web3.eth.getBalance(this.receiver.address)).to.be.bignumber.equal('0'); - - // Run proposal - const txPropose = await this.helper.propose({ from: proposer }); - await this.helper.waitForSnapshot(); - await this.helper.vote({ support: Enums.VoteType.For, reason: 'This is nice' }, { from: voter1 }); - await this.helper.vote({ support: Enums.VoteType.For }, { from: voter2 }); - await this.helper.vote({ support: Enums.VoteType.Against }, { from: voter3 }); - await this.helper.vote({ support: Enums.VoteType.Abstain }, { from: voter4 }); - await this.helper.waitForDeadline(); - await this.helper.queue(); - await this.helper.waitForEta(); - const txExecute = await this.helper.execute(); - - // After - expect(await this.mock.hasVoted(this.proposal.id, owner)).to.be.equal(false); - expect(await this.mock.hasVoted(this.proposal.id, voter1)).to.be.equal(true); - expect(await this.mock.hasVoted(this.proposal.id, voter2)).to.be.equal(true); - expect(await web3.eth.getBalance(this.mock.address)).to.be.bignumber.equal('0'); - expect(await web3.eth.getBalance(this.timelock.address)).to.be.bignumber.equal('0'); - expect(await web3.eth.getBalance(this.receiver.address)).to.be.bignumber.equal(value); - - const proposal = await this.mock.proposals(this.proposal.id); - expect(proposal.id).to.be.bignumber.equal(this.proposal.id); - expect(proposal.proposer).to.be.equal(proposer); - expect(proposal.eta).to.be.bignumber.equal(await this.mock.proposalEta(this.proposal.id)); - expect(proposal.startBlock).to.be.bignumber.equal(await this.mock.proposalSnapshot(this.proposal.id)); - expect(proposal.endBlock).to.be.bignumber.equal(await this.mock.proposalDeadline(this.proposal.id)); - expect(proposal.canceled).to.be.equal(false); - expect(proposal.executed).to.be.equal(true); - - const action = await this.mock.getActions(this.proposal.id); - expect(action.targets).to.be.deep.equal(this.proposal.targets); - // expect(action.values).to.be.deep.equal(this.proposal.values); - expect(action.signatures).to.be.deep.equal(this.proposal.signatures); - expect(action.calldatas).to.be.deep.equal(this.proposal.data); - - const voteReceipt1 = await this.mock.getReceipt(this.proposal.id, voter1); - expect(voteReceipt1.hasVoted).to.be.equal(true); - expect(voteReceipt1.support).to.be.bignumber.equal(Enums.VoteType.For); - expect(voteReceipt1.votes).to.be.bignumber.equal(web3.utils.toWei('10')); - - const voteReceipt2 = await this.mock.getReceipt(this.proposal.id, voter2); - expect(voteReceipt2.hasVoted).to.be.equal(true); - expect(voteReceipt2.support).to.be.bignumber.equal(Enums.VoteType.For); - expect(voteReceipt2.votes).to.be.bignumber.equal(web3.utils.toWei('7')); - - const voteReceipt3 = await this.mock.getReceipt(this.proposal.id, voter3); - expect(voteReceipt3.hasVoted).to.be.equal(true); - expect(voteReceipt3.support).to.be.bignumber.equal(Enums.VoteType.Against); - expect(voteReceipt3.votes).to.be.bignumber.equal(web3.utils.toWei('5')); - - const voteReceipt4 = await this.mock.getReceipt(this.proposal.id, voter4); - expect(voteReceipt4.hasVoted).to.be.equal(true); - expect(voteReceipt4.support).to.be.bignumber.equal(Enums.VoteType.Abstain); - expect(voteReceipt4.votes).to.be.bignumber.equal(web3.utils.toWei('2')); - - expectEvent(txPropose, 'ProposalCreated', { - proposalId: this.proposal.id, - proposer, - targets: this.proposal.targets, - // values: this.proposal.values, - signatures: this.proposal.signatures.map(() => ''), // this event doesn't contain the proposal detail - calldatas: this.proposal.fulldata, - voteStart: web3.utils.toBN(await clockFromReceipt[mode](txPropose.receipt)).add(votingDelay), - voteEnd: web3.utils - .toBN(await clockFromReceipt[mode](txPropose.receipt)) - .add(votingDelay) - .add(votingPeriod), - description: this.proposal.description, - }); - expectEvent(txExecute, 'ProposalExecuted', { proposalId: this.proposal.id }); - await expectEvent.inTransaction(txExecute.tx, this.receiver, 'MockFunctionCalled'); - }); - - it('double voting is forbidden', async function () { - await this.helper.propose({ from: proposer }); - await this.helper.waitForSnapshot(); - await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }); - await expectRevertCustomError( - this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }), - 'GovernorAlreadyCastVote', - [voter1], - ); - }); - - it('with function selector and arguments', async function () { - const target = this.receiver.address; - this.helper.setProposal( - [ - { target, data: this.receiver.contract.methods.mockFunction().encodeABI() }, - { target, data: this.receiver.contract.methods.mockFunctionWithArgs(17, 42).encodeABI() }, - { target, signature: 'mockFunctionNonPayable()' }, - { - target, - signature: 'mockFunctionWithArgs(uint256,uint256)', - data: web3.eth.abi.encodeParameters(['uint256', 'uint256'], [18, 43]), - }, - ], - '', - ); - - await this.helper.propose({ from: proposer }); - await this.helper.waitForSnapshot(); - await this.helper.vote({ support: Enums.VoteType.For, reason: 'This is nice' }, { from: voter1 }); - await this.helper.waitForDeadline(); - await this.helper.queue(); - await this.helper.waitForEta(); - const txExecute = await this.helper.execute(); - - await expectEvent.inTransaction(txExecute.tx, this.receiver, 'MockFunctionCalled'); - await expectEvent.inTransaction(txExecute.tx, this.receiver, 'MockFunctionCalled'); - await expectEvent.inTransaction(txExecute.tx, this.receiver, 'MockFunctionCalledWithArgs', { - a: '17', - b: '42', - }); - await expectEvent.inTransaction(txExecute.tx, this.receiver, 'MockFunctionCalledWithArgs', { - a: '18', - b: '43', - }); - }); - - it('with inconsistent array size for selector and arguments', async function () { - const target = this.receiver.address; - const signatures = ['mockFunction()']; // One signature - const data = ['0x', this.receiver.contract.methods.mockFunctionWithArgs(17, 42).encodeABI()]; // Two data entries - this.helper.setProposal( - { - targets: [target, target], - values: [0, 0], - signatures, - data, - }, - '', - ); - - await expectRevertCustomError(this.helper.propose({ from: proposer }), 'GovernorInvalidSignaturesLength', [ - signatures.length, - data.length, - ]); - }); - - describe('should revert', function () { - describe('on propose', function () { - it('if proposal does not meet proposalThreshold', async function () { - await expectRevertCustomError(this.helper.propose({ from: other }), 'GovernorInsufficientProposerVotes', [ - other, - votes[other], - proposalThreshold, - ]); - }); - }); - - describe('on vote', function () { - it('if vote type is invalid', async function () { - await this.helper.propose({ from: proposer }); - await this.helper.waitForSnapshot(); - await expectRevertCustomError( - this.helper.vote({ support: 5 }, { from: voter1 }), - 'GovernorInvalidVoteType', - [], - ); - }); - }); - }); - - describe('cancel', function () { - it('proposer can cancel', async function () { - await this.helper.propose({ from: proposer }); - await this.helper.cancel('external', { from: proposer }); - }); - - it('anyone can cancel if proposer drop below threshold', async function () { - await this.helper.propose({ from: proposer }); - await this.token.transfer(voter1, web3.utils.toWei('1'), { from: proposer }); - await this.helper.cancel('external'); - }); - - it('cannot cancel is proposer is still above threshold', async function () { - await this.helper.propose({ from: proposer }); - await expectRevertCustomError(this.helper.cancel('external'), 'GovernorInsufficientProposerVotes', [ - proposer, - votes[proposer], - proposalThreshold, - ]); - }); - }); - }); - } -}); diff --git a/test/governance/extensions/GovernorStorage.test.js b/test/governance/extensions/GovernorStorage.test.js new file mode 100644 index 000000000..99a97886c --- /dev/null +++ b/test/governance/extensions/GovernorStorage.test.js @@ -0,0 +1,150 @@ +const { constants, expectEvent, expectRevert } = require('@openzeppelin/test-helpers'); +const { expect } = require('chai'); + +const { expectRevertCustomError } = require('../../helpers/customError'); +const Enums = require('../../helpers/enums'); +const { GovernorHelper, timelockSalt } = require('../../helpers/governance'); + +const Timelock = artifacts.require('TimelockController'); +const Governor = artifacts.require('$GovernorStorageMock'); +const CallReceiver = artifacts.require('CallReceiverMock'); + +const TOKENS = [ + { Token: artifacts.require('$ERC20Votes'), mode: 'blocknumber' }, + { Token: artifacts.require('$ERC20VotesTimestampMock'), mode: 'timestamp' }, +]; + +contract('GovernorStorage', function (accounts) { + const [owner, voter1, voter2, voter3, voter4] = accounts; + + const DEFAULT_ADMIN_ROLE = '0x0000000000000000000000000000000000000000000000000000000000000000'; + const PROPOSER_ROLE = web3.utils.soliditySha3('PROPOSER_ROLE'); + const EXECUTOR_ROLE = web3.utils.soliditySha3('EXECUTOR_ROLE'); + const CANCELLER_ROLE = web3.utils.soliditySha3('CANCELLER_ROLE'); + + const name = 'OZ-Governor'; + const version = '1'; + const tokenName = 'MockToken'; + const tokenSymbol = 'MTKN'; + const tokenSupply = web3.utils.toWei('100'); + const votingDelay = web3.utils.toBN(4); + const votingPeriod = web3.utils.toBN(16); + const value = web3.utils.toWei('1'); + + for (const { mode, Token } of TOKENS) { + describe(`using ${Token._json.contractName}`, function () { + beforeEach(async function () { + const [deployer] = await web3.eth.getAccounts(); + + this.token = await Token.new(tokenName, tokenSymbol, tokenName, version); + this.timelock = await Timelock.new(3600, [], [], deployer); + this.mock = await Governor.new( + name, + votingDelay, + votingPeriod, + 0, + this.timelock.address, + this.token.address, + 0, + ); + this.receiver = await CallReceiver.new(); + + this.helper = new GovernorHelper(this.mock, mode); + + await web3.eth.sendTransaction({ from: owner, to: this.timelock.address, value }); + + // normal setup: governor is proposer, everyone is executor, timelock is its own admin + await this.timelock.grantRole(PROPOSER_ROLE, this.mock.address); + await this.timelock.grantRole(PROPOSER_ROLE, owner); + await this.timelock.grantRole(CANCELLER_ROLE, this.mock.address); + await this.timelock.grantRole(CANCELLER_ROLE, owner); + await this.timelock.grantRole(EXECUTOR_ROLE, constants.ZERO_ADDRESS); + await this.timelock.revokeRole(DEFAULT_ADMIN_ROLE, deployer); + + await this.token.$_mint(owner, tokenSupply); + await this.helper.delegate({ token: this.token, to: voter1, value: web3.utils.toWei('10') }, { from: owner }); + await this.helper.delegate({ token: this.token, to: voter2, value: web3.utils.toWei('7') }, { from: owner }); + await this.helper.delegate({ token: this.token, to: voter3, value: web3.utils.toWei('5') }, { from: owner }); + await this.helper.delegate({ token: this.token, to: voter4, value: web3.utils.toWei('2') }, { from: owner }); + + // default proposal + this.proposal = this.helper.setProposal( + [ + { + target: this.receiver.address, + value, + data: this.receiver.contract.methods.mockFunction().encodeABI(), + }, + ], + '', + ); + this.proposal.timelockid = await this.timelock.hashOperationBatch( + ...this.proposal.shortProposal.slice(0, 3), + '0x0', + timelockSalt(this.mock.address, this.proposal.shortProposal[3]), + ); + }); + + describe('proposal indexing', function () { + it('before propose', async function () { + expect(await this.mock.proposalCount()).to.be.bignumber.equal('0'); + + // panic code 0x32 (out-of-bound) + await expectRevert.unspecified(this.mock.proposalDetailsAt(0)); + + await expectRevertCustomError(this.mock.proposalDetails(this.proposal.id), 'GovernorNonexistentProposal', [ + this.proposal.id, + ]); + }); + + it('after propose', async function () { + await this.helper.propose(); + + expect(await this.mock.proposalCount()).to.be.bignumber.equal('1'); + + const proposalDetailsAt0 = await this.mock.proposalDetailsAt(0); + expect(proposalDetailsAt0[0]).to.be.bignumber.equal(this.proposal.id); + expect(proposalDetailsAt0[1]).to.be.deep.equal(this.proposal.targets); + expect(proposalDetailsAt0[2].map(x => x.toString())).to.be.deep.equal(this.proposal.values); + expect(proposalDetailsAt0[3]).to.be.deep.equal(this.proposal.fulldata); + expect(proposalDetailsAt0[4]).to.be.equal(this.proposal.descriptionHash); + + const proposalDetailsForId = await this.mock.proposalDetails(this.proposal.id); + expect(proposalDetailsForId[0]).to.be.deep.equal(this.proposal.targets); + expect(proposalDetailsForId[1].map(x => x.toString())).to.be.deep.equal(this.proposal.values); + expect(proposalDetailsForId[2]).to.be.deep.equal(this.proposal.fulldata); + expect(proposalDetailsForId[3]).to.be.equal(this.proposal.descriptionHash); + }); + }); + + it('queue and execute by id', async function () { + await this.helper.propose(); + await this.helper.waitForSnapshot(); + await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }); + await this.helper.vote({ support: Enums.VoteType.For }, { from: voter2 }); + await this.helper.vote({ support: Enums.VoteType.Against }, { from: voter3 }); + await this.helper.vote({ support: Enums.VoteType.Abstain }, { from: voter4 }); + await this.helper.waitForDeadline(); + const txQueue = await this.mock.queue(this.proposal.id); + await this.helper.waitForEta(); + const txExecute = await this.mock.execute(this.proposal.id); + + expectEvent(txQueue, 'ProposalQueued', { proposalId: this.proposal.id }); + await expectEvent.inTransaction(txQueue.tx, this.timelock, 'CallScheduled', { id: this.proposal.timelockid }); + await expectEvent.inTransaction(txQueue.tx, this.timelock, 'CallSalt', { + id: this.proposal.timelockid, + }); + + expectEvent(txExecute, 'ProposalExecuted', { proposalId: this.proposal.id }); + await expectEvent.inTransaction(txExecute.tx, this.timelock, 'CallExecuted', { id: this.proposal.timelockid }); + await expectEvent.inTransaction(txExecute.tx, this.receiver, 'MockFunctionCalled'); + }); + + it('cancel by id', async function () { + await this.helper.propose(); + const txCancel = await this.mock.cancel(this.proposal.id); + expectEvent(txCancel, 'ProposalCanceled', { proposalId: this.proposal.id }); + }); + }); + } +}); diff --git a/test/governance/extensions/GovernorTimelockCompound.test.js b/test/governance/extensions/GovernorTimelockCompound.test.js index 7a2cb0abd..0f7fd6352 100644 --- a/test/governance/extensions/GovernorTimelockCompound.test.js +++ b/test/governance/extensions/GovernorTimelockCompound.test.js @@ -6,8 +6,6 @@ const { GovernorHelper, proposalStatesToBitMap } = require('../../helpers/govern const { expectRevertCustomError } = require('../../helpers/customError'); const { computeCreateAddress } = require('../../helpers/create'); -const { shouldSupportInterfaces } = require('../../utils/introspection/SupportsInterface.behavior'); - const Timelock = artifacts.require('CompTimelock'); const Governor = artifacts.require('$GovernorTimelockCompoundMock'); const CallReceiver = artifacts.require('CallReceiverMock'); @@ -77,8 +75,6 @@ contract('GovernorTimelockCompound', function (accounts) { ); }); - shouldSupportInterfaces(['ERC165', 'Governor', 'GovernorWithParams', 'GovernorTimelock']); - it("doesn't accept ether transfers", async function () { await expectRevert.unspecified(web3.eth.sendTransaction({ from: owner, to: this.mock.address, value: 1 })); }); diff --git a/test/governance/extensions/GovernorTimelockControl.test.js b/test/governance/extensions/GovernorTimelockControl.test.js index 8dde8acb9..fe40d30e9 100644 --- a/test/governance/extensions/GovernorTimelockControl.test.js +++ b/test/governance/extensions/GovernorTimelockControl.test.js @@ -2,11 +2,9 @@ const { constants, expectEvent, expectRevert, time } = require('@openzeppelin/te const { expect } = require('chai'); const Enums = require('../../helpers/enums'); -const { GovernorHelper, proposalStatesToBitMap } = require('../../helpers/governance'); +const { GovernorHelper, proposalStatesToBitMap, timelockSalt } = require('../../helpers/governance'); const { expectRevertCustomError } = require('../../helpers/customError'); -const { shouldSupportInterfaces } = require('../../utils/introspection/SupportsInterface.behavior'); - const Timelock = artifacts.require('TimelockController'); const Governor = artifacts.require('$GovernorTimelockControlMock'); const CallReceiver = artifacts.require('CallReceiverMock'); @@ -37,9 +35,6 @@ contract('GovernorTimelockControl', function (accounts) { for (const { mode, Token } of TOKENS) { describe(`using ${Token._json.contractName}`, function () { - const timelockSalt = (address, descriptionHash) => - '0x' + web3.utils.toBN(address).shln(96).xor(web3.utils.toBN(descriptionHash)).toString(16, 64); - beforeEach(async function () { const [deployer] = await web3.eth.getAccounts(); @@ -97,8 +92,6 @@ contract('GovernorTimelockControl', function (accounts) { ); }); - shouldSupportInterfaces(['ERC165', 'Governor', 'GovernorWithParams', 'GovernorTimelock']); - it("doesn't accept ether transfers", async function () { await expectRevert.unspecified(web3.eth.sendTransaction({ from: owner, to: this.mock.address, value: 1 })); }); diff --git a/test/helpers/create.js b/test/helpers/create.js index e0cea66e2..98a0d4c47 100644 --- a/test/helpers/create.js +++ b/test/helpers/create.js @@ -1,17 +1,16 @@ -const { rlp } = require('ethereumjs-util'); +const RLP = require('rlp'); function computeCreateAddress(deployer, nonce) { - return web3.utils.toChecksumAddress(web3.utils.sha3(rlp.encode([deployer.address ?? deployer, nonce])).slice(-40)); + return web3.utils.toChecksumAddress(web3.utils.sha3(RLP.encode([deployer.address ?? deployer, nonce])).slice(-40)); } function computeCreate2Address(saltHex, bytecode, deployer) { return web3.utils.toChecksumAddress( web3.utils .sha3( - '0x' + - ['ff', deployer.address ?? deployer, saltHex, web3.utils.soliditySha3(bytecode)] - .map(x => x.replace(/0x/, '')) - .join(''), + `0x${['ff', deployer.address ?? deployer, saltHex, web3.utils.soliditySha3(bytecode)] + .map(x => x.replace(/0x/, '')) + .join('')}`, ) .slice(-40), ); diff --git a/test/helpers/governance.js b/test/helpers/governance.js index 9b3349cf8..fc4e30095 100644 --- a/test/helpers/governance.js +++ b/test/helpers/governance.js @@ -1,3 +1,4 @@ +const { web3 } = require('hardhat'); const { forward } = require('../helpers/time'); const { ProposalState } = require('./enums'); @@ -15,6 +16,9 @@ function concatOpts(args, opts = null) { return opts ? args.concat(opts) : args; } +const timelockSalt = (address, descriptionHash) => + '0x' + web3.utils.toBN(address).shln(96).xor(web3.utils.toBN(descriptionHash)).toString(16, 64); + class GovernorHelper { constructor(governor, mode = 'blocknumber') { this.governor = governor; @@ -245,4 +249,5 @@ function proposalStatesToBitMap(proposalStates, options = {}) { module.exports = { GovernorHelper, proposalStatesToBitMap, + timelockSalt, }; diff --git a/test/proxy/Clones.test.js b/test/proxy/Clones.test.js index 14a70e20b..0862778f7 100644 --- a/test/proxy/Clones.test.js +++ b/test/proxy/Clones.test.js @@ -1,7 +1,6 @@ const { expectEvent } = require('@openzeppelin/test-helpers'); -const { computeCreate2Address } = require('../helpers/create'); const { expect } = require('chai'); - +const { computeCreate2Address } = require('../helpers/create'); const { expectRevertCustomError } = require('../helpers/customError'); const shouldBehaveLikeClone = require('./Clones.behaviour'); diff --git a/test/utils/Create2.test.js b/test/utils/Create2.test.js index afbcc3db9..4215e37c0 100644 --- a/test/utils/Create2.test.js +++ b/test/utils/Create2.test.js @@ -1,6 +1,6 @@ const { balance, ether, expectEvent, expectRevert, send } = require('@openzeppelin/test-helpers'); -const { computeCreate2Address } = require('../helpers/create'); const { expect } = require('chai'); +const { computeCreate2Address } = require('../helpers/create'); const { expectRevertCustomError } = require('../helpers/customError'); const Create2 = artifacts.require('$Create2'); diff --git a/test/utils/introspection/SupportsInterface.behavior.js b/test/utils/introspection/SupportsInterface.behavior.js index 80e144f18..d254082d2 100644 --- a/test/utils/introspection/SupportsInterface.behavior.js +++ b/test/utils/introspection/SupportsInterface.behavior.js @@ -56,27 +56,11 @@ const INTERFACES = { 'COUNTING_MODE()', 'hashProposal(address[],uint256[],bytes[],bytes32)', 'state(uint256)', + 'proposalThreshold()', 'proposalSnapshot(uint256)', 'proposalDeadline(uint256)', - 'votingDelay()', - 'votingPeriod()', - 'quorum(uint256)', - 'getVotes(address,uint256)', - 'hasVoted(uint256,address)', - 'propose(address[],uint256[],bytes[],string)', - 'execute(address[],uint256[],bytes[],bytes32)', - 'castVote(uint256,uint8)', - 'castVoteWithReason(uint256,uint8,string)', - 'castVoteBySig(uint256,uint8,address,bytes)', - ], - GovernorWithParams: [ - 'name()', - 'version()', - 'COUNTING_MODE()', - 'hashProposal(address[],uint256[],bytes[],bytes32)', - 'state(uint256)', - 'proposalSnapshot(uint256)', - 'proposalDeadline(uint256)', + 'proposalProposer(uint256)', + 'proposalEta(uint256)', 'votingDelay()', 'votingPeriod()', 'quorum(uint256)', @@ -84,15 +68,15 @@ const INTERFACES = { 'getVotesWithParams(address,uint256,bytes)', 'hasVoted(uint256,address)', 'propose(address[],uint256[],bytes[],string)', + 'queue(address[],uint256[],bytes[],bytes32)', 'execute(address[],uint256[],bytes[],bytes32)', + 'cancel(address[],uint256[],bytes[],bytes32)', 'castVote(uint256,uint8)', 'castVoteWithReason(uint256,uint8,string)', 'castVoteWithReasonAndParams(uint256,uint8,string,bytes)', 'castVoteBySig(uint256,uint8,address,bytes)', 'castVoteWithReasonAndParamsBySig(uint256,uint8,address,string,bytes,bytes)', ], - GovernorCancel: ['proposalProposer(uint256)', 'cancel(address[],uint256[],bytes[],bytes32)'], - GovernorTimelock: ['timelock()', 'proposalEta(uint256)', 'queue(address[],uint256[],bytes[],bytes32)'], ERC2981: ['royaltyInfo(uint256,uint256)'], }; From f715365ec40515159ac5f4a4d6f85625ab3b405d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Fri, 4 Aug 2023 14:23:38 -0600 Subject: [PATCH 180/182] Implement recommendations from 5.0 audit Phase 1B (#4502) Co-authored-by: Hadrien Croubois Co-authored-by: Francisco Giordano --- .changeset/fifty-owls-retire.md | 5 + .changeset/hip-goats-fail.md | 5 + contracts/finance/VestingWallet.sol | 5 +- contracts/metatx/ERC2771Context.sol | 32 ++++- contracts/metatx/ERC2771Forwarder.sol | 112 +++++++++++---- contracts/mocks/AddressFnPointersMock.sol | 50 ------- contracts/mocks/CallReceiverMock.sol | 12 ++ contracts/mocks/UpgradeableBeaconMock.sol | 27 ++++ contracts/mocks/UpgreadeableBeaconMock.sol | 12 -- contracts/proxy/ERC1967/ERC1967Proxy.sol | 10 +- contracts/proxy/ERC1967/ERC1967Utils.sol | 15 +- contracts/proxy/Proxy.sol | 9 -- contracts/proxy/beacon/IBeacon.sol | 2 +- contracts/proxy/beacon/UpgradeableBeacon.sol | 2 +- contracts/proxy/transparent/ProxyAdmin.sol | 2 +- .../TransparentUpgradeableProxy.sol | 20 ++- contracts/proxy/utils/UUPSUpgradeable.sol | 7 +- contracts/utils/Address.sol | 132 ++++-------------- contracts/utils/Strings.sol | 6 +- test/metatx/ERC2771Context.test.js | 6 +- test/metatx/ERC2771Forwarder.t.sol | 6 +- test/metatx/ERC2771Forwarder.test.js | 29 +++- test/proxy/ERC1967/ERC1967Utils.test.js | 12 ++ test/proxy/beacon/UpgradeableBeacon.test.js | 7 + test/utils/Address.test.js | 44 +----- 25 files changed, 286 insertions(+), 283 deletions(-) create mode 100644 .changeset/fifty-owls-retire.md create mode 100644 .changeset/hip-goats-fail.md delete mode 100644 contracts/mocks/AddressFnPointersMock.sol create mode 100644 contracts/mocks/UpgradeableBeaconMock.sol delete mode 100644 contracts/mocks/UpgreadeableBeaconMock.sol diff --git a/.changeset/fifty-owls-retire.md b/.changeset/fifty-owls-retire.md new file mode 100644 index 000000000..118fad421 --- /dev/null +++ b/.changeset/fifty-owls-retire.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': major +--- + +`Address`: Removed the ability to customize error messages. A common custom error is always used if the underlying revert reason cannot be bubbled up. diff --git a/.changeset/hip-goats-fail.md b/.changeset/hip-goats-fail.md new file mode 100644 index 000000000..5cfe2ef79 --- /dev/null +++ b/.changeset/hip-goats-fail.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': patch +--- + +`VestingWallet`: Fix revert during 1 second time window when duration is 0. diff --git a/contracts/finance/VestingWallet.sol b/contracts/finance/VestingWallet.sol index 840837d27..2e8fdab82 100644 --- a/contracts/finance/VestingWallet.sol +++ b/contracts/finance/VestingWallet.sol @@ -19,6 +19,9 @@ import {Context} from "../utils/Context.sol"; * * By setting the duration to 0, one can configure this contract to behave like an asset timelock that hold tokens for * a beneficiary until a specified time. + * + * NOTE: When using this contract with any token whose balance is adjusted automatically (i.e. a rebase token), make sure + * to account the supply/balance adjustment in the vesting schedule to ensure the vested amount is as intended. */ contract VestingWallet is Context { event EtherReleased(uint256 amount); @@ -154,7 +157,7 @@ contract VestingWallet is Context { function _vestingSchedule(uint256 totalAllocation, uint64 timestamp) internal view virtual returns (uint256) { if (timestamp < start()) { return 0; - } else if (timestamp > end()) { + } else if (timestamp >= end()) { return totalAllocation; } else { return (totalAllocation * (timestamp - start())) / duration(); diff --git a/contracts/metatx/ERC2771Context.sol b/contracts/metatx/ERC2771Context.sol index 4a2df6e3d..1084afb15 100644 --- a/contracts/metatx/ERC2771Context.sol +++ b/contracts/metatx/ERC2771Context.sol @@ -18,15 +18,36 @@ abstract contract ERC2771Context is Context { /// @custom:oz-upgrades-unsafe-allow state-variable-immutable address private immutable _trustedForwarder; + /** + * @dev Initializes the contract with a trusted forwarder, which will be able to + * invoke functions on this contract on behalf of other accounts. + * + * NOTE: The trusted forwarder can be replaced by overriding {trustedForwarder}. + */ /// @custom:oz-upgrades-unsafe-allow constructor - constructor(address trustedForwarder) { - _trustedForwarder = trustedForwarder; + constructor(address trustedForwarder_) { + _trustedForwarder = trustedForwarder_; } + /** + * @dev Returns the address of the trusted forwarder. + */ + function trustedForwarder() public view virtual returns (address) { + return _trustedForwarder; + } + + /** + * @dev Indicates whether any particular address is the trusted forwarder. + */ function isTrustedForwarder(address forwarder) public view virtual returns (bool) { - return forwarder == _trustedForwarder; + return forwarder == trustedForwarder(); } + /** + * @dev Override for `msg.sender`. Defaults to the original `msg.sender` whenever + * a call is not performed by the trusted forwarder or the calldata length is less than + * 20 bytes (an address length). + */ function _msgSender() internal view virtual override returns (address sender) { if (isTrustedForwarder(msg.sender) && msg.data.length >= 20) { // The assembly code is more direct than the Solidity version using `abi.decode`. @@ -39,6 +60,11 @@ abstract contract ERC2771Context is Context { } } + /** + * @dev Override for `msg.data`. Defaults to the original `msg.data` whenever + * a call is not performed by the trusted forwarder or the calldata length is less than + * 20 bytes (an address length). + */ function _msgData() internal view virtual override returns (bytes calldata) { if (isTrustedForwarder(msg.sender) && msg.data.length >= 20) { return msg.data[:msg.data.length - 20]; diff --git a/contracts/metatx/ERC2771Forwarder.sol b/contracts/metatx/ERC2771Forwarder.sol index 93e334622..6f8e000d2 100644 --- a/contracts/metatx/ERC2771Forwarder.sol +++ b/contracts/metatx/ERC2771Forwarder.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.20; +import {ERC2771Context} from "./ERC2771Context.sol"; import {ECDSA} from "../utils/cryptography/ECDSA.sol"; import {EIP712} from "../utils/cryptography/EIP712.sol"; import {Nonces} from "../utils/Nonces.sol"; @@ -26,6 +27,16 @@ import {Address} from "../utils/Address.sol"; * transactions in the mempool. In these cases the recommendation is to distribute the load among * multiple accounts. * + * WARNING: Do not approve this contract to spend tokens. Anyone can use this forwarder + * to execute calls with an arbitrary calldata to any address. Any form of approval may + * result in a loss of funds for the approving party. + * + * NOTE: Batching requests includes an optional refund for unused `msg.value` that is achieved by + * performing a call with empty calldata. While this is within the bounds of ERC-2771 compliance, + * if the refund receiver happens to consider the forwarder a trusted forwarder, it MUST properly + * handle `msg.data.length == 0`. `ERC2771Context` in OpenZeppelin Contracts versions prior to 4.9.3 + * do not handle this properly. + * * ==== Security Considerations * * If a relayer submits a forward request, it should be willing to pay up to 100% of the gas amount @@ -82,6 +93,11 @@ contract ERC2771Forwarder is EIP712, Nonces { */ error ERC2771ForwarderExpiredRequest(uint48 deadline); + /** + * @dev The request target doesn't trust the `forwarder`. + */ + error ERC2771UntrustfulTarget(address target, address forwarder); + /** * @dev See {EIP712-constructor}. */ @@ -90,15 +106,15 @@ contract ERC2771Forwarder is EIP712, Nonces { /** * @dev Returns `true` if a request is valid for a provided `signature` at the current block timestamp. * - * A transaction is considered valid when it hasn't expired (deadline is not met), and the signer - * matches the `from` parameter of the signed request. + * A transaction is considered valid when the target trusts this forwarder, the request hasn't expired + * (deadline is not met), and the signer matches the `from` parameter of the signed request. * * NOTE: A request may return false here but it won't cause {executeBatch} to revert if a refund * receiver is provided. */ function verify(ForwardRequestData calldata request) public view virtual returns (bool) { - (bool alive, bool signerMatch, ) = _validate(request); - return alive && signerMatch; + (bool isTrustedForwarder, bool active, bool signerMatch, ) = _validate(request); + return isTrustedForwarder && active && signerMatch; } /** @@ -186,31 +202,42 @@ contract ERC2771Forwarder is EIP712, Nonces { */ function _validate( ForwardRequestData calldata request - ) internal view virtual returns (bool alive, bool signerMatch, address signer) { - signer = _recoverForwardRequestSigner(request); - return (request.deadline >= block.timestamp, signer == request.from, signer); + ) internal view virtual returns (bool isTrustedForwarder, bool active, bool signerMatch, address signer) { + (bool isValid, address recovered) = _recoverForwardRequestSigner(request); + + return ( + _isTrustedByTarget(request.to), + request.deadline >= block.timestamp, + isValid && recovered == request.from, + recovered + ); } /** - * @dev Recovers the signer of an EIP712 message hash for a forward `request` and its corresponding `signature`. - * See {ECDSA-recover}. + * @dev Returns a tuple with the recovered the signer of an EIP712 forward request message hash + * and a boolean indicating if the signature is valid. + * + * NOTE: The signature is considered valid if {ECDSA-tryRecover} indicates no recover error for it. */ - function _recoverForwardRequestSigner(ForwardRequestData calldata request) internal view virtual returns (address) { - return - _hashTypedDataV4( - keccak256( - abi.encode( - _FORWARD_REQUEST_TYPEHASH, - request.from, - request.to, - request.value, - request.gas, - nonces(request.from), - request.deadline, - keccak256(request.data) - ) + function _recoverForwardRequestSigner( + ForwardRequestData calldata request + ) internal view virtual returns (bool, address) { + (address recovered, ECDSA.RecoverError err, ) = _hashTypedDataV4( + keccak256( + abi.encode( + _FORWARD_REQUEST_TYPEHASH, + request.from, + request.to, + request.value, + request.gas, + nonces(request.from), + request.deadline, + keccak256(request.data) ) - ).recover(request.signature); + ) + ).tryRecover(request.signature); + + return (err == ECDSA.RecoverError.NoError, recovered); } /** @@ -232,12 +259,16 @@ contract ERC2771Forwarder is EIP712, Nonces { ForwardRequestData calldata request, bool requireValidRequest ) internal virtual returns (bool success) { - (bool alive, bool signerMatch, address signer) = _validate(request); + (bool isTrustedForwarder, bool active, bool signerMatch, address signer) = _validate(request); // Need to explicitly specify if a revert is required since non-reverting is default for // batches and reversion is opt-in since it could be useful in some scenarios if (requireValidRequest) { - if (!alive) { + if (!isTrustedForwarder) { + revert ERC2771UntrustfulTarget(request.to, address(this)); + } + + if (!active) { revert ERC2771ForwarderExpiredRequest(request.deadline); } @@ -247,7 +278,7 @@ contract ERC2771Forwarder is EIP712, Nonces { } // Ignore an invalid request because requireValidRequest = false - if (signerMatch && alive) { + if (isTrustedForwarder && signerMatch && active) { // Nonce should be used before the call to prevent reusing by reentrancy uint256 currentNonce = _useNonce(signer); @@ -269,6 +300,33 @@ contract ERC2771Forwarder is EIP712, Nonces { } } + /** + * @dev Returns whether the target trusts this forwarder. + * + * This function performs a static call to the target contract calling the + * {ERC2771Context-isTrustedForwarder} function. + */ + function _isTrustedByTarget(address target) private view returns (bool) { + bytes memory encodedParams = abi.encodeCall(ERC2771Context.isTrustedForwarder, (address(this))); + + bool success; + uint256 returnSize; + uint256 returnValue; + /// @solidity memory-safe-assembly + assembly { + // Perform the staticcal and save the result in the scratch space. + // | Location | Content | Content (Hex) | + // |-----------|----------|--------------------------------------------------------------------| + // | | | result ↓ | + // | 0x00:0x1F | selector | 0x0000000000000000000000000000000000000000000000000000000000000001 | + success := staticcall(gas(), target, add(encodedParams, 0x20), mload(encodedParams), 0, 0x20) + returnSize := returndatasize() + returnValue := mload(0) + } + + return success && returnSize >= 0x20 && returnValue > 0; + } + /** * @dev Checks if the requested gas was correctly forwarded to the callee. * diff --git a/contracts/mocks/AddressFnPointersMock.sol b/contracts/mocks/AddressFnPointersMock.sol deleted file mode 100644 index dc4edc075..000000000 --- a/contracts/mocks/AddressFnPointersMock.sol +++ /dev/null @@ -1,50 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.20; - -import {Address} from "../utils/Address.sol"; - -/** - * @dev A mock to expose `Address`'s functions with function pointers. - */ -contract AddressFnPointerMock { - error CustomRevert(); - - function functionCall(address target, bytes memory data) external returns (bytes memory) { - return Address.functionCall(target, data, _customRevert); - } - - function functionCallWithValue(address target, bytes memory data, uint256 value) external returns (bytes memory) { - return Address.functionCallWithValue(target, data, value, _customRevert); - } - - function functionStaticCall(address target, bytes memory data) external view returns (bytes memory) { - return Address.functionStaticCall(target, data, _customRevert); - } - - function functionDelegateCall(address target, bytes memory data) external returns (bytes memory) { - return Address.functionDelegateCall(target, data, _customRevert); - } - - function verifyCallResultFromTarget( - address target, - bool success, - bytes memory returndata - ) external view returns (bytes memory) { - return Address.verifyCallResultFromTarget(target, success, returndata, _customRevert); - } - - function verifyCallResult(bool success, bytes memory returndata) external view returns (bytes memory) { - return Address.verifyCallResult(success, returndata, _customRevert); - } - - function verifyCallResultVoid(bool success, bytes memory returndata) external view returns (bytes memory) { - return Address.verifyCallResult(success, returndata, _customRevertVoid); - } - - function _customRevert() internal pure { - revert CustomRevert(); - } - - function _customRevertVoid() internal pure {} -} diff --git a/contracts/mocks/CallReceiverMock.sol b/contracts/mocks/CallReceiverMock.sol index 281afacfe..e371c7db8 100644 --- a/contracts/mocks/CallReceiverMock.sol +++ b/contracts/mocks/CallReceiverMock.sol @@ -59,3 +59,15 @@ contract CallReceiverMock { return "0x1234"; } } + +contract CallReceiverMockTrustingForwarder is CallReceiverMock { + address private _trustedForwarder; + + constructor(address trustedForwarder_) { + _trustedForwarder = trustedForwarder_; + } + + function isTrustedForwarder(address forwarder) public view virtual returns (bool) { + return forwarder == _trustedForwarder; + } +} diff --git a/contracts/mocks/UpgradeableBeaconMock.sol b/contracts/mocks/UpgradeableBeaconMock.sol new file mode 100644 index 000000000..354ac02f0 --- /dev/null +++ b/contracts/mocks/UpgradeableBeaconMock.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {IBeacon} from "../proxy/beacon/IBeacon.sol"; + +contract UpgradeableBeaconMock is IBeacon { + address public implementation; + + constructor(address impl) { + implementation = impl; + } +} + +interface IProxyExposed { + // solhint-disable-next-line func-name-mixedcase + function $getBeacon() external view returns (address); +} + +contract UpgradeableBeaconReentrantMock is IBeacon { + error BeaconProxyBeaconSlotAddress(address beacon); + + function implementation() external view override returns (address) { + // Revert with the beacon seen in the proxy at the moment of calling to check if it's + // set before the call. + revert BeaconProxyBeaconSlotAddress(IProxyExposed(msg.sender).$getBeacon()); + } +} diff --git a/contracts/mocks/UpgreadeableBeaconMock.sol b/contracts/mocks/UpgreadeableBeaconMock.sol deleted file mode 100644 index 4bee5c0f2..000000000 --- a/contracts/mocks/UpgreadeableBeaconMock.sol +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import {IBeacon} from "../proxy/beacon/IBeacon.sol"; - -contract UpgradeableBeaconMock is IBeacon { - address public implementation; - - constructor(address impl) { - implementation = impl; - } -} diff --git a/contracts/proxy/ERC1967/ERC1967Proxy.sol b/contracts/proxy/ERC1967/ERC1967Proxy.sol index d4104ccfb..df3a61369 100644 --- a/contracts/proxy/ERC1967/ERC1967Proxy.sol +++ b/contracts/proxy/ERC1967/ERC1967Proxy.sol @@ -14,17 +14,17 @@ import {ERC1967Utils} from "./ERC1967Utils.sol"; */ contract ERC1967Proxy is Proxy { /** - * @dev Initializes the upgradeable proxy with an initial implementation specified by `_logic`. + * @dev Initializes the upgradeable proxy with an initial implementation specified by `implementation`. * - * If `_data` is nonempty, it's used as data in a delegate call to `_logic`. This will typically be an encoded + * If `_data` is nonempty, it's used as data in a delegate call to `implementation`. This will typically be an encoded * function call, and allows initializing the storage of the proxy like a Solidity constructor. * * Requirements: * * - If `data` is empty, `msg.value` must be zero. */ - constructor(address _logic, bytes memory _data) payable { - ERC1967Utils.upgradeToAndCall(_logic, _data); + constructor(address implementation, bytes memory _data) payable { + ERC1967Utils.upgradeToAndCall(implementation, _data); } /** @@ -34,7 +34,7 @@ contract ERC1967Proxy is Proxy { * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call. * `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc` */ - function _implementation() internal view virtual override returns (address impl) { + function _implementation() internal view virtual override returns (address) { return ERC1967Utils.getImplementation(); } } diff --git a/contracts/proxy/ERC1967/ERC1967Utils.sol b/contracts/proxy/ERC1967/ERC1967Utils.sol index bd2108096..b6f705e54 100644 --- a/contracts/proxy/ERC1967/ERC1967Utils.sol +++ b/contracts/proxy/ERC1967/ERC1967Utils.sol @@ -31,8 +31,7 @@ library ERC1967Utils { /** * @dev Storage slot with the address of the current implementation. - * This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1, and is - * validated in the constructor. + * This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1. */ // solhint-disable-next-line private-vars-leading-underscore bytes32 internal constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; @@ -94,8 +93,7 @@ library ERC1967Utils { /** * @dev Storage slot with the admin of the contract. - * This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1, and is - * validated in the constructor. + * This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1. */ // solhint-disable-next-line private-vars-leading-underscore bytes32 internal constant ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; @@ -133,7 +131,7 @@ library ERC1967Utils { /** * @dev The storage slot of the UpgradeableBeacon contract which defines the implementation for this proxy. - * This is bytes32(uint256(keccak256('eip1967.proxy.beacon')) - 1) and is validated in the constructor. + * This is the keccak-256 hash of "eip1967.proxy.beacon" subtracted by 1. */ // solhint-disable-next-line private-vars-leading-underscore bytes32 internal constant BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50; @@ -153,12 +151,12 @@ library ERC1967Utils { revert ERC1967InvalidBeacon(newBeacon); } + StorageSlot.getAddressSlot(BEACON_SLOT).value = newBeacon; + address beaconImplementation = IBeacon(newBeacon).implementation(); if (beaconImplementation.code.length == 0) { revert ERC1967InvalidImplementation(beaconImplementation); } - - StorageSlot.getAddressSlot(BEACON_SLOT).value = newBeacon; } /** @@ -184,7 +182,8 @@ library ERC1967Utils { } /** - * @dev Reverts if `msg.value` is not zero. + * @dev Reverts if `msg.value` is not zero. It can be used to avoid `msg.value` stuck in the contract + * if an upgrade doesn't perform an initialization call. */ function _checkNonPayable() private { if (msg.value > 0) { diff --git a/contracts/proxy/Proxy.sol b/contracts/proxy/Proxy.sol index 3644197fa..005a876ac 100644 --- a/contracts/proxy/Proxy.sol +++ b/contracts/proxy/Proxy.sol @@ -56,7 +56,6 @@ abstract contract Proxy { * This function does not return to its internal call site, it will return directly to the external caller. */ function _fallback() internal virtual { - _beforeFallback(); _delegate(_implementation()); } @@ -67,12 +66,4 @@ abstract contract Proxy { fallback() external payable virtual { _fallback(); } - - /** - * @dev Hook that is called before falling back to the implementation. Can happen as part of a manual `_fallback` - * call, or as part of the Solidity `fallback` or `receive` functions. - * - * If overridden should call `super._beforeFallback()`. - */ - function _beforeFallback() internal virtual {} } diff --git a/contracts/proxy/beacon/IBeacon.sol b/contracts/proxy/beacon/IBeacon.sol index f5e9f7981..56477c92a 100644 --- a/contracts/proxy/beacon/IBeacon.sol +++ b/contracts/proxy/beacon/IBeacon.sol @@ -10,7 +10,7 @@ interface IBeacon { /** * @dev Must return an address that can be used as a delegate call target. * - * {BeaconProxy} will check that this address is a contract. + * {UpgradeableBeacon} will check that this address is a contract. */ function implementation() external view returns (address); } diff --git a/contracts/proxy/beacon/UpgradeableBeacon.sol b/contracts/proxy/beacon/UpgradeableBeacon.sol index 81ce50902..a7816a1e6 100644 --- a/contracts/proxy/beacon/UpgradeableBeacon.sol +++ b/contracts/proxy/beacon/UpgradeableBeacon.sol @@ -51,7 +51,6 @@ contract UpgradeableBeacon is IBeacon, Ownable { */ function upgradeTo(address newImplementation) public virtual onlyOwner { _setImplementation(newImplementation); - emit Upgraded(newImplementation); } /** @@ -66,5 +65,6 @@ contract UpgradeableBeacon is IBeacon, Ownable { revert BeaconInvalidImplementation(newImplementation); } _implementation = newImplementation; + emit Upgraded(newImplementation); } } diff --git a/contracts/proxy/transparent/ProxyAdmin.sol b/contracts/proxy/transparent/ProxyAdmin.sol index c0e6780f4..cd03fb939 100644 --- a/contracts/proxy/transparent/ProxyAdmin.sol +++ b/contracts/proxy/transparent/ProxyAdmin.sol @@ -16,7 +16,7 @@ contract ProxyAdmin is Ownable { * and `upgradeAndCall(address,bytes)` are present, and `upgradeTo` must be used if no function should be called, * while `upgradeAndCall` will invoke the `receive` function if the second argument is the empty byte string. * If the getter returns `"5.0.0"`, only `upgradeAndCall(address,bytes)` is present, and the second argument must - * be the empty byte string if no function should be called, being impossible to invoke the `receive` function + * be the empty byte string if no function should be called, making it impossible to invoke the `receive` function * during an upgrade. */ string public constant UPGRADE_INTERFACE_VERSION = "5.0.0"; diff --git a/contracts/proxy/transparent/TransparentUpgradeableProxy.sol b/contracts/proxy/transparent/TransparentUpgradeableProxy.sol index b51d2973e..77ed5fe73 100644 --- a/contracts/proxy/transparent/TransparentUpgradeableProxy.sol +++ b/contracts/proxy/transparent/TransparentUpgradeableProxy.sol @@ -45,6 +45,9 @@ interface ITransparentUpgradeableProxy is IERC1967 { * implement transparency without decoding reverts caused by selector clashes between the proxy and the * implementation. * + * NOTE: This proxy does not inherit from {Context} deliberately. The {ProxyAdmin} of this contract won't send a + * meta-transaction in any way, and any other meta-transaction setup should be made in the implementation contract. + * * IMPORTANT: This contract avoids unnecessary storage reads by setting the admin only during construction as an immutable variable, * preventing any changes thereafter. However, the admin slot defined in ERC-1967 can still be overwritten by the implementation * logic pointed to by this proxy. In such cases, the contract may end up in an undesirable state where the admin slot is different @@ -75,18 +78,25 @@ contract TransparentUpgradeableProxy is ERC1967Proxy { constructor(address _logic, address initialOwner, bytes memory _data) payable ERC1967Proxy(_logic, _data) { _admin = address(new ProxyAdmin(initialOwner)); // Set the storage value and emit an event for ERC-1967 compatibility - ERC1967Utils.changeAdmin(_admin); + ERC1967Utils.changeAdmin(_proxyAdmin()); + } + + /** + * @dev Returns the admin of this proxy. + */ + function _proxyAdmin() internal virtual returns (address) { + return _admin; } /** * @dev If caller is the admin process the call internally, otherwise transparently fallback to the proxy behavior. */ function _fallback() internal virtual override { - if (msg.sender == _admin) { - if (msg.sig == ITransparentUpgradeableProxy.upgradeToAndCall.selector) { - _dispatchUpgradeToAndCall(); - } else { + if (msg.sender == _proxyAdmin()) { + if (msg.sig != ITransparentUpgradeableProxy.upgradeToAndCall.selector) { revert ProxyDeniedAdminAccess(); + } else { + _dispatchUpgradeToAndCall(); } } else { super._fallback(); diff --git a/contracts/proxy/utils/UUPSUpgradeable.sol b/contracts/proxy/utils/UUPSUpgradeable.sol index 7ad0c9abc..f08e61c1e 100644 --- a/contracts/proxy/utils/UUPSUpgradeable.sol +++ b/contracts/proxy/utils/UUPSUpgradeable.sol @@ -25,7 +25,7 @@ abstract contract UUPSUpgradeable is IERC1822Proxiable { * and `upgradeToAndCall(address,bytes)` are present, and `upgradeTo` must be used if no function should be called, * while `upgradeToAndCall` will invoke the `receive` function if the second argument is the empty byte string. * If the getter returns `"5.0.0"`, only `upgradeToAndCall(address,bytes)` is present, and the second argument must - * be the empty byte string if no function should be called, being impossible to invoke the `receive` function + * be the empty byte string if no function should be called, making it impossible to invoke the `receive` function * during an upgrade. */ string public constant UPGRADE_INTERFACE_VERSION = "5.0.0"; @@ -126,7 +126,10 @@ abstract contract UUPSUpgradeable is IERC1822Proxiable { function _authorizeUpgrade(address newImplementation) internal virtual; /** - * @dev Perform implementation upgrade with security checks for UUPS proxies, and additional setup call. + * @dev Performs an implementation upgrade with a security check for UUPS proxies, and additional setup call. + * + * As a security check, {proxiableUUID} is invoked in the new implementation, and the return value + * is expected to be the implementation slot in ERC1967. * * Emits an {IERC1967-Upgraded} event. */ diff --git a/contracts/utils/Address.sol b/contracts/utils/Address.sol index e3a71313c..fd22b05ab 100644 --- a/contracts/utils/Address.sol +++ b/contracts/utils/Address.sol @@ -54,8 +54,10 @@ library Address { * plain `call` is an unsafe replacement for a function call: use this * function instead. * - * If `target` reverts with a revert reason, it is bubbled up by this - * function (like regular Solidity function calls). + * If `target` reverts with a revert reason or custom error, it is bubbled + * up by this function (like regular Solidity function calls). However, if + * the call reverted with no returned reason, this function reverts with a + * {FailedInnerCall} error. * * Returns the raw returned data. To convert to the expected return value, * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. @@ -66,23 +68,7 @@ library Address { * - calling `target` with `data` must not revert. */ function functionCall(address target, bytes memory data) internal returns (bytes memory) { - return functionCallWithValue(target, data, 0, defaultRevert); - } - - /** - * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with a - * `customRevert` function as a fallback when `target` reverts. - * - * Requirements: - * - * - `customRevert` must be a reverting function. - */ - function functionCall( - address target, - bytes memory data, - function() internal view customRevert - ) internal returns (bytes memory) { - return functionCallWithValue(target, data, 0, customRevert); + return functionCallWithValue(target, data, 0); } /** @@ -95,28 +81,11 @@ library Address { * - the called Solidity function must be `payable`. */ function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) { - return functionCallWithValue(target, data, value, defaultRevert); - } - - /** - * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but - * with a `customRevert` function as a fallback revert reason when `target` reverts. - * - * Requirements: - * - * - `customRevert` must be a reverting function. - */ - function functionCallWithValue( - address target, - bytes memory data, - uint256 value, - function() internal view customRevert - ) internal returns (bytes memory) { if (address(this).balance < value) { revert AddressInsufficientBalance(address(this)); } (bool success, bytes memory returndata) = target.call{value: value}(data); - return verifyCallResultFromTarget(target, success, returndata, customRevert); + return verifyCallResultFromTarget(target, success, returndata); } /** @@ -124,20 +93,8 @@ library Address { * but performing a static call. */ function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { - return functionStaticCall(target, data, defaultRevert); - } - - /** - * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], - * but performing a static call. - */ - function functionStaticCall( - address target, - bytes memory data, - function() internal view customRevert - ) internal view returns (bytes memory) { (bool success, bytes memory returndata) = target.staticcall(data); - return verifyCallResultFromTarget(target, success, returndata, customRevert); + return verifyCallResultFromTarget(target, success, returndata); } /** @@ -145,82 +102,48 @@ library Address { * but performing a delegate call. */ function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { - return functionDelegateCall(target, data, defaultRevert); - } - - /** - * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], - * but performing a delegate call. - */ - function functionDelegateCall( - address target, - bytes memory data, - function() internal view customRevert - ) internal returns (bytes memory) { (bool success, bytes memory returndata) = target.delegatecall(data); - return verifyCallResultFromTarget(target, success, returndata, customRevert); + return verifyCallResultFromTarget(target, success, returndata); } /** - * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling - * the revert reason or using the provided `customRevert`) in case of unsuccessful call or if target was not a contract. + * @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target + * was not a contract or bubbling up the revert reason (falling back to {FailedInnerCall}) in case of an + * unsuccessful call. */ function verifyCallResultFromTarget( address target, bool success, - bytes memory returndata, - function() internal view customRevert + bytes memory returndata ) internal view returns (bytes memory) { - if (success) { - if (returndata.length == 0) { - // only check if target is a contract if the call was successful and the return data is empty - // otherwise we already know that it was a contract - if (target.code.length == 0) { - revert AddressEmptyCode(target); - } + if (!success) { + _revert(returndata); + } else { + // only check if target is a contract if the call was successful and the return data is empty + // otherwise we already know that it was a contract + if (returndata.length == 0 && target.code.length == 0) { + revert AddressEmptyCode(target); } return returndata; - } else { - _revert(returndata, customRevert); } } /** - * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the - * revert reason or with a default revert error. + * @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the + * revert reason or with a default {FailedInnerCall} error. */ - function verifyCallResult(bool success, bytes memory returndata) internal view returns (bytes memory) { - return verifyCallResult(success, returndata, defaultRevert); - } - - /** - * @dev Same as {xref-Address-verifyCallResult-bool-bytes-}[`verifyCallResult`], but with a - * `customRevert` function as a fallback when `success` is `false`. - * - * Requirements: - * - * - `customRevert` must be a reverting function. - */ - function verifyCallResult( - bool success, - bytes memory returndata, - function() internal view customRevert - ) internal view returns (bytes memory) { - if (success) { - return returndata; + function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) { + if (!success) { + _revert(returndata); } else { - _revert(returndata, customRevert); + return returndata; } } /** - * @dev Default reverting function when no `customRevert` is provided in a function call. + * @dev Reverts with returndata if present. Otherwise reverts with {FailedInnerCall}. */ - function defaultRevert() internal pure { - revert FailedInnerCall(); - } - - function _revert(bytes memory returndata, function() internal view customRevert) private view { + function _revert(bytes memory returndata) private pure { // Look for revert reason and bubble it up if present if (returndata.length > 0) { // The easiest way to bubble the revert reason is using memory via assembly @@ -230,7 +153,6 @@ library Address { revert(add(32, returndata), returndata_size) } } else { - customRevert(); revert FailedInnerCall(); } } diff --git a/contracts/utils/Strings.sol b/contracts/utils/Strings.sol index 0037eee1b..24c22d53d 100644 --- a/contracts/utils/Strings.sol +++ b/contracts/utils/Strings.sol @@ -10,7 +10,7 @@ import {SignedMath} from "./math/SignedMath.sol"; * @dev String operations. */ library Strings { - bytes16 private constant _SYMBOLS = "0123456789abcdef"; + bytes16 private constant _HEX_DIGITS = "0123456789abcdef"; uint8 private constant _ADDRESS_LENGTH = 20; /** @@ -34,7 +34,7 @@ library Strings { ptr--; /// @solidity memory-safe-assembly assembly { - mstore8(ptr, byte(mod(value, 10), _SYMBOLS)) + mstore8(ptr, byte(mod(value, 10), _HEX_DIGITS)) } value /= 10; if (value == 0) break; @@ -68,7 +68,7 @@ library Strings { buffer[0] = "0"; buffer[1] = "x"; for (uint256 i = 2 * length + 1; i > 1; --i) { - buffer[i] = _SYMBOLS[localValue & 0xf]; + buffer[i] = _HEX_DIGITS[localValue & 0xf]; localValue >>= 4; } if (localValue != 0) { diff --git a/test/metatx/ERC2771Context.test.js b/test/metatx/ERC2771Context.test.js index 0ec8d98dd..522b05cd1 100644 --- a/test/metatx/ERC2771Context.test.js +++ b/test/metatx/ERC2771Context.test.js @@ -36,7 +36,11 @@ contract('ERC2771Context', function (accounts) { }); it('recognize trusted forwarder', async function () { - expect(await this.recipient.isTrustedForwarder(this.forwarder.address)); + expect(await this.recipient.isTrustedForwarder(this.forwarder.address)).to.equal(true); + }); + + it('returns the trusted forwarder', async function () { + expect(await this.recipient.trustedForwarder()).to.equal(this.forwarder.address); }); context('when called directly', function () { diff --git a/test/metatx/ERC2771Forwarder.t.sol b/test/metatx/ERC2771Forwarder.t.sol index 189ed6ac5..3256289ea 100644 --- a/test/metatx/ERC2771Forwarder.t.sol +++ b/test/metatx/ERC2771Forwarder.t.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.20; import {Test} from "forge-std/Test.sol"; import {ERC2771Forwarder} from "contracts/metatx/ERC2771Forwarder.sol"; -import {CallReceiverMock} from "contracts/mocks/CallReceiverMock.sol"; +import {CallReceiverMockTrustingForwarder, CallReceiverMock} from "contracts/mocks/CallReceiverMock.sol"; struct ForwardRequest { address from; @@ -40,7 +40,7 @@ contract ERC2771ForwarderMock is ERC2771Forwarder { contract ERC2771ForwarderTest is Test { ERC2771ForwarderMock internal _erc2771Forwarder; - CallReceiverMock internal _receiver; + CallReceiverMockTrustingForwarder internal _receiver; uint256 internal _signerPrivateKey; uint256 internal _relayerPrivateKey; @@ -52,7 +52,7 @@ contract ERC2771ForwarderTest is Test { function setUp() public { _erc2771Forwarder = new ERC2771ForwarderMock("ERC2771Forwarder"); - _receiver = new CallReceiverMock(); + _receiver = new CallReceiverMockTrustingForwarder(address(_erc2771Forwarder)); _signerPrivateKey = 0xA11CE; _relayerPrivateKey = 0xB0B; diff --git a/test/metatx/ERC2771Forwarder.test.js b/test/metatx/ERC2771Forwarder.test.js index 26726e8df..209c84b2f 100644 --- a/test/metatx/ERC2771Forwarder.test.js +++ b/test/metatx/ERC2771Forwarder.test.js @@ -7,14 +7,13 @@ const { constants, expectRevert, expectEvent, time } = require('@openzeppelin/te const { expect } = require('chai'); const ERC2771Forwarder = artifacts.require('ERC2771Forwarder'); -const CallReceiverMock = artifacts.require('CallReceiverMock'); +const CallReceiverMockTrustingForwarder = artifacts.require('CallReceiverMockTrustingForwarder'); contract('ERC2771Forwarder', function (accounts) { const [, refundReceiver, another] = accounts; const tamperedValues = { from: another, - to: another, value: web3.utils.toWei('0.5'), data: '0x1742', deadline: 0xdeadbeef, @@ -41,7 +40,7 @@ contract('ERC2771Forwarder', function (accounts) { this.alice.address = web3.utils.toChecksumAddress(this.alice.getAddressString()); this.timestamp = await time.latest(); - this.receiver = await CallReceiverMock.new(); + this.receiver = await CallReceiverMockTrustingForwarder.new(this.forwarder.address); this.request = { from: this.alice.address, to: this.receiver.address, @@ -97,6 +96,10 @@ contract('ERC2771Forwarder', function (accounts) { }); } + it('returns false with an untrustful to', async function () { + expect(await this.forwarder.verify(this.forgeData({ to: another }).message)).to.be.equal(false); + }); + it('returns false with tampered signature', async function () { const tamperedsign = web3.utils.hexToBytes(this.requestData.signature); tamperedsign[42] ^= 0xff; @@ -169,6 +172,14 @@ contract('ERC2771Forwarder', function (accounts) { }); } + it('reverts with an untrustful to', async function () { + const data = this.forgeData({ to: another }); + await expectRevertCustomError(this.forwarder.execute(data.message), 'ERC2771UntrustfulTarget', [ + data.message.to, + this.forwarder.address, + ]); + }); + it('reverts with tampered signature', async function () { const tamperedSig = web3.utils.hexToBytes(this.requestData.signature); tamperedSig[42] ^= 0xff; @@ -360,6 +371,18 @@ contract('ERC2771Forwarder', function (accounts) { }); } + it('reverts with at least one untrustful to', async function () { + const data = this.forgeData({ ...this.requestDatas[this.idx], to: another }); + + this.requestDatas[this.idx] = data.message; + + await expectRevertCustomError( + this.forwarder.executeBatch(this.requestDatas, this.refundReceiver, { value: this.msgValue }), + 'ERC2771UntrustfulTarget', + [this.requestDatas[this.idx].to, this.forwarder.address], + ); + }); + it('reverts with at least one tampered request signature', async function () { const tamperedSig = web3.utils.hexToBytes(this.requestDatas[this.idx].signature); tamperedSig[42] ^= 0xff; diff --git a/test/proxy/ERC1967/ERC1967Utils.test.js b/test/proxy/ERC1967/ERC1967Utils.test.js index cce874cd9..975b08d81 100644 --- a/test/proxy/ERC1967/ERC1967Utils.test.js +++ b/test/proxy/ERC1967/ERC1967Utils.test.js @@ -9,6 +9,7 @@ const ERC1967Utils = artifacts.require('$ERC1967Utils'); const V1 = artifacts.require('DummyImplementation'); const V2 = artifacts.require('CallReceiverMock'); const UpgradeableBeaconMock = artifacts.require('UpgradeableBeaconMock'); +const UpgradeableBeaconReentrantMock = artifacts.require('UpgradeableBeaconReentrantMock'); contract('ERC1967Utils', function (accounts) { const [, admin, anotherAccount] = accounts; @@ -155,6 +156,17 @@ contract('ERC1967Utils', function (accounts) { await expectEvent.inTransaction(receipt.tx, await V2.at(this.utils.address), 'MockFunctionCalled'); }); }); + + describe('reentrant beacon implementation() call', function () { + it('sees the new beacon implementation', async function () { + const newBeacon = await UpgradeableBeaconReentrantMock.new(); + await expectRevertCustomError( + this.utils.$upgradeBeaconToAndCall(newBeacon.address, '0x'), + 'BeaconProxyBeaconSlotAddress', + [newBeacon.address], + ); + }); + }); }); }); }); diff --git a/test/proxy/beacon/UpgradeableBeacon.test.js b/test/proxy/beacon/UpgradeableBeacon.test.js index 4c58f1740..0737f6fdf 100644 --- a/test/proxy/beacon/UpgradeableBeacon.test.js +++ b/test/proxy/beacon/UpgradeableBeacon.test.js @@ -20,6 +20,13 @@ contract('UpgradeableBeacon', function (accounts) { this.beacon = await UpgradeableBeacon.new(this.v1.address, owner); }); + it('emits Upgraded event to the first implementation', async function () { + const beacon = await UpgradeableBeacon.new(this.v1.address, owner); + await expectEvent.inTransaction(beacon.contract.transactionHash, beacon, 'Upgraded', { + implementation: this.v1.address, + }); + }); + it('returns implementation', async function () { expect(await this.beacon.implementation()).to.equal(this.v1.address); }); diff --git a/test/utils/Address.test.js b/test/utils/Address.test.js index beded18e1..57453abd5 100644 --- a/test/utils/Address.test.js +++ b/test/utils/Address.test.js @@ -3,7 +3,6 @@ const { expect } = require('chai'); const { expectRevertCustomError } = require('../helpers/customError'); const Address = artifacts.require('$Address'); -const AddressFnPointerMock = artifacts.require('$AddressFnPointerMock'); const EtherReceiver = artifacts.require('EtherReceiverMock'); const CallReceiverMock = artifacts.require('CallReceiverMock'); @@ -12,7 +11,6 @@ contract('Address', function (accounts) { beforeEach(async function () { this.mock = await Address.new(); - this.mockFnPointer = await AddressFnPointerMock.new(); }); describe('sendValue', function () { @@ -141,14 +139,6 @@ contract('Address', function (accounts) { await expectRevert.unspecified(this.mock.$functionCall(this.target.address, abiEncodedCall)); }); - it('bubbles up error if specified', async function () { - await expectRevertCustomError( - this.mockFnPointer.functionCall(this.target.address, '0x12345678'), - 'CustomRevert', - [], - ); - }); - it('reverts when function does not exist', async function () { const abiEncodedCall = web3.eth.abi.encodeFunctionCall( { @@ -254,14 +244,6 @@ contract('Address', function (accounts) { [], ); }); - - it('bubbles up error if specified', async function () { - await expectRevertCustomError( - this.mockFnPointer.functionCallWithValue(this.target.address, '0x12345678', 0), - 'CustomRevert', - [], - ); - }); }); }); @@ -305,14 +287,6 @@ contract('Address', function (accounts) { recipient, ]); }); - - it('bubbles up error if specified', async function () { - await expectRevertCustomError( - this.mockFnPointer.functionCallWithValue(this.target.address, '0x12345678', 0), - 'CustomRevert', - [], - ); - }); }); describe('functionDelegateCall', function () { @@ -355,28 +329,12 @@ contract('Address', function (accounts) { recipient, ]); }); - - it('bubbles up error if specified', async function () { - await expectRevertCustomError( - this.mockFnPointer.functionCallWithValue(this.target.address, '0x12345678', 0), - 'CustomRevert', - [], - ); - }); }); describe('verifyCallResult', function () { it('returns returndata on success', async function () { const returndata = '0x123abc'; - expect(await this.mockFnPointer.verifyCallResult(true, returndata)).to.equal(returndata); - }); - - it('reverts with return data and error', async function () { - await expectRevertCustomError(this.mockFnPointer.verifyCallResult(false, '0x'), 'CustomRevert', []); - }); - - it('reverts expecting error if provided onRevert is a non-reverting function', async function () { - await expectRevertCustomError(this.mockFnPointer.verifyCallResultVoid(false, '0x'), 'FailedInnerCall', []); + expect(await this.mock.$verifyCallResult(true, returndata)).to.equal(returndata); }); }); }); From b81bec45525db369f91a5d4e4351a89c13588db9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Fri, 4 Aug 2023 15:57:53 -0600 Subject: [PATCH 181/182] Use `Ownable` in `VestingWallet` instead of an immutable beneficiary (#4508) Co-authored-by: Francisco Giordano --- .changeset/healthy-gorillas-applaud.md | 5 ++++ contracts/finance/VestingWallet.sol | 37 ++++++++++++-------------- test/finance/VestingWallet.test.js | 2 +- test/utils/Create2.test.js | 4 ++- 4 files changed, 26 insertions(+), 22 deletions(-) create mode 100644 .changeset/healthy-gorillas-applaud.md diff --git a/.changeset/healthy-gorillas-applaud.md b/.changeset/healthy-gorillas-applaud.md new file mode 100644 index 000000000..1d4156ebf --- /dev/null +++ b/.changeset/healthy-gorillas-applaud.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': major +--- + +`VestingWallet`: Use `Ownable` instead of an immutable `beneficiary`. diff --git a/contracts/finance/VestingWallet.sol b/contracts/finance/VestingWallet.sol index 2e8fdab82..1edb0113e 100644 --- a/contracts/finance/VestingWallet.sol +++ b/contracts/finance/VestingWallet.sol @@ -6,24 +6,28 @@ import {IERC20} from "../token/ERC20/IERC20.sol"; import {SafeERC20} from "../token/ERC20/utils/SafeERC20.sol"; import {Address} from "../utils/Address.sol"; import {Context} from "../utils/Context.sol"; +import {Ownable} from "../access/Ownable.sol"; /** - * @title VestingWallet - * @dev This contract handles the vesting of Eth and ERC20 tokens for a given beneficiary. Custody of multiple tokens - * can be given to this contract, which will release the token to the beneficiary following a given vesting schedule. - * The vesting schedule is customizable through the {vestedAmount} function. + * @dev A vesting wallet is an ownable contract that can receive native currency and ERC20 tokens, and release these + * assets to the wallet owner, also referred to as "beneficiary", according to a vesting schedule. * - * Any token transferred to this contract will follow the vesting schedule as if they were locked from the beginning. + * Any assets transferred to this contract will follow the vesting schedule as if they were locked from the beginning. * Consequently, if the vesting has already started, any amount of tokens sent to this contract will (at least partly) * be immediately releasable. * * By setting the duration to 0, one can configure this contract to behave like an asset timelock that hold tokens for * a beneficiary until a specified time. * + * NOTE: Since the wallet is {Ownable}, and ownership can be transferred, it is possible to sell unvested tokens. + * Preventing this in a smart contract is difficult, considering that: 1) a beneficiary address could be a + * counterfactually deployed contract, 2) there is likely to be a migration path for EOAs to become contracts in the + * near future. + * * NOTE: When using this contract with any token whose balance is adjusted automatically (i.e. a rebase token), make sure * to account the supply/balance adjustment in the vesting schedule to ensure the vested amount is as intended. */ -contract VestingWallet is Context { +contract VestingWallet is Context, Ownable { event EtherReleased(uint256 amount); event ERC20Released(address indexed token, uint256 amount); @@ -34,18 +38,18 @@ contract VestingWallet is Context { uint256 private _released; mapping(address => uint256) private _erc20Released; - address private immutable _beneficiary; uint64 private immutable _start; uint64 private immutable _duration; /** - * @dev Set the beneficiary, start timestamp and vesting duration of the vesting wallet. + * @dev Sets the sender as the initial owner, the beneficiary as the pending owner, the start timestamp and the + * vesting duration of the vesting wallet. */ - constructor(address beneficiaryAddress, uint64 startTimestamp, uint64 durationSeconds) payable { - if (beneficiaryAddress == address(0)) { + constructor(address beneficiary, uint64 startTimestamp, uint64 durationSeconds) payable Ownable(beneficiary) { + if (beneficiary == address(0)) { revert VestingWalletInvalidBeneficiary(address(0)); } - _beneficiary = beneficiaryAddress; + _start = startTimestamp; _duration = durationSeconds; } @@ -55,13 +59,6 @@ contract VestingWallet is Context { */ receive() external payable virtual {} - /** - * @dev Getter for the beneficiary address. - */ - function beneficiary() public view virtual returns (address) { - return _beneficiary; - } - /** * @dev Getter for the start timestamp. */ @@ -121,7 +118,7 @@ contract VestingWallet is Context { uint256 amount = releasable(); _released += amount; emit EtherReleased(amount); - Address.sendValue(payable(beneficiary()), amount); + Address.sendValue(payable(owner()), amount); } /** @@ -133,7 +130,7 @@ contract VestingWallet is Context { uint256 amount = releasable(token); _erc20Released[token] += amount; emit ERC20Released(token, amount); - SafeERC20.safeTransfer(IERC20(token), beneficiary(), amount); + SafeERC20.safeTransfer(IERC20(token), owner(), amount); } /** diff --git a/test/finance/VestingWallet.test.js b/test/finance/VestingWallet.test.js index 91ca04da0..d79aea195 100644 --- a/test/finance/VestingWallet.test.js +++ b/test/finance/VestingWallet.test.js @@ -29,7 +29,7 @@ contract('VestingWallet', function (accounts) { }); it('check vesting contract', async function () { - expect(await this.mock.beneficiary()).to.be.equal(beneficiary); + expect(await this.mock.owner()).to.be.equal(beneficiary); expect(await this.mock.start()).to.be.bignumber.equal(this.start); expect(await this.mock.duration()).to.be.bignumber.equal(duration); expect(await this.mock.end()).to.be.bignumber.equal(this.start.add(duration)); diff --git a/test/utils/Create2.test.js b/test/utils/Create2.test.js index 4215e37c0..336ab1acc 100644 --- a/test/utils/Create2.test.js +++ b/test/utils/Create2.test.js @@ -59,7 +59,9 @@ contract('Create2', function (accounts) { addr: offChainComputed, }); - expect(await VestingWallet.at(offChainComputed).then(instance => instance.beneficiary())).to.be.equal(other); + const instance = await VestingWallet.at(offChainComputed); + + expect(await instance.owner()).to.be.equal(other); }); it('deploys a contract with funds deposited in the factory', async function () { From 54a235f8959ecffb1916cf5693ec9bbd695cbf71 Mon Sep 17 00:00:00 2001 From: Vladislav Volosnikov Date: Sat, 5 Aug 2023 00:12:41 +0200 Subject: [PATCH 182/182] Refactor Governor proposal struct for efficient access (#4495) Co-authored-by: Hadrien Croubois --- contracts/governance/Governor.sol | 51 +++++++++++++------------------ 1 file changed, 21 insertions(+), 30 deletions(-) diff --git a/contracts/governance/Governor.sol b/contracts/governance/Governor.sol index b9874c820..9d9e2eb59 100644 --- a/contracts/governance/Governor.sol +++ b/contracts/governance/Governor.sol @@ -40,22 +40,13 @@ abstract contract Governor is Context, ERC165, EIP712, Nonces, IGovernor, IERC72 uint32 voteDuration; bool executed; bool canceled; - } - - struct ProposalExtra { uint48 eta; } - // Each object in this should fit into a single slot so it can be cached efficiently - struct ProposalFull { - ProposalCore core; - ProposalExtra extra; - } - bytes32 private constant _ALL_PROPOSAL_STATES_BITMAP = bytes32((2 ** (uint8(type(ProposalState).max) + 1)) - 1); string private _name; - mapping(uint256 => ProposalFull) private _proposals; + mapping(uint256 => ProposalCore) private _proposals; // This queue keeps track of the governor operating on itself. Calls to functions protected by the // {onlyGovernance} modifier needs to be whitelisted in this queue. Whitelisting is set in {_beforeExecute}, @@ -144,13 +135,16 @@ abstract contract Governor is Context, ERC165, EIP712, Nonces, IGovernor, IERC72 * @dev See {IGovernor-state}. */ function state(uint256 proposalId) public view virtual override returns (ProposalState) { - // ProposalCore is just one slot. We can load it from storage to memory with a single sload and use memory - // object as a cache. This avoid duplicating expensive sloads. - ProposalCore memory core = _proposals[proposalId].core; + // ProposalCore is just one slot. We can load it from storage to stack with a single sload + ProposalCore storage proposal = _proposals[proposalId]; + bool proposalExecuted = proposal.executed; + bool proposalCanceled = proposal.canceled; - if (core.executed) { + if (proposalExecuted) { return ProposalState.Executed; - } else if (core.canceled) { + } + + if (proposalCanceled) { return ProposalState.Canceled; } @@ -190,28 +184,28 @@ abstract contract Governor is Context, ERC165, EIP712, Nonces, IGovernor, IERC72 * @dev See {IGovernor-proposalSnapshot}. */ function proposalSnapshot(uint256 proposalId) public view virtual override returns (uint256) { - return _proposals[proposalId].core.voteStart; + return _proposals[proposalId].voteStart; } /** * @dev See {IGovernor-proposalDeadline}. */ function proposalDeadline(uint256 proposalId) public view virtual override returns (uint256) { - return _proposals[proposalId].core.voteStart + _proposals[proposalId].core.voteDuration; + return _proposals[proposalId].voteStart + _proposals[proposalId].voteDuration; } /** * @dev See {IGovernor-proposalProposer}. */ function proposalProposer(uint256 proposalId) public view virtual override returns (address) { - return _proposals[proposalId].core.proposer; + return _proposals[proposalId].proposer; } /** * @dev See {IGovernor-proposalEta}. */ function proposalEta(uint256 proposalId) public view virtual override returns (uint256) { - return _proposals[proposalId].extra.eta; + return _proposals[proposalId].eta; } /** @@ -311,20 +305,17 @@ abstract contract Governor is Context, ERC165, EIP712, Nonces, IGovernor, IERC72 if (targets.length != values.length || targets.length != calldatas.length || targets.length == 0) { revert GovernorInvalidProposalLength(targets.length, calldatas.length, values.length); } - if (_proposals[proposalId].core.voteStart != 0) { + if (_proposals[proposalId].voteStart != 0) { revert GovernorUnexpectedProposalState(proposalId, state(proposalId), bytes32(0)); } uint256 snapshot = clock() + votingDelay(); uint256 duration = votingPeriod(); - _proposals[proposalId].core = ProposalCore({ - proposer: proposer, - voteStart: SafeCast.toUint48(snapshot), - voteDuration: SafeCast.toUint32(duration), - executed: false, - canceled: false - }); + ProposalCore storage proposal = _proposals[proposalId]; + proposal.proposer = proposer; + proposal.voteStart = SafeCast.toUint48(snapshot); + proposal.voteDuration = SafeCast.toUint32(duration); emit ProposalCreated( proposalId, @@ -357,7 +348,7 @@ abstract contract Governor is Context, ERC165, EIP712, Nonces, IGovernor, IERC72 uint48 eta = _queueOperations(proposalId, targets, values, calldatas, descriptionHash); if (eta != 0) { - _proposals[proposalId].extra.eta = eta; + _proposals[proposalId].eta = eta; emit ProposalQueued(proposalId, eta); } else { revert GovernorQueueNotImplemented(); @@ -406,7 +397,7 @@ abstract contract Governor is Context, ERC165, EIP712, Nonces, IGovernor, IERC72 ); // mark as executed before calls to avoid reentrancy - _proposals[proposalId].core.executed = true; + _proposals[proposalId].executed = true; // before execute: register governance call in queue. if (_executor() != address(this)) { @@ -494,7 +485,7 @@ abstract contract Governor is Context, ERC165, EIP712, Nonces, IGovernor, IERC72 _encodeStateBitmap(ProposalState.Executed) ); - _proposals[proposalId].core.canceled = true; + _proposals[proposalId].canceled = true; emit ProposalCanceled(proposalId); return proposalId;