commit dbe796d5423d732efe6d062629dbc6c120d83650 Author: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue Sep 19 19:19:10 2023 +0000 Update docs 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/.changeset/angry-ties-switch.md b/.changeset/angry-ties-switch.md new file mode 100644 index 000000000..f3ec7db38 --- /dev/null +++ b/.changeset/angry-ties-switch.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': major +--- + +`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)) 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/.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/.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/.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/bright-tomatoes-sing.md b/.changeset/bright-tomatoes-sing.md new file mode 100644 index 000000000..7ef6d929a --- /dev/null +++ b/.changeset/bright-tomatoes-sing.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': major +--- + +`ERC20`, `ERC721`, `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), [#4377](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4377)) 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/.changeset/clever-bats-kick.md b/.changeset/clever-bats-kick.md new file mode 100644 index 000000000..b35301b73 --- /dev/null +++ b/.changeset/clever-bats-kick.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': patch +--- + +`Ownable`: Prevent using address(0) as the initial owner. 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/.changeset/config.json b/.changeset/config.json new file mode 100644 index 000000000..66794faef --- /dev/null +++ b/.changeset/config.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://unpkg.com/@changesets/config@2.3.0/schema.json", + "changelog": [ + "@changesets/changelog-github", + { + "repo": "OpenZeppelin/openzeppelin-contracts" + } + ], + "commit": false, + "access": "public", + "baseBranch": "master" +} 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/.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/.changeset/eighty-lemons-shake.md b/.changeset/eighty-lemons-shake.md new file mode 100644 index 000000000..4e53893f5 --- /dev/null +++ b/.changeset/eighty-lemons-shake.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': major +--- + +`ERC721`: `_approve` no longer allows approving the owner of the tokenId. `_setApprovalForAll` no longer allows setting address(0) as an operator. diff --git a/.changeset/empty-cheetahs-hunt.md b/.changeset/empty-cheetahs-hunt.md new file mode 100644 index 000000000..eb20381a6 --- /dev/null +++ b/.changeset/empty-cheetahs-hunt.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': major +--- + +`ERC721URIStorage`: Allow setting the token URI prior to minting. 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/fair-humans-peel.md b/.changeset/fair-humans-peel.md new file mode 100644 index 000000000..3c0dc3c06 --- /dev/null +++ b/.changeset/fair-humans-peel.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': major +--- + +`ERC721URIStorage`, `ERC721Royalty`: Stop resetting token-specific URI and royalties when burning. 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/flat-bottles-wonder.md b/.changeset/flat-bottles-wonder.md new file mode 100644 index 000000000..f7ee7dd5d --- /dev/null +++ b/.changeset/flat-bottles-wonder.md @@ -0,0 +1,7 @@ +--- +'openzeppelin-solidity': minor +--- + +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/.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/.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/.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/.changeset/green-pumpkins-end.md b/.changeset/green-pumpkins-end.md new file mode 100644 index 000000000..03cfe023f --- /dev/null +++ b/.changeset/green-pumpkins-end.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': major +--- + +`SafeERC20`: Removed `safePermit` in favor of documentation-only `permit` recommendations. 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/.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/.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/.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/.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/.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/.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/.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/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/.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/.changeset/large-humans-remain.md b/.changeset/large-humans-remain.md new file mode 100644 index 000000000..95b72aea4 --- /dev/null +++ b/.changeset/large-humans-remain.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': patch +--- + +`MerkleProof`: Use custom error to report invalid multiproof instead of reverting with overflow panic. diff --git a/.changeset/lazy-rice-joke.md b/.changeset/lazy-rice-joke.md new file mode 100644 index 000000000..6e1243002 --- /dev/null +++ b/.changeset/lazy-rice-joke.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': minor +--- + +`Initializable`: Use intermediate variables to improve readability. 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/.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/.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/.changeset/mean-walls-watch.md b/.changeset/mean-walls-watch.md new file mode 100644 index 000000000..6bcf609b8 --- /dev/null +++ b/.changeset/mean-walls-watch.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': major +--- + +`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)) 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/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/.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/.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/.changeset/pre.json b/.changeset/pre.json new file mode 100644 index 000000000..66e5164bf --- /dev/null +++ b/.changeset/pre.json @@ -0,0 +1,85 @@ +{ + "mode": "pre", + "tag": "rc", + "initialVersions": { + "openzeppelin-solidity": "4.9.2" + }, + "changesets": [ + "afraid-walls-smell", + "angry-ties-switch", + "big-plums-cover", + "blue-horses-do", + "blue-scissors-design", + "brave-lobsters-punch", + "bright-tomatoes-sing", + "chilled-spiders-attack", + "clever-bats-kick", + "clever-pumas-beg", + "eight-peaches-guess", + "eighty-crabs-listen", + "eighty-lemons-shake", + "empty-cheetahs-hunt", + "empty-taxis-kiss", + "fair-humans-peel", + "fifty-owls-retire", + "flat-bottles-wonder", + "fluffy-countries-buy", + "four-adults-knock", + "fresh-birds-kiss", + "green-pumpkins-end", + "grumpy-bulldogs-call", + "grumpy-worms-tease", + "happy-falcons-walk", + "healthy-gorillas-applaud", + "heavy-drinks-fail", + "hip-beds-provide", + "hip-goats-fail", + "hot-coins-judge", + "hot-dingos-kiss", + "hot-plums-approve", + "large-humans-remain", + "lazy-rice-joke", + "little-falcons-build", + "loud-shrimps-play", + "lovely-geckos-hide", + "mean-walls-watch", + "mighty-donuts-smile", + "orange-apes-draw", + "pink-suns-mix", + "popular-deers-raise", + "proud-seals-complain", + "proud-spiders-attend", + "purple-cats-cheer", + "quiet-trainers-kick", + "red-dots-fold", + "rotten-insects-wash", + "serious-books-lie", + "short-eels-enjoy", + "silly-bees-beam", + "six-frogs-turn", + "sixty-numbers-reply", + "slimy-penguins-attack", + "smooth-books-wink", + "smooth-cougars-jump", + "spicy-sheep-eat", + "spotty-hotels-type", + "strong-poems-thank", + "swift-bags-divide", + "swift-numbers-cry", + "tasty-tomatoes-turn", + "tender-shirts-turn", + "thick-pumpkins-exercise", + "thin-camels-matter", + "tough-drinks-hammer", + "two-wasps-punch", + "unlucky-beans-obey", + "violet-dancers-cough", + "violet-melons-press", + "warm-guests-rule", + "wet-bears-heal", + "wild-beds-visit", + "wild-peas-remain", + "wild-rockets-rush", + "wild-windows-trade" + ] +} 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/.changeset/proud-spiders-attend.md b/.changeset/proud-spiders-attend.md new file mode 100644 index 000000000..a8f7694c7 --- /dev/null +++ b/.changeset/proud-spiders-attend.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': major +--- + +`ERC721`: Renamed `_requireMinted` to `_requireOwned` and added a return value with the current owner. Implemented `ownerOf` in terms of `_requireOwned`. 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/.changeset/quiet-trainers-kick.md b/.changeset/quiet-trainers-kick.md new file mode 100644 index 000000000..5de96467d --- /dev/null +++ b/.changeset/quiet-trainers-kick.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': minor +--- + +`AccessManager`: Added a new contract for managing access control of complex systems in a consolidated location. diff --git a/.changeset/red-dots-fold.md b/.changeset/red-dots-fold.md new file mode 100644 index 000000000..08cc77843 --- /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` and `ERC721.totalSupply` in `ERC721Enumerable`, `ERC20.totalSupply` in `ERC20FlashMint`, and `ERC1967._getImplementation` in `ERC1967Proxy`. diff --git a/.changeset/rotten-insects-wash.md b/.changeset/rotten-insects-wash.md new file mode 100644 index 000000000..9b2f11706 --- /dev/null +++ b/.changeset/rotten-insects-wash.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': major +--- + +`ProxyAdmin`: Removed `getProxyAdmin` and `getProxyImplementation` getters. ([#3820](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3820)) 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/.changeset/short-eels-enjoy.md b/.changeset/short-eels-enjoy.md new file mode 100644 index 000000000..e826c6d19 --- /dev/null +++ b/.changeset/short-eels-enjoy.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': major +--- + +Bump minimum compiler version required to 0.8.20 diff --git a/.changeset/silly-bees-beam.md b/.changeset/silly-bees-beam.md new file mode 100644 index 000000000..0f4f40507 --- /dev/null +++ b/.changeset/silly-bees-beam.md @@ -0,0 +1,7 @@ +--- +'openzeppelin-solidity': major +--- + +`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/.changeset/six-frogs-turn.md b/.changeset/six-frogs-turn.md new file mode 100644 index 000000000..9c5668b6d --- /dev/null +++ b/.changeset/six-frogs-turn.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': major +--- + +`ERC20`: Remove the non-standard `increaseAllowance` and `decreaseAllowance` functions. 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/.changeset/slimy-penguins-attack.md b/.changeset/slimy-penguins-attack.md new file mode 100644 index 000000000..dcf91e90b --- /dev/null +++ b/.changeset/slimy-penguins-attack.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': major +--- + +`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)) 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/.changeset/smooth-cougars-jump.md b/.changeset/smooth-cougars-jump.md new file mode 100644 index 000000000..337101cd0 --- /dev/null +++ b/.changeset/smooth-cougars-jump.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': major +--- + +`ReentrancyGuard`, `Pausable`: Moved to `utils` directory. 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/.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/.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/.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/.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/.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/.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/.changeset/thick-pumpkins-exercise.md b/.changeset/thick-pumpkins-exercise.md new file mode 100644 index 000000000..8df8b51cc --- /dev/null +++ b/.changeset/thick-pumpkins-exercise.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': major +--- + +`Initializable`: Use the namespaced storage pattern to avoid putting critical variables in slot 0. Allow reinitializer versions greater than 256. 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/.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/.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/.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/.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/.changeset/violet-melons-press.md b/.changeset/violet-melons-press.md new file mode 100644 index 000000000..18fd70b58 --- /dev/null +++ b/.changeset/violet-melons-press.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': major +--- + +`GovernorTimelockAccess`: Added a module to connect a governor with an instance of `AccessManager`, allowing the governor to make calls that are delay-restricted by the manager using the normal `queue` workflow. 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/.changeset/wet-bears-heal.md b/.changeset/wet-bears-heal.md new file mode 100644 index 000000000..2df32f39a --- /dev/null +++ b/.changeset/wet-bears-heal.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': major +--- + +Upgradeable contracts now use namespaced storage (EIP-7201). 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/.changeset/wild-peas-remain.md b/.changeset/wild-peas-remain.md new file mode 100644 index 000000000..83b4bb307 --- /dev/null +++ b/.changeset/wild-peas-remain.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': major +--- + +`Votes`: Use Trace208 for checkpoints. This enables EIP-6372 clock support for keys but reduces the max supported voting power to uint208. 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/.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/.codecov.yml b/.codecov.yml new file mode 100644 index 000000000..9455306a4 --- /dev/null +++ b/.codecov.yml @@ -0,0 +1,12 @@ +comment: off +github_checks: + annotations: false +coverage: + status: + patch: + default: + target: 95% + only_pulls: true + project: + default: + threshold: 1% diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..f162e8d20 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,21 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = false +max_line_length = 120 + +[*.sol] +indent_size = 4 + +[*.js] +indent_size = 2 + +[*.{adoc,md}] +max_line_length = 0 diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 000000000..a5418c5e4 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,20 @@ +{ + "root": true, + "extends" : [ + "eslint:recommended", + "prettier", + ], + "env": { + "es2022": true, + "browser": true, + "node": true, + "mocha": true, + }, + "globals" : { + "artifacts": "readonly", + "contract": "readonly", + "web3": "readonly", + "extendEnvironment": "readonly", + "expect": "readonly", + } +} diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 000000000..2797a0889 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,21 @@ +--- +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 new file mode 100644 index 000000000..4018cef29 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,4 @@ +contact_links: + - 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 new file mode 100644 index 000000000..ff596b0c3 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,14 @@ +--- +name: Feature request +about: Suggest an idea for OpenZeppelin Contracts + +--- + +**🧐 Motivation** + + +**πŸ“ Details** + + + + diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..239451830 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,20 @@ + + + + + +Fixes #???? + + + + + +#### PR Checklist + + + + + +- [ ] Tests +- [ ] Documentation +- [ ] Changeset entry (run `npx changeset add`) diff --git a/.github/actions/gas-compare/action.yml b/.github/actions/gas-compare/action.yml new file mode 100644 index 000000000..e38c48e89 --- /dev/null +++ b/.github/actions/gas-compare/action.yml @@ -0,0 +1,49 @@ +name: Compare gas costs +inputs: + token: + description: github token + required: true + report: + description: report to read from + required: false + default: gasReporterOutput.json + out_report: + description: report to read + required: false + default: ${{ github.ref_name }}.gasreport.json + ref_report: + description: report to read from + required: false + default: ${{ github.base_ref }}.gasreport.json + +runs: + using: composite + steps: + - name: Download reference report + if: github.event_name == 'pull_request' + run: | + RUN_ID=`gh run list --repo ${{ github.repository }} --branch ${{ github.base_ref }} --workflow ${{ github.workflow }} --limit 100 --json 'conclusion,databaseId,event' --jq 'map(select(.conclusion=="success" and .event!="pull_request"))[0].databaseId'` + gh run download ${RUN_ID} --repo ${{ github.repository }} -n gasreport + env: + GITHUB_TOKEN: ${{ inputs.token }} + shell: bash + continue-on-error: true + id: reference + - name: Compare reports + if: steps.reference.outcome == 'success' && github.event_name == 'pull_request' + run: | + node scripts/checks/compareGasReports.js ${{ inputs.report }} ${{ inputs.ref_report }} >> $GITHUB_STEP_SUMMARY + env: + STYLE: markdown + shell: bash + - name: Rename report for upload + if: github.event_name != 'pull_request' + run: | + mv ${{ inputs.report }} ${{ inputs.out_report }} + shell: bash + - name: Save report + if: github.event_name != 'pull_request' + uses: actions/upload-artifact@v3 + with: + name: gasreport + path: ${{ inputs.out_report }} diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml new file mode 100644 index 000000000..84ebe823a --- /dev/null +++ b/.github/actions/setup/action.yml @@ -0,0 +1,19 @@ +name: Setup + +runs: + using: composite + steps: + - uses: actions/setup-node@v3 + with: + node-version: 16.x + - uses: actions/cache@v3 + id: cache + with: + path: '**/node_modules' + key: npm-v3-${{ hashFiles('**/package-lock.json') }} + - name: Install dependencies + run: npm ci + shell: bash + if: steps.cache.outputs.cache-hit != 'true' + env: + SKIP_COMPILE: true diff --git a/.github/actions/storage-layout/action.yml b/.github/actions/storage-layout/action.yml new file mode 100644 index 000000000..fdfd5a25b --- /dev/null +++ b/.github/actions/storage-layout/action.yml @@ -0,0 +1,55 @@ +name: Compare storage layouts +inputs: + token: + description: github token + required: true + buildinfo: + description: compilation artifacts + required: false + default: artifacts/build-info/*.json + layout: + description: extracted storage layout + required: false + default: HEAD.layout.json + out_layout: + description: storage layout to upload + required: false + default: ${{ github.ref_name }}.layout.json + ref_layout: + description: storage layout for the reference branch + required: false + default: ${{ github.base_ref }}.layout.json + +runs: + using: composite + steps: + - name: Extract layout + run: | + node scripts/checks/extract-layout.js ${{ inputs.buildinfo }} > ${{ inputs.layout }} + shell: bash + - name: Download reference + if: github.event_name == 'pull_request' + run: | + RUN_ID=`gh run list --repo ${{ github.repository }} --branch ${{ github.base_ref }} --workflow ${{ github.workflow }} --limit 100 --json 'conclusion,databaseId,event' --jq 'map(select(.conclusion=="success" and .event!="pull_request"))[0].databaseId'` + gh run download ${RUN_ID} --repo ${{ github.repository }} -n layout + env: + GITHUB_TOKEN: ${{ inputs.token }} + shell: bash + continue-on-error: true + id: reference + - name: Compare layouts + if: steps.reference.outcome == 'success' && github.event_name == 'pull_request' + run: | + node scripts/checks/compare-layout.js --head ${{ inputs.layout }} --ref ${{ inputs.ref_layout }} + shell: bash + - name: Rename artifacts for upload + if: github.event_name != 'pull_request' + run: | + mv ${{ inputs.layout }} ${{ inputs.out_layout }} + shell: bash + - name: Save artifacts + if: github.event_name != 'pull_request' + uses: actions/upload-artifact@v3 + with: + name: layout + path: ${{ inputs.out_layout }} diff --git a/.github/workflows/actionlint.yml b/.github/workflows/actionlint.yml new file mode 100644 index 000000000..3e42c8a26 --- /dev/null +++ b/.github/workflows/actionlint.yml @@ -0,0 +1,18 @@ +name: lint workflows + +on: + pull_request: + paths: + - '.github/**/*.ya?ml' + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Add problem matchers + run: | + # https://github.com/rhysd/actionlint/blob/3a2f2c7/docs/usage.md#problem-matchers + curl -LO https://raw.githubusercontent.com/rhysd/actionlint/main/.github/actionlint-matcher.json + echo "::add-matcher::actionlint-matcher.json" + - uses: docker://rhysd/actionlint:latest diff --git a/.github/workflows/changeset.yml b/.github/workflows/changeset.yml new file mode 100644 index 000000000..efc5c5347 --- /dev/null +++ b/.github/workflows/changeset.yml @@ -0,0 +1,28 @@ +name: changeset + +on: + pull_request: + branches: + - master + types: + - opened + - synchronize + - labeled + - unlabeled + +concurrency: + group: changeset-${{ github.ref }} + cancel-in-progress: true + +jobs: + check: + runs-on: ubuntu-latest + if: ${{ !contains(github.event.pull_request.labels.*.name, 'ignore-changeset') }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # Include history so Changesets finds merge-base + - name: Set up environment + uses: ./.github/actions/setup + - name: Check changeset + run: npx changeset status --since=origin/${{ github.base_ref }} diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml new file mode 100644 index 000000000..3d9b199fb --- /dev/null +++ b/.github/workflows/checks.yml @@ -0,0 +1,116 @@ +name: checks + +on: + push: + branches: + - master + - next-v* + - release-v* + pull_request: {} + workflow_dispatch: {} + +concurrency: + group: checks-${{ github.ref }} + cancel-in-progress: true + +env: + NODE_OPTIONS: --max_old_space_size=5120 + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up environment + uses: ./.github/actions/setup + - run: npm run lint + + tests: + runs-on: ubuntu-latest + env: + FORCE_COLOR: 1 + GAS: true + steps: + - uses: actions/checkout@v4 + - name: Set up environment + uses: ./.github/actions/setup + - name: Run tests and generate gas report + run: npm run test + - name: Check linearisation of the inheritance graph + run: npm run test:inheritance + - name: Check proceduraly generated contracts are up-to-date + run: npm run test:generation + - name: Compare gas costs + uses: ./.github/actions/gas-compare + if: github.base_ref == 'master' + with: + token: ${{ github.token }} + + tests-upgradeable: + runs-on: ubuntu-latest + env: + FORCE_COLOR: 1 + steps: + - uses: actions/checkout@v4 + 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 + - name: Check linearisation of the inheritance graph + run: npm run test:inheritance + - name: Check storage layout + uses: ./.github/actions/storage-layout + if: github.base_ref == 'master' + continue-on-error: ${{ contains(github.event.pull_request.labels.*.name, 'breaking change') }} + with: + token: ${{ github.token }} + + tests-foundry: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + with: + version: nightly + - name: Run tests + run: forge test -vv + + coverage: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up environment + uses: ./.github/actions/setup + - run: npm run coverage + - uses: codecov/codecov-action@v3 + with: + token: ${{ secrets.CODECOV_TOKEN }} + + slither: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up environment + uses: ./.github/actions/setup + - run: rm foundry.toml + - uses: crytic/slither-action@v0.3.0 + with: + node-version: 18.15 + + codespell: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Run CodeSpell + uses: codespell-project/actions-codespell@v2.0 + with: + check_hidden: true + check_filenames: true + skip: package-lock.json,*.pdf diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 000000000..04b8131cb --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,19 @@ +name: Build Docs + +on: + push: + branches: [release-v*] + +permissions: + contents: write + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up environment + uses: ./.github/actions/setup + - run: bash scripts/git-user-config.sh + - run: node scripts/update-docs-branch.js + - run: git push --all origin diff --git a/.github/workflows/formal-verification.yml b/.github/workflows/formal-verification.yml new file mode 100644 index 000000000..6b4ca2cad --- /dev/null +++ b/.github/workflows/formal-verification.yml @@ -0,0 +1,68 @@ +name: formal verification + +on: + pull_request: + types: + - opened + - reopened + - synchronize + - labeled + workflow_dispatch: {} + +env: + PIP_VERSION: '3.10' + JAVA_VERSION: '11' + SOLC_VERSION: '0.8.20' + +concurrency: ${{ github.workflow }}-${{ github.ref }} + +jobs: + apply-diff: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Apply patches + run: make -C certora apply + + verify: + runs-on: ubuntu-latest + if: github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'formal-verification') + steps: + - uses: actions/checkout@v4 + 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: + python-version: ${{ env.PIP_VERSION }} + cache: 'pip' + - name: Install python packages + run: pip install -r requirements.txt + - name: Install java + uses: actions/setup-java@v3 + with: + distribution: temurin + java-version: ${{ env.JAVA_VERSION }} + - name: Install solc + run: | + wget https://github.com/ethereum/solidity/releases/download/v${{ env.SOLC_VERSION }}/solc-static-linux + sudo mv solc-static-linux /usr/local/bin/solc + chmod +x /usr/local/bin/solc + - name: Verify specification + run: | + make -C certora apply + node certora/run.js ${{ steps.arguments.outputs.result }} >> "$GITHUB_STEP_SUMMARY" + env: + CERTORAKEY: ${{ secrets.CERTORAKEY }} diff --git a/.github/workflows/release-cycle.yml b/.github/workflows/release-cycle.yml new file mode 100644 index 000000000..21d77abb9 --- /dev/null +++ b/.github/workflows/release-cycle.yml @@ -0,0 +1,218 @@ +# D: Manual Dispatch +# M: Merge release PR +# C: Commit +# β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +# β”‚Developmentβ”œβ”€β”€D──►RC-Unreleasedβ”‚ β”Œβ”€β”€β–ΊFinal-Unreleasedβ”‚ +# β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β–²β”€β”˜ β”‚ β””β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–²β”€β”˜ +# β”‚ β”‚ β”‚ β”‚ β”‚ +# M C D M C +# β”‚ β”‚ β”‚ β”‚ β”‚ +# β”Œβ–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β” β”‚ β”Œβ–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β” +# β”‚RC-Releasedβ”œβ”€β”€β”€β”˜ β”‚Final-Releasedβ”‚ +# β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +name: Release Cycle + +on: + push: + branches: + - release-v* + workflow_dispatch: {} + +concurrency: ${{ github.workflow }}-${{ github.ref }} + +jobs: + state: + name: Check state + permissions: + pull-requests: read + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up environment + uses: ./.github/actions/setup + - id: state + name: Get state + uses: actions/github-script@v6 + env: + TRIGGERING_ACTOR: ${{ github.triggering_actor }} + with: + result-encoding: string + script: await require('./scripts/release/workflow/state.js')({ github, context, core }) + outputs: + # Job Flags + start: ${{ steps.state.outputs.start }} + changesets: ${{ steps.state.outputs.changesets }} + promote: ${{ steps.state.outputs.promote }} + publish: ${{ steps.state.outputs.publish }} + merge: ${{ steps.state.outputs.merge }} + + # Global variables + is_prerelease: ${{ steps.state.outputs.is_prerelease }} + + start: + needs: state + name: Start new release candidate + permissions: + contents: write + actions: write + if: needs.state.outputs.start == 'true' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up environment + uses: ./.github/actions/setup + - run: bash scripts/git-user-config.sh + - id: start + name: Create branch with release candidate + run: bash scripts/release/workflow/start.sh + - name: Re-run workflow + uses: actions/github-script@v6 + env: + REF: ${{ steps.start.outputs.branch }} + with: + script: await require('./scripts/release/workflow/rerun.js')({ github, context }) + + promote: + needs: state + name: Promote to final release + permissions: + contents: write + actions: write + if: needs.state.outputs.promote == 'true' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up environment + uses: ./.github/actions/setup + - run: bash scripts/git-user-config.sh + - name: Exit prerelease state + if: needs.state.outputs.is_prerelease == 'true' + run: bash scripts/release/workflow/exit-prerelease.sh + - name: Re-run workflow + uses: actions/github-script@v6 + with: + script: await require('./scripts/release/workflow/rerun.js')({ github, context }) + + changesets: + needs: state + name: Update PR to release + permissions: + contents: write + pull-requests: write + if: needs.state.outputs.changesets == 'true' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # To get all tags + - name: Set up environment + uses: ./.github/actions/setup + - name: Set release title + uses: actions/github-script@v6 + with: + result-encoding: string + script: await require('./scripts/release/workflow/set-changesets-pr-title.js')({ core }) + - name: Create PR + uses: changesets/action@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PRERELEASE: ${{ needs.state.outputs.is_prerelease }} + with: + version: npm run version + title: ${{ env.TITLE }} + commit: ${{ env.TITLE }} + body: | # Wait for support on this https://github.com/changesets/action/pull/250 + This is an automated PR for releasing ${{ github.repository }} + Check [CHANGELOG.md](${{ github.repository }}/CHANGELOG.md) + + publish: + needs: state + name: Publish to npm + environment: npm + permissions: + contents: write + if: needs.state.outputs.publish == 'true' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up environment + uses: ./.github/actions/setup + - id: pack + name: Pack + run: bash scripts/release/workflow/pack.sh + env: + PRERELEASE: ${{ needs.state.outputs.is_prerelease }} + - name: Upload tarball artifact + uses: actions/upload-artifact@v3 + with: + name: ${{ github.ref_name }} + path: ${{ steps.pack.outputs.tarball }} + - name: Tag + run: npx changeset tag + - name: Publish + run: bash scripts/release/workflow/publish.sh + env: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + TARBALL: ${{ steps.pack.outputs.tarball }} + TAG: ${{ steps.pack.outputs.tag }} + - name: Push tags + run: git push --tags + - name: Create Github Release + uses: actions/github-script@v6 + env: + PRERELEASE: ${{ needs.state.outputs.is_prerelease }} + with: + script: await require('./scripts/release/workflow/github-release.js')({ github, context }) + outputs: + tarball_name: ${{ steps.pack.outputs.tarball_name }} + + integrity_check: + needs: publish + name: Tarball Integrity Check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Download tarball artifact + id: artifact + # Replace with actions/upload-artifact@v3 when + # https://github.com/actions/download-artifact/pull/194 gets released + uses: actions/download-artifact@e9ef242655d12993efdcda9058dee2db83a2cb9b + with: + name: ${{ github.ref_name }} + - name: Check integrity + run: bash scripts/release/workflow/integrity-check.sh + env: + TARBALL: ${{ steps.artifact.outputs.download-path }}/${{ needs.publish.outputs.tarball_name }} + + merge: + needs: state + name: Create PR back to master + permissions: + contents: write + pull-requests: write + if: needs.state.outputs.merge == 'true' + runs-on: ubuntu-latest + env: + MERGE_BRANCH: merge/${{ github.ref_name }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # All branches + - name: Set up environment + uses: ./.github/actions/setup + - run: bash scripts/git-user-config.sh + - name: Create branch to merge + 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: + script: | + await github.rest.pulls.create({ + owner: context.repo.owner, + repo: context.repo.repo, + head: process.env.MERGE_BRANCH, + base: 'master', + title: '${{ format('Merge {0} branch', github.ref_name) }}' + }); diff --git a/.github/workflows/upgradeable.yml b/.github/workflows/upgradeable.yml new file mode 100644 index 000000000..ed63a6dbe --- /dev/null +++ b/.github/workflows/upgradeable.yml @@ -0,0 +1,30 @@ +name: transpile upgradeable + +on: + push: + branches: + - master + - release-v* + +jobs: + transpile: + environment: push-upgradeable + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + 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/.gitignore b/.gitignore new file mode 100644 index 000000000..2aac74485 --- /dev/null +++ b/.gitignore @@ -0,0 +1,71 @@ +*.swp +*.swo + +# Logs +logs +*.log + +# Runtime data +pids +*.pid +*.seed +allFiredEvents +scTopics + +# Coverage directory used by tools like istanbul +coverage +coverage.json +coverageEnv + +# node-waf configuration +.lock-wscript + +# Dependency directory +node_modules + +# Debug log from npm +npm-debug.log + +# local env variables +.env + +# truffle build directory +build/ + +# macOS +.DS_Store + +# truffle +.node-xmlhttprequest-* + +# IntelliJ IDE +.idea + +# docs artifacts +docs/modules/api + +# only used to package @openzeppelin/contracts +contracts/build/ +contracts/README.md + +# temporary artifact from solidity-coverage +allFiredEvents +.coverage_artifacts +.coverage_cache +.coverage_contracts + +# hardat-exposed +contracts-exposed + +# Hardhat +/cache +/artifacts + +# Foundry +/out + +# Certora +.certora* +.last_confs +certora_* +.zip-output-url.txt diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..08a09bbcf --- /dev/null +++ b/.gitmodules @@ -0,0 +1,7 @@ +[submodule "lib/forge-std"] + branch = v1 + path = lib/forge-std + url = https://github.com/foundry-rs/forge-std +[submodule "lib/erc4626-tests"] + path = lib/erc4626-tests + url = https://github.com/a16z/erc4626-tests.git diff --git a/.mocharc.js b/.mocharc.js new file mode 100644 index 000000000..920662dbc --- /dev/null +++ b/.mocharc.js @@ -0,0 +1,4 @@ +module.exports = { + require: 'hardhat/register', + timeout: 4000, +}; diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 000000000..39c004c70 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,15 @@ +{ + "printWidth": 120, + "singleQuote": true, + "trailingComma": "all", + "arrowParens": "avoid", + "overrides": [ + { + "files": "*.sol", + "options": { + "singleQuote": false + } + } + ], + "plugins": ["prettier-plugin-solidity"] +} diff --git a/.solcover.js b/.solcover.js new file mode 100644 index 000000000..e0dea5e2c --- /dev/null +++ b/.solcover.js @@ -0,0 +1,13 @@ +module.exports = { + norpc: true, + testCommand: 'npm test', + compileCommand: 'npm run compile', + skipFiles: ['mocks'], + providerOptions: { + default_balance_ether: '10000000000000000000000000', + }, + mocha: { + fgrep: '[skip-on-coverage]', + invert: true, + }, +}; diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..b500d334f --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,884 @@ +# Changelog + + +## 5.0.0-rc.0 (2023-09-19) + +- `ERC1155Receiver`: Removed in favor of `ERC1155Holder`. ([#4450](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4450)) +- `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)) + +- Use `abi.encodeCall` in place of `abi.encodeWithSelector` and `abi.encodeWithSignature` for improved type-checking of parameters ([#4293](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4293)) +- `ERC2771Forwarder`: Added `deadline` for expiring transactions, batching, and more secure handling of `msg.value`. ([#4346](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4346)) +- `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. ([#4360](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4360)) +- `ERC20`, `ERC721`, `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), [#4377](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4377)) + +- `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 . ([#3962](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3962)) +- `Ownable`: Add an `initialOwner` parameter to the constructor, making the ownership initialization explicit. ([#4267](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4267)) +- `ERC721`: `_approve` no longer allows approving the owner of the tokenId. `_setApprovalForAll` no longer allows setting address(0) as an operator. ([#4377](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4377)) +- `ERC721URIStorage`: Allow setting the token URI prior to minting. ([#4559](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4559)) +- `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. ([#4382](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4382)) +- `ERC721URIStorage`, `ERC721Royalty`: Stop resetting token-specific URI and royalties when burning. ([#4561](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4561)) +- `Address`: Removed the ability to customize error messages. A common custom error is always used if the underlying revert reason cannot be bubbled up. ([#4502](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4502)) +- `Checkpoints`: library moved from `utils` to `utils/structs` ([#4275](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4275)) +- `SafeERC20`: Removed `safePermit` in favor of documentation-only `permit` recommendations. ([#4582](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4582)) +- `Governor`: Optimized use of storage for proposal data ([#4268](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4268)) +- `ERC1967Utils`: Refactor the `ERC1967Upgrade` abstract contract as a library. ([#4325](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4325)) +- `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`. ([#4354](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4354)) +- `VestingWallet`: Use `Ownable` instead of an immutable `beneficiary`. ([#4508](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4508)) +- `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. ([#4370](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4370)) +- Move the logic to validate ERC-1822 during an upgrade from `ERC1967Utils` to `UUPSUpgradeable`. ([#4356](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4356)) +- `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. ([#4430](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4430)) +- Replace revert strings and require statements with custom errors. ([#4261](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4261)) +- `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)) + +- Switched to using explicit Solidity import statements. Some previously available symbols may now have to be separately imported. ([#4399](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4399)) +- `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. ([#4418](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4418)) +- `ERC721`: Renamed `_requireMinted` to `_requireOwned` and added a return value with the current owner. Implemented `ownerOf` in terms of `_requireOwned`. ([#4566](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4566)) +- `GovernorTimelockControl`: Add the Governor instance address as part of the TimelockController operation `salt` to avoid operation id collisions between governors using the same TimelockController. +- 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`. ([#4299](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4299)) +- `ProxyAdmin`: Removed `getProxyAdmin` and `getProxyImplementation` getters. ([#3820](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3820)) + +- Bump minimum compiler version required to 0.8.20 ([#4288](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4288)) +- `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. ([#3816](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3816)) +- `ERC20`: Remove the non-standard `increaseAllowance` and `decreaseAllowance` functions. ([#4585](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4585)) +- `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. ([#4378](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4378)) +- `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)) + +- `ERC1155`: Remove check for address zero in `balanceOf`. ([#4263](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4263)) +- `ReentrancyGuard`, `Pausable`: Moved to `utils` directory. ([#4551](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4551)) +- `access`: Move `AccessControl` extensions to a dedicated directory. ([#4359](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4359)) +- `DoubleEndedQueue`: refactor internal structure to use `uint128` instead of `int128`. This has no effect on the library interface. ([#4150](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4150)) +- `Strings`: Rename `toString(int256)` to `toStringSigned(int256)`. ([#4330](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4330)) +- `BeaconProxy`: Reject value in initialization unless a payable function is explicitly invoked. ([#4382](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4382)) +- `Initializable`: Use the namespaced storage pattern to avoid putting critical variables in slot 0. Allow reinitializer versions greater than 256. ([#4460](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4460)) +- `GovernorTimelockAccess`: Added a module to connect a governor with an instance of `AccessManager`, allowing the governor to make calls that are delay-restricted by the manager using the normal `queue` workflow. ([#4523](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4523)) +- Upgradeable contracts now use namespaced storage (EIP-7201). ([#4534](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4534)) +- `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. ([#4360](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4360)) +- `Votes`: Use Trace208 for checkpoints. This enables EIP-6372 clock support for keys but reduces the max supported voting power to uint208. ([#4539](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4539)) +- `Math`: Renamed members of `Rounding` enum, and added a new rounding mode for "away from zero". ([#4455](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4455)) +- `SafeERC20`: Refactor `safeDecreaseAllowance` and `safeIncreaseAllowance` to support USDT-like tokens. ([#4260](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4260)) +- `Math`: Make `ceilDiv` to revert on 0 division even if the numerator is 0 ([#4348](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4348)) +- `Proxy`: Removed redundant `receive` function. ([#4434](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4434)) +- Replace some uses of `abi.encodePacked` with clearer alternatives (e.g. `bytes.concat`, `string.concat`). (#4504)[https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4504] ([#4296](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4296)) +- `Arrays`: Optimize `findUpperBound` by removing redundant SLOAD. ([#4442](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4442)) +- `Arrays`: Add `unsafeMemoryAccess` helpers to read from a memory array without checking the length. ([#4300](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4300)) +- `GovernorTimelockControl`: Clean up timelock id on execution for gas refund. ([#4118](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4118)) +- `Initializable`: Use intermediate variables to improve readability. ([#4576](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4576)) +- `EIP712`: Add internal getters for the name and version strings ([#4303](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4303)) +- `TimelockController`: Add a state getter that returns an `OperationState` enum. ([#4358](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4358)) +- `AccessManager`: Added a new contract for managing access control of complex systems in a consolidated location. ([#4121](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4121)) +- `Governor`, `Initializable`, and `UUPSUpgradeable`: Use internal functions in modifiers to optimize bytecode size. ([#4472](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4472)) +- `ERC1155`: Bubble errors triggered in the `onERC1155Received` and `onERC1155BatchReceived` hooks. ([#4314](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4314)) +- `AccessControl`: Add a boolean return value to the internal `_grantRole` and `_revokeRole` functions indicating whether the role was granted or revoked. ([#4241](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4241)) +- Remove the `override` specifier from functions that only override a single interface function. ([#4315](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4315)) +- `Ownable`: Prevent using address(0) as the initial owner. ([#4531](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4531)) +- Optimize `Strings.equal` ([#4262](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4262)) +- `ECDSA`: Use unchecked arithmetic for the `tryRecover` function that receives the `r` and `vs` short-signature fields separately. ([#4301](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4301)) +- `VestingWallet`: Fix revert during 1 second time window when duration is 0. ([#4502](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4502)) +- `MerkleProof`: Use custom error to report invalid multiproof instead of reverting with overflow panic. ([#4564](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4564)) +- `Governor`: Add validation in ERC1155 and ERC721 receiver hooks to ensure Governor is the executor. ([#4314](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4314)) +- `Math`: Optimized stack operations in `mulDiv`. ([#4494](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4494)) +- `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. ([#4435](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4435)) +- `ERC1155`: Optimize array allocation. ([#4196](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4196)) +- `ERC721Consecutive`: Add a `_firstConsecutiveId` internal function that can be overridden to change the id of the first token minted through `_mintConsecutive`. ([#4097](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4097)) +- `Governor`: Add a mechanism to restrict the address of the proposer using a suffix in the description. +- `ERC1155`: Optimize array accesses by skipping bounds checking when unnecessary. ([#4300](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4300)) +- `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. ([#4481](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4481)) +- `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. ([#4484](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4484)) + +## 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: + +- `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` +- `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 + +#### 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. `_transfer`, `_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); + } +``` + +### More about ERC721 + +In the case of `ERC721`, the `_update` function does not include a `from` parameter, as the sender is implicitly the previous owner of the `tokenId`. The address of +this previous owner is returned by the `_update` function, so it can be used for a posteriori checks. In addition to `to` and `tokenId`, a third parameter (`auth`) is +present in this function. This parameter enabled an optional check that the caller/spender is approved to do the transfer. This check cannot be performed after the transfer (because the transfer resets the approval), and doing it before `_update` would require a duplicate call to `_ownerOf`. + +In this logic of removing hidden SLOADs, the `_isApprovedOrOwner` function was removed in favor of a new `_isAuthorized` function. Overrides that used to target the +`_isApprovedOrOwner` should now be performed on the `_isAuthorized` function. Calls to `_isApprovedOrOwner` that preceded a call to `_transfer`, `_burn` or `_approve` +should be removed in favor of using the `auth` argument in `_update` and `_approve`. This is showcased in `ERC721Burnable.burn` and in `ERC721Wrapper.withdrawTo`. + +The `_exists` function was removed. Calls to this function can be replaced by `_ownerOf(tokenId) != address(0)`. + +#### 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); +} +``` + +## 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. + +## 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`. +- `EIP712`: Optimization of the cache for the upgradeable version affects the way `name` and `version` are set. This is no longer done through an initializer, and is instead part of the implementation's constructor. As a consequence, all proxies using the same implementation will necessarily share the same `name` and `version`. Additionally, an implementation upgrade risks changing the EIP712 domain unless the same `name` and `version` are used when deploying the new implementation contract. + +### Deprecations + +- `ERC20Permit`: Added the file `IERC20Permit.sol` and `ERC20Permit.sol` and deprecated `draft-IERC20Permit.sol` and `draft-ERC20Permit.sol` since [EIP-2612](https://eips.ethereum.org/EIPS/eip-2612) is no longer a Draft. Developers are encouraged to update their imports. ([#3793](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3793)) +- `Timers`: The `Timers` library is now deprecated and will be removed in the next major release. ([#4062](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4062)) +- `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`. + +### Breaking changes + +- `ERC721`: The internal function `_beforeTokenTransfer` no longer updates balances, which it previously did when `batchSize` was greater than 1. This change has no consequence unless a custom ERC721 extension is explicitly invoking `_beforeTokenTransfer`. Balance updates in extensions must now be done explicitly using `__unsafe_increaseBalance`, with a name that indicates that there is an invariant that has to be manually verified. + +## 4.8.1 (2023-01-12) + +- `ERC4626`: Use staticcall instead of call when fetching underlying ERC-20 decimals. ([#3943](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3943)) + +## 4.8.0 (2022-11-08) + +- `TimelockController`: Added a new `admin` constructor parameter that is assigned the admin role instead of the deployer account. ([#3722](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3722)) +- `Initializable`: add internal functions `_getInitializedVersion` and `_isInitializing` ([#3598](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3598)) +- `ERC165Checker`: add `supportsERC165InterfaceUnchecked` for consulting individual interfaces without the full ERC165 protocol. ([#3339](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3339)) +- `Address`: optimize `functionCall` by calling `functionCallWithValue` directly. ([#3468](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3468)) +- `Address`: optimize `functionCall` functions by checking contract size only if there is no returned data. ([#3469](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3469)) +- `Governor`: make the `relay` function payable, and add support for EOA payments. ([#3730](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3730)) +- `GovernorCompatibilityBravo`: remove unused `using` statements. ([#3506](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3506)) +- `ERC20`: optimize `_transfer`, `_mint` and `_burn` by using `unchecked` arithmetic when possible. ([#3513](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3513)) +- `ERC20Votes`, `ERC721Votes`: optimize `getPastVotes` for looking up recent checkpoints. ([#3673](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3673)) +- `ERC20FlashMint`: add an internal `_flashFee` function for overriding. ([#3551](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3551)) +- `ERC4626`: use the same `decimals()` as the underlying asset by default (if available). ([#3639](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3639)) +- `ERC4626`: add internal `_initialConvertToShares` and `_initialConvertToAssets` functions to customize empty vaults behavior. ([#3639](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3639)) +- `ERC721`: optimize transfers by making approval clearing implicit instead of emitting an event. ([#3481](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3481)) +- `ERC721`: optimize burn by making approval clearing implicit instead of emitting an event. ([#3538](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3538)) +- `ERC721`: Fix balance accounting when a custom `_beforeTokenTransfer` hook results in a transfer of the token under consideration. ([#3611](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3611)) +- `ERC721`: use unchecked arithmetic for balance updates. ([#3524](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3524)) +- `ERC721Consecutive`: Implementation of EIP-2309 that allows batch minting of ERC721 tokens during construction. ([#3311](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3311)) +- `ReentrancyGuard`: Reduce code size impact of the modifier by using internal functions. ([#3515](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3515)) +- `SafeCast`: optimize downcasting of signed integers. ([#3565](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3565)) +- `ECDSA`: Remove redundant check on the `v` value. ([#3591](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3591)) +- `VestingWallet`: add `releasable` getters. ([#3580](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3580)) +- `VestingWallet`: remove unused library `Math.sol`. ([#3605](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3605)) +- `VestingWallet`: make constructor payable. ([#3665](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3665)) +- `Create2`: optimize address computation by using assembly instead of `abi.encodePacked`. ([#3600](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3600)) +- `Clones`: optimized the assembly to use only the scratch space during deployments, and optimized `predictDeterministicAddress` to use fewer operations. ([#3640](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3640)) +- `Checkpoints`: Use procedural generation to support multiple key/value lengths. ([#3589](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3589)) +- `Checkpoints`: Add new lookup mechanisms. ([#3589](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3589)) +- `Arrays`: Add `unsafeAccess` functions that allow reading and writing to an element in a storage array bypassing Solidity's "out-of-bounds" check. ([#3589](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3589)) +- `Strings`: optimize `toString`. ([#3573](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3573)) +- `Ownable2Step`: extension of `Ownable` that makes the ownership transfers a two step process. ([#3620](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3620)) +- `Math` and `SignedMath`: optimize function `max` by using `>` instead of `>=`. ([#3679](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3679)) +- `Math`: Add `log2`, `log10` and `log256`. ([#3670](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3670)) +- Arbitrum: Update the vendored arbitrum contracts to match the nitro upgrade. ([#3692](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3692)) + +### Breaking changes + +- `ERC721`: In order to add support for batch minting via `ERC721Consecutive` it was necessary to make a minor breaking change in the internal interface of `ERC721`. Namely, the hooks `_beforeTokenTransfer` and `_afterTokenTransfer` have one additional argument that may need to be added to overrides: + +```diff + function _beforeTokenTransfer( + address from, + address to, + uint256 tokenId, ++ uint256 batchSize + ) internal virtual override +``` + +- `ERC4626`: Conversion from shares to assets (and vice-versa) in an empty vault used to consider the possible mismatch between the underlying asset's and the vault's decimals. This initial conversion rate is now set to 1-to-1 irrespective of decimals, which are meant for usability purposes only. The vault now uses the assets decimals by default, so off-chain the numbers should appear the same. Developers overriding the vault decimals to a value that does not match the underlying asset may want to override the `_initialConvertToShares` and `_initialConvertToAssets` to replicate the previous behavior. + +- `TimelockController`: During deployment, the TimelockController used to grant the `TIMELOCK_ADMIN_ROLE` to the deployer and to the timelock itself. The deployer was then expected to renounce this role once configuration of the timelock is over. Failing to renounce that role allows the deployer to change the timelock permissions (but not to bypass the delay for any time-locked actions). The role is no longer given to the deployer by default. A new parameter `admin` can be set to a non-zero address to grant the admin role during construction (to the deployer or any other address). Just like previously, this admin role should be renounced after configuration. If this param is given `address(0)`, the role is not allocated and doesn't need to be revoked. In any case, the timelock itself continues to have this role. + +### Deprecations + +- `EIP712`: Added the file `EIP712.sol` and deprecated `draft-EIP712.sol` since the EIP is no longer a Draft. Developers are encouraged to update their imports. ([#3621](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3621)) + +```diff +-import "@openzeppelin/contracts/utils/cryptography/draft-EIP712.sol"; ++import "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; +``` + +- `ERC721Votes`: Added the file `ERC721Votes.sol` and deprecated `draft-ERC721Votes.sol` since it no longer depends on a Draft EIP (EIP-712). Developers are encouraged to update their imports. ([#3699](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3699)) + +```diff +-import "@openzeppelin/contracts/token/ERC721/extensions/draft-ERC721Votes.sol"; ++import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Votes.sol"; +``` + +### ERC-721 Compatibility Note + +ERC-721 integrators that interpret contract state from events should make sure that they implement the clearing of approval that is implicit in every transfer according to the EIP. Previous versions of OpenZeppelin Contracts emitted an explicit `Approval` event even though it was not required by the specification, and this is no longer the case. + +With the new `ERC721Consecutive` extension, the internal workings of `ERC721` are slightly changed. Custom extensions to ERC721 should be reviewed to ensure they remain correct. The internal functions that should be considered are `_ownerOf` (new), `_beforeTokenTransfer`, and `_afterTokenTransfer`. + +### ERC-4626 Upgrade Note + +Existing `ERC4626` contracts that are upgraded to 4.8 must initialize a new variable that holds the vault token decimals. The recommended way to do this is to use a [reinitializer]: + +[reinitializer]: https://docs.openzeppelin.com/contracts/4.x/api/proxy#Initializable-reinitializer-uint8- + +```solidity +function migrateToV48() public reinitializer(2) { + __ERC4626_init(IERC20Upgradeable(asset())); +} +``` + +## 4.7.3 (2022-08-10) + +### Breaking changes + +- `ECDSA`: `recover(bytes32,bytes)` and `tryRecover(bytes32,bytes)` no longer accept compact signatures to prevent malleability. Compact signature support remains available using `recover(bytes32,bytes32,bytes32)` and `tryRecover(bytes32,bytes32,bytes32)`. + +## 4.7.2 (2022-07-25) + +- `LibArbitrumL2`, `CrossChainEnabledArbitrumL2`: Fixed detection of cross-chain calls for EOAs. Previously, calls from EOAs would be classified as cross-chain calls. ([#3578](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3578)) +- `GovernorVotesQuorumFraction`: Fixed quorum updates so they do not affect past proposals that failed due to lack of quorum. ([#3561](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3561)) +- `ERC165Checker`: Added protection against large returndata. ([#3587](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3587)) + +## 4.7.1 (2022-07-18) + +- `SignatureChecker`: Fix an issue that causes `isValidSignatureNow` to revert when the target contract returns ill-encoded data. ([#3552](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3552)) +- `ERC165Checker`: Fix an issue that causes `supportsInterface` to revert when the target contract returns ill-encoded data. ([#3552](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3552)) + +## 4.7.0 (2022-06-29) + +- `TimelockController`: Migrate `_call` to `_execute` and allow inheritance and overriding similar to `Governor`. ([#3317](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3317)) +- `CrossChainEnabledPolygonChild`: replace the `require` statement with the custom error `NotCrossChainCall`. ([#3380](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3380)) +- `ERC20FlashMint`: Add customizable flash fee receiver. ([#3327](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3327)) +- `ERC4626`: add an extension of `ERC20` that implements the ERC4626 Tokenized Vault Standard. ([#3171](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3171)) +- `SafeERC20`: add `safePermit` as mitigation against phantom permit functions. ([#3280](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3280)) +- `Math`: add a `mulDiv` function that can round the result either up or down. ([#3171](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3171)) +- `Math`: Add a `sqrt` function to compute square roots of integers, rounding either up or down. ([#3242](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3242)) +- `Strings`: add a new overloaded function `toHexString` that converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation. ([#3403](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3403)) +- `EnumerableMap`: add new `UintToUintMap` map type. ([#3338](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3338)) +- `EnumerableMap`: add new `Bytes32ToUintMap` map type. ([#3416](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3416)) +- `SafeCast`: add support for many more types, using procedural code generation. ([#3245](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3245)) +- `MerkleProof`: add `multiProofVerify` to prove multiple values are part of a Merkle tree. ([#3276](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3276)) +- `MerkleProof`: add calldata versions of the functions to avoid copying input arrays to memory and save gas. ([#3200](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3200)) +- `ERC721`, `ERC1155`: simplified revert reasons. ([#3254](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3254), ([#3438](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3438))) +- `ERC721`: removed redundant require statement. ([#3434](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3434)) +- `PaymentSplitter`: add `releasable` getters. ([#3350](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3350)) +- `Initializable`: refactored implementation of modifiers for easier understanding. ([#3450](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3450)) +- `Proxies`: remove runtime check of ERC1967 storage slots. ([#3455](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3455)) + +### Breaking changes + +- `Initializable`: functions decorated with the modifier `reinitializer(1)` may no longer invoke each other. + +## 4.6.0 (2022-04-26) + +- `crosschain`: Add a new set of contracts for cross-chain applications. `CrossChainEnabled` is a base contract with instantiations for several chains and bridges, and `AccessControlCrossChain` is an extension of access control that allows cross-chain operation. ([#3183](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3183)) +- `AccessControl`: add a virtual `_checkRole(bytes32)` function that can be overridden to alter the `onlyRole` modifier behavior. ([#3137](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3137)) +- `EnumerableMap`: add new `AddressToUintMap` map type. ([#3150](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3150)) +- `EnumerableMap`: add new `Bytes32ToBytes32Map` map type. ([#3192](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3192)) +- `ERC20FlashMint`: support infinite allowance when paying back a flash loan. ([#3226](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3226)) +- `ERC20Wrapper`: the `decimals()` function now tries to fetch the value from the underlying token instance. If that calls revert, then the default value is used. ([#3259](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3259)) +- `draft-ERC20Permit`: replace `immutable` with `constant` for `_PERMIT_TYPEHASH` since the `keccak256` of string literals is treated specially and the hash is evaluated at compile time. ([#3196](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3196)) +- `ERC1155`: Add a `_afterTokenTransfer` hook for improved extensibility. ([#3166](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3166)) +- `ERC1155URIStorage`: add a new extension that implements a `_setURI` behavior similar to ERC721's `_setTokenURI`. ([#3210](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3210)) +- `DoubleEndedQueue`: a new data structure that supports efficient push and pop to both front and back, useful for FIFO and LIFO queues. ([#3153](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3153)) +- `Governor`: improved security of `onlyGovernance` modifier when using an external executor contract (e.g. a timelock) that can operate without necessarily going through the governance protocol. ([#3147](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3147)) +- `Governor`: Add a way to parameterize votes. This can be used to implement voting systems such as fractionalized voting, ERC721 based voting, or any number of other systems. The `params` argument added to `_countVote` method, and included in the newly added `_getVotes` method, can be used by counting and voting modules respectively for such purposes. ([#3043](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3043)) +- `Governor`: rewording of revert reason for consistency. ([#3275](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3275)) +- `Governor`: fix an inconsistency in data locations that could lead to invalid bytecode being produced. ([#3295](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3295)) +- `Governor`: Implement `IERC721Receiver` and `IERC1155Receiver` to improve token custody by governors. ([#3230](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3230)) +- `TimelockController`: Implement `IERC721Receiver` and `IERC1155Receiver` to improve token custody by timelocks. ([#3230](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3230)) +- `TimelockController`: Add a separate canceller role for the ability to cancel. ([#3165](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3165)) +- `Initializable`: add a reinitializer modifier that enables the initialization of new modules, added to already initialized contracts through upgradeability. ([#3232](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3232)) +- `Initializable`: add an Initialized event that tracks initialized version numbers. ([#3294](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3294)) +- `ERC2981`: make `royaltyInfo` public to allow super call in overrides. ([#3305](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3305)) + +### Upgradeability notice + +- `TimelockController`: **(Action needed)** The upgrade from <4.6 to >=4.6 introduces a new `CANCELLER_ROLE` that requires set up to be assignable. After the upgrade, only addresses with this role will have the ability to cancel. Proposers will no longer be able to cancel. Assigning cancellers can be done by an admin (including the timelock itself) once the role admin is set up. To do this, we recommend upgrading to the `TimelockControllerWith46MigrationUpgradeable` contract and then calling the `migrateTo46` function. + +### Breaking changes + +- `Governor`: Adds internal virtual `_getVotes` method that must be implemented; this is a breaking change for existing concrete extensions to `Governor`. To fix this on an existing voting module extension, rename `getVotes` to `_getVotes` and add a `bytes memory` argument. ([#3043](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3043)) +- `Governor`: Adds `params` parameter to internal virtual `_countVote` method; this is a breaking change for existing concrete extensions to `Governor`. To fix this on an existing counting module extension, add a `bytes memory` argument to `_countVote`. ([#3043](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3043)) +- `Governor`: Does not emit `VoteCast` event when params data is non-empty; instead emits `VoteCastWithParams` event. To fix this on an integration that consumes the `VoteCast` event, also fetch/monitor `VoteCastWithParams` events. ([#3043](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3043)) +- `Votes`: The internal virtual function `_getVotingUnits` was made `view` (which was accidentally missing). Any overrides should now be updated so they are `view` as well. + +## 4.5.0 (2022-02-09) + +- `ERC2981`: add implementation of the royalty standard, and the respective extensions for `ERC721` and `ERC1155`. ([#3012](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3012)) +- `GovernorTimelockControl`: improve the `state()` function to have it reflect cases where a proposal has been canceled directly on the timelock. ([#2977](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2977)) +- Preset contracts are now deprecated in favor of [Contracts Wizard](https://wizard.openzeppelin.com). ([#2986](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2986)) +- `Governor`: add a relay function to help recover assets sent to a governor that is not its own executor (e.g. when using a timelock). ([#2926](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2926)) +- `GovernorPreventLateQuorum`: add new module to ensure a minimum voting duration is available after the quorum is reached. ([#2973](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2973)) +- `ERC721`: improved revert reason when transferring from wrong owner. ([#2975](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2975)) +- `Votes`: Added a base contract for vote tracking with delegation. ([#2944](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2944)) +- `ERC721Votes`: Added an extension of ERC721 enabled with vote tracking and delegation. ([#2944](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2944)) +- `ERC2771Context`: use immutable storage to store the forwarder address, no longer an issue since Solidity >=0.8.8 allows reading immutable variables in the constructor. ([#2917](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2917)) +- `Base64`: add a library to parse bytes into base64 strings using `encode(bytes memory)` function, and provide examples to show how to use to build URL-safe `tokenURIs`. ([#2884](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2884)) +- `ERC20`: reduce allowance before triggering transfer. ([#3056](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3056)) +- `ERC20`: do not update allowance on `transferFrom` when allowance is `type(uint256).max`. ([#3085](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3085)) +- `ERC20`: add a `_spendAllowance` internal function. ([#3170](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3170)) +- `ERC20Burnable`: do not update allowance on `burnFrom` when allowance is `type(uint256).max`. ([#3170](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3170)) +- `ERC777`: do not update allowance on `transferFrom` when allowance is `type(uint256).max`. ([#3085](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3085)) +- `ERC777`: add a `_spendAllowance` internal function. ([#3170](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3170)) +- `SignedMath`: a new signed version of the Math library with `max`, `min`, and `average`. ([#2686](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2686)) +- `SignedMath`: add an `abs(int256)` method that returns the unsigned absolute value of a signed value. ([#2984](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2984)) +- `ERC1967Upgrade`: Refactor the secure upgrade to use `ERC1822` instead of the previous rollback mechanism. This reduces code complexity and attack surface with similar security guarantees. ([#3021](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3021)) +- `UUPSUpgradeable`: Add `ERC1822` compliance to support the updated secure upgrade mechanism. ([#3021](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3021)) +- Some more functions have been made virtual to customize them via overrides. In many cases this will not imply that other functions in the contract will automatically adapt to the overridden definitions. People who wish to override should consult the source code to understand the impact and if they need to override any additional functions to achieve the desired behavior. + +### Breaking changes + +- `ERC1967Upgrade`: The function `_upgradeToAndCallSecure` was renamed to `_upgradeToAndCallUUPS`, along with the change in security mechanism described above. +- `Address`: The Solidity pragma is increased from `^0.8.0` to `^0.8.1`. This is required by the `account.code.length` syntax that replaces inline assembly. This may require users to bump their compiler version from `0.8.0` to `0.8.1` or later. Note that other parts of the code already include stricter requirements. + +## 4.4.2 (2022-01-11) + +### Bugfixes + +- `GovernorCompatibilityBravo`: Fix error in the encoding of calldata for proposals submitted through the compatibility interface with explicit signatures. ([#3100](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3100)) + +## 4.4.1 (2021-12-14) + +- `Initializable`: change the existing `initializer` modifier and add a new `onlyInitializing` modifier to prevent reentrancy risk. ([#3006](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3006)) + +### Breaking change + +It is no longer possible to call an `initializer`-protected function from within another `initializer` function outside the context of a constructor. Projects using OpenZeppelin upgradeable proxies should continue to work as is, since in the common case the initializer is invoked in the constructor directly. If this is not the case for you, the suggested change is to use the new `onlyInitializing` modifier in the following way: + +```diff + contract A { +- function initialize() public initializer { ... } ++ function initialize() internal onlyInitializing { ... } + } + contract B is A { + function initialize() public initializer { + A.initialize(); + } + } +``` + +## 4.4.0 (2021-11-25) + +- `Ownable`: add an internal `_transferOwnership(address)`. ([#2568](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2568)) +- `AccessControl`: add internal `_grantRole(bytes32,address)` and `_revokeRole(bytes32,address)`. ([#2568](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2568)) +- `AccessControl`: mark `_setupRole(bytes32,address)` as deprecated in favor of `_grantRole(bytes32,address)`. ([#2568](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2568)) +- `AccessControlEnumerable`: hook into `_grantRole(bytes32,address)` and `_revokeRole(bytes32,address)`. ([#2946](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2946)) +- `EIP712`: cache `address(this)` to immutable storage to avoid potential issues if a vanilla contract is used in a delegatecall context. ([#2852](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2852)) +- Add internal `_setApprovalForAll` to `ERC721` and `ERC1155`. ([#2834](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2834)) +- `Governor`: shift vote start and end by one block to better match Compound's GovernorBravo and prevent voting at the Governor level if the voting snapshot is not ready. ([#2892](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2892)) +- `GovernorCompatibilityBravo`: consider quorum an inclusive rather than exclusive minimum to match Compound's GovernorBravo. ([#2974](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2974)) +- `GovernorSettings`: a new governor module that manages voting settings updatable through governance actions. ([#2904](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2904)) +- `PaymentSplitter`: now supports ERC20 assets in addition to Ether. ([#2858](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2858)) +- `ECDSA`: add a variant of `toEthSignedMessageHash` for arbitrary length message hashing. ([#2865](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2865)) +- `MerkleProof`: add a `processProof` function that returns the rebuilt root hash given a leaf and a proof. ([#2841](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2841)) +- `VestingWallet`: new contract that handles the vesting of Ether and ERC20 tokens following a customizable vesting schedule. ([#2748](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2748)) +- `Governor`: enable receiving Ether when a Timelock contract is not used. ([#2849](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2849)) +- `GovernorTimelockCompound`: fix ability to use Ether stored in the Timelock contract. ([#2849](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2849)) + +## 4.3.3 (2021-11-08) + +- `ERC1155Supply`: Handle `totalSupply` changes by hooking into `_beforeTokenTransfer` to ensure consistency of balances and supply during `IERC1155Receiver.onERC1155Received` calls. + +## 4.3.2 (2021-09-14) + +- `UUPSUpgradeable`: Add modifiers to prevent `upgradeTo` and `upgradeToAndCall` being executed on any contract that is not the active ERC1967 proxy. This prevents these functions being called on implementation contracts or minimal ERC1167 clones, in particular. + +## 4.3.1 (2021-08-26) + +- `TimelockController`: Add additional isOperationReady check. + +## 4.3.0 (2021-08-17) + +- `ERC2771Context`: use private variable from storage to store the forwarder address. Fixes issues where `_msgSender()` was not callable from constructors. ([#2754](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2754)) +- `EnumerableSet`: add `values()` functions that returns an array containing all values in a single call. ([#2768](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2768)) +- `Governor`: added a modular system of `Governor` contracts based on `GovernorAlpha` and `GovernorBravo`. ([#2672](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2672)) +- Add an `interfaces` folder containing solidity interfaces to final ERCs. ([#2517](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2517)) +- `ECDSA`: add `tryRecover` functions that will not throw if the signature is invalid, and will return an error flag instead. ([#2661](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2661)) +- `SignatureChecker`: Reduce gas usage of the `isValidSignatureNow` function for the "signature by EOA" case. ([#2661](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2661)) + +## 4.2.0 (2021-06-30) + +- `ERC20Votes`: add a new extension of the `ERC20` token with support for voting snapshots and delegation. ([#2632](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2632)) +- `ERC20VotesComp`: Variant of `ERC20Votes` that is compatible with Compound's `Comp` token interface but restricts supply to `uint96`. ([#2706](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2706)) +- `ERC20Wrapper`: add a new extension of the `ERC20` token which wraps an underlying token. Deposit and withdraw guarantee that the total supply is backed by a corresponding amount of underlying token. ([#2633](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2633)) +- Enumerables: Improve gas cost of removal in `EnumerableSet` and `EnumerableMap`. +- Enumerables: Improve gas cost of lookup in `EnumerableSet` and `EnumerableMap`. +- `Counter`: add a reset method. ([#2678](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2678)) +- Tokens: Wrap definitely safe subtractions in `unchecked` blocks. +- `Math`: Add a `ceilDiv` method for performing ceiling division. +- `ERC1155Supply`: add a new `ERC1155` extension that keeps track of the totalSupply of each tokenId. ([#2593](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2593)) +- `BitMaps`: add a new `BitMaps` library that provides a storage efficient datastructure for `uint256` to `bool` mapping with contiguous keys. ([#2710](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2710)) + +### Breaking Changes + +- `ERC20FlashMint` is no longer a Draft ERC. ([#2673](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2673))) + +**How to update:** Change your import paths by removing the `draft-` prefix from `@openzeppelin/contracts/token/ERC20/extensions/draft-ERC20FlashMint.sol`. + +> See [Releases and Stability: Drafts](https://docs.openzeppelin.com/contracts/4.x/releases-stability#drafts). + +## 4.1.0 (2021-04-29) + +- `IERC20Metadata`: add a new extended interface that includes the optional `name()`, `symbol()` and `decimals()` functions. ([#2561](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2561)) +- `ERC777`: make reception acquirement optional in `_mint`. ([#2552](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2552)) +- `ERC20Permit`: add a `_useNonce` to enable further usage of ERC712 signatures. ([#2565](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2565)) +- `ERC20FlashMint`: add an implementation of the ERC3156 extension for flash-minting ERC20 tokens. ([#2543](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2543)) +- `SignatureChecker`: add a signature verification library that supports both EOA and ERC1271 compliant contracts as signers. ([#2532](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2532)) +- `Multicall`: add abstract contract with `multicall(bytes[] calldata data)` function to bundle multiple calls together ([#2608](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2608)) +- `ECDSA`: add support for ERC2098 short-signatures. ([#2582](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2582)) +- `AccessControl`: add an `onlyRole` modifier to restrict specific function to callers bearing a specific role. ([#2609](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2609)) +- `StorageSlot`: add a library for reading and writing primitive types to specific storage slots. ([#2542](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2542)) +- UUPS Proxies: add `UUPSUpgradeable` to implement the UUPS proxy pattern together with `EIP1967Proxy`. ([#2542](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2542)) + +### Breaking changes + +This release includes two small breaking changes in `TimelockController`. + +1. The `onlyRole` modifier in this contract was designed to let anyone through if the role was granted to `address(0)`, + allowing the possibility to to make a role "open", which can be used for `EXECUTOR_ROLE`. This modifier is now + replaced by `AccessControl.onlyRole`, which does not have this ability. The previous behavior was moved to the + modifier `TimelockController.onlyRoleOrOpenRole`. +2. It was possible to make `PROPOSER_ROLE` an open role (as described in the previous item) if it was granted to + `address(0)`. This would affect the `schedule`, `scheduleBatch`, and `cancel` operations in `TimelockController`. + This ability was removed as it does not make sense to open up the `PROPOSER_ROLE` in the same way that it does for + `EXECUTOR_ROLE`. + +## 4.0.0 (2021-03-23) + +- Now targeting the 0.8.x line of Solidity compilers. For 0.6.x (resp 0.7.x) support, use version 3.4.0 (resp 3.4.0-solc-0.7) of OpenZeppelin. +- `Context`: making `_msgData` return `bytes calldata` instead of `bytes memory` ([#2492](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2492)) +- `ERC20`: removed the `_setDecimals` function and the storage slot associated to decimals. ([#2502](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2502)) +- `Strings`: addition of a `toHexString` function. ([#2504](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2504)) +- `EnumerableMap`: change implementation to optimize for `key β†’ value` lookups instead of enumeration. ([#2518](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2518)) +- `GSN`: deprecate GSNv1 support in favor of upcoming support for GSNv2. ([#2521](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2521)) +- `ERC165`: remove uses of storage in the base ERC165 implementation. ERC165 based contracts now use storage-less virtual functions. Old behavior remains available in the `ERC165Storage` extension. ([#2505](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2505)) +- `Initializable`: make initializer check stricter during construction. ([#2531](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2531)) +- `ERC721`: remove enumerability of tokens from the base implementation. This feature is now provided separately through the `ERC721Enumerable` extension. ([#2511](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2511)) +- `AccessControl`: removed enumerability by default for a more lightweight contract. It is now opt-in through `AccessControlEnumerable`. ([#2512](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2512)) +- Meta Transactions: add `ERC2771Context` and a `MinimalForwarder` for meta-transactions. ([#2508](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2508)) +- Overall reorganization of the contract folder to improve clarity and discoverability. ([#2503](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2503)) +- `ERC20Capped`: optimize gas usage by enforcing the check directly in `_mint`. ([#2524](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2524)) +- Rename `UpgradeableProxy` to `ERC1967Proxy`. ([#2547](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2547)) +- `ERC777`: optimize the gas costs of the constructor. ([#2551](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2551)) +- `ERC721URIStorage`: add a new extension that implements the `_setTokenURI` behavior as it was available in 3.4.0. ([#2555](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2555)) +- `AccessControl`: added ERC165 interface detection. ([#2562](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2562)) +- `ERC1155`: make `uri` public so overloading function can call it using super. ([#2576](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2576)) + +### Bug fixes for beta releases + +- `AccessControlEnumerable`: Fixed `renounceRole` not updating enumerable set of addresses for a role. ([#2572](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2572)) + +### How to upgrade from 3.x + +Since this version has moved a few contracts to different directories, users upgrading from a previous version will need to adjust their import statements. To make this easier, the package includes a script that will migrate import statements automatically. After upgrading to the latest version of the package, run: + +``` +npx openzeppelin-contracts-migrate-imports +``` + +Make sure you're using git or another version control system to be able to recover from any potential error in our script. + +### How to upgrade from 4.0-beta.x + +Some further changes have been done between the different beta iterations. Transitions made during this period are configured in the `migrate-imports` script. Consequently, you can upgrade from any previous 4.0-beta.x version using the same script as described in the _How to upgrade from 3.x_ section. + +## 3.4.2 (2021-07-24) + +- `TimelockController`: Add additional isOperationReady check. + +## 3.4.1 (2021-03-03) + +- `ERC721`: made `_approve` an internal function (was private). + +## 3.4.0 (2021-02-02) + +- `BeaconProxy`: added new kind of proxy that allows simultaneous atomic upgrades. ([#2411](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2411)) +- `EIP712`: added helpers to verify EIP712 typed data signatures on chain. ([#2418](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2418)) +- `ERC20Permit`: added an implementation of the ERC20 permit extension for gasless token approvals. ([#2237](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2237)) +- Presets: added token presets with preminted fixed supply `ERC20PresetFixedSupply` and `ERC777PresetFixedSupply`. ([#2399](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2399)) +- `Address`: added `functionDelegateCall`, similar to the existing `functionCall`. ([#2333](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2333)) +- `Clones`: added a library for deploying EIP 1167 minimal proxies. ([#2449](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2449)) +- `Context`: moved from `contracts/GSN` to `contracts/utils`. ([#2453](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2453)) +- `PaymentSplitter`: replace usage of `.transfer()` with `Address.sendValue` for improved compatibility with smart wallets. ([#2455](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2455)) +- `UpgradeableProxy`: bubble revert reasons from initialization calls. ([#2454](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2454)) +- `SafeMath`: fix a memory allocation issue by adding new `SafeMath.tryOp(uint,uint)β†’(bool,uint)` functions. `SafeMath.op(uint,uint,string)β†’uint` are now deprecated. ([#2462](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2462)) +- `EnumerableMap`: fix a memory allocation issue by adding new `EnumerableMap.tryGet(uint)β†’(bool,address)` functions. `EnumerableMap.get(uint)β†’string` is now deprecated. ([#2462](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2462)) +- `ERC165Checker`: added batch `getSupportedInterfaces`. ([#2469](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2469)) +- `RefundEscrow`: `beneficiaryWithdraw` will forward all available gas to the beneficiary. ([#2480](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2480)) +- Many view and pure functions have been made virtual to customize them via overrides. In many cases this will not imply that other functions in the contract will automatically adapt to the overridden definitions. People who wish to override should consult the source code to understand the impact and if they need to override any additional functions to achieve the desired behavior. + +### Security Fixes + +- `ERC777`: fix potential reentrancy issues for custom extensions to `ERC777`. ([#2483](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2483)) + +If you're using our implementation of ERC777 from version 3.3.0 or earlier, and you define a custom `_beforeTokenTransfer` function that writes to a storage variable, you may be vulnerable to a reentrancy attack. If you're affected and would like assistance please write to security@openzeppelin.com. [Read more in the pull request.](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2483) + +## 3.3.0 (2020-11-26) + +- Now supports both Solidity 0.6 and 0.7. Compiling with solc 0.7 will result in warnings. Install the `solc-0.7` tag to compile without warnings. +- `Address`: added `functionStaticCall`, similar to the existing `functionCall`. ([#2333](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2333)) +- `TimelockController`: added a contract to augment access control schemes with a delay. ([#2354](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2354)) +- `EnumerableSet`: added `Bytes32Set`, for sets of `bytes32`. ([#2395](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2395)) + +## 3.2.2-solc-0.7 (2020-10-28) + +- Resolve warnings introduced by Solidity 0.7.4. ([#2396](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2396)) + +## 3.2.1-solc-0.7 (2020-09-15) + +- `ERC777`: Remove a warning about function state visibility in Solidity 0.7. ([#2327](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2327)) + +## 3.2.0 (2020-09-10) + +### New features + +- Proxies: added the proxy contracts from OpenZeppelin SDK. ([#2335](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2335)) + +#### Proxy changes with respect to OpenZeppelin SDK + +Aside from upgrading them from Solidity 0.5 to 0.6, we've changed a few minor things from the proxy contracts as they were found in OpenZeppelin SDK. + +- `UpgradeabilityProxy` was renamed to `UpgradeableProxy`. +- `AdminUpgradeabilityProxy` was renamed to `TransparentUpgradeableProxy`. +- `Proxy._willFallback` was renamed to `Proxy._beforeFallback`. +- `UpgradeabilityProxy._setImplementation` and `AdminUpgradeabilityProxy._setAdmin` were made private. + +### Improvements + +- `Address.isContract`: switched from `extcodehash` to `extcodesize` for less gas usage. ([#2311](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2311)) + +### Breaking changes + +- `ERC20Snapshot`: switched to using `_beforeTokenTransfer` hook instead of overriding ERC20 operations. ([#2312](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2312)) + +This small change in the way we implemented `ERC20Snapshot` may affect users who are combining this contract with +other ERC20 flavors, since it no longer overrides `_transfer`, `_mint`, and `_burn`. This can result in having to remove Solidity `override(...)` specifiers in derived contracts for these functions, and to instead have to add it for `_beforeTokenTransfer`. See [Using Hooks](https://docs.openzeppelin.com/contracts/3.x/extending-contracts#using-hooks) in the documentation. + +## 3.1.0 (2020-06-23) + +### New features + +- `SafeCast`: added functions to downcast signed integers (e.g. `toInt32`), improving usability of `SignedSafeMath`. ([#2243](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2243)) +- `functionCall`: new helpers that replicate Solidity's function call semantics, reducing the need to rely on `call`. ([#2264](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2264)) +- `ERC1155`: added support for a base implementation, non-standard extensions and a preset contract. ([#2014](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2014), [#2230](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/2230)) + +### Improvements + +- `ReentrancyGuard`: reduced overhead of using the `nonReentrant` modifier. ([#2171](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2171)) +- `AccessControl`: added a `RoleAdminChanged` event to `_setAdminRole`. ([#2214](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2214)) +- Made all `public` functions in the token preset contracts `virtual`. ([#2257](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2257)) + +### Deprecations + +- `SafeERC20`: deprecated `safeApprove`. ([#2268](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2268)) + +## 3.0.2 (2020-06-08) + +### Improvements + +- Added SPX license identifier to all contracts. ([#2235](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2235)) + +## 3.0.1 (2020-04-27) + +### Bugfixes + +- `ERC777`: fixed the `_approve` internal function not validating some of their arguments for non-zero addresses. ([#2213](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2213)) + +## 3.0.0 (2020-04-20) + +### New features + +- `AccessControl`: new contract for managing permissions in a system, replacement for `Ownable` and `Roles`. ([#2112](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2112)) +- `SafeCast`: new functions to convert to and from signed and unsigned values: `toUint256` and `toInt256`. ([#2123](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2123)) +- `EnumerableMap`: a new data structure for key-value pairs (like `mapping`) that can be iterated over. ([#2160](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2160)) + +### Breaking changes + +- `ERC721`: `burn(owner, tokenId)` was removed, use `burn(tokenId)` instead. ([#2125](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2125)) +- `ERC721`: `_checkOnERC721Received` was removed. ([#2125](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2125)) +- `ERC721`: `_transferFrom` and `_safeTransferFrom` were renamed to `_transfer` and `_safeTransfer`. ([#2162](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2162)) +- `Ownable`: removed `_transferOwnership`. ([#2162](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2162)) +- `PullPayment`, `Escrow`: `withdrawWithGas` was removed. The old `withdraw` function now forwards all gas. ([#2125](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2125)) +- `Roles` was removed, use `AccessControl` as a replacement. ([#2112](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2112)) +- `ECDSA`: when receiving an invalid signature, `recover` now reverts instead of returning the zero address. ([#2114](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2114)) +- `Create2`: added an `amount` argument to `deploy` for contracts with `payable` constructors. ([#2117](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2117)) +- `Pausable`: moved to the `utils` directory. ([#2122](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2122)) +- `Strings`: moved to the `utils` directory. ([#2122](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2122)) +- `Counters`: moved to the `utils` directory. ([#2122](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2122)) +- `SignedSafeMath`: moved to the `math` directory. ([#2122](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2122)) +- `ERC20Snapshot`: moved to the `token/ERC20` directory. `snapshot` was changed into an `internal` function. ([#2122](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2122)) +- `Ownable`: moved to the `access` directory. ([#2120](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2120)) +- `Ownable`: removed `isOwner`. ([#2120](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2120)) +- `Secondary`: removed from the library, use `Ownable` instead. ([#2120](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2120)) +- `Escrow`, `ConditionalEscrow`, `RefundEscrow`: these now use `Ownable` instead of `Secondary`, their external API changed accordingly. ([#2120](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2120)) +- `ERC20`: removed `_burnFrom`. ([#2119](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2119)) +- `Address`: removed `toPayable`, use `payable(address)` instead. ([#2133](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2133)) +- `ERC777`: `_send`, `_mint` and `_burn` now use the caller as the operator. ([#2134](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2134)) +- `ERC777`: removed `_callsTokensToSend` and `_callTokensReceived`. ([#2134](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2134)) +- `EnumerableSet`: renamed `get` to `at`. ([#2151](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2151)) +- `ERC165Checker`: functions no longer have a leading underscore. ([#2150](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2150)) +- `ERC721Metadata`, `ERC721Enumerable`: these contracts were removed, and their functionality merged into `ERC721`. ([#2160](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2160)) +- `ERC721`: added a constructor for `name` and `symbol`. ([#2160](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2160)) +- `ERC20Detailed`: this contract was removed and its functionality merged into `ERC20`. ([#2161](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2161)) +- `ERC20`: added a constructor for `name` and `symbol`. `decimals` now defaults to 18. ([#2161](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2161)) +- `Strings`: renamed `fromUint256` to `toString` ([#2188](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2188)) + +## 2.5.1 (2020-04-24) + +### Bugfixes + +- `ERC777`: fixed the `_send` and `_approve` internal functions not validating some of their arguments for non-zero addresses. ([#2212](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2212)) + +## 2.5.0 (2020-02-04) + +### New features + +- `SafeCast.toUintXX`: new library for integer downcasting, which allows for safe operation on smaller types (e.g. `uint32`) when combined with `SafeMath`. ([#1926](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1926)) +- `ERC721Metadata`: added `baseURI`, which can be used for dramatic gas savings when all token URIs share a prefix (e.g. `http://api.myapp.com/tokens/`). ([#1970](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1970)) +- `EnumerableSet`: new library for storing enumerable sets of values. Only `AddressSet` is supported in this release. ([#2061](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/2061)) +- `Create2`: simple library to make usage of the `CREATE2` opcode easier. ([#1744](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/1744)) + +### Improvements + +- `ERC777`: `_burn` is now internal, providing more flexibility and making it easier to create tokens that deflate. ([#1908](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/1908)) +- `ReentrancyGuard`: greatly improved gas efficiency by using the net gas metering mechanism introduced in the Istanbul hardfork. ([#1992](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/1992), [#1996](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/1996)) +- `ERC777`: improve extensibility by making `_send` and related functions `internal`. ([#2027](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2027)) +- `ERC721`: improved revert reason when transferring tokens to a non-recipient contract. ([#2018](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2018)) + +### Breaking changes + +- `ERC165Checker` now requires a minimum Solidity compiler version of 0.5.10. ([#1829](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1829)) + +## 2.4.0 (2019-10-29) + +### New features + +- `Address.toPayable`: added a helper to convert between address types without having to resort to low-level casting. ([#1773](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1773)) +- Facilities to make metatransaction-enabled contracts through the Gas Station Network. ([#1844](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/1844)) +- `Address.sendValue`: added a replacement to Solidity's `transfer`, removing the fixed gas stipend. ([#1962](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1962)) +- Added replacement for functions that don't forward all gas (which have been deprecated): ([#1976](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1976)) + - `PullPayment.withdrawPaymentsWithGas(address payable payee)` + - `Escrow.withdrawWithGas(address payable payee)` +- `SafeMath`: added support for custom error messages to `sub`, `div` and `mod` functions. ([#1828](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/1828)) + +### Improvements + +- `Address.isContract`: switched from `extcodesize` to `extcodehash` for less gas usage. ([#1802](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1802)) +- `ERC20` and `ERC777` updated to throw custom errors on subtraction overflows. ([#1828](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/1828)) + +### Deprecations + +- Deprecated functions that don't forward all gas: ([#1976](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1976)) + - `PullPayment.withdrawPayments(address payable payee)` + - `Escrow.withdraw(address payable payee)` + +### Breaking changes + +- `Address` now requires a minimum Solidity compiler version of 0.5.5. ([#1802](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1802)) +- `SignatureBouncer` has been removed from drafts, both to avoid confusions with the GSN and `GSNRecipientSignature` (previously called `GSNBouncerSignature`) and because the API was not very clear. ([#1879](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/1879)) + +### How to upgrade from 2.4.0-beta + +The final 2.4.0 release includes a refactor of the GSN contracts that will be a breaking change for 2.4.0-beta users. + +- The default empty implementations of `_preRelayedCall` and `_postRelayedCall` were removed and must now be explicitly implemented always in custom recipients. If your custom recipient didn't include an implementation, you can provide an empty one. +- `GSNRecipient`, `GSNBouncerBase`, and `GSNContext` were all merged into `GSNRecipient`. +- `GSNBouncerSignature` and `GSNBouncerERC20Fee` were renamed to `GSNRecipientSignature` and `GSNRecipientERC20Fee`. +- It is no longer necessary to inherit from `GSNRecipient` when using `GSNRecipientSignature` and `GSNRecipientERC20Fee`. + +For example, a contract using `GSNBouncerSignature` would have to be changed in the following way. + +```diff +-contract MyDapp is GSNRecipient, GSNBouncerSignature { ++contract MyDapp is GSNRecipientSignature { +``` + +Refer to the table below to adjust your inheritance list. + +| 2.4.0-beta | 2.4.0 | +| ----------------------------------- | ----------------------- | +| `GSNRecipient, GSNBouncerSignature` | `GSNRecipientSignature` | +| `GSNRecipient, GSNBouncerERC20Fee` | `GSNRecipientERC20Fee` | +| `GSNBouncerBase` | `GSNRecipient` | + +## 2.3.0 (2019-05-27) + +### New features + +- `ERC1820`: added support for interacting with the [ERC1820](https://eips.ethereum.org/EIPS/eip-1820) registry contract (`IERC1820Registry`), as well as base contracts that can be registered as implementers there. ([#1677](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1677)) +- `ERC777`: support for the [ERC777 token](https://eips.ethereum.org/EIPS/eip-777), which has multiple improvements over `ERC20` (but is backwards compatible with it) such as built-in burning, a more straightforward permission system, and optional sender and receiver hooks on transfer (mandatory for contracts!). ([#1684](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1684)) +- All contracts now have revert reason strings, which give insight into error conditions, and help debug failing transactions. ([#1704](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1704)) + +### Improvements + +- Reverted the Solidity version bump done in v2.2.0, setting the minimum compiler version to v0.5.0, to prevent unexpected build breakage. Users are encouraged however to stay on top of new compiler releases, which usually include bugfixes. ([#1729](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1729)) + +### Bugfixes + +- `PostDeliveryCrowdsale`: some validations where skipped when paired with other crowdsale flavors, such as `AllowanceCrowdsale`, or `MintableCrowdsale` and `ERC20Capped`, which could cause buyers to not be able to claim their purchased tokens. ([#1721](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1721)) +- `ERC20._transfer`: the `from` argument was allowed to be the zero address, so it was possible to internally trigger a transfer of 0 tokens from the zero address. This address is not a valid destinatary of transfers, nor can it give or receive allowance, so this behavior was inconsistent. It now reverts. ([#1752](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1752)) + +## 2.2.0 (2019-03-14) + +### New features + +- `ERC20Snapshot`: create snapshots on demand of the token balances and total supply, to later retrieve and e.g. calculate dividends at a past time. ([#1617](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1617)) +- `SafeERC20`: `ERC20` contracts with no return value (i.e. that revert on failure) are now supported. ([#1655](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1655)) +- `ERC20`: added internal `_approve(address owner, address spender, uint256 value)`, allowing derived contracts to set the allowance of arbitrary accounts. ([#1609](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1609)) +- `ERC20Metadata`: added internal `_setTokenURI(string memory tokenURI)`. ([#1618](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1618)) +- `TimedCrowdsale`: added internal `_extendTime(uint256 newClosingTime)` as well as `TimedCrowdsaleExtended(uint256 prevClosingTime, uint256 newClosingTime)` event allowing to extend the crowdsale, as long as it hasn't already closed. + +### Improvements + +- Upgraded the minimum compiler version to v0.5.2: this removes many Solidity warnings that were false positives. ([#1606](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1606)) +- `ECDSA`: `recover` no longer accepts malleable signatures (those using upper-range values for `s`, or 0/1 for `v`). ([#1622](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1622)) +- `ERC721`'s transfers are now more gas efficient due to removal of unnecessary `SafeMath` calls. ([#1610](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1610)) +- Fixed variable shadowing issues. ([#1606](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1606)) + +### Bugfixes + +- (minor) `SafeERC20`: `safeApprove` wasn't properly checking for a zero allowance when attempting to set a non-zero allowance. ([#1647](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1647)) + +### Breaking changes in drafts + +- `TokenMetadata` has been renamed to `ERC20Metadata`. ([#1618](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1618)) +- The library `Counter` has been renamed to `Counters` and its API has been improved. See an example in `ERC721`, lines [17](https://github.com/OpenZeppelin/openzeppelin-solidity/blob/3cb4a00fce1da76196ac0ac3a0ae9702b99642b5/contracts/token/ERC721/ERC721.sol#L17) and [204](https://github.com/OpenZeppelin/openzeppelin-solidity/blob/3cb4a00fce1da76196ac0ac3a0ae9702b99642b5/contracts/token/ERC721/ERC721.sol#L204). ([#1610](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1610)) + +## 2.1.3 (2019-02-26) + +- Backported `SafeERC20.safeApprove` bugfix. ([#1647](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1647)) + +## 2.1.2 (2019-01-17) + +- Removed most of the test suite from the npm package, except `PublicRole.behavior.js`, which may be useful to users testing their own `Roles`. + +## 2.1.1 (2019-01-04) + +- Version bump to avoid conflict in the npm registry. + +## 2.1.0 (2019-01-04) + +### New features + +- Now targeting the 0.5.x line of Solidity compilers. For 0.4.24 support, use version 2.0 of OpenZeppelin. +- `WhitelistCrowdsale`: a crowdsale where only whitelisted accounts (`WhitelistedRole`) can purchase tokens. Adding or removing accounts from the whitelist is done by whitelist admins (`WhitelistAdminRole`). Similar to the pre-2.0 `WhitelistedCrowdsale`. ([#1525](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1525), [#1589](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1589)) +- `RefundablePostDeliveryCrowdsale`: replacement for `RefundableCrowdsale` (deprecated, see below) where tokens are only granted once the crowdsale ends (if it meets its goal). ([#1543](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1543)) +- `PausableCrowdsale`: allows for pausers (`PauserRole`) to pause token purchases. Other crowdsale operations (e.g. withdrawals and refunds, if applicable) are not affected. ([#832](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/832)) +- `ERC20`: `transferFrom` and `_burnFrom ` now emit `Approval` events, to represent the token's state comprehensively through events. ([#1524](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1524)) +- `ERC721`: added `_burn(uint256 tokenId)`, replacing the similar deprecated function (see below). ([#1550](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1550)) +- `ERC721`: added `_tokensOfOwner(address owner)`, allowing to internally retrieve the array of an account's owned tokens. ([#1522](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1522)) +- Crowdsales: all constructors are now `public`, meaning it is not necessary to extend these contracts in order to deploy them. The exception is `FinalizableCrowdsale`, since it is meaningless unless extended. ([#1564](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1564)) +- `SignedSafeMath`: added overflow-safe operations for signed integers (`int256`). ([#1559](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1559), [#1588](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1588)) + +### Improvements + +- The compiler version required by `Array` was behind the rest of the library so it was updated to `v0.4.24`. ([#1553](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1553)) +- Now conforming to a 4-space indentation code style. ([1508](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1508)) +- `ERC20`: more gas efficient due to removed redundant `require`s. ([#1409](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1409)) +- `ERC721`: fixed a bug that prevented internal data structures from being properly cleaned, missing potential gas refunds. ([#1539](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1539) and [#1549](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1549)) +- `ERC721`: general gas savings on `transferFrom`, `_mint` and `_burn`, due to redundant `require`s and `SSTORE`s. ([#1549](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1549)) + +### Bugfixes + +### Breaking changes + +### Deprecations + +- `ERC721._burn(address owner, uint256 tokenId)`: due to the `owner` parameter being unnecessary. ([#1550](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1550)) +- `RefundableCrowdsale`: due to trading abuse potential on crowdsales that miss their goal. ([#1543](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1543)) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..cd7770dac --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,73 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at contact@openzeppelin.com. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..1a44cc27d --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,36 @@ +# Contributing Guidelines + +There are many ways to contribute to OpenZeppelin Contracts. + +## Troubleshooting + +You can help other users in the community to solve their smart contract issues in the [OpenZeppelin Forum]. + +[OpenZeppelin Forum]: https://forum.openzeppelin.com/ + +## Opening an issue + +You can [open an issue] to suggest a feature or report a minor bug. For serious bugs please do not open an issue, instead refer to our [security policy] for appropriate steps. + +If you believe your issue may be due to user error and not a problem in the library, consider instead posting a question on the [OpenZeppelin Forum]. + +Before opening an issue, be sure to search through the existing open and closed issues, and consider posting a comment in one of those instead. + +When requesting a new feature, include as many details as you can, especially around the use cases that motivate it. Features are prioritized according to the impact they may have on the ecosystem, so we appreciate information showing that the impact could be high. + +[security policy]: https://github.com/OpenZeppelin/openzeppelin-contracts/security +[open an issue]: https://github.com/OpenZeppelin/openzeppelin-contracts/issues/new/choose + +## Submitting a pull request + +If you would like to contribute code or documentation you may do so by forking the repository and submitting a pull request. + +Any non-trivial code contribution must be first discussed with the maintainers in an issue (see [Opening an issue](#opening-an-issue)). Only very minor changes are accepted without prior discussion. + +Make sure to read and follow the [engineering guidelines](./GUIDELINES.md). Run linter and tests to make sure your pull request is good before submitting it. + +Changelog entries should be added to each pull request by using [Changesets](https://github.com/changesets/changesets/). + +When opening the pull request you will be presented with a template and a series of instructions. Read through it carefully and follow all the steps. Expect a review and feedback from the maintainers afterwards. + +If you're looking for a good place to start, look for issues labelled ["good first issue"](https://github.com/OpenZeppelin/openzeppelin-contracts/labels/good%20first%20issue)! diff --git a/GUIDELINES.md b/GUIDELINES.md new file mode 100644 index 000000000..4b7f5e76e --- /dev/null +++ b/GUIDELINES.md @@ -0,0 +1,148 @@ +# Engineering Guidelines + +## Testing + +Code must be thoroughly tested with quality unit tests. + +We defer to the [Moloch Testing Guide](https://github.com/MolochVentures/moloch/tree/master/test#readme) for specific recommendations, though not all of it is relevant here. Note the introduction: + +> Tests should be written, not only to verify correctness of the target code, but to be comprehensively reviewed by other programmers. Therefore, for mission critical Solidity code, the quality of the tests are just as important (if not more so) than the code itself, and should be written with the highest standards of clarity and elegance. + +Every addition or change to the code must come with relevant and comprehensive tests. + +Refactors should avoid simultaneous changes to tests. + +Flaky tests are not acceptable. + +The test suite should run automatically for every change in the repository, and in pull requests tests must pass before merging. + +The test suite coverage must be kept as close to 100% as possible, enforced in pull requests. + +In some cases unit tests may be insufficient and complementary techniques should be used: + +1. Property-based tests (aka. fuzzing) for math-heavy code. +2. Formal verification for state machines. + +## Code style + +Solidity code should be written in a consistent format enforced by a linter, following the official [Solidity Style Guide](https://docs.soliditylang.org/en/latest/style-guide.html). See below for further [Solidity Conventions](#solidity-conventions). + +The code should be simple and straightforward, prioritizing readability and understandability. Consistency and predictability should be maintained across the codebase. In particular, this applies to naming, which should be systematic, clear, and concise. + +Sometimes these guidelines may be broken if doing so brings significant efficiency gains, but explanatory comments should be added. + +Modularity should be pursued, but not at the cost of the above priorities. + +## Documentation + +For contributors, project guidelines and processes must be documented publicly. + +For users, features must be abundantly documented. Documentation should include answers to common questions, solutions to common problems, and recommendations for critical decisions that the user may face. + +All changes to the core codebase (excluding tests, auxiliary scripts, etc.) must be documented in a changelog, except for purely cosmetic or documentation changes. + +## Peer review + +All changes must be submitted through pull requests and go through peer code review. + +The review must be approached by the reviewer in a similar way as if it was an audit of the code in question (but importantly it is not a substitute for and should not be considered an audit). + +Reviewers should enforce code and project guidelines. + +External contributions must be reviewed separately by multiple maintainers. + +## Automation + +Automation should be used as much as possible to reduce the possibility of human error and forgetfulness. + +Automations that make use of sensitive credentials must use secure secret management, and must be strengthened against attacks such as [those on GitHub Actions worklows](https://github.com/nikitastupin/pwnhub). + +Some other examples of automation are: + +- Looking for common security vulnerabilities or errors in our code (eg. reentrancy analysis). +- Keeping dependencies up to date and monitoring for vulnerable dependencies. + +## Pull requests + +Pull requests are squash-merged to keep the `master` branch history clean. The title of the pull request becomes the commit message, so it should be written in a consistent format: + +1) Begin with a capital letter. +2) Do not end with a period. +3) Write in the imperative: "Add feature X" and not "Adds feature X" or "Added feature X". + +This repository does not follow conventional commits, so do not prefix the title with "fix:" or "feat:". + +Work in progress pull requests should be submitted as Drafts and should not be prefixed with "WIP:". + +Branch names don't matter, and commit messages within a pull request mostly don't matter either, although they can help the review process. + +# Solidity Conventions + +In addition to the official Solidity Style Guide we have a number of other conventions that must be followed. + +* All state variables should be private. + + Changes to state should be accompanied by events, and in some cases it is not correct to arbitrarily set state. Encapsulating variables as private and only allowing modification via setters enables us to ensure that events and other rules are followed reliably and prevents this kind of user error. + +* Internal or private state variables or functions should have an underscore prefix. + + ```solidity + contract TestContract { + uint256 private _privateVar; + uint256 internal _internalVar; + function _testInternal() internal { ... } + function _testPrivate() private { ... } + } + ``` + +* Functions should be declared virtual, with few exceptions listed below. The + contract logic should be written considering that these functions may be + overridden by developers, e.g. getting a value using an internal getter rather + than reading directly from a state variable. + + If function A is an "alias" of function B, i.e. it invokes function B without + significant additional logic, then function A should not be virtual so that + any user overrides are implemented on B, preventing inconsistencies. + +* Events should generally be emitted immediately after the state change that they + represent, and should be named in the past tense. Some exceptions may be made for gas + efficiency if the result doesn't affect observable ordering of events. + + ```solidity + function _burn(address who, uint256 value) internal { + super._burn(who, value); + emit TokensBurned(who, value); + } + ``` + + Some standards (e.g. ERC20) use present tense, and in those cases the + standard specification is used. + +* Interface names should have a capital I prefix. + + ```solidity + 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: + + * 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/LICENSE b/LICENSE new file mode 100644 index 000000000..817249a0c --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2016-2023 zOS Global Limited and contributors + +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/README.md b/README.md new file mode 100644 index 000000000..11ee615bb --- /dev/null +++ b/README.md @@ -0,0 +1,107 @@ +> **Warning** +> Version 5.0 is under active development. The code in this branch is not recommended for use. + +# OpenZeppelin + +[![NPM Package](https://img.shields.io/npm/v/@openzeppelin/contracts.svg)](https://www.npmjs.org/package/@openzeppelin/contracts) +[![Coverage Status](https://codecov.io/gh/OpenZeppelin/openzeppelin-contracts/graph/badge.svg)](https://codecov.io/gh/OpenZeppelin/openzeppelin-contracts) +[![GitPOAPs](https://public-api.gitpoap.io/v1/repo/OpenZeppelin/openzeppelin-contracts/badge)](https://www.gitpoap.io/gh/OpenZeppelin/openzeppelin-contracts) +[![Docs](https://img.shields.io/badge/docs-%F0%9F%93%84-yellow)](https://docs.openzeppelin.com/contracts) +[![Forum](https://img.shields.io/badge/forum-%F0%9F%92%AC-yellow)](https://docs.openzeppelin.com/contracts) + +**A library for secure smart contract development.** Build on a solid foundation of community-vetted code. + + * Implementations of standards like [ERC20](https://docs.openzeppelin.com/contracts/erc20) and [ERC721](https://docs.openzeppelin.com/contracts/erc721). + * Flexible [role-based permissioning](https://docs.openzeppelin.com/contracts/access-control) scheme. + * Reusable [Solidity components](https://docs.openzeppelin.com/contracts/utilities) to build custom contracts and complex decentralized systems. + +:mage: **Not sure how to get started?** Check out [Contracts Wizard](https://wizard.openzeppelin.com/) β€” an interactive smart contract generator. + +: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. + +## Overview + +### 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. + +#### 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 +``` + +Add `@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/` in `remappings.txt.` + +### Usage + +Once installed, you can use the contracts in the library by importing them: + +```solidity +pragma solidity ^0.8.20; + +import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; + +contract MyCollectible is ERC721 { + constructor() ERC721("MyCollectible", "MCO") { + } +} +``` + +_If you're new to smart contract development, head to [Developing Smart Contracts](https://docs.openzeppelin.com/learn/developing-smart-contracts) to learn about creating a new project and compiling your contracts._ + +To keep your system secure, you should **always** use the installed code as-is, and neither copy-paste it from online sources nor modify it yourself. The library is designed so that only the contracts and functions you use are deployed, so you don't need to worry about it needlessly increasing gas costs. + +## Learn More + +The guides in the [documentation site](https://docs.openzeppelin.com/contracts) will teach about different concepts, and how to use the related contracts that OpenZeppelin Contracts provides: + +* [Access Control](https://docs.openzeppelin.com/contracts/access-control): decide who can perform each of the actions on your system. +* [Tokens](https://docs.openzeppelin.com/contracts/tokens): create tradeable assets or collectives, and distribute them via [Crowdsales](https://docs.openzeppelin.com/contracts/crowdsales). +* [Utilities](https://docs.openzeppelin.com/contracts/utilities): generic useful tools including non-overflowing math, signature verification, and trustless paying systems. + +The [full API](https://docs.openzeppelin.com/contracts/api/token/ERC20) is also thoroughly documented, and serves as a great reference when developing your smart contract application. You can also ask for help or follow Contracts's development in the [community forum](https://forum.openzeppelin.com). + +Finally, you may want to take a look at the [guides on our blog](https://blog.openzeppelin.com/), which cover several common use cases and good practices. The following articles provide great background reading, though please note that some of the referenced tools have changed, as the tooling in the ecosystem continues to rapidly evolve. + +* [The Hitchhiker’s Guide to Smart Contracts in Ethereum](https://blog.openzeppelin.com/the-hitchhikers-guide-to-smart-contracts-in-ethereum-848f08001f05) will help you get an overview of the various tools available for smart contract development, and help you set up your environment. +* [A Gentle Introduction to Ethereum Programming, Part 1](https://blog.openzeppelin.com/a-gentle-introduction-to-ethereum-programming-part-1-783cc7796094) provides very useful information on an introductory level, including many basic concepts from the Ethereum platform. +* For a more in-depth dive, you may read the guide [Designing the Architecture for Your Ethereum Application](https://blog.openzeppelin.com/designing-the-architecture-for-your-ethereum-application-9cec086f8317), which discusses how to better structure your application and its relationship to the real world. + +## Security + +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 [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). + +Past audits can be found in [`audits/`](./audits). + +Smart contracts are a nascent technology and carry a high level of technical risk and uncertainty. Although OpenZeppelin is well known for its security audits, using OpenZeppelin Contracts is not a substitute for a security audit. + +OpenZeppelin Contracts is made available under the MIT License, which disclaims all warranties in relation to the project and which limits the liability of those that contribute and maintain the project, including OpenZeppelin. As set out further in the Terms, you acknowledge that you are solely responsible for any use of OpenZeppelin Contracts and you assume all risks associated with any such use. + +## Contribute + +OpenZeppelin Contracts exists thanks to its contributors. There are many ways you can participate and help build high quality software. Check out the [contribution guide](CONTRIBUTING.md)! + +## License + +OpenZeppelin Contracts is released under the [MIT License](LICENSE). + +## Legal + +Your use of this Project is governed by the terms found at www.openzeppelin.com/tos (the "Terms"). diff --git a/RELEASING.md b/RELEASING.md new file mode 100644 index 000000000..0318f6338 --- /dev/null +++ b/RELEASING.md @@ -0,0 +1,47 @@ +# Releasing + +> Visit the documentation for [details about release schedule](https://docs.openzeppelin.com/contracts/releases-stability). + +OpenZeppelin Contracts uses a fully automated release process that takes care of compiling, packaging, and publishing the library, all of which is carried out in a clean CI environment (GitHub Actions), implemented in the ([`release-cycle`](.github/workflows/release-cycle.yml)) workflow. This helps to reduce the potential for human error and inconsistencies, and ensures that the release process is ongoing and reliable. + +## Changesets + +[Changesets](https://github.com/changesets/changesets/) is used as part of our release process for `CHANGELOG.md` management. Each change that is relevant for the codebase is expected to include a changeset. + +## Branching model + +The release cycle happens on release branches called `release-vX.Y`. Each of these branches starts as a release candidate (rc) and is eventually promoted to final. + +A release branch can be updated with cherry-picked patches from `master`, or may sometimes be committed to directly in the case of old releases. These commits will lead to a new release candidate or a patch increment depending on the state of the release branch. + +```mermaid + %%{init: {'gitGraph': {'mainBranchName': 'master'}} }%% + gitGraph + commit id: "Feature A" + commit id: "Feature B" + branch release-vX.Y + commit id: "Start release" + commit id: "Release vX.Y.0-rc.0" + + checkout master + commit id: "Feature C" + commit id: "Fix A" + + checkout release-vX.Y + cherry-pick id: "Fix A" tag: "" + commit id: "Release vX.Y.0-rc.1" + commit id: "Release vX.Y.0" + + checkout master + merge release-vX.Y + commit id: "Feature D" + commit id: "Patch B" + + checkout release-vX.Y + cherry-pick id: "Patch B" tag: "" + commit id: "Release vX.Y.1" + + checkout master + merge release-vX.Y + commit id: "Feature E" +``` diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000..4d6ff96ed --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,42 @@ +# Security Policy + +Security vulnerabilities should be disclosed to the project maintainers through [Immunefi], or alternatively by email to security@openzeppelin.com. + +[Immunefi]: https://immunefi.com/bounty/openzeppelin + +## Bug Bounty + +Responsible disclosure of security vulnerabilities is rewarded through a bug bounty program on [Immunefi]. + +There is a bonus reward for issues introduced in release candidates that are reported before making it into a stable release. + +## Security Patches + +Security vulnerabilities will be patched as soon as responsibly possible, and published as an advisory on this repository (see [advisories]) and on the affected npm packages. + +[advisories]: https://github.com/OpenZeppelin/openzeppelin-contracts/security/advisories + +Projects that build on OpenZeppelin Contracts are encouraged to clearly state, in their source code and websites, how to be contacted about security issues in the event that a direct notification is considered necessary. We recommend including it in the NatSpec for the contract as `/// @custom:security-contact security@example.com`. + +Additionally, we recommend installing the library through npm and setting up vulnerability alerts such as [Dependabot]. + +[Dependabot]: https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-supply-chain-security#what-is-dependabot + +### Supported Versions + +Security patches will be released for the latest minor of a given major release. For example, if an issue is found in versions >=4.6.0 and the latest is 4.8.0, the patch will be released only in version 4.8.1. + +Only critical severity bug fixes will be backported to past major releases. + +| Version | Critical security fixes | Other security fixes | +| ------- | ----------------------- | -------------------- | +| 4.x | :white_check_mark: | :white_check_mark: | +| 3.4 | :white_check_mark: | :x: | +| 2.5 | :white_check_mark: | :x: | +| < 2.0 | :x: | :x: | + +Note as well that the Solidity language itself only guarantees security updates for the latest release. + +## Legal + +Smart contracts are a nascent techology and carry a high level of technical risk and uncertainty. OpenZeppelin Contracts is made available under the MIT License, which disclaims all warranties in relation to the project and which limits the liability of those that contribute and maintain the project, including OpenZeppelin. Your use of the project is also governed by the terms found at www.openzeppelin.com/tos (the "Terms"). As set out in the Terms, you are solely responsible for any use of OpenZeppelin Contracts and you assume all risks associated with any such use. This Security Policy in no way evidences or represents an on-going duty by any contributor, including OpenZeppelin, to correct any flaws or alert you to all or any of the potential risks of utilizing the project. diff --git a/audits/2017-03.md b/audits/2017-03.md new file mode 100644 index 000000000..4cd6dbfd3 --- /dev/null +++ b/audits/2017-03.md @@ -0,0 +1,292 @@ +# OpenZeppelin Audit + +NOTE ON 2021-07-19: This report makes reference to Zeppelin, OpenZeppelin, OpenZeppelin Contracts, the OpenZeppelin team, and OpenZeppelin library. Many of these things have since been renamed and know that this audit applies to what is currently called the OpenZeppelin Contracts which are maintained by the OpenZeppelin Contracts Community. + +March, 2017 +Authored by Dennis Peterson and Peter Vessenes + +# Introduction + +Zeppelin requested that New Alchemy perform an audit of the contracts in their OpenZeppelin library. The OpenZeppelin contracts are a set of contracts intended to be a safe building block for a variety of uses by parties that may not be as sophisticated as the OpenZeppelin team. It is a design goal that the contracts be deployable safely and "as-is". + +The contracts are hosted at: + +https://github.com/OpenZeppelin/zeppelin-solidity + +All the contracts in the "contracts" folder are in scope. + +The git commit hash we evaluated is: +9c5975a706b076b7000e8179f8101e0c61024c87 + +# Disclaimer + +The audit makes no statements or warrantees about utility of the code, safety of the code, suitability of the business model, regulatory regime for the business model, or any other statements about fitness of the contracts to purpose, or their bugfree status. The audit documentation is for discussion purposes only. + +# Executive Summary + +Overall the OpenZeppelin codebase is of reasonably high quality -- it is clean, modular and follows best practices throughout. + +It is still in flux as a codebase, and needs better documentation per file as to expected behavior and future plans. It probably needs more comprehensive and aggressive tests written by people less nice than the current OpenZeppelin team. + +We identified two critical errors and one moderate issue, and would not recommend this commit hash for public use until these bugs are remedied. + +The repository includes a set of Truffle unit tests, a requirement and best practice for smart contracts like these; we recommend these be bulked up. + +# Discussion + +## Big Picture: Is This A Worthwhile Project? + +As soon as a developer touches OpenZeppelin contracts, they will modify something, leaving them in an un-audited state. We do not recommend developers deploy any unaudited code to the Blockchain if it will handle money, information or other things of value. + +> "In accordance with Unix philosophy, Perl gives you enough rope to hang yourself" +> --Larry Wall + +We think this is an incredibly worthwhile project -- aided by the high code quality. Creating a framework that can be easily extended helps increase the average code quality on the Blockchain by charting a course for developers and encouraging containment of modifications to certain sections. + +> "Rust: The language that makes you take the safety off before shooting yourself in the foot" +> -- (@mbrubeck) + +We think much more could be done here, and recommend the OpenZeppelin team keep at this and keep focusing on the design goal of removing rope and adding safety. + +## Solidity Version Updates Recommended + +Most of the code uses Solidity 0.4.11, but some files under `Ownership` are marked 0.4.0. These should be updated. + +Solidity 0.4.10 will add several features which could be useful in these contracts: + +- `assert(condition)`, which throws if the condition is false + +- `revert()`, which rolls back without consuming all remaining gas. + +- `address.transfer(value)`, which is like `send` but automatically propagates exceptions, and supports `.gas()`. See https://github.com/ethereum/solidity/issues/610 for more on this. + +## Error Handling: Throw vs Return False +Solidity standards allow two ways to handle an error -- either calling `throw` or returning `false`. Both have benefits. In particular, a `throw` guarantees a complete wipe of the call stack (up to the preceding external call), whereas `false` allows a function to continue. + +In general we prefer `throw` in our code audits, because it is simpler -- it's less for an engineer to keep track of. Returning `false` and using logic to check results can quickly become a poorly-tracked state machine, and this sort of complexity can cause errors. + +In the OpenZeppelin contracts, both styles are used in different parts of the codebase. `SimpleToken` transfers throw upon failure, while the full ERC20 token returns `false`. Some modifiers `throw`, others just wrap the function body in a conditional, effectively allowing the function to return false if the condition is not met. + +We don't love this, and would usually recommend you stick with one style or the other throughout the codebase. + +In at least one case, these different techniques are combined cleverly (see the Multisig comments, line 65). As a set of contracts intended for general use, we recommend you either strive for more consistency or document explicit design criteria that govern which techniques are used where. + +Note that it may be impossible to use either one in all situations. For example, SafeMath functions pretty much have to throw upon failure, but ERC20 specifies returning booleans. Therefore we make no particular recommendations, but simply point out inconsistencies to consider. + +# Critical Issues + +## Stuck Ether in Crowdsale contract +CrowdsaleToken.sol has no provision for withdrawing the raised ether. We *strongly* recommend a standard `withdraw` function be added. There is no scenario in which someone should deploy this contract as is, whether for testing or live. + +## Recursive Call in MultisigWallet +Line 45 of `MultisigWallet.sol` checks if the amount being sent by `execute` is under a daily limit. + +This function can only be called by the "Owner". As a first angle of attack, it's worth asking what will happen if the multisig wallet owners reset the daily limit by approving a call to `resetSpentToday`. + +If a chain of calls can be constructed in which the owner confirms the `resetSpentToday` function and then withdraws through `execute` in a recursive call, the contract can be drained. In fact, this could be done without a recursive call, just through repeated `execute` calls alternating with the `confirm` calls. + +We are still working through the confirmation protocol in `Shareable.sol`, but we are not convinced that this is impossible, in fact it looks possible. The flexibility any shared owner has in being able to revoke confirmation later is another worrisome angle of approach even if some simple patches are included. + +This bug has a number of causes that need to be addressed: + +1. `resetSpentToday` and `confirm` together do not limit the days on which the function can be called or (it appears) the number of times it can be called. +1. Once a call has been confirmed and `execute`d it appears that it can be re-executed. This is not good. +3. `confirmandCheck` doesn't seem to have logic about whether or not the function in question has been called. +4. Even if it did, `revoke` would need updates and logic to deal with revocation requests after a function call had been completed. + +We do not recommend using the MultisigWallet until these issues are fixed. + +# Moderate to Minor Issues + +## PullPayment +PullPayment.sol needs some work. It has no explicit provision for cancelling a payment. This would be desirable in a number of scenarios; consider a payee losing their wallet, or giving a griefing address, or just an address that requires more than the default gas offered by `send`. + +`asyncSend` has no overflow checking. This is a bad plan. We recommend overflow and underflow checking at the layer closest to the data manipulation. + +`asyncSend` allows more balance to be queued up for sending than the contract holds. This is probably a bad idea, or at the very least should be called something different. If the intent is to allow this, it should have provisions for dealing with race conditions between competing `withdrawPayments` calls. + +It would be nice to see how many payments are pending. This would imply a bit of a rewrite; we recommend this contract get some design time, and that developers don't rely on it in its current state. + +## Shareable Contract + +We do not believe the `Shareable.sol` contract is ready for primetime. It is missing functions, and as written may be vulnerable to a reordering attack -- an attack in which a miner or other party "racing" with a smart contract participant inserts their own information into a list or mapping. + +The confirmation and revocation code needs to be looked over with a very careful eye imagining extraordinarily bad behavior by shared owners before this contract can be called safe. + +No sanity checks on the initial constructor's `required` argument are worrisome as well. + +# Line by Line Comments + +## Lifecycle + +### Killable + +Very simple, allows owner to call selfdestruct, sending funds to owner. No issues. However, note that `selfdestruct` should typically not be used; it is common that a developer may want to access data in a former contract, and they may not understand that `selfdestruct` limits access to the contract. We recommend better documentation about this dynamic, and an alternate function name for `kill` like `completelyDestroy` while `kill` would perhaps merely send funds to the owner. + +Also note that a killable function allows the owner to take funds regardless of other logic. This may be desirable or undesirable depending on the circumstances. Perhaps `Killable` should have a different name as well. + +### Migrations + +I presume that the goal of this contract is to allow and annotate a migration to a new smart contract address. We are not clear here how this would be accomplished by the code; we'd like to review with the OpenZeppelin team. + +### Pausable + +We like these pauses! Note that these allow significant griefing potential by owners, and that this might not be obvious to participants in smart contracts using the OpenZeppelin framework. We would recommend that additional sample logic be added to for instance the TokenContract showing safer use of the pause and resume functions. In particular, we would recommend a timelock after which anyone could unpause the contract. + +The modifiers use the pattern `if(bool){_;}`. This is fine for functions that return false upon failure, but could be problematic for functions expected to throw upon failure. See our comments above on standardizing on `throw` or `return(false)`. + +## Ownership + +### Ownable + +Line 19: Modifier throws if doesn't meet condition, in contrast to some other inheritable modifiers (e.g. in Pausable) that use `if(bool){_;}`. + +### Claimable + +Inherits from Ownable but the existing owner sets a pendingOwner who has to claim ownership. + +Line 17: Another modifier that throws. + +### DelayedClaimable + +Is there any reason to descend from Ownable directly, instead of just Claimable, which descends from Ownable? If not, descending from both just adds confusion. + +### Contactable + +Allows owner to set a public string of contract information. No issues. + +### Shareable + +This needs some work. Doesn't check if `_required <= len(_owners)` for instance, that would be a bummer. What if _required were like `MAX - 1`? + +I have a general concern about the difference between `owners`, `_owners`, and `owner` in `Ownable.sol`. I recommend "Owners" be renamed. In general we do not recomment single character differences in variable names, although a preceding underscore is not uncommon in Solidity code. + +Line 34: "this contract only has six types of events"...actually only two. + +Line 61: Why is `ownerIndex` keyed by addresses hashed to `uint`s? Why not use the addresses directly, so `ownerIndex` is less obscure, and so there's stronger typing? + +Line 62: Do not love `++i) ... owners[2+ i]`. Makes me do math, which is not what I want to do. I want to not have to do math. + +There should probably be a function for adding a new operation, so the developer doesn't have to work directly with the internal data. (This would make the multisig contract even shorter.) + +There's a `revoke` function but not a `propose` function that we can see. + +Beware reordering. If `propose` allows the user to choose a bytes string for their proposal, bad things(TM) will happen as currently written. + + +### Multisig + +Just an interface. Note it allows changing an owner address, but not changing the number of owners. This is somewhat limiting but also simplifies implementation. + +## Payment + +### PullPayment + +Safe from reentrance attack since ether send is at the end, plus it uses `.send()` rather than `.call.value()`. + +There's an argument to be made that `.call.value()` is a better option *if* you're sure that it will be done after all state updates, since `.send` will fail if the recipient has an expensive fallback function. However, in the context of a function meant to be embedded in other contracts, it's probably better to use `.send`. One possible compromise is to add a function which allows only the owner to send ether via `.call.value`. + +If you don't use `call.value` you should implement a `cancel` function in case some value is pending here. + +Line 14: +Doesn't use safeAdd. Although it appears that payout amounts can only be increased, in fact the payer could lower the payout as much as desired via overflow. Also, the payer could add a large non-overflowing amount, causing the payment to exceed the contract balance and therefore fail when withdraw is attempted. + +Recommendation: track the sum of non-withdrawn asyncSends, and don't allow a new one which exceeds the leftover balance. If it's ever desirable to make payments revocable, it should be done explicitly. + +## Tokens + +### ERC20 + +Standard ERC20 interface only. + +There's a security hole in the standard, reported at Edcon: `approve` does not protect against race conditions and simply replaces the current value. An approved spender could wait for the owner to call `approve` again, then attempt to spend the old limit before the new limit is applied. If successful, this attacker could successfully spend the sum of both limits. + +This could be fixed by either (1) including the old limit as a parameter, so the update will fail if some gets spent, or (2) using the value parameter as a delta instead of replacement value. + +This is not fixable while adhering to the current full ERC20 standard, though it would be possible to add a "secureApprove" function. The impact isn't extreme since at least you can only be attacked by addresses you approved. Also, users could mitigate this by always setting spending limits to zero and checking for spends, before setting the new limit. + +Edcon slides: +https://drive.google.com/file/d/0ByMtMw2hul0EN3NCaVFHSFdxRzA/view + +### ERC20Basic + +Simpler interface skipping the Approve function. Note this departs from ERC20 in another way: transfer throws instead of returning false. + +### BasicToken + +Uses `SafeSub` and `SafeMath`, so transfer `throw`s instead of returning false. This complies with ERC20Basic but not the actual ERC20 standard. + +### StandardToken + +Implementation of full ERC20 token. + +Transfer() and transferFrom() use SafeMath functions, which will cause them to throw instead of returning false. Not a security issue but departs from standard. + +### SimpleToken + +Sample instantiation of StandardToken. Note that in this sample, decimals is 18 and supply only 10,000, so the supply is a small fraction of a single nominal token. + +### CrowdsaleToken + +StandardToken which mints tokens at a fixed price when sent ether. + +There's no provision for owner withdrawing the ether. As a sample for crowdsales it should be Ownable and allow the owner to withdraw ether, rather than stranding the ether in the contract. + +Note: an alternative pattern is a mint() function which is only callable from a separate crowdsale contract, so any sort of rules can be added without modifying the token itself. + +### VestedToken + +Lines 23, 27: +Functions `transfer()` and `transferFrom()` have a modifier canTransfer which throws if not enough tokens are available. However, transfer() returns a boolean success. Inconsistent treatment of failure conditions may cause problems for other contracts using the token. (Note that transferableTokens() relies on safeSub(), so will also throw if there's insufficient balance.) + +Line 64: +Delete not actually necessary since the value is overwritten in the next line anyway. + +## Root level + +### Bounty + +Avoids potential race condition by having each researcher deploy a separate contract for attack; if a research manages to break his associated contract, other researchers can't immediately claim the reward, they have to reproduce the attack in their own contracts. + +A developer could subvert this intent by implementing `deployContract()` to always return the same address. However, this would break the `researchers` mapping, updating the researcher address associated with the contract. This could be prevented by blocking rewrites in `researchers`. + +### DayLimit + +The modifier `limitedDaily` calls `underLimit`, which both checks that the spend is below the daily limit, and adds the input value to the daily spend. This is fine if all functions throw upon failure. However, not all OpenZeppelin functions do this; there are functions that returns false, and modifiers that wrap the function body in `if (bool) {_;}`. In these cases, `_value` will be added to `spentToday`, but ether may not actually be sent because other preconditions were not met. (However in the OpenZeppelin multisig this is not a problem.) + +Lines 4, 11: +Comment claims that `DayLimit` is multiowned, and Shareable is imported, but DayLimit does not actually inherit from Shareable. The intent may be for child contracts to inherit from Shareable (as Multisig does); in this case the import should be removed and the comment altered. + +Line 46: +Manual overflow check instead of using safeAdd. Since this is called from a function that throws upon failure anyway, there's no real downside to using safeAdd. + +### LimitBalance + +No issues. + +### MultisigWallet + +Lines 28, 76, 80: +`kill`, `setDailyLimit`, and `resetSpentToday` only happen with multisig approval, and hashes for these actions are logged by Shareable. However, they should probably post their own events for easy reading. + +Line 45: +This call to underLimit will reduce the daily limit, and then either throw or return 0. So in this case there's no danger that the limit will be reduced without the operation going through. + +Line 65: +Shareable's onlyManyOwners will take the user's confirmation, and execute the function body if and only if enough users have confirmed. Whole thing throws if the send fails, which will roll back the confirmation. Confirm returns false if not enough have confirmed yet, true if the whole thing succeeds, and throws only in the exceptional circumstance that the designated transaction unexpectedly fails. Elegant design. + +Line 68: +Throw here is good but note this function can fail either by returning false or by throwing. + +Line 92: +A bit odd to split `clearPending()` between this contract and Shareable. However this does allow contracts inheriting from Shareable to use custom structs for pending transactions. + + +### SafeMath + +Another interesting comment from the same Edcon presentation was that the overflow behavior of Solidity is undocumented, so in theory, source code that relies on it could break with a future revision. + +However, compiled code should be fine, and in the unlikely event that the compiler is revised in this way, there should be plenty of warning. (But this is an argument for keeping overflow checks isolated in SafeMath.) + +Aside from that small caveat, these are fine. + diff --git a/audits/2018-10.pdf b/audits/2018-10.pdf new file mode 100644 index 000000000..d5bf12741 Binary files /dev/null and b/audits/2018-10.pdf differ diff --git a/audits/2022-10-Checkpoints.pdf b/audits/2022-10-Checkpoints.pdf new file mode 100644 index 000000000..7e0d08387 Binary files /dev/null and b/audits/2022-10-Checkpoints.pdf differ diff --git a/audits/2022-10-ERC4626.pdf b/audits/2022-10-ERC4626.pdf new file mode 100644 index 000000000..154fe7521 Binary files /dev/null and b/audits/2022-10-ERC4626.pdf differ diff --git a/audits/2023-05-v4.9.pdf b/audits/2023-05-v4.9.pdf new file mode 100644 index 000000000..21cd7f593 Binary files /dev/null and b/audits/2023-05-v4.9.pdf differ diff --git a/audits/README.md b/audits/README.md new file mode 100644 index 000000000..c629f51d3 --- /dev/null +++ b/audits/README.md @@ -0,0 +1,16 @@ +# Audits + +| 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) | + +# Formal Verification + +| Date | Version | Commit | Tool | Scope | Links | +| ------------ | ------- | --------- | ------- | -------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ | +| May 2022 | v4.7.0 | `109778c` | Certora | Initializable, GovernorPreventLateQuorum, ERC1155Burnable, ERC1155Pausable, ERC1155Supply, ERC1155Holder, ERC1155Receiver | [πŸ”—](../certora/reports/2022-05.pdf) | +| March 2022 | v4.4.0 | `4088540` | Certora | ERC20Votes, ERC20FlashMint, ERC20Wrapper, TimelockController, ERC721Votes, Votes, AccessControl, ERC1155 | [πŸ”—](../certora/reports/2022-03.pdf) | +| October 2021 | v4.4.0 | `4088540` | Certora | Governor, GovernorCountingSimple, GovernorProposalThreshold, GovernorTimelockControl, GovernorVotes, GovernorVotesQuorumFraction | [πŸ”—](../certora/reports/2021-10.pdf) | diff --git a/certora/.gitignore b/certora/.gitignore new file mode 100644 index 000000000..893adcd31 --- /dev/null +++ b/certora/.gitignore @@ -0,0 +1 @@ +patched diff --git a/certora/Makefile b/certora/Makefile new file mode 100644 index 000000000..50fd6da08 --- /dev/null +++ b/certora/Makefile @@ -0,0 +1,54 @@ +default: help + +SRC := ../contracts +DST := patched +DIFF := diff +SRCS := $(shell find $(SRC) -type f) +DSTS := $(shell find $(DST) -type f) +DIFFS := $(shell find $(DIFF) -type f) + +############################################################################### +# Apply all patches in the $DIFF folder to the $DST folder +apply: $(DST) $(patsubst $(DIFF)/%.patch,$(DST)/%,$(subst _,/,$(DIFFS))) + +# Reset the $DST folder +$(DST): FORCE + @rm -rf $@ + @cp -r $(SRC) $@ + +# Update a solidity file in the $DST directory using the corresponding patch +$(DST)/%.sol: FORCE + @echo Applying patch to $@ + @patch -p0 -d $(DST) < $(patsubst $(DST)_%,$(DIFF)/%.patch,$(subst /,_,$@)) + +############################################################################### +# Record all difference between $SRC and $DST in patches +record: $(DIFF) $(patsubst %,$(DIFF)/%.patch,$(subst /,_,$(subst $(SRC)/,,$(SRCS)) $(subst $(DST)/,,$(DSTS)))) + +# Create the $DIFF folder +$(DIFF): FORCE + @rm -rf $@ + @mkdir $@ + +# Create the patch file by comparing the source and the destination +$(DIFF)/%.patch: FORCE + @echo Generating patch $@ + @diff -ruN \ + $(patsubst $(DIFF)/%.patch,$(SRC)/%,$(subst _,/,$@)) \ + $(patsubst $(DIFF)/%.patch,$(DST)/%,$(subst _,/,$@)) \ + | sed 's+$(SRC)/++g' \ + | sed 's+$(DST)/++g' \ + > $@ + @[ -s $@ ] || rm $@ + +############################################################################### +help: + @echo "usage:" + @echo " make apply: create $(DST) directory by applying the patches to $(SRC)" + @echo " make record: record the patches capturing the differences between $(SRC) and $(DST)" + @echo " make clean: remove all generated files (those ignored by git)" + +clean: + git clean -fdX + +FORCE: ; diff --git a/certora/README.md b/certora/README.md new file mode 100644 index 000000000..cd85ba3d4 --- /dev/null +++ b/certora/README.md @@ -0,0 +1,60 @@ +# Running the certora verification tool + +These instructions detail the process for running Certora Verification Tool on OpenZeppelin Contracts. + +Documentation for CVT and the specification language are available [here](https://certora.atlassian.net/wiki/spaces/CPD/overview). + +## Prerequisites + +Follow the [Certora installation guide](https://docs.certora.com/en/latest/docs/user-guide/getting-started/install.html) in order to get the Certora Prover Package and the `solc` executable folder in your path. + +> **Note** +> An API Key is required for local testing. Although the prover will run on a Github Actions' CI environment on selected Pull Requests. + +## Running the verification + +The Certora Verification Tool proves specs for contracts, which are defined by the `./specs.json` file along with their pre-configured options. + +The verification script `./run.js` is used to submit verification jobs to the Certora Verification service. + +You can run it from the root of the repository with the following command: + +```bash +node certora/run.js [[CONTRACT_NAME:]SPEC_NAME] [OPTIONS...] +``` + +Where: + +- `CONTRACT_NAME` matches the `contract` key in the `./spec.json` file and may be empty. It will run all matching contracts if not provided. +- `SPEC_NAME` refers to a `spec` key from the `./specs.json` file. It will run every spec if not provided. +- `OPTIONS` extend the [Certora Prover CLI options](https://docs.certora.com/en/latest/docs/prover/cli/options.html#certora-prover-cli-options) and will respect the preconfigured options in the `specs.json` file. + +> **Note** +> A single spec may be configured to run for multiple contracts, whereas a single contract may run multiple specs. + +Example usage: + +```bash +node certora/run.js AccessControl # Run the AccessControl spec against every contract implementing it +``` + +## Adapting to changes in the contracts + +Some of our rules require the code to be simplified in various ways. Our primary tool for performing these simplifications is to run verification on a contract that extends the original contracts and overrides some of the methods. These "harness" contracts can be found in the `certora/harness` directory. + +This pattern does require some modifications to the original code: some methods need to be made virtual or public, for example. These changes are handled by applying a patch +to the code before verification by running: + +```bash +make -C certora apply +``` + +Before running the `certora/run.js` script, it's required to apply the corresponding patches to the `contracts` directory, placing the output in the `certora/patched` directory. Then, the contracts are verified by running the verification for the `certora/patched` directory. + +If the original contracts change, it is possible to create a conflict with the patch. In this case, the verify scripts will report an error message and output rejected changes in the `patched` directory. After merging the changes, run `make record` in the `certora` directory; this will regenerate the patch file, which can then be checked into git. + +For more information about the `make` scripts available, run: + +```bash +make -C certora help +``` diff --git a/certora/harnesses/AccessControlDefaultAdminRulesHarness.sol b/certora/harnesses/AccessControlDefaultAdminRulesHarness.sol new file mode 100644 index 000000000..e96883fa2 --- /dev/null +++ b/certora/harnesses/AccessControlDefaultAdminRulesHarness.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {AccessControlDefaultAdminRules} from "../patched/access/extensions/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/harnesses/AccessControlHarness.sol b/certora/harnesses/AccessControlHarness.sol new file mode 100644 index 000000000..e862d3eca --- /dev/null +++ b/certora/harnesses/AccessControlHarness.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {AccessControl} from "../patched/access/AccessControl.sol"; + +contract AccessControlHarness is AccessControl {} diff --git a/certora/harnesses/DoubleEndedQueueHarness.sol b/certora/harnesses/DoubleEndedQueueHarness.sol new file mode 100644 index 000000000..d684c7382 --- /dev/null +++ b/certora/harnesses/DoubleEndedQueueHarness.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {DoubleEndedQueue} from "../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 (uint128) { + return _deque._begin; + } + + function end() external view returns (uint128) { + 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/harnesses/ERC20FlashMintHarness.sol b/certora/harnesses/ERC20FlashMintHarness.sol new file mode 100644 index 000000000..2f989b243 --- /dev/null +++ b/certora/harnesses/ERC20FlashMintHarness.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import "../patched/token/ERC20/ERC20.sol"; +import "../patched/token/ERC20/extensions/ERC20Permit.sol"; +import "../patched/token/ERC20/extensions/ERC20FlashMint.sol"; + +contract ERC20FlashMintHarness is ERC20, ERC20Permit, ERC20FlashMint { + uint256 someFee; + address someFeeReceiver; + + constructor(string memory name, string memory symbol) ERC20(name, symbol) ERC20Permit(name) {} + + function mint(address account, uint256 amount) external { + _mint(account, amount); + } + + function burn(address account, uint256 amount) external { + _burn(account, amount); + } + + // public accessor + function flashFeeReceiver() public view returns (address) { + return someFeeReceiver; + } + + // internal hook + function _flashFee(address, uint256) internal view override returns (uint256) { + return someFee; + } + + function _flashFeeReceiver() internal view override returns (address) { + return someFeeReceiver; + } +} diff --git a/certora/harnesses/ERC20PermitHarness.sol b/certora/harnesses/ERC20PermitHarness.sol new file mode 100644 index 000000000..08113f4ea --- /dev/null +++ b/certora/harnesses/ERC20PermitHarness.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {ERC20Permit, ERC20} from "../patched/token/ERC20/extensions/ERC20Permit.sol"; + +contract ERC20PermitHarness is ERC20Permit { + constructor(string memory name, string memory symbol) ERC20(name, symbol) ERC20Permit(name) {} + + function mint(address account, uint256 amount) external { + _mint(account, amount); + } + + function burn(address account, uint256 amount) external { + _burn(account, amount); + } +} diff --git a/certora/harnesses/ERC20WrapperHarness.sol b/certora/harnesses/ERC20WrapperHarness.sol new file mode 100644 index 000000000..ca183ad92 --- /dev/null +++ b/certora/harnesses/ERC20WrapperHarness.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {ERC20Permit} from "../patched/token/ERC20/extensions/ERC20Permit.sol"; +import {ERC20Wrapper, IERC20, ERC20} from "../patched/token/ERC20/extensions/ERC20Wrapper.sol"; + +contract ERC20WrapperHarness is ERC20Permit, ERC20Wrapper { + constructor( + IERC20 _underlying, + string memory _name, + string memory _symbol + ) ERC20(_name, _symbol) ERC20Permit(_name) ERC20Wrapper(_underlying) {} + + function underlyingTotalSupply() public view returns (uint256) { + return underlying().totalSupply(); + } + + function underlyingBalanceOf(address account) public view returns (uint256) { + return underlying().balanceOf(account); + } + + function underlyingAllowanceToThis(address account) public view returns (uint256) { + return underlying().allowance(account, address(this)); + } + + function recover(address account) public returns (uint256) { + return _recover(account); + } + + function decimals() public view override(ERC20Wrapper, ERC20) returns (uint8) { + return super.decimals(); + } +} diff --git a/certora/harnesses/ERC3156FlashBorrowerHarness.sol b/certora/harnesses/ERC3156FlashBorrowerHarness.sol new file mode 100644 index 000000000..1c76da2d4 --- /dev/null +++ b/certora/harnesses/ERC3156FlashBorrowerHarness.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT + +import {IERC3156FlashBorrower} from "../patched/interfaces/IERC3156FlashBorrower.sol"; + +pragma solidity ^0.8.20; + +contract ERC3156FlashBorrowerHarness is IERC3156FlashBorrower { + bytes32 somethingToReturn; + + function onFlashLoan(address, address, uint256, uint256, bytes calldata) external view override returns (bytes32) { + return somethingToReturn; + } +} diff --git a/certora/harnesses/ERC721Harness.sol b/certora/harnesses/ERC721Harness.sol new file mode 100644 index 000000000..69c4c205a --- /dev/null +++ b/certora/harnesses/ERC721Harness.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {ERC721} from "../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 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..3843ef4a2 --- /dev/null +++ b/certora/harnesses/ERC721ReceiverHarness.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +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/harnesses/EnumerableMapHarness.sol b/certora/harnesses/EnumerableMapHarness.sol new file mode 100644 index 000000000..5c2f3229b --- /dev/null +++ b/certora/harnesses/EnumerableMapHarness.sol @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {EnumerableMap} from "../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..3d18b183b --- /dev/null +++ b/certora/harnesses/EnumerableSetHarness.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {EnumerableSet} from "../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/harnesses/InitializableHarness.sol b/certora/harnesses/InitializableHarness.sol new file mode 100644 index 000000000..743d677dd --- /dev/null +++ b/certora/harnesses/InitializableHarness.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {Initializable} from "../patched/proxy/utils/Initializable.sol"; + +contract InitializableHarness is Initializable { + function initialize() public initializer {} + function reinitialize(uint64 n) public reinitializer(n) {} + function disable() public { _disableInitializers(); } + + function nested_init_init() public initializer { initialize(); } + function nested_init_reinit(uint64 m) public initializer { reinitialize(m); } + function nested_reinit_init(uint64 n) public reinitializer(n) { initialize(); } + function nested_reinit_reinit(uint64 n, uint64 m) public reinitializer(n) { reinitialize(m); } + + function version() public view returns (uint64) { + return _getInitializedVersion(); + } + + function initializing() public view returns (bool) { + return _isInitializing(); + } +} diff --git a/certora/harnesses/Ownable2StepHarness.sol b/certora/harnesses/Ownable2StepHarness.sol new file mode 100644 index 000000000..09a5faa23 --- /dev/null +++ b/certora/harnesses/Ownable2StepHarness.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {Ownable2Step, Ownable} from "../patched/access/Ownable2Step.sol"; + +contract Ownable2StepHarness is Ownable2Step { + constructor(address initialOwner) Ownable(initialOwner) {} + + function restricted() external onlyOwner {} +} diff --git a/certora/harnesses/OwnableHarness.sol b/certora/harnesses/OwnableHarness.sol new file mode 100644 index 000000000..79b4b1b6c --- /dev/null +++ b/certora/harnesses/OwnableHarness.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {Ownable} from "../patched/access/Ownable.sol"; + +contract OwnableHarness is Ownable { + constructor(address initialOwner) Ownable(initialOwner) {} + + function restricted() external onlyOwner {} +} diff --git a/certora/harnesses/PausableHarness.sol b/certora/harnesses/PausableHarness.sol new file mode 100644 index 000000000..5977b9202 --- /dev/null +++ b/certora/harnesses/PausableHarness.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {Pausable} from "../patched/utils/Pausable.sol"; + +contract PausableHarness is Pausable { + function pause() external { + _pause(); + } + + function unpause() external { + _unpause(); + } + + function onlyWhenPaused() external whenPaused {} + + function onlyWhenNotPaused() external whenNotPaused {} +} diff --git a/certora/harnesses/TimelockControllerHarness.sol b/certora/harnesses/TimelockControllerHarness.sol new file mode 100644 index 000000000..95ae40621 --- /dev/null +++ b/certora/harnesses/TimelockControllerHarness.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {TimelockController} from "../patched/governance/TimelockController.sol"; + +contract TimelockControllerHarness is TimelockController { + constructor( + uint256 minDelay, + address[] memory proposers, + address[] memory executors, + address admin + ) TimelockController(minDelay, proposers, executors, admin) {} +} diff --git a/certora/reports/2021-10.pdf b/certora/reports/2021-10.pdf new file mode 100644 index 000000000..22df9c61e Binary files /dev/null and b/certora/reports/2021-10.pdf differ diff --git a/certora/reports/2022-03.pdf b/certora/reports/2022-03.pdf new file mode 100644 index 000000000..b6ff81d18 Binary files /dev/null and b/certora/reports/2022-03.pdf differ diff --git a/certora/reports/2022-05.pdf b/certora/reports/2022-05.pdf new file mode 100644 index 000000000..f24a44910 Binary files /dev/null and b/certora/reports/2022-05.pdf differ diff --git a/certora/run.js b/certora/run.js new file mode 100755 index 000000000..7b65534ea --- /dev/null +++ b/certora/run.js @@ -0,0 +1,160 @@ +#!/usr/bin/env node + +// USAGE: +// 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 proc = require('child_process'); +const { PassThrough } = require('stream'); +const events = require('events'); + +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, + }, + verbose: { + alias: 'v', + type: 'count', + default: 0, + }, + 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); +} + +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; + } +} + +if (process.exitCode) { + process.exit(process.exitCode); +} + +for (const { spec, contract, files, options = [] } of specs) { + limit( + runCertora, + spec, + contract, + files, + [...options, ...argv.options].flatMap(opt => opt.split(' ')), + ); +} + +// 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(); + const output = collect(stream); + + child.stdout.pipe(stream, { end: false }); + child.stderr.pipe(stream, { end: false }); + + // as soon as we have a job id, print the output link + stream.on('data', function logStatusUrl(data) { + 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); + } + }); + + // wait for process end + const [code, signal] = await events.once(child, 'exit'); + + // error + if (code || signal) { + console.error(`[${spec}] Exited with code ${code || signal}`); + process.exitCode = 1; + } + + // get all output + stream.end(); + + // write results in markdown format + 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)); +} + +// Collects stream data into a string +async function collect(stream) { + const buffers = []; + for await (const data of stream) { + const buf = Buffer.isBuffer(data) ? data : Buffer.from(data); + buffers.push(buf); + } + return Buffer.concat(buffers).toString('utf8'); +} + +// Formatting +let hasHeader = false; + +function formatRow(...array) { + return ['', ...array, ''].join(' | '); +} + +function writeHeader() { + console.log(formatRow('spec', 'contract', 'result', 'status', 'output')); + console.log(formatRow('-', '-', '-', '-', '-')); +} + +function writeEntry(spec, contract, success, url) { + if (!hasHeader) { + hasHeader = true; + writeHeader(); + } + console.log( + formatRow( + spec, + contract, + success ? ':x:' : ':heavy_check_mark:', + url ? `[link](${url?.replace('/output/', '/jobStatus/')})` : 'error', + url ? `[link](${url})` : 'error', + ), + ); +} diff --git a/certora/specs.json b/certora/specs.json new file mode 100644 index 000000000..3e5acb568 --- /dev/null +++ b/certora/specs.json @@ -0,0 +1,86 @@ +[ + { + "spec": "Pausable", + "contract": "PausableHarness", + "files": ["certora/harnesses/PausableHarness.sol"] + }, + { + "spec": "AccessControl", + "contract": "AccessControlHarness", + "files": ["certora/harnesses/AccessControlHarness.sol"] + }, + { + "spec": "AccessControlDefaultAdminRules", + "contract": "AccessControlDefaultAdminRulesHarness", + "files": ["certora/harnesses/AccessControlDefaultAdminRulesHarness.sol"] + }, + { + "spec": "DoubleEndedQueue", + "contract": "DoubleEndedQueueHarness", + "files": ["certora/harnesses/DoubleEndedQueueHarness.sol"] + }, + { + "spec": "Ownable", + "contract": "OwnableHarness", + "files": ["certora/harnesses/OwnableHarness.sol"] + }, + { + "spec": "Ownable2Step", + "contract": "Ownable2StepHarness", + "files": ["certora/harnesses/Ownable2StepHarness.sol"] + }, + { + "spec": "ERC20", + "contract": "ERC20PermitHarness", + "files": ["certora/harnesses/ERC20PermitHarness.sol"], + "options": ["--optimistic_loop"] + }, + { + "spec": "ERC20FlashMint", + "contract": "ERC20FlashMintHarness", + "files": [ + "certora/harnesses/ERC20FlashMintHarness.sol", + "certora/harnesses/ERC3156FlashBorrowerHarness.sol" + ], + "options": ["--optimistic_loop"] + }, + { + "spec": "ERC20Wrapper", + "contract": "ERC20WrapperHarness", + "files": [ + "certora/harnesses/ERC20PermitHarness.sol", + "certora/harnesses/ERC20WrapperHarness.sol" + ], + "options": [ + "--link ERC20WrapperHarness:_underlying=ERC20PermitHarness", + "--optimistic_loop" + ] + }, + { + "spec": "ERC721", + "contract": "ERC721Harness", + "files": ["certora/harnesses/ERC721Harness.sol", "certora/harnesses/ERC721ReceiverHarness.sol"], + "options": ["--optimistic_loop"] + }, + { + "spec": "Initializable", + "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", + "files": ["certora/harnesses/TimelockControllerHarness.sol"], + "options": ["--optimistic_hashing", "--optimistic_loop"] + } +] diff --git a/certora/specs/AccessControl.spec b/certora/specs/AccessControl.spec new file mode 100644 index 000000000..70b067218 --- /dev/null +++ b/certora/specs/AccessControl.spec @@ -0,0 +1,119 @@ +import "helpers/helpers.spec"; +import "methods/IAccessControl.spec"; + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Identify entrypoints: only grantRole, revokeRole and renounceRole can alter permissions β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +rule onlyGrantCanGrant(env e, method f, bytes32 role, address account) { + calldataarg args; + + bool hasRoleBefore = hasRole(role, account); + f(e, args); + bool hasRoleAfter = hasRole(role, account); + + assert ( + !hasRoleBefore && + hasRoleAfter + ) => ( + f.selector == sig:grantRole(bytes32, address).selector + ); + + assert ( + hasRoleBefore && + !hasRoleAfter + ) => ( + f.selector == sig:revokeRole(bytes32, address).selector || + f.selector == sig:renounceRole(bytes32, address).selector + ); +} + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Function correctness: grantRole only affects the specified user/role combo β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +rule grantRoleEffect(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); + + grantRole@withrevert(e, role, account); + bool success = !lastReverted; + + bool hasOtherRoleAfter = hasRole(otherRole, otherAccount); + + // liveness + assert success <=> isCallerAdmin; + + // effect + assert success => hasRole(role, account); + + // no side effect + assert hasOtherRoleBefore != hasOtherRoleAfter => (role == otherRole && account == otherAccount); +} + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ 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; + + // effect + assert success => !hasRole(role, account); + + // no side effect + assert hasOtherRoleBefore != hasOtherRoleAfter => (role == otherRole && account == otherAccount); +} + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ 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); + + renounceRole@withrevert(e, role, account); + bool success = !lastReverted; + + bool hasOtherRoleAfter = hasRole(otherRole, otherAccount); + + // liveness + assert success <=> account == e.msg.sender; + + // effect + assert success => !hasRole(role, account); + + // no side effect + assert hasOtherRoleBefore != hasOtherRoleAfter => (role == otherRole && account == otherAccount); +} diff --git a/certora/specs/AccessControlDefaultAdminRules.spec b/certora/specs/AccessControlDefaultAdminRules.spec new file mode 100644 index 000000000..2f5bb9d45 --- /dev/null +++ b/certora/specs/AccessControlDefaultAdminRules.spec @@ -0,0 +1,464 @@ +import "helpers/helpers.spec"; +import "methods/IAccessControlDefaultAdminRules.spec"; +import "methods/IAccessControl.spec"; +import "AccessControl.spec"; + +use rule onlyGrantCanGrant filtered { + f -> f.selector != sig:acceptDefaultAdminTransfer().selector +} + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Definitions β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +definition timeSanity(env e) returns bool = + e.block.timestamp > 0 && e.block.timestamp + defaultAdminDelay(e) < max_uint48; + +definition delayChangeWaitSanity(env e, uint48 newDelay) returns bool = + e.block.timestamp + delayChangeWait_(e, newDelay) < max_uint48; + +definition isSet(uint48 schedule) returns bool = + schedule != 0; + +definition hasPassed(env e, uint48 schedule) returns bool = + assert_uint256(schedule) < e.block.timestamp; + +definition increasingDelaySchedule(env e, uint48 newDelay) returns mathint = + e.block.timestamp + min(newDelay, defaultAdminDelayIncreaseWait()); + +definition decreasingDelaySchedule(env e, uint48 newDelay) returns mathint = + e.block.timestamp + defaultAdminDelay(e) - newDelay; + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Invariant: defaultAdmin holds the DEFAULT_ADMIN_ROLE β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +invariant defaultAdminConsistency(address account) + (account == defaultAdmin() && account != 0) <=> hasRole(DEFAULT_ADMIN_ROLE(), account) + { + preserved with (env e) { + require nonzerosender(e); + } + } + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ 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 + { + preserved { + requireInvariant defaultAdminConsistency(account); + requireInvariant defaultAdminConsistency(another); + } + } + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ 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); + address adminBefore = defaultAdmin(); + address pendingAdminBefore = pendingDefaultAdmin_(); + uint48 scheduleBefore = pendingDefaultAdminSchedule_(); + + renounceRole@withrevert(e, role, account); + bool success = !lastReverted; + + 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 || + ( + 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"; + + 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 + ), + "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) { + address adminBefore = defaultAdmin(); + f(e, args); + address adminAfter = defaultAdmin(); + + assert adminBefore != adminAfter => ( + f.selector == sig:acceptDefaultAdminTransfer().selector || + f.selector == sig:renounceRole(bytes32,address).selector + ), + "default admin is only affected by accepting an admin transfer or renoucing"; +} + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Rule: pendingDefaultAdmin is only affected by beginning, completing (accept or renounce), or canceling an admin β”‚ +β”‚ transfer β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +rule noPendingDefaultAdminChange(env e, method f, calldataarg args) { + address pendingAdminBefore = pendingDefaultAdmin_(); + uint48 scheduleBefore = pendingDefaultAdminSchedule_(); + f(e, args); + address pendingAdminAfter = pendingDefaultAdmin_(); + uint48 scheduleAfter = pendingDefaultAdminSchedule_(); + + assert ( + pendingAdminBefore != pendingAdminAfter || + scheduleBefore != scheduleAfter + ) => ( + f.selector == sig:beginDefaultAdminTransfer(address).selector || + f.selector == sig:acceptDefaultAdminTransfer().selector || + f.selector == sig:cancelDefaultAdminTransfer().selector || + f.selector == sig:renounceRole(bytes32,address).selector + ), + "pending admin and its schedule is only affected by beginning, completing, 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 == sig:changeDefaultAdminDelay(uint48).selector || + f.selector == sig: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 timeSanity(e); + require nonpayable(e); + require nonzerosender(e); + requireInvariant defaultAdminConsistency(e.msg.sender); + + 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 => to_mathint(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(); + + // change can only happen towards the newAdmin, with the delay + assert adminAfter != adminBefore => ( + adminAfter == newAdmin && + to_mathint(e2.block.timestamp) >= e1.block.timestamp + delayBefore + ), + "The admin can only change after the enforced delay and to the previously scheduled new admin"; +} + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Function correctness: acceptDefaultAdminTransfer updates defaultAdmin resetting the pending admin and its schedule β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +rule acceptDefaultAdminTransfer(env e) { + require nonpayable(e); + + address pendingAdminBefore = pendingDefaultAdmin_(); + uint48 scheduleBefore = pendingDefaultAdminSchedule_(); + + acceptDefaultAdminTransfer@withrevert(e); + bool success = !lastReverted; + + // liveness + 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 + 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); + require nonzerosender(e); + requireInvariant defaultAdminConsistency(e.msg.sender); + + 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 timeSanity(e); + require nonpayable(e); + require nonzerosender(e); + require delayChangeWaitSanity(e, newDelay); + requireInvariant defaultAdminConsistency(e.msg.sender); + + 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 => ( + assert_uint256(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 != delayBefore => ( + delayAfter == newDelay && + to_mathint(e2.block.timestamp) >= delayWait + ), + "A delay can only change after the 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 => to_mathint(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 => to_mathint(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); + require nonzerosender(e); + requireInvariant defaultAdminConsistency(e.msg.sender); + + 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"; +} diff --git a/certora/specs/DoubleEndedQueue.spec b/certora/specs/DoubleEndedQueue.spec new file mode 100644 index 000000000..3b71bb4c7 --- /dev/null +++ b/certora/specs/DoubleEndedQueue.spec @@ -0,0 +1,300 @@ +import "helpers/helpers.spec"; + +methods { + 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 + function begin() external returns (uint128) envfree; + function end() external returns (uint128) envfree; + + // view + 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 +} + +definition full() returns bool = length() == max_uint128; + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Invariant: empty() is length 0 and no element exists β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +invariant emptiness() + empty() <=> length() == 0 + filtered { f -> !f.isView } + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Invariant: front points to the first index and back points to the last one β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +invariant queueFront() + at_(0) == front() + filtered { f -> !f.isView } + +invariant queueBack() + at_(require_uint256(length() - 1)) == back() + filtered { f -> !f.isView } + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Function correctness: pushFront adds an element at the beginning of the queue β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +rule pushFront(bytes32 value) { + uint256 lengthBefore = length(); + bool fullBefore = full(); + + pushFront@withrevert(value); + bool success = !lastReverted; + + // liveness + assert success <=> !fullBefore, "never revert if not previously full"; + + // effect + assert success => front() == value, "front set to value"; + assert success => to_mathint(length()) == lengthBefore + 1, "queue extended"; +} + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Rule: pushFront preserves the previous values in the queue with a +1 offset β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +rule pushFrontConsistency(uint256 index) { + bytes32 beforeAt = at_(index); + + bytes32 value; + pushFront(value); + + // try to read value + bytes32 afterAt = at_@withrevert(require_uint256(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) { + uint256 lengthBefore = length(); + bool fullBefore = full(); + + pushBack@withrevert(value); + bool success = !lastReverted; + + // liveness + assert success <=> !fullBefore, "never revert if not previously full"; + + // effect + assert success => back() == value, "back set to value"; + assert success => to_mathint(length()) == lengthBefore + 1, "queue increased"; +} + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Rule: pushBack preserves the previous values in the queue β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +rule pushBackConsistency(uint256 index) { + 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 { + 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 => to_mathint(length()) == lengthBefore - 1, "queue decreased"; +} + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Rule: at(x) is preserved and offset to at(x - 1) after calling popFront | +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +rule popFrontConsistency(uint256 index) { + // 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(require_uint256(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 { + 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 => to_mathint(length()) == lengthBefore - 1, "queue decreased"; +} + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Rule: at(x) is preserved after calling popBack | +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +rule popBackConsistency(uint256 index) { + // Read (any) value that is not the back (this asserts the value exists / the queue is long enough) + require to_mathint(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 onlyEmptyOrFullRevert(env e) { + require nonpayable(e); + + method f; + calldataarg args; + + bool emptyBefore = empty(); + bool fullBefore = full(); + + f@withrevert(e, args); + + assert lastReverted => ( + (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"; +} + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Rule: at(index) only reverts if index is out of bounds | +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +rule onlyOutOfBoundsRevert(uint256 index) { + 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) { + method f; + calldataarg args; + + uint256 lengthBefore = length(); + f(e, args); + uint256 lengthAfter = length(); + + assert lengthAfter != lengthBefore => ( + (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"; +} + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Rule: only push/pop can change values bounded in the queue (outside values aren't cleared) β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +rule noDataChange(env e) { + 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 == 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 == 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/ERC20.spec b/certora/specs/ERC20.spec new file mode 100644 index 000000000..21a033585 --- /dev/null +++ b/certora/specs/ERC20.spec @@ -0,0 +1,352 @@ +import "helpers/helpers.spec"; +import "methods/IERC20.spec"; +import "methods/IERC2612.spec"; + +methods { + // exposed for FV + function mint(address,uint256) external; + function burn(address,uint256) external; +} + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Ghost & hooks: sum of all balances β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +ghost mathint sumOfBalances { + init_state axiom sumOfBalances == 0; +} + +// Because `balance` has a uint256 type, any balance addition in CVL1 behaved as a `require_uint256()` casting, +// leaving out the possibility of overflow. This is not the case in CVL2 where casting became more explicit. +// A counterexample in CVL2 is having an initial state where Alice initial balance is larger than totalSupply, which +// overflows Alice's balance when receiving a transfer. This is not possible unless the contract is deployed into an +// already used address (or upgraded from corrupted state). +// We restrict such behavior by making sure no balance is greater than the sum of balances. +hook Sload uint256 balance _balances[KEY address addr] STORAGE { + require sumOfBalances >= to_mathint(balance); +} + +hook Sstore _balances[KEY address addr] uint256 newValue (uint256 oldValue) STORAGE { + sumOfBalances = sumOfBalances - oldValue + newValue; +} + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Invariant: totalSupply is the sum of all balances β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +invariant totalSupplyIsSumOfBalances() + to_mathint(totalSupply()) == sumOfBalances; + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Invariant: balance of address(0) is 0 β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +invariant zeroAddressNoBalance() + balanceOf(0) == 0; + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Rules: only mint and burn can change total supply β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +rule noChangeTotalSupply(env e) { + requireInvariant totalSupplyIsSumOfBalances(); + + method f; + calldataarg args; + + uint256 totalSupplyBefore = totalSupply(); + f(e, args); + uint256 totalSupplyAfter = totalSupply(); + + assert totalSupplyAfter > totalSupplyBefore => f.selector == sig:mint(address,uint256).selector; + assert totalSupplyAfter < totalSupplyBefore => f.selector == sig:burn(address,uint256).selector; +} + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Rules: only the token holder or an approved third party can reduce an account's balance β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +rule onlyAuthorizedCanTransfer(env e) { + requireInvariant totalSupplyIsSumOfBalances(); + + method f; + calldataarg args; + address account; + + uint256 allowanceBefore = allowance(account, e.msg.sender); + uint256 balanceBefore = balanceOf(account); + f(e, args); + uint256 balanceAfter = balanceOf(account); + + assert ( + balanceAfter < balanceBefore + ) => ( + f.selector == sig:burn(address,uint256).selector || + e.msg.sender == account || + balanceBefore - balanceAfter <= to_mathint(allowanceBefore) + ); +} + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Rules: only the token holder (or a permit) can increase allowance. The spender can decrease it by using it β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +rule onlyHolderOfSpenderCanChangeAllowance(env e) { + requireInvariant totalSupplyIsSumOfBalances(); + + method f; + calldataarg args; + address holder; + address spender; + + uint256 allowanceBefore = allowance(holder, spender); + f(e, args); + uint256 allowanceAfter = allowance(holder, spender); + + assert ( + allowanceAfter > allowanceBefore + ) => ( + (f.selector == sig:approve(address,uint256).selector && e.msg.sender == holder) || + (f.selector == sig:permit(address,address,uint256,uint256,uint8,bytes32,bytes32).selector) + ); + + assert ( + allowanceAfter < allowanceBefore + ) => ( + (f.selector == sig:transferFrom(address,address,uint256).selector && e.msg.sender == spender) || + (f.selector == sig:approve(address,uint256).selector && e.msg.sender == holder ) || + (f.selector == sig:permit(address,address,uint256,uint256,uint8,bytes32,bytes32).selector) + ); +} + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Rules: mint behavior and side effects β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +rule mint(env e) { + requireInvariant totalSupplyIsSumOfBalances(); + require nonpayable(e); + + address to; + address other; + uint256 amount; + + // cache state + uint256 toBalanceBefore = balanceOf(to); + uint256 otherBalanceBefore = balanceOf(other); + uint256 totalSupplyBefore = totalSupply(); + + // run transaction + mint@withrevert(e, to, amount); + + // check outcome + if (lastReverted) { + assert to == 0 || totalSupplyBefore + amount > max_uint256; + } else { + // updates balance and totalSupply + assert to_mathint(balanceOf(to)) == toBalanceBefore + amount; + assert to_mathint(totalSupply()) == totalSupplyBefore + amount; + + // no other balance is modified + assert balanceOf(other) != otherBalanceBefore => other == to; + } +} + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Rules: burn behavior and side effects β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +rule burn(env e) { + requireInvariant totalSupplyIsSumOfBalances(); + require nonpayable(e); + + address from; + address other; + uint256 amount; + + // cache state + uint256 fromBalanceBefore = balanceOf(from); + uint256 otherBalanceBefore = balanceOf(other); + uint256 totalSupplyBefore = totalSupply(); + + // run transaction + burn@withrevert(e, from, amount); + + // check outcome + if (lastReverted) { + assert from == 0 || fromBalanceBefore < amount; + } else { + // updates balance and totalSupply + assert to_mathint(balanceOf(from)) == fromBalanceBefore - amount; + assert to_mathint(totalSupply()) == totalSupplyBefore - amount; + + // no other balance is modified + assert balanceOf(other) != otherBalanceBefore => other == from; + } +} + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Rule: transfer behavior and side effects β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +rule transfer(env e) { + requireInvariant totalSupplyIsSumOfBalances(); + require nonpayable(e); + + address holder = e.msg.sender; + address recipient; + address other; + uint256 amount; + + // cache state + uint256 holderBalanceBefore = balanceOf(holder); + uint256 recipientBalanceBefore = balanceOf(recipient); + uint256 otherBalanceBefore = balanceOf(other); + + // run transaction + transfer@withrevert(e, recipient, amount); + + // check outcome + if (lastReverted) { + assert holder == 0 || recipient == 0 || amount > holderBalanceBefore; + } else { + // balances of holder and recipient are updated + assert to_mathint(balanceOf(holder)) == holderBalanceBefore - (holder == recipient ? 0 : amount); + assert to_mathint(balanceOf(recipient)) == recipientBalanceBefore + (holder == recipient ? 0 : amount); + + // no other balance is modified + assert balanceOf(other) != otherBalanceBefore => (other == holder || other == recipient); + } +} + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Rule: transferFrom behavior and side effects β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +rule transferFrom(env e) { + requireInvariant totalSupplyIsSumOfBalances(); + require nonpayable(e); + + address spender = e.msg.sender; + address holder; + address recipient; + address other; + uint256 amount; + + // cache state + uint256 allowanceBefore = allowance(holder, spender); + uint256 holderBalanceBefore = balanceOf(holder); + uint256 recipientBalanceBefore = balanceOf(recipient); + uint256 otherBalanceBefore = balanceOf(other); + + // run transaction + transferFrom@withrevert(e, holder, recipient, amount); + + // check outcome + if (lastReverted) { + assert holder == 0 || recipient == 0 || spender == 0 || amount > holderBalanceBefore || amount > allowanceBefore; + } else { + // allowance is valid & updated + assert allowanceBefore >= amount; + assert to_mathint(allowance(holder, spender)) == (allowanceBefore == max_uint256 ? max_uint256 : allowanceBefore - amount); + + // balances of holder and recipient are updated + assert to_mathint(balanceOf(holder)) == holderBalanceBefore - (holder == recipient ? 0 : amount); + assert to_mathint(balanceOf(recipient)) == recipientBalanceBefore + (holder == recipient ? 0 : amount); + + // no other balance is modified + assert balanceOf(other) != otherBalanceBefore => (other == holder || other == recipient); + } +} + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Rule: approve behavior and side effects β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +rule approve(env e) { + require nonpayable(e); + + address holder = e.msg.sender; + address spender; + address otherHolder; + address otherSpender; + uint256 amount; + + // cache state + uint256 otherAllowanceBefore = allowance(otherHolder, otherSpender); + + // run transaction + approve@withrevert(e, spender, amount); + + // check outcome + if (lastReverted) { + assert holder == 0 || spender == 0; + } else { + // allowance is updated + assert allowance(holder, spender) == amount; + + // other allowances are untouched + assert allowance(otherHolder, otherSpender) != otherAllowanceBefore => (otherHolder == holder && otherSpender == spender); + } +} + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Rule: permit behavior and side effects β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +rule permit(env e) { + require nonpayable(e); + + address holder; + address spender; + uint256 amount; + uint256 deadline; + uint8 v; + bytes32 r; + bytes32 s; + + address account1; + address account2; + address account3; + + // cache state + uint256 nonceBefore = nonces(holder); + uint256 otherNonceBefore = nonces(account1); + uint256 otherAllowanceBefore = allowance(account2, account3); + + // sanity: nonce overflow, which possible in theory, is assumed to be impossible in practice + require nonceBefore < max_uint256; + require otherNonceBefore < max_uint256; + + // run transaction + permit@withrevert(e, holder, spender, amount, deadline, v, r, s); + + // check outcome + if (lastReverted) { + // Without formally checking the signature, we can't verify exactly the revert causes + assert true; + } else { + // allowance and nonce are updated + assert allowance(holder, spender) == amount; + assert to_mathint(nonces(holder)) == nonceBefore + 1; + + // deadline was respected + assert deadline >= e.block.timestamp; + + // no other allowance or nonce is modified + assert nonces(account1) != otherNonceBefore => account1 == holder; + assert allowance(account2, account3) != otherAllowanceBefore => (account2 == holder && account3 == spender); + } +} diff --git a/certora/specs/ERC20FlashMint.spec b/certora/specs/ERC20FlashMint.spec new file mode 100644 index 000000000..5c87f0ded --- /dev/null +++ b/certora/specs/ERC20FlashMint.spec @@ -0,0 +1,55 @@ +import "helpers/helpers.spec"; +import "methods/IERC20.spec"; +import "methods/IERC3156FlashLender.spec"; +import "methods/IERC3156FlashBorrower.spec"; + +methods { + // non standard ERC3156 functions + function flashFeeReceiver() external returns (address) envfree; + + // function summaries below + function _._update(address from, address to, uint256 amount) internal => specUpdate(from, to, amount) expect void ALL; +} + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Ghost: track mint and burns in the CVL β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +ghost mapping(address => mathint) trackedMintAmount; +ghost mapping(address => mathint) trackedBurnAmount; +ghost mapping(address => mapping(address => mathint)) trackedTransferedAmount; + +function specUpdate(address from, address to, uint256 amount) { + if (from == 0 && to == 0) { assert(false); } // defensive + + if (from == 0) { + trackedMintAmount[to] = amount; + } else if (to == 0) { + trackedBurnAmount[from] = amount; + } else { + trackedTransferedAmount[from][to] = amount; + } +} + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Rule: When doing a flashLoan, "amount" is minted and burnt, additionally, the fee is either burnt β”‚ +β”‚ (if the fee recipient is 0) or transferred (if the fee recipient is not 0) β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +rule checkMintAndBurn(env e) { + address receiver; + address token; + uint256 amount; + bytes data; + + uint256 fees = flashFee(token, amount); + address recipient = flashFeeReceiver(); + + flashLoan(e, receiver, token, amount, data); + + assert trackedMintAmount[receiver] == to_mathint(amount); + assert trackedBurnAmount[receiver] == amount + to_mathint(recipient == 0 ? fees : 0); + assert (fees > 0 && recipient != 0) => trackedTransferedAmount[receiver][recipient] == to_mathint(fees); +} diff --git a/certora/specs/ERC20Wrapper.spec b/certora/specs/ERC20Wrapper.spec new file mode 100644 index 000000000..04e67042a --- /dev/null +++ b/certora/specs/ERC20Wrapper.spec @@ -0,0 +1,198 @@ +import "helpers/helpers.spec"; +import "ERC20.spec"; + +methods { + function underlying() external returns(address) envfree; + function underlyingTotalSupply() external returns(uint256) envfree; + function underlyingBalanceOf(address) external returns(uint256) envfree; + function underlyingAllowanceToThis(address) external returns(uint256) envfree; + + function depositFor(address, uint256) external returns(bool); + function withdrawTo(address, uint256) external returns(bool); + function recover(address) external returns(uint256); +} + +use invariant totalSupplyIsSumOfBalances; + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Helper: consequence of `totalSupplyIsSumOfBalances` applied to underlying β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +definition underlyingBalancesLowerThanUnderlyingSupply(address a) returns bool = + underlyingBalanceOf(a) <= underlyingTotalSupply(); + +definition sumOfUnderlyingBalancesLowerThanUnderlyingSupply(address a, address b) returns bool = + a != b => underlyingBalanceOf(a) + underlyingBalanceOf(b) <= to_mathint(underlyingTotalSupply()); + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Invariant: wrapped token can't be undercollateralized (solvency of the wrapper) β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +invariant totalSupplyIsSmallerThanUnderlyingBalance() + totalSupply() <= underlyingBalanceOf(currentContract) && + underlyingBalanceOf(currentContract) <= underlyingTotalSupply() && + underlyingTotalSupply() <= max_uint256 + { + preserved { + requireInvariant totalSupplyIsSumOfBalances; + require underlyingBalancesLowerThanUnderlyingSupply(currentContract); + } + preserved depositFor(address account, uint256 amount) with (env e) { + require sumOfUnderlyingBalancesLowerThanUnderlyingSupply(e.msg.sender, currentContract); + } + } + +invariant noSelfWrap() + currentContract != underlying(); + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Rule: depositFor liveness and effects β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +rule depositFor(env e) { + require nonpayable(e); + + address sender = e.msg.sender; + address receiver; + address other; + uint256 amount; + + // sanity + requireInvariant noSelfWrap; + requireInvariant totalSupplyIsSumOfBalances; + requireInvariant totalSupplyIsSmallerThanUnderlyingBalance; + require sumOfUnderlyingBalancesLowerThanUnderlyingSupply(currentContract, sender); + + uint256 balanceBefore = balanceOf(receiver); + uint256 supplyBefore = totalSupply(); + uint256 senderUnderlyingBalanceBefore = underlyingBalanceOf(sender); + uint256 senderUnderlyingAllowanceBefore = underlyingAllowanceToThis(sender); + uint256 wrapperUnderlyingBalanceBefore = underlyingBalanceOf(currentContract); + uint256 underlyingSupplyBefore = underlyingTotalSupply(); + + uint256 otherBalanceBefore = balanceOf(other); + uint256 otherUnderlyingBalanceBefore = underlyingBalanceOf(other); + + depositFor@withrevert(e, receiver, amount); + bool success = !lastReverted; + + // liveness + assert success <=> ( + sender != currentContract && // invalid sender + sender != 0 && // invalid sender + receiver != currentContract && // invalid receiver + receiver != 0 && // invalid receiver + amount <= senderUnderlyingBalanceBefore && // deposit doesn't exceed balance + amount <= senderUnderlyingAllowanceBefore // deposit doesn't exceed allowance + ); + + // effects + assert success => ( + to_mathint(balanceOf(receiver)) == balanceBefore + amount && + to_mathint(totalSupply()) == supplyBefore + amount && + to_mathint(underlyingBalanceOf(currentContract)) == wrapperUnderlyingBalanceBefore + amount && + to_mathint(underlyingBalanceOf(sender)) == senderUnderlyingBalanceBefore - amount + ); + + // no side effect + assert underlyingTotalSupply() == underlyingSupplyBefore; + assert balanceOf(other) != otherBalanceBefore => other == receiver; + assert underlyingBalanceOf(other) != otherUnderlyingBalanceBefore => (other == sender || other == currentContract); +} + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Rule: withdrawTo liveness and effects β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +rule withdrawTo(env e) { + require nonpayable(e); + + address sender = e.msg.sender; + address receiver; + address other; + uint256 amount; + + // sanity + requireInvariant noSelfWrap; + requireInvariant totalSupplyIsSumOfBalances; + requireInvariant totalSupplyIsSmallerThanUnderlyingBalance; + require sumOfUnderlyingBalancesLowerThanUnderlyingSupply(currentContract, receiver); + + uint256 balanceBefore = balanceOf(sender); + uint256 supplyBefore = totalSupply(); + uint256 receiverUnderlyingBalanceBefore = underlyingBalanceOf(receiver); + uint256 wrapperUnderlyingBalanceBefore = underlyingBalanceOf(currentContract); + uint256 underlyingSupplyBefore = underlyingTotalSupply(); + + uint256 otherBalanceBefore = balanceOf(other); + uint256 otherUnderlyingBalanceBefore = underlyingBalanceOf(other); + + withdrawTo@withrevert(e, receiver, amount); + bool success = !lastReverted; + + // liveness + assert success <=> ( + sender != 0 && // invalid sender + receiver != currentContract && // invalid receiver + receiver != 0 && // invalid receiver + amount <= balanceBefore // withdraw doesn't exceed balance + ); + + // effects + assert success => ( + to_mathint(balanceOf(sender)) == balanceBefore - amount && + to_mathint(totalSupply()) == supplyBefore - amount && + to_mathint(underlyingBalanceOf(currentContract)) == wrapperUnderlyingBalanceBefore - (currentContract != receiver ? amount : 0) && + to_mathint(underlyingBalanceOf(receiver)) == receiverUnderlyingBalanceBefore + (currentContract != receiver ? amount : 0) + ); + + // no side effect + assert underlyingTotalSupply() == underlyingSupplyBefore; + assert balanceOf(other) != otherBalanceBefore => other == sender; + assert underlyingBalanceOf(other) != otherUnderlyingBalanceBefore => (other == receiver || other == currentContract); +} + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Rule: recover liveness and effects β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +rule recover(env e) { + require nonpayable(e); + + address receiver; + address other; + + // sanity + requireInvariant noSelfWrap; + requireInvariant totalSupplyIsSumOfBalances; + requireInvariant totalSupplyIsSmallerThanUnderlyingBalance; + + mathint value = underlyingBalanceOf(currentContract) - totalSupply(); + uint256 supplyBefore = totalSupply(); + uint256 balanceBefore = balanceOf(receiver); + + uint256 otherBalanceBefore = balanceOf(other); + uint256 otherUnderlyingBalanceBefore = underlyingBalanceOf(other); + + recover@withrevert(e, receiver); + bool success = !lastReverted; + + // liveness + assert success <=> receiver != 0; + + // effect + assert success => ( + to_mathint(balanceOf(receiver)) == balanceBefore + value && + to_mathint(totalSupply()) == supplyBefore + value && + totalSupply() == underlyingBalanceOf(currentContract) + ); + + // no side effect + assert underlyingBalanceOf(other) == otherUnderlyingBalanceBefore; + assert balanceOf(other) != otherBalanceBefore => other == receiver; +} diff --git a/certora/specs/ERC721.spec b/certora/specs/ERC721.spec new file mode 100644 index 000000000..bad4c4737 --- /dev/null +++ b/certora/specs/ERC721.spec @@ -0,0 +1,679 @@ +import "helpers/helpers.spec"; +import "methods/IERC721.spec"; +import "methods/IERC721Receiver.spec"; + +methods { + // exposed for FV + function mint(address,uint256) external; + function safeMint(address,uint256) external; + function safeMint(address,uint256,bytes) external; + function burn(uint256) external; + + function unsafeOwnerOf(uint256) external returns (address) envfree; + function unsafeGetApproved(uint256) external returns (address) envfree; +} + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Helpers β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ + +definition authSanity(env e) returns bool = e.msg.sender != 0; + +// Could be broken in theory, but not in practice +definition balanceLimited(address account) returns bool = balanceOf(account) < max_uint256; + +function helperTransferWithRevert(env e, method f, address from, address to, uint256 tokenId) { + if (f.selector == sig:transferFrom(address,address,uint256).selector) { + transferFrom@withrevert(e, from, to, tokenId); + } else if (f.selector == sig:safeTransferFrom(address,address,uint256).selector) { + safeTransferFrom@withrevert(e, from, to, tokenId); + } else if (f.selector == sig: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 == sig:mint(address,uint256).selector) { + mint@withrevert(e, to, tokenId); + } else if (f.selector == sig:safeMint(address,uint256).selector) { + safeMint@withrevert(e, to, tokenId); + } else if (f.selector == sig:safeMint(address,uint256,bytes).selector) { + bytes params; + require params.length < 0xffff; + safeMint@withrevert(e, to, tokenId, params); + } else { + require false; + } +} + +function helperSoundFnCall(env e, method f) { + if (f.selector == sig:mint(address,uint256).selector) { + address to; uint256 tokenId; + require balanceLimited(to); + requireInvariant notMintedUnset(tokenId); + mint(e, to, tokenId); + } else if (f.selector == sig:safeMint(address,uint256).selector) { + address to; uint256 tokenId; + require balanceLimited(to); + requireInvariant notMintedUnset(tokenId); + safeMint(e, to, tokenId); + } else if (f.selector == sig:safeMint(address,uint256,bytes).selector) { + address to; uint256 tokenId; bytes data; + require data.length < 0xffff; + require balanceLimited(to); + requireInvariant notMintedUnset(tokenId); + safeMint(e, to, tokenId, data); + } else if (f.selector == sig:burn(uint256).selector) { + uint256 tokenId; + requireInvariant ownerHasBalance(tokenId); + requireInvariant notMintedUnset(tokenId); + burn(e, tokenId); + } else if (f.selector == sig:transferFrom(address,address,uint256).selector) { + address from; address to; uint256 tokenId; + require balanceLimited(to); + requireInvariant ownerHasBalance(tokenId); + requireInvariant notMintedUnset(tokenId); + transferFrom(e, from, to, tokenId); + } else if (f.selector == sig:safeTransferFrom(address,address,uint256).selector) { + address from; address to; uint256 tokenId; + require balanceLimited(to); + requireInvariant ownerHasBalance(tokenId); + requireInvariant notMintedUnset(tokenId); + safeTransferFrom(e, from, to, tokenId); + } else if (f.selector == sig:safeTransferFrom(address,address,uint256,bytes).selector) { + address from; address to; uint256 tokenId; bytes data; + require data.length < 0xffff; + require balanceLimited(to); + requireInvariant ownerHasBalance(tokenId); + requireInvariant notMintedUnset(tokenId); + safeTransferFrom(e, from, to, tokenId, data); + } else { + calldataarg args; + f(e, args); + } +} + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Ghost & hooks: ownership count β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +ghost mathint _ownedTotal { + init_state axiom _ownedTotal == 0; +} + +ghost mapping(address => mathint) _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_mathint(newOwner != 0 ? 1 : 0); + _ownedByUser[oldOwner] = _ownedByUser[oldOwner] - to_mathint(oldOwner != 0 ? 1 : 0); + _ownedTotal = _ownedTotal + to_mathint(newOwner != 0 ? 1 : 0) - to_mathint(oldOwner != 0 ? 1 : 0); +} + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Ghost & hooks: sum of all balances β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +ghost mathint _supply { + init_state axiom _supply == 0; +} + +ghost mapping(address => mathint) _balances { + init_state axiom forall address a. _balances[a] == 0; +} + +hook Sstore _balances[KEY address addr] uint256 newValue (uint256 oldValue) STORAGE { + _supply = _supply - oldValue + newValue; +} + +// TODO: This used to not be necessary. We should try to remove it. In order to do so, we will probably need to add +// many "preserved" directive that require the "balanceOfConsistency" invariant on the accounts involved. +hook Sload uint256 value _balances[KEY address user] STORAGE { + require _balances[user] == to_mathint(value); +} + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Invariant: number of owned tokens is the sum of all balances β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +invariant ownedTotalIsSumOfBalances() + _ownedTotal == _supply + { + preserved mint(address to, uint256 tokenId) with (env e) { + require balanceLimited(to); + } + preserved safeMint(address to, uint256 tokenId) with (env e) { + require balanceLimited(to); + } + preserved safeMint(address to, uint256 tokenId, bytes data) with (env e) { + require balanceLimited(to); + } + preserved burn(uint256 tokenId) with (env e) { + requireInvariant ownerHasBalance(tokenId); + requireInvariant balanceOfConsistency(ownerOf(tokenId)); + } + preserved transferFrom(address from, address to, uint256 tokenId) with (env e) { + require balanceLimited(to); + requireInvariant ownerHasBalance(tokenId); + requireInvariant balanceOfConsistency(from); + requireInvariant balanceOfConsistency(to); + } + preserved safeTransferFrom(address from, address to, uint256 tokenId) with (env e) { + require balanceLimited(to); + requireInvariant ownerHasBalance(tokenId); + requireInvariant balanceOfConsistency(from); + requireInvariant balanceOfConsistency(to); + } + preserved safeTransferFrom(address from, address to, uint256 tokenId, bytes data) with (env e) { + require balanceLimited(to); + requireInvariant ownerHasBalance(tokenId); + requireInvariant balanceOfConsistency(from); + requireInvariant balanceOfConsistency(to); + } + } + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Invariant: balanceOf is the number of tokens owned β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +invariant balanceOfConsistency(address user) + to_mathint(balanceOf(user)) == _ownedByUser[user] && + to_mathint(balanceOf(user)) == _balances[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)); + } + } + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Rule: balance of address(0) is 0 β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +rule zeroAddressBalanceRevert() { + balanceOf@withrevert(0); + assert lastReverted; +} + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Invariant: address(0) has no authorized operator β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +invariant zeroAddressHasNoApprovedOperator(address a) + !isApprovedForAll(0, a) + { + preserved with (env e) { + require nonzerosender(e); + } + } + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Invariant: tokens that do not exist are not owned and not approved β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +invariant notMintedUnset(uint256 tokenId) + unsafeOwnerOf(tokenId) == 0 => unsafeGetApproved(tokenId) == 0; + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Rule: unsafeOwnerOf and unsafeGetApproved don't revert + ownerOf and getApproved revert if token does not exist β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +rule notMintedRevert(uint256 tokenId) { + requireInvariant notMintedUnset(tokenId); + + address _owner = unsafeOwnerOf@withrevert(tokenId); + assert !lastReverted; + + address _approved = unsafeGetApproved@withrevert(tokenId); + assert !lastReverted; + + address owner = ownerOf@withrevert(tokenId); + assert lastReverted <=> _owner == 0; + assert !lastReverted => _owner == owner; + + address approved = getApproved@withrevert(tokenId); + assert lastReverted <=> _owner == 0; + assert !lastReverted => _approved == approved; +} + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Rules: total supply can only change through mint and burn β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +rule supplyChange(env e) { + require nonzerosender(e); + requireInvariant zeroAddressHasNoApprovedOperator(e.msg.sender); + + mathint supplyBefore = _supply; + method f; helperSoundFnCall(e, f); + mathint supplyAfter = _supply; + + assert supplyAfter > supplyBefore => ( + supplyAfter == supplyBefore + 1 && + ( + f.selector == sig:mint(address,uint256).selector || + f.selector == sig:safeMint(address,uint256).selector || + f.selector == sig:safeMint(address,uint256,bytes).selector + ) + ); + assert supplyAfter < supplyBefore => ( + supplyAfter == supplyBefore - 1 && + f.selector == sig: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); + + mathint balanceBefore = balanceOf(account); + method f; helperSoundFnCall(e, f); + mathint 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 == sig:transferFrom(address,address,uint256).selector || + f.selector == sig:safeTransferFrom(address,address,uint256).selector || + f.selector == sig:safeTransferFrom(address,address,uint256,bytes).selector || + f.selector == sig:mint(address,uint256).selector || + f.selector == sig:safeMint(address,uint256).selector || + f.selector == sig:safeMint(address,uint256,bytes).selector || + f.selector == sig:burn(uint256).selector + ); +} + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Rules: ownership can only change through mint, burn or transfers. β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +rule ownershipChange(env e, uint256 tokenId) { + require nonzerosender(e); + requireInvariant zeroAddressHasNoApprovedOperator(e.msg.sender); + + address ownerBefore = unsafeOwnerOf(tokenId); + method f; helperSoundFnCall(e, f); + address ownerAfter = unsafeOwnerOf(tokenId); + + assert ownerBefore == 0 && ownerAfter != 0 => ( + f.selector == sig:mint(address,uint256).selector || + f.selector == sig:safeMint(address,uint256).selector || + f.selector == sig:safeMint(address,uint256,bytes).selector + ); + + assert ownerBefore != 0 && ownerAfter == 0 => ( + f.selector == sig:burn(uint256).selector + ); + + assert (ownerBefore != ownerAfter && ownerBefore != 0 && ownerAfter != 0) => ( + f.selector == sig:transferFrom(address,address,uint256).selector || + f.selector == sig:safeTransferFrom(address,address,uint256).selector || + f.selector == sig: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; helperSoundFnCall(e, f); + address approvalAfter = unsafeGetApproved(tokenId); + + // approve can set any value, other functions reset + assert approvalBefore != approvalAfter => ( + f.selector == sig:approve(address,uint256).selector || + ( + ( + f.selector == sig:transferFrom(address,address,uint256).selector || + f.selector == sig:safeTransferFrom(address,address,uint256).selector || + f.selector == sig:safeTransferFrom(address,address,uint256,bytes).selector || + f.selector == sig: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; helperSoundFnCall(e, f); + bool approvedForAllAfter = isApprovedForAll(owner, spender); + + assert approvedForAllBefore != approvedForAllAfter => f.selector == sig:setApprovalForAll(address,bool).selector; +} + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Rule: transferFrom behavior and side effects β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +rule transferFrom(env e, address from, address to, uint256 tokenId) { + require nonpayable(e); + require authSanity(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 => ( + to_mathint(balanceOf(from)) == balanceOfFromBefore - assert_uint256(from != to ? 1 : 0) && + to_mathint(balanceOf(to)) == balanceOfToBefore + assert_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 == sig:safeTransferFrom(address,address,uint256).selector || + f.selector == sig:safeTransferFrom(address,address,uint256,bytes).selector +} { + require nonpayable(e); + require authSanity(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 => ( + to_mathint(balanceOf(from)) == balanceOfFromBefore - assert_uint256(from != to ? 1: 0) && + to_mathint(balanceOf(to)) == balanceOfToBefore + assert_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); + + mathint supplyBefore = _supply; + 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 => ( + _supply == supplyBefore + 1 && + to_mathint(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 == sig:safeMint(address,uint256).selector || + f.selector == sig:safeMint(address,uint256,bytes).selector +} { + require nonpayable(e); + requireInvariant notMintedUnset(tokenId); + + uint256 otherTokenId; + address otherAccount; + + require balanceLimited(to); + + mathint supplyBefore = _supply; + 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 => ( + _supply == supplyBefore + 1 && + to_mathint(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); + + mathint supplyBefore = _supply; + 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 => ( + _supply == supplyBefore - 1 && + to_mathint(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); + require authSanity(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 == 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 <=> operator != 0; + + // effect + assert success => isApprovedForAll(owner, operator) == approved; + + // no side effect + assert isApprovedForAll(otherOwner, otherOperator) != otherIsApprovedForAllBefore => ( + otherOwner == owner && + otherOperator == operator + ); +} diff --git a/certora/specs/EnumerableMap.spec b/certora/specs/EnumerableMap.spec new file mode 100644 index 000000000..7b503031f --- /dev/null +++ b/certora/specs/EnumerableMap.spec @@ -0,0 +1,333 @@ +import "helpers/helpers.spec"; + +methods { + // library + function set(bytes32,bytes32) external returns (bool) envfree; + function remove(bytes32) external returns (bool) envfree; + function contains(bytes32) external returns (bool) envfree; + function length() external returns (uint256) envfree; + function key_at(uint256) external returns (bytes32) envfree; + function value_at(uint256) external returns (bytes32) envfree; + function tryGet_contains(bytes32) external returns (bool) envfree; + function tryGet_value(bytes32) external returns (bytes32) envfree; + function get(bytes32) external returns (bytes32) envfree; + + // FV + function _indexOf(bytes32) external returns (uint256) envfree; +} + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Helpers β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +definition sanity() returns bool = + 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) == to_bytes32(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(require_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, require_uint256(length() - 1)); + requireInvariant atUniqueness(index2, require_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() => to_mathint(_indexOf(key_at(index))) == index + 1 + { + preserved remove(bytes32 key) { + requireInvariant consistencyIndex(require_uint256(length() - 1)); + } + } + +invariant consistencyKey(bytes32 key) + contains(key) => ( + _indexOf(key) > 0 && + _indexOf(key) <= length() && + key_at(require_uint256(_indexOf(key) - 1)) == key + ) + { + preserved remove(bytes32 otherKey) { + requireInvariant consistencyKey(otherKey); + requireInvariant atUniqueness( + require_uint256(_indexOf(key) - 1), + require_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 == sig:set(bytes32,bytes32).selector && to_mathint(lengthAfter) == lengthBefore + 1) || + (f.selector == sig:remove(bytes32).selector && to_mathint(lengthAfter) == lengthBefore - 1) + ); + + assert containsBefore != containsAfter => ( + (f.selector == sig:set(bytes32,bytes32).selector && containsAfter) || + (f.selector == sig:remove(bytes32).selector && !containsAfter) + ); + + assert valueBefore != valueAfter => ( + (f.selector == sig:set(bytes32,bytes32).selector && containsAfter) || + (f.selector == sig:remove(bytes32).selector && !containsAfter && valueAfter == to_bytes32(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 == to_bytes32(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 to_mathint(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 to_mathint(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 = require_uint256(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..3db515838 --- /dev/null +++ b/certora/specs/EnumerableSet.spec @@ -0,0 +1,246 @@ +import "helpers/helpers.spec"; + +methods { + // library + function add(bytes32) external returns (bool) envfree; + function remove(bytes32) external returns (bool) envfree; + function contains(bytes32) external returns (bool) envfree; + function length() external returns (uint256) envfree; + function at_(uint256) external returns (bytes32) envfree; + + // FV + function _indexOf(bytes32) external returns (uint256) envfree; +} + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Helpers β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +definition sanity() returns bool = + length() < max_uint256; + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Invariant: All indexed keys are contained β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +invariant indexedContained(uint256 index) + index < length() => contains(at_(index)) + { + preserved { + requireInvariant consistencyIndex(index); + requireInvariant consistencyIndex(require_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, require_uint256(length() - 1)); + requireInvariant atUniqueness(index2, require_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)) == require_uint256(index + 1) + { + preserved remove(bytes32 key) { + requireInvariant consistencyIndex(require_uint256(length() - 1)); + } + } + +invariant consistencyKey(bytes32 key) + contains(key) => ( + _indexOf(key) > 0 && + _indexOf(key) <= length() && + at_(require_uint256(_indexOf(key) - 1)) == key + ) + { + preserved remove(bytes32 otherKey) { + requireInvariant consistencyKey(otherKey); + requireInvariant atUniqueness( + require_uint256(_indexOf(key) - 1), + require_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 == sig:add(bytes32).selector && lengthAfter == require_uint256(lengthBefore + 1)) || + (f.selector == sig:remove(bytes32).selector && lengthAfter == require_uint256(lengthBefore - 1)) + ); + + assert containsBefore != containsAfter => ( + (f.selector == sig:add(bytes32).selector && containsAfter) || + (f.selector == sig: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() == require_uint256(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() == require_uint256(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 = require_uint256(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 + ); +} diff --git a/certora/specs/Initializable.spec b/certora/specs/Initializable.spec new file mode 100644 index 000000000..07c2930c2 --- /dev/null +++ b/certora/specs/Initializable.spec @@ -0,0 +1,165 @@ +import "helpers/helpers.spec"; + +methods { + // initialize, reinitialize, disable + function initialize() external envfree; + function reinitialize(uint64) external envfree; + function disable() external envfree; + + function nested_init_init() external envfree; + function nested_init_reinit(uint64) external envfree; + function nested_reinit_init(uint64) external envfree; + function nested_reinit_reinit(uint64,uint64) external envfree; + + // view + function version() external returns uint64 envfree; + function initializing() external returns bool envfree; +} + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Definitions β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +definition isUninitialized() returns bool = version() == 0; +definition isInitialized() returns bool = version() > 0; +definition isDisabled() returns bool = version() == max_uint64; + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Invariant: A contract must only ever be in an initializing state while in the middle of a transaction execution. β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +invariant notInitializing() + !initializing(); + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Rule: The version cannot decrease & disable state is irrevocable. β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +rule increasingVersion(env e) { + uint64 versionBefore = version(); + bool disabledBefore = isDisabled(); + + method f; calldataarg args; + f(e, args); + + assert versionBefore <= version(), "_initialized must only increase"; + assert disabledBefore => isDisabled(), "a disabled initializer must stay disabled"; +} + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Rule: Cannot initialize a contract that is already initialized. β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +rule cannotInitializeTwice() { + require isInitialized(); + + initialize@withrevert(); + + assert lastReverted, "contract must only be initialized once"; +} + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Rule: Cannot initialize once disabled. β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +rule cannotInitializeOnceDisabled() { + require isDisabled(); + + initialize@withrevert(); + + assert lastReverted, "contract is disabled"; +} + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Rule: Cannot reinitialize once disabled. β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +rule cannotReinitializeOnceDisabled() { + require isDisabled(); + + uint64 n; + reinitialize@withrevert(n); + + assert lastReverted, "contract is disabled"; +} + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Rule: Cannot nest initializers (after construction). β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +rule cannotNestInitializers_init_init() { + nested_init_init@withrevert(); + assert lastReverted, "nested initializers"; +} + +rule cannotNestInitializers_init_reinit(uint64 m) { + nested_init_reinit@withrevert(m); + assert lastReverted, "nested initializers"; +} + +rule cannotNestInitializers_reinit_init(uint64 n) { + nested_reinit_init@withrevert(n); + assert lastReverted, "nested initializers"; +} + +rule cannotNestInitializers_reinit_reinit(uint64 n, uint64 m) { + nested_reinit_reinit@withrevert(n, m); + assert lastReverted, "nested initializers"; +} + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Rule: Initialize correctly sets the version. β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +rule initializeEffects() { + requireInvariant notInitializing(); + + bool isUninitializedBefore = isUninitialized(); + + initialize@withrevert(); + bool success = !lastReverted; + + assert success <=> isUninitializedBefore, "can only initialize uninitialized contracts"; + assert success => version() == 1, "initialize must set version() to 1"; +} + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Rule: Reinitialize correctly sets the version. β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +rule reinitializeEffects() { + requireInvariant notInitializing(); + + uint64 versionBefore = version(); + + uint64 n; + reinitialize@withrevert(n); + bool success = !lastReverted; + + assert success <=> versionBefore < n, "can only reinitialize to a latter versions"; + assert success => version() == n, "reinitialize must set version() to n"; +} + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Rule: Can disable. β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +rule disableEffect() { + requireInvariant notInitializing(); + + disable@withrevert(); + bool success = !lastReverted; + + assert success, "call to _disableInitializers failed"; + assert isDisabled(), "disable state not set"; +} diff --git a/certora/specs/Ownable.spec b/certora/specs/Ownable.spec new file mode 100644 index 000000000..0d50813cf --- /dev/null +++ b/certora/specs/Ownable.spec @@ -0,0 +1,77 @@ +import "helpers/helpers.spec"; +import "methods/IOwnable.spec"; + +methods { + function restricted() external; +} + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Function correctness: transferOwnership changes ownership β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +rule transferOwnership(env e) { + require nonpayable(e); + + address newOwner; + address current = owner(); + + transferOwnership@withrevert(e, newOwner); + bool success = !lastReverted; + + assert success <=> (e.msg.sender == current && newOwner != 0), "unauthorized caller or invalid arg"; + assert success => owner() == newOwner, "current owner changed"; +} + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Function correctness: renounceOwnership removes the owner β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +rule renounceOwnership(env e) { + require nonpayable(e); + + address current = owner(); + + renounceOwnership@withrevert(e); + bool success = !lastReverted; + + assert success <=> e.msg.sender == current, "unauthorized caller"; + assert success => owner() == 0, "owner not cleared"; +} + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Access control: only current owner can call restricted functions β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +rule onlyCurrentOwnerCanCallOnlyOwner(env e) { + require nonpayable(e); + + address current = owner(); + + calldataarg args; + restricted@withrevert(e, args); + + assert !lastReverted <=> e.msg.sender == current, "access control failed"; +} + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Rule: ownership can only change in specific ways β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +rule onlyOwnerOrPendingOwnerCanChangeOwnership(env e) { + address oldCurrent = owner(); + + method f; calldataarg args; + f(e, args); + + address newCurrent = owner(); + + // If owner changes, must be either transferOwnership or renounceOwnership + assert oldCurrent != newCurrent => ( + (e.msg.sender == oldCurrent && newCurrent != 0 && f.selector == sig:transferOwnership(address).selector) || + (e.msg.sender == oldCurrent && newCurrent == 0 && f.selector == sig:renounceOwnership().selector) + ); +} diff --git a/certora/specs/Ownable2Step.spec b/certora/specs/Ownable2Step.spec new file mode 100644 index 000000000..d13c6d3e6 --- /dev/null +++ b/certora/specs/Ownable2Step.spec @@ -0,0 +1,108 @@ +import "helpers/helpers.spec"; +import "methods/IOwnable2Step.spec"; + +methods { + function restricted() external; +} + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Function correctness: transferOwnership sets the pending owner β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +rule transferOwnership(env e) { + require nonpayable(e); + + address newOwner; + address current = owner(); + + transferOwnership@withrevert(e, newOwner); + bool success = !lastReverted; + + assert success <=> e.msg.sender == current, "unauthorized caller"; + assert success => pendingOwner() == newOwner, "pending owner not set"; + assert success => owner() == current, "current owner changed"; +} + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Function correctness: renounceOwnership removes the owner and the pendingOwner β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +rule renounceOwnership(env e) { + require nonpayable(e); + + address current = owner(); + + renounceOwnership@withrevert(e); + bool success = !lastReverted; + + assert success <=> e.msg.sender == current, "unauthorized caller"; + assert success => pendingOwner() == 0, "pending owner not cleared"; + assert success => owner() == 0, "owner not cleared"; +} + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Function correctness: acceptOwnership changes owner and reset pending owner β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +rule acceptOwnership(env e) { + + require nonpayable(e); + + address current = owner(); + address pending = pendingOwner(); + + acceptOwnership@withrevert(e); + bool success = !lastReverted; + + assert success <=> e.msg.sender == pending, "unauthorized caller"; + assert success => pendingOwner() == 0, "pending owner not cleared"; + assert success => owner() == pending, "owner not transferred"; +} + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Access control: only current owner can call restricted functions β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +rule onlyCurrentOwnerCanCallOnlyOwner(env e) { + require nonpayable(e); + + address current = owner(); + + calldataarg args; + restricted@withrevert(e, args); + + assert !lastReverted <=> e.msg.sender == current, "access control failed"; +} + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Rule: ownership and pending ownership can only change in specific ways β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +rule ownerOrPendingOwnerChange(env e, method f) { + address oldCurrent = owner(); + address oldPending = pendingOwner(); + + calldataarg args; + f(e, args); + + address newCurrent = owner(); + address newPending = pendingOwner(); + + // If owner changes, must be either acceptOwnership or renounceOwnership + assert oldCurrent != newCurrent => ( + (e.msg.sender == oldPending && newCurrent == oldPending && newPending == 0 && f.selector == sig:acceptOwnership().selector) || + (e.msg.sender == oldCurrent && newCurrent == 0 && newPending == 0 && f.selector == sig:renounceOwnership().selector) + ); + + // If pending changes, must be either acceptance or reset + assert oldPending != newPending => ( + (e.msg.sender == oldCurrent && newCurrent == oldCurrent && f.selector == sig:transferOwnership(address).selector) || + (e.msg.sender == oldPending && newCurrent == oldPending && newPending == 0 && f.selector == sig:acceptOwnership().selector) || + (e.msg.sender == oldCurrent && newCurrent == 0 && newPending == 0 && f.selector == sig:renounceOwnership().selector) + ); +} diff --git a/certora/specs/Pausable.spec b/certora/specs/Pausable.spec new file mode 100644 index 000000000..a7aff9cc1 --- /dev/null +++ b/certora/specs/Pausable.spec @@ -0,0 +1,96 @@ +import "helpers/helpers.spec"; + +methods { + function paused() external returns (bool) envfree; + function pause() external; + function unpause() external; + function onlyWhenPaused() external; + function onlyWhenNotPaused() external; +} + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Function correctness: _pause pauses the contract β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +rule pause(env e) { + require nonpayable(e); + + bool pausedBefore = paused(); + + pause@withrevert(e); + bool success = !lastReverted; + + bool pausedAfter = paused(); + + // liveness + assert success <=> !pausedBefore, "works if and only if the contract was not paused before"; + + // effect + assert success => pausedAfter, "contract must be paused after a successful call"; +} + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Function correctness: _unpause unpauses the contract β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +rule unpause(env e) { + require nonpayable(e); + + bool pausedBefore = paused(); + + unpause@withrevert(e); + bool success = !lastReverted; + + bool pausedAfter = paused(); + + // liveness + assert success <=> pausedBefore, "works if and only if the contract was paused before"; + + // effect + assert success => !pausedAfter, "contract must be unpaused after a successful call"; +} + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Function correctness: whenPaused modifier can only be called if the contract is paused β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +rule whenPaused(env e) { + require nonpayable(e); + + onlyWhenPaused@withrevert(e); + assert !lastReverted <=> paused(), "works if and only if the contract is paused"; +} + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Function correctness: whenNotPaused modifier can only be called if the contract is not paused β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +rule whenNotPaused(env e) { + require nonpayable(e); + + onlyWhenNotPaused@withrevert(e); + assert !lastReverted <=> !paused(), "works if and only if the contract is not paused"; +} + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Rules: only _pause and _unpause can change paused status β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +rule noPauseChange(env e) { + method f; + calldataarg args; + + bool pausedBefore = paused(); + f(e, args); + bool pausedAfter = paused(); + + assert pausedBefore != pausedAfter => ( + (!pausedAfter && f.selector == sig:unpause().selector) || + (pausedAfter && f.selector == sig:pause().selector) + ), "contract's paused status can only be changed by _pause() or _unpause()"; +} diff --git a/certora/specs/TimelockController.spec b/certora/specs/TimelockController.spec new file mode 100644 index 000000000..5123768da --- /dev/null +++ b/certora/specs/TimelockController.spec @@ -0,0 +1,274 @@ +import "helpers/helpers.spec"; +import "methods/IAccessControl.spec"; + +methods { + function PROPOSER_ROLE() external returns (bytes32) envfree; + function EXECUTOR_ROLE() external returns (bytes32) envfree; + function CANCELLER_ROLE() external returns (bytes32) envfree; + function isOperation(bytes32) external returns (bool); + function isOperationPending(bytes32) external returns (bool); + function isOperationReady(bytes32) external returns (bool); + function isOperationDone(bytes32) external returns (bool); + function getTimestamp(bytes32) external returns (uint256) envfree; + function getMinDelay() external returns (uint256) envfree; + + function hashOperation(address, uint256, bytes, bytes32, bytes32) external returns(bytes32) envfree; + function hashOperationBatch(address[], uint256[], bytes[], bytes32, bytes32) external returns(bytes32) envfree; + + function schedule(address, uint256, bytes, bytes32, bytes32, uint256) external; + function scheduleBatch(address[], uint256[], bytes[], bytes32, bytes32, uint256) external; + function execute(address, uint256, bytes, bytes32, bytes32) external; + function executeBatch(address[], uint256[], bytes[], bytes32, bytes32) external; + function cancel(bytes32) external; + + function updateDelay(uint256) external; +} + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Helpers β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +// Uniformly handle scheduling of batched and non-batched operations. +function helperScheduleWithRevert(env e, method f, bytes32 id, uint256 delay) { + if (f.selector == sig:schedule(address, uint256, bytes, bytes32, bytes32, uint256).selector) { + address target; uint256 value; bytes data; bytes32 predecessor; bytes32 salt; + require hashOperation(target, value, data, predecessor, salt) == id; // Correlation + schedule@withrevert(e, target, value, data, predecessor, salt, delay); + } else if (f.selector == sig:scheduleBatch(address[], uint256[], bytes[], bytes32, bytes32, uint256).selector) { + address[] targets; uint256[] values; bytes[] payloads; bytes32 predecessor; bytes32 salt; + require hashOperationBatch(targets, values, payloads, predecessor, salt) == id; // Correlation + scheduleBatch@withrevert(e, targets, values, payloads, predecessor, salt, delay); + } else { + calldataarg args; + f@withrevert(e, args); + } +} + +// Uniformly handle execution of batched and non-batched operations. +function helperExecuteWithRevert(env e, method f, bytes32 id, bytes32 predecessor) { + if (f.selector == sig:execute(address, uint256, bytes, bytes32, bytes32).selector) { + address target; uint256 value; bytes data; bytes32 salt; + require hashOperation(target, value, data, predecessor, salt) == id; // Correlation + execute@withrevert(e, target, value, data, predecessor, salt); + } else if (f.selector == sig:executeBatch(address[], uint256[], bytes[], bytes32, bytes32).selector) { + address[] targets; uint256[] values; bytes[] payloads; bytes32 salt; + require hashOperationBatch(targets, values, payloads, predecessor, salt) == id; // Correlation + executeBatch@withrevert(e, targets, values, payloads, predecessor, salt); + } else { + calldataarg args; + f@withrevert(e, args); + } +} + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Definitions β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +definition DONE_TIMESTAMP() returns uint256 = 1; +definition UNSET() returns uint8 = 0x1; +definition PENDING() returns uint8 = 0x2; +definition DONE() returns uint8 = 0x4; + +definition isUnset(env e, bytes32 id) returns bool = !isOperation(e, id); +definition isPending(env e, bytes32 id) returns bool = isOperationPending(e, id); +definition isDone(env e, bytes32 id) returns bool = isOperationDone(e, id); +definition state(env e, bytes32 id) returns uint8 = (isUnset(e, id) ? UNSET() : 0) | (isPending(e, id) ? PENDING() : 0) | (isDone(e, id) ? DONE() : 0); + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Invariants: consistency of accessors β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +invariant isOperationCheck(env e, bytes32 id) + isOperation(e, id) <=> getTimestamp(id) > 0 + filtered { f -> !f.isView } + +invariant isOperationPendingCheck(env e, bytes32 id) + isOperationPending(e, id) <=> getTimestamp(id) > DONE_TIMESTAMP() + filtered { f -> !f.isView } + +invariant isOperationDoneCheck(env e, bytes32 id) + isOperationDone(e, id) <=> getTimestamp(id) == DONE_TIMESTAMP() + filtered { f -> !f.isView } + +invariant isOperationReadyCheck(env e, bytes32 id) + isOperationReady(e, id) <=> (isOperationPending(e, id) && getTimestamp(id) <= e.block.timestamp) + filtered { f -> !f.isView } + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Invariant: a proposal id is either unset, pending or done β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +invariant stateConsistency(bytes32 id, env e) + // Check states are mutually exclusive + (isUnset(e, id) <=> (!isPending(e, id) && !isDone(e, id) )) && + (isPending(e, id) <=> (!isUnset(e, id) && !isDone(e, id) )) && + (isDone(e, id) <=> (!isUnset(e, id) && !isPending(e, id))) && + // Check that the state helper behaves as expected: + (isUnset(e, id) <=> state(e, id) == UNSET() ) && + (isPending(e, id) <=> state(e, id) == PENDING() ) && + (isDone(e, id) <=> state(e, id) == DONE() ) && + // Check substate + isOperationReady(e, id) => isPending(e, id) + filtered { f -> !f.isView } + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Rule: state transition rules β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +rule stateTransition(bytes32 id, env e, method f, calldataarg args) { + require e.block.timestamp > 1; // Sanity + + uint8 stateBefore = state(e, id); + f(e, args); + uint8 stateAfter = state(e, id); + + // Cannot jump from UNSET to DONE + assert stateBefore == UNSET() => stateAfter != DONE(); + + // UNSET β†’ PENDING: schedule or scheduleBatch + assert stateBefore == UNSET() && stateAfter == PENDING() => ( + f.selector == sig:schedule(address, uint256, bytes, bytes32, bytes32, uint256).selector || + f.selector == sig:scheduleBatch(address[], uint256[], bytes[], bytes32, bytes32, uint256).selector + ); + + // PENDING β†’ UNSET: cancel + assert stateBefore == PENDING() && stateAfter == UNSET() => ( + f.selector == sig:cancel(bytes32).selector + ); + + // PENDING β†’ DONE: execute or executeBatch + assert stateBefore == PENDING() && stateAfter == DONE() => ( + f.selector == sig:execute(address, uint256, bytes, bytes32, bytes32).selector || + f.selector == sig:executeBatch(address[], uint256[], bytes[], bytes32, bytes32).selector + ); + + // DONE is final + assert stateBefore == DONE() => stateAfter == DONE(); +} + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Rule: minimum delay can only be updated through a timelock execution β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +rule minDelayOnlyChange(env e) { + uint256 delayBefore = getMinDelay(); + + method f; calldataarg args; + f(e, args); + + assert delayBefore != getMinDelay() => (e.msg.sender == currentContract && f.selector == sig:updateDelay(uint256).selector), "Unauthorized delay update"; +} + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Rule: schedule liveness and effects β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +rule schedule(env e, method f, bytes32 id, uint256 delay) filtered { f -> + f.selector == sig:schedule(address, uint256, bytes, bytes32, bytes32, uint256).selector || + f.selector == sig:scheduleBatch(address[], uint256[], bytes[], bytes32, bytes32, uint256).selector +} { + require nonpayable(e); + + // Basic timestamp assumptions + require e.block.timestamp > 1; + require e.block.timestamp + delay < max_uint256; + require e.block.timestamp + getMinDelay() < max_uint256; + + bytes32 otherId; uint256 otherTimestamp = getTimestamp(otherId); + + uint8 stateBefore = state(e, id); + bool isDelaySufficient = delay >= getMinDelay(); + bool isProposerBefore = hasRole(PROPOSER_ROLE(), e.msg.sender); + + helperScheduleWithRevert(e, f, id, delay); + bool success = !lastReverted; + + // liveness + assert success <=> ( + stateBefore == UNSET() && + isDelaySufficient && + isProposerBefore + ); + + // effect + assert success => state(e, id) == PENDING(), "State transition violation"; + assert success => getTimestamp(id) == require_uint256(e.block.timestamp + delay), "Proposal timestamp not correctly set"; + + // no side effect + assert otherTimestamp != getTimestamp(otherId) => id == otherId, "Other proposal affected"; +} + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Rule: execute liveness and effects β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +rule execute(env e, method f, bytes32 id, bytes32 predecessor) filtered { f -> + f.selector == sig:execute(address, uint256, bytes, bytes32, bytes32).selector || + f.selector == sig:executeBatch(address[], uint256[], bytes[], bytes32, bytes32).selector +} { + bytes32 otherId; uint256 otherTimestamp = getTimestamp(otherId); + + uint8 stateBefore = state(e, id); + bool isOperationReadyBefore = isOperationReady(e, id); + bool isExecutorOrOpen = hasRole(EXECUTOR_ROLE(), e.msg.sender) || hasRole(EXECUTOR_ROLE(), 0); + bool predecessorDependency = predecessor == to_bytes32(0) || isDone(e, predecessor); + + helperExecuteWithRevert(e, f, id, predecessor); + bool success = !lastReverted; + + // The underlying transaction can revert, and that would cause the execution to revert. We can check that all non + // reverting calls meet the requirements in terms of proposal readiness, access control and predecessor dependency. + // We can't however guarantee that these requirements being meet ensure liveness of the proposal, because the + // proposal can revert for reasons beyond our control. + + // liveness, should be `<=>` but can only check `=>` (see comment above) + assert success => ( + stateBefore == PENDING() && + isOperationReadyBefore && + predecessorDependency && + isExecutorOrOpen + ); + + // effect + assert success => state(e, id) == DONE(), "State transition violation"; + + // no side effect + assert otherTimestamp != getTimestamp(otherId) => id == otherId, "Other proposal affected"; +} + +/* +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Rule: cancel liveness and effects β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +*/ +rule cancel(env e, bytes32 id) { + require nonpayable(e); + + bytes32 otherId; uint256 otherTimestamp = getTimestamp(otherId); + + uint8 stateBefore = state(e, id); + bool isCanceller = hasRole(CANCELLER_ROLE(), e.msg.sender); + + cancel@withrevert(e, id); + bool success = !lastReverted; + + // liveness + assert success <=> ( + stateBefore == PENDING() && + isCanceller + ); + + // effect + assert success => state(e, id) == UNSET(), "State transition violation"; + + // no side effect + assert otherTimestamp != getTimestamp(otherId) => id == otherId, "Other proposal affected"; +} diff --git a/certora/specs/helpers/helpers.spec b/certora/specs/helpers/helpers.spec new file mode 100644 index 000000000..a6c1e2302 --- /dev/null +++ b/certora/specs/helpers/helpers.spec @@ -0,0 +1,7 @@ +// environment +definition nonpayable(env e) returns bool = e.msg.value == 0; +definition nonzerosender(env e) returns bool = e.msg.sender != 0; + +// 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/certora/specs/methods/IAccessControl.spec b/certora/specs/methods/IAccessControl.spec new file mode 100644 index 000000000..5c395b088 --- /dev/null +++ b/certora/specs/methods/IAccessControl.spec @@ -0,0 +1,8 @@ +methods { + function DEFAULT_ADMIN_ROLE() external returns (bytes32) envfree; + function hasRole(bytes32, address) external returns(bool) envfree; + function getRoleAdmin(bytes32) external returns(bytes32) envfree; + function grantRole(bytes32, address) external; + function revokeRole(bytes32, address) external; + function renounceRole(bytes32, address) external; +} diff --git a/certora/specs/methods/IAccessControlDefaultAdminRules.spec b/certora/specs/methods/IAccessControlDefaultAdminRules.spec new file mode 100644 index 000000000..d02db180d --- /dev/null +++ b/certora/specs/methods/IAccessControlDefaultAdminRules.spec @@ -0,0 +1,36 @@ +import "./IERC5313.spec"; + +methods { + // === View == + + // Default Admin + function defaultAdmin() external returns(address) envfree; + function pendingDefaultAdmin() external returns(address, uint48) envfree; + + // Default Admin Delay + function defaultAdminDelay() external returns(uint48); + function pendingDefaultAdminDelay() external returns(uint48, uint48); + function defaultAdminDelayIncreaseWait() external returns(uint48) envfree; + + // === Mutations == + + // Default Admin + function beginDefaultAdminTransfer(address) external; + function cancelDefaultAdminTransfer() external; + function acceptDefaultAdminTransfer() external; + + // Default Admin Delay + function changeDefaultAdminDelay(uint48) external; + function rollbackDefaultAdminDelay() external; + + // == FV == + + // Default Admin + function pendingDefaultAdmin_() external returns (address) envfree; + function pendingDefaultAdminSchedule_() external returns (uint48) envfree; + + // Default Admin Delay + function pendingDelay_() external returns (uint48); + function pendingDelaySchedule_() external returns (uint48); + function delayChangeWait_(uint48) external returns (uint48); +} diff --git a/certora/specs/methods/IERC20.spec b/certora/specs/methods/IERC20.spec new file mode 100644 index 000000000..100901a04 --- /dev/null +++ b/certora/specs/methods/IERC20.spec @@ -0,0 +1,11 @@ +methods { + function name() external returns (string) envfree; + function symbol() external returns (string) envfree; + function decimals() external returns (uint8) envfree; + function totalSupply() external returns (uint256) envfree; + function balanceOf(address) external returns (uint256) envfree; + function allowance(address,address) external returns (uint256) envfree; + function approve(address,uint256) external returns (bool); + function transfer(address,uint256) external returns (bool); + function transferFrom(address,address,uint256) external returns (bool); +} diff --git a/certora/specs/methods/IERC2612.spec b/certora/specs/methods/IERC2612.spec new file mode 100644 index 000000000..4ecc17b49 --- /dev/null +++ b/certora/specs/methods/IERC2612.spec @@ -0,0 +1,5 @@ +methods { + function permit(address,address,uint256,uint256,uint8,bytes32,bytes32) external; + function nonces(address) external returns (uint256) envfree; + function DOMAIN_SEPARATOR() external returns (bytes32) envfree; +} diff --git a/certora/specs/methods/IERC3156FlashBorrower.spec b/certora/specs/methods/IERC3156FlashBorrower.spec new file mode 100644 index 000000000..733c168c7 --- /dev/null +++ b/certora/specs/methods/IERC3156FlashBorrower.spec @@ -0,0 +1,3 @@ +methods { + function _.onFlashLoan(address,address,uint256,uint256,bytes) external => DISPATCHER(true); +} diff --git a/certora/specs/methods/IERC3156FlashLender.spec b/certora/specs/methods/IERC3156FlashLender.spec new file mode 100644 index 000000000..66ed14cd1 --- /dev/null +++ b/certora/specs/methods/IERC3156FlashLender.spec @@ -0,0 +1,5 @@ +methods { + function maxFlashLoan(address) external returns (uint256) envfree; + function flashFee(address,uint256) external returns (uint256) envfree; + function flashLoan(address,address,uint256,bytes) external returns (bool); +} diff --git a/certora/specs/methods/IERC5313.spec b/certora/specs/methods/IERC5313.spec new file mode 100644 index 000000000..f1d469faf --- /dev/null +++ b/certora/specs/methods/IERC5313.spec @@ -0,0 +1,3 @@ +methods { + function owner() external returns (address) envfree; +} diff --git a/certora/specs/methods/IERC721.spec b/certora/specs/methods/IERC721.spec new file mode 100644 index 000000000..34ff50bd1 --- /dev/null +++ b/certora/specs/methods/IERC721.spec @@ -0,0 +1,17 @@ +methods { + // IERC721 + function balanceOf(address) external returns (uint256) envfree; + function ownerOf(uint256) external returns (address) envfree; + function getApproved(uint256) external returns (address) envfree; + function isApprovedForAll(address,address) external returns (bool) envfree; + function safeTransferFrom(address,address,uint256,bytes) external; + function safeTransferFrom(address,address,uint256) external; + function transferFrom(address,address,uint256) external; + function approve(address,uint256) external; + function setApprovalForAll(address,bool) external; + + // IERC721Metadata + function name() external returns (string); + function symbol() external returns (string); + function tokenURI(uint256) external returns (string); +} diff --git a/certora/specs/methods/IERC721Receiver.spec b/certora/specs/methods/IERC721Receiver.spec new file mode 100644 index 000000000..e6bdf4283 --- /dev/null +++ b/certora/specs/methods/IERC721Receiver.spec @@ -0,0 +1,3 @@ +methods { + function _.onERC721Received(address,address,uint256,bytes) external => DISPATCHER(true); +} diff --git a/certora/specs/methods/IOwnable.spec b/certora/specs/methods/IOwnable.spec new file mode 100644 index 000000000..4d7c925c5 --- /dev/null +++ b/certora/specs/methods/IOwnable.spec @@ -0,0 +1,5 @@ +methods { + function owner() external returns (address) envfree; + function transferOwnership(address) external; + function renounceOwnership() external; +} diff --git a/certora/specs/methods/IOwnable2Step.spec b/certora/specs/methods/IOwnable2Step.spec new file mode 100644 index 000000000..e6a99570a --- /dev/null +++ b/certora/specs/methods/IOwnable2Step.spec @@ -0,0 +1,7 @@ +methods { + function owner() external returns (address) envfree; + function pendingOwner() external returns (address) envfree; + function transferOwnership(address) external; + function acceptOwnership() external; + function renounceOwnership() external; +} diff --git a/contracts/access/AccessControl.sol b/contracts/access/AccessControl.sol new file mode 100644 index 000000000..47ce7d863 --- /dev/null +++ b/contracts/access/AccessControl.sol @@ -0,0 +1,209 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (access/AccessControl.sol) + +pragma solidity ^0.8.20; + +import {IAccessControl} from "./IAccessControl.sol"; +import {Context} from "../utils/Context.sol"; +import {ERC165} from "../utils/introspection/ERC165.sol"; + +/** + * @dev Contract module that allows children to implement role-based access + * control mechanisms. This is a lightweight version that doesn't allow enumerating role + * members except through off-chain means by accessing the contract event logs. Some + * applications may benefit from on-chain enumerability, for those cases see + * {AccessControlEnumerable}. + * + * Roles are referred to by their `bytes32` identifier. These should be exposed + * in the external API and be unique. The best way to achieve this is by + * using `public constant` hash digests: + * + * ```solidity + * bytes32 public constant MY_ROLE = keccak256("MY_ROLE"); + * ``` + * + * Roles can be used to represent a set of permissions. To restrict access to a + * function call, use {hasRole}: + * + * ```solidity + * function foo() public { + * require(hasRole(MY_ROLE, msg.sender)); + * ... + * } + * ``` + * + * Roles can be granted and revoked dynamically via the {grantRole} and + * {revokeRole} functions. Each role has an associated admin role, and only + * accounts that have a role's admin role can call {grantRole} and {revokeRole}. + * + * By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means + * that only accounts with this role will be able to grant or revoke other + * roles. More complex role relationships can be created by using + * {_setRoleAdmin}. + * + * WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to + * grant and revoke this role. Extra precautions should be taken to secure + * accounts that have been granted it. We recommend using {AccessControlDefaultAdminRules} + * to enforce additional security measures for this role. + */ +abstract contract AccessControl is Context, IAccessControl, ERC165 { + struct RoleData { + mapping(address account => bool) hasRole; + bytes32 adminRole; + } + + mapping(bytes32 role => RoleData) private _roles; + + bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00; + + /** + * @dev Modifier that checks that an account has a specific role. Reverts + * with an {AccessControlUnauthorizedAccount} error including the required role. + */ + modifier onlyRole(bytes32 role) { + _checkRole(role); + _; + } + + /** + * @dev See {IERC165-supportsInterface}. + */ + function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { + return interfaceId == type(IAccessControl).interfaceId || super.supportsInterface(interfaceId); + } + + /** + * @dev Returns `true` if `account` has been granted `role`. + */ + function hasRole(bytes32 role, address account) public view virtual returns (bool) { + return _roles[role].hasRole[account]; + } + + /** + * @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 Reverts with an {AccessControlUnauthorizedAccount} error if `account` + * is missing `role`. + */ + function _checkRole(bytes32 role, address account) internal view virtual { + if (!hasRole(role, account)) { + revert AccessControlUnauthorizedAccount(account, role); + } + } + + /** + * @dev Returns the admin role that controls `role`. See {grantRole} and + * {revokeRole}. + * + * To change a role's admin, use {_setRoleAdmin}. + */ + function getRoleAdmin(bytes32 role) public view virtual returns (bytes32) { + return _roles[role].adminRole; + } + + /** + * @dev Grants `role` to `account`. + * + * If `account` had not been already granted `role`, emits a {RoleGranted} + * event. + * + * Requirements: + * + * - the caller must have ``role``'s admin role. + * + * May emit a {RoleGranted} event. + */ + function grantRole(bytes32 role, address account) public virtual onlyRole(getRoleAdmin(role)) { + _grantRole(role, account); + } + + /** + * @dev Revokes `role` from `account`. + * + * If `account` had been granted `role`, emits a {RoleRevoked} event. + * + * Requirements: + * + * - the caller must have ``role``'s admin role. + * + * May emit a {RoleRevoked} event. + */ + function revokeRole(bytes32 role, address account) public virtual onlyRole(getRoleAdmin(role)) { + _revokeRole(role, account); + } + + /** + * @dev Revokes `role` from the calling account. + * + * Roles are often managed via {grantRole} and {revokeRole}: this function's + * purpose is to provide a mechanism for accounts to lose their privileges + * if they are compromised (such as when a trusted device is misplaced). + * + * If the calling account had been revoked `role`, emits a {RoleRevoked} + * event. + * + * Requirements: + * + * - the caller must be `callerConfirmation`. + * + * May emit a {RoleRevoked} event. + */ + function renounceRole(bytes32 role, address callerConfirmation) public virtual { + if (callerConfirmation != _msgSender()) { + revert AccessControlBadConfirmation(); + } + + _revokeRole(role, callerConfirmation); + } + + /** + * @dev Sets `adminRole` as ``role``'s admin role. + * + * Emits a {RoleAdminChanged} event. + */ + function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual { + bytes32 previousAdminRole = getRoleAdmin(role); + _roles[role].adminRole = adminRole; + emit RoleAdminChanged(role, previousAdminRole, adminRole); + } + + /** + * @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 returns (bool) { + if (!hasRole(role, account)) { + _roles[role].hasRole[account] = true; + emit RoleGranted(role, account, _msgSender()); + return true; + } else { + return false; + } + } + + /** + * @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 returns (bool) { + if (hasRole(role, account)) { + _roles[role].hasRole[account] = false; + emit RoleRevoked(role, account, _msgSender()); + return true; + } else { + return false; + } + } +} diff --git a/contracts/access/IAccessControl.sol b/contracts/access/IAccessControl.sol new file mode 100644 index 000000000..35c5ff519 --- /dev/null +++ b/contracts/access/IAccessControl.sol @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (access/IAccessControl.sol) + +pragma solidity ^0.8.20; + +/** + * @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` + * + * `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite + * {RoleAdminChanged} not being emitted signaling this. + */ + event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole); + + /** + * @dev Emitted when `account` is granted `role`. + * + * `sender` is the account that originated the contract call, an admin role + * bearer except when using {AccessControl-_setupRole}. + */ + event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender); + + /** + * @dev Emitted when `account` is revoked `role`. + * + * `sender` is the account that originated the contract call: + * - if using `revokeRole`, it is the admin role bearer + * - if using `renounceRole`, it is the role bearer (i.e. `account`) + */ + event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender); + + /** + * @dev Returns `true` if `account` has been granted `role`. + */ + function hasRole(bytes32 role, address account) external view returns (bool); + + /** + * @dev Returns the admin role that controls `role`. See {grantRole} and + * {revokeRole}. + * + * To change a role's admin, use {AccessControl-_setRoleAdmin}. + */ + function getRoleAdmin(bytes32 role) external view returns (bytes32); + + /** + * @dev Grants `role` to `account`. + * + * If `account` had not been already granted `role`, emits a {RoleGranted} + * event. + * + * Requirements: + * + * - the caller must have ``role``'s admin role. + */ + function grantRole(bytes32 role, address account) external; + + /** + * @dev Revokes `role` from `account`. + * + * If `account` had been granted `role`, emits a {RoleRevoked} event. + * + * Requirements: + * + * - the caller must have ``role``'s admin role. + */ + function revokeRole(bytes32 role, address account) external; + + /** + * @dev Revokes `role` from the calling account. + * + * Roles are often managed via {grantRole} and {revokeRole}: this function's + * purpose is to provide a mechanism for accounts to lose their privileges + * if they are compromised (such as when a trusted device is misplaced). + * + * If the calling account had been granted `role`, emits a {RoleRevoked} + * event. + * + * Requirements: + * + * - the caller must be `callerConfirmation`. + */ + function renounceRole(bytes32 role, address callerConfirmation) external; +} diff --git a/contracts/access/Ownable.sol b/contracts/access/Ownable.sol new file mode 100644 index 000000000..6738b7297 --- /dev/null +++ b/contracts/access/Ownable.sol @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (access/Ownable.sol) + +pragma solidity ^0.8.20; + +import {Context} from "../utils/Context.sol"; + +/** + * @dev Contract module which provides a basic access control mechanism, where + * there is an account (an owner) that can be granted exclusive access to + * specific functions. + * + * 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 + * the owner. + */ +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); + + /** + * @dev Initializes the contract setting the address provided by the deployer as the initial owner. + */ + constructor(address initialOwner) { + if (initialOwner == address(0)) { + revert OwnableInvalidOwner(address(0)); + } + _transferOwnership(initialOwner); + } + + /** + * @dev Throws if called by any account other than the owner. + */ + modifier onlyOwner() { + _checkOwner(); + _; + } + + /** + * @dev Returns the address of the current owner. + */ + function owner() public view virtual returns (address) { + return _owner; + } + + /** + * @dev Throws if the sender is not the owner. + */ + function _checkOwner() internal view virtual { + if (owner() != _msgSender()) { + revert OwnableUnauthorizedAccount(_msgSender()); + } + } + + /** + * @dev Leaves the contract without owner. It will not be possible to call + * `onlyOwner` functions. Can only be called by the current owner. + * + * NOTE: Renouncing ownership will leave the contract without an owner, + * thereby disabling any functionality that is only available to the owner. + */ + function renounceOwnership() public virtual onlyOwner { + _transferOwnership(address(0)); + } + + /** + * @dev Transfers ownership of the contract to a new account (`newOwner`). + * Can only be called by the current owner. + */ + function transferOwnership(address newOwner) public virtual onlyOwner { + if (newOwner == address(0)) { + revert OwnableInvalidOwner(address(0)); + } + _transferOwnership(newOwner); + } + + /** + * @dev Transfers ownership of the contract to a new account (`newOwner`). + * Internal function without access restriction. + */ + function _transferOwnership(address newOwner) internal virtual { + address oldOwner = _owner; + _owner = newOwner; + emit OwnershipTransferred(oldOwner, newOwner); + } +} diff --git a/contracts/access/Ownable2Step.sol b/contracts/access/Ownable2Step.sol new file mode 100644 index 000000000..90ad840cc --- /dev/null +++ b/contracts/access/Ownable2Step.sol @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (access/Ownable2Step.sol) + +pragma solidity ^0.8.20; + +import {Ownable} from "./Ownable.sol"; + +/** + * @dev Contract module which provides access control mechanism, where + * there is an account (an owner) that can be granted exclusive access to + * specific functions. + * + * 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 parent (Ownable). + */ +abstract contract Ownable2Step is Ownable { + address private _pendingOwner; + + event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner); + + /** + * @dev Returns the address of the pending owner. + */ + function pendingOwner() public view virtual returns (address) { + return _pendingOwner; + } + + /** + * @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one. + * Can only be called by the current owner. + */ + function transferOwnership(address newOwner) public virtual override onlyOwner { + _pendingOwner = newOwner; + emit OwnershipTransferStarted(owner(), newOwner); + } + + /** + * @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner. + * Internal function without access restriction. + */ + function _transferOwnership(address newOwner) internal virtual override { + delete _pendingOwner; + super._transferOwnership(newOwner); + } + + /** + * @dev The new owner accepts the ownership transfer. + */ + function acceptOwnership() public virtual { + address sender = _msgSender(); + if (pendingOwner() != sender) { + revert OwnableUnauthorizedAccount(sender); + } + _transferOwnership(sender); + } +} diff --git a/contracts/access/README.adoc b/contracts/access/README.adoc new file mode 100644 index 000000000..c40b8dbee --- /dev/null +++ b/contracts/access/README.adoc @@ -0,0 +1,39 @@ += Access Control + +[.readme-notice] +NOTE: This document is better viewed at https://docs.openzeppelin.com/contracts/api/access + +This directory provides ways to restrict who can access the functions of a contract or when they can do it. + +- {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. + +== Core + +{{Ownable}} + +{{Ownable2Step}} + +{{IAccessControl}} + +{{AccessControl}} + +== Extensions + +{{IAccessControlEnumerable}} + +{{AccessControlEnumerable}} + +{{IAccessControlDefaultAdminRules}} + +{{AccessControlDefaultAdminRules}} + +== AccessManager + +{{IAuthority}} + +{{AccessManager}} + +{{AccessManaged}} + +{{AccessManagerAdapter}} diff --git a/contracts/access/extensions/AccessControlDefaultAdminRules.sol b/contracts/access/extensions/AccessControlDefaultAdminRules.sol new file mode 100644 index 000000000..f0f63bd23 --- /dev/null +++ b/contracts/access/extensions/AccessControlDefaultAdminRules.sol @@ -0,0 +1,396 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (access/extensions/AccessControlDefaultAdminRules.sol) + +pragma solidity ^0.8.20; + +import {IAccessControlDefaultAdminRules} from "./IAccessControlDefaultAdminRules.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 + * the `DEFAULT_ADMIN_ROLE` holder, which is a sensitive role with special permissions + * over other roles that may potentially have privileged rights in the system. + * + * If a specific role doesn't have an admin role assigned, the holder of the + * `DEFAULT_ADMIN_ROLE` will have the ability to grant it and revoke it. + * + * This contract implements the following risk mitigations on top of {AccessControl}: + * + * * Only one account holds the `DEFAULT_ADMIN_ROLE` since deployment until it's potentially renounced. + * * Enforces a 2-step process to transfer the `DEFAULT_ADMIN_ROLE` to another account. + * * Enforces a configurable delay between the two steps, with the ability to cancel before the transfer is accepted. + * * The delay can be changed by scheduling, see {changeDefaultAdminDelay}. + * * It is not possible to use another role to manage the `DEFAULT_ADMIN_ROLE`. + * + * Example usage: + * + * ```solidity + * contract MyToken is AccessControlDefaultAdminRules { + * constructor() AccessControlDefaultAdminRules( + * 3 days, + * msg.sender // Explicit initial `DEFAULT_ADMIN_ROLE` holder + * ) {} + * } + * ``` + */ +abstract contract AccessControlDefaultAdminRules is IAccessControlDefaultAdminRules, IERC5313, AccessControl { + // pending admin pair read/written together frequently + address private _pendingDefaultAdmin; + uint48 private _pendingDefaultAdminSchedule; // 0 == unset + + uint48 private _currentDelay; + address private _currentDefaultAdmin; + + // pending delay pair read/written together frequently + uint48 private _pendingDelay; + uint48 private _pendingDelaySchedule; // 0 == unset + + /** + * @dev Sets the initial values for {defaultAdminDelay} and {defaultAdmin} address. + */ + constructor(uint48 initialDelay, address initialDefaultAdmin) { + if (initialDefaultAdmin == address(0)) { + revert AccessControlInvalidDefaultAdmin(address(0)); + } + _currentDelay = initialDelay; + _grantRole(DEFAULT_ADMIN_ROLE, initialDefaultAdmin); + } + + /** + * @dev See {IERC165-supportsInterface}. + */ + function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { + return interfaceId == type(IAccessControlDefaultAdminRules).interfaceId || super.supportsInterface(interfaceId); + } + + /** + * @dev See {IERC5313-owner}. + */ + function owner() public view virtual returns (address) { + return defaultAdmin(); + } + + /// + /// Override AccessControl role management + /// + + /** + * @dev See {AccessControl-grantRole}. Reverts for `DEFAULT_ADMIN_ROLE`. + */ + function grantRole(bytes32 role, address account) public virtual override(AccessControl, IAccessControl) { + if (role == DEFAULT_ADMIN_ROLE) { + revert AccessControlEnforcedDefaultAdminRules(); + } + super.grantRole(role, account); + } + + /** + * @dev See {AccessControl-revokeRole}. Reverts for `DEFAULT_ADMIN_ROLE`. + */ + function revokeRole(bytes32 role, address account) public virtual override(AccessControl, IAccessControl) { + if (role == DEFAULT_ADMIN_ROLE) { + revert AccessControlEnforcedDefaultAdminRules(); + } + super.revokeRole(role, account); + } + + /** + * @dev See {AccessControl-renounceRole}. + * + * For the `DEFAULT_ADMIN_ROLE`, it only allows renouncing in two steps by first calling + * {beginDefaultAdminTransfer} to the `address(0)`, so it's required that the {pendingDefaultAdmin} schedule + * has also passed when calling this function. + * + * After its execution, it will not be possible to call `onlyRole(DEFAULT_ADMIN_ROLE)` functions. + * + * NOTE: Renouncing `DEFAULT_ADMIN_ROLE` will leave the contract without a {defaultAdmin}, + * thereby disabling any functionality that is only available for it, and the possibility of reassigning a + * non-administrated role. + */ + function renounceRole(bytes32 role, address account) public virtual override(AccessControl, IAccessControl) { + if (role == DEFAULT_ADMIN_ROLE && account == defaultAdmin()) { + (address newDefaultAdmin, uint48 schedule) = pendingDefaultAdmin(); + if (newDefaultAdmin != address(0) || !_isScheduleSet(schedule) || !_hasSchedulePassed(schedule)) { + revert AccessControlEnforcedDefaultAdminDelay(schedule); + } + delete _pendingDefaultAdminSchedule; + } + super.renounceRole(role, account); + } + + /** + * @dev See {AccessControl-_grantRole}. + * + * For `DEFAULT_ADMIN_ROLE`, it only allows granting if there isn't already a {defaultAdmin} or if the + * role has been previously renounced. + * + * 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 returns (bool) { + if (role == DEFAULT_ADMIN_ROLE) { + if (defaultAdmin() != address(0)) { + revert AccessControlEnforcedDefaultAdminRules(); + } + _currentDefaultAdmin = account; + } + return super._grantRole(role, account); + } + + /** + * @dev See {AccessControl-_revokeRole}. + */ + function _revokeRole(bytes32 role, address account) internal virtual override returns (bool) { + if (role == DEFAULT_ADMIN_ROLE && account == defaultAdmin()) { + delete _currentDefaultAdmin; + } + return super._revokeRole(role, account); + } + + /** + * @dev See {AccessControl-_setRoleAdmin}. Reverts for `DEFAULT_ADMIN_ROLE`. + */ + function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual override { + if (role == DEFAULT_ADMIN_ROLE) { + revert AccessControlEnforcedDefaultAdminRules(); + } + super._setRoleAdmin(role, adminRole); + } + + /// + /// AccessControlDefaultAdminRules accessors + /// + + /** + * @inheritdoc IAccessControlDefaultAdminRules + */ + function defaultAdmin() public view virtual returns (address) { + return _currentDefaultAdmin; + } + + /** + * @inheritdoc IAccessControlDefaultAdminRules + */ + function pendingDefaultAdmin() public view virtual returns (address newAdmin, uint48 schedule) { + return (_pendingDefaultAdmin, _pendingDefaultAdminSchedule); + } + + /** + * @inheritdoc IAccessControlDefaultAdminRules + */ + function defaultAdminDelay() public view virtual returns (uint48) { + uint48 schedule = _pendingDelaySchedule; + return (_isScheduleSet(schedule) && _hasSchedulePassed(schedule)) ? _pendingDelay : _currentDelay; + } + + /** + * @inheritdoc IAccessControlDefaultAdminRules + */ + function pendingDefaultAdminDelay() public view virtual returns (uint48 newDelay, uint48 schedule) { + schedule = _pendingDelaySchedule; + return (_isScheduleSet(schedule) && !_hasSchedulePassed(schedule)) ? (_pendingDelay, schedule) : (0, 0); + } + + /** + * @inheritdoc IAccessControlDefaultAdminRules + */ + function defaultAdminDelayIncreaseWait() public view virtual returns (uint48) { + return 5 days; + } + + /// + /// AccessControlDefaultAdminRules public and internal setters for defaultAdmin/pendingDefaultAdmin + /// + + /** + * @inheritdoc IAccessControlDefaultAdminRules + */ + function beginDefaultAdminTransfer(address newAdmin) public virtual onlyRole(DEFAULT_ADMIN_ROLE) { + _beginDefaultAdminTransfer(newAdmin); + } + + /** + * @dev See {beginDefaultAdminTransfer}. + * + * Internal function without access restriction. + */ + function _beginDefaultAdminTransfer(address newAdmin) internal virtual { + uint48 newSchedule = SafeCast.toUint48(block.timestamp) + defaultAdminDelay(); + _setPendingDefaultAdmin(newAdmin, newSchedule); + emit DefaultAdminTransferScheduled(newAdmin, newSchedule); + } + + /** + * @inheritdoc IAccessControlDefaultAdminRules + */ + function cancelDefaultAdminTransfer() public virtual onlyRole(DEFAULT_ADMIN_ROLE) { + _cancelDefaultAdminTransfer(); + } + + /** + * @dev See {cancelDefaultAdminTransfer}. + * + * Internal function without access restriction. + */ + function _cancelDefaultAdminTransfer() internal virtual { + _setPendingDefaultAdmin(address(0), 0); + } + + /** + * @inheritdoc IAccessControlDefaultAdminRules + */ + function acceptDefaultAdminTransfer() public virtual { + (address newDefaultAdmin, ) = pendingDefaultAdmin(); + if (_msgSender() != newDefaultAdmin) { + // Enforce newDefaultAdmin explicit acceptance. + revert AccessControlInvalidDefaultAdmin(_msgSender()); + } + _acceptDefaultAdminTransfer(); + } + + /** + * @dev See {acceptDefaultAdminTransfer}. + * + * Internal function without access restriction. + */ + function _acceptDefaultAdminTransfer() internal virtual { + (address newAdmin, uint48 schedule) = pendingDefaultAdmin(); + if (!_isScheduleSet(schedule) || !_hasSchedulePassed(schedule)) { + revert AccessControlEnforcedDefaultAdminDelay(schedule); + } + _revokeRole(DEFAULT_ADMIN_ROLE, defaultAdmin()); + _grantRole(DEFAULT_ADMIN_ROLE, newAdmin); + delete _pendingDefaultAdmin; + delete _pendingDefaultAdminSchedule; + } + + /// + /// AccessControlDefaultAdminRules public and internal setters for defaultAdminDelay/pendingDefaultAdminDelay + /// + + /** + * @inheritdoc IAccessControlDefaultAdminRules + */ + function changeDefaultAdminDelay(uint48 newDelay) public virtual onlyRole(DEFAULT_ADMIN_ROLE) { + _changeDefaultAdminDelay(newDelay); + } + + /** + * @dev See {changeDefaultAdminDelay}. + * + * Internal function without access restriction. + */ + function _changeDefaultAdminDelay(uint48 newDelay) internal virtual { + uint48 newSchedule = SafeCast.toUint48(block.timestamp) + _delayChangeWait(newDelay); + _setPendingDelay(newDelay, newSchedule); + emit DefaultAdminDelayChangeScheduled(newDelay, newSchedule); + } + + /** + * @inheritdoc IAccessControlDefaultAdminRules + */ + function rollbackDefaultAdminDelay() public virtual onlyRole(DEFAULT_ADMIN_ROLE) { + _rollbackDefaultAdminDelay(); + } + + /** + * @dev See {rollbackDefaultAdminDelay}. + * + * Internal function without access restriction. + */ + function _rollbackDefaultAdminDelay() internal virtual { + _setPendingDelay(0, 0); + } + + /** + * @dev Returns the amount of seconds to wait after the `newDelay` will + * become the new {defaultAdminDelay}. + * + * The value returned guarantees that if the delay is reduced, it will go into effect + * after a wait that honors the previously set delay. + * + * See {defaultAdminDelayIncreaseWait}. + */ + function _delayChangeWait(uint48 newDelay) internal view virtual returns (uint48) { + uint48 currentDelay = defaultAdminDelay(); + + // When increasing the delay, we schedule the delay change to occur after a period of "new delay" has passed, up + // to a maximum given by defaultAdminDelayIncreaseWait, by default 5 days. For example, if increasing from 1 day + // to 3 days, the new delay will come into effect after 3 days. If increasing from 1 day to 10 days, the new + // delay will come into effect after 5 days. The 5 day wait period is intended to be able to fix an error like + // using milliseconds instead of seconds. + // + // When decreasing the delay, we wait the difference between "current delay" and "new delay". This guarantees + // that an admin transfer cannot be made faster than "current delay" at the time the delay change is scheduled. + // For example, if decreasing from 10 days to 3 days, the new delay will come into effect after 7 days. + return + newDelay > currentDelay + ? uint48(Math.min(newDelay, defaultAdminDelayIncreaseWait())) // no need to safecast, both inputs are uint48 + : currentDelay - newDelay; + } + + /// + /// Private setters + /// + + /** + * @dev Setter of the tuple for pending admin and its schedule. + * + * May emit a DefaultAdminTransferCanceled event. + */ + function _setPendingDefaultAdmin(address newAdmin, uint48 newSchedule) private { + (, uint48 oldSchedule) = pendingDefaultAdmin(); + + _pendingDefaultAdmin = newAdmin; + _pendingDefaultAdminSchedule = newSchedule; + + // An `oldSchedule` from `pendingDefaultAdmin()` is only set if it hasn't been accepted. + if (_isScheduleSet(oldSchedule)) { + // Emit for implicit cancellations when another default admin was scheduled. + emit DefaultAdminTransferCanceled(); + } + } + + /** + * @dev Setter of the tuple for pending delay and its schedule. + * + * May emit a DefaultAdminDelayChangeCanceled event. + */ + function _setPendingDelay(uint48 newDelay, uint48 newSchedule) private { + uint48 oldSchedule = _pendingDelaySchedule; + + if (_isScheduleSet(oldSchedule)) { + if (_hasSchedulePassed(oldSchedule)) { + // Materialize a virtual delay + _currentDelay = _pendingDelay; + } else { + // Emit for implicit cancellations when another delay was scheduled. + emit DefaultAdminDelayChangeCanceled(); + } + } + + _pendingDelay = newDelay; + _pendingDelaySchedule = newSchedule; + } + + /// + /// Private helpers + /// + + /** + * @dev Defines if an `schedule` is considered set. For consistency purposes. + */ + function _isScheduleSet(uint48 schedule) private pure returns (bool) { + return schedule != 0; + } + + /** + * @dev Defines if an `schedule` is considered passed. For consistency purposes. + */ + function _hasSchedulePassed(uint48 schedule) private view returns (bool) { + return schedule < block.timestamp; + } +} diff --git a/contracts/access/extensions/AccessControlEnumerable.sol b/contracts/access/extensions/AccessControlEnumerable.sol new file mode 100644 index 000000000..39ab221c4 --- /dev/null +++ b/contracts/access/extensions/AccessControlEnumerable.sol @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (access/extensions/AccessControlEnumerable.sol) + +pragma solidity ^0.8.20; + +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. + */ +abstract contract AccessControlEnumerable is IAccessControlEnumerable, AccessControl { + using EnumerableSet for EnumerableSet.AddressSet; + + mapping(bytes32 role => EnumerableSet.AddressSet) private _roleMembers; + + /** + * @dev See {IERC165-supportsInterface}. + */ + function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { + return interfaceId == type(IAccessControlEnumerable).interfaceId || super.supportsInterface(interfaceId); + } + + /** + * @dev Returns one of the accounts that have `role`. `index` must be a + * value between 0 and {getRoleMemberCount}, non-inclusive. + * + * Role bearers are not sorted in any particular way, and their ordering may + * change at any point. + * + * WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure + * you perform all queries on the same block. See the following + * 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 returns (address) { + return _roleMembers[role].at(index); + } + + /** + * @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 returns (uint256) { + return _roleMembers[role].length(); + } + + /** + * @dev Overload {AccessControl-_grantRole} to track enumerable memberships + */ + 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 {AccessControl-_revokeRole} to track enumerable memberships + */ + 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/contracts/access/extensions/IAccessControlDefaultAdminRules.sol b/contracts/access/extensions/IAccessControlDefaultAdminRules.sol new file mode 100644 index 000000000..b2c6fc831 --- /dev/null +++ b/contracts/access/extensions/IAccessControlDefaultAdminRules.sol @@ -0,0 +1,192 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (access/extensions/IAccessControlDefaultAdminRules.sol) + +pragma solidity ^0.8.20; + +import {IAccessControl} from "../IAccessControl.sol"; + +/** + * @dev External interface of AccessControlDefaultAdminRules declared to support ERC165 detection. + */ +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` + * passes. + */ + event DefaultAdminTransferScheduled(address indexed newAdmin, uint48 acceptSchedule); + + /** + * @dev Emitted when a {pendingDefaultAdmin} is reset if it was never accepted, regardless of its schedule. + */ + event DefaultAdminTransferCanceled(); + + /** + * @dev Emitted when a {defaultAdminDelay} change is started, setting `newDelay` as the next + * delay to be applied between default admin transfer after `effectSchedule` has passed. + */ + event DefaultAdminDelayChangeScheduled(uint48 newDelay, uint48 effectSchedule); + + /** + * @dev Emitted when a {pendingDefaultAdminDelay} is reset if its schedule didn't pass. + */ + event DefaultAdminDelayChangeCanceled(); + + /** + * @dev Returns the address of the current `DEFAULT_ADMIN_ROLE` holder. + */ + function defaultAdmin() external view returns (address); + + /** + * @dev Returns a tuple of a `newAdmin` and an accept schedule. + * + * After the `schedule` passes, the `newAdmin` will be able to accept the {defaultAdmin} role + * by calling {acceptDefaultAdminTransfer}, completing the role transfer. + * + * A zero value only in `acceptSchedule` indicates no pending admin transfer. + * + * NOTE: A zero address `newAdmin` means that {defaultAdmin} is being renounced. + */ + function pendingDefaultAdmin() external view returns (address newAdmin, uint48 acceptSchedule); + + /** + * @dev Returns the delay required to schedule the acceptance of a {defaultAdmin} transfer started. + * + * This delay will be added to the current timestamp when calling {beginDefaultAdminTransfer} to set + * the acceptance schedule. + * + * NOTE: If a delay change has been scheduled, it will take effect as soon as the schedule passes, making this + * function returns the new delay. See {changeDefaultAdminDelay}. + */ + function defaultAdminDelay() external view returns (uint48); + + /** + * @dev Returns a tuple of `newDelay` and an effect schedule. + * + * After the `schedule` passes, the `newDelay` will get into effect immediately for every + * new {defaultAdmin} transfer started with {beginDefaultAdminTransfer}. + * + * A zero value only in `effectSchedule` indicates no pending delay change. + * + * NOTE: A zero value only for `newDelay` means that the next {defaultAdminDelay} + * will be zero after the effect schedule. + */ + function pendingDefaultAdminDelay() external view returns (uint48 newDelay, uint48 effectSchedule); + + /** + * @dev Starts a {defaultAdmin} transfer by setting a {pendingDefaultAdmin} scheduled for acceptance + * after the current timestamp plus a {defaultAdminDelay}. + * + * Requirements: + * + * - Only can be called by the current {defaultAdmin}. + * + * Emits a DefaultAdminRoleChangeStarted event. + */ + function beginDefaultAdminTransfer(address newAdmin) external; + + /** + * @dev Cancels a {defaultAdmin} transfer previously started with {beginDefaultAdminTransfer}. + * + * A {pendingDefaultAdmin} not yet accepted can also be cancelled with this function. + * + * Requirements: + * + * - Only can be called by the current {defaultAdmin}. + * + * May emit a DefaultAdminTransferCanceled event. + */ + function cancelDefaultAdminTransfer() external; + + /** + * @dev Completes a {defaultAdmin} transfer previously started with {beginDefaultAdminTransfer}. + * + * After calling the function: + * + * - `DEFAULT_ADMIN_ROLE` should be granted to the caller. + * - `DEFAULT_ADMIN_ROLE` should be revoked from the previous holder. + * - {pendingDefaultAdmin} should be reset to zero values. + * + * Requirements: + * + * - Only can be called by the {pendingDefaultAdmin}'s `newAdmin`. + * - The {pendingDefaultAdmin}'s `acceptSchedule` should've passed. + */ + function acceptDefaultAdminTransfer() external; + + /** + * @dev Initiates a {defaultAdminDelay} update by setting a {pendingDefaultAdminDelay} scheduled for getting + * into effect after the current timestamp plus a {defaultAdminDelay}. + * + * This function guarantees that any call to {beginDefaultAdminTransfer} done between the timestamp this + * method is called and the {pendingDefaultAdminDelay} effect schedule will use the current {defaultAdminDelay} + * set before calling. + * + * The {pendingDefaultAdminDelay}'s effect schedule is defined in a way that waiting until the schedule and then + * calling {beginDefaultAdminTransfer} with the new delay will take at least the same as another {defaultAdmin} + * complete transfer (including acceptance). + * + * The schedule is designed for two scenarios: + * + * - When the delay is changed for a larger one the schedule is `block.timestamp + newDelay` capped by + * {defaultAdminDelayIncreaseWait}. + * - When the delay is changed for a shorter one, the schedule is `block.timestamp + (current delay - new delay)`. + * + * A {pendingDefaultAdminDelay} that never got into effect will be canceled in favor of a new scheduled change. + * + * Requirements: + * + * - Only can be called by the current {defaultAdmin}. + * + * Emits a DefaultAdminDelayChangeScheduled event and may emit a DefaultAdminDelayChangeCanceled event. + */ + function changeDefaultAdminDelay(uint48 newDelay) external; + + /** + * @dev Cancels a scheduled {defaultAdminDelay} change. + * + * Requirements: + * + * - Only can be called by the current {defaultAdmin}. + * + * May emit a DefaultAdminDelayChangeCanceled event. + */ + function rollbackDefaultAdminDelay() external; + + /** + * @dev Maximum time in seconds for an increase to {defaultAdminDelay} (that is scheduled using {changeDefaultAdminDelay}) + * to take effect. Default to 5 days. + * + * When the {defaultAdminDelay} is scheduled to be increased, it goes into effect after the new delay has passed with + * the purpose of giving enough time for reverting any accidental change (i.e. using milliseconds instead of seconds) + * that may lock the contract. However, to avoid excessive schedules, the wait is capped by this function and it can + * be overrode for a custom {defaultAdminDelay} increase scheduling. + * + * IMPORTANT: Make sure to add a reasonable amount of time while overriding this value, otherwise, + * there's a risk of setting a high new delay that goes into effect almost immediately without the + * possibility of human intervention in the case of an input error (eg. set milliseconds instead of seconds). + */ + function defaultAdminDelayIncreaseWait() external view returns (uint48); +} diff --git a/contracts/access/extensions/IAccessControlEnumerable.sol b/contracts/access/extensions/IAccessControlEnumerable.sol new file mode 100644 index 000000000..4dc8da478 --- /dev/null +++ b/contracts/access/extensions/IAccessControlEnumerable.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (access/extensions/IAccessControlEnumerable.sol) + +pragma solidity ^0.8.20; + +import {IAccessControl} from "../IAccessControl.sol"; + +/** + * @dev External interface of AccessControlEnumerable declared to support ERC165 detection. + */ +interface IAccessControlEnumerable is IAccessControl { + /** + * @dev Returns one of the accounts that have `role`. `index` must be a + * value between 0 and {getRoleMemberCount}, non-inclusive. + * + * Role bearers are not sorted in any particular way, and their ordering may + * change at any point. + * + * WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure + * you perform all queries on the same block. See the following + * 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) external view returns (address); + + /** + * @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) external view returns (uint256); +} diff --git a/contracts/access/manager/AccessManaged.sol b/contracts/access/manager/AccessManaged.sol new file mode 100644 index 000000000..6d10d6aa3 --- /dev/null +++ b/contracts/access/manager/AccessManaged.sol @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (access/manager/AccessManaged.sol) + +pragma solidity ^0.8.20; + +import {IAuthority} from "./IAuthority.sol"; +import {AuthorityUtils} from "./AuthorityUtils.sol"; +import {IAccessManager} from "./IAccessManager.sol"; +import {IAccessManaged} from "./IAccessManaged.sol"; +import {Context} from "../../utils/Context.sol"; + +/** + * @dev This contract module makes available a {restricted} modifier. Functions decorated with this modifier will be + * permissioned according to an "authority": a contract like {AccessManager} that follows the {IAuthority} interface, + * implementing a policy that allows certain callers to access certain functions. + * + * IMPORTANT: The `restricted` modifier should never be used on `internal` functions, judiciously used in `public` + * functions, and ideally only used in `external` functions. See {restricted}. + */ +abstract contract AccessManaged is Context, IAccessManaged { + address private _authority; + + bool private _consumingSchedule; + + /** + * @dev Initializes the contract connected to an initial authority. + */ + constructor(address initialAuthority) { + _setAuthority(initialAuthority); + } + + /** + * @dev Restricts access to a function as defined by the connected Authority for this contract and the + * caller and selector of the function that entered the contract. + * + * [IMPORTANT] + * ==== + * In general, this modifier should only be used on `external` functions. It is okay to use it on `public` + * functions that are used as external entry points and are not called internally. Unless you know what you're + * doing, it should never be used on `internal` functions. Failure to follow these rules can have critical security + * implications! This is because the permissions are determined by the function that entered the contract, i.e. the + * function at the bottom of the call stack, and not the function where the modifier is visible in the source code. + * ==== + * + * [NOTE] + * ==== + * Selector collisions are mitigated by scoping permissions per contract, but some edge cases must be considered: + * + * * If the https://docs.soliditylang.org/en/v0.8.20/contracts.html#receive-ether-function[`receive()`] function + * is restricted, any other function with a `0x00000000` selector will share permissions with `receive()`. + * * Similarly, if there's no `receive()` function but a `fallback()` instead, the fallback might be called with + * empty `calldata`, sharing the `0x00000000` selector permissions as well. + * * For any other selector, if the restricted function is set on an upgradeable contract, an upgrade may remove + * the restricted function and replace it with a new method whose selector replaces the last one, keeping the + * previous permissions. + * ==== + */ + modifier restricted() { + _checkCanCall(_msgSender(), _msgData()); + _; + } + + /** + * @dev Returns the current authority. + */ + function authority() public view virtual returns (address) { + return _authority; + } + + /** + * @dev Transfers control to a new authority. The caller must be the current authority. + */ + function setAuthority(address newAuthority) public virtual { + address caller = _msgSender(); + if (caller != authority()) { + revert AccessManagedUnauthorized(caller); + } + if (newAuthority.code.length == 0) { + revert AccessManagedInvalidAuthority(newAuthority); + } + _setAuthority(newAuthority); + } + + /** + * @dev Returns true only in the context of a delayed restricted call, at the moment that the scheduled operation is + * being consumed. Prevents denial of service for delayed restricted calls in the case that the contract performs + * attacker controlled calls. + */ + function isConsumingScheduledOp() public view returns (bytes4) { + return _consumingSchedule ? this.isConsumingScheduledOp.selector : bytes4(0); + } + + /** + * @dev Transfers control to a new authority. Internal function with no access restriction. Allows bypassing the + * permissions set by the current authority. + */ + function _setAuthority(address newAuthority) internal virtual { + _authority = newAuthority; + emit AuthorityUpdated(newAuthority); + } + + /** + * @dev Reverts if the caller is not allowed to call the function identified by a selector. + */ + function _checkCanCall(address caller, bytes calldata data) internal virtual { + (bool immediate, uint32 delay) = AuthorityUtils.canCallWithDelay( + authority(), + caller, + address(this), + bytes4(data) + ); + if (!immediate) { + if (delay > 0) { + _consumingSchedule = true; + IAccessManager(authority()).consumeScheduledOp(caller, data); + _consumingSchedule = false; + } else { + revert AccessManagedUnauthorized(caller); + } + } + } +} diff --git a/contracts/access/manager/AccessManager.sol b/contracts/access/manager/AccessManager.sol new file mode 100644 index 000000000..3ae74764a --- /dev/null +++ b/contracts/access/manager/AccessManager.sol @@ -0,0 +1,882 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (access/manager/AccessManager.sol) + +pragma solidity ^0.8.20; + +import {IAccessManager} from "./IAccessManager.sol"; +import {IAccessManaged} from "./IAccessManaged.sol"; +import {Address} from "../../utils/Address.sol"; +import {Context} from "../../utils/Context.sol"; +import {Multicall} from "../../utils/Multicall.sol"; +import {Math} from "../../utils/math/Math.sol"; +import {Time} from "../../utils/types/Time.sol"; + +/** + * @dev AccessManager is a central contract to store the permissions of a system. + * + * The smart contracts under the control of an AccessManager instance will have a set of "restricted" functions, and the + * exact details of how access is restricted for each of those functions is configurable by the admins of the instance. + * These restrictions are expressed in terms of "roles". + * + * An AccessManager instance will define a set of roles. Accounts can be added into any number of these roles. Each of + * them defines a role, and may confer access to some of the restricted functions in the system, as configured by admins + * through the use of {setFunctionAllowedRoles}. + * + * Note that a function in a target contract may become permissioned in this way only when: 1) said contract is + * {AccessManaged} and is connected to this contract as its manager, and 2) said function is decorated with the + * `restricted` modifier. + * + * There is a special role defined by default named "public" which all accounts automatically have. + * + * In addition to the access rules defined by each target's functions being assigned to roles, then entire target can + * be "closed". This "closed" mode is set/unset by the admin using {setTargetClosed} and can be used to lock a contract + * while permissions are being (re-)configured. + * + * Since all the permissions of the managed system can be modified by the admins of this instance, it is expected that + * they will be highly secured (e.g., a multisig or a well-configured DAO). + * + * NOTE: This contract implements a form of the {IAuthority} interface, but {canCall} has additional return data so it + * doesn't inherit `IAuthority`. It is however compatible with the `IAuthority` interface since the first 32 bytes of + * the return data are a boolean as expected by that interface. + * + * NOTE: Systems that implement other access control mechanisms (for example using {Ownable}) can be paired with an + * {AccessManager} by transferring permissions (ownership in the case of {Ownable}) directly to the {AccessManager}. + * Users will be able to interact with these contracts through the {execute} function, following the access rules + * registered in the {AccessManager}. Keep in mind that in that context, the msg.sender seen by restricted functions + * will be {AccessManager} itself. + * + * WARNING: When granting permissions over an {Ownable} or {AccessControl} contract to an {AccessManager}, be very + * mindful of the danger associated with functions such as {{Ownable-renounceOwnership}} or + * {{AccessControl-renounceRole}}. + */ +contract AccessManager is Context, Multicall, IAccessManager { + using Time for *; + + // Structure that stores the details for a target contract. + struct TargetConfig { + mapping(bytes4 selector => uint64 roleId) allowedRoles; + Time.Delay adminDelay; + bool closed; + } + + // Structure that stores the details for a role/account pair. This structures fit into a single slot. + struct Access { + // Timepoint at which the user gets the permission. If this is either 0, or in the future, the role + // permission is not available. + uint48 since; + // Delay for execution. Only applies to restricted() / execute() calls. + Time.Delay delay; + } + + // Structure that stores the details of a role, including: + // - the members of the role + // - the admin role (that can grant or revoke permissions) + // - the guardian role (that can cancel operations targeting functions that need this role) + // - the grand delay + struct Role { + mapping(address user => Access access) members; + uint64 admin; + uint64 guardian; + Time.Delay grantDelay; + } + + // Structure that stores the details for a scheduled operation. This structure fits into a single slot. + struct Schedule { + uint48 timepoint; + uint32 nonce; + } + + uint64 public constant ADMIN_ROLE = type(uint64).min; // 0 + uint64 public constant PUBLIC_ROLE = type(uint64).max; // 2**64-1 + + mapping(address target => TargetConfig mode) private _targets; + mapping(uint64 roleId => Role) private _roles; + mapping(bytes32 operationId => Schedule) private _schedules; + + // This should be transient storage when supported by the EVM. + bytes32 private _executionId; + + /** + * @dev Check that the caller is authorized to perform the operation, following the restrictions encoded in + * {_getAdminRestrictions}. + */ + modifier onlyAuthorized() { + _checkAuthorized(); + _; + } + + constructor(address initialAdmin) { + if (initialAdmin == address(0)) { + revert AccessManagerInvalidInitialAdmin(address(0)); + } + + // admin is active immediately and without any execution delay. + _grantRole(ADMIN_ROLE, initialAdmin, 0, 0); + } + + // =================================================== GETTERS ==================================================== + /** + * @dev Check if an address (`caller`) is authorised to call a given function on a given contract directly (with + * no restriction). Additionally, it returns the delay needed to perform the call indirectly through the {schedule} + * & {execute} workflow. + * + * This function is usually called by the targeted contract to control immediate execution of restricted functions. + * Therefore we only return true is the call can be performed without any delay. If the call is subject to a delay, + * then the function should return false, and the caller should schedule the operation for future execution. + * + * We may be able to hash the operation, and check if the call was scheduled, but we would not be able to cleanup + * the schedule, leaving the possibility of multiple executions. Maybe this function should not be view? + * + * NOTE: The IAuthority interface does not include the `uint32` delay. This is an extension of that interface that + * is backward compatible. Some contracts may thus ignore the second return argument. In that case they will fail + * to identify the indirect workflow, and will consider calls that require a delay to be forbidden. + */ + function canCall(address caller, address target, bytes4 selector) public view virtual returns (bool, uint32) { + if (isTargetClosed(target)) { + return (false, 0); + } else if (caller == address(this)) { + // Caller is AccessManager, this means the call was sent through {execute} and it already checked + // permissions. We verify that the call "identifier", which is set during {execute}, is correct. + return (_isExecuting(target, selector), 0); + } else { + uint64 roleId = getTargetFunctionRole(target, selector); + (bool isMember, uint32 currentDelay) = hasRole(roleId, caller); + return isMember ? (currentDelay == 0, currentDelay) : (false, 0); + } + } + + /** + * @dev Expiration delay for scheduled proposals. Defaults to 1 week. + */ + function expiration() public view virtual returns (uint32) { + return 1 weeks; + } + + /** + * @dev Minimum setback for all delay updates, with the exception of execution delays, which + * can be increased without setback (and in the event of an accidental increase can be reset + * via {revokeRole}). Defaults to 5 days. + */ + function minSetback() public view virtual returns (uint32) { + return 5 days; + } + + /** + * @dev Get the mode under which a contract is operating. + */ + function isTargetClosed(address target) public view virtual returns (bool) { + return _targets[target].closed; + } + + /** + * @dev Get the role required to call a function. + */ + function getTargetFunctionRole(address target, bytes4 selector) public view virtual returns (uint64) { + return _targets[target].allowedRoles[selector]; + } + + /** + * @dev Get the admin delay for a target contract. Changes to contract configuration are subject to this delay. + */ + function getTargetAdminDelay(address target) public view virtual returns (uint32) { + return _targets[target].adminDelay.get(); + } + + /** + * @dev Get the id of the role that acts as an admin for given role. + * + * The admin permission is required to grant the role, revoke the role and update the execution delay to execute + * an operation that is restricted to this role. + */ + function getRoleAdmin(uint64 roleId) public view virtual returns (uint64) { + return _roles[roleId].admin; + } + + /** + * @dev Get the role that acts as a guardian for a given role. + * + * The guardian permission allows canceling operations that have been scheduled under the role. + */ + function getRoleGuardian(uint64 roleId) public view virtual returns (uint64) { + return _roles[roleId].guardian; + } + + /** + * @dev Get the role current grant delay, that value may change at any point, without an event emitted, following + * a call to {setGrantDelay}. Changes to this value, including effect timepoint are notified by the + * {RoleGrantDelayChanged} event. + */ + function getRoleGrantDelay(uint64 roleId) public view virtual returns (uint32) { + return _roles[roleId].grantDelay.get(); + } + + /** + * @dev Get the access details for a given account for a given role. These details include the timepoint at which + * membership becomes active, and the delay applied to all operation by this user that requires this permission + * level. + * + * Returns: + * [0] Timestamp at which the account membership becomes valid. 0 means role is not granted. + * [1] Current execution delay for the account. + * [2] Pending execution delay for the account. + * [3] Timestamp at which the pending execution delay will become active. 0 means no delay update is scheduled. + */ + function getAccess(uint64 roleId, address account) public view virtual returns (uint48, uint32, uint32, uint48) { + Access storage access = _roles[roleId].members[account]; + + uint48 since = access.since; + (uint32 currentDelay, uint32 pendingDelay, uint48 effect) = access.delay.getFull(); + + return (since, currentDelay, pendingDelay, effect); + } + + /** + * @dev Check if a given account currently had the permission level corresponding to a given role. Note that this + * permission might be associated with a delay. {getAccess} can provide more details. + */ + function hasRole(uint64 roleId, address account) public view virtual returns (bool, uint32) { + if (roleId == PUBLIC_ROLE) { + return (true, 0); + } else { + (uint48 hasRoleSince, uint32 currentDelay, , ) = getAccess(roleId, account); + return (hasRoleSince != 0 && hasRoleSince <= Time.timestamp(), currentDelay); + } + } + + // =============================================== ROLE MANAGEMENT =============================================== + /** + * @dev Give a label to a role, for improved role discoverabily by UIs. + * + * Emits a {RoleLabel} event. + */ + function labelRole(uint64 roleId, string calldata label) public virtual onlyAuthorized { + if (roleId == ADMIN_ROLE || roleId == PUBLIC_ROLE) { + revert AccessManagerLockedRole(roleId); + } + emit RoleLabel(roleId, label); + } + + /** + * @dev Add `account` to `roleId`, or change its execution delay. + * + * This gives the account the authorization to call any function that is restricted to this role. An optional + * execution delay (in seconds) can be set. If that delay is non 0, the user is required to schedule any operation + * that is restricted to members this role. The user will only be able to execute the operation after the delay has + * passed, before it has expired. During this period, admin and guardians can cancel the operation (see {cancel}). + * + * If the account has already been granted this role, the execution delay will be updated. This update is not + * immediate and follows the delay rules. For example, If a user currently has a delay of 3 hours, and this is + * called to reduce that delay to 1 hour, the new delay will take some time to take effect, enforcing that any + * operation executed in the 3 hours that follows this update was indeed scheduled before this update. + * + * Requirements: + * + * - the caller must be an admin for the role (see {getRoleAdmin}) + * + * Emits a {RoleGranted} event + */ + function grantRole(uint64 roleId, address account, uint32 executionDelay) public virtual onlyAuthorized { + _grantRole(roleId, account, getRoleGrantDelay(roleId), executionDelay); + } + + /** + * @dev Remove an account from a role, with immediate effect. If the account does not have the role, this call has + * no effect. + * + * Requirements: + * + * - the caller must be an admin for the role (see {getRoleAdmin}) + * + * Emits a {RoleRevoked} event if the account had the role. + */ + function revokeRole(uint64 roleId, address account) public virtual onlyAuthorized { + _revokeRole(roleId, account); + } + + /** + * @dev Renounce role permissions for the calling account, with immediate effect. If the sender is not in + * the role, this call has no effect. + * + * Requirements: + * + * - the caller must be `callerConfirmation`. + * + * Emits a {RoleRevoked} event if the account had the role. + */ + function renounceRole(uint64 roleId, address callerConfirmation) public virtual { + if (callerConfirmation != _msgSender()) { + revert AccessManagerBadConfirmation(); + } + _revokeRole(roleId, callerConfirmation); + } + + /** + * @dev Change admin role for a given role. + * + * Requirements: + * + * - the caller must be a global admin + * + * Emits a {RoleAdminChanged} event + */ + function setRoleAdmin(uint64 roleId, uint64 admin) public virtual onlyAuthorized { + _setRoleAdmin(roleId, admin); + } + + /** + * @dev Change guardian role for a given role. + * + * Requirements: + * + * - the caller must be a global admin + * + * Emits a {RoleGuardianChanged} event + */ + function setRoleGuardian(uint64 roleId, uint64 guardian) public virtual onlyAuthorized { + _setRoleGuardian(roleId, guardian); + } + + /** + * @dev Update the delay for granting a `roleId`. + * + * Requirements: + * + * - the caller must be a global admin + * + * Emits a {RoleGrantDelayChanged} event. + */ + function setGrantDelay(uint64 roleId, uint32 newDelay) public virtual onlyAuthorized { + _setGrantDelay(roleId, newDelay); + } + + /** + * @dev Internal version of {grantRole} without access control. Returns true if the role was newly granted. + * + * Emits a {RoleGranted} event. + */ + function _grantRole( + uint64 roleId, + address account, + uint32 grantDelay, + uint32 executionDelay + ) internal virtual returns (bool) { + if (roleId == PUBLIC_ROLE) { + revert AccessManagerLockedRole(roleId); + } + + bool newMember = _roles[roleId].members[account].since == 0; + uint48 since; + + if (newMember) { + since = Time.timestamp() + grantDelay; + _roles[roleId].members[account] = Access({since: since, delay: executionDelay.toDelay()}); + } else { + // No setback here. Value can be reset by doing revoke + grant, effectively allowing the admin to perform + // any change to the execution delay within the duration of the role admin delay. + (_roles[roleId].members[account].delay, since) = _roles[roleId].members[account].delay.withUpdate( + executionDelay, + 0 + ); + } + + emit RoleGranted(roleId, account, executionDelay, since, newMember); + return newMember; + } + + /** + * @dev Internal version of {revokeRole} without access control. This logic is also used by {renounceRole}. + * Returns true if the role was previously granted. + * + * Emits a {RoleRevoked} event if the account had the role. + */ + function _revokeRole(uint64 roleId, address account) internal virtual returns (bool) { + if (roleId == PUBLIC_ROLE) { + revert AccessManagerLockedRole(roleId); + } + + if (_roles[roleId].members[account].since == 0) { + return false; + } + + delete _roles[roleId].members[account]; + + emit RoleRevoked(roleId, account); + return true; + } + + /** + * @dev Internal version of {setRoleAdmin} without access control. + * + * Emits a {RoleAdminChanged} event + */ + function _setRoleAdmin(uint64 roleId, uint64 admin) internal virtual { + if (roleId == ADMIN_ROLE || roleId == PUBLIC_ROLE) { + revert AccessManagerLockedRole(roleId); + } + + _roles[roleId].admin = admin; + + emit RoleAdminChanged(roleId, admin); + } + + /** + * @dev Internal version of {setRoleGuardian} without access control. + * + * Emits a {RoleGuardianChanged} event + */ + function _setRoleGuardian(uint64 roleId, uint64 guardian) internal virtual { + if (roleId == ADMIN_ROLE || roleId == PUBLIC_ROLE) { + revert AccessManagerLockedRole(roleId); + } + + _roles[roleId].guardian = guardian; + + emit RoleGuardianChanged(roleId, guardian); + } + + /** + * @dev Internal version of {setGrantDelay} without access control. + * + * Emits a {RoleGrantDelayChanged} event + */ + function _setGrantDelay(uint64 roleId, uint32 newDelay) internal virtual { + if (roleId == PUBLIC_ROLE) { + revert AccessManagerLockedRole(roleId); + } + + uint48 effect; + (_roles[roleId].grantDelay, effect) = _roles[roleId].grantDelay.withUpdate(newDelay, minSetback()); + + emit RoleGrantDelayChanged(roleId, newDelay, effect); + } + + // ============================================= FUNCTION MANAGEMENT ============================================== + /** + * @dev Set the role required to call functions identified by the `selectors` in the `target` contract. + * + * Requirements: + * + * - the caller must be a global admin + * + * Emits a {TargetFunctionRoleUpdated} event per selector. + */ + function setTargetFunctionRole( + address target, + bytes4[] calldata selectors, + uint64 roleId + ) public virtual onlyAuthorized { + for (uint256 i = 0; i < selectors.length; ++i) { + _setTargetFunctionRole(target, selectors[i], roleId); + } + } + + /** + * @dev Internal version of {setFunctionAllowedRole} without access control. + * + * Emits a {TargetFunctionRoleUpdated} event + */ + function _setTargetFunctionRole(address target, bytes4 selector, uint64 roleId) internal virtual { + _targets[target].allowedRoles[selector] = roleId; + emit TargetFunctionRoleUpdated(target, selector, roleId); + } + + /** + * @dev Set the delay for changing the configuration of a given target contract. + * + * Requirements: + * + * - the caller must be a global admin + * + * Emits a {TargetAdminDelayUpdated} event per selector + */ + function setTargetAdminDelay(address target, uint32 newDelay) public virtual onlyAuthorized { + _setTargetAdminDelay(target, newDelay); + } + + /** + * @dev Internal version of {setTargetAdminDelay} without access control. + * + * Emits a {TargetAdminDelayUpdated} event + */ + function _setTargetAdminDelay(address target, uint32 newDelay) internal virtual { + uint48 effect; + (_targets[target].adminDelay, effect) = _targets[target].adminDelay.withUpdate(newDelay, minSetback()); + + emit TargetAdminDelayUpdated(target, newDelay, effect); + } + + // =============================================== MODE MANAGEMENT ================================================ + /** + * @dev Set the closed flag for a contract. + * + * Requirements: + * + * - the caller must be a global admin + * + * Emits a {TargetClosed} event. + */ + function setTargetClosed(address target, bool closed) public virtual onlyAuthorized { + _setTargetClosed(target, closed); + } + + /** + * @dev Set the closed flag for a contract. This is an internal setter with no access restrictions. + * + * Emits a {TargetClosed} event. + */ + function _setTargetClosed(address target, bool closed) internal virtual { + if (target == address(this)) { + revert AccessManagerLockedAccount(target); + } + _targets[target].closed = closed; + emit TargetClosed(target, closed); + } + + // ============================================== DELAYED OPERATIONS ============================================== + /** + * @dev Return the timepoint at which a scheduled operation will be ready for execution. This returns 0 if the + * operation is not yet scheduled, has expired, was executed, or was canceled. + */ + function getSchedule(bytes32 id) public view virtual returns (uint48) { + uint48 timepoint = _schedules[id].timepoint; + return _isExpired(timepoint) ? 0 : timepoint; + } + + /** + * @dev Return the nonce for the latest scheduled operation with a given id. Returns 0 if the operation has never + * been scheduled. + */ + function getNonce(bytes32 id) public view virtual returns (uint32) { + return _schedules[id].nonce; + } + + /** + * @dev Schedule a delayed operation for future execution, and return the operation identifier. It is possible to + * choose the timestamp at which the operation becomes executable as long as it satisfies the execution delays + * required for the caller. The special value zero will automatically set the earliest possible time. + * + * Returns the `operationId` that was scheduled. Since this value is a hash of the parameters, it can reoccur when + * the same parameters are used; if this is relevant, the returned `nonce` can be used to uniquely identify this + * scheduled operation from other occurrences of the same `operationId` in invocations of {execute} and {cancel}. + * + * Emits a {OperationScheduled} event. + * + * NOTE: It is not possible to concurrently schedule more than one operation with the same `target` and `data`. If + * this is necessary, a random byte can be appended to `data` to act as a salt that will be ignored by the target + * contract if it is using standard Solidity ABI encoding. + */ + function schedule( + address target, + bytes calldata data, + uint48 when + ) public virtual returns (bytes32 operationId, uint32 nonce) { + address caller = _msgSender(); + + // Fetch restrictions that apply to the caller on the targeted function + (bool immediate, uint32 setback) = _canCallExtended(caller, target, data); + + uint48 minWhen = Time.timestamp() + setback; + + // if call is not authorized, or if requested timing is too soon + if ((!immediate && setback == 0) || (when > 0 && when < minWhen)) { + revert AccessManagerUnauthorizedCall(caller, target, bytes4(data[0:4])); + } + + // Reuse variable due to stack too deep + when = uint48(Math.max(when, minWhen)); // cast is safe: both inputs are uint48 + + // If caller is authorised, schedule operation + operationId = hashOperation(caller, target, data); + + _checkNotScheduled(operationId); + + unchecked { + // It's not feasible to overflow the nonce in less than 1000 years + nonce = _schedules[operationId].nonce + 1; + } + _schedules[operationId].timepoint = when; + _schedules[operationId].nonce = nonce; + emit OperationScheduled(operationId, nonce, when, caller, target, data); + + // Using named return values because otherwise we get stack too deep + } + + /** + * @dev Reverts if the operation is currently scheduled and has not expired. + * (Note: This function was introduced due to stack too deep errors in schedule.) + */ + function _checkNotScheduled(bytes32 operationId) private view { + uint48 prevTimepoint = _schedules[operationId].timepoint; + if (prevTimepoint != 0 && !_isExpired(prevTimepoint)) { + revert AccessManagerAlreadyScheduled(operationId); + } + } + + /** + * @dev Execute a function that is delay restricted, provided it was properly scheduled beforehand, or the + * execution delay is 0. + * + * Returns the nonce that identifies the previously scheduled operation that is executed, or 0 if the + * operation wasn't previously scheduled (if the caller doesn't have an execution delay). + * + * Emits an {OperationExecuted} event only if the call was scheduled and delayed. + */ + // Reentrancy is not an issue because permissions are checked on msg.sender. Additionally, + // _consumeScheduledOp guarantees a scheduled operation is only executed once. + // slither-disable-next-line reentrancy-no-eth + function execute(address target, bytes calldata data) public payable virtual returns (uint32) { + address caller = _msgSender(); + + // Fetch restrictions that apply to the caller on the targeted function + (bool immediate, uint32 setback) = _canCallExtended(caller, target, data); + + // If caller is not authorised, revert + if (!immediate && setback == 0) { + revert AccessManagerUnauthorizedCall(caller, target, bytes4(data)); + } + + // If caller is authorised, check operation was scheduled early enough + bytes32 operationId = hashOperation(caller, target, data); + uint32 nonce; + + if (setback != 0) { + nonce = _consumeScheduledOp(operationId); + } + + // Mark the target and selector as authorised + bytes32 executionIdBefore = _executionId; + _executionId = _hashExecutionId(target, bytes4(data)); + + // Perform call + Address.functionCallWithValue(target, data, msg.value); + + // Reset execute identifier + _executionId = executionIdBefore; + + return nonce; + } + + /** + * @dev Consume a scheduled operation targeting the caller. If such an operation exists, mark it as consumed + * (emit an {OperationExecuted} event and clean the state). Otherwise, throw an error. + * + * This is useful for contract that want to enforce that calls targeting them were scheduled on the manager, + * with all the verifications that it implies. + * + * Emit a {OperationExecuted} event + */ + function consumeScheduledOp(address caller, bytes calldata data) public virtual { + address target = _msgSender(); + if (IAccessManaged(target).isConsumingScheduledOp() != IAccessManaged.isConsumingScheduledOp.selector) { + revert AccessManagerUnauthorizedConsume(target); + } + _consumeScheduledOp(hashOperation(caller, target, data)); + } + + /** + * @dev Internal variant of {consumeScheduledOp} that operates on bytes32 operationId. + * + * Returns the nonce of the scheduled operation that is consumed. + */ + function _consumeScheduledOp(bytes32 operationId) internal virtual returns (uint32) { + uint48 timepoint = _schedules[operationId].timepoint; + uint32 nonce = _schedules[operationId].nonce; + + if (timepoint == 0) { + revert AccessManagerNotScheduled(operationId); + } else if (timepoint > Time.timestamp()) { + revert AccessManagerNotReady(operationId); + } else if (_isExpired(timepoint)) { + revert AccessManagerExpired(operationId); + } + + delete _schedules[operationId].timepoint; // reset the timepoint, keep the nonce + emit OperationExecuted(operationId, nonce); + + return nonce; + } + + /** + * @dev Cancel a scheduled (delayed) operation. Returns the nonce that identifies the previously scheduled + * operation that is cancelled. + * + * Requirements: + * + * - the caller must be the proposer, a guardian of the targeted function, or a global admin + * + * Emits a {OperationCanceled} event. + */ + function cancel(address caller, address target, bytes calldata data) public virtual returns (uint32) { + address msgsender = _msgSender(); + bytes4 selector = bytes4(data[0:4]); + + bytes32 operationId = hashOperation(caller, target, data); + if (_schedules[operationId].timepoint == 0) { + revert AccessManagerNotScheduled(operationId); + } else if (caller != msgsender) { + // calls can only be canceled by the account that scheduled them, a global admin, or by a guardian of the required role. + (bool isAdmin, ) = hasRole(ADMIN_ROLE, msgsender); + (bool isGuardian, ) = hasRole(getRoleGuardian(getTargetFunctionRole(target, selector)), msgsender); + if (!isAdmin && !isGuardian) { + revert AccessManagerUnauthorizedCancel(msgsender, caller, target, selector); + } + } + + delete _schedules[operationId].timepoint; // reset the timepoint, keep the nonce + uint32 nonce = _schedules[operationId].nonce; + emit OperationCanceled(operationId, nonce); + + return nonce; + } + + /** + * @dev Hashing function for delayed operations + */ + function hashOperation(address caller, address target, bytes calldata data) public view virtual returns (bytes32) { + return keccak256(abi.encode(caller, target, data)); + } + + /** + * @dev Hashing function for execute protection + */ + function _hashExecutionId(address target, bytes4 selector) private pure returns (bytes32) { + return keccak256(abi.encode(target, selector)); + } + + // ==================================================== OTHERS ==================================================== + /** + * @dev Change the AccessManager instance used by a contract that correctly uses this instance. + * + * Requirements: + * + * - the caller must be a global admin + */ + function updateAuthority(address target, address newAuthority) public virtual onlyAuthorized { + IAccessManaged(target).setAuthority(newAuthority); + } + + // ================================================= ADMIN LOGIC ================================================== + /** + * @dev Check if the current call is authorized according to admin logic. + */ + function _checkAuthorized() private { + address caller = _msgSender(); + (bool immediate, uint32 delay) = _canCallSelf(caller, _msgData()); + if (!immediate) { + if (delay == 0) { + (, uint64 requiredRole, ) = _getAdminRestrictions(_msgData()); + revert AccessManagerUnauthorizedAccount(caller, requiredRole); + } else { + _consumeScheduledOp(hashOperation(caller, address(this), _msgData())); + } + } + } + + /** + * @dev Get the admin restrictions of a given function call based on the function and arguments involved. + * + * Returns: + * - bool restricted: does this data match a restricted operation + * - uint64: which role is this operation restricted to + * - uint32: minimum delay to enforce for that operation (on top of the admin's execution delay) + */ + function _getAdminRestrictions(bytes calldata data) private view returns (bool, uint64, uint32) { + bytes4 selector = bytes4(data); + + if (data.length < 4) { + return (false, 0, 0); + } + + // Restricted to ADMIN with no delay beside any execution delay the caller may have + if ( + selector == this.labelRole.selector || + selector == this.setRoleAdmin.selector || + selector == this.setRoleGuardian.selector || + selector == this.setGrantDelay.selector || + selector == this.setTargetAdminDelay.selector + ) { + return (true, ADMIN_ROLE, 0); + } + + // Restricted to ADMIN with the admin delay corresponding to the target + if ( + selector == this.updateAuthority.selector || + selector == this.setTargetClosed.selector || + selector == this.setTargetFunctionRole.selector + ) { + // First argument is a target. + address target = abi.decode(data[0x04:0x24], (address)); + uint32 delay = getTargetAdminDelay(target); + return (true, ADMIN_ROLE, delay); + } + + // Restricted to that role's admin with no delay beside any execution delay the caller may have. + if (selector == this.grantRole.selector || selector == this.revokeRole.selector) { + // First argument is a roleId. + uint64 roleId = abi.decode(data[0x04:0x24], (uint64)); + uint64 roleAdminId = getRoleAdmin(roleId); + return (true, roleAdminId, 0); + } + + return (false, 0, 0); + } + + // =================================================== HELPERS ==================================================== + /** + * @dev An extended version of {canCall} for internal use that considers restrictions for admin functions. + * + * Returns: + * - bool immediate: whether the operation can be executed immediately (with no delay) + * - uint32 delay: the execution delay + * + * If immediate is true, the delay can be disregarded and the operation can be immediately executed. + * If immediate is false, the operation can be executed if and only if delay is greater than 0. + */ + function _canCallExtended(address caller, address target, bytes calldata data) private view returns (bool, uint32) { + if (target == address(this)) { + return _canCallSelf(caller, data); + } else { + bytes4 selector = bytes4(data); + return canCall(caller, target, selector); + } + } + + /** + * @dev A version of {canCall} that checks for admin restrictions in this contract. + */ + function _canCallSelf(address caller, bytes calldata data) private view returns (bool immediate, uint32 delay) { + if (caller == address(this)) { + // Caller is AccessManager, this means the call was sent through {execute} and it already checked + // permissions. We verify that the call "identifier", which is set during {execute}, is correct. + return (_isExecuting(address(this), bytes4(data)), 0); + } + + (bool enabled, uint64 roleId, uint32 operationDelay) = _getAdminRestrictions(data); + if (!enabled) { + return (false, 0); + } + + (bool inRole, uint32 executionDelay) = hasRole(roleId, caller); + if (!inRole) { + return (false, 0); + } + + // downcast is safe because both options are uint32 + delay = uint32(Math.max(operationDelay, executionDelay)); + return (delay == 0, delay); + } + + /** + * @dev Returns true if a call with `target` and `selector` is being executed via {executed}. + */ + function _isExecuting(address target, bytes4 selector) private view returns (bool) { + return _executionId == _hashExecutionId(target, selector); + } + + /** + * @dev Returns true if a schedule timepoint is past its expiration deadline. + */ + function _isExpired(uint48 timepoint) private view returns (bool) { + return timepoint + expiration() <= Time.timestamp(); + } +} diff --git a/contracts/access/manager/AuthorityUtils.sol b/contracts/access/manager/AuthorityUtils.sol new file mode 100644 index 000000000..c2a118e80 --- /dev/null +++ b/contracts/access/manager/AuthorityUtils.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (access/manager/AuthorityUtils.sol) + +pragma solidity ^0.8.20; + +import {IAuthority} from "./IAuthority.sol"; + +library AuthorityUtils { + /** + * @dev Since `AccessManager` implements an extended IAuthority interface, invoking `canCall` with backwards compatibility + * for the preexisting `IAuthority` interface requires special care to avoid reverting on insufficient return data. + * This helper function takes care of invoking `canCall` in a backwards compatible way without reverting. + */ + function canCallWithDelay( + address authority, + address caller, + address target, + bytes4 selector + ) internal view returns (bool immediate, uint32 delay) { + (bool success, bytes memory data) = authority.staticcall( + abi.encodeCall(IAuthority.canCall, (caller, target, selector)) + ); + if (success) { + if (data.length >= 0x40) { + (immediate, delay) = abi.decode(data, (bool, uint32)); + } else if (data.length >= 0x20) { + immediate = abi.decode(data, (bool)); + } + } + return (immediate, delay); + } +} diff --git a/contracts/access/manager/IAccessManaged.sol b/contracts/access/manager/IAccessManaged.sol new file mode 100644 index 000000000..fad7c5df9 --- /dev/null +++ b/contracts/access/manager/IAccessManaged.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (access/manager/IAccessManaged.sol) + +pragma solidity ^0.8.20; + +interface IAccessManaged { + event AuthorityUpdated(address authority); + + error AccessManagedUnauthorized(address caller); + error AccessManagedRequiredDelay(address caller, uint32 delay); + error AccessManagedInvalidAuthority(address authority); + + function authority() external view returns (address); + + function setAuthority(address) external; + + function isConsumingScheduledOp() external view returns (bytes4); +} diff --git a/contracts/access/manager/IAccessManager.sol b/contracts/access/manager/IAccessManager.sol new file mode 100644 index 000000000..01ecedd1c --- /dev/null +++ b/contracts/access/manager/IAccessManager.sol @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (access/manager/IAccessManager.sol) + +pragma solidity ^0.8.20; + +import {IAccessManaged} from "./IAccessManaged.sol"; +import {Time} from "../../utils/types/Time.sol"; + +interface IAccessManager { + /** + * @dev A delayed operation was scheduled. + */ + event OperationScheduled( + bytes32 indexed operationId, + uint32 indexed nonce, + uint48 schedule, + address caller, + address target, + bytes data + ); + + /** + * @dev A scheduled operation was executed. + */ + event OperationExecuted(bytes32 indexed operationId, uint32 indexed nonce); + + /** + * @dev A scheduled operation was canceled. + */ + event OperationCanceled(bytes32 indexed operationId, uint32 indexed nonce); + + event RoleLabel(uint64 indexed roleId, string label); + event RoleGranted(uint64 indexed roleId, address indexed account, uint32 delay, uint48 since, bool newMember); + event RoleRevoked(uint64 indexed roleId, address indexed account); + event RoleAdminChanged(uint64 indexed roleId, uint64 indexed admin); + event RoleGuardianChanged(uint64 indexed roleId, uint64 indexed guardian); + event RoleGrantDelayChanged(uint64 indexed roleId, uint32 delay, uint48 since); + event TargetClosed(address indexed target, bool closed); + event TargetFunctionRoleUpdated(address indexed target, bytes4 selector, uint64 indexed roleId); + event TargetAdminDelayUpdated(address indexed target, uint32 delay, uint48 since); + + error AccessManagerAlreadyScheduled(bytes32 operationId); + error AccessManagerNotScheduled(bytes32 operationId); + error AccessManagerNotReady(bytes32 operationId); + error AccessManagerExpired(bytes32 operationId); + error AccessManagerLockedAccount(address account); + error AccessManagerLockedRole(uint64 roleId); + error AccessManagerBadConfirmation(); + error AccessManagerUnauthorizedAccount(address msgsender, uint64 roleId); + error AccessManagerUnauthorizedCall(address caller, address target, bytes4 selector); + error AccessManagerUnauthorizedConsume(address target); + error AccessManagerUnauthorizedCancel(address msgsender, address caller, address target, bytes4 selector); + error AccessManagerInvalidInitialAdmin(address initialAdmin); + + function canCall( + address caller, + address target, + bytes4 selector + ) external view returns (bool allowed, uint32 delay); + + function hashOperation(address caller, address target, bytes calldata data) external view returns (bytes32); + + function expiration() external view returns (uint32); + + function isTargetClosed(address target) external view returns (bool); + + function getTargetFunctionRole(address target, bytes4 selector) external view returns (uint64); + + function getTargetAdminDelay(address target) external view returns (uint32); + + function getRoleAdmin(uint64 roleId) external view returns (uint64); + + function getRoleGuardian(uint64 roleId) external view returns (uint64); + + function getRoleGrantDelay(uint64 roleId) external view returns (uint32); + + function getAccess(uint64 roleId, address account) external view returns (uint48, uint32, uint32, uint48); + + function hasRole(uint64 roleId, address account) external view returns (bool, uint32); + + function labelRole(uint64 roleId, string calldata label) external; + + function grantRole(uint64 roleId, address account, uint32 executionDelay) external; + + function revokeRole(uint64 roleId, address account) external; + + function renounceRole(uint64 roleId, address callerConfirmation) external; + + function setRoleAdmin(uint64 roleId, uint64 admin) external; + + function setRoleGuardian(uint64 roleId, uint64 guardian) external; + + function setGrantDelay(uint64 roleId, uint32 newDelay) external; + + function setTargetFunctionRole(address target, bytes4[] calldata selectors, uint64 roleId) external; + + function setTargetAdminDelay(address target, uint32 newDelay) external; + + function setTargetClosed(address target, bool closed) external; + + function getSchedule(bytes32 id) external view returns (uint48); + + function getNonce(bytes32 id) external view returns (uint32); + + function schedule(address target, bytes calldata data, uint48 when) external returns (bytes32, uint32); + + function execute(address target, bytes calldata data) external payable returns (uint32); + + function cancel(address caller, address target, bytes calldata data) external returns (uint32); + + function consumeScheduledOp(address caller, bytes calldata data) external; + + function updateAuthority(address target, address newAuthority) external; +} diff --git a/contracts/access/manager/IAuthority.sol b/contracts/access/manager/IAuthority.sol new file mode 100644 index 000000000..78d610415 --- /dev/null +++ b/contracts/access/manager/IAuthority.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (access/manager/IAuthority.sol) + +pragma solidity ^0.8.20; + +/** + * @dev Standard interface for permissioning originally defined in Dappsys. + */ +interface IAuthority { + /** + * @dev Returns true if the caller can invoke on a target the function identified by a function selector. + */ + function canCall(address caller, address target, bytes4 selector) external view returns (bool allowed); +} diff --git a/contracts/finance/README.adoc b/contracts/finance/README.adoc new file mode 100644 index 000000000..ac7e4f015 --- /dev/null +++ b/contracts/finance/README.adoc @@ -0,0 +1,14 @@ += Finance + +[.readme-notice] +NOTE: This document is better viewed at https://docs.openzeppelin.com/contracts/api/finance + +This directory includes primitives for financial systems: + +- {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 + +{{VestingWallet}} diff --git a/contracts/finance/VestingWallet.sol b/contracts/finance/VestingWallet.sol new file mode 100644 index 000000000..f6373a617 --- /dev/null +++ b/contracts/finance/VestingWallet.sol @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (finance/VestingWallet.sol) +pragma solidity ^0.8.20; + +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"; + +/** + * @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 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, Ownable { + event EtherReleased(uint256 amount); + event ERC20Released(address indexed token, uint256 amount); + + uint256 private _released; + mapping(address token => uint256) private _erc20Released; + uint64 private immutable _start; + uint64 private immutable _duration; + + /** + * @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 beneficiary, uint64 startTimestamp, uint64 durationSeconds) payable Ownable(beneficiary) { + _start = startTimestamp; + _duration = durationSeconds; + } + + /** + * @dev The contract should be able to receive Eth. + */ + receive() external payable virtual {} + + /** + * @dev Getter for the start timestamp. + */ + function start() public view virtual returns (uint256) { + return _start; + } + + /** + * @dev Getter for the vesting duration. + */ + function duration() public view virtual returns (uint256) { + return _duration; + } + + /** + * @dev Getter for the end timestamp. + */ + function end() public view virtual returns (uint256) { + return start() + duration(); + } + + /** + * @dev Amount of eth already released + */ + function released() public view virtual returns (uint256) { + return _released; + } + + /** + * @dev Amount of token already released + */ + function released(address token) public view virtual returns (uint256) { + return _erc20Released[token]; + } + + /** + * @dev Getter for the amount of releasable eth. + */ + function releasable() public view virtual returns (uint256) { + return vestedAmount(uint64(block.timestamp)) - released(); + } + + /** + * @dev Getter for the amount of releasable `token` tokens. `token` should be the address of an + * IERC20 contract. + */ + function releasable(address token) public view virtual returns (uint256) { + return vestedAmount(token, uint64(block.timestamp)) - released(token); + } + + /** + * @dev Release the native token (ether) that have already vested. + * + * Emits a {EtherReleased} event. + */ + function release() public virtual { + uint256 amount = releasable(); + _released += amount; + emit EtherReleased(amount); + Address.sendValue(payable(owner()), amount); + } + + /** + * @dev Release the tokens that have already vested. + * + * Emits a {ERC20Released} event. + */ + function release(address token) public virtual { + uint256 amount = releasable(token); + _erc20Released[token] += amount; + emit ERC20Released(token, amount); + SafeERC20.safeTransfer(IERC20(token), owner(), amount); + } + + /** + * @dev Calculates the amount of ether that has already vested. Default implementation is a linear vesting curve. + */ + function vestedAmount(uint64 timestamp) public view virtual returns (uint256) { + return _vestingSchedule(address(this).balance + released(), timestamp); + } + + /** + * @dev Calculates the amount of tokens that has already vested. Default implementation is a linear vesting curve. + */ + function vestedAmount(address token, uint64 timestamp) public view virtual returns (uint256) { + return _vestingSchedule(IERC20(token).balanceOf(address(this)) + released(token), timestamp); + } + + /** + * @dev Virtual implementation of the vesting formula. This returns the amount vested, as a function of time, for + * an asset given its total historical allocation. + */ + function _vestingSchedule(uint256 totalAllocation, uint64 timestamp) internal view virtual returns (uint256) { + if (timestamp < start()) { + return 0; + } else if (timestamp >= end()) { + return totalAllocation; + } else { + return (totalAllocation * (timestamp - start())) / duration(); + } + } +} diff --git a/contracts/governance/Governor.sol b/contracts/governance/Governor.sol new file mode 100644 index 000000000..9c65483ce --- /dev/null +++ b/contracts/governance/Governor.sol @@ -0,0 +1,850 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (governance/Governor.sol) + +pragma solidity ^0.8.20; + +import {IERC721Receiver} from "../token/ERC721/IERC721Receiver.sol"; +import {IERC1155Receiver} from "../token/ERC1155/IERC1155Receiver.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"; +import {Address} from "../utils/Address.sol"; +import {Context} from "../utils/Context.sol"; +import {Nonces} from "../utils/Nonces.sol"; +import {IGovernor, IERC6372} from "./IGovernor.sol"; + +/** + * @dev Core of the governance system, designed to be extended through 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} + * - Additionally, {votingPeriod} must also be implemented + */ +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,address voter,uint256 nonce)"); + bytes32 public constant EXTENDED_BALLOT_TYPEHASH = + keccak256( + "ExtendedBallot(uint256 proposalId,uint8 support,address voter,uint256 nonce,string reason,bytes params)" + ); + + struct ProposalCore { + address proposer; + uint48 voteStart; + uint32 voteDuration; + bool executed; + bool canceled; + uint48 etaSeconds; + } + + bytes32 private constant ALL_PROPOSAL_STATES_BITMAP = bytes32((2 ** (uint8(type(ProposalState).max) + 1)) - 1); + string private _name; + + mapping(uint256 proposalId => 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 {execute}, consumed by the + // {onlyGovernance} modifier and eventually reset after {_executeOperations} completes. This ensures that the + // execution of {onlyGovernance} protected calls can only be achieved through successful proposals. + DoubleEndedQueue.Bytes32Deque private _governanceCall; + + /** + * @dev Restricts a function so it can only be executed through governance proposals. For example, governance + * parameter setters in {GovernorSettings} are protected using this modifier. + * + * The governance executing address may be different from the Governor's own address, for example it could be a + * timelock. This can be customized by modules by overriding {_executor}. The executor is only able to invoke these + * functions during the execution of the governor's {execute} function, and not under any other circumstances. Thus, + * for example, additional timelock proposers are not able to change governance parameters without going through the + * governance protocol (since v4.6). + */ + modifier onlyGovernance() { + _checkGovernance(); + _; + } + + /** + * @dev Sets the value for {name} and {version} + */ + constructor(string memory name_) EIP712(name_, version()) { + _name = name_; + } + + /** + * @dev Function to receive ETH that will be handled by the governor (disabled if executor is a third party contract) + */ + receive() external payable virtual { + if (_executor() != address(this)) { + revert GovernorDisabledDeposit(); + } + } + + /** + * @dev See {IERC165-supportsInterface}. + */ + function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC165) returns (bool) { + return + interfaceId == type(IGovernor).interfaceId || + interfaceId == type(IERC1155Receiver).interfaceId || + super.supportsInterface(interfaceId); + } + + /** + * @dev See {IGovernor-name}. + */ + function name() public view virtual returns (string memory) { + return _name; + } + + /** + * @dev See {IGovernor-version}. + */ + function version() public view virtual returns (string memory) { + return "1"; + } + + /** + * @dev See {IGovernor-hashProposal}. + * + * The proposal id is produced by hashing the ABI encoded `targets` array, the `values` array, the `calldatas` array + * and the descriptionHash (bytes32 which itself is the keccak256 hash of the description string). This proposal id + * can be produced from the proposal data which is part of the {ProposalCreated} event. It can even be computed in + * advance, before the proposal is submitted. + * + * Note that the chainId and the governor address are not part of the proposal id computation. Consequently, the + * same proposal (with same operation and same description) will have the same id if submitted on multiple governors + * across multiple networks. This also means that in order to execute the same operation twice (on the same + * governor) the proposer will have to change the description in order to avoid proposal id conflicts. + */ + function hashProposal( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 descriptionHash + ) public pure virtual returns (uint256) { + return uint256(keccak256(abi.encode(targets, values, calldatas, descriptionHash))); + } + + /** + * @dev See {IGovernor-state}. + */ + function state(uint256 proposalId) public view virtual returns (ProposalState) { + // We read the struct fields into the stack at once so Solidity emits a single SLOAD + ProposalCore storage proposal = _proposals[proposalId]; + bool proposalExecuted = proposal.executed; + bool proposalCanceled = proposal.canceled; + + if (proposalExecuted) { + return ProposalState.Executed; + } + + if (proposalCanceled) { + return ProposalState.Canceled; + } + + uint256 snapshot = proposalSnapshot(proposalId); + + if (snapshot == 0) { + revert GovernorNonexistentProposal(proposalId); + } + + uint256 currentTimepoint = clock(); + + if (snapshot >= currentTimepoint) { + return ProposalState.Pending; + } + + uint256 deadline = proposalDeadline(proposalId); + + if (deadline >= currentTimepoint) { + return ProposalState.Active; + } else if (!_quorumReached(proposalId) || !_voteSucceeded(proposalId)) { + return ProposalState.Defeated; + } else if (proposalEta(proposalId) == 0) { + return ProposalState.Succeeded; + } else { + return ProposalState.Queued; + } + } + + /** + * @dev See {IGovernor-proposalThreshold}. + */ + function proposalThreshold() public view virtual returns (uint256) { + return 0; + } + + /** + * @dev See {IGovernor-proposalSnapshot}. + */ + function proposalSnapshot(uint256 proposalId) public view virtual returns (uint256) { + return _proposals[proposalId].voteStart; + } + + /** + * @dev See {IGovernor-proposalDeadline}. + */ + function proposalDeadline(uint256 proposalId) public view virtual returns (uint256) { + return _proposals[proposalId].voteStart + _proposals[proposalId].voteDuration; + } + + /** + * @dev See {IGovernor-proposalProposer}. + */ + function proposalProposer(uint256 proposalId) public view virtual returns (address) { + return _proposals[proposalId].proposer; + } + + /** + * @dev See {IGovernor-proposalEta}. + */ + function proposalEta(uint256 proposalId) public view virtual returns (uint256) { + return _proposals[proposalId].etaSeconds; + } + + /** + * @dev See {IGovernor-proposalNeedsQueuing}. + */ + function proposalNeedsQueuing(uint256) public view virtual returns (bool) { + return false; + } + + /** + * @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. + */ + function _quorumReached(uint256 proposalId) internal view virtual returns (bool); + + /** + * @dev Is the proposal successful or not. + */ + function _voteSucceeded(uint256 proposalId) internal view virtual returns (bool); + + /** + * @dev Get the voting weight of `account` at a specific `timepoint`, for a vote as described by `params`. + */ + function _getVotes(address account, uint256 timepoint, bytes memory params) internal view virtual returns (uint256); + + /** + * @dev Register a vote for `proposalId` by `account` with a given `support`, voting `weight` and voting `params`. + * + * Note: Support is generic and can represent various things depending on the voting system used. + */ + function _countVote( + uint256 proposalId, + address account, + uint8 support, + uint256 weight, + bytes memory params + ) internal virtual; + + /** + * @dev Default additional encoded parameters used by castVote methods that don't include them + * + * Note: Should be overridden by specific implementations to use an appropriate value, the + * meaning of the additional params, in the context of that implementation + */ + function _defaultParams() internal view virtual returns (bytes memory) { + return ""; + } + + /** + * @dev See {IGovernor-propose}. This function has opt-in frontrunning protection, described in {_isValidDescriptionForProposer}. + */ + function propose( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + string memory description + ) public virtual returns (uint256) { + address proposer = _msgSender(); + + // check description restriction + if (!_isValidDescriptionForProposer(proposer, description)) { + revert GovernorRestrictedProposer(proposer); + } + + // 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 proposalId) { + 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) { + revert GovernorUnexpectedProposalState(proposalId, state(proposalId), bytes32(0)); + } + + uint256 snapshot = clock() + votingDelay(); + uint256 duration = votingPeriod(); + + ProposalCore storage proposal = _proposals[proposalId]; + proposal.proposer = proposer; + proposal.voteStart = SafeCast.toUint48(snapshot); + proposal.voteDuration = SafeCast.toUint32(duration); + + emit ProposalCreated( + proposalId, + proposer, + targets, + values, + new string[](targets.length), + calldatas, + snapshot, + snapshot + duration, + description + ); + + // Using a named return variable to avoid stack too deep errors + } + + /** + * @dev See {IGovernor-queue}. + */ + function queue( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 descriptionHash + ) public virtual returns (uint256) { + uint256 proposalId = hashProposal(targets, values, calldatas, descriptionHash); + + _validateStateBitmap(proposalId, _encodeStateBitmap(ProposalState.Succeeded)); + + uint48 etaSeconds = _queueOperations(proposalId, targets, values, calldatas, descriptionHash); + + if (etaSeconds != 0) { + _proposals[proposalId].etaSeconds = etaSeconds; + emit ProposalQueued(proposalId, etaSeconds); + } else { + revert GovernorQueueNotImplemented(); + } + + return proposalId; + } + + /** + * @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}. + */ + 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 execute( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 descriptionHash + ) public payable virtual returns (uint256) { + uint256 proposalId = hashProposal(targets, values, calldatas, descriptionHash); + + _validateStateBitmap( + proposalId, + _encodeStateBitmap(ProposalState.Succeeded) | _encodeStateBitmap(ProposalState.Queued) + ); + + // mark as executed before calls to avoid reentrancy + _proposals[proposalId].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])); + } + } + } + + _executeOperations(proposalId, targets, values, calldatas, descriptionHash); + + // after execute: cleanup governance call queue. + if (_executor() != address(this) && !_governanceCall.empty()) { + _governanceCall.clear(); + } + + emit ProposalExecuted(proposalId); + + return proposalId; + } + + /** + * @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 _executeOperations( + uint256 /* proposalId */, + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 /*descriptionHash*/ + ) internal virtual { + 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); + } + } + + /** + * @dev See {IGovernor-cancel}. + */ + function cancel( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 descriptionHash + ) public virtual 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); + + // 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 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. + */ + function _cancel( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 descriptionHash + ) internal virtual returns (uint256) { + uint256 proposalId = hashProposal(targets, values, calldatas, descriptionHash); + + _validateStateBitmap( + proposalId, + ALL_PROPOSAL_STATES_BITMAP ^ + _encodeStateBitmap(ProposalState.Canceled) ^ + _encodeStateBitmap(ProposalState.Expired) ^ + _encodeStateBitmap(ProposalState.Executed) + ); + + _proposals[proposalId].canceled = true; + emit ProposalCanceled(proposalId); + + return proposalId; + } + + /** + * @dev See {IGovernor-getVotes}. + */ + function getVotes(address account, uint256 timepoint) public view virtual returns (uint256) { + return _getVotes(account, timepoint, _defaultParams()); + } + + /** + * @dev See {IGovernor-getVotesWithParams}. + */ + function getVotesWithParams( + address account, + uint256 timepoint, + bytes memory params + ) public view virtual returns (uint256) { + return _getVotes(account, timepoint, params); + } + + /** + * @dev See {IGovernor-castVote}. + */ + function castVote(uint256 proposalId, uint8 support) public virtual returns (uint256) { + address voter = _msgSender(); + return _castVote(proposalId, voter, support, ""); + } + + /** + * @dev See {IGovernor-castVoteWithReason}. + */ + function castVoteWithReason( + uint256 proposalId, + uint8 support, + string calldata reason + ) public virtual returns (uint256) { + address voter = _msgSender(); + return _castVote(proposalId, voter, support, reason); + } + + /** + * @dev See {IGovernor-castVoteWithReasonAndParams}. + */ + function castVoteWithReasonAndParams( + uint256 proposalId, + uint8 support, + string calldata reason, + bytes memory params + ) public virtual returns (uint256) { + address voter = _msgSender(); + return _castVote(proposalId, voter, support, reason, params); + } + + /** + * @dev See {IGovernor-castVoteBySig}. + */ + function castVoteBySig( + uint256 proposalId, + uint8 support, + address voter, + bytes memory signature + ) public virtual returns (uint256) { + bool valid = SignatureChecker.isValidSignatureNow( + voter, + _hashTypedDataV4(keccak256(abi.encode(BALLOT_TYPEHASH, proposalId, support, voter, _useNonce(voter)))), + signature + ); + + if (!valid) { + revert GovernorInvalidSignature(voter); + } + + return _castVote(proposalId, voter, support, ""); + } + + /** + * @dev See {IGovernor-castVoteWithReasonAndParamsBySig}. + */ + function castVoteWithReasonAndParamsBySig( + uint256 proposalId, + uint8 support, + address voter, + string calldata reason, + bytes memory params, + bytes memory signature + ) public virtual returns (uint256) { + bool valid = SignatureChecker.isValidSignatureNow( + voter, + _hashTypedDataV4( + keccak256( + abi.encode( + EXTENDED_BALLOT_TYPEHASH, + proposalId, + support, + voter, + _useNonce(voter), + keccak256(bytes(reason)), + keccak256(params) + ) + ) + ), + signature + ); + + if (!valid) { + revert GovernorInvalidSignature(voter); + } + + return _castVote(proposalId, voter, support, reason, params); + } + + /** + * @dev Internal vote casting mechanism: Check that the vote is pending, that it has not been cast yet, retrieve + * voting weight using {IGovernor-getVotes} and call the {_countVote} internal function. Uses the _defaultParams(). + * + * Emits a {IGovernor-VoteCast} event. + */ + function _castVote( + uint256 proposalId, + address account, + uint8 support, + string memory reason + ) internal virtual returns (uint256) { + return _castVote(proposalId, account, support, reason, _defaultParams()); + } + + /** + * @dev Internal vote casting mechanism: Check that the vote is pending, that it has not been cast yet, retrieve + * voting weight using {IGovernor-getVotes} and call the {_countVote} internal function. + * + * Emits a {IGovernor-VoteCast} event. + */ + function _castVote( + uint256 proposalId, + address account, + uint8 support, + string memory reason, + bytes memory params + ) internal virtual returns (uint256) { + _validateStateBitmap(proposalId, _encodeStateBitmap(ProposalState.Active)); + + uint256 weight = _getVotes(account, proposalSnapshot(proposalId), params); + _countVote(proposalId, account, support, weight, params); + + if (params.length == 0) { + emit VoteCast(account, proposalId, support, weight, reason); + } else { + emit VoteCastWithParams(account, proposalId, support, weight, reason, params); + } + + return weight; + } + + /** + * @dev Relays a transaction or function call to an arbitrary target. In cases where the governance executor + * is some contract other than the governor itself, like when using a timelock, this function can be invoked + * 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 { + (bool success, bytes memory returndata) = target.call{value: value}(data); + Address.verifyCallResult(success, returndata); + } + + /** + * @dev Address through which the governor executes action. Will be overloaded by module that execute actions + * through another contract such as a timelock. + */ + function _executor() internal view virtual returns (address) { + return address(this); + } + + /** + * @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, + address, + uint256[] memory, + uint256[] memory, + bytes memory + ) public virtual returns (bytes4) { + if (_executor() != address(this)) { + revert GovernorDisabledDeposit(); + } + return this.onERC1155BatchReceived.selector; + } + + /** + * @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 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. + * + * 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); + } + } + } + + /** + * @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 new file mode 100644 index 000000000..e0fcc8f78 --- /dev/null +++ b/contracts/governance/IGovernor.sol @@ -0,0 +1,427 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (governance/IGovernor.sol) + +pragma solidity ^0.8.20; + +import {IERC165} from "../interfaces/IERC165.sol"; +import {IERC6372} from "../interfaces/IERC6372.sol"; + +/** + * @dev Interface of the {Governor} core. + */ +interface IGovernor is IERC165, IERC6372 { + enum ProposalState { + Pending, + Active, + Canceled, + Defeated, + Succeeded, + Queued, + Expired, + 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 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}. + */ + error GovernorInvalidSignature(address voter); + + /** + * @dev Emitted when a proposal is created. + */ + event ProposalCreated( + uint256 proposalId, + address proposer, + address[] targets, + uint256[] values, + string[] signatures, + bytes[] calldatas, + uint256 voteStart, + uint256 voteEnd, + string description + ); + + /** + * @dev Emitted when a proposal is queued. + */ + event ProposalQueued(uint256 proposalId, uint256 etaSeconds); + + /** + * @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. + * + * Note: `support` values should be seen as buckets. Their interpretation depends on the voting module used. + */ + event VoteCast(address indexed voter, uint256 proposalId, uint8 support, uint256 weight, string reason); + + /** + * @dev Emitted when a vote is cast with params. + * + * Note: `support` values should be seen as buckets. Their interpretation depends on the voting module used. + * `params` are additional encoded parameters. Their interpepretation also depends on the voting module used. + */ + event VoteCastWithParams( + address indexed voter, + uint256 proposalId, + uint8 support, + uint256 weight, + string reason, + bytes params + ); + + /** + * @notice module:core + * @dev Name of the governor instance (used in building the ERC712 domain separator). + */ + 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() external view returns (string memory); + + /** + * @notice module:voting + * @dev A description of the possible `support` values for {castVote} and the way these votes are counted, meant to + * be consumed by UIs to show correct vote options and interpret the results. The string is a URL-encoded sequence of + * key-value pairs that each describe one aspect, for example `support=bravo&quorum=for,abstain`. + * + * There are 2 standard keys: `support` and `quorum`. + * + * - `support=bravo` refers to the vote options 0 = Against, 1 = For, 2 = Abstain, as in `GovernorBravo`. + * - `quorum=bravo` means that only For votes are counted towards quorum. + * - `quorum=for,abstain` means that both For and Abstain votes are counted towards quorum. + * + * If a counting module makes use of encoded `params`, it should include this under a `params` key with a unique + * name that describes the behavior. For example: + * + * - `params=fractional` might refer to a scheme where votes are divided fractionally between for/against/abstain. + * - `params=erc721` might refer to a scheme where specific NFTs are delegated to vote. + * + * NOTE: The string can be decoded by the standard + * https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams[`URLSearchParams`] + * JavaScript class. + */ + // solhint-disable-next-line func-name-mixedcase + function COUNTING_MODE() external view returns (string memory); + + /** + * @notice module:core + * @dev Hashing function used to (re)build the proposal id from the proposal details.. + */ + function hashProposal( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 descriptionHash + ) external pure returns (uint256); + + /** + * @notice module:core + * @dev Current state of a proposal, following Compound's convention + */ + 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 + * @dev Timepoint used to retrieve user's votes and quorum. If using block number (as per Compound's Comp), the + * 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) 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) external view returns (uint256); + + /** + * @notice module:core + * @dev The account that created a proposal. + */ + 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:core + * @dev Whether a proposal needs to be queued before execution. + */ + function proposalNeedsQueuing(uint256 proposalId) external view returns (bool); + + /** + * @notice module:user-config + * @dev Delay, between the proposal is created and the vote starts. The unit this duration is expressed in depends + * on the clock (see EIP-6372) this contract uses. + * + * 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() external view returns (uint256); + + /** + * @notice module:user-config + * @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 + * 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() external view returns (uint256); + + /** + * @notice module:user-config + * @dev Minimum number of cast voted required for a proposal to be successful. + * + * 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) external view returns (uint256); + + /** + * @notice module:reputation + * @dev Voting power of an `account` at a specific `timepoint`. + * + * 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) external view returns (uint256); + + /** + * @notice module:reputation + * @dev Voting power of an `account` at a specific `timepoint` given additional encoded parameters. + */ + function getVotesWithParams( + address account, + uint256 timepoint, + bytes memory params + ) external view returns (uint256); + + /** + * @notice module:voting + * @dev Returns whether `account` has cast a vote on `proposalId`. + */ + 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 + * duration specified by {IGovernor-votingPeriod}. + * + * Emits a {ProposalCreated} event. + */ + function propose( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + string memory description + ) 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. 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 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 + ) 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. + * before the vote starts. + * + * Emits a {ProposalCanceled} event. + */ + function cancel( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 descriptionHash + ) external returns (uint256 proposalId); + + /** + * @dev Cast a vote + * + * Emits a {VoteCast} event. + */ + function castVote(uint256 proposalId, uint8 support) external returns (uint256 balance); + + /** + * @dev Cast a vote with a reason + * + * Emits a {VoteCast} event. + */ + function castVoteWithReason( + uint256 proposalId, + uint8 support, + string calldata reason + ) external returns (uint256 balance); + + /** + * @dev Cast a vote with a reason and additional encoded parameters + * + * Emits a {VoteCast} or {VoteCastWithParams} event depending on the length of params. + */ + function castVoteWithReasonAndParams( + uint256 proposalId, + uint8 support, + string calldata reason, + bytes memory params + ) external returns (uint256 balance); + + /** + * @dev Cast a vote using the voter's signature, including ERC-1271 signature support. + * + * Emits a {VoteCast} event. + */ + function castVoteBySig( + uint256 proposalId, + uint8 support, + address voter, + bytes memory signature + ) external returns (uint256 balance); + + /** + * @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. + */ + function castVoteWithReasonAndParamsBySig( + uint256 proposalId, + uint8 support, + address voter, + string calldata reason, + bytes memory params, + bytes memory signature + ) external returns (uint256 balance); +} diff --git a/contracts/governance/README.adoc b/contracts/governance/README.adoc new file mode 100644 index 000000000..5b38c4d53 --- /dev/null +++ b/contracts/governance/README.adoc @@ -0,0 +1,167 @@ += Governance + +[.readme-notice] +NOTE: This document is better viewed at https://docs.openzeppelin.com/contracts/api/governance + +This directory includes primitives for on-chain governance. + +== Governor + +This modular system of Governor contracts allows the deployment on-chain voting protocols similar to https://compound.finance/docs/governance[Compound's Governor Alpha & Bravo] and beyond, through the ability to easily customize multiple aspects of the protocol. + +[TIP] +==== +For a guided experience, set up your Governor contract using https://wizard.openzeppelin.com/#governor[Contracts Wizard]. + +For a written walkthrough, check out our guide on xref:ROOT:governance.adoc[How to set up on-chain governance]. +==== + +* {Governor}: The core contract that contains all the logic and primitives. It is abstract and requires choosing one of each of the modules below, or custom ones. + +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. + +* {GovernorVotesQuorumFraction}: Combines with `GovernorVotes` to set the quorum as a fraction of the total token supply. + +Counting modules determine valid voting options. + +* {GovernorCountingSimple}: Simple voting mechanism with 3 voting options: Against, For and Abstain. + +Timelock extensions add a delay for governance decisions to be executed. The workflow is extended to require a `queue` step before execution. With these modules, proposals are executed by the external timelock contract, thus it is the timelock that has to hold the assets that are being governed. + +* {GovernorTimelockControl}: Connects with an instance of {TimelockController}. Allows multiple proposers and executors, in addition to the Governor itself. + +* {GovernorTimelockCompound}: Connects with an instance of Compound's https://github.com/compound-finance/compound-protocol/blob/master/contracts/Timelock.sol[`Timelock`] contract. + +Other extensions can customize the behavior or interface in multiple ways. + +* {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. + +* {GovernorPreventLateQuorum}: Ensures there is a minimum voting period after quorum is reached as a security protection against large voters. + +In addition to modules and extensions, the core contract requires a few virtual functions to be implemented to your particular specifications: + +* <>: Delay (in EIP-6372 clock) since the proposal is submitted until voting power is fixed and voting starts. This can be used to enforce a delay after a proposal is published for users to buy tokens, or delegate their votes. +* <>: Delay (in EIP-6372 clock) since the proposal starts until voting ends. +* <>: Quorum required for a proposal to be successful. This function includes a `timepoint` argument (see EIP-6372) so the quorum can adapt through time, for example, to follow a token's `totalSupply`. + +NOTE: Functions of the `Governor` contract do not include access control. If you want to restrict access, you should add these checks by overloading the particular functions. Among these, {Governor-_cancel} is internal by default, and you will have to expose it (with the right access control mechanism) yourself if this function is needed. + +=== Core + +{{IGovernor}} + +{{Governor}} + +=== Modules + +{{GovernorCountingSimple}} + +{{GovernorVotes}} + +{{GovernorVotesQuorumFraction}} + +=== Extensions + +{{GovernorTimelockControl}} + +{{GovernorTimelockCompound}} + +{{GovernorSettings}} + +{{GovernorPreventLateQuorum}} + +{{GovernorStorage}} + +== Utils + +{{Votes}} + +== Timelock + +In a governance system, the {TimelockController} contract is in charge of introducing a delay between a proposal and its execution. It can be used with or without a {Governor}. + +{{TimelockController}} + +[[timelock-terminology]] +==== Terminology + +* *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. +** *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*: +** *Admin:* An address (smart contract or EOA) that is in charge of granting the roles of Proposer and Executor. +** *Proposer:* An address (smart contract or EOA) that is in charge of scheduling (and cancelling) operations. +** *Executor:* An address (smart contract or EOA) that is in charge of executing operations once the timelock has expired. This role can be given to the zero address to allow anyone to execute operations. + +[[timelock-operation]] +==== Operation structure + +Operation executed by the xref:api:governance.adoc#TimelockController[`TimelockController`] can contain one or multiple subsequent calls. Depending on whether you need to multiple calls to be executed atomically, you can either use simple or batched operations. + +Both operations contain: + +* *Target*, the address of the smart contract that the timelock should operate on. +* *Value*, in wei, that should be sent with the transaction. Most of the time this will be 0. Ether can be deposited before-end or passed along when executing the transaction. +* *Data*, containing the encoded function selector and parameters of the call. This can be produced using a number of tools. For example, a maintenance operation granting role `ROLE` to `ACCOUNT` can be encoded using web3js as follows: + +```javascript +const data = timelock.contract.methods.grantRole(ROLE, ACCOUNT).encodeABI() +``` + +* *Predecessor*, that specifies a dependency between operations. This dependency is optional. Use `bytes32(0)` if the operation does not have any dependency. +* *Salt*, used to disambiguate two otherwise identical operations. This can be any random value. + +In the case of batched operations, `target`, `value` and `data` are specified as arrays, which must be of the same length. + +[[timelock-operation-lifecycle]] +==== Operation lifecycle + +Timelocked operations are identified by a unique id (their hash) and follow a specific lifecycle: + +`Unset` -> `Pending` -> `Pending` + `Ready` -> `Done` + +* By calling xref:api:governance.adoc#TimelockController-schedule-address-uint256-bytes-bytes32-bytes32-uint256-[`schedule`] (or xref:api:governance.adoc#TimelockController-scheduleBatch-address---uint256---bytes---bytes32-bytes32-uint256-[`scheduleBatch`]), a proposer moves the operation from the `Unset` to the `Pending` state. This starts a timer that must be longer than the minimum delay. The timer expires at a timestamp accessible through the xref:api:governance.adoc#TimelockController-getTimestamp-bytes32-[`getTimestamp`] method. +* Once the timer expires, the operation automatically gets the `Ready` state. At this point, it can be executed. +* By calling xref:api:governance.adoc#TimelockController-TimelockController-execute-address-uint256-bytes-bytes32-bytes32-[`execute`] (or xref:api:governance.adoc#TimelockController-executeBatch-address---uint256---bytes---bytes32-bytes32-[`executeBatch`]), an executor triggers the operation's underlying transactions and moves it to the `Done` state. If the operation has a predecessor, it has to be in the `Done` state for this transition to succeed. +* xref:api:governance.adoc#TimelockController-TimelockController-cancel-bytes32-[`cancel`] allows proposers to cancel any `Pending` operation. This resets the operation to the `Unset` state. It is thus possible for a proposer to re-schedule an operation that has been cancelled. In this case, the timer restarts when the operation is re-scheduled. + +Operations status can be queried using the functions: + +* xref:api:governance.adoc#TimelockController-isOperationPending-bytes32-[`isOperationPending(bytes32)`] +* xref:api:governance.adoc#TimelockController-isOperationReady-bytes32-[`isOperationReady(bytes32)`] +* xref:api:governance.adoc#TimelockController-isOperationDone-bytes32-[`isOperationDone(bytes32)`] + +[[timelock-roles]] +==== Roles + +[[timelock-admin]] +===== Admin + +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. + +[[timelock-proposer]] +===== Proposer + +The proposers are in charge of scheduling (and cancelling) operations. This is a critical role, that should be given to governing entities. This could be an EOA, a multisig, or a DAO. + +WARNING: *Proposer fight:* Having multiple proposers, while providing redundancy in case one becomes unavailable, can be dangerous. As proposer have their say on all operations, they could cancel operations they disagree with, including operations to remove them for the proposers. + +This role is identified by the *PROPOSER_ROLE* value: `0xb09aa5aeb3702cfd50b6b62bc4532604938f21248a27a1d5ca736082b6819cc1` + +[[timelock-executor]] +===== Executor + +The executors are in charge of executing the operations scheduled by the proposers once the timelock expires. Logic dictates that multisig or DAO that are proposers should also be executors in order to guarantee operations that have been scheduled will eventually be executed. However, having additional executors can reduce the cost (the executing transaction does not require validation by the multisig or DAO that proposed it), while ensuring whoever is in charge of execution cannot trigger actions that have not been scheduled by the proposers. Alternatively, it is possible to allow _any_ address to execute a proposal once the timelock has expired by granting the executor role to the zero address. + +This role is identified by the *EXECUTOR_ROLE* value: `0xd8aa0f3194971a2a116679f7c2090f6939c8d4e01a2a8d7e41d55e5351469e63` + +WARNING: A live contract without at least one proposer and one executor is locked. Make sure these roles are filled by reliable entities before the deployer renounces its administrative rights in favour of the timelock contract itself. See the {AccessControl} documentation to learn more about role management. diff --git a/contracts/governance/TimelockController.sol b/contracts/governance/TimelockController.sol new file mode 100644 index 000000000..53596d396 --- /dev/null +++ b/contracts/governance/TimelockController.sol @@ -0,0 +1,472 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (governance/TimelockController.sol) + +pragma solidity ^0.8.20; + +import {AccessControl} from "../access/AccessControl.sol"; +import {ERC721Holder} from "../token/ERC721/utils/ERC721Holder.sol"; +import {ERC1155Holder} from "../token/ERC1155/utils/ERC1155Holder.sol"; +import {Address} from "../utils/Address.sol"; + +/** + * @dev Contract module which acts as a timelocked controller. When set as the + * owner of an `Ownable` smart contract, it enforces a timelock on all + * `onlyOwner` maintenance operations. This gives time for users of the + * controlled contract to exit before a potentially dangerous maintenance + * operation is applied. + * + * By default, this contract is self administered, meaning administration tasks + * have to go through the timelock process. The proposer (resp executor) role + * 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. + */ +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"); + uint256 internal constant _DONE_TIMESTAMP = uint256(1); + + mapping(bytes32 id => uint256) private _timestamps; + uint256 private _minDelay; + + enum OperationState { + Unset, + Waiting, + 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. + * 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, bytes32 expectedStates); + + /** + * @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`. + */ + event CallScheduled( + bytes32 indexed id, + uint256 indexed index, + address target, + uint256 value, + bytes data, + bytes32 predecessor, + uint256 delay + ); + + /** + * @dev Emitted when a call is performed as part of operation `id`. + */ + event CallExecuted(bytes32 indexed id, uint256 indexed index, address target, uint256 value, bytes data); + + /** + * @dev Emitted when new proposal is scheduled with non-zero salt. + */ + event CallSalt(bytes32 indexed id, bytes32 salt); + + /** + * @dev Emitted when operation `id` is cancelled. + */ + event Cancelled(bytes32 indexed id); + + /** + * @dev Emitted when the minimum delay for future operations is modified. + */ + event MinDelayChange(uint256 oldDuration, uint256 newDuration); + + /** + * @dev Initializes the contract with the following parameters: + * + * - `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 + * + * IMPORTANT: The optional admin can aid with initial configuration of roles after deployment + * without being subject to delay, but this role should be subsequently renounced in favor of + * 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) { + // self administration + _grantRole(DEFAULT_ADMIN_ROLE, address(this)); + + // optional admin + if (admin != address(0)) { + _grantRole(DEFAULT_ADMIN_ROLE, admin); + } + + // register proposers and cancellers + for (uint256 i = 0; i < proposers.length; ++i) { + _grantRole(PROPOSER_ROLE, proposers[i]); + _grantRole(CANCELLER_ROLE, proposers[i]); + } + + // register executors + for (uint256 i = 0; i < executors.length; ++i) { + _grantRole(EXECUTOR_ROLE, executors[i]); + } + + _minDelay = minDelay; + emit MinDelayChange(0, minDelay); + } + + /** + * @dev Modifier to make a function callable only by a certain role. In + * addition to checking the sender's role, `address(0)` 's role is also + * considered. Granting a role to `address(0)` is equivalent to enabling + * this role for everyone. + */ + modifier onlyRoleOrOpenRole(bytes32 role) { + if (!hasRole(role, address(0))) { + _checkRole(role, _msgSender()); + } + _; + } + + /** + * @dev Contract might receive/hold ETH as part of the maintenance process. + */ + receive() external payable {} + + /** + * @dev See {IERC165-supportsInterface}. + */ + function supportsInterface( + bytes4 interfaceId + ) public view virtual override(AccessControl, ERC1155Holder) returns (bool) { + return super.supportsInterface(interfaceId); + } + + /** + * @dev Returns whether an id corresponds to a registered operation. This + * includes both Waiting, Ready, and Done operations. + */ + 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 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 returns (bool) { + return getOperationState(id) == OperationState.Ready; + } + + /** + * @dev Returns whether an operation is done or not. + */ + function isOperationDone(bytes32 id) public view returns (bool) { + return getOperationState(id) == OperationState.Done; + } + + /** + * @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) { + 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 in seconds for an operation to become valid. + * + * This value can be changed by executing an operation that calls `updateDelay`. + */ + function getMinDelay() public view virtual returns (uint256) { + return _minDelay; + } + + /** + * @dev Returns the identifier of an operation containing a single + * transaction. + */ + function hashOperation( + address target, + uint256 value, + bytes calldata data, + bytes32 predecessor, + bytes32 salt + ) public pure virtual returns (bytes32) { + return keccak256(abi.encode(target, value, data, predecessor, salt)); + } + + /** + * @dev Returns the identifier of an operation containing a batch of + * transactions. + */ + function hashOperationBatch( + address[] calldata targets, + uint256[] calldata values, + bytes[] calldata payloads, + bytes32 predecessor, + bytes32 salt + ) public pure virtual returns (bytes32) { + return keccak256(abi.encode(targets, values, payloads, predecessor, salt)); + } + + /** + * @dev Schedule an operation containing a single transaction. + * + * Emits {CallSalt} if salt is nonzero, and {CallScheduled}. + * + * Requirements: + * + * - the caller must have the 'proposer' role. + */ + function schedule( + address target, + uint256 value, + bytes calldata data, + bytes32 predecessor, + bytes32 salt, + uint256 delay + ) public virtual onlyRole(PROPOSER_ROLE) { + bytes32 id = hashOperation(target, value, data, predecessor, salt); + _schedule(id, delay); + emit CallScheduled(id, 0, target, value, data, predecessor, delay); + if (salt != bytes32(0)) { + emit CallSalt(id, salt); + } + } + + /** + * @dev Schedule an operation containing a batch of transactions. + * + * Emits {CallSalt} if salt is nonzero, and one {CallScheduled} event per transaction in the batch. + * + * Requirements: + * + * - the caller must have the 'proposer' role. + */ + function scheduleBatch( + address[] calldata targets, + uint256[] calldata values, + bytes[] calldata payloads, + bytes32 predecessor, + bytes32 salt, + uint256 delay + ) public virtual onlyRole(PROPOSER_ROLE) { + 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); + for (uint256 i = 0; i < targets.length; ++i) { + emit CallScheduled(id, i, targets[i], values[i], payloads[i], predecessor, delay); + } + if (salt != bytes32(0)) { + emit CallSalt(id, salt); + } + } + + /** + * @dev Schedule an operation that is to become valid after a given delay. + */ + function _schedule(bytes32 id, uint256 delay) private { + if (isOperation(id)) { + revert TimelockUnexpectedOperationState(id, _encodeStateBitmap(OperationState.Unset)); + } + uint256 minDelay = getMinDelay(); + if (delay < minDelay) { + revert TimelockInsufficientDelay(delay, minDelay); + } + _timestamps[id] = block.timestamp + delay; + } + + /** + * @dev Cancel an operation. + * + * Requirements: + * + * - the caller must have the 'canceller' role. + */ + function cancel(bytes32 id) public virtual onlyRole(CANCELLER_ROLE) { + if (!isOperationPending(id)) { + revert TimelockUnexpectedOperationState( + id, + _encodeStateBitmap(OperationState.Waiting) | _encodeStateBitmap(OperationState.Ready) + ); + } + delete _timestamps[id]; + + emit Cancelled(id); + } + + /** + * @dev Execute an (ready) operation containing a single transaction. + * + * Emits a {CallExecuted} event. + * + * Requirements: + * + * - the caller must have the 'executor' role. + */ + // This function can reenter, but it doesn't pose a risk because _afterCall checks that the proposal is pending, + // thus any modifications to the operation during reentrancy should be caught. + // slither-disable-next-line reentrancy-eth + function execute( + address target, + uint256 value, + bytes calldata payload, + bytes32 predecessor, + bytes32 salt + ) public payable virtual onlyRoleOrOpenRole(EXECUTOR_ROLE) { + bytes32 id = hashOperation(target, value, payload, predecessor, salt); + + _beforeCall(id, predecessor); + _execute(target, value, payload); + emit CallExecuted(id, 0, target, value, payload); + _afterCall(id); + } + + /** + * @dev Execute an (ready) operation containing a batch of transactions. + * + * Emits one {CallExecuted} event per transaction in the batch. + * + * Requirements: + * + * - the caller must have the 'executor' role. + */ + // This function can reenter, but it doesn't pose a risk because _afterCall checks that the proposal is pending, + // thus any modifications to the operation during reentrancy should be caught. + // slither-disable-next-line reentrancy-eth + function executeBatch( + address[] calldata targets, + uint256[] calldata values, + bytes[] calldata payloads, + bytes32 predecessor, + bytes32 salt + ) public payable virtual onlyRoleOrOpenRole(EXECUTOR_ROLE) { + 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); + + _beforeCall(id, predecessor); + for (uint256 i = 0; i < targets.length; ++i) { + address target = targets[i]; + uint256 value = values[i]; + bytes calldata payload = payloads[i]; + _execute(target, value, payload); + emit CallExecuted(id, i, target, value, payload); + } + _afterCall(id); + } + + /** + * @dev Execute an operation's call. + */ + function _execute(address target, uint256 value, bytes calldata data) internal virtual { + (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 { + if (!isOperationReady(id)) { + revert TimelockUnexpectedOperationState(id, _encodeStateBitmap(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 { + if (!isOperationReady(id)) { + revert TimelockUnexpectedOperationState(id, _encodeStateBitmap(OperationState.Ready)); + } + _timestamps[id] = _DONE_TIMESTAMP; + } + + /** + * @dev Changes the minimum timelock duration for future operations. + * + * Emits a {MinDelayChange} event. + * + * Requirements: + * + * - the caller must be the timelock itself. This can only be achieved by scheduling and later executing + * 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 { + address sender = _msgSender(); + if (sender != address(this)) { + revert TimelockUnauthorizedCaller(sender); + } + 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/contracts/governance/extensions/GovernorCountingSimple.sol b/contracts/governance/extensions/GovernorCountingSimple.sol new file mode 100644 index 000000000..44e71848c --- /dev/null +++ b/contracts/governance/extensions/GovernorCountingSimple.sol @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (governance/extensions/GovernorCountingSimple.sol) + +pragma solidity ^0.8.20; + +import {Governor} from "../Governor.sol"; + +/** + * @dev Extension of {Governor} for simple, 3 options, vote counting. + */ +abstract contract GovernorCountingSimple is Governor { + /** + * @dev Supported vote types. Matches Governor Bravo ordering. + */ + enum VoteType { + Against, + For, + Abstain + } + + struct ProposalVote { + uint256 againstVotes; + uint256 forVotes; + uint256 abstainVotes; + mapping(address voter => bool) hasVoted; + } + + mapping(uint256 proposalId => ProposalVote) private _proposalVotes; + + /** + * @dev See {IGovernor-COUNTING_MODE}. + */ + // solhint-disable-next-line func-name-mixedcase + function COUNTING_MODE() public pure virtual override returns (string memory) { + return "support=bravo&quorum=for,abstain"; + } + + /** + * @dev See {IGovernor-hasVoted}. + */ + function hasVoted(uint256 proposalId, address account) public view virtual override returns (bool) { + return _proposalVotes[proposalId].hasVoted[account]; + } + + /** + * @dev Accessor to the internal vote counts. + */ + 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); + } + + /** + * @dev See {Governor-_quorumReached}. + */ + function _quorumReached(uint256 proposalId) internal view virtual override returns (bool) { + ProposalVote storage proposalVote = _proposalVotes[proposalId]; + + return quorum(proposalSnapshot(proposalId)) <= proposalVote.forVotes + proposalVote.abstainVotes; + } + + /** + * @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) { + ProposalVote storage proposalVote = _proposalVotes[proposalId]; + + return proposalVote.forVotes > proposalVote.againstVotes; + } + + /** + * @dev See {Governor-_countVote}. In this module, the support follows the `VoteType` enum (from Governor Bravo). + */ + function _countVote( + uint256 proposalId, + address account, + uint8 support, + uint256 weight, + bytes memory // params + ) internal virtual override { + ProposalVote storage proposalVote = _proposalVotes[proposalId]; + + if (proposalVote.hasVoted[account]) { + revert GovernorAlreadyCastVote(account); + } + proposalVote.hasVoted[account] = true; + + if (support == uint8(VoteType.Against)) { + proposalVote.againstVotes += weight; + } else if (support == uint8(VoteType.For)) { + proposalVote.forVotes += weight; + } else if (support == uint8(VoteType.Abstain)) { + proposalVote.abstainVotes += weight; + } else { + revert GovernorInvalidVoteType(); + } + } +} diff --git a/contracts/governance/extensions/GovernorPreventLateQuorum.sol b/contracts/governance/extensions/GovernorPreventLateQuorum.sol new file mode 100644 index 000000000..6dd3f9458 --- /dev/null +++ b/contracts/governance/extensions/GovernorPreventLateQuorum.sol @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (governance/extensions/GovernorPreventLateQuorum.sol) + +pragma solidity ^0.8.20; + +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 + * swaying a vote and triggering quorum at the last minute, by ensuring there is always time for other voters to react + * 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 specified time has passed (the "vote extension" parameter). This parameter can be set through a governance + * proposal. + */ +abstract contract GovernorPreventLateQuorum is Governor { + uint48 private _voteExtension; + + mapping(uint256 proposalId => 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); + + /// @dev Emitted when the {lateQuorumVoteExtension} parameter is changed. + event LateQuorumVoteExtensionSet(uint64 oldVoteExtension, uint64 newVoteExtension); + + /** + * @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(uint48 initialVoteExtension) { + _setLateQuorumVoteExtension(initialVoteExtension); + } + + /** + * @dev Returns the proposal deadline, which may have been extended beyond that set at proposal creation, if the + * proposal reached quorum late in the voting period. See {Governor-proposalDeadline}. + */ + function proposalDeadline(uint256 proposalId) public view virtual override returns (uint256) { + return Math.max(super.proposalDeadline(proposalId), _extendedDeadlines[proposalId]); + } + + /** + * @dev Casts a vote and detects if it caused quorum to be reached, potentially extending the voting period. See + * {Governor-_castVote}. + * + * May emit a {ProposalExtended} event. + */ + function _castVote( + uint256 proposalId, + address account, + uint8 support, + string memory reason, + bytes memory params + ) internal virtual override returns (uint256) { + uint256 result = super._castVote(proposalId, account, support, reason, params); + + if (_extendedDeadlines[proposalId] == 0 && _quorumReached(proposalId)) { + uint48 extendedDeadline = clock() + lateQuorumVoteExtension(); + + if (extendedDeadline > proposalDeadline(proposalId)) { + emit ProposalExtended(proposalId, extendedDeadline); + } + + _extendedDeadlines[proposalId] = extendedDeadline; + } + + return result; + } + + /** + * @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 (uint48) { + return _voteExtension; + } + + /** + * @dev Changes the {lateQuorumVoteExtension}. This operation can only be performed by the governance executor, + * generally through a governance proposal. + * + * Emits a {LateQuorumVoteExtensionSet} event. + */ + function setLateQuorumVoteExtension(uint48 newVoteExtension) public virtual onlyGovernance { + _setLateQuorumVoteExtension(newVoteExtension); + } + + /** + * @dev Changes the {lateQuorumVoteExtension}. This is an internal function that can be exposed in a public function + * like {setLateQuorumVoteExtension} if another access control mechanism is needed. + * + * Emits a {LateQuorumVoteExtensionSet} event. + */ + 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 new file mode 100644 index 000000000..82ce646be --- /dev/null +++ b/contracts/governance/extensions/GovernorSettings.sol @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (governance/extensions/GovernorSettings.sol) + +pragma solidity ^0.8.20; + +import {Governor} from "../Governor.sol"; + +/** + * @dev Extension of {Governor} for settings updatable through governance. + */ +abstract contract GovernorSettings is Governor { + // 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); + event ProposalThresholdSet(uint256 oldProposalThreshold, uint256 newProposalThreshold); + + /** + * @dev Initialize the governance parameters. + */ + constructor(uint48 initialVotingDelay, uint32 initialVotingPeriod, uint256 initialProposalThreshold) { + _setVotingDelay(initialVotingDelay); + _setVotingPeriod(initialVotingPeriod); + _setProposalThreshold(initialProposalThreshold); + } + + /** + * @dev See {IGovernor-votingDelay}. + */ + function votingDelay() public view virtual override returns (uint256) { + return _votingDelay; + } + + /** + * @dev See {IGovernor-votingPeriod}. + */ + function votingPeriod() public view virtual override returns (uint256) { + return _votingPeriod; + } + + /** + * @dev See {Governor-proposalThreshold}. + */ + function proposalThreshold() public view virtual override returns (uint256) { + return _proposalThreshold; + } + + /** + * @dev Update the voting delay. This operation can only be performed through a governance proposal. + * + * Emits a {VotingDelaySet} event. + */ + function setVotingDelay(uint48 newVotingDelay) public virtual onlyGovernance { + _setVotingDelay(newVotingDelay); + } + + /** + * @dev Update the voting period. This operation can only be performed through a governance proposal. + * + * Emits a {VotingPeriodSet} event. + */ + function setVotingPeriod(uint32 newVotingPeriod) public virtual onlyGovernance { + _setVotingPeriod(newVotingPeriod); + } + + /** + * @dev Update the proposal threshold. This operation can only be performed through a governance proposal. + * + * Emits a {ProposalThresholdSet} event. + */ + function setProposalThreshold(uint256 newProposalThreshold) public virtual onlyGovernance { + _setProposalThreshold(newProposalThreshold); + } + + /** + * @dev Internal setter for the voting delay. + * + * Emits a {VotingDelaySet} event. + */ + function _setVotingDelay(uint48 newVotingDelay) internal virtual { + emit VotingDelaySet(_votingDelay, newVotingDelay); + _votingDelay = newVotingDelay; + } + + /** + * @dev Internal setter for the voting period. + * + * Emits a {VotingPeriodSet} event. + */ + function _setVotingPeriod(uint32 newVotingPeriod) internal virtual { + if (newVotingPeriod == 0) { + revert GovernorInvalidVotingPeriod(0); + } + emit VotingPeriodSet(_votingPeriod, newVotingPeriod); + _votingPeriod = newVotingPeriod; + } + + /** + * @dev Internal setter for the proposal threshold. + * + * Emits a {ProposalThresholdSet} event. + */ + function _setProposalThreshold(uint256 newProposalThreshold) internal virtual { + emit ProposalThresholdSet(_proposalThreshold, newProposalThreshold); + _proposalThreshold = newProposalThreshold; + } +} diff --git a/contracts/governance/extensions/GovernorStorage.sol b/contracts/governance/extensions/GovernorStorage.sol new file mode 100644 index 000000000..a90f13a74 --- /dev/null +++ b/contracts/governance/extensions/GovernorStorage.sol @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (governance/extensions/GovernorStorage.sol) + +pragma solidity ^0.8.20; + +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 proposalId => 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/GovernorTimelockAccess.sol b/contracts/governance/extensions/GovernorTimelockAccess.sol new file mode 100644 index 000000000..007e0c6f9 --- /dev/null +++ b/contracts/governance/extensions/GovernorTimelockAccess.sol @@ -0,0 +1,343 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (governance/extensions/GovernorTimelockAccess.sol) + +pragma solidity ^0.8.20; + +import {Governor} from "../Governor.sol"; +import {AuthorityUtils} from "../../access/manager/AuthorityUtils.sol"; +import {IAccessManager} from "../../access/manager/IAccessManager.sol"; +import {Address} from "../../utils/Address.sol"; +import {Math} from "../../utils/math/Math.sol"; +import {SafeCast} from "../../utils/math/SafeCast.sol"; +import {Time} from "../../utils/types/Time.sol"; + +/** + * @dev This module connects a {Governor} instance to an {AccessManager} instance, allowing the governor to make calls + * that are delay-restricted by the manager using the normal {queue} workflow. An optional base delay is applied to + * operations that are not delayed externally by the manager. Execution of a proposal will be delayed as much as + * necessary to meet the required delays of all of its operations. + * + * This extension allows the governor to hold and use its own assets and permissions, unlike {GovernorTimelockControl} + * and {GovernorTimelockCompound}, where the timelock is a separate contract that must be the one to hold assets and + * permissions. Operations that are delay-restricted by the manager, however, will be executed through the + * {AccessManager-execute} function. + * + * ==== Security Considerations + * + * Some operations may be cancelable in the `AccessManager` by the admin or a set of guardians, depending on the + * restricted function being invoked. Since proposals are atomic, the cancellation by a guardian of a single operation + * in a proposal will cause all of the proposal to become unable to execute. Consider proposing cancellable operations + * separately. + * + * By default, function calls will be routed through the associated `AccessManager` whenever it claims the target + * function to be restricted by it. However, admins may configure the manager to make that claim for functions that a + * governor would want to call directly (e.g., token transfers) in an attempt to deny it access to those functions. To + * mitigate this attack vector, the governor is able to ignore the restrictions claimed by the `AccessManager` using + * {setAccessManagerIgnored}. While permanent denial of service is mitigated, temporary DoS may still be technically + * possible. All of the governor's own functions (e.g., {setBaseDelaySeconds}) ignore the `AccessManager` by default. + */ +abstract contract GovernorTimelockAccess is Governor { + // An execution plan is produced at the moment a proposal is created, in order to fix at that point the exact + // execution semantics of the proposal, namely whether a call will go through {AccessManager-execute}. + struct ExecutionPlan { + uint16 length; + uint32 delay; + // We use mappings instead of arrays because it allows us to pack values in storage more tightly without + // storing the length redundantly. + // We pack 8 operations' data in each bucket. Each uint32 value is set to 1 upon proposal creation if it has + // to be scheduled and executed through the manager. Upon queuing, the value is set to nonce + 2, where the + // nonce is received from the manager when scheduling the operation. + mapping(uint256 operationBucket => uint32[8]) managerData; + } + + // The meaning of the "toggle" set to true depends on the target contract. + // If target == address(this), the manager is ignored by default, and a true toggle means it won't be ignored. + // For all other target contracts, the manager is used by default, and a true toggle means it will be ignored. + mapping(address target => mapping(bytes4 selector => bool)) private _ignoreToggle; + + mapping(uint256 proposalId => ExecutionPlan) private _executionPlan; + + uint32 private _baseDelay; + + IAccessManager private immutable _manager; + + error GovernorUnmetDelay(uint256 proposalId, uint256 neededTimestamp); + error GovernorMismatchedNonce(uint256 proposalId, uint256 expectedNonce, uint256 actualNonce); + error GovernorLockedIgnore(); + + event BaseDelaySet(uint32 oldBaseDelaySeconds, uint32 newBaseDelaySeconds); + event AccessManagerIgnoredSet(address target, bytes4 selector, bool ignored); + + /** + * @dev Initialize the governor with an {AccessManager} and initial base delay. + */ + constructor(address manager, uint32 initialBaseDelay) { + _manager = IAccessManager(manager); + _setBaseDelaySeconds(initialBaseDelay); + } + + /** + * @dev Returns the {AccessManager} instance associated to this governor. + */ + function accessManager() public view virtual returns (IAccessManager) { + return _manager; + } + + /** + * @dev Base delay that will be applied to all function calls. Some may be further delayed by their associated + * `AccessManager` authority; in this case the final delay will be the maximum of the base delay and the one + * demanded by the authority. + * + * NOTE: Execution delays are processed by the `AccessManager` contracts, and according to that contract are + * expressed in seconds. Therefore, the base delay is also in seconds, regardless of the governor's clock mode. + */ + function baseDelaySeconds() public view virtual returns (uint32) { + return _baseDelay; + } + + /** + * @dev Change the value of {baseDelaySeconds}. This operation can only be invoked through a governance proposal. + */ + function setBaseDelaySeconds(uint32 newBaseDelay) public virtual onlyGovernance { + _setBaseDelaySeconds(newBaseDelay); + } + + /** + * @dev Change the value of {baseDelaySeconds}. Internal function without access control. + */ + function _setBaseDelaySeconds(uint32 newBaseDelay) internal virtual { + emit BaseDelaySet(_baseDelay, newBaseDelay); + _baseDelay = newBaseDelay; + } + + /** + * @dev Check if restrictions from the associated {AccessManager} are ignored for a target function. Returns true + * when the target function will be invoked directly regardless of `AccessManager` settings for the function. + * See {setAccessManagerIgnored} and Security Considerations above. + */ + function isAccessManagerIgnored(address target, bytes4 selector) public view virtual returns (bool) { + bool isGovernor = target == address(this); + return _ignoreToggle[target][selector] != isGovernor; // equivalent to: isGovernor ? !toggle : toggle + } + + /** + * @dev Configure whether restrictions from the associated {AccessManager} are ignored for a target function. + * See Security Considerations above. + */ + function setAccessManagerIgnored( + address target, + bytes4[] calldata selectors, + bool ignored + ) public virtual onlyGovernance { + for (uint256 i = 0; i < selectors.length; ++i) { + _setAccessManagerIgnored(target, selectors[i], ignored); + } + } + + /** + * @dev Internal version of {setAccessManagerIgnored} without access restriction. + */ + function _setAccessManagerIgnored(address target, bytes4 selector, bool ignored) internal virtual { + bool isGovernor = target == address(this); + if (isGovernor && selector == this.setAccessManagerIgnored.selector) { + revert GovernorLockedIgnore(); + } + _ignoreToggle[target][selector] = ignored != isGovernor; // equivalent to: isGovernor ? !ignored : ignored + emit AccessManagerIgnoredSet(target, selector, ignored); + } + + /** + * @dev Public accessor to check the execution plan, including the number of seconds that the proposal will be + * delayed since queuing, an array indicating which of the proposal actions will be executed indirectly through + * the associated {AccessManager}, and another indicating which will be scheduled in {queue}. Note that + * those that must be scheduled are cancellable by `AccessManager` guardians. + */ + function proposalExecutionPlan( + uint256 proposalId + ) public view returns (uint32 delay, bool[] memory indirect, bool[] memory withDelay) { + ExecutionPlan storage plan = _executionPlan[proposalId]; + + uint32 length = plan.length; + delay = plan.delay; + indirect = new bool[](length); + withDelay = new bool[](length); + for (uint256 i = 0; i < length; ++i) { + (indirect[i], withDelay[i], ) = _getManagerData(plan, i); + } + + return (delay, indirect, withDelay); + } + + /** + * @dev See {IGovernor-proposalNeedsQueuing}. + */ + function proposalNeedsQueuing(uint256 proposalId) public view virtual override returns (bool) { + return _executionPlan[proposalId].delay > 0; + } + + /** + * @dev See {IGovernor-propose} + */ + function propose( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + string memory description + ) public virtual override returns (uint256) { + uint256 proposalId = super.propose(targets, values, calldatas, description); + + uint32 neededDelay = baseDelaySeconds(); + + ExecutionPlan storage plan = _executionPlan[proposalId]; + plan.length = SafeCast.toUint16(targets.length); + + for (uint256 i = 0; i < targets.length; ++i) { + address target = targets[i]; + bytes4 selector = bytes4(calldatas[i]); + (bool immediate, uint32 delay) = AuthorityUtils.canCallWithDelay( + address(_manager), + address(this), + target, + selector + ); + if ((immediate || delay > 0) && !isAccessManagerIgnored(target, selector)) { + _setManagerData(plan, i, !immediate, 0); + // downcast is safe because both arguments are uint32 + neededDelay = uint32(Math.max(delay, neededDelay)); + } + } + + plan.delay = neededDelay; + + return proposalId; + } + + /** + * @dev Mechanism to queue a proposal, potentially scheduling some of its operations in the AccessManager. + * + * NOTE: The execution delay is chosen based on the delay information retrieved in {propose}. This value may be + * off if the delay was updated since proposal creation. In this case, the proposal needs to be recreated. + */ + function _queueOperations( + uint256 proposalId, + address[] memory targets, + uint256[] memory /* values */, + bytes[] memory calldatas, + bytes32 /* descriptionHash */ + ) internal virtual override returns (uint48) { + ExecutionPlan storage plan = _executionPlan[proposalId]; + uint48 etaSeconds = Time.timestamp() + plan.delay; + + for (uint256 i = 0; i < targets.length; ++i) { + (, bool withDelay, ) = _getManagerData(plan, i); + if (withDelay) { + (, uint32 nonce) = _manager.schedule(targets[i], calldatas[i], etaSeconds); + _setManagerData(plan, i, true, nonce); + } + } + + return etaSeconds; + } + + /** + * @dev Mechanism to execute a proposal, potentially going through {AccessManager-execute} for delayed operations. + */ + function _executeOperations( + uint256 proposalId, + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 /* descriptionHash */ + ) internal virtual override { + uint48 etaSeconds = SafeCast.toUint48(proposalEta(proposalId)); + if (block.timestamp < etaSeconds) { + revert GovernorUnmetDelay(proposalId, etaSeconds); + } + + ExecutionPlan storage plan = _executionPlan[proposalId]; + + for (uint256 i = 0; i < targets.length; ++i) { + (bool controlled, bool withDelay, uint32 nonce) = _getManagerData(plan, i); + if (controlled) { + uint32 executedNonce = _manager.execute{value: values[i]}(targets[i], calldatas[i]); + if (withDelay && executedNonce != nonce) { + revert GovernorMismatchedNonce(proposalId, nonce, executedNonce); + } + } else { + (bool success, bytes memory returndata) = targets[i].call{value: values[i]}(calldatas[i]); + Address.verifyCallResult(success, returndata); + } + } + } + + /** + * @dev See {IGovernor-_cancel} + */ + function _cancel( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 descriptionHash + ) internal virtual override returns (uint256) { + uint256 proposalId = super._cancel(targets, values, calldatas, descriptionHash); + + uint48 etaSeconds = SafeCast.toUint48(proposalEta(proposalId)); + + ExecutionPlan storage plan = _executionPlan[proposalId]; + + // If the proposal has been scheduled it will have an ETA and we may have to externally cancel + if (etaSeconds != 0) { + for (uint256 i = 0; i < targets.length; ++i) { + (, bool withDelay, uint32 nonce) = _getManagerData(plan, i); + // Only attempt to cancel if the execution plan included a delay + if (withDelay) { + bytes32 operationId = _manager.hashOperation(address(this), targets[i], calldatas[i]); + // Check first if the current operation nonce is the one that we observed previously. It could + // already have been cancelled and rescheduled. We don't want to cancel unless it is exactly the + // instance that we previously scheduled. + if (nonce == _manager.getNonce(operationId)) { + // It is important that all calls have an opportunity to be cancelled. We chose to ignore + // potential failures of some of the cancel operations to give the other operations a chance to + // be properly cancelled. In particular cancel might fail if the operation was already cancelled + // by guardians previously. We don't match on the revert reason to avoid encoding assumptions + // about specific errors. + try _manager.cancel(address(this), targets[i], calldatas[i]) {} catch {} + } + } + } + } + + return proposalId; + } + + /** + * @dev Returns whether the operation at an index is delayed by the manager, and its scheduling nonce once queued. + */ + function _getManagerData( + ExecutionPlan storage plan, + uint256 index + ) private view returns (bool controlled, bool withDelay, uint32 nonce) { + (uint256 bucket, uint256 subindex) = _getManagerDataIndices(index); + uint32 value = plan.managerData[bucket][subindex]; + unchecked { + return (value > 0, value > 1, value > 1 ? value - 2 : 0); + } + } + + /** + * @dev Marks an operation at an index as permissioned by the manager, potentially delayed, and + * when delayed sets its scheduling nonce. + */ + function _setManagerData(ExecutionPlan storage plan, uint256 index, bool withDelay, uint32 nonce) private { + (uint256 bucket, uint256 subindex) = _getManagerDataIndices(index); + plan.managerData[bucket][subindex] = withDelay ? nonce + 2 : 1; + } + + /** + * @dev Returns bucket and subindex for reading manager data from the packed array mapping. + */ + function _getManagerDataIndices(uint256 index) private pure returns (uint256 bucket, uint256 subindex) { + bucket = index >> 3; // index / 8 + subindex = index & 7; // index % 8 + } +} diff --git a/contracts/governance/extensions/GovernorTimelockCompound.sol b/contracts/governance/extensions/GovernorTimelockCompound.sol new file mode 100644 index 000000000..8f2ec1863 --- /dev/null +++ b/contracts/governance/extensions/GovernorTimelockCompound.sol @@ -0,0 +1,167 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (governance/extensions/GovernorTimelockCompound.sol) + +pragma solidity ^0.8.20; + +import {IGovernor, Governor} from "../Governor.sol"; +import {ICompoundTimelock} from "../../vendor/compound/ICompoundTimelock.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 + * the external timelock to all successful proposal (in addition to the voting duration). The {Governor} needs to be + * the admin of the timelock for any operation to be performed. A public, unrestricted, + * {GovernorTimelockCompound-__acceptAdmin} is available to accept ownership of the timelock. + * + * 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. + */ +abstract contract GovernorTimelockCompound is Governor { + ICompoundTimelock private _timelock; + + /** + * @dev Emitted when the timelock controller used for proposal execution is modified. + */ + event TimelockChange(address oldTimelock, address newTimelock); + + /** + * @dev Set the timelock. + */ + constructor(ICompoundTimelock timelockAddress) { + _updateTimelock(timelockAddress); + } + + /** + * @dev Overridden version of the {Governor-state} function with added support for the `Expired` state. + */ + function state(uint256 proposalId) public view virtual override returns (ProposalState) { + ProposalState currentState = super.state(proposalId); + + 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 returns (address) { + return address(_timelock); + } + + /** + * @dev See {IGovernor-proposalNeedsQueuing}. + */ + function proposalNeedsQueuing(uint256) public view virtual override returns (bool) { + return true; + } + + /** + * @dev Function to queue a proposal to the timelock. + */ + function _queueOperations( + uint256 proposalId, + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 /*descriptionHash*/ + ) internal virtual override returns (uint48) { + uint48 etaSeconds = 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], etaSeconds))) + ) { + revert GovernorAlreadyQueuedProposal(proposalId); + } + _timelock.queueTransaction(targets[i], values[i], "", calldatas[i], etaSeconds); + } + + return etaSeconds; + } + + /** + * @dev Overridden version of the {Governor-_executeOperations} function that run the already queued proposal + * through the timelock. + */ + function _executeOperations( + uint256 proposalId, + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 /*descriptionHash*/ + ) internal virtual override { + uint256 etaSeconds = proposalEta(proposalId); + if (etaSeconds == 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], etaSeconds); + } + } + + /** + * @dev Overridden version of the {Governor-_cancel} function to cancel the timelocked proposal if it has already + * been queued. + */ + function _cancel( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 descriptionHash + ) internal virtual override returns (uint256) { + uint256 proposalId = super._cancel(targets, values, calldatas, descriptionHash); + + uint256 etaSeconds = proposalEta(proposalId); + if (etaSeconds > 0) { + // do external call later + for (uint256 i = 0; i < targets.length; ++i) { + _timelock.cancelTransaction(targets[i], values[i], "", calldatas[i], etaSeconds); + } + } + + return proposalId; + } + + /** + * @dev Address through which the governor executes action. In this case, the timelock. + */ + function _executor() internal view virtual override returns (address) { + return address(_timelock); + } + + /** + * @dev Accept admin right over the timelock. + */ + // solhint-disable-next-line private-vars-leading-underscore + function __acceptAdmin() public { + _timelock.acceptAdmin(); + } + + /** + * @dev Public endpoint to update the underlying timelock instance. Restricted to the timelock itself, so updates + * must be proposed, scheduled, and executed through governance proposals. + * + * For security reasons, the timelock must be handed over to another admin before setting up a new one. The two + * operations (hand over the timelock) and do the update can be batched in a single proposal. + * + * Note that if the timelock admin has been handed over in a previous operation, we refuse updates made through the + * timelock if admin of the timelock has already been accepted and the operation is executed outside the scope of + * governance. + + * CAUTION: It is not recommended to change the timelock while there are other queued governance proposals. + */ + function updateTimelock(ICompoundTimelock newTimelock) external virtual onlyGovernance { + _updateTimelock(newTimelock); + } + + function _updateTimelock(ICompoundTimelock newTimelock) private { + emit TimelockChange(address(_timelock), address(newTimelock)); + _timelock = newTimelock; + } +} diff --git a/contracts/governance/extensions/GovernorTimelockControl.sol b/contracts/governance/extensions/GovernorTimelockControl.sol new file mode 100644 index 000000000..c41eb7ee0 --- /dev/null +++ b/contracts/governance/extensions/GovernorTimelockControl.sol @@ -0,0 +1,173 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (governance/extensions/GovernorTimelockControl.sol) + +pragma solidity ^0.8.20; + +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 + * delay, enforced by the {TimelockController} to all successful proposal (in addition to the voting duration). The + * {Governor} needs the proposer (and ideally the executor) roles for the {Governor} to work properly. + * + * 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 from a proposal, unless executed via {Governor-relay}. + * + * WARNING: Setting up the TimelockController to have additional proposers or cancellers besides the governor is very + * risky, as it grants them the ability to: 1) execute operations as the timelock, and thus possibly performing + * operations or accessing funds that are expected to only be accessible through a vote, and 2) block governance + * proposals that have been approved by the voters, effectively executing a Denial of Service attack. + * + * NOTE: `AccessManager` does not support scheduling more than one operation with the same target and calldata at + * the same time. See {AccessManager-schedule} for a workaround. + */ +abstract contract GovernorTimelockControl is Governor { + TimelockController private _timelock; + mapping(uint256 proposalId => bytes32) private _timelockIds; + + /** + * @dev Emitted when the timelock controller used for proposal execution is modified. + */ + event TimelockChange(address oldTimelock, address newTimelock); + + /** + * @dev Set the timelock. + */ + constructor(TimelockController timelockAddress) { + _updateTimelock(timelockAddress); + } + + /** + * @dev Overridden version of the {Governor-state} function that considers the status reported by the timelock. + */ + function state(uint256 proposalId) public view virtual override returns (ProposalState) { + ProposalState currentState = super.state(proposalId); + + if (currentState != ProposalState.Queued) { + return currentState; + } + + bytes32 queueid = _timelockIds[proposalId]; + 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; + } + } + + /** + * @dev Public accessor to check the address of the timelock + */ + function timelock() public view virtual returns (address) { + return address(_timelock); + } + + /** + * @dev See {IGovernor-proposalNeedsQueuing}. + */ + function proposalNeedsQueuing(uint256) public view virtual override returns (bool) { + return true; + } + + /** + * @dev Function to queue a proposal to the timelock. + */ + function _queueOperations( + uint256 proposalId, + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 descriptionHash + ) 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); + + return SafeCast.toUint48(block.timestamp + delay); + } + + /** + * @dev Overridden version of the {Governor-_executeOperations} function that runs the already queued proposal + * through the timelock. + */ + function _executeOperations( + 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, _timelockSalt(descriptionHash)); + // cleanup for refund + delete _timelockIds[proposalId]; + } + + /** + * @dev Overridden version of the {Governor-_cancel} function to cancel the timelocked proposal if it has already + * been queued. + */ + // This function can reenter through the external call to the timelock, but we assume the timelock is trusted and + // well behaved (according to TimelockController) and this will not happen. + // slither-disable-next-line reentrancy-no-eth + function _cancel( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 descriptionHash + ) internal virtual override returns (uint256) { + uint256 proposalId = super._cancel(targets, values, calldatas, descriptionHash); + + bytes32 timelockId = _timelockIds[proposalId]; + if (timelockId != 0) { + // cancel + _timelock.cancel(timelockId); + // cleanup + delete _timelockIds[proposalId]; + } + + return proposalId; + } + + /** + * @dev Address through which the governor executes action. In this case, the timelock. + */ + function _executor() internal view virtual override returns (address) { + return address(_timelock); + } + + /** + * @dev Public endpoint to update the underlying timelock instance. Restricted to the timelock itself, so updates + * must be proposed, scheduled, and executed through governance proposals. + * + * CAUTION: It is not recommended to change the timelock while there are other queued governance proposals. + */ + function updateTimelock(TimelockController newTimelock) external virtual onlyGovernance { + _updateTimelock(newTimelock); + } + + function _updateTimelock(TimelockController newTimelock) private { + 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/contracts/governance/extensions/GovernorVotes.sol b/contracts/governance/extensions/GovernorVotes.sol new file mode 100644 index 000000000..d95fee1bc --- /dev/null +++ b/contracts/governance/extensions/GovernorVotes.sol @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (governance/extensions/GovernorVotes.sol) + +pragma solidity ^0.8.20; + +import {Governor} from "../Governor.sol"; +import {IVotes} from "../utils/IVotes.sol"; +import {IERC5805} from "../../interfaces/IERC5805.sol"; +import {SafeCast} from "../../utils/math/SafeCast.sol"; +import {Time} from "../../utils/types/Time.sol"; + +/** + * @dev Extension of {Governor} for voting weight extraction from an {ERC20Votes} token, or since v4.5 an {ERC721Votes} + * token. + */ +abstract contract GovernorVotes is Governor { + IERC5805 private immutable _token; + + constructor(IVotes tokenAddress) { + _token = IERC5805(address(tokenAddress)); + } + + /** + * @dev The token that voting power is sourced from. + */ + function token() public view virtual returns (IERC5805) { + return _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 Time.blockNumber(); + } + } + + /** + * @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().getPastVotes(account, timepoint); + } +} diff --git a/contracts/governance/extensions/GovernorVotesQuorumFraction.sol b/contracts/governance/extensions/GovernorVotesQuorumFraction.sol new file mode 100644 index 000000000..4a433889d --- /dev/null +++ b/contracts/governance/extensions/GovernorVotesQuorumFraction.sol @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (governance/extensions/GovernorVotesQuorumFraction.sol) + +pragma solidity ^0.8.20; + +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 + * fraction of the total supply. + */ +abstract contract GovernorVotesQuorumFraction is GovernorVotes { + using Checkpoints for Checkpoints.Trace208; + + Checkpoints.Trace208 private _quorumNumeratorHistory; + + 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. + * + * The fraction is specified as `numerator / denominator`. By default the denominator is 100, so quorum is + * specified as a percent: a numerator of 10 corresponds to quorum being 10% of total supply. The denominator can be + * customized by overriding {quorumDenominator}. + */ + constructor(uint256 quorumNumeratorValue) { + _updateQuorumNumerator(quorumNumeratorValue); + } + + /** + * @dev Returns the current quorum numerator. See {quorumDenominator}. + */ + function quorumNumerator() public view virtual returns (uint256) { + return _quorumNumeratorHistory.latest(); + } + + /** + * @dev Returns the quorum numerator at a specific timepoint. See {quorumDenominator}. + */ + function quorumNumerator(uint256 timepoint) public view virtual returns (uint256) { + uint256 length = _quorumNumeratorHistory._checkpoints.length; + + // Optimistic search, check the latest checkpoint + Checkpoints.Checkpoint208 storage latest = _quorumNumeratorHistory._checkpoints[length - 1]; + uint48 latestKey = latest._key; + uint208 latestValue = latest._value; + if (latestKey <= timepoint) { + return latestValue; + } + + // Otherwise, do the binary search + return _quorumNumeratorHistory.upperLookupRecent(SafeCast.toUint48(timepoint)); + } + + /** + * @dev Returns the quorum denominator. Defaults to 100, but may be overridden. + */ + function quorumDenominator() public view virtual returns (uint256) { + return 100; + } + + /** + * @dev Returns the quorum for a timepoint, in terms of number of votes: `supply * numerator / denominator`. + */ + function quorum(uint256 timepoint) public view virtual override returns (uint256) { + return (token().getPastTotalSupply(timepoint) * quorumNumerator(timepoint)) / quorumDenominator(); + } + + /** + * @dev Changes the quorum numerator. + * + * Emits a {QuorumNumeratorUpdated} event. + * + * Requirements: + * + * - Must be called through a governance proposal. + * - New numerator must be smaller or equal to the denominator. + */ + function updateQuorumNumerator(uint256 newQuorumNumerator) external virtual onlyGovernance { + _updateQuorumNumerator(newQuorumNumerator); + } + + /** + * @dev Changes the quorum numerator. + * + * Emits a {QuorumNumeratorUpdated} event. + * + * Requirements: + * + * - New numerator must be smaller or equal to the denominator. + */ + function _updateQuorumNumerator(uint256 newQuorumNumerator) internal virtual { + uint256 denominator = quorumDenominator(); + if (newQuorumNumerator > denominator) { + revert GovernorInvalidQuorumFraction(newQuorumNumerator, denominator); + } + + uint256 oldQuorumNumerator = quorumNumerator(); + _quorumNumeratorHistory.push(clock(), SafeCast.toUint208(newQuorumNumerator)); + + emit QuorumNumeratorUpdated(oldQuorumNumerator, newQuorumNumerator); + } +} diff --git a/contracts/governance/utils/IVotes.sol b/contracts/governance/utils/IVotes.sol new file mode 100644 index 000000000..7131ecf09 --- /dev/null +++ b/contracts/governance/utils/IVotes.sol @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (governance/utils/IVotes.sol) +pragma solidity ^0.8.20; + +/** + * @dev Common interface for {ERC20Votes}, {ERC721Votes}, and other {Votes}-enabled contracts. + */ +interface IVotes { + /** + * @dev The signature used has expired. + */ + error VotesExpiredSignature(uint256 expiry); + + /** + * @dev Emitted when an account changes their delegate. + */ + 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 voting units. + */ + event DelegateVotesChanged(address indexed delegate, uint256 previousVotes, uint256 newVotes); + + /** + * @dev Returns the current amount of votes that `account` has. + */ + function getVotes(address account) external view returns (uint256); + + /** + * @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 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 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 + * vote. + */ + function getPastTotalSupply(uint256 timepoint) external view returns (uint256); + + /** + * @dev Returns the delegate that `account` has chosen. + */ + function delegates(address account) external view returns (address); + + /** + * @dev Delegates votes from the sender to `delegatee`. + */ + function delegate(address delegatee) external; + + /** + * @dev Delegates votes from signer to `delegatee`. + */ + 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 new file mode 100644 index 000000000..56c8772f9 --- /dev/null +++ b/contracts/governance/utils/Votes.sol @@ -0,0 +1,251 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (governance/utils/Votes.sol) +pragma solidity ^0.8.20; + +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"; +import {Time} from "../../utils/types/Time.sol"; + +/** + * @dev This is a base abstract contract that tracks voting units, which are a measure of voting power that can be + * transferred, and provides a system of vote delegation, where an account can delegate its voting units to a sort of + * "representative" that will pool delegated voting units from different accounts and can then use it to vote in + * decisions. In fact, voting units _must_ be delegated in order to count as actual votes, and an account has to + * delegate those votes to itself if it wishes to participate in decisions and does not have a trusted representative. + * + * This contract is often combined with a token contract such that voting units correspond to token units. For an + * example, see {ERC721Votes}. + * + * The full history of delegate votes is tracked on-chain so that governance protocols can consider votes as distributed + * at a particular block number to protect against flash loans and double voting. The opt-in delegate system makes the + * cost of this history tracking optional. + * + * 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}). + */ +abstract contract Votes is Context, EIP712, Nonces, IERC5805 { + using Checkpoints for Checkpoints.Trace208; + + bytes32 private constant DELEGATION_TYPEHASH = + keccak256("Delegation(address delegatee,uint256 nonce,uint256 expiry)"); + + mapping(address account => address) private _delegatee; + + mapping(address delegatee => Checkpoints.Trace208) private _delegateCheckpoints; + + Checkpoints.Trace208 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. + */ + function clock() public view virtual returns (uint48) { + return Time.blockNumber(); + } + + /** + * @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 returns (string memory) { + // Check that the clock was not modified + if (clock() != Time.blockNumber()) { + revert ERC6372InconsistentClock(); + } + return "mode=blocknumber&from=default"; + } + + /** + * @dev Returns the current amount of votes that `account` has. + */ + function getVotes(address account) public view virtual returns (uint256) { + return _delegateCheckpoints[account].latest(); + } + + /** + * @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 at the end of the corresponding block. + * + * Requirements: + * + * - `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) { + uint48 currentTimepoint = clock(); + if (timepoint >= currentTimepoint) { + revert ERC5805FutureLookup(timepoint, currentTimepoint); + } + return _delegateCheckpoints[account].upperLookupRecent(SafeCast.toUint48(timepoint)); + } + + /** + * @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 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 + * vote. + * + * Requirements: + * + * - `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) { + uint48 currentTimepoint = clock(); + if (timepoint >= currentTimepoint) { + revert ERC5805FutureLookup(timepoint, currentTimepoint); + } + return _totalCheckpoints.upperLookupRecent(SafeCast.toUint48(timepoint)); + } + + /** + * @dev Returns the current total supply of votes. + */ + function _getTotalSupply() internal view virtual returns (uint256) { + return _totalCheckpoints.latest(); + } + + /** + * @dev Returns the delegate that `account` has chosen. + */ + function delegates(address account) public view virtual returns (address) { + return _delegatee[account]; + } + + /** + * @dev Delegates votes from the sender to `delegatee`. + */ + function delegate(address delegatee) public virtual { + address account = _msgSender(); + _delegate(account, delegatee); + } + + /** + * @dev Delegates votes from signer to `delegatee`. + */ + function delegateBySig( + address delegatee, + uint256 nonce, + uint256 expiry, + uint8 v, + bytes32 r, + bytes32 s + ) public virtual { + if (block.timestamp > expiry) { + revert VotesExpiredSignature(expiry); + } + address signer = ECDSA.recover( + _hashTypedDataV4(keccak256(abi.encode(DELEGATION_TYPEHASH, delegatee, nonce, expiry))), + v, + r, + s + ); + _useCheckedNonce(signer, nonce); + _delegate(signer, delegatee); + } + + /** + * @dev Delegate all of `account`'s voting units to `delegatee`. + * + * Emits events {IVotes-DelegateChanged} and {IVotes-DelegateVotesChanged}. + */ + function _delegate(address account, address delegatee) internal virtual { + address oldDelegate = delegates(account); + _delegatee[account] = delegatee; + + emit DelegateChanged(account, oldDelegate, delegatee); + _moveDelegateVotes(oldDelegate, delegatee, _getVotingUnits(account)); + } + + /** + * @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 { + if (from == address(0)) { + _push(_totalCheckpoints, _add, SafeCast.toUint208(amount)); + } + if (to == address(0)) { + _push(_totalCheckpoints, _subtract, SafeCast.toUint208(amount)); + } + _moveDelegateVotes(delegates(from), delegates(to), amount); + } + + /** + * @dev Moves delegated votes from one delegate to another. + */ + function _moveDelegateVotes(address from, address to, uint256 amount) private { + if (from != to && amount > 0) { + if (from != address(0)) { + (uint256 oldValue, uint256 newValue) = _push( + _delegateCheckpoints[from], + _subtract, + SafeCast.toUint208(amount) + ); + emit DelegateVotesChanged(from, oldValue, newValue); + } + if (to != address(0)) { + (uint256 oldValue, uint256 newValue) = _push( + _delegateCheckpoints[to], + _add, + SafeCast.toUint208(amount) + ); + emit DelegateVotesChanged(to, oldValue, newValue); + } + } + } + + /** + * @dev Get number of checkpoints for `account`. + */ + function _numCheckpoints(address account) internal view virtual returns (uint32) { + return SafeCast.toUint32(_delegateCheckpoints[account].length()); + } + + /** + * @dev Get the `pos`-th checkpoint for `account`. + */ + function _checkpoints( + address account, + uint32 pos + ) internal view virtual returns (Checkpoints.Checkpoint208 memory) { + return _delegateCheckpoints[account].at(pos); + } + + function _push( + Checkpoints.Trace208 storage store, + function(uint208, uint208) view returns (uint208) op, + uint208 delta + ) private returns (uint208, uint208) { + return store.push(clock(), op(store.latest(), delta)); + } + + function _add(uint208 a, uint208 b) private pure returns (uint208) { + return a + b; + } + + function _subtract(uint208 a, uint208 b) private pure returns (uint208) { + return a - b; + } + + /** + * @dev Must return the voting units held by an account. + */ + function _getVotingUnits(address) internal view virtual returns (uint256); +} diff --git a/contracts/interfaces/IERC1155.sol b/contracts/interfaces/IERC1155.sol new file mode 100644 index 000000000..4375627cb --- /dev/null +++ b/contracts/interfaces/IERC1155.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (interfaces/IERC1155.sol) + +pragma solidity ^0.8.20; + +import {IERC1155} from "../token/ERC1155/IERC1155.sol"; diff --git a/contracts/interfaces/IERC1155MetadataURI.sol b/contracts/interfaces/IERC1155MetadataURI.sol new file mode 100644 index 000000000..72448a2a8 --- /dev/null +++ b/contracts/interfaces/IERC1155MetadataURI.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (interfaces/IERC1155MetadataURI.sol) + +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 new file mode 100644 index 000000000..ec3aa3919 --- /dev/null +++ b/contracts/interfaces/IERC1155Receiver.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (interfaces/IERC1155Receiver.sol) + +pragma solidity ^0.8.20; + +import {IERC1155Receiver} from "../token/ERC1155/IERC1155Receiver.sol"; diff --git a/contracts/interfaces/IERC1271.sol b/contracts/interfaces/IERC1271.sol new file mode 100644 index 000000000..52a1ef2e1 --- /dev/null +++ b/contracts/interfaces/IERC1271.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (interfaces/IERC1271.sol) + +pragma solidity ^0.8.20; + +/** + * @dev Interface of the ERC1271 standard signature validation method for + * contracts as defined in https://eips.ethereum.org/EIPS/eip-1271[ERC-1271]. + */ +interface IERC1271 { + /** + * @dev Should return whether the signature provided is valid for the provided data + * @param hash Hash of the data to be signed + * @param signature Signature byte array associated with _data + */ + function isValidSignature(bytes32 hash, bytes memory signature) external view returns (bytes4 magicValue); +} diff --git a/contracts/interfaces/IERC1363.sol b/contracts/interfaces/IERC1363.sol new file mode 100644 index 000000000..6f32a6dcb --- /dev/null +++ b/contracts/interfaces/IERC1363.sol @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (interfaces/IERC1363.sol) + +pragma solidity ^0.8.20; + +import {IERC20} from "./IERC20.sol"; +import {IERC165} from "./IERC165.sol"; + +/** + * @dev Interface of an ERC1363 compliant contract, as defined in the + * https://eips.ethereum.org/EIPS/eip-1363[EIP]. + * + * Defines a interface for ERC20 tokens that supports executing recipient + * code after `transfer` or `transferFrom`, or spender code after `approve`. + */ +interface IERC1363 is IERC165, IERC20 { + /* + * Note: the ERC-165 identifier for this interface is 0xb0202a11. + * 0xb0202a11 === + * bytes4(keccak256('transferAndCall(address,uint256)')) ^ + * bytes4(keccak256('transferAndCall(address,uint256,bytes)')) ^ + * bytes4(keccak256('transferFromAndCall(address,address,uint256)')) ^ + * bytes4(keccak256('transferFromAndCall(address,address,uint256,bytes)')) ^ + * bytes4(keccak256('approveAndCall(address,uint256)')) ^ + * bytes4(keccak256('approveAndCall(address,uint256,bytes)')) + */ + + /** + * @dev Transfer tokens from `msg.sender` to another address and then call `onTransferReceived` on receiver + * @param to address The address which you want to transfer to + * @param amount uint256 The amount of tokens to be transferred + * @return true unless throwing + */ + function transferAndCall(address to, uint256 amount) external returns (bool); + + /** + * @dev Transfer tokens from `msg.sender` to another address and then call `onTransferReceived` on receiver + * @param to address The address which you want to transfer to + * @param amount uint256 The amount of tokens to be transferred + * @param data bytes Additional data with no specified format, sent in call to `to` + * @return true unless throwing + */ + function transferAndCall(address to, uint256 amount, bytes memory data) external returns (bool); + + /** + * @dev Transfer tokens from one address to another and then call `onTransferReceived` on receiver + * @param from address The address which you want to send tokens from + * @param to address The address which you want to transfer to + * @param amount uint256 The amount of tokens to be transferred + * @return true unless throwing + */ + function transferFromAndCall(address from, address to, uint256 amount) external returns (bool); + + /** + * @dev Transfer tokens from one address to another and then call `onTransferReceived` on receiver + * @param from address The address which you want to send tokens from + * @param to address The address which you want to transfer to + * @param amount uint256 The amount of tokens to be transferred + * @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 amount, bytes memory data) external returns (bool); + + /** + * @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender + * and then call `onApprovalReceived` on spender. + * @param spender address The address which will spend the funds + * @param amount uint256 The amount of tokens to be spent + */ + function approveAndCall(address spender, uint256 amount) external returns (bool); + + /** + * @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender + * and then call `onApprovalReceived` on spender. + * @param spender address The address which will spend the funds + * @param amount 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 amount, bytes memory data) external returns (bool); +} diff --git a/contracts/interfaces/IERC1363Receiver.sol b/contracts/interfaces/IERC1363Receiver.sol new file mode 100644 index 000000000..875fc60cc --- /dev/null +++ b/contracts/interfaces/IERC1363Receiver.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (interfaces/IERC1363Receiver.sol) + +pragma solidity ^0.8.20; + +/** + * @dev Interface for any contract that wants to support {IERC1363-transferAndCall} + * or {IERC1363-transferFromAndCall} from {ERC1363} token contracts. + */ +interface IERC1363Receiver { + /* + * Note: the ERC-165 identifier for this interface is 0x88a7ca5c. + * 0x88a7ca5c === bytes4(keccak256("onTransferReceived(address,address,uint256,bytes)")) + */ + + /** + * @notice Handle the receipt of ERC1363 tokens + * @dev Any ERC1363 smart contract calls this function on the recipient + * after a `transfer` or a `transferFrom`. This function MAY throw to revert and reject the + * transfer. Return of other than the magic value MUST result in the + * transaction being reverted. + * Note: the token contract address is always the message sender. + * @param operator address The address which called `transferAndCall` or `transferFromAndCall` function + * @param from address The address which are token transferred from + * @param amount uint256 The amount of tokens transferred + * @param data bytes Additional data with no specified format + * @return `bytes4(keccak256("onTransferReceived(address,address,uint256,bytes)"))` unless throwing + */ + function onTransferReceived( + address operator, + address from, + uint256 amount, + bytes memory data + ) external returns (bytes4); +} diff --git a/contracts/interfaces/IERC1363Spender.sol b/contracts/interfaces/IERC1363Spender.sol new file mode 100644 index 000000000..b68d9b534 --- /dev/null +++ b/contracts/interfaces/IERC1363Spender.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (interfaces/IERC1363Spender.sol) + +pragma solidity ^0.8.20; + +/** + * @dev Interface for any contract that wants to support {IERC1363-approveAndCall} + * from {ERC1363} token contracts. + */ +interface IERC1363Spender { + /* + * Note: the ERC-165 identifier for this interface is 0x7b04a2d0. + * 0x7b04a2d0 === bytes4(keccak256("onApprovalReceived(address,uint256,bytes)")) + */ + + /** + * @notice Handle the approval of ERC1363 tokens + * @dev Any ERC1363 smart contract calls this function on the recipient + * after an `approve`. This function MAY throw to revert and reject the + * approval. Return of other than the magic value MUST result in the + * transaction being reverted. + * Note: the token contract address is always the message sender. + * @param owner address The address which called `approveAndCall` function + * @param amount uint256 The amount of tokens to be spent + * @param data bytes Additional data with no specified format + * @return `bytes4(keccak256("onApprovalReceived(address,uint256,bytes)"))`unless throwing + */ + function onApprovalReceived(address owner, uint256 amount, bytes memory data) external returns (bytes4); +} diff --git a/contracts/interfaces/IERC165.sol b/contracts/interfaces/IERC165.sol new file mode 100644 index 000000000..e3cc30c37 --- /dev/null +++ b/contracts/interfaces/IERC165.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (interfaces/IERC165.sol) + +pragma solidity ^0.8.20; + +import {IERC165} from "../utils/introspection/IERC165.sol"; diff --git a/contracts/interfaces/IERC1820Implementer.sol b/contracts/interfaces/IERC1820Implementer.sol new file mode 100644 index 000000000..debe03c4e --- /dev/null +++ b/contracts/interfaces/IERC1820Implementer.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (interfaces/IERC1820Implementer.sol) + +pragma solidity ^0.8.20; + +/** + * @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 new file mode 100644 index 000000000..7e7a96276 --- /dev/null +++ b/contracts/interfaces/IERC1820Registry.sol @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (interfaces/IERC1820Registry.sol) + +pragma solidity ^0.8.20; + +/** + * @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/IERC1967.sol b/contracts/interfaces/IERC1967.sol new file mode 100644 index 000000000..9af812c1c --- /dev/null +++ b/contracts/interfaces/IERC1967.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (interfaces/IERC1967.sol) + +pragma solidity ^0.8.20; + +/** + * @dev ERC-1967: Proxy Storage Slots. This interface contains the events defined in the ERC. + */ +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/interfaces/IERC20.sol b/contracts/interfaces/IERC20.sol new file mode 100644 index 000000000..d97be9f3d --- /dev/null +++ b/contracts/interfaces/IERC20.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (interfaces/IERC20.sol) + +pragma solidity ^0.8.20; + +import {IERC20} from "../token/ERC20/IERC20.sol"; diff --git a/contracts/interfaces/IERC20Metadata.sol b/contracts/interfaces/IERC20Metadata.sol new file mode 100644 index 000000000..b8b33bf3e --- /dev/null +++ b/contracts/interfaces/IERC20Metadata.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (interfaces/IERC20Metadata.sol) + +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 new file mode 100644 index 000000000..49d9be1fc --- /dev/null +++ b/contracts/interfaces/IERC2309.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (interfaces/IERC2309.sol) + +pragma solidity ^0.8.20; + +/** + * @dev ERC-2309: ERC-721 Consecutive Transfer Extension. + */ +interface IERC2309 { + /** + * @dev Emitted when the tokens from `fromTokenId` to `toTokenId` are transferred from `fromAddress` to `toAddress`. + */ + event ConsecutiveTransfer( + uint256 indexed fromTokenId, + uint256 toTokenId, + address indexed fromAddress, + address indexed toAddress + ); +} diff --git a/contracts/interfaces/IERC2612.sol b/contracts/interfaces/IERC2612.sol new file mode 100644 index 000000000..626eb00b1 --- /dev/null +++ b/contracts/interfaces/IERC2612.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (interfaces/IERC2612.sol) + +pragma solidity ^0.8.20; + +import {IERC20Permit} from "../token/ERC20/extensions/IERC20Permit.sol"; + +interface IERC2612 is IERC20Permit {} diff --git a/contracts/interfaces/IERC2981.sol b/contracts/interfaces/IERC2981.sol new file mode 100644 index 000000000..16244ab82 --- /dev/null +++ b/contracts/interfaces/IERC2981.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (interfaces/IERC2981.sol) + +pragma solidity ^0.8.20; + +import {IERC165} from "../utils/introspection/IERC165.sol"; + +/** + * @dev Interface for the NFT Royalty Standard. + * + * 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. + */ +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); +} diff --git a/contracts/interfaces/IERC3156.sol b/contracts/interfaces/IERC3156.sol new file mode 100644 index 000000000..1446e040d --- /dev/null +++ b/contracts/interfaces/IERC3156.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (interfaces/IERC3156.sol) + +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 new file mode 100644 index 000000000..ded8ef2f4 --- /dev/null +++ b/contracts/interfaces/IERC3156FlashBorrower.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (interfaces/IERC3156FlashBorrower.sol) + +pragma solidity ^0.8.20; + +/** + * @dev Interface of the ERC3156 FlashBorrower, as defined in + * https://eips.ethereum.org/EIPS/eip-3156[ERC-3156]. + */ +interface IERC3156FlashBorrower { + /** + * @dev Receive a flash loan. + * @param initiator The initiator of the loan. + * @param token The loan currency. + * @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 "ERC3156FlashBorrower.onFlashLoan" + */ + function onFlashLoan( + address initiator, + address token, + uint256 amount, + uint256 fee, + bytes calldata data + ) external returns (bytes32); +} diff --git a/contracts/interfaces/IERC3156FlashLender.sol b/contracts/interfaces/IERC3156FlashLender.sol new file mode 100644 index 000000000..1ad0decac --- /dev/null +++ b/contracts/interfaces/IERC3156FlashLender.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (interfaces/IERC3156FlashLender.sol) + +pragma solidity ^0.8.20; + +import {IERC3156FlashBorrower} from "./IERC3156FlashBorrower.sol"; + +/** + * @dev Interface of the ERC3156 FlashLender, as defined in + * https://eips.ethereum.org/EIPS/eip-3156[ERC-3156]. + */ +interface IERC3156FlashLender { + /** + * @dev The amount of currency available to be lended. + * @param token The loan currency. + * @return The amount of `token` that can be borrowed. + */ + function maxFlashLoan(address token) external view returns (uint256); + + /** + * @dev The fee to be charged for a given loan. + * @param token The loan currency. + * @param amount The amount of tokens lent. + * @return The amount of `token` to be charged for the loan, on top of the returned principal. + */ + function flashFee(address token, uint256 amount) external view returns (uint256); + + /** + * @dev Initiate a flash loan. + * @param receiver The receiver of the tokens in the loan, and the receiver of the callback. + * @param token The loan currency. + * @param amount The amount of tokens lent. + * @param data Arbitrary data structure, intended to contain user-defined parameters. + */ + function flashLoan( + IERC3156FlashBorrower receiver, + address token, + uint256 amount, + bytes calldata data + ) external returns (bool); +} diff --git a/contracts/interfaces/IERC4626.sol b/contracts/interfaces/IERC4626.sol new file mode 100644 index 000000000..bd76c8427 --- /dev/null +++ b/contracts/interfaces/IERC4626.sol @@ -0,0 +1,230 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (interfaces/IERC4626.sol) + +pragma solidity ^0.8.20; + +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 + * https://eips.ethereum.org/EIPS/eip-4626[ERC-4626]. + */ +interface IERC4626 is IERC20, IERC20Metadata { + event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares); + + event Withdraw( + address indexed sender, + address indexed receiver, + address indexed owner, + uint256 assets, + uint256 shares + ); + + /** + * @dev Returns the address of the underlying token used for the Vault for accounting, depositing, and withdrawing. + * + * - MUST be an ERC-20 token contract. + * - MUST NOT revert. + */ + function asset() external view returns (address assetTokenAddress); + + /** + * @dev Returns the total amount of the underlying asset that is β€œmanaged” by Vault. + * + * - SHOULD include any compounding that occurs from yield. + * - MUST be inclusive of any fees that are charged against assets in the Vault. + * - MUST NOT revert. + */ + function totalAssets() external view returns (uint256 totalManagedAssets); + + /** + * @dev Returns the amount of shares that the Vault would exchange for the amount of assets provided, in an ideal + * scenario where all the conditions are met. + * + * - MUST NOT be inclusive of any fees that are charged against assets in the Vault. + * - MUST NOT show any variations depending on the caller. + * - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange. + * - MUST NOT revert. + * + * NOTE: This calculation MAY NOT reflect the β€œper-user” price-per-share, and instead should reflect the + * β€œaverage-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and + * from. + */ + function convertToShares(uint256 assets) external view returns (uint256 shares); + + /** + * @dev Returns the amount of assets that the Vault would exchange for the amount of shares provided, in an ideal + * scenario where all the conditions are met. + * + * - MUST NOT be inclusive of any fees that are charged against assets in the Vault. + * - MUST NOT show any variations depending on the caller. + * - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange. + * - MUST NOT revert. + * + * NOTE: This calculation MAY NOT reflect the β€œper-user” price-per-share, and instead should reflect the + * β€œaverage-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and + * from. + */ + function convertToAssets(uint256 shares) external view returns (uint256 assets); + + /** + * @dev Returns the maximum amount of the underlying asset that can be deposited into the Vault for the receiver, + * through a deposit call. + * + * - MUST return a limited value if receiver is subject to some deposit limit. + * - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of assets that may be deposited. + * - MUST NOT revert. + */ + function maxDeposit(address receiver) external view returns (uint256 maxAssets); + + /** + * @dev Allows an on-chain or off-chain user to simulate the effects of their deposit at the current block, given + * current on-chain conditions. + * + * - MUST return as close to and no more than the exact amount of Vault shares that would be minted in a deposit + * call in the same transaction. I.e. deposit should return the same or more shares as previewDeposit if called + * in the same transaction. + * - MUST NOT account for deposit limits like those returned from maxDeposit and should always act as though the + * deposit would be accepted, regardless if the user has enough tokens approved, etc. + * - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees. + * - MUST NOT revert. + * + * NOTE: any unfavorable discrepancy between convertToShares and previewDeposit SHOULD be considered slippage in + * share price or some other type of condition, meaning the depositor will lose assets by depositing. + */ + function previewDeposit(uint256 assets) external view returns (uint256 shares); + + /** + * @dev Mints shares Vault shares to receiver by depositing exactly amount of underlying tokens. + * + * - MUST emit the Deposit event. + * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the + * deposit execution, and are accounted for during deposit. + * - MUST revert if all of assets cannot be deposited (due to deposit limit being reached, slippage, the user not + * approving enough underlying tokens to the Vault contract, etc). + * + * NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token. + */ + function deposit(uint256 assets, address receiver) external returns (uint256 shares); + + /** + * @dev Returns the maximum amount of the Vault shares that can be minted for the receiver, through a mint call. + * - MUST return a limited value if receiver is subject to some mint limit. + * - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of shares that may be minted. + * - MUST NOT revert. + */ + function maxMint(address receiver) external view returns (uint256 maxShares); + + /** + * @dev Allows an on-chain or off-chain user to simulate the effects of their mint at the current block, given + * current on-chain conditions. + * + * - MUST return as close to and no fewer than the exact amount of assets that would be deposited in a mint call + * in the same transaction. I.e. mint should return the same or fewer assets as previewMint if called in the + * same transaction. + * - MUST NOT account for mint limits like those returned from maxMint and should always act as though the mint + * would be accepted, regardless if the user has enough tokens approved, etc. + * - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees. + * - MUST NOT revert. + * + * NOTE: any unfavorable discrepancy between convertToAssets and previewMint SHOULD be considered slippage in + * share price or some other type of condition, meaning the depositor will lose assets by minting. + */ + function previewMint(uint256 shares) external view returns (uint256 assets); + + /** + * @dev Mints exactly shares Vault shares to receiver by depositing amount of underlying tokens. + * + * - MUST emit the Deposit event. + * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the mint + * execution, and are accounted for during mint. + * - MUST revert if all of shares cannot be minted (due to deposit limit being reached, slippage, the user not + * approving enough underlying tokens to the Vault contract, etc). + * + * NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token. + */ + function mint(uint256 shares, address receiver) external returns (uint256 assets); + + /** + * @dev Returns the maximum amount of the underlying asset that can be withdrawn from the owner balance in the + * Vault, through a withdraw call. + * + * - MUST return a limited value if owner is subject to some withdrawal limit or timelock. + * - MUST NOT revert. + */ + function maxWithdraw(address owner) external view returns (uint256 maxAssets); + + /** + * @dev Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the current block, + * given current on-chain conditions. + * + * - MUST return as close to and no fewer than the exact amount of Vault shares that would be burned in a withdraw + * call in the same transaction. I.e. withdraw should return the same or fewer shares as previewWithdraw if + * called + * in the same transaction. + * - MUST NOT account for withdrawal limits like those returned from maxWithdraw and should always act as though + * the withdrawal would be accepted, regardless if the user has enough shares, etc. + * - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees. + * - MUST NOT revert. + * + * NOTE: any unfavorable discrepancy between convertToShares and previewWithdraw SHOULD be considered slippage in + * share price or some other type of condition, meaning the depositor will lose assets by depositing. + */ + function previewWithdraw(uint256 assets) external view returns (uint256 shares); + + /** + * @dev Burns shares from owner and sends exactly assets of underlying tokens to receiver. + * + * - MUST emit the Withdraw event. + * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the + * withdraw execution, and are accounted for during withdraw. + * - MUST revert if all of assets cannot be withdrawn (due to withdrawal limit being reached, slippage, the owner + * not having enough shares, etc). + * + * 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); + + /** + * @dev Returns the maximum amount of Vault shares that can be redeemed from the owner balance in the Vault, + * through a redeem call. + * + * - MUST return a limited value if owner is subject to some withdrawal limit or timelock. + * - MUST return balanceOf(owner) if owner is not subject to any withdrawal limit or timelock. + * - MUST NOT revert. + */ + function maxRedeem(address owner) external view returns (uint256 maxShares); + + /** + * @dev Allows an on-chain or off-chain user to simulate the effects of their redeemption at the current block, + * given current on-chain conditions. + * + * - MUST return as close to and no more than the exact amount of assets that would be withdrawn in a redeem call + * in the same transaction. I.e. redeem should return the same or more assets as previewRedeem if called in the + * same transaction. + * - MUST NOT account for redemption limits like those returned from maxRedeem and should always act as though the + * redemption would be accepted, regardless if the user has enough shares, etc. + * - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees. + * - MUST NOT revert. + * + * NOTE: any unfavorable discrepancy between convertToAssets and previewRedeem SHOULD be considered slippage in + * share price or some other type of condition, meaning the depositor will lose assets by redeeming. + */ + function previewRedeem(uint256 shares) external view returns (uint256 assets); + + /** + * @dev Burns exactly shares from owner and sends assets of underlying tokens to receiver. + * + * - MUST emit the Withdraw event. + * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the + * redeem execution, and are accounted for during redeem. + * - MUST revert if all of shares cannot be redeemed (due to withdrawal limit being reached, slippage, the owner + * not having enough shares, etc). + * + * 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); +} diff --git a/contracts/interfaces/IERC4906.sol b/contracts/interfaces/IERC4906.sol new file mode 100644 index 000000000..ba7fbd3a6 --- /dev/null +++ b/contracts/interfaces/IERC4906.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (interfaces/IERC4906.sol) + +pragma solidity ^0.8.20; + +import {IERC165} from "./IERC165.sol"; +import {IERC721} from "./IERC721.sol"; + +/// @title EIP-721 Metadata Update Extension +interface IERC4906 is IERC165, IERC721 { + /// @dev This event emits when the metadata of a token is changed. + /// So that the third-party platforms such as NFT market could + /// timely update the images and related attributes of the NFT. + event MetadataUpdate(uint256 _tokenId); + + /// @dev This event emits when the metadata of a range of tokens is changed. + /// So that the third-party platforms such as NFT market could + /// timely update the images and related attributes of the NFTs. + event BatchMetadataUpdate(uint256 _fromTokenId, uint256 _toTokenId); +} diff --git a/contracts/interfaces/IERC5267.sol b/contracts/interfaces/IERC5267.sol new file mode 100644 index 000000000..6e0a9bc23 --- /dev/null +++ b/contracts/interfaces/IERC5267.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (interfaces/IERC5267.sol) + +pragma solidity ^0.8.20; + +interface IERC5267 { + /** + * @dev MAY be emitted to signal that the domain could have changed. + */ + event EIP712DomainChanged(); + + /** + * @dev returns the fields and values that describe the domain separator used by this contract for EIP-712 + * signature. + */ + function eip712Domain() + external + view + returns ( + bytes1 fields, + string memory name, + string memory version, + uint256 chainId, + address verifyingContract, + bytes32 salt, + uint256[] memory extensions + ); +} diff --git a/contracts/interfaces/IERC5313.sol b/contracts/interfaces/IERC5313.sol new file mode 100644 index 000000000..61556dd08 --- /dev/null +++ b/contracts/interfaces/IERC5313.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (interfaces/IERC5313.sol) + +pragma solidity ^0.8.20; + +/** + * @dev Interface for the Light Contract Ownership Standard. + * + * A standardized minimal interface required to identify an account that controls a contract + */ +interface IERC5313 { + /** + * @dev Gets the address of the owner. + */ + function owner() external view returns (address); +} diff --git a/contracts/interfaces/IERC5805.sol b/contracts/interfaces/IERC5805.sol new file mode 100644 index 000000000..1d7451d0a --- /dev/null +++ b/contracts/interfaces/IERC5805.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (interfaces/IERC5805.sol) + +pragma solidity ^0.8.20; + +import {IVotes} from "../governance/utils/IVotes.sol"; +import {IERC6372} from "./IERC6372.sol"; + +interface IERC5805 is IERC6372, IVotes {} diff --git a/contracts/interfaces/IERC6372.sol b/contracts/interfaces/IERC6372.sol new file mode 100644 index 000000000..f4973ecb1 --- /dev/null +++ b/contracts/interfaces/IERC6372.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (interfaces/IERC6372.sol) + +pragma solidity ^0.8.20; + +interface IERC6372 { + /** + * @dev Clock used for flagging checkpoints. Can be overridden to implement timestamp based checkpoints (and voting). + */ + function clock() external view returns (uint48); + + /** + * @dev Description of the clock + */ + // solhint-disable-next-line func-name-mixedcase + function CLOCK_MODE() external view returns (string memory); +} diff --git a/contracts/interfaces/IERC721.sol b/contracts/interfaces/IERC721.sol new file mode 100644 index 000000000..d671eca40 --- /dev/null +++ b/contracts/interfaces/IERC721.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (interfaces/IERC721.sol) + +pragma solidity ^0.8.20; + +import {IERC721} from "../token/ERC721/IERC721.sol"; diff --git a/contracts/interfaces/IERC721Enumerable.sol b/contracts/interfaces/IERC721Enumerable.sol new file mode 100644 index 000000000..4134593f4 --- /dev/null +++ b/contracts/interfaces/IERC721Enumerable.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (interfaces/IERC721Enumerable.sol) + +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 new file mode 100644 index 000000000..6d8452e8e --- /dev/null +++ b/contracts/interfaces/IERC721Metadata.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (interfaces/IERC721Metadata.sol) + +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 new file mode 100644 index 000000000..950e0a410 --- /dev/null +++ b/contracts/interfaces/IERC721Receiver.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (interfaces/IERC721Receiver.sol) + +pragma solidity ^0.8.20; + +import {IERC721Receiver} from "../token/ERC721/IERC721Receiver.sol"; diff --git a/contracts/interfaces/IERC777.sol b/contracts/interfaces/IERC777.sol new file mode 100644 index 000000000..a432ca69a --- /dev/null +++ b/contracts/interfaces/IERC777.sol @@ -0,0 +1,200 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (interfaces/IERC777.sol) + +pragma solidity ^0.8.20; + +/** + * @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 new file mode 100644 index 000000000..2a937f5cd --- /dev/null +++ b/contracts/interfaces/IERC777Recipient.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (interfaces/IERC777Recipient.sol) + +pragma solidity ^0.8.20; + +/** + * @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 new file mode 100644 index 000000000..5c10973f5 --- /dev/null +++ b/contracts/interfaces/IERC777Sender.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (interfaces/IERC777Sender.sol) + +pragma solidity ^0.8.20; + +/** + * @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/README.adoc b/contracts/interfaces/README.adoc new file mode 100644 index 000000000..379a24a1e --- /dev/null +++ b/contracts/interfaces/README.adoc @@ -0,0 +1,82 @@ += Interfaces + +[.readme-notice] +NOTE: This document is better viewed at https://docs.openzeppelin.com/contracts/api/interfaces + +== List of standardized interfaces +These interfaces are available as `.sol` files, and also as compiler `.json` ABI files (through the npm package). These +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} +- {IERC1363Spender} +- {IERC1820Implementer} +- {IERC1820Registry} +- {IERC1822Proxiable} +- {IERC2612} +- {IERC2981} +- {IERC3156FlashLender} +- {IERC3156FlashBorrower} +- {IERC4626} +- {IERC4906} +- {IERC5267} +- {IERC5313} +- {IERC5805} +- {IERC6372} + +== Detailed ABI + +{{IERC20Errors}} + +{{IERC721Errors}} + +{{IERC1155Errors}} + +{{IERC1271}} + +{{IERC1363}} + +{{IERC1363Receiver}} + +{{IERC1363Spender}} + +{{IERC1820Implementer}} + +{{IERC1820Registry}} + +{{IERC1822Proxiable}} + +{{IERC2612}} + +{{IERC2981}} + +{{IERC3156FlashLender}} + +{{IERC3156FlashBorrower}} + +{{IERC4626}} + +{{IERC5313}} + +{{IERC5267}} + +{{IERC5805}} + +{{IERC6372}} diff --git a/contracts/interfaces/draft-IERC1822.sol b/contracts/interfaces/draft-IERC1822.sol new file mode 100644 index 000000000..c3679a6e6 --- /dev/null +++ b/contracts/interfaces/draft-IERC1822.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (interfaces/draft-IERC1822.sol) + +pragma solidity ^0.8.20; + +/** + * @dev ERC1822: Universal Upgradeable Proxy Standard (UUPS) documents a method for upgradeability through a simplified + * proxy whose upgrades are fully controlled by the current implementation. + */ +interface IERC1822Proxiable { + /** + * @dev Returns the storage slot that the proxiable contract assumes is being used to store the implementation + * address. + * + * IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks + * 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. + */ + function proxiableUUID() external view returns (bytes32); +} diff --git a/contracts/interfaces/draft-IERC6093.sol b/contracts/interfaces/draft-IERC6093.sol new file mode 100644 index 000000000..c6bcfff58 --- /dev/null +++ b/contracts/interfaces/draft-IERC6093.sol @@ -0,0 +1,161 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (interfaces/draft-IERC6093.sol) +pragma solidity ^0.8.20; + +/** + * @dev Standard ERC20 Errors + * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC20 tokens. + */ +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 https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC721 tokens. + */ +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 https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC1155 tokens. + */ +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. + * @param tokenId Identifier number of a token. + */ + 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 ERC1155MissingApprovalForAll(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/ERC2771Context.sol b/contracts/metatx/ERC2771Context.sol new file mode 100644 index 000000000..5b5954e6a --- /dev/null +++ b/contracts/metatx/ERC2771Context.sol @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (metatx/ERC2771Context.sol) + +pragma solidity ^0.8.20; + +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 + 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_; + } + + /** + * @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(); + } + + /** + * @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`. + /// @solidity memory-safe-assembly + assembly { + sender := shr(96, calldataload(sub(calldatasize(), 20))) + } + } else { + return super._msgSender(); + } + } + + /** + * @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]; + } else { + return super._msgData(); + } + } +} diff --git a/contracts/metatx/ERC2771Forwarder.sol b/contracts/metatx/ERC2771Forwarder.sol new file mode 100644 index 000000000..52984057f --- /dev/null +++ b/contracts/metatx/ERC2771Forwarder.sol @@ -0,0 +1,370 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (metatx/ERC2771Forwarder.sol) + +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"; +import {Address} from "../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. + * + * 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. + * + * 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 + * 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; + + struct ForwardRequestData { + address from; + address to; + uint256 value; + uint256 gas; + uint48 deadline; + bytes data; + bytes signature; + } + + bytes32 internal 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 The request target doesn't trust the `forwarder`. + */ + error ERC2771UntrustfulTarget(address target, address forwarder); + + /** + * @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 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 isTrustedForwarder, bool active, bool signerMatch, ) = _validate(request); + return isTrustedForwarder && active && 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 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 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 (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) + ) + ) + ).tryRecover(request.signature); + + return (err == ECDSA.RecoverError.NoError, recovered); + } + + /** + * @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 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 (!isTrustedForwarder) { + revert ERC2771UntrustfulTarget(request.to, address(this)); + } + + if (!active) { + revert ERC2771ForwarderExpiredRequest(request.deadline); + } + + if (!signerMatch) { + revert ERC2771ForwarderInvalidSigner(signer, request.from); + } + } + + // Ignore an invalid request because requireValidRequest = false + if (isTrustedForwarder && signerMatch && active) { + // Nonce should be used before the call to prevent reusing by reentrancy + uint256 currentNonce = _useNonce(signer); + + uint256 reqGas = request.gas; + address to = request.to; + uint256 value = request.value; + bytes memory data = abi.encodePacked(request.data, request.from); + + 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); + } + } + + /** + * @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. + * + * 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: 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(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 + // 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.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/metatx/README.adoc b/contracts/metatx/README.adoc new file mode 100644 index 000000000..9f25802e4 --- /dev/null +++ b/contracts/metatx/README.adoc @@ -0,0 +1,12 @@ += Meta Transactions + +[.readme-notice] +NOTE: This document is better viewed at https://docs.openzeppelin.com/contracts/api/metatx + +== Core + +{{ERC2771Context}} + +== Utils + +{{ERC2771Forwarder}} diff --git a/contracts/mocks/AccessManagedTarget.sol b/contracts/mocks/AccessManagedTarget.sol new file mode 100644 index 000000000..305c989f6 --- /dev/null +++ b/contracts/mocks/AccessManagedTarget.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {AccessManaged} from "../access/manager/AccessManaged.sol"; + +abstract contract AccessManagedTarget is AccessManaged { + event CalledRestricted(address caller); + event CalledUnrestricted(address caller); + + function fnRestricted() public restricted { + emit CalledRestricted(msg.sender); + } + + function fnUnrestricted() public { + emit CalledUnrestricted(msg.sender); + } +} diff --git a/contracts/mocks/ArraysMock.sol b/contracts/mocks/ArraysMock.sol new file mode 100644 index 000000000..a00def29c --- /dev/null +++ b/contracts/mocks/ArraysMock.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {Arrays} from "../utils/Arrays.sol"; + +contract Uint256ArraysMock { + using Arrays for uint256[]; + + uint256[] private _array; + + constructor(uint256[] memory array) { + _array = array; + } + + function findUpperBound(uint256 element) external view returns (uint256) { + return _array.findUpperBound(element); + } + + function unsafeAccess(uint256 pos) external view returns (uint256) { + return _array.unsafeAccess(pos).value; + } +} + +contract AddressArraysMock { + using Arrays for address[]; + + address[] private _array; + + constructor(address[] memory array) { + _array = array; + } + + function unsafeAccess(uint256 pos) external view returns (address) { + return _array.unsafeAccess(pos).value; + } +} + +contract Bytes32ArraysMock { + using Arrays for bytes32[]; + + bytes32[] private _array; + + constructor(bytes32[] memory array) { + _array = array; + } + + function unsafeAccess(uint256 pos) external view returns (bytes32) { + return _array.unsafeAccess(pos).value; + } +} diff --git a/contracts/mocks/CallReceiverMock.sol b/contracts/mocks/CallReceiverMock.sol new file mode 100644 index 000000000..e371c7db8 --- /dev/null +++ b/contracts/mocks/CallReceiverMock.sol @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +contract CallReceiverMock { + event MockFunctionCalled(); + event MockFunctionCalledWithArgs(uint256 a, uint256 b); + + uint256[] private _array; + + function mockFunction() public payable returns (string memory) { + emit MockFunctionCalled(); + + return "0x1234"; + } + + function mockFunctionEmptyReturn() public payable { + emit MockFunctionCalled(); + } + + function mockFunctionWithArgs(uint256 a, uint256 b) public payable returns (string memory) { + emit MockFunctionCalledWithArgs(a, b); + + return "0x1234"; + } + + function mockFunctionNonPayable() public returns (string memory) { + emit MockFunctionCalled(); + + return "0x1234"; + } + + function mockStaticFunction() public pure returns (string memory) { + return "0x1234"; + } + + function mockFunctionRevertsNoReason() public payable { + revert(); + } + + function mockFunctionRevertsReason() public payable { + revert("CallReceiverMock: reverting"); + } + + function mockFunctionThrows() public payable { + assert(false); + } + + function mockFunctionOutOfGas() public payable { + for (uint256 i = 0; ; ++i) { + _array.push(i); + } + } + + function mockFunctionWritesStorage(bytes32 slot, bytes32 value) public returns (string memory) { + assembly { + sstore(slot, value) + } + 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/ContextMock.sol b/contracts/mocks/ContextMock.sol new file mode 100644 index 000000000..199b2a978 --- /dev/null +++ b/contracts/mocks/ContextMock.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {Context} from "../utils/Context.sol"; + +contract ContextMock is Context { + event Sender(address sender); + + function msgSender() public { + emit Sender(_msgSender()); + } + + event Data(bytes data, uint256 integerValue, string stringValue); + + 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 { + function callSender(ContextMock context) public { + context.msgSender(); + } + + function callData(ContextMock context, uint256 integerValue, string memory stringValue) public { + context.msgData(integerValue, stringValue); + } +} diff --git a/contracts/mocks/DummyImplementation.sol b/contracts/mocks/DummyImplementation.sol new file mode 100644 index 000000000..4925c89df --- /dev/null +++ b/contracts/mocks/DummyImplementation.sol @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {ERC1967Utils} from "../proxy/ERC1967/ERC1967Utils.sol"; +import {StorageSlot} from "../utils/StorageSlot.sol"; + +abstract contract Impl { + function version() public pure virtual returns (string memory); +} + +contract DummyImplementation { + uint256 public value; + string public text; + uint256[] public values; + + function initializeNonPayable() public { + value = 10; + } + + function initializePayable() public payable { + value = 100; + } + + function initializeNonPayableWithValue(uint256 _value) public { + value = _value; + } + + function initializePayableWithValue(uint256 _value) public payable { + value = _value; + } + + function initialize(uint256 _value, string memory _text, uint256[] memory _values) public { + value = _value; + text = _text; + values = _values; + } + + function get() public pure returns (bool) { + return true; + } + + function version() public pure virtual returns (string memory) { + return "V1"; + } + + 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 { + function migrate(uint256 newVal) public payable { + value = newVal; + } + + function version() public pure override returns (string memory) { + return "V2"; + } +} diff --git a/contracts/mocks/EIP712Verifier.sol b/contracts/mocks/EIP712Verifier.sol new file mode 100644 index 000000000..fe32a2189 --- /dev/null +++ b/contracts/mocks/EIP712Verifier.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +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 { + bytes32 digest = _hashTypedDataV4( + keccak256(abi.encode(keccak256("Mail(address to,string contents)"), mailTo, keccak256(bytes(mailContents)))) + ); + address recoveredSigner = ECDSA.recover(digest, signature); + require(recoveredSigner == signer); + } +} diff --git a/contracts/mocks/ERC1271WalletMock.sol b/contracts/mocks/ERC1271WalletMock.sol new file mode 100644 index 000000000..cba7d47d7 --- /dev/null +++ b/contracts/mocks/ERC1271WalletMock.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +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) {} + + 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 returns (bytes4) { + assembly { + mstore(0, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff) + return(0, 32) + } + } +} diff --git a/contracts/mocks/ERC165/ERC165InterfacesSupported.sol b/contracts/mocks/ERC165/ERC165InterfacesSupported.sol new file mode 100644 index 000000000..4010b2103 --- /dev/null +++ b/contracts/mocks/ERC165/ERC165InterfacesSupported.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {IERC165} from "../../utils/introspection/IERC165.sol"; + +/** + * https://eips.ethereum.org/EIPS/eip-214#specification + * From the specification: + * > Any attempts to make state-changing operations inside an execution instance with STATIC set to true will instead + * throw an exception. + * > These operations include [...], LOG0, LOG1, LOG2, [...] + * + * therefore, because this contract is staticcall'd we need to not emit events (which is how solidity-coverage works) + * solidity-coverage ignores the /mocks folder, so we duplicate its implementation here to avoid instrumenting it + */ +contract SupportsInterfaceWithLookupMock is IERC165 { + /* + * bytes4(keccak256('supportsInterface(bytes4)')) == 0x01ffc9a7 + */ + bytes4 public constant INTERFACE_ID_ERC165 = 0x01ffc9a7; + + /** + * @dev A mapping of interface id to whether or not it's supported. + */ + mapping(bytes4 interfaceId => bool) private _supportedInterfaces; + + /** + * @dev A contract implementing SupportsInterfaceWithLookup + * implement ERC165 itself. + */ + constructor() { + _registerInterface(INTERFACE_ID_ERC165); + } + + /** + * @dev Implement supportsInterface(bytes4) using a lookup table. + */ + function supportsInterface(bytes4 interfaceId) public view override returns (bool) { + return _supportedInterfaces[interfaceId]; + } + + /** + * @dev Private method for registering an interface. + */ + function _registerInterface(bytes4 interfaceId) internal { + require(interfaceId != 0xffffffff, "ERC165InterfacesSupported: invalid interface id"); + _supportedInterfaces[interfaceId] = true; + } +} + +contract ERC165InterfacesSupported is SupportsInterfaceWithLookupMock { + constructor(bytes4[] memory interfaceIds) { + for (uint256 i = 0; i < interfaceIds.length; i++) { + _registerInterface(interfaceIds[i]); + } + } +} diff --git a/contracts/mocks/ERC165/ERC165MaliciousData.sol b/contracts/mocks/ERC165/ERC165MaliciousData.sol new file mode 100644 index 000000000..35427567d --- /dev/null +++ b/contracts/mocks/ERC165/ERC165MaliciousData.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +contract ERC165MaliciousData { + function supportsInterface(bytes4) public pure returns (bool) { + assembly { + mstore(0, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff) + return(0, 32) + } + } +} diff --git a/contracts/mocks/ERC165/ERC165MissingData.sol b/contracts/mocks/ERC165/ERC165MissingData.sol new file mode 100644 index 000000000..fec43391b --- /dev/null +++ b/contracts/mocks/ERC165/ERC165MissingData.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT + +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 new file mode 100644 index 000000000..78ef9c8e0 --- /dev/null +++ b/contracts/mocks/ERC165/ERC165NotSupported.sol @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +contract ERC165NotSupported {} diff --git a/contracts/mocks/ERC165/ERC165ReturnBomb.sol b/contracts/mocks/ERC165/ERC165ReturnBomb.sol new file mode 100644 index 000000000..4bfacfd66 --- /dev/null +++ b/contracts/mocks/ERC165/ERC165ReturnBomb.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {IERC165} from "../../utils/introspection/IERC165.sol"; + +contract ERC165ReturnBombMock is IERC165 { + function supportsInterface(bytes4 interfaceId) public pure override returns (bool) { + if (interfaceId == type(IERC165).interfaceId) { + assembly { + mstore(0, 1) + } + } + assembly { + return(0, 101500) + } + } +} diff --git a/contracts/mocks/ERC2771ContextMock.sol b/contracts/mocks/ERC2771ContextMock.sol new file mode 100644 index 000000000..22b9203e7 --- /dev/null +++ b/contracts/mocks/ERC2771ContextMock.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +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 { + /// @custom:oz-upgrades-unsafe-allow constructor + constructor(address trustedForwarder) ERC2771Context(trustedForwarder) { + emit Sender(_msgSender()); // _msgSender() should be accessible during construction + } + + function _msgSender() internal view override(Context, ERC2771Context) returns (address) { + return ERC2771Context._msgSender(); + } + + function _msgData() internal view override(Context, ERC2771Context) returns (bytes calldata) { + return ERC2771Context._msgData(); + } +} diff --git a/contracts/mocks/ERC3156FlashBorrowerMock.sol b/contracts/mocks/ERC3156FlashBorrowerMock.sol new file mode 100644 index 000000000..261fea177 --- /dev/null +++ b/contracts/mocks/ERC3156FlashBorrowerMock.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +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. + * Writing a secure flash lock borrower is not an easy task, and should be done with the utmost care. + * This is not an example of how it should be done, and no pattern present in this mock should be considered secure. + * Following best practices, always have your contract properly audited before using them to manipulate important funds on + * live networks. + */ +contract ERC3156FlashBorrowerMock is IERC3156FlashBorrower { + bytes32 internal constant _RETURN_VALUE = keccak256("ERC3156FlashBorrower.onFlashLoan"); + + bool immutable _enableApprove; + bool immutable _enableReturn; + + event BalanceOf(address token, address account, uint256 value); + event TotalSupply(address token, uint256 value); + + constructor(bool enableReturn, bool enableApprove) { + _enableApprove = enableApprove; + _enableReturn = enableReturn; + } + + function onFlashLoan( + address /*initiator*/, + address token, + uint256 amount, + uint256 fee, + bytes calldata data + ) public returns (bytes32) { + require(msg.sender == token); + + emit BalanceOf(token, address(this), IERC20(token).balanceOf(address(this))); + emit TotalSupply(token, IERC20(token).totalSupply()); + + if (data.length > 0) { + // WARNING: This code is for testing purposes only! Do not use. + Address.functionCall(token, data); + } + + if (_enableApprove) { + IERC20(token).approve(token, amount + fee); + } + + return _enableReturn ? _RETURN_VALUE : bytes32(0); + } +} diff --git a/contracts/mocks/EtherReceiverMock.sol b/contracts/mocks/EtherReceiverMock.sol new file mode 100644 index 000000000..1b1c9363a --- /dev/null +++ b/contracts/mocks/EtherReceiverMock.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +contract EtherReceiverMock { + bool private _acceptEther; + + function setAcceptEther(bool acceptEther) public { + _acceptEther = acceptEther; + } + + receive() external payable { + if (!_acceptEther) { + revert(); + } + } +} diff --git a/contracts/mocks/InitializableMock.sol b/contracts/mocks/InitializableMock.sol new file mode 100644 index 000000000..7f76caacd --- /dev/null +++ b/contracts/mocks/InitializableMock.sol @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {Initializable} from "../proxy/utils/Initializable.sol"; + +/** + * @title InitializableMock + * @dev This contract is a mock to test initializable functionality + */ +contract InitializableMock is Initializable { + bool public initializerRan; + bool public onlyInitializingRan; + uint256 public x; + + function isInitializing() public view returns (bool) { + return _isInitializing(); + } + + function initialize() public initializer { + initializerRan = true; + } + + function initializeOnlyInitializing() public onlyInitializing { + onlyInitializingRan = true; + } + + function initializerNested() public initializer { + initialize(); + } + + function onlyInitializingNested() public initializer { + initializeOnlyInitializing(); + } + + function initializeWithX(uint256 _x) public payable initializer { + x = _x; + } + + function nonInitializable(uint256 _x) public payable { + x = _x; + } + + function fail() public pure { + require(false, "InitializableMock forced failure"); + } +} + +contract ConstructorInitializableMock is Initializable { + bool public initializerRan; + bool public onlyInitializingRan; + + constructor() initializer { + initialize(); + initializeOnlyInitializing(); + } + + function initialize() public initializer { + initializerRan = true; + } + + function initializeOnlyInitializing() public onlyInitializing { + onlyInitializingRan = true; + } +} + +contract ChildConstructorInitializableMock is ConstructorInitializableMock { + bool public childInitializerRan; + + constructor() initializer { + childInitialize(); + } + + function childInitialize() public initializer { + childInitializerRan = true; + } +} + +contract ReinitializerMock is Initializable { + uint256 public counter; + + function getInitializedVersion() public view returns (uint64) { + return _getInitializedVersion(); + } + + function initialize() public initializer { + doStuff(); + } + + function reinitialize(uint64 i) public reinitializer(i) { + doStuff(); + } + + function nestedReinitialize(uint64 i, uint64 j) public reinitializer(i) { + reinitialize(j); + } + + function chainReinitialize(uint64 i, uint64 j) public { + reinitialize(i); + reinitialize(j); + } + + function disableInitializers() public { + _disableInitializers(); + } + + function doStuff() public onlyInitializing { + counter++; + } +} + +contract DisableNew is Initializable { + constructor() { + _disableInitializers(); + } +} + +contract DisableOld is Initializable { + constructor() initializer {} +} + +contract DisableBad1 is DisableNew, DisableOld {} + +contract DisableBad2 is Initializable { + constructor() initializer { + _disableInitializers(); + } +} + +contract DisableOk is DisableOld, DisableNew {} diff --git a/contracts/mocks/MulticallTest.sol b/contracts/mocks/MulticallTest.sol new file mode 100644 index 000000000..74be7d8b4 --- /dev/null +++ b/contracts/mocks/MulticallTest.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {ERC20MulticallMock} from "./token/ERC20MulticallMock.sol"; + +contract MulticallTest { + function checkReturnValues( + ERC20MulticallMock multicallToken, + address[] calldata recipients, + uint256[] calldata amounts + ) external { + bytes[] memory calls = new bytes[](recipients.length); + for (uint256 i = 0; i < recipients.length; i++) { + calls[i] = abi.encodeCall(multicallToken.transfer, (recipients[i], amounts[i])); + } + + bytes[] memory results = multicallToken.multicall(calls); + for (uint256 i = 0; i < results.length; i++) { + require(abi.decode(results[i], (bool))); + } + } +} diff --git a/contracts/mocks/MultipleInheritanceInitializableMocks.sol b/contracts/mocks/MultipleInheritanceInitializableMocks.sol new file mode 100644 index 000000000..51030acd6 --- /dev/null +++ b/contracts/mocks/MultipleInheritanceInitializableMocks.sol @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +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. +// +// Human +// / \ +// | Gramps +// | | +// Mother Father +// | | +// -- Child -- + +/** + * Sample base initializable contract that is a human + */ +contract SampleHuman is Initializable { + bool public isHuman; + + function initialize() public initializer { + __SampleHuman_init(); + } + + // solhint-disable-next-line func-name-mixedcase + function __SampleHuman_init() internal onlyInitializing { + __SampleHuman_init_unchained(); + } + + // solhint-disable-next-line func-name-mixedcase + function __SampleHuman_init_unchained() internal onlyInitializing { + isHuman = true; + } +} + +/** + * Sample base initializable contract that defines a field mother + */ +contract SampleMother is Initializable, SampleHuman { + uint256 public mother; + + function initialize(uint256 value) public initializer { + __SampleMother_init(value); + } + + // solhint-disable-next-line func-name-mixedcase + function __SampleMother_init(uint256 value) internal onlyInitializing { + __SampleHuman_init(); + __SampleMother_init_unchained(value); + } + + // solhint-disable-next-line func-name-mixedcase + function __SampleMother_init_unchained(uint256 value) internal onlyInitializing { + mother = value; + } +} + +/** + * Sample base initializable contract that defines a field gramps + */ +contract SampleGramps is Initializable, SampleHuman { + string public gramps; + + function initialize(string memory value) public initializer { + __SampleGramps_init(value); + } + + // solhint-disable-next-line func-name-mixedcase + function __SampleGramps_init(string memory value) internal onlyInitializing { + __SampleHuman_init(); + __SampleGramps_init_unchained(value); + } + + // solhint-disable-next-line func-name-mixedcase + function __SampleGramps_init_unchained(string memory value) internal onlyInitializing { + gramps = value; + } +} + +/** + * Sample base initializable contract that defines a field father and extends from gramps + */ +contract SampleFather is Initializable, SampleGramps { + uint256 public father; + + function initialize(string memory _gramps, uint256 _father) public initializer { + __SampleFather_init(_gramps, _father); + } + + // solhint-disable-next-line func-name-mixedcase + function __SampleFather_init(string memory _gramps, uint256 _father) internal onlyInitializing { + __SampleGramps_init(_gramps); + __SampleFather_init_unchained(_father); + } + + // solhint-disable-next-line func-name-mixedcase + function __SampleFather_init_unchained(uint256 _father) internal onlyInitializing { + father = _father; + } +} + +/** + * Child extends from mother, father (gramps) + */ +contract SampleChild is Initializable, SampleMother, SampleFather { + uint256 public child; + + function initialize(uint256 _mother, string memory _gramps, uint256 _father, uint256 _child) public initializer { + __SampleChild_init(_mother, _gramps, _father, _child); + } + + // solhint-disable-next-line func-name-mixedcase + function __SampleChild_init( + uint256 _mother, + string memory _gramps, + uint256 _father, + uint256 _child + ) internal onlyInitializing { + __SampleMother_init(_mother); + __SampleFather_init(_gramps, _father); + __SampleChild_init_unchained(_child); + } + + // solhint-disable-next-line func-name-mixedcase + function __SampleChild_init_unchained(uint256 _child) internal onlyInitializing { + child = _child; + } +} diff --git a/contracts/mocks/PausableMock.sol b/contracts/mocks/PausableMock.sol new file mode 100644 index 000000000..fa701e2c7 --- /dev/null +++ b/contracts/mocks/PausableMock.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {Pausable} from "../utils/Pausable.sol"; + +contract PausableMock is Pausable { + bool public drasticMeasureTaken; + uint256 public count; + + constructor() { + drasticMeasureTaken = false; + count = 0; + } + + function normalProcess() external whenNotPaused { + count++; + } + + function drasticMeasure() external whenPaused { + drasticMeasureTaken = true; + } + + function pause() external { + _pause(); + } + + function unpause() external { + _unpause(); + } +} diff --git a/contracts/mocks/ReentrancyAttack.sol b/contracts/mocks/ReentrancyAttack.sol new file mode 100644 index 000000000..3df2d1c2b --- /dev/null +++ b/contracts/mocks/ReentrancyAttack.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {Context} from "../utils/Context.sol"; + +contract ReentrancyAttack is Context { + 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 new file mode 100644 index 000000000..39e2d5ed8 --- /dev/null +++ b/contracts/mocks/ReentrancyMock.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {ReentrancyGuard} from "../utils/ReentrancyGuard.sol"; +import {ReentrancyAttack} from "./ReentrancyAttack.sol"; + +contract ReentrancyMock is ReentrancyGuard { + uint256 public counter; + + constructor() { + counter = 0; + } + + function callback() external nonReentrant { + _count(); + } + + function countLocalRecursive(uint256 n) public nonReentrant { + if (n > 0) { + _count(); + countLocalRecursive(n - 1); + } + } + + function countThisRecursive(uint256 n) public nonReentrant { + if (n > 0) { + _count(); + (bool success, ) = address(this).call(abi.encodeCall(this.countThisRecursive, (n - 1))); + require(success, "ReentrancyMock: failed call"); + } + } + + function countAndCall(ReentrancyAttack attacker) public nonReentrant { + _count(); + attacker.callSender(abi.encodeCall(this.callback, ())); + } + + function _count() private { + counter += 1; + } + + function guardedCheckEntered() public nonReentrant { + require(_reentrancyGuardEntered()); + } + + function unguardedCheckNotEntered() public view { + require(!_reentrancyGuardEntered()); + } +} diff --git a/contracts/mocks/RegressionImplementation.sol b/contracts/mocks/RegressionImplementation.sol new file mode 100644 index 000000000..19b9706d4 --- /dev/null +++ b/contracts/mocks/RegressionImplementation.sol @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {Initializable} from "../proxy/utils/Initializable.sol"; + +contract Implementation1 is Initializable { + uint256 internal _value; + + function initialize() public initializer {} + + function setValue(uint256 _number) public { + _value = _number; + } +} + +contract Implementation2 is Initializable { + uint256 internal _value; + + function initialize() public initializer {} + + function setValue(uint256 _number) public { + _value = _number; + } + + function getValue() public view returns (uint256) { + return _value; + } +} + +contract Implementation3 is Initializable { + uint256 internal _value; + + function initialize() public initializer {} + + function setValue(uint256 _number) public { + _value = _number; + } + + function getValue(uint256 _number) public view returns (uint256) { + return _value + _number; + } +} + +contract Implementation4 is Initializable { + uint256 internal _value; + + function initialize() public initializer {} + + function setValue(uint256 _number) public { + _value = _number; + } + + function getValue() public view returns (uint256) { + return _value; + } + + fallback() external { + _value = 1; + } +} diff --git a/contracts/mocks/SingleInheritanceInitializableMocks.sol b/contracts/mocks/SingleInheritanceInitializableMocks.sol new file mode 100644 index 000000000..0bd3c614f --- /dev/null +++ b/contracts/mocks/SingleInheritanceInitializableMocks.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {Initializable} from "../proxy/utils/Initializable.sol"; + +/** + * @title MigratableMockV1 + * @dev This contract is a mock to test initializable functionality through migrations + */ +contract MigratableMockV1 is Initializable { + uint256 public x; + + function initialize(uint256 value) public payable initializer { + x = value; + } +} + +/** + * @title MigratableMockV2 + * @dev This contract is a mock to test migratable functionality with params + */ +contract MigratableMockV2 is MigratableMockV1 { + bool internal _migratedV2; + uint256 public y; + + function migrate(uint256 value, uint256 anotherValue) public payable { + require(!_migratedV2); + x = value; + y = anotherValue; + _migratedV2 = true; + } +} + +/** + * @title MigratableMockV3 + * @dev This contract is a mock to test migratable functionality without params + */ +contract MigratableMockV3 is MigratableMockV2 { + bool internal _migratedV3; + + function migrate() public payable { + require(!_migratedV3); + uint256 oldX = x; + x = y; + y = oldX; + _migratedV3 = true; + } +} diff --git a/contracts/mocks/StorageSlotMock.sol b/contracts/mocks/StorageSlotMock.sol new file mode 100644 index 000000000..dbdad7a2a --- /dev/null +++ b/contracts/mocks/StorageSlotMock.sol @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {StorageSlot} from "../utils/StorageSlot.sol"; + +contract StorageSlotMock { + using StorageSlot for *; + + function setBoolean(bytes32 slot, bool value) public { + slot.getBooleanSlot().value = value; + } + + function setAddress(bytes32 slot, address value) public { + slot.getAddressSlot().value = value; + } + + function setBytes32(bytes32 slot, bytes32 value) public { + slot.getBytes32Slot().value = value; + } + + function setUint256(bytes32 slot, uint256 value) public { + slot.getUint256Slot().value = value; + } + + function getBoolean(bytes32 slot) public view returns (bool) { + return slot.getBooleanSlot().value; + } + + function getAddress(bytes32 slot) public view returns (address) { + return slot.getAddressSlot().value; + } + + function getBytes32(bytes32 slot) public view returns (bytes32) { + return slot.getBytes32Slot().value; + } + + function getUint256(bytes32 slot) public view returns (uint256) { + return slot.getUint256Slot().value; + } + + mapping(uint256 key => string) public stringMap; + + function setString(bytes32 slot, string calldata value) public { + slot.getStringSlot().value = value; + } + + function setStringStorage(uint256 key, string calldata value) public { + stringMap[key].getStringSlot().value = value; + } + + function getString(bytes32 slot) public view returns (string memory) { + return slot.getStringSlot().value; + } + + function getStringStorage(uint256 key) public view returns (string memory) { + return stringMap[key].getStringSlot().value; + } + + mapping(uint256 key => bytes) public bytesMap; + + function setBytes(bytes32 slot, bytes calldata value) public { + slot.getBytesSlot().value = value; + } + + function setBytesStorage(uint256 key, bytes calldata value) public { + bytesMap[key].getBytesSlot().value = value; + } + + function getBytes(bytes32 slot) public view returns (bytes memory) { + return slot.getBytesSlot().value; + } + + function getBytesStorage(uint256 key) public view returns (bytes memory) { + return bytesMap[key].getBytesSlot().value; + } +} diff --git a/contracts/mocks/TimelockReentrant.sol b/contracts/mocks/TimelockReentrant.sol new file mode 100644 index 000000000..aab676a50 --- /dev/null +++ b/contracts/mocks/TimelockReentrant.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {Address} from "../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/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/VotesMock.sol b/contracts/mocks/VotesMock.sol new file mode 100644 index 000000000..e28d6b557 --- /dev/null +++ b/contracts/mocks/VotesMock.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {Votes} from "../governance/utils/Votes.sol"; + +abstract contract VotesMock is Votes { + mapping(address voter => uint256) private _votingUnits; + + function getTotalSupply() public view returns (uint256) { + return _getTotalSupply(); + } + + function delegate(address account, address newDelegation) public { + return _delegate(account, newDelegation); + } + + function _getVotingUnits(address account) internal view override returns (uint256) { + return _votingUnits[account]; + } + + function _mint(address account, uint256 votes) internal { + _votingUnits[account] += votes; + _transferVotingUnits(address(0), account, votes); + } + + function _burn(address account, uint256 votes) internal { + _votingUnits[account] += votes; + _transferVotingUnits(account, address(0), votes); + } +} + +abstract contract VotesTimestampMock is VotesMock { + function clock() public view override returns (uint48) { + return uint48(block.timestamp); + } + + // solhint-disable-next-line func-name-mixedcase + function CLOCK_MODE() public view virtual override returns (string memory) { + return "mode=timestamp"; + } +} diff --git a/contracts/mocks/compound/CompTimelock.sol b/contracts/mocks/compound/CompTimelock.sol new file mode 100644 index 000000000..c72ed0833 --- /dev/null +++ b/contracts/mocks/compound/CompTimelock.sol @@ -0,0 +1,174 @@ +// SPDX-License-Identifier: BSD-3-Clause +// solhint-disable private-vars-leading-underscore +/** + * Copyright 2020 Compound Labs, Inc. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +pragma solidity ^0.8.20; + +contract CompTimelock { + event NewAdmin(address indexed newAdmin); + event NewPendingAdmin(address indexed newPendingAdmin); + event NewDelay(uint256 indexed newDelay); + event CancelTransaction( + bytes32 indexed txHash, + address indexed target, + uint256 value, + string signature, + bytes data, + uint256 eta + ); + event ExecuteTransaction( + bytes32 indexed txHash, + address indexed target, + uint256 value, + string signature, + bytes data, + uint256 eta + ); + event QueueTransaction( + bytes32 indexed txHash, + address indexed target, + uint256 value, + string signature, + bytes data, + uint256 eta + ); + + uint256 public constant GRACE_PERIOD = 14 days; + uint256 public constant MINIMUM_DELAY = 2 days; + uint256 public constant MAXIMUM_DELAY = 30 days; + + address public admin; + address public pendingAdmin; + uint256 public delay; + + mapping(bytes32 => bool) public queuedTransactions; + + constructor(address admin_, uint256 delay_) { + require(delay_ >= MINIMUM_DELAY, "Timelock::constructor: Delay must exceed minimum delay."); + require(delay_ <= MAXIMUM_DELAY, "Timelock::setDelay: Delay must not exceed maximum delay."); + + admin = admin_; + delay = delay_; + } + + receive() external payable {} + + function setDelay(uint256 delay_) public { + require(msg.sender == address(this), "Timelock::setDelay: Call must come from Timelock."); + require(delay_ >= MINIMUM_DELAY, "Timelock::setDelay: Delay must exceed minimum delay."); + require(delay_ <= MAXIMUM_DELAY, "Timelock::setDelay: Delay must not exceed maximum delay."); + delay = delay_; + + emit NewDelay(delay); + } + + function acceptAdmin() public { + require(msg.sender == pendingAdmin, "Timelock::acceptAdmin: Call must come from pendingAdmin."); + admin = msg.sender; + pendingAdmin = address(0); + + emit NewAdmin(admin); + } + + function setPendingAdmin(address pendingAdmin_) public { + require(msg.sender == address(this), "Timelock::setPendingAdmin: Call must come from Timelock."); + pendingAdmin = pendingAdmin_; + + emit NewPendingAdmin(pendingAdmin); + } + + function queueTransaction( + address target, + uint256 value, + string memory signature, + bytes memory data, + uint256 eta + ) public returns (bytes32) { + require(msg.sender == admin, "Timelock::queueTransaction: Call must come from admin."); + require( + eta >= getBlockTimestamp() + delay, + "Timelock::queueTransaction: Estimated execution block must satisfy delay." + ); + + bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta)); + queuedTransactions[txHash] = true; + + emit QueueTransaction(txHash, target, value, signature, data, eta); + return txHash; + } + + function cancelTransaction( + address target, + uint256 value, + string memory signature, + bytes memory data, + uint256 eta + ) public { + require(msg.sender == admin, "Timelock::cancelTransaction: Call must come from admin."); + + bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta)); + queuedTransactions[txHash] = false; + + emit CancelTransaction(txHash, target, value, signature, data, eta); + } + + function executeTransaction( + address target, + uint256 value, + string memory signature, + bytes memory data, + uint256 eta + ) public payable returns (bytes memory) { + require(msg.sender == admin, "Timelock::executeTransaction: Call must come from admin."); + + bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta)); + require(queuedTransactions[txHash], "Timelock::executeTransaction: Transaction hasn't been queued."); + require(getBlockTimestamp() >= eta, "Timelock::executeTransaction: Transaction hasn't surpassed time lock."); + require(getBlockTimestamp() <= eta + GRACE_PERIOD, "Timelock::executeTransaction: Transaction is stale."); + + queuedTransactions[txHash] = false; + + bytes memory callData; + + if (bytes(signature).length == 0) { + callData = data; + } else { + callData = abi.encodePacked(bytes4(keccak256(bytes(signature))), data); + } + + // solium-disable-next-line security/no-call-value + (bool success, bytes memory returnData) = target.call{value: value}(callData); + require(success, "Timelock::executeTransaction: Transaction execution reverted."); + + emit ExecuteTransaction(txHash, target, value, signature, data, eta); + + return returnData; + } + + function getBlockTimestamp() internal view returns (uint256) { + // solium-disable-next-line security/no-block-members + return block.timestamp; + } +} diff --git a/contracts/mocks/docs/ERC4626Fees.sol b/contracts/mocks/docs/ERC4626Fees.sol new file mode 100644 index 000000000..17bc92d7c --- /dev/null +++ b/contracts/mocks/docs/ERC4626Fees.sol @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +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"; + +/// @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; + + 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, _entryFeeBasisPoints()); + return super.previewDeposit(assets - fee); + } + + /// @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, _entryFeeBasisPoints()); + } + + /// @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, _exitFeeBasisPoints()); + return super.previewWithdraw(assets + fee); + } + + /// @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, _exitFeeBasisPoints()); + } + + /// @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, _entryFeeBasisPoints()); + address recipient = _entryFeeRecipient(); + + super._deposit(caller, receiver, assets, shares); + + if (fee > 0 && recipient != address(this)) { + SafeERC20.safeTransfer(IERC20(asset()), recipient, fee); + } + } + + /// @dev Send exit fee to {_exitFeeRecipient}. See {IERC4626-_deposit}. + function _withdraw( + address caller, + address receiver, + address owner, + uint256 assets, + uint256 shares + ) internal virtual override { + uint256 fee = _feeOnRaw(assets, _exitFeeBasisPoints()); + address recipient = _exitFeeRecipient(); + + super._withdraw(caller, receiver, owner, assets, shares); + + if (fee > 0 && recipient != address(this)) { + SafeERC20.safeTransfer(IERC20(asset()), recipient, fee); + } + } + + // === Fee configuration === + + function _entryFeeBasisPoints() internal view virtual returns (uint256) { + return 0; // replace with e.g. 100 for 1% + } + + function _exitFeeBasisPoints() internal view virtual returns (uint256) { + return 0; // replace with e.g. 100 for 1% + } + + 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); // replace with e.g. a treasury address + } + + // === 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); + } + + /// @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/docs/governance/MyGovernor.sol b/contracts/mocks/docs/governance/MyGovernor.sol new file mode 100644 index 000000000..d898fc715 --- /dev/null +++ b/contracts/mocks/docs/governance/MyGovernor.sol @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {IGovernor, Governor} from "../../../governance/Governor.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"; +import {TimelockController} from "../../../governance/TimelockController.sol"; +import {IVotes} from "../../../governance/utils/IVotes.sol"; +import {IERC165} from "../../../interfaces/IERC165.sol"; + +contract MyGovernor is + Governor, + GovernorCountingSimple, + 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, GovernorTimelockControl) returns (ProposalState) { + return super.state(proposalId); + } + + function proposalNeedsQueuing( + uint256 proposalId + ) public view virtual override(Governor, GovernorTimelockControl) returns (bool) { + return super.proposalNeedsQueuing(proposalId); + } + + 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/docs/governance/MyToken.sol b/contracts/mocks/docs/governance/MyToken.sol new file mode 100644 index 000000000..cfb167557 --- /dev/null +++ b/contracts/mocks/docs/governance/MyToken.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +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") {} + + // 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..7c0d32956 --- /dev/null +++ b/contracts/mocks/docs/governance/MyTokenTimestampBased.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +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") {} + + // 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..c9d567dc5 --- /dev/null +++ b/contracts/mocks/docs/governance/MyTokenWrapped.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +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( + 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/mocks/governance/GovernorMock.sol b/contracts/mocks/governance/GovernorMock.sol new file mode 100644 index 000000000..69668d285 --- /dev/null +++ b/contracts/mocks/governance/GovernorMock.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +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) { + return super.proposalThreshold(); + } +} diff --git a/contracts/mocks/governance/GovernorPreventLateQuorumMock.sol b/contracts/mocks/governance/GovernorPreventLateQuorumMock.sol new file mode 100644 index 000000000..fde0863ce --- /dev/null +++ b/contracts/mocks/governance/GovernorPreventLateQuorumMock.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +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, + GovernorVotes, + GovernorCountingSimple, + GovernorPreventLateQuorum +{ + uint256 private _quorum; + + constructor(uint256 quorum_) { + _quorum = quorum_; + } + + function quorum(uint256) public view override returns (uint256) { + return _quorum; + } + + function proposalDeadline( + uint256 proposalId + ) public view override(Governor, GovernorPreventLateQuorum) returns (uint256) { + return super.proposalDeadline(proposalId); + } + + function proposalThreshold() public view override(Governor, GovernorSettings) returns (uint256) { + return super.proposalThreshold(); + } + + function _castVote( + uint256 proposalId, + address account, + uint8 support, + string memory reason, + bytes memory params + ) internal override(Governor, GovernorPreventLateQuorum) returns (uint256) { + return super._castVote(proposalId, account, support, reason, params); + } +} diff --git a/contracts/mocks/governance/GovernorStorageMock.sol b/contracts/mocks/governance/GovernorStorageMock.sol new file mode 100644 index 000000000..88c6bf906 --- /dev/null +++ b/contracts/mocks/governance/GovernorStorageMock.sol @@ -0,0 +1,79 @@ +// 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 proposalNeedsQueuing( + uint256 proposalId + ) public view virtual override(Governor, GovernorTimelockControl) returns (bool) { + return super.proposalNeedsQueuing(proposalId); + } + + 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/GovernorTimelockAccessMock.sol b/contracts/mocks/governance/GovernorTimelockAccessMock.sol new file mode 100644 index 000000000..3d1bbeeef --- /dev/null +++ b/contracts/mocks/governance/GovernorTimelockAccessMock.sol @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {IGovernor, Governor} from "../../governance/Governor.sol"; +import {GovernorTimelockAccess} from "../../governance/extensions/GovernorTimelockAccess.sol"; +import {GovernorSettings} from "../../governance/extensions/GovernorSettings.sol"; +import {GovernorCountingSimple} from "../../governance/extensions/GovernorCountingSimple.sol"; +import {GovernorVotesQuorumFraction} from "../../governance/extensions/GovernorVotesQuorumFraction.sol"; + +abstract contract GovernorTimelockAccessMock is + GovernorSettings, + GovernorTimelockAccess, + GovernorVotesQuorumFraction, + GovernorCountingSimple +{ + function nonGovernanceFunction() external {} + + function quorum(uint256 blockNumber) public view override(Governor, GovernorVotesQuorumFraction) returns (uint256) { + return super.quorum(blockNumber); + } + + function proposalThreshold() public view override(Governor, GovernorSettings) returns (uint256) { + return super.proposalThreshold(); + } + + function proposalNeedsQueuing( + uint256 proposalId + ) public view virtual override(Governor, GovernorTimelockAccess) returns (bool) { + return super.proposalNeedsQueuing(proposalId); + } + + function propose( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + string memory description + ) public override(Governor, GovernorTimelockAccess) returns (uint256) { + return super.propose(targets, values, calldatas, description); + } + + function _queueOperations( + uint256 proposalId, + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 descriptionHash + ) internal override(Governor, GovernorTimelockAccess) 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, GovernorTimelockAccess) { + super._executeOperations(proposalId, targets, values, calldatas, descriptionHash); + } + + function _cancel( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 descriptionHash + ) internal override(Governor, GovernorTimelockAccess) returns (uint256) { + return super._cancel(targets, values, calldatas, descriptionHash); + } +} diff --git a/contracts/mocks/governance/GovernorTimelockCompoundMock.sol b/contracts/mocks/governance/GovernorTimelockCompoundMock.sol new file mode 100644 index 000000000..03ef62510 --- /dev/null +++ b/contracts/mocks/governance/GovernorTimelockCompoundMock.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +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, + GovernorTimelockCompound, + GovernorVotesQuorumFraction, + GovernorCountingSimple +{ + function quorum(uint256 blockNumber) public view override(Governor, GovernorVotesQuorumFraction) returns (uint256) { + return super.quorum(blockNumber); + } + + function state( + uint256 proposalId + ) public view override(Governor, GovernorTimelockCompound) returns (ProposalState) { + return super.state(proposalId); + } + + function proposalThreshold() public view override(Governor, GovernorSettings) returns (uint256) { + return super.proposalThreshold(); + } + + function proposalNeedsQueuing( + uint256 proposalId + ) public view virtual override(Governor, GovernorTimelockCompound) returns (bool) { + return super.proposalNeedsQueuing(proposalId); + } + + 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._executeOperations(proposalId, targets, values, calldatas, descriptionHash); + } + + function _cancel( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 descriptionHash + ) internal override(Governor, GovernorTimelockCompound) returns (uint256) { + return super._cancel(targets, values, calldatas, descriptionHash); + } + + function _executor() internal view override(Governor, GovernorTimelockCompound) returns (address) { + return super._executor(); + } +} diff --git a/contracts/mocks/governance/GovernorTimelockControlMock.sol b/contracts/mocks/governance/GovernorTimelockControlMock.sol new file mode 100644 index 000000000..edaccc0b7 --- /dev/null +++ b/contracts/mocks/governance/GovernorTimelockControlMock.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +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, + GovernorTimelockControl, + GovernorVotesQuorumFraction, + GovernorCountingSimple +{ + 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 proposalNeedsQueuing( + uint256 proposalId + ) public view virtual override(Governor, GovernorTimelockControl) returns (bool) { + return super.proposalNeedsQueuing(proposalId); + } + + 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(); + } + + function nonGovernanceFunction() external {} +} diff --git a/contracts/mocks/governance/GovernorVoteMock.sol b/contracts/mocks/governance/GovernorVoteMock.sol new file mode 100644 index 000000000..e6949b5b2 --- /dev/null +++ b/contracts/mocks/governance/GovernorVoteMock.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +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) { + 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/GovernorWithParamsMock.sol b/contracts/mocks/governance/GovernorWithParamsMock.sol new file mode 100644 index 000000000..d535f811c --- /dev/null +++ b/contracts/mocks/governance/GovernorWithParamsMock.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +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); + + 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; + } + + function _getVotes( + address account, + uint256 blockNumber, + bytes memory params + ) internal view override(Governor, GovernorVotes) returns (uint256) { + uint256 reduction = 0; + // If the user provides parameters, we reduce the voting weight by the amount of the integer param + if (params.length > 0) { + (reduction, ) = abi.decode(params, (uint256, string)); + } + // reverts on overflow + return super._getVotes(account, blockNumber, params) - reduction; + } + + function _countVote( + uint256 proposalId, + address account, + uint8 support, + uint256 weight, + bytes memory params + ) internal override(Governor, GovernorCountingSimple) { + if (params.length > 0) { + (uint256 _uintParam, string memory _strParam) = abi.decode(params, (uint256, string)); + emit CountParams(_uintParam, _strParam); + } + return super._countVote(proposalId, account, support, weight, params); + } +} diff --git a/contracts/mocks/proxy/BadBeacon.sol b/contracts/mocks/proxy/BadBeacon.sol new file mode 100644 index 000000000..f3153a843 --- /dev/null +++ b/contracts/mocks/proxy/BadBeacon.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +contract BadBeaconNoImpl {} + +contract BadBeaconNotContract { + function implementation() external pure returns (address) { + return address(0x1); + } +} diff --git a/contracts/mocks/proxy/ClashingImplementation.sol b/contracts/mocks/proxy/ClashingImplementation.sol new file mode 100644 index 000000000..43d5a34f2 --- /dev/null +++ b/contracts/mocks/proxy/ClashingImplementation.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +/** + * @dev Implementation contract with a payable changeAdmin(address) function made to clash with + * TransparentUpgradeableProxy's to test correct functioning of the Transparent Proxy feature. + */ +contract ClashingImplementation { + event ClashingImplementationCall(); + + function upgradeToAndCall(address, bytes calldata) external payable { + emit ClashingImplementationCall(); + } + + function delegatedFunction() external pure returns (bool) { + return true; + } +} diff --git a/contracts/mocks/proxy/UUPSUpgradeableMock.sol b/contracts/mocks/proxy/UUPSUpgradeableMock.sol new file mode 100644 index 000000000..a5f2d4a25 --- /dev/null +++ b/contracts/mocks/proxy/UUPSUpgradeableMock.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {UUPSUpgradeable} from "../../proxy/utils/UUPSUpgradeable.sol"; +import {ERC1967Utils} from "../../proxy/ERC1967/ERC1967Utils.sol"; + +contract NonUpgradeableMock { + uint256 internal _counter; + + function current() external view returns (uint256) { + return _counter; + } + + function increment() external { + ++_counter; + } +} + +contract UUPSUpgradeableMock is NonUpgradeableMock, UUPSUpgradeable { + // Not having any checks in this function is dangerous! Do not do this outside tests! + function _authorizeUpgrade(address) internal override {} +} + +contract UUPSUpgradeableUnsafeMock is UUPSUpgradeableMock { + function upgradeToAndCall(address newImplementation, bytes memory data) public payable override { + ERC1967Utils.upgradeToAndCall(newImplementation, data); + } +} + +contract UUPSUnsupportedProxiableUUID is UUPSUpgradeableMock { + function proxiableUUID() external pure override returns (bytes32) { + return keccak256("invalid UUID"); + } +} diff --git a/contracts/mocks/token/ERC1155ReceiverMock.sol b/contracts/mocks/token/ERC1155ReceiverMock.sol new file mode 100644 index 000000000..2a85d1dfa --- /dev/null +++ b/contracts/mocks/token/ERC1155ReceiverMock.sol @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {IERC1155Receiver} from "../../token/ERC1155/IERC1155Receiver.sol"; +import {ERC165} from "../../utils/introspection/ERC165.sol"; + +contract ERC1155ReceiverMock is ERC165, IERC1155Receiver { + enum RevertType { + None, + RevertWithoutMessage, + RevertWithMessage, + RevertWithCustomError, + Panic + } + + 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); + + constructor(bytes4 recRetval, bytes4 batRetval, RevertType error) { + _recRetval = recRetval; + _batRetval = batRetval; + _error = error; + } + + function onERC1155Received( + address operator, + address from, + uint256 id, + uint256 value, + bytes calldata data + ) external returns (bytes4) { + if (_error == RevertType.RevertWithoutMessage) { + revert(); + } else if (_error == RevertType.RevertWithMessage) { + revert("ERC1155ReceiverMock: reverting on receive"); + } 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()); + return _recRetval; + } + + function onERC1155BatchReceived( + address operator, + address from, + uint256[] calldata ids, + uint256[] calldata values, + bytes calldata data + ) external returns (bytes4) { + if (_error == RevertType.RevertWithoutMessage) { + revert(); + } else if (_error == RevertType.RevertWithMessage) { + revert("ERC1155ReceiverMock: reverting on batch receive"); + } 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()); + return _batRetval; + } +} diff --git a/contracts/mocks/token/ERC20ApprovalMock.sol b/contracts/mocks/token/ERC20ApprovalMock.sol new file mode 100644 index 000000000..ff33a36df --- /dev/null +++ b/contracts/mocks/token/ERC20ApprovalMock.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {ERC20} from "../../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/mocks/token/ERC20DecimalsMock.sol b/contracts/mocks/token/ERC20DecimalsMock.sol new file mode 100644 index 000000000..a26e1f52b --- /dev/null +++ b/contracts/mocks/token/ERC20DecimalsMock.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {ERC20} from "../../token/ERC20/ERC20.sol"; + +abstract contract ERC20DecimalsMock is ERC20 { + uint8 private immutable _decimals; + + constructor(uint8 decimals_) { + _decimals = decimals_; + } + + function decimals() public view override returns (uint8) { + return _decimals; + } +} diff --git a/contracts/mocks/token/ERC20ExcessDecimalsMock.sol b/contracts/mocks/token/ERC20ExcessDecimalsMock.sol new file mode 100644 index 000000000..4627efd37 --- /dev/null +++ b/contracts/mocks/token/ERC20ExcessDecimalsMock.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +contract ERC20ExcessDecimalsMock { + function decimals() public pure returns (uint256) { + return type(uint256).max; + } +} diff --git a/contracts/mocks/token/ERC20FlashMintMock.sol b/contracts/mocks/token/ERC20FlashMintMock.sol new file mode 100644 index 000000000..508573c2b --- /dev/null +++ b/contracts/mocks/token/ERC20FlashMintMock.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {ERC20FlashMint} from "../../token/ERC20/extensions/ERC20FlashMint.sol"; + +abstract contract ERC20FlashMintMock is ERC20FlashMint { + uint256 _flashFeeAmount; + address _flashFeeReceiverAddress; + + function setFlashFee(uint256 amount) public { + _flashFeeAmount = amount; + } + + function _flashFee(address, uint256) internal view override returns (uint256) { + return _flashFeeAmount; + } + + function setFlashFeeReceiver(address receiver) public { + _flashFeeReceiverAddress = receiver; + } + + function _flashFeeReceiver() internal view override returns (address) { + return _flashFeeReceiverAddress; + } +} diff --git a/contracts/mocks/token/ERC20ForceApproveMock.sol b/contracts/mocks/token/ERC20ForceApproveMock.sol new file mode 100644 index 000000000..9fbca9727 --- /dev/null +++ b/contracts/mocks/token/ERC20ForceApproveMock.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {ERC20} from "../../token/ERC20/ERC20.sol"; + +// contract that replicate USDT (0xdac17f958d2ee523a2206206994597c13d831ec7) approval beavior +abstract contract ERC20ForceApproveMock is ERC20 { + function approve(address spender, uint256 amount) public virtual override returns (bool) { + require(amount == 0 || allowance(msg.sender, spender) == 0, "USDT approval failure"); + return super.approve(spender, amount); + } +} diff --git a/contracts/mocks/token/ERC20Mock.sol b/contracts/mocks/token/ERC20Mock.sol new file mode 100644 index 000000000..39ab12952 --- /dev/null +++ b/contracts/mocks/token/ERC20Mock.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {ERC20} from "../../token/ERC20/ERC20.sol"; + +contract ERC20Mock is ERC20 { + constructor() ERC20("ERC20Mock", "E20M") {} + + function mint(address account, uint256 amount) external { + _mint(account, amount); + } + + function burn(address account, uint256 amount) external { + _burn(account, amount); + } +} diff --git a/contracts/mocks/token/ERC20MulticallMock.sol b/contracts/mocks/token/ERC20MulticallMock.sol new file mode 100644 index 000000000..dce3e7056 --- /dev/null +++ b/contracts/mocks/token/ERC20MulticallMock.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +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 new file mode 100644 index 000000000..2129537b5 --- /dev/null +++ b/contracts/mocks/token/ERC20NoReturnMock.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {ERC20} from "../../token/ERC20/ERC20.sol"; + +abstract contract ERC20NoReturnMock is ERC20 { + function transfer(address to, uint256 amount) public override returns (bool) { + super.transfer(to, amount); + assembly { + return(0, 0) + } + } + + function transferFrom(address from, address to, uint256 amount) public override returns (bool) { + super.transferFrom(from, to, amount); + assembly { + return(0, 0) + } + } + + function approve(address spender, uint256 amount) public override returns (bool) { + super.approve(spender, amount); + assembly { + return(0, 0) + } + } +} diff --git a/contracts/mocks/token/ERC20Reentrant.sol b/contracts/mocks/token/ERC20Reentrant.sol new file mode 100644 index 000000000..813913f75 --- /dev/null +++ b/contracts/mocks/token/ERC20Reentrant.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {ERC20} from "../../token/ERC20/ERC20.sol"; +import {Address} from "../../utils/Address.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 _update(address from, address to, uint256 amount) internal override { + if (_reenterType == Type.Before) { + _reenterType = Type.No; + functionCall(_reenterTarget, _reenterData); + } + super._update(from, to, amount); + if (_reenterType == Type.After) { + _reenterType = Type.No; + functionCall(_reenterTarget, _reenterData); + } + } +} diff --git a/contracts/mocks/token/ERC20ReturnFalseMock.sol b/contracts/mocks/token/ERC20ReturnFalseMock.sol new file mode 100644 index 000000000..94bff32f1 --- /dev/null +++ b/contracts/mocks/token/ERC20ReturnFalseMock.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {ERC20} from "../../token/ERC20/ERC20.sol"; + +abstract contract ERC20ReturnFalseMock is ERC20 { + function transfer(address, uint256) public pure override returns (bool) { + return false; + } + + function transferFrom(address, address, uint256) public pure override returns (bool) { + return false; + } + + function approve(address, uint256) public pure override returns (bool) { + return false; + } +} diff --git a/contracts/mocks/token/ERC20VotesLegacyMock.sol b/contracts/mocks/token/ERC20VotesLegacyMock.sol new file mode 100644 index 000000000..3246fd42e --- /dev/null +++ b/contracts/mocks/token/ERC20VotesLegacyMock.sol @@ -0,0 +1,253 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +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 + */ +abstract contract ERC20VotesLegacyMock is IVotes, ERC20Permit { + struct Checkpoint { + uint32 fromBlock; + uint224 votes; + } + + bytes32 private constant _DELEGATION_TYPEHASH = + keccak256("Delegation(address delegatee,uint256 nonce,uint256 expiry)"); + + mapping(address account => address) private _delegatee; + mapping(address delegatee => 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 returns (address) { + return _delegatee[account]; + } + + /** + * @dev Gets the current votes balance for `account` + */ + function getVotes(address account) public view virtual 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 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 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 { + _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 { + 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); + } + + /** + * @dev Maximum token supply. Defaults to `type(uint224).max` (2^224^ - 1). + */ + function _maxSupply() internal view virtual returns (uint224) { + return type(uint224).max; + } + + /** + * @dev Move voting power when tokens are transferred. + * + * Emits a {IVotes-DelegateVotesChanged} event. + */ + 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"); + _writeCheckpoint(_totalSupplyCheckpoints, _add, amount); + } + + if (to == address(0)) { + _writeCheckpoint(_totalSupplyCheckpoints, _subtract, amount); + } + + _moveVotingPower(delegates(from), delegates(to), amount); + } + + /** + * @dev Change delegation for `delegator` to `delegatee`. + * + * Emits events {IVotes-DelegateChanged} and {IVotes-DelegateVotesChanged}. + */ + function _delegate(address delegator, address delegatee) internal virtual { + address currentDelegate = delegates(delegator); + uint256 delegatorBalance = balanceOf(delegator); + _delegatee[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 _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 Access an element of the array without performing bounds check. The position is assumed to be within bounds. + */ + 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) + } + } +} diff --git a/contracts/mocks/token/ERC4626LimitsMock.sol b/contracts/mocks/token/ERC4626LimitsMock.sol new file mode 100644 index 000000000..a845365af --- /dev/null +++ b/contracts/mocks/token/ERC4626LimitsMock.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {ERC4626} from "../../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/mocks/token/ERC4626Mock.sol b/contracts/mocks/token/ERC4626Mock.sol new file mode 100644 index 000000000..22ac5e8c7 --- /dev/null +++ b/contracts/mocks/token/ERC4626Mock.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +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)) {} + + function mint(address account, uint256 amount) external { + _mint(account, amount); + } + + function burn(address account, uint256 amount) external { + _burn(account, amount); + } +} diff --git a/contracts/mocks/token/ERC4626OffsetMock.sol b/contracts/mocks/token/ERC4626OffsetMock.sol new file mode 100644 index 000000000..3dde0952c --- /dev/null +++ b/contracts/mocks/token/ERC4626OffsetMock.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {ERC4626} from "../../token/ERC20/extensions/ERC4626.sol"; + +abstract contract ERC4626OffsetMock is ERC4626 { + uint8 private immutable _offset; + + constructor(uint8 offset_) { + _offset = offset_; + } + + function _decimalsOffset() internal view virtual override returns (uint8) { + return _offset; + } +} diff --git a/contracts/mocks/token/ERC4646FeesMock.sol b/contracts/mocks/token/ERC4646FeesMock.sol new file mode 100644 index 000000000..368b078ec --- /dev/null +++ b/contracts/mocks/token/ERC4646FeesMock.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {ERC4626Fees} from "../docs/ERC4626Fees.sol"; + +abstract contract ERC4626FeesMock is ERC4626Fees { + uint256 private immutable _entryFeeBasisPointValue; + address private immutable _entryFeeRecipientValue; + uint256 private immutable _exitFeeBasisPointValue; + address private immutable _exitFeeRecipientValue; + + constructor( + uint256 entryFeeBasisPoints, + address entryFeeRecipient, + uint256 exitFeeBasisPoints, + address exitFeeRecipient + ) { + _entryFeeBasisPointValue = entryFeeBasisPoints; + _entryFeeRecipientValue = entryFeeRecipient; + _exitFeeBasisPointValue = exitFeeBasisPoints; + _exitFeeRecipientValue = exitFeeRecipient; + } + + function _entryFeeBasisPoints() internal view virtual override returns (uint256) { + return _entryFeeBasisPointValue; + } + + function _entryFeeRecipient() internal view virtual override returns (address) { + return _entryFeeRecipientValue; + } + + function _exitFeeBasisPoints() internal view virtual override returns (uint256) { + return _exitFeeBasisPointValue; + } + + function _exitFeeRecipient() internal view virtual override returns (address) { + return _exitFeeRecipientValue; + } +} diff --git a/contracts/mocks/token/ERC721ConsecutiveEnumerableMock.sol b/contracts/mocks/token/ERC721ConsecutiveEnumerableMock.sol new file mode 100644 index 000000000..7732ae4a5 --- /dev/null +++ b/contracts/mocks/token/ERC721ConsecutiveEnumerableMock.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +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( + string memory name, + string memory symbol, + address[] memory receivers, + uint96[] memory amounts + ) ERC721(name, symbol) { + for (uint256 i = 0; i < receivers.length; ++i) { + _mintConsecutive(receivers[i], amounts[i]); + } + } + + function supportsInterface( + bytes4 interfaceId + ) public view virtual override(ERC721, ERC721Enumerable) returns (bool) { + return super.supportsInterface(interfaceId); + } + + function _ownerOf(uint256 tokenId) internal view virtual override(ERC721, ERC721Consecutive) returns (address) { + return super._ownerOf(tokenId); + } + + function _update( + address to, + uint256 tokenId, + address auth + ) internal virtual override(ERC721Consecutive, ERC721Enumerable) returns (address) { + return super._update(to, tokenId, auth); + } + + function _increaseBalance(address account, uint128 amount) internal virtual override(ERC721, ERC721Enumerable) { + super._increaseBalance(account, amount); + } +} diff --git a/contracts/mocks/token/ERC721ConsecutiveMock.sol b/contracts/mocks/token/ERC721ConsecutiveMock.sol new file mode 100644 index 000000000..109864718 --- /dev/null +++ b/contracts/mocks/token/ERC721ConsecutiveMock.sol @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +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 + */ +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]); + } + + for (uint256 i = 0; i < receivers.length; ++i) { + _mintConsecutive(receivers[i], amounts[i]); + } + } + + 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); + } + + function _update( + address to, + uint256 tokenId, + address auth + ) internal virtual override(ERC721Consecutive, ERC721Pausable, ERC721Votes) returns (address) { + return super._update(to, tokenId, auth); + } + + function _increaseBalance(address account, uint128 amount) internal virtual override(ERC721, ERC721Votes) { + super._increaseBalance(account, amount); + } +} + +contract ERC721ConsecutiveNoConstructorMintMock is ERC721Consecutive { + constructor(string memory name, string memory symbol) ERC721(name, symbol) { + _mint(msg.sender, 0); + } +} diff --git a/contracts/mocks/token/ERC721ReceiverMock.sol b/contracts/mocks/token/ERC721ReceiverMock.sol new file mode 100644 index 000000000..14120f5d1 --- /dev/null +++ b/contracts/mocks/token/ERC721ReceiverMock.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {IERC721Receiver} from "../../token/ERC721/IERC721Receiver.sol"; + +contract ERC721ReceiverMock is IERC721Receiver { + enum RevertType { + None, + RevertWithoutMessage, + RevertWithMessage, + RevertWithCustomError, + Panic + } + + bytes4 private immutable _retval; + RevertType private immutable _error; + + event Received(address operator, address from, uint256 tokenId, bytes data, uint256 gas); + error CustomError(bytes4); + + constructor(bytes4 retval, RevertType error) { + _retval = retval; + _error = error; + } + + function onERC721Received( + address operator, + address from, + uint256 tokenId, + bytes memory data + ) public returns (bytes4) { + if (_error == RevertType.RevertWithoutMessage) { + revert(); + } 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/contracts/mocks/token/ERC721URIStorageMock.sol b/contracts/mocks/token/ERC721URIStorageMock.sol new file mode 100644 index 000000000..254435e07 --- /dev/null +++ b/contracts/mocks/token/ERC721URIStorageMock.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {ERC721URIStorage} from "../../token/ERC721/extensions/ERC721URIStorage.sol"; + +abstract contract ERC721URIStorageMock is ERC721URIStorage { + string private _baseTokenURI; + + function _baseURI() internal view virtual override returns (string memory) { + return _baseTokenURI; + } + + function setBaseURI(string calldata newBaseTokenURI) public { + _baseTokenURI = newBaseTokenURI; + } +} diff --git a/contracts/mocks/token/VotesTimestamp.sol b/contracts/mocks/token/VotesTimestamp.sol new file mode 100644 index 000000000..78fdfae9c --- /dev/null +++ b/contracts/mocks/token/VotesTimestamp.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +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) { + 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); + } + + // solhint-disable-next-line func-name-mixedcase + function CLOCK_MODE() public view virtual override returns (string memory) { + return "mode=timestamp"; + } +} diff --git a/contracts/package.json b/contracts/package.json new file mode 100644 index 000000000..e2cd89d91 --- /dev/null +++ b/contracts/package.json @@ -0,0 +1,32 @@ +{ + "name": "@openzeppelin/contracts", + "description": "Secure Smart Contract library for Solidity", + "version": "5.0.0-rc.0", + "files": [ + "**/*.sol", + "/build/contracts/*.json", + "!/mocks/**/*" + ], + "scripts": { + "prepare": "bash ../scripts/prepare-contracts-package.sh", + "prepare-docs": "cd ..; npm run prepare-docs" + }, + "repository": { + "type": "git", + "url": "https://github.com/OpenZeppelin/openzeppelin-contracts.git" + }, + "keywords": [ + "solidity", + "ethereum", + "smart", + "contracts", + "security", + "zeppelin" + ], + "author": "OpenZeppelin Community ", + "license": "MIT", + "bugs": { + "url": "https://github.com/OpenZeppelin/openzeppelin-contracts/issues" + }, + "homepage": "https://openzeppelin.com/contracts/" +} diff --git a/contracts/proxy/Clones.sol b/contracts/proxy/Clones.sol new file mode 100644 index 000000000..1e35a55b7 --- /dev/null +++ b/contracts/proxy/Clones.sol @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (proxy/Clones.sol) + +pragma solidity ^0.8.20; + +/** + * @dev https://eips.ethereum.org/EIPS/eip-1167[EIP 1167] is a standard for + * deploying minimal proxy contracts, also known as "clones". + * + * > To simply and cheaply clone contract functionality in an immutable way, this standard specifies + * > a minimal bytecode implementation that delegates all calls to a known, fixed address. + * + * 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. + */ +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`. + * + * This function uses the create opcode, which should never revert. + */ + function clone(address implementation) internal returns (address instance) { + /// @solidity memory-safe-assembly + assembly { + // Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes + // of the `implementation` address with the bytecode before the address. + mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000)) + // Packs the remaining 17 bytes of `implementation` with the bytecode after the address. + mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3)) + instance := create(0, 0x09, 0x37) + } + if (instance == address(0)) { + revert ERC1167FailedCreateClone(); + } + } + + /** + * @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`. + * + * This function uses the create2 opcode and a `salt` to deterministically deploy + * the clone. Using the same `implementation` and `salt` multiple time will revert, since + * the clones cannot be deployed twice at the same address. + */ + function cloneDeterministic(address implementation, bytes32 salt) internal returns (address instance) { + /// @solidity memory-safe-assembly + assembly { + // Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes + // of the `implementation` address with the bytecode before the address. + mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000)) + // Packs the remaining 17 bytes of `implementation` with the bytecode after the address. + mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3)) + instance := create2(0, 0x09, 0x37, salt) + } + if (instance == address(0)) { + revert ERC1167FailedCreateClone(); + } + } + + /** + * @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}. + */ + function predictDeterministicAddress( + address implementation, + bytes32 salt, + address deployer + ) internal pure returns (address predicted) { + /// @solidity memory-safe-assembly + assembly { + let ptr := mload(0x40) + mstore(add(ptr, 0x38), deployer) + mstore(add(ptr, 0x24), 0x5af43d82803e903d91602b57fd5bf3ff) + mstore(add(ptr, 0x14), implementation) + mstore(ptr, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73) + mstore(add(ptr, 0x58), salt) + mstore(add(ptr, 0x78), keccak256(add(ptr, 0x0c), 0x37)) + predicted := keccak256(add(ptr, 0x43), 0x55) + } + } + + /** + * @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}. + */ + function predictDeterministicAddress( + address implementation, + bytes32 salt + ) internal view returns (address predicted) { + return predictDeterministicAddress(implementation, salt, address(this)); + } +} diff --git a/contracts/proxy/ERC1967/ERC1967Proxy.sol b/contracts/proxy/ERC1967/ERC1967Proxy.sol new file mode 100644 index 000000000..0f49f5034 --- /dev/null +++ b/contracts/proxy/ERC1967/ERC1967Proxy.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (proxy/ERC1967/ERC1967Proxy.sol) + +pragma solidity ^0.8.20; + +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 + * implementation address that can be changed. This address is stored in storage in the location specified by + * 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 { + /** + * @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 `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 implementation, bytes memory _data) payable { + ERC1967Utils.upgradeToAndCall(implementation, _data); + } + + /** + * @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) { + return ERC1967Utils.getImplementation(); + } +} diff --git a/contracts/proxy/ERC1967/ERC1967Utils.sol b/contracts/proxy/ERC1967/ERC1967Utils.sol new file mode 100644 index 000000000..e0689c672 --- /dev/null +++ b/contracts/proxy/ERC1967/ERC1967Utils.sol @@ -0,0 +1,193 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (proxy/ERC1967/ERC1967Utils.sol) + +pragma solidity ^0.8.20; + +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 + * https://eips.ethereum.org/EIPS/eip-1967[EIP1967] slots. + */ +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); + + /** + * @dev Storage slot with the address of the current implementation. + * 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; + + /** + * @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 An upgrade function sees `msg.value > 0` that may be lost. + */ + error ERC1967NonPayable(); + + /** + * @dev Returns the current implementation address. + */ + function getImplementation() internal view returns (address) { + return StorageSlot.getAddressSlot(IMPLEMENTATION_SLOT).value; + } + + /** + * @dev Stores a new address in the EIP1967 implementation slot. + */ + function _setImplementation(address newImplementation) private { + if (newImplementation.code.length == 0) { + revert ERC1967InvalidImplementation(newImplementation); + } + StorageSlot.getAddressSlot(IMPLEMENTATION_SLOT).value = newImplementation; + } + + /** + * @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 upgradeToAndCall(address newImplementation, bytes memory data) internal { + _setImplementation(newImplementation); + emit Upgraded(newImplementation); + + if (data.length > 0) { + Address.functionDelegateCall(newImplementation, data); + } else { + _checkNonPayable(); + } + } + + /** + * @dev Storage slot with the admin of the contract. + * 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; + + /** + * @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; + } + + /** + * @dev Stores a new address in the EIP1967 admin slot. + */ + function _setAdmin(address newAdmin) private { + if (newAdmin == address(0)) { + revert ERC1967InvalidAdmin(address(0)); + } + StorageSlot.getAddressSlot(ADMIN_SLOT).value = newAdmin; + } + + /** + * @dev Changes the admin of the proxy. + * + * Emits an {IERC1967-AdminChanged} event. + */ + 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 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; + + /** + * @dev Returns the current beacon. + */ + function getBeacon() internal view returns (address) { + return StorageSlot.getAddressSlot(BEACON_SLOT).value; + } + + /** + * @dev Stores a new beacon in the EIP1967 beacon slot. + */ + function _setBeacon(address newBeacon) private { + if (newBeacon.code.length == 0) { + revert ERC1967InvalidBeacon(newBeacon); + } + + StorageSlot.getAddressSlot(BEACON_SLOT).value = newBeacon; + + address beaconImplementation = IBeacon(newBeacon).implementation(); + if (beaconImplementation.code.length == 0) { + revert ERC1967InvalidImplementation(beaconImplementation); + } + } + + /** + * @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. + * + * 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) internal { + _setBeacon(newBeacon); + emit BeaconUpgraded(newBeacon); + + if (data.length > 0) { + Address.functionDelegateCall(IBeacon(newBeacon).implementation(), data); + } else { + _checkNonPayable(); + } + } + + /** + * @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) { + revert ERC1967NonPayable(); + } + } +} diff --git a/contracts/proxy/Proxy.sol b/contracts/proxy/Proxy.sol new file mode 100644 index 000000000..8a59ff348 --- /dev/null +++ b/contracts/proxy/Proxy.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (proxy/Proxy.sol) + +pragma solidity ^0.8.20; + +/** + * @dev This abstract contract provides a fallback function that delegates all calls to another contract using the EVM + * instruction `delegatecall`. We refer to the second contract as the _implementation_ behind the proxy, and it has to + * be specified by overriding the virtual {_implementation} function. + * + * Additionally, delegation to the implementation can be triggered manually through the {_fallback} function, or to a + * different contract through the {_delegate} function. + * + * The success and return data of the delegated call will be returned back to the caller of the proxy. + */ +abstract contract Proxy { + /** + * @dev Delegates the current call to `implementation`. + * + * This function does not return to its internal call site, it will return directly to the external caller. + */ + function _delegate(address implementation) internal virtual { + assembly { + // Copy msg.data. We take full control of memory in this inline assembly + // block because it will not return to Solidity code. We overwrite the + // Solidity scratch pad at memory position 0. + calldatacopy(0, 0, calldatasize()) + + // Call the implementation. + // out and outsize are 0 because we don't know the size yet. + let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0) + + // Copy the returned data. + returndatacopy(0, 0, returndatasize()) + + switch result + // delegatecall returns 0 on error. + case 0 { + revert(0, returndatasize()) + } + default { + return(0, returndatasize()) + } + } + } + + /** + * @dev This is a virtual function that should be overridden so it returns the address to which the fallback + * function and {_fallback} should delegate. + */ + function _implementation() internal view virtual returns (address); + + /** + * @dev Delegates the current call to the address returned by `_implementation()`. + * + * This function does not return to its internal call site, it will return directly to the external caller. + */ + function _fallback() internal virtual { + _delegate(_implementation()); + } + + /** + * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if no other + * function in the contract matches the call data. + */ + fallback() external payable virtual { + _fallback(); + } +} diff --git a/contracts/proxy/README.adoc b/contracts/proxy/README.adoc new file mode 100644 index 000000000..3c4a78d19 --- /dev/null +++ b/contracts/proxy/README.adoc @@ -0,0 +1,87 @@ += Proxies + +[.readme-notice] +NOTE: This document is better viewed at https://docs.openzeppelin.com/contracts/api/proxy + +This is a low-level set of contracts implementing different proxy patterns with and without upgradeability. For an in-depth overview of this pattern check out the xref:upgrades-plugins::proxies.adoc[Proxy Upgrade Pattern] page. + +Most of the proxies below are built on an abstract base contract. + +- {Proxy}: Abstract contract implementing the core delegation functionality. + +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. + +- {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 <>. + +- {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. + +A different family of proxies are beacon proxies. This pattern, popularized by Dharma, allows multiple proxies to be upgraded to a different implementation in a single transaction. + +- {BeaconProxy}: A proxy that retrieves its implementation from a beacon contract. +- {UpgradeableBeacon}: A beacon contract with a built in admin that can upgrade the {BeaconProxy} pointing to it. + +In this pattern, the proxy contract doesn't hold the implementation address in storage like an ERC1967 proxy. Instead, the address is stored in a separate beacon contract. The `upgrade` operations are sent to the beacon instead of to the proxy contract, and all proxies that follow that beacon are automatically upgraded. + +Outside the realm of upgradeability, proxies can also be useful to make cheap contract clones, such as those created by an on-chain factory contract that creates many instances of the same contract. These instances are designed to be both cheap to deploy, and cheap to call. + +- {Clones}: A library that can deploy cheap minimal non-upgradeable proxies. + +[[transparent-vs-uups]] +== Transparent vs UUPS Proxies + +The original proxies included in OpenZeppelin followed the https://blog.openzeppelin.com/the-transparent-proxy-pattern/[Transparent Proxy Pattern]. While this pattern is still provided, our recommendation is now shifting towards UUPS proxies, which are both lightweight and versatile. The name UUPS comes from https://eips.ethereum.org/EIPS/eip-1822[EIP1822], which first documented the pattern. + +While both of these share the same interface for upgrades, in UUPS proxies the upgrade is handled by the implementation, and can eventually be removed. Transparent proxies, on the other hand, include the upgrade and admin logic in the proxy itself. This means {TransparentUpgradeableProxy} is more expensive to deploy than what is possible with UUPS proxies. + +UUPS proxies are implemented using an {ERC1967Proxy}. Note that this proxy is not by itself upgradeable. It is the role of the implementation to include, alongside the contract's logic, all the code necessary to update the implementation's address that is stored at a specific slot in the proxy's storage space. This is where the {UUPSUpgradeable} contract comes in. Inheriting from it (and overriding the {xref-UUPSUpgradeable-_authorizeUpgrade-address-}[`_authorizeUpgrade`] function with the relevant access control mechanism) will turn your contract into a UUPS compliant implementation. + +Note that since both proxies use the same storage slot for the implementation address, using a UUPS compliant implementation with a {TransparentUpgradeableProxy} might allow non-admins to perform upgrade operations. + +By default, the upgrade functionality included in {UUPSUpgradeable} contains a security mechanism that will prevent any upgrades to a non UUPS compliant implementation. This prevents upgrades to an implementation contract that wouldn't contain the necessary upgrade mechanism, as it would lock the upgradeability of the proxy forever. This security mechanism can be bypassed by either of: + +- Adding a flag mechanism in the implementation that will disable the upgrade function when triggered. +- Upgrading to an implementation that features an upgrade mechanism without the additional security check, and then upgrading again to another implementation without the upgrade mechanism. + +The current implementation of this security mechanism uses https://eips.ethereum.org/EIPS/eip-1822[EIP1822] to detect the storage slot used by the implementation. A previous implementation, now deprecated, relied on a rollback check. It is possible to upgrade from a contract using the old mechanism to a new one. The inverse is however not possible, as old implementations (before version 4.5) did not include the `ERC1822` interface. + +== Core + +{{Proxy}} + +== ERC1967 + +{{IERC1967}} + +{{ERC1967Proxy}} + +{{ERC1967Utils}} + +== Transparent Proxy + +{{TransparentUpgradeableProxy}} + +{{ProxyAdmin}} + +== Beacon + +{{BeaconProxy}} + +{{IBeacon}} + +{{UpgradeableBeacon}} + +== Minimal Clones + +{{Clones}} + +== Utils + +{{Initializable}} + +{{UUPSUpgradeable}} diff --git a/contracts/proxy/beacon/BeaconProxy.sol b/contracts/proxy/beacon/BeaconProxy.sol new file mode 100644 index 000000000..b9d7d0034 --- /dev/null +++ b/contracts/proxy/beacon/BeaconProxy.sol @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (proxy/beacon/BeaconProxy.sol) + +pragma solidity ^0.8.20; + +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}. + * + * 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: 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. + address private immutable _beacon; + + /** + * @dev Initializes the proxy with `beacon`. + * + * If `data` is nonempty, it's used as data in a delegate call to the implementation returned by the beacon. This + * will typically be an encoded function call, and allows initializing the storage of the proxy like a Solidity + * constructor. + * + * 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); + _beacon = beacon; + } + + /** + * @dev Returns the current implementation address of the associated beacon. + */ + function _implementation() internal view virtual override returns (address) { + return IBeacon(_getBeacon()).implementation(); + } + + /** + * @dev Returns the beacon. + */ + function _getBeacon() internal view virtual returns (address) { + return _beacon; + } +} diff --git a/contracts/proxy/beacon/IBeacon.sol b/contracts/proxy/beacon/IBeacon.sol new file mode 100644 index 000000000..bdc38e124 --- /dev/null +++ b/contracts/proxy/beacon/IBeacon.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (proxy/beacon/IBeacon.sol) + +pragma solidity ^0.8.20; + +/** + * @dev This is the interface that {BeaconProxy} expects of its beacon. + */ +interface IBeacon { + /** + * @dev Must return an address that can be used as a delegate call target. + * + * {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 new file mode 100644 index 000000000..db3caf91f --- /dev/null +++ b/contracts/proxy/beacon/UpgradeableBeacon.sol @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (proxy/beacon/UpgradeableBeacon.sol) + +pragma solidity ^0.8.20; + +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 + * implementation contract, which is where they will delegate all function calls. + * + * An owner is able to change the implementation the beacon points to, thus upgrading the proxies that use this beacon. + */ +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. + */ + event Upgraded(address indexed implementation); + + /** + * @dev Sets the address of the initial implementation, and the initial owner who can upgrade the beacon. + */ + constructor(address implementation_, address initialOwner) Ownable(initialOwner) { + _setImplementation(implementation_); + } + + /** + * @dev Returns the current implementation address. + */ + function implementation() public view virtual returns (address) { + return _implementation; + } + + /** + * @dev Upgrades the beacon to a new implementation. + * + * Emits an {Upgraded} event. + * + * Requirements: + * + * - msg.sender must be the owner of the contract. + * - `newImplementation` must be a contract. + */ + function upgradeTo(address newImplementation) public virtual onlyOwner { + _setImplementation(newImplementation); + } + + /** + * @dev Sets the implementation contract address for this beacon + * + * Requirements: + * + * - `newImplementation` must be a contract. + */ + function _setImplementation(address newImplementation) private { + if (newImplementation.code.length == 0) { + revert BeaconInvalidImplementation(newImplementation); + } + _implementation = newImplementation; + emit Upgraded(newImplementation); + } +} diff --git a/contracts/proxy/transparent/ProxyAdmin.sol b/contracts/proxy/transparent/ProxyAdmin.sol new file mode 100644 index 000000000..e546293d0 --- /dev/null +++ b/contracts/proxy/transparent/ProxyAdmin.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (proxy/transparent/ProxyAdmin.sol) + +pragma solidity ^0.8.20; + +import {ITransparentUpgradeableProxy} 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 + * explanation of why you would want to use this see the documentation for {TransparentUpgradeableProxy}. + */ +contract ProxyAdmin is Ownable { + /** + * @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, making it impossible to invoke the `receive` function + * during an upgrade. + */ + string public constant UPGRADE_INTERFACE_VERSION = "5.0.0"; + + /** + * @dev Sets the initial owner who can perform upgrades. + */ + constructor(address initialOwner) Ownable(initialOwner) {} + + /** + * @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, + address implementation, + bytes memory data + ) public payable virtual onlyOwner { + proxy.upgradeToAndCall{value: msg.value}(implementation, data); + } +} diff --git a/contracts/proxy/transparent/TransparentUpgradeableProxy.sol b/contracts/proxy/transparent/TransparentUpgradeableProxy.sol new file mode 100644 index 000000000..ba23a603f --- /dev/null +++ b/contracts/proxy/transparent/TransparentUpgradeableProxy.sol @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (proxy/transparent/TransparentUpgradeableProxy.sol) + +pragma solidity ^0.8.20; + +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 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 upgradeToAndCall(address, bytes calldata) external payable; +} + +/** + * @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 + * https://blog.openzeppelin.com/the-transparent-proxy-pattern/[transparent proxy pattern]. This pattern implies two + * 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 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. 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 `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. + * + * 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 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 `upgradeToAndCall` function inaccessible, preventing upgradeability and compromising transparency. + */ +contract TransparentUpgradeableProxy is ERC1967Proxy { + // 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. + address private immutable _admin; + + /** + * @dev The proxy caller is the current admin, and can't fallback to the proxy target. + */ + error ProxyDeniedAdminAccess(); + + /** + * @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 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(_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 == _proxyAdmin()) { + if (msg.sig != ITransparentUpgradeableProxy.upgradeToAndCall.selector) { + revert ProxyDeniedAdminAccess(); + } else { + _dispatchUpgradeToAndCall(); + } + } else { + super._fallback(); + } + } + + /** + * @dev Upgrade the implementation of the proxy. See {ERC1967Utils-upgradeToAndCall}. + * + * Requirements: + * + * - If `data` is empty, `msg.value` must be zero. + */ + function _dispatchUpgradeToAndCall() private { + (address newImplementation, bytes memory data) = abi.decode(msg.data[4:], (address, bytes)); + ERC1967Utils.upgradeToAndCall(newImplementation, data); + } +} diff --git a/contracts/proxy/utils/Initializable.sol b/contracts/proxy/utils/Initializable.sol new file mode 100644 index 000000000..955c264b4 --- /dev/null +++ b/contracts/proxy/utils/Initializable.sol @@ -0,0 +1,228 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (proxy/utils/Initializable.sol) + +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 + * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an + * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer + * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect. + * + * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be + * reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in + * case an upgrade adds a module that needs to be initialized. + * + * For example: + * + * [.hljs-theme-light.nopadding] + * ```solidity + * contract MyToken is ERC20Upgradeable { + * function initialize() initializer public { + * __ERC20_init("MyToken", "MTK"); + * } + * } + * + * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable { + * function initializeV2() reinitializer(2) public { + * __ERC20Permit_init("MyToken"); + * } + * } + * ``` + * + * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as + * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}. + * + * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure + * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity. + * + * [CAUTION] + * ==== + * Avoid leaving a contract uninitialized. + * + * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation + * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke + * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed: + * + * [.hljs-theme-light.nopadding] + * ``` + * /// @custom:oz-upgrades-unsafe-allow constructor + * constructor() { + * _disableInitializers(); + * } + * ``` + * ==== + */ +abstract contract Initializable { + /** + * @dev Storage of the initializable contract. + * + * It's implemented on a custom ERC-7201 namespace to reduce the risk of storage collisions + * when using with upgradeable contracts. + * + * @custom:storage-location erc7201:openzeppelin.storage.Initializable + */ + struct InitializableStorage { + /** + * @dev Indicates that the contract has been initialized. + */ + uint64 _initialized; + /** + * @dev Indicates that the contract is in the process of being initialized. + */ + bool _initializing; + } + + // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Initializable")) - 1)) & ~bytes32(uint256(0xff)) + bytes32 private constant INITIALIZABLE_STORAGE = 0xf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00; + + /** + * @dev The contract is already initialized. + */ + error InvalidInitialization(); + + /** + * @dev The contract is not initializing. + */ + error NotInitializing(); + + /** + * @dev Triggered when the contract has been initialized or reinitialized. + */ + event Initialized(uint64 version); + + /** + * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope, + * `onlyInitializing` functions can be used to initialize parent contracts. + * + * Similar to `reinitializer(1)`, except that in the context of a constructor an `initializer` may be invoked any + * number of times. This behavior in the constructor can be useful during testing and is not expected to be used in + * production. + * + * Emits an {Initialized} event. + */ + modifier initializer() { + // solhint-disable-next-line var-name-mixedcase + InitializableStorage storage $ = _getInitializableStorage(); + + // Cache values to avoid duplicated sloads + bool isTopLevelCall = !$._initializing; + uint64 initialized = $._initialized; + + // Allowed calls: + // - initialSetup: the contract is not in the initializing state and no previous version was + // initialized + // - construction: the contract is initialized at version 1 (no reininitialization) and the + // current contract is just being deployed + bool initialSetup = initialized == 0 && isTopLevelCall; + bool construction = initialized == 1 && address(this).code.length == 0; + + if (!initialSetup && !construction) { + revert InvalidInitialization(); + } + $._initialized = 1; + if (isTopLevelCall) { + $._initializing = true; + } + _; + if (isTopLevelCall) { + $._initializing = false; + emit Initialized(1); + } + } + + /** + * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the + * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be + * used to initialize parent contracts. + * + * A reinitializer may be used after the original initialization step. This is essential to configure modules that + * are added through upgrades and that require initialization. + * + * When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer` + * cannot be nested. If one is invoked in the context of another, execution will revert. + * + * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in + * a contract, executing them in the right order is up to the developer or operator. + * + * WARNING: Setting the version to 2**64 - 1 will prevent any future reinitialization. + * + * Emits an {Initialized} event. + */ + modifier reinitializer(uint64 version) { + // solhint-disable-next-line var-name-mixedcase + InitializableStorage storage $ = _getInitializableStorage(); + + if ($._initializing || $._initialized >= version) { + revert InvalidInitialization(); + } + $._initialized = version; + $._initializing = true; + _; + $._initializing = false; + emit Initialized(version); + } + + /** + * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the + * {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 (!_isInitializing()) { + revert NotInitializing(); + } + } + + /** + * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call. + * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized + * to any version. It is recommended to use this to lock implementation contracts that are designed to be called + * through proxies. + * + * Emits an {Initialized} event the first time it is successfully executed. + */ + function _disableInitializers() internal virtual { + // solhint-disable-next-line var-name-mixedcase + InitializableStorage storage $ = _getInitializableStorage(); + + if ($._initializing) { + revert InvalidInitialization(); + } + if ($._initialized != type(uint64).max) { + $._initialized = type(uint64).max; + emit Initialized(type(uint64).max); + } + } + + /** + * @dev Returns the highest version that has been initialized. See {reinitializer}. + */ + function _getInitializedVersion() internal view returns (uint64) { + return _getInitializableStorage()._initialized; + } + + /** + * @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}. + */ + function _isInitializing() internal view returns (bool) { + return _getInitializableStorage()._initializing; + } + + /** + * @dev Returns a pointer to the storage namespace. + */ + // solhint-disable-next-line var-name-mixedcase + function _getInitializableStorage() private pure returns (InitializableStorage storage $) { + assembly { + $.slot := INITIALIZABLE_STORAGE + } + } +} diff --git a/contracts/proxy/utils/UUPSUpgradeable.sol b/contracts/proxy/utils/UUPSUpgradeable.sol new file mode 100644 index 000000000..8fad1f711 --- /dev/null +++ b/contracts/proxy/utils/UUPSUpgradeable.sol @@ -0,0 +1,147 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (proxy/utils/UUPSUpgradeable.sol) + +pragma solidity ^0.8.20; + +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 + * {ERC1967Proxy}, when this contract is set as the implementation behind such a proxy. + * + * A security mechanism ensures that an upgrade does not turn off upgradeability accidentally, although this risk is + * reinstated if the upgrade retains upgradeability but removes the security mechanism, e.g. by replacing + * `UUPSUpgradeable` with a custom implementation of upgrades. + * + * 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 + 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, making it 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. + */ + 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 + * for UUPS and transparent proxies that are using the current contract as their implementation. Execution of a + * function through ERC1167 minimal proxies (clones) would not normally pass this test, but is not guaranteed to + * fail. + */ + modifier onlyProxy() { + _checkProxy(); + _; + } + + /** + * @dev Check that the execution is not being performed through a delegate call. This allows a function to be + * callable on the implementing contract but not through proxies. + */ + modifier notDelegated() { + _checkNotDelegated(); + _; + } + + /** + * @dev Implementation of the ERC1822 {proxiableUUID} function. This returns the storage slot used by the + * implementation. It is used to validate the implementation's compatibility when performing an upgrade. + * + * IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks + * 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 notDelegated returns (bytes32) { + return ERC1967Utils.IMPLEMENTATION_SLOT; + } + + /** + * @dev Upgrade the implementation of the proxy to `newImplementation`, and subsequently execute the function call + * encoded in `data`. + * + * Calls {_authorizeUpgrade}. + * + * Emits an {Upgraded} event. + * + * @custom:oz-upgrades-unsafe-allow-reachable delegatecall + */ + function upgradeToAndCall(address newImplementation, bytes memory data) public payable virtual onlyProxy { + _authorizeUpgrade(newImplementation); + _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}. + * + * 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 newImplementation) internal virtual; + + /** + * @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. + */ + 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); + } catch { + // The implementation is not UUPS + revert ERC1967Utils.ERC1967InvalidImplementation(newImplementation); + } + } +} diff --git a/contracts/token/ERC1155/ERC1155.sol b/contracts/token/ERC1155/ERC1155.sol new file mode 100644 index 000000000..b88381e7b --- /dev/null +++ b/contracts/token/ERC1155/ERC1155.sol @@ -0,0 +1,468 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (token/ERC1155/ERC1155.sol) + +pragma solidity ^0.8.20; + +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. + * See https://eips.ethereum.org/EIPS/eip-1155 + * Originally based on code by Enjin: https://github.com/enjin/erc-1155 + */ +abstract contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI, IERC1155Errors { + using Arrays for uint256[]; + using Arrays for address[]; + + mapping(uint256 id => mapping(address account => uint256)) private _balances; + + mapping(address account => mapping(address operator => bool)) private _operatorApprovals; + + // Used as the URI for all token types by relying on ID substitution, e.g. https://token-cdn-domain/{id}.json + string private _uri; + + /** + * @dev See {_setURI}. + */ + constructor(string memory uri_) { + _setURI(uri_); + } + + /** + * @dev See {IERC165-supportsInterface}. + */ + function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) { + return + interfaceId == type(IERC1155).interfaceId || + interfaceId == type(IERC1155MetadataURI).interfaceId || + super.supportsInterface(interfaceId); + } + + /** + * @dev See {IERC1155MetadataURI-uri}. + * + * This implementation returns the same URI for *all* token types. It relies + * on the token type ID substitution mechanism + * https://eips.ethereum.org/EIPS/eip-1155#metadata[defined in the EIP]. + * + * Clients calling this function must replace the `\{id\}` substring with the + * actual token type ID. + */ + function uri(uint256 /* id */) public view virtual returns (string memory) { + return _uri; + } + + /** + * @dev See {IERC1155-balanceOf}. + */ + function balanceOf(address account, uint256 id) public view virtual returns (uint256) { + return _balances[id][account]; + } + + /** + * @dev See {IERC1155-balanceOfBatch}. + * + * Requirements: + * + * - `accounts` and `ids` must have the same length. + */ + function balanceOfBatch( + address[] memory accounts, + uint256[] memory ids + ) public view virtual returns (uint256[] memory) { + if (accounts.length != ids.length) { + revert ERC1155InvalidArrayLength(ids.length, accounts.length); + } + + uint256[] memory batchBalances = new uint256[](accounts.length); + + for (uint256 i = 0; i < accounts.length; ++i) { + batchBalances[i] = balanceOf(accounts.unsafeMemoryAccess(i), ids.unsafeMemoryAccess(i)); + } + + return batchBalances; + } + + /** + * @dev See {IERC1155-setApprovalForAll}. + */ + 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 returns (bool) { + return _operatorApprovals[account][operator]; + } + + /** + * @dev See {IERC1155-safeTransferFrom}. + */ + 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, value, data); + } + + /** + * @dev See {IERC1155-safeBatchTransferFrom}. + */ + function safeBatchTransferFrom( + address from, + address to, + uint256[] memory ids, + uint256[] memory values, + bytes memory data + ) public virtual { + address sender = _msgSender(); + if (from != sender && !isApprovedForAll(from, sender)) { + revert ERC1155MissingApprovalForAll(sender, from); + } + _safeBatchTransferFrom(from, to, ids, values, data); + } + + /** + * @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. + * + * Requirements: + * + * - 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 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 value = values.unsafeMemoryAccess(i); + + if (from != address(0)) { + uint256 fromBalance = _balances[id][from]; + if (fromBalance < value) { + revert ERC1155InsufficientBalance(from, fromBalance, value, id); + } + unchecked { + // Overflow not possible: value <= fromBalance + _balances[id][from] = fromBalance - value; + } + } + + if (to != address(0)) { + _balances[id][to] += value; + } + } + + if (ids.length == 1) { + uint256 id = ids.unsafeMemoryAccess(0); + uint256 value = values.unsafeMemoryAccess(0); + emit TransferSingle(operator, from, to, id, value); + } else { + 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 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 `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 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 values) = _asSingletonArrays(id, value); + _updateWithAcceptanceCheck(from, to, ids, values, data); + } + + /** + * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {_safeTransferFrom}. + * + * Emits a {TransferBatch} event. + * + * Requirements: + * + * - 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 values, + bytes memory data + ) internal { + if (to == address(0)) { + revert ERC1155InvalidReceiver(address(0)); + } + if (from == address(0)) { + revert ERC1155InvalidSender(address(0)); + } + _updateWithAcceptanceCheck(from, to, ids, values, data); + } + + /** + * @dev Sets a new URI for all token types, by relying on the token type ID + * substitution mechanism + * 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 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 + * interpreted by clients as + * `https://token-cdn-domain/000000000000000000000000000000000000000000000000000000000004cce0.json` + * for token type ID 0x4cce0. + * + * See {uri}. + * + * Because these URIs cannot be meaningfully represented by the {URI} event, + * this function emits no events. + */ + function _setURI(string memory newuri) internal virtual { + _uri = newuri; + } + + /** + * @dev Creates a `value` amount of tokens of type `id`, and assigns them to `to`. + * + * Emits a {TransferSingle} event. + * + * Requirements: + * + * - `to` cannot be the zero address. + * - 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 value, bytes memory data) internal { + if (to == address(0)) { + revert ERC1155InvalidReceiver(address(0)); + } + (uint256[] memory ids, uint256[] memory values) = _asSingletonArrays(id, value); + _updateWithAcceptanceCheck(address(0), to, ids, values, data); + } + + /** + * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {_mint}. + * + * Emits a {TransferBatch} event. + * + * Requirements: + * + * - `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 values, bytes memory data) internal { + if (to == address(0)) { + revert ERC1155InvalidReceiver(address(0)); + } + _updateWithAcceptanceCheck(address(0), to, ids, values, data); + } + + /** + * @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 `value` amount of tokens of type `id`. + */ + function _burn(address from, uint256 id, uint256 value) internal { + if (from == address(0)) { + revert ERC1155InvalidSender(address(0)); + } + (uint256[] memory ids, uint256[] memory values) = _asSingletonArrays(id, value); + _updateWithAcceptanceCheck(from, address(0), ids, values, ""); + } + + /** + * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {_burn}. + * + * Emits a {TransferBatch} event. + * + * Requirements: + * + * - `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 values) internal { + if (from == address(0)) { + revert ERC1155InvalidSender(address(0)); + } + _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 (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 value, + bytes memory data + ) private { + if (to.code.length > 0) { + try IERC1155Receiver(to).onERC1155Received(operator, from, id, value, data) returns (bytes4 response) { + if (response != IERC1155Receiver.onERC1155Received.selector) { + // Tokens rejected + 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)) + } + } + } + } + } + + /** + * @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 values, + bytes memory data + ) private { + if (to.code.length > 0) { + try IERC1155Receiver(to).onERC1155BatchReceived(operator, from, ids, values, data) returns ( + bytes4 response + ) { + if (response != IERC1155Receiver.onERC1155BatchReceived.selector) { + // Tokens rejected + 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)) + } + } + } + } + } + + /** + * @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 new file mode 100644 index 000000000..7e86df9a8 --- /dev/null +++ b/contracts/token/ERC1155/IERC1155.sol @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (token/ERC1155/IERC1155.sol) + +pragma solidity ^0.8.20; + +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]. + */ +interface IERC1155 is IERC165 { + /** + * @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); + + /** + * @dev Equivalent to multiple {TransferSingle} events, where `operator`, `from` and `to` are the same for all + * transfers. + */ + event TransferBatch( + address indexed operator, + address indexed from, + address indexed to, + uint256[] ids, + uint256[] values + ); + + /** + * @dev Emitted when `account` grants or revokes permission to `operator` to transfer their tokens, according to + * `approved`. + */ + event ApprovalForAll(address indexed account, address indexed operator, bool approved); + + /** + * @dev Emitted when the URI for token type `id` changes to `value`, if it is a non-programmatic URI. + * + * If an {URI} event was emitted for `id`, the standard + * https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions[guarantees] that `value` will equal the value + * returned by {IERC1155MetadataURI-uri}. + */ + event URI(string value, uint256 indexed id); + + /** + * @dev Returns the value of tokens of token type `id` owned by `account`. + * + * Requirements: + * + * - `account` cannot be the zero address. + */ + function balanceOf(address account, uint256 id) external view returns (uint256); + + /** + * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {balanceOf}. + * + * Requirements: + * + * - `accounts` and `ids` must have the same length. + */ + 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`, + * + * Emits an {ApprovalForAll} event. + * + * Requirements: + * + * - `operator` cannot be the caller. + */ + function setApprovalForAll(address operator, bool approved) external; + + /** + * @dev Returns true if `operator` is approved to transfer ``account``'s tokens. + * + * See {setApprovalForAll}. + */ + function isApprovedForAll(address account, address operator) external view returns (bool); + + /** + * @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. + * Ensure to follow the checks-effects-interactions pattern and consider employing + * reentrancy guards when interacting with untrusted contracts. + * + * Emits a {TransferSingle} event. + * + * Requirements: + * + * - `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 `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 value, bytes calldata data) external; + + /** + * @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 {onERC1155BatchReceived} 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: + * + * - `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. + */ + function safeBatchTransferFrom( + address from, + address to, + uint256[] calldata ids, + uint256[] calldata values, + bytes calldata data + ) external; +} diff --git a/contracts/token/ERC1155/IERC1155Receiver.sol b/contracts/token/ERC1155/IERC1155Receiver.sol new file mode 100644 index 000000000..4abe83469 --- /dev/null +++ b/contracts/token/ERC1155/IERC1155Receiver.sol @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (token/ERC1155/IERC1155Receiver.sol) + +pragma solidity ^0.8.20; + +import {IERC165} from "../../utils/introspection/IERC165.sol"; + +/** + * @dev Interface that must be implemented by smart contracts in order to receive + * ERC-1155 token transfers. + */ +interface IERC1155Receiver is IERC165 { + /** + * @dev Handles the receipt of a single ERC1155 token type. This function is + * called at the end of a `safeTransferFrom` after the balance has been updated. + * + * NOTE: To accept the transfer, this must return + * `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` + * (i.e. 0xf23a6e61, or its own function selector). + * + * @param operator The address which initiated the transfer (i.e. msg.sender) + * @param from The address which previously owned the token + * @param id The ID of the token being transferred + * @param value The amount of tokens being transferred + * @param data Additional data with no specified format + * @return `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` if transfer is allowed + */ + function onERC1155Received( + address operator, + address from, + uint256 id, + uint256 value, + bytes calldata data + ) external returns (bytes4); + + /** + * @dev Handles the receipt of a multiple ERC1155 token types. This function + * is called at the end of a `safeBatchTransferFrom` after the balances have + * been updated. + * + * NOTE: To accept the transfer(s), this must return + * `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` + * (i.e. 0xbc197c81, or its own function selector). + * + * @param operator The address which initiated the batch transfer (i.e. msg.sender) + * @param from The address which previously owned the token + * @param ids An array containing ids of each token being transferred (order and length must match values array) + * @param values An array containing amounts of each token being transferred (order and length must match ids array) + * @param data Additional data with no specified format + * @return `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` if transfer is allowed + */ + function onERC1155BatchReceived( + address operator, + address from, + uint256[] calldata ids, + uint256[] calldata values, + bytes calldata data + ) external returns (bytes4); +} diff --git a/contracts/token/ERC1155/README.adoc b/contracts/token/ERC1155/README.adoc new file mode 100644 index 000000000..1a56358ef --- /dev/null +++ b/contracts/token/ERC1155/README.adoc @@ -0,0 +1,41 @@ += ERC 1155 + +[.readme-notice] +NOTE: This document is better viewed at https://docs.openzeppelin.com/contracts/api/token/erc1155 + +This set of interfaces and contracts are all related to the https://eips.ethereum.org/EIPS/eip-1155[ERC1155 Multi Token Standard]. + +The EIP consists of three interfaces which fulfill different roles, found here as {IERC1155}, {IERC1155MetadataURI} and {IERC1155Receiver}. + +{ERC1155} implements the mandatory {IERC1155} interface, as well as the optional extension {IERC1155MetadataURI}, by relying on the substitution mechanism to use the same URI for all token types, dramatically reducing gas costs. + +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. + +== Core + +{{IERC1155}} + +{{IERC1155MetadataURI}} + +{{ERC1155}} + +{{IERC1155Receiver}} + +== Extensions + +{{ERC1155Pausable}} + +{{ERC1155Burnable}} + +{{ERC1155Supply}} + +{{ERC1155URIStorage}} + +== Utilities + +{{ERC1155Holder}} diff --git a/contracts/token/ERC1155/extensions/ERC1155Burnable.sol b/contracts/token/ERC1155/extensions/ERC1155Burnable.sol new file mode 100644 index 000000000..a6264f880 --- /dev/null +++ b/contracts/token/ERC1155/extensions/ERC1155Burnable.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (token/ERC1155/extensions/ERC1155Burnable.sol) + +pragma solidity ^0.8.20; + +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. + */ +abstract contract ERC1155Burnable is ERC1155 { + function burn(address account, uint256 id, uint256 value) public virtual { + if (account != _msgSender() && !isApprovedForAll(account, _msgSender())) { + revert ERC1155MissingApprovalForAll(_msgSender(), account); + } + + _burn(account, id, value); + } + + function burnBatch(address account, uint256[] memory ids, uint256[] memory values) public virtual { + if (account != _msgSender() && !isApprovedForAll(account, _msgSender())) { + revert ERC1155MissingApprovalForAll(_msgSender(), account); + } + + _burnBatch(account, ids, values); + } +} diff --git a/contracts/token/ERC1155/extensions/ERC1155Pausable.sol b/contracts/token/ERC1155/extensions/ERC1155Pausable.sol new file mode 100644 index 000000000..6d2e79811 --- /dev/null +++ b/contracts/token/ERC1155/extensions/ERC1155Pausable.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (token/ERC1155/extensions/ERC1155Pausable.sol) + +pragma solidity ^0.8.20; + +import {ERC1155} from "../ERC1155.sol"; +import {Pausable} from "../../../utils/Pausable.sol"; + +/** + * @dev ERC1155 token with pausable token transfers, minting and burning. + * + * Useful for scenarios such as preventing trades until the end of an evaluation + * period, or having an emergency switch for freezing all token transfers in the + * event of a large bug. + * + * IMPORTANT: This contract does not include public pause and unpause functions. In + * 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 pause mechanism of the contract unreachable, and thus unusable. + */ +abstract contract ERC1155Pausable is ERC1155, Pausable { + /** + * @dev See {ERC1155-_update}. + * + * Requirements: + * + * - the contract must not be paused. + */ + function _update( + address from, + address to, + uint256[] memory ids, + uint256[] memory values + ) internal virtual override whenNotPaused { + super._update(from, to, ids, values); + } +} diff --git a/contracts/token/ERC1155/extensions/ERC1155Supply.sol b/contracts/token/ERC1155/extensions/ERC1155Supply.sol new file mode 100644 index 000000000..7f5b16316 --- /dev/null +++ b/contracts/token/ERC1155/extensions/ERC1155Supply.sol @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (token/ERC1155/extensions/ERC1155Supply.sol) + +pragma solidity ^0.8.20; + +import {ERC1155} from "../ERC1155.sol"; + +/** + * @dev Extension of ERC1155 that adds tracking of total supply per id. + * + * Useful for scenarios where Fungible and Non-fungible tokens have to be + * 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. + * + * CAUTION: This extension should not be added in an upgrade to an already deployed contract. + */ +abstract contract ERC1155Supply is ERC1155 { + mapping(uint256 id => uint256) private _totalSupply; + uint256 private _totalSupplyAll; + + /** + * @dev Total value of tokens in with a given id. + */ + function totalSupply(uint256 id) public view virtual returns (uint256) { + return _totalSupply[id]; + } + + /** + * @dev Total value of tokens. + */ + function totalSupply() public view virtual returns (uint256) { + return _totalSupplyAll; + } + + /** + * @dev Indicates whether any token exist with a given id, or not. + */ + function exists(uint256 id) public view virtual returns (bool) { + return totalSupply(id) > 0; + } + + /** + * @dev See {ERC1155-_update}. + */ + function _update( + address from, + address to, + uint256[] memory ids, + uint256[] memory values + ) internal virtual override { + super._update(from, to, ids, values); + + if (from == address(0)) { + uint256 totalMintValue = 0; + for (uint256 i = 0; i < ids.length; ++i) { + uint256 value = values[i]; + // Overflow check required: The rest of the code assumes that totalSupply never overflows + _totalSupply[ids[i]] += value; + totalMintValue += value; + } + // Overflow check required: The rest of the code assumes that totalSupplyAll never overflows + _totalSupplyAll += totalMintValue; + } + + if (to == address(0)) { + uint256 totalBurnValue = 0; + for (uint256 i = 0; i < ids.length; ++i) { + uint256 value = values[i]; + + unchecked { + // 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: totalBurnValue = sum_i(values[i]) <= sum_i(totalSupply(ids[i])) <= totalSupplyAll + _totalSupplyAll -= totalBurnValue; + } + } + } +} diff --git a/contracts/token/ERC1155/extensions/ERC1155URIStorage.sol b/contracts/token/ERC1155/extensions/ERC1155URIStorage.sol new file mode 100644 index 000000000..dfbde20c9 --- /dev/null +++ b/contracts/token/ERC1155/extensions/ERC1155URIStorage.sol @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (token/ERC1155/extensions/ERC1155URIStorage.sol) + +pragma solidity ^0.8.20; + +import {Strings} from "../../../utils/Strings.sol"; +import {ERC1155} from "../ERC1155.sol"; + +/** + * @dev ERC1155 token with storage based token URI management. + * Inspired by the ERC721URIStorage extension + */ +abstract contract ERC1155URIStorage is ERC1155 { + using Strings for uint256; + + // Optional base URI + string private _baseURI = ""; + + // Optional mapping for token URIs + mapping(uint256 tokenId => string) private _tokenURIs; + + /** + * @dev See {IERC1155MetadataURI-uri}. + * + * This implementation returns the concatenation of the `_baseURI` + * and the token-specific uri if the latter is set + * + * This enables the following behaviors: + * + * - if `_tokenURIs[tokenId]` is set, then the result is the concatenation + * of `_baseURI` and `_tokenURIs[tokenId]` (keep in mind that `_baseURI` + * is empty per default); + * + * - if `_tokenURIs[tokenId]` is NOT set then we fallback to `super.uri()` + * which in most cases will contain `ERC1155._uri`; + * + * - if `_tokenURIs[tokenId]` is NOT set, and if the parents do not have a + * uri value set, then the result is empty. + */ + 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 string.concat). + return bytes(tokenURI).length > 0 ? string.concat(_baseURI, tokenURI) : super.uri(tokenId); + } + + /** + * @dev Sets `tokenURI` as the tokenURI of `tokenId`. + */ + function _setURI(uint256 tokenId, string memory tokenURI) internal virtual { + _tokenURIs[tokenId] = tokenURI; + emit URI(uri(tokenId), tokenId); + } + + /** + * @dev Sets `baseURI` as the `_baseURI` for all tokens + */ + function _setBaseURI(string memory baseURI) internal virtual { + _baseURI = baseURI; + } +} diff --git a/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol b/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol new file mode 100644 index 000000000..c21848645 --- /dev/null +++ b/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (token/ERC1155/extensions/IERC1155MetadataURI.sol) + +pragma solidity ^0.8.20; + +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]. + */ +interface IERC1155MetadataURI is IERC1155 { + /** + * @dev Returns the URI for token type `id`. + * + * If the `\{id\}` substring is present in the URI, it must be replaced by + * clients with the actual token type ID. + */ + function uri(uint256 id) external view returns (string memory); +} diff --git a/contracts/token/ERC1155/utils/ERC1155Holder.sol b/contracts/token/ERC1155/utils/ERC1155Holder.sol new file mode 100644 index 000000000..98787225b --- /dev/null +++ b/contracts/token/ERC1155/utils/ERC1155Holder.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (token/ERC1155/utils/ERC1155Holder.sol) + +pragma solidity ^0.8.20; + +import {IERC165, ERC165} from "../../../utils/introspection/ERC165.sol"; +import {IERC1155Receiver} from "../IERC1155Receiver.sol"; + +/** + * @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 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, + uint256, + uint256, + bytes memory + ) public virtual override returns (bytes4) { + return this.onERC1155Received.selector; + } + + function onERC1155BatchReceived( + address, + address, + uint256[] memory, + uint256[] memory, + bytes memory + ) public virtual override returns (bytes4) { + return this.onERC1155BatchReceived.selector; + } +} diff --git a/contracts/token/ERC20/ERC20.sol b/contracts/token/ERC20/ERC20.sol new file mode 100644 index 000000000..e45639193 --- /dev/null +++ b/contracts/token/ERC20/ERC20.sol @@ -0,0 +1,316 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (token/ERC20/ERC20.sol) + +pragma solidity ^0.8.20; + +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. + * + * 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}. + * + * TIP: For a detailed writeup see our guide + * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How + * to implement supply mechanisms]. + * + * The default value of {decimals} is 18. To change this, you should override + * this function so it returns a different value. + * + * We have followed general OpenZeppelin Contracts guidelines: functions revert + * instead returning `false` on failure. This behavior is nonetheless + * conventional and does not conflict with the expectations of ERC20 + * applications. + * + * Additionally, an {Approval} event is emitted on calls to {transferFrom}. + * This allows applications to reconstruct the allowance for all accounts just + * by listening to said events. Other implementations of the EIP may not emit + * these events, as it isn't required by the specification. + */ +abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors { + mapping(address account => uint256) private _balances; + + mapping(address account => mapping(address spender => uint256)) private _allowances; + + uint256 private _totalSupply; + + string private _name; + string private _symbol; + + /** + * @dev Sets the values for {name} and {symbol}. + * + * All two of these values are immutable: they can only be set once during + * construction. + */ + constructor(string memory name_, string memory symbol_) { + _name = name_; + _symbol = symbol_; + } + + /** + * @dev Returns the name of the token. + */ + function name() public view virtual returns (string memory) { + return _name; + } + + /** + * @dev Returns the symbol of the token, usually a shorter version of the + * name. + */ + function symbol() public view virtual returns (string memory) { + return _symbol; + } + + /** + * @dev Returns the number of decimals used to get its user representation. + * For example, if `decimals` equals `2`, a balance of `505` tokens should + * be displayed to a user as `5.05` (`505 / 10 ** 2`). + * + * Tokens usually opt for a value of 18, imitating the relationship between + * Ether and Wei. This is the default value returned by this function, unless + * it's overridden. + * + * NOTE: This information is only used for _display_ purposes: it in + * no way affects any of the arithmetic of the contract, including + * {IERC20-balanceOf} and {IERC20-transfer}. + */ + function decimals() public view virtual returns (uint8) { + return 18; + } + + /** + * @dev See {IERC20-totalSupply}. + */ + function totalSupply() public view virtual returns (uint256) { + return _totalSupply; + } + + /** + * @dev See {IERC20-balanceOf}. + */ + function balanceOf(address account) public view virtual returns (uint256) { + return _balances[account]; + } + + /** + * @dev See {IERC20-transfer}. + * + * Requirements: + * + * - `to` cannot be the zero address. + * - the caller must have a balance of at least `value`. + */ + function transfer(address to, uint256 value) public virtual returns (bool) { + address owner = _msgSender(); + _transfer(owner, to, value); + return true; + } + + /** + * @dev See {IERC20-allowance}. + */ + function allowance(address owner, address spender) public view virtual returns (uint256) { + return _allowances[owner][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. + * + * Requirements: + * + * - `spender` cannot be the zero address. + */ + function approve(address spender, uint256 value) public virtual returns (bool) { + address owner = _msgSender(); + _approve(owner, spender, value); + return true; + } + + /** + * @dev See {IERC20-transferFrom}. + * + * Emits an {Approval} event indicating the updated allowance. This is not + * required by the EIP. See the note at the beginning of {ERC20}. + * + * NOTE: Does not update the allowance if the current allowance + * is the maximum `uint256`. + * + * Requirements: + * + * - `from` and `to` cannot be the zero address. + * - `from` must have a balance of at least `value`. + * - the caller must have allowance for ``from``'s tokens of at least + * `value`. + */ + function transferFrom(address from, address to, uint256 value) public virtual returns (bool) { + address spender = _msgSender(); + _spendAllowance(from, spender, value); + _transfer(from, to, value); + return true; + } + + /** + * @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. + * + * Emits a {Transfer} event. + * + * NOTE: This function is not virtual, {_update} should be overridden instead. + */ + 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, value); + } + + /** + * @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 value) internal virtual { + if (from == address(0)) { + // Overflow check required: The rest of the code assumes that totalSupply never overflows + _totalSupply += value; + } else { + uint256 fromBalance = _balances[from]; + if (fromBalance < value) { + revert ERC20InsufficientBalance(from, fromBalance, value); + } + unchecked { + // Overflow not possible: value <= fromBalance <= totalSupply. + _balances[from] = fromBalance - value; + } + } + + if (to == address(0)) { + unchecked { + // Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply. + _totalSupply -= value; + } + } else { + unchecked { + // Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256. + _balances[to] += value; + } + } + + emit Transfer(from, to, value); + } + + /** + * @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 value) internal { + if (account == address(0)) { + revert ERC20InvalidReceiver(address(0)); + } + _update(address(0), account, value); + } + + /** + * @dev Destroys a `value` amount of tokens from `account`, lowering the total supply. + * 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 value) internal { + if (account == address(0)) { + revert ERC20InvalidSender(address(0)); + } + _update(account, address(0), value); + } + + /** + * @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. + * + * Emits an {Approval} event. + * + * Requirements: + * + * - `owner` cannot be the zero address. + * - `spender` cannot be the zero address. + * + * Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument. + */ + function _approve(address owner, address spender, uint256 value) internal { + _approve(owner, spender, value, true); + } + + /** + * @dev Variant of {_approve} with an optional flag to 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 value, bool) internal virtual override { + * super._approve(owner, spender, value, true); + * } + * ``` + * + * Requirements are the same as {_approve}. + */ + 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] = value; + if (emitEvent) { + emit Approval(owner, spender, value); + } + } + + /** + * @dev Updates `owner` s allowance for `spender` based on spent `value`. + * + * Does not update the allowance value in case of infinite allowance. + * Revert if not enough allowance is available. + * + * Does not emit an {Approval} event. + */ + function _spendAllowance(address owner, address spender, uint256 value) internal virtual { + uint256 currentAllowance = allowance(owner, spender); + if (currentAllowance != type(uint256).max) { + if (currentAllowance < value) { + revert ERC20InsufficientAllowance(spender, currentAllowance, value); + } + unchecked { + _approve(owner, spender, currentAllowance - value, false); + } + } + } +} diff --git a/contracts/token/ERC20/IERC20.sol b/contracts/token/ERC20/IERC20.sol new file mode 100644 index 000000000..8b00999bf --- /dev/null +++ b/contracts/token/ERC20/IERC20.sol @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (token/ERC20/IERC20.sol) + +pragma solidity ^0.8.20; + +/** + * @dev Interface of the ERC20 standard as defined in the EIP. + */ +interface IERC20 { + /** + * @dev Emitted when `value` tokens are moved from one account (`from`) to + * another (`to`). + * + * Note that `value` may be zero. + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev Emitted when the allowance of a `spender` for an `owner` is set by + * a call to {approve}. `value` is the new allowance. + */ + event Approval(address indexed owner, address indexed spender, uint256 value); + + /** + * @dev Returns the value of tokens in existence. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns the value of tokens owned by `account`. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @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 value) external returns (bool); + + /** + * @dev Returns the remaining number of tokens that `spender` will be + * allowed to spend on behalf of `owner` through {transferFrom}. This is + * zero by default. + * + * This value changes when {approve} or {transferFrom} are called. + */ + function allowance(address owner, address spender) external view returns (uint256); + + /** + * @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. + * + * IMPORTANT: Beware that changing an allowance with this method brings the risk + * that someone may use both the old and the new allowance by unfortunate + * transaction ordering. One possible solution to mitigate this race + * condition is to first reduce the spender's allowance to 0 and set the + * desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * + * Emits an {Approval} event. + */ + function approve(address spender, uint256 value) external returns (bool); + + /** + * @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 value) external returns (bool); +} diff --git a/contracts/token/ERC20/README.adoc b/contracts/token/ERC20/README.adoc new file mode 100644 index 000000000..2c508802d --- /dev/null +++ b/contracts/token/ERC20/README.adoc @@ -0,0 +1,67 @@ += ERC 20 + +[.readme-notice] +NOTE: This document is better viewed at https://docs.openzeppelin.com/contracts/api/token/erc20 + +This set of interfaces, contracts, and utilities are all related to the https://eips.ethereum.org/EIPS/eip-20[ERC20 Token Standard]. + +TIP: For an overview of ERC20 tokens and a walk through on how to create a token contract read our xref:ROOT:erc20.adoc[ERC20 guide]. + +There are a few core contracts that implement the behavior specified in the EIP: + +* {IERC20}: the interface all ERC20 implementations should conform to. +* {IERC20Metadata}: the extended ERC20 interface including the <>, <> and <> functions. +* {ERC20}: the implementation of the ERC20 interface, including the <>, <> and <> optional standard extension to the base interface. + +Additionally there are multiple custom extensions, including: + +* {ERC20Permit}: gasless approval of tokens (standardized as ERC2612). +* {ERC20Burnable}: destruction of own tokens. +* {ERC20Capped}: enforcement of a cap to the total supply when minting tokens. +* {ERC20Pausable}: ability to pause token transfers. +* {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. +* {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: + +* {SafeERC20}: a wrapper around the interface that eliminates the need to handle boolean return values. + +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. + +== Core + +{{IERC20}} + +{{IERC20Metadata}} + +{{ERC20}} + +== Extensions + +{{IERC20Permit}} + +{{ERC20Permit}} + +{{ERC20Burnable}} + +{{ERC20Capped}} + +{{ERC20Pausable}} + +{{ERC20Votes}} + +{{ERC20Wrapper}} + +{{ERC20FlashMint}} + +{{ERC4626}} + +== Utilities + +{{SafeERC20}} diff --git a/contracts/token/ERC20/extensions/ERC20Burnable.sol b/contracts/token/ERC20/extensions/ERC20Burnable.sol new file mode 100644 index 000000000..05008641b --- /dev/null +++ b/contracts/token/ERC20/extensions/ERC20Burnable.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (token/ERC20/extensions/ERC20Burnable.sol) + +pragma solidity ^0.8.20; + +import {ERC20} from "../ERC20.sol"; +import {Context} from "../../../utils/Context.sol"; + +/** + * @dev Extension of {ERC20} that allows token holders to destroy both their own + * tokens and those that they have an allowance for, in a way that can be + * recognized off-chain (via event analysis). + */ +abstract contract ERC20Burnable is Context, ERC20 { + /** + * @dev Destroys a `value` amount of tokens from the caller. + * + * See {ERC20-_burn}. + */ + function burn(uint256 value) public virtual { + _burn(_msgSender(), value); + } + + /** + * @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 + * `value`. + */ + 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 new file mode 100644 index 000000000..765f9c18e --- /dev/null +++ b/contracts/token/ERC20/extensions/ERC20Capped.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (token/ERC20/extensions/ERC20Capped.sol) + +pragma solidity ^0.8.20; + +import {ERC20} from "../ERC20.sol"; + +/** + * @dev Extension of {ERC20} that adds a cap to the supply of tokens. + */ +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_) { + if (cap_ == 0) { + revert ERC20InvalidCap(0); + } + _cap = cap_; + } + + /** + * @dev Returns the cap on the token's total supply. + */ + function cap() public view virtual returns (uint256) { + return _cap; + } + + /** + * @dev See {ERC20-_update}. + */ + function _update(address from, address to, uint256 value) internal virtual override { + super._update(from, to, value); + + if (from == address(0)) { + uint256 maxSupply = cap(); + uint256 supply = totalSupply(); + if (supply > maxSupply) { + revert ERC20ExceededCap(supply, maxSupply); + } + } + } +} diff --git a/contracts/token/ERC20/extensions/ERC20FlashMint.sol b/contracts/token/ERC20/extensions/ERC20FlashMint.sol new file mode 100644 index 000000000..1b9a53ca0 --- /dev/null +++ b/contracts/token/ERC20/extensions/ERC20FlashMint.sol @@ -0,0 +1,134 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (token/ERC20/extensions/ERC20FlashMint.sol) + +pragma solidity ^0.8.20; + +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 + * https://eips.ethereum.org/EIPS/eip-3156[ERC-3156]. + * + * 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. + */ +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 value 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. + * @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; + } + + /** + * @dev Returns the fee applied when doing flash loans. This function calls + * the {_flashFee} function which returns the fee applied when doing flash + * loans. + * @param token The token to be flash loaned. + * @param value The amount of tokens to be loaned. + * @return The fees applied to the corresponding flash loan. + */ + function flashFee(address token, uint256 value) public view virtual returns (uint256) { + if (token != address(this)) { + revert ERC3156UnsupportedToken(token); + } + return _flashFee(token, value); + } + + /** + * @dev Returns the fee applied when doing flash loans. By default this + * 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 value The amount of tokens to be loaned. + * @return The fees applied to the corresponding flash loan. + */ + function _flashFee(address token, uint256 value) internal view virtual returns (uint256) { + // silence warning about unused variable without the addition of bytecode. + token; + value; + return 0; + } + + /** + * @dev Returns the receiver address of the flash fee. By default this + * implementation returns the address(0) which means the fee amount will be burnt. + * This function can be overloaded to change the fee receiver. + * @return The address for which the flash fee will be sent to. + */ + function _flashFeeReceiver() internal view virtual returns (address) { + return address(0); + } + + /** + * @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 + * 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 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. + */ + // This function can reenter, but it doesn't pose a risk because it always preserves the property that the amount + // minted at the beginning is always recovered and burned at the end, or else the entire function will revert. + // slither-disable-next-line reentrancy-no-eth + function flashLoan( + IERC3156FlashBorrower receiver, + address token, + uint256 value, + bytes calldata data + ) public virtual returns (bool) { + uint256 maxLoan = maxFlashLoan(token); + if (value > maxLoan) { + revert ERC3156ExceededMaxLoan(maxLoan); + } + 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), value + fee); + if (fee == 0 || flashFeeReceiver == address(0)) { + _burn(address(receiver), value + fee); + } else { + _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 new file mode 100644 index 000000000..db0c0a535 --- /dev/null +++ b/contracts/token/ERC20/extensions/ERC20Pausable.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (token/ERC20/extensions/ERC20Pausable.sol) + +pragma solidity ^0.8.20; + +import {ERC20} from "../ERC20.sol"; +import {Pausable} from "../../../utils/Pausable.sol"; + +/** + * @dev ERC20 token with pausable token transfers, minting and burning. + * + * Useful for scenarios such as preventing trades until the end of an evaluation + * period, or having an emergency switch for freezing all token transfers in the + * event of a large bug. + * + * IMPORTANT: This contract does not include public pause and unpause functions. In + * 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 pause mechanism of the contract unreachable, and thus unusable. + */ +abstract contract ERC20Pausable is ERC20, Pausable { + /** + * @dev See {ERC20-_update}. + * + * Requirements: + * + * - the contract must not be paused. + */ + function _update(address from, address to, uint256 value) internal virtual override whenNotPaused { + super._update(from, to, value); + } +} diff --git a/contracts/token/ERC20/extensions/ERC20Permit.sol b/contracts/token/ERC20/extensions/ERC20Permit.sol new file mode 100644 index 000000000..f9d1b49e7 --- /dev/null +++ b/contracts/token/ERC20/extensions/ERC20Permit.sol @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (token/ERC20/extensions/ERC20Permit.sol) + +pragma solidity ^0.8.20; + +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 + * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612]. + * + * 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. + */ +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"`. + * + * It's a good idea to use the same `name` that is defined as the ERC20 token name. + */ + constructor(string memory name) EIP712(name, "1") {} + + /** + * @inheritdoc IERC20Permit + */ + function permit( + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) public virtual { + 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); + if (signer != owner) { + revert ERC2612InvalidSigner(signer, owner); + } + + _approve(owner, spender, value); + } + + /** + * @inheritdoc IERC20Permit + */ + function nonces(address owner) public view virtual override(IERC20Permit, Nonces) returns (uint256) { + return super.nonces(owner); + } + + /** + * @inheritdoc IERC20Permit + */ + // solhint-disable-next-line func-name-mixedcase + function DOMAIN_SEPARATOR() external view virtual returns (bytes32) { + return _domainSeparatorV4(); + } +} diff --git a/contracts/token/ERC20/extensions/ERC20Votes.sol b/contracts/token/ERC20/extensions/ERC20Votes.sol new file mode 100644 index 000000000..a3081bdce --- /dev/null +++ b/contracts/token/ERC20/extensions/ERC20Votes.sol @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (token/ERC20/extensions/ERC20Votes.sol) + +pragma solidity ^0.8.20; + +import {ERC20} from "../ERC20.sol"; +import {Votes} from "../../../governance/utils/Votes.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, + * and supports token supply up to 2^208^ - 1, while COMP is limited to 2^96^ - 1. + * + * 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 + * power can be queried through the public accessors {getVotes} and {getPastVotes}. + * + * 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. + */ +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(uint208).max` (2^208^ - 1). + * + * This maximum is enforced in {_update}. It limits the total supply of the token, which is otherwise a uint256, + * so that checkpoints can be stored in the Trace208 structure used by {{Votes}}. Increasing this value will not + * remove the underlying limitation, and will cause {_update} to fail because of a math overflow in + * {_transferVotingUnits}. An override could be used to further restrict the total supply (to a lower value) if + * additional logic requires it. When resolving override conflicts on this function, the minimum should be + * returned. + */ + function _maxSupply() internal view virtual returns (uint256) { + return type(uint208).max; + } + + /** + * @dev Move voting power when tokens are transferred. + * + * Emits a {IVotes-DelegateVotesChanged} event. + */ + 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(); + if (supply > cap) { + revert ERC20ExceededSafeSupply(supply, cap); + } + } + _transferVotingUnits(from, to, value); + } + + /** + * @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); + } + + /** + * @dev Get number of checkpoints for `account`. + */ + function numCheckpoints(address account) public view virtual returns (uint32) { + return _numCheckpoints(account); + } + + /** + * @dev Get the `pos`-th checkpoint for `account`. + */ + function checkpoints(address account, uint32 pos) public view virtual returns (Checkpoints.Checkpoint208 memory) { + return _checkpoints(account, pos); + } +} diff --git a/contracts/token/ERC20/extensions/ERC20Wrapper.sol b/contracts/token/ERC20/extensions/ERC20Wrapper.sol new file mode 100644 index 000000000..0d4ec867a --- /dev/null +++ b/contracts/token/ERC20/extensions/ERC20Wrapper.sol @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (token/ERC20/extensions/ERC20Wrapper.sol) + +pragma solidity ^0.8.20; + +import {IERC20, IERC20Metadata, ERC20} from "../ERC20.sol"; +import {SafeERC20} from "../utils/SafeERC20.sol"; + +/** + * @dev Extension of the ERC20 token contract to support token wrapping. + * + * 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. + */ +abstract contract ERC20Wrapper is ERC20 { + IERC20 private immutable _underlying; + + /** + * @dev The underlying token couldn't be wrapped. + */ + error ERC20InvalidUnderlying(address token); + + constructor(IERC20 underlyingToken) { + if (underlyingToken == this) { + revert ERC20InvalidUnderlying(address(this)); + } + _underlying = underlyingToken; + } + + /** + * @dev See {ERC20-decimals}. + */ + function decimals() public view virtual override returns (uint8) { + try IERC20Metadata(address(_underlying)).decimals() returns (uint8 value) { + return value; + } catch { + return super.decimals(); + } + } + + /** + * @dev Returns the address of the underlying ERC-20 token that is being wrapped. + */ + function underlying() public view returns (IERC20) { + return _underlying; + } + + /** + * @dev Allow a user to deposit underlying tokens and mint the corresponding number of wrapped tokens. + */ + function depositFor(address account, uint256 value) public virtual returns (bool) { + address sender = _msgSender(); + if (sender == address(this)) { + revert ERC20InvalidSender(address(this)); + } + 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 value) public virtual returns (bool) { + if (account == address(this)) { + revert ERC20InvalidReceiver(account); + } + _burn(_msgSender(), value); + SafeERC20.safeTransfer(_underlying, account, value); + return true; + } + + /** + * @dev Mint wrapped token to cover any underlyingTokens that would have been transferred by mistake. Internal + * function that can be exposed with access control if desired. + */ + function _recover(address account) internal virtual returns (uint256) { + uint256 value = _underlying.balanceOf(address(this)) - totalSupply(); + _mint(account, value); + return value; + } +} diff --git a/contracts/token/ERC20/extensions/ERC4626.sol b/contracts/token/ERC20/extensions/ERC4626.sol new file mode 100644 index 000000000..bded3b497 --- /dev/null +++ b/contracts/token/ERC20/extensions/ERC4626.sol @@ -0,0 +1,286 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (token/ERC20/extensions/ERC4626.sol) + +pragma solidity ^0.8.20; + +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 + * https://eips.ethereum.org/EIPS/eip-4626[EIP-4626]. + * + * This extension allows the minting and burning of "shares" (represented using the ERC20 inheritance) in exchange for + * underlying "assets" through standardized {deposit}, {mint}, {redeem} and {burn} workflows. This contract extends + * the ERC20 standard. Any additional extensions included along it would affect the "shares" token represented by this + * contract and not the "assets" token which is an independent contract. + * + * [CAUTION] + * ==== + * In empty (or nearly empty) ERC-4626 vaults, deposits are at high risk of being stolen through frontrunning + * with a "donation" to the vault that inflates the price of a share. This is variously known as a donation or inflation + * attack and is essentially a problem of slippage. Vault deployers can protect against this attack by making an initial + * deposit of a non-trivial amount of the asset, such that price manipulation becomes infeasible. Withdrawals may + * similarly be affected by slippage. Users can protect against this attack as well as unexpected slippage in general by + * verifying the amount received is as expected, using a wrapper that performs these checks such as + * https://github.com/fei-protocol/ERC4626#erc4626router-and-base[ERC4626Router]. + * + * Since v4.9, this implementation uses virtual assets and shares to mitigate that risk. The `_decimalsOffset()` + * corresponds to an offset in the decimal representation between the underlying asset's decimals and the vault + * decimals. This offset also determines the rate of virtual shares to virtual assets in the vault, which itself + * determines the initial exchange rate. While not fully preventing the attack, analysis shows that the default offset + * (0) makes it non-profitable, as a result of the value being captured by the virtual shares (out of the attacker's + * donation) matching the attacker's expected gains. With a larger offset, the attack becomes orders of magnitude more + * expensive than it is profitable. More details about the underlying math can be found + * xref:erc4626.adoc#inflation-attack[here]. + * + * The drawback of this approach is that the virtual shares do capture (a very small) part of the value being accrued + * to the vault. Also, if the vault experiences losses, the users try to exit the vault, the virtual shares and assets + * will cause the first user to exit to experience reduced losses in detriment to the last users that will experience + * bigger losses. Developers willing to revert back to the pre-v4.9 behavior just need to override the + * `_convertToShares` and `_convertToAssets` functions. + * + * To learn more, check out our xref:ROOT:erc4626.adoc[ERC-4626 guide]. + * ==== + */ +abstract contract ERC4626 is ERC20, IERC4626 { + using Math for uint256; + + 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). + */ + constructor(IERC20 asset_) { + (bool success, uint8 assetDecimals) = _tryGetAssetDecimals(asset_); + _underlyingDecimals = success ? assetDecimals : 18; + _asset = asset_; + } + + /** + * @dev Attempts to fetch the asset decimals. A return value of false indicates that the attempt failed in some way. + */ + function _tryGetAssetDecimals(IERC20 asset_) private view returns (bool, uint8) { + (bool success, bytes memory encodedDecimals) = address(asset_).staticcall( + abi.encodeCall(IERC20Metadata.decimals, ()) + ); + if (success && encodedDecimals.length >= 32) { + uint256 returnedDecimals = abi.decode(encodedDecimals, (uint256)); + if (returnedDecimals <= type(uint8).max) { + return (true, uint8(returnedDecimals)); + } + } + return (false, 0); + } + + /** + * @dev Decimals are computed by adding the decimal offset on top of the underlying asset's decimals. This + * "original" value is cached during construction of the vault contract. If this read operation fails (e.g., the + * asset has not been created yet), a default of 18 is used to represent the underlying asset's decimals. + * + * See {IERC20Metadata-decimals}. + */ + function decimals() public view virtual override(IERC20Metadata, ERC20) returns (uint8) { + return _underlyingDecimals + _decimalsOffset(); + } + + /** @dev See {IERC4626-asset}. */ + function asset() public view virtual returns (address) { + return address(_asset); + } + + /** @dev See {IERC4626-totalAssets}. */ + function totalAssets() public view virtual returns (uint256) { + return _asset.balanceOf(address(this)); + } + + /** @dev See {IERC4626-convertToShares}. */ + function convertToShares(uint256 assets) public view virtual returns (uint256) { + return _convertToShares(assets, Math.Rounding.Floor); + } + + /** @dev See {IERC4626-convertToAssets}. */ + function convertToAssets(uint256 shares) public view virtual returns (uint256) { + return _convertToAssets(shares, Math.Rounding.Floor); + } + + /** @dev See {IERC4626-maxDeposit}. */ + function maxDeposit(address) public view virtual returns (uint256) { + return type(uint256).max; + } + + /** @dev See {IERC4626-maxMint}. */ + function maxMint(address) public view virtual returns (uint256) { + return type(uint256).max; + } + + /** @dev See {IERC4626-maxWithdraw}. */ + function maxWithdraw(address owner) public view virtual returns (uint256) { + return _convertToAssets(balanceOf(owner), Math.Rounding.Floor); + } + + /** @dev See {IERC4626-maxRedeem}. */ + function maxRedeem(address owner) public view virtual returns (uint256) { + return balanceOf(owner); + } + + /** @dev See {IERC4626-previewDeposit}. */ + function previewDeposit(uint256 assets) public view virtual returns (uint256) { + return _convertToShares(assets, Math.Rounding.Floor); + } + + /** @dev See {IERC4626-previewMint}. */ + function previewMint(uint256 shares) public view virtual returns (uint256) { + return _convertToAssets(shares, Math.Rounding.Ceil); + } + + /** @dev See {IERC4626-previewWithdraw}. */ + function previewWithdraw(uint256 assets) public view virtual returns (uint256) { + return _convertToShares(assets, Math.Rounding.Ceil); + } + + /** @dev See {IERC4626-previewRedeem}. */ + function previewRedeem(uint256 shares) public view virtual returns (uint256) { + return _convertToAssets(shares, Math.Rounding.Floor); + } + + /** @dev See {IERC4626-deposit}. */ + function deposit(uint256 assets, address receiver) public virtual returns (uint256) { + uint256 maxAssets = maxDeposit(receiver); + if (assets > maxAssets) { + revert ERC4626ExceededMaxDeposit(receiver, assets, maxAssets); + } + + uint256 shares = previewDeposit(assets); + _deposit(_msgSender(), receiver, assets, shares); + + return shares; + } + + /** @dev See {IERC4626-mint}. + * + * 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 returns (uint256) { + uint256 maxShares = maxMint(receiver); + if (shares > maxShares) { + revert ERC4626ExceededMaxMint(receiver, shares, maxShares); + } + + uint256 assets = previewMint(shares); + _deposit(_msgSender(), receiver, assets, shares); + + return assets; + } + + /** @dev See {IERC4626-withdraw}. */ + function withdraw(uint256 assets, address receiver, address owner) public virtual returns (uint256) { + uint256 maxAssets = maxWithdraw(owner); + if (assets > maxAssets) { + revert ERC4626ExceededMaxWithdraw(owner, assets, maxAssets); + } + + uint256 shares = previewWithdraw(assets); + _withdraw(_msgSender(), receiver, owner, assets, shares); + + return shares; + } + + /** @dev See {IERC4626-redeem}. */ + function redeem(uint256 shares, address receiver, address owner) public virtual returns (uint256) { + uint256 maxShares = maxRedeem(owner); + if (shares > maxShares) { + revert ERC4626ExceededMaxRedeem(owner, shares, maxShares); + } + + uint256 assets = previewRedeem(shares); + _withdraw(_msgSender(), receiver, owner, assets, shares); + + return assets; + } + + /** + * @dev Internal conversion function (from assets to shares) with support for rounding direction. + */ + function _convertToShares(uint256 assets, Math.Rounding rounding) internal view virtual returns (uint256) { + return assets.mulDiv(totalSupply() + 10 ** _decimalsOffset(), totalAssets() + 1, rounding); + } + + /** + * @dev Internal conversion function (from shares to assets) with support for rounding direction. + */ + function _convertToAssets(uint256 shares, Math.Rounding rounding) internal view virtual returns (uint256) { + return shares.mulDiv(totalAssets() + 1, totalSupply() + 10 ** _decimalsOffset(), rounding); + } + + /** + * @dev Deposit/mint common workflow. + */ + function _deposit(address caller, address receiver, uint256 assets, uint256 shares) internal virtual { + // If _asset is ERC777, `transferFrom` can trigger a reentrancy 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. + // + // Conclusion: we need to do the transfer before we mint so that any reentrancy would happen before the + // assets are transferred and before the shares are minted, which is a valid state. + // slither-disable-next-line reentrancy-no-eth + SafeERC20.safeTransferFrom(_asset, caller, address(this), assets); + _mint(receiver, shares); + + emit Deposit(caller, receiver, assets, shares); + } + + /** + * @dev Withdraw/redeem common workflow. + */ + function _withdraw( + address caller, + address receiver, + address owner, + uint256 assets, + uint256 shares + ) internal virtual { + if (caller != owner) { + _spendAllowance(owner, caller, shares); + } + + // If _asset is ERC777, `transfer` can trigger a reentrancy AFTER the transfer happens through the + // `tokensReceived` hook. On the other hand, the `tokensToSend` hook, that is triggered before the transfer, + // calls the vault, which is assumed not malicious. + // + // Conclusion: we need to do the transfer after the burn so that any reentrancy would happen after the + // shares are burned and after the assets are transferred, which is a valid state. + _burn(owner, shares); + SafeERC20.safeTransfer(_asset, receiver, assets); + + emit Withdraw(caller, receiver, owner, assets, shares); + } + + function _decimalsOffset() internal view virtual returns (uint8) { + return 0; + } +} diff --git a/contracts/token/ERC20/extensions/IERC20Metadata.sol b/contracts/token/ERC20/extensions/IERC20Metadata.sol new file mode 100644 index 000000000..14dd7257f --- /dev/null +++ b/contracts/token/ERC20/extensions/IERC20Metadata.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (token/ERC20/extensions/IERC20Metadata.sol) + +pragma solidity ^0.8.20; + +import {IERC20} from "../IERC20.sol"; + +/** + * @dev Interface for the optional metadata functions from the ERC20 standard. + */ +interface IERC20Metadata is IERC20 { + /** + * @dev Returns the name of the token. + */ + function name() external view returns (string memory); + + /** + * @dev Returns the symbol of the token. + */ + function symbol() external view returns (string memory); + + /** + * @dev Returns the decimals places of the token. + */ + function decimals() external view returns (uint8); +} diff --git a/contracts/token/ERC20/extensions/IERC20Permit.sol b/contracts/token/ERC20/extensions/IERC20Permit.sol new file mode 100644 index 000000000..40ae7ba7b --- /dev/null +++ b/contracts/token/ERC20/extensions/IERC20Permit.sol @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (token/ERC20/extensions/IERC20Permit.sol) + +pragma solidity ^0.8.20; + +/** + * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in + * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612]. + * + * 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. + * + * ==== Security Considerations + * + * There are two important considerations concerning the use of `permit`. The first is that a valid permit signature + * expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be + * considered as an intention to spend the allowance in any specific way. The second is that because permits have + * built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should + * take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be + * generally recommended is: + * + * ```solidity + * function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public { + * try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {} + * doThing(..., value); + * } + * + * function doThing(..., uint256 value) public { + * token.safeTransferFrom(msg.sender, address(this), value); + * ... + * } + * ``` + * + * Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of + * `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also + * {SafeERC20-safeTransferFrom}). + * + * Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so + * contracts should have entry points that don't rely on permit. + */ +interface IERC20Permit { + /** + * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens, + * given ``owner``'s signed approval. + * + * IMPORTANT: The same issues {IERC20-approve} has related to transaction + * ordering also apply here. + * + * Emits an {Approval} event. + * + * Requirements: + * + * - `spender` cannot be the zero address. + * - `deadline` must be a timestamp in the future. + * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner` + * over the EIP712-formatted function arguments. + * - the signature must use ``owner``'s current nonce (see {nonces}). + * + * For more information on the signature format, see the + * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP + * section]. + * + * CAUTION: See Security Considerations above. + */ + function permit( + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external; + + /** + * @dev Returns the current nonce for `owner`. This value must be + * included whenever a signature is generated for {permit}. + * + * Every successful call to {permit} increases ``owner``'s nonce by one. This + * prevents a signature from being used multiple times. + */ + function nonces(address owner) external view returns (uint256); + + /** + * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}. + */ + // solhint-disable-next-line func-name-mixedcase + function DOMAIN_SEPARATOR() external view returns (bytes32); +} diff --git a/contracts/token/ERC20/utils/SafeERC20.sol b/contracts/token/ERC20/utils/SafeERC20.sol new file mode 100644 index 000000000..af8115957 --- /dev/null +++ b/contracts/token/ERC20/utils/SafeERC20.sol @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (token/ERC20/utils/SafeERC20.sol) + +pragma solidity ^0.8.20; + +import {IERC20} from "../IERC20.sol"; +import {IERC20Permit} from "../extensions/IERC20Permit.sol"; +import {Address} from "../../../utils/Address.sol"; + +/** + * @title SafeERC20 + * @dev Wrappers around ERC20 operations that throw on failure (when the token + * contract returns false). Tokens that return no value (and instead revert or + * throw on failure) are also supported, non-reverting calls are assumed to be + * successful. + * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract, + * which allows you to call the safe operations as `token.safeTransfer(...)`, etc. + */ +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. + */ + function safeTransfer(IERC20 token, address to, uint256 value) internal { + _callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value))); + } + + /** + * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the + * 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.encodeCall(token.transferFrom, (from, to, 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. + */ + function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal { + uint256 oldAllowance = token.allowance(address(this), spender); + forceApprove(token, spender, oldAllowance + 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 requestedDecrease) internal { + unchecked { + uint256 currentAllowance = token.allowance(address(this), spender); + if (currentAllowance < requestedDecrease) { + revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease); + } + forceApprove(token, spender, currentAllowance - requestedDecrease); + } + } + + /** + * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no 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)); + + if (!_callOptionalReturnBool(token, approvalCall)) { + _callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0))); + _callOptionalReturn(token, approvalCall); + } + } + + /** + * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement + * on the return value: the return value is optional (but if data is returned, it must not be false). + * @param token The token targeted by the call. + * @param data The call data (encoded using abi.encode or one of its variants). + */ + function _callOptionalReturn(IERC20 token, bytes memory data) private { + // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since + // 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); + if (returndata.length != 0 && !abi.decode(returndata, (bool))) { + revert SafeERC20FailedOperation(address(token)); + } + } + + /** + * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement + * on the return value: the return value is optional (but if data is returned, it must not be false). + * @param token The token targeted by the call. + * @param data The call data (encoded using abi.encode or one of its variants). + * + * This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead. + */ + function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) { + // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since + // we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false + // and not revert is the subcall reverts. + + (bool success, bytes memory returndata) = address(token).call(data); + return success && (returndata.length == 0 || abi.decode(returndata, (bool))) && address(token).code.length > 0; + } +} diff --git a/contracts/token/ERC721/ERC721.sol b/contracts/token/ERC721/ERC721.sol new file mode 100644 index 000000000..b912797d3 --- /dev/null +++ b/contracts/token/ERC721/ERC721.sol @@ -0,0 +1,483 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (token/ERC721/ERC721.sol) + +pragma solidity ^0.8.20; + +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 + * the Metadata extension, but not including the Enumerable extension, which is available separately as + * {ERC721Enumerable}. + */ +abstract contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Errors { + using Strings for uint256; + + // Token name + string private _name; + + // Token symbol + string private _symbol; + + mapping(uint256 tokenId => address) private _owners; + + mapping(address owner => uint256) private _balances; + + mapping(uint256 tokenId => address) private _tokenApprovals; + + mapping(address owner => mapping(address operator => bool)) private _operatorApprovals; + + /** + * @dev Initializes the contract by setting a `name` and a `symbol` to the token collection. + */ + constructor(string memory name_, string memory symbol_) { + _name = name_; + _symbol = symbol_; + } + + /** + * @dev See {IERC165-supportsInterface}. + */ + function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) { + return + interfaceId == type(IERC721).interfaceId || + interfaceId == type(IERC721Metadata).interfaceId || + super.supportsInterface(interfaceId); + } + + /** + * @dev See {IERC721-balanceOf}. + */ + function balanceOf(address owner) public view virtual returns (uint256) { + if (owner == address(0)) { + revert ERC721InvalidOwner(address(0)); + } + return _balances[owner]; + } + + /** + * @dev See {IERC721-ownerOf}. + */ + function ownerOf(uint256 tokenId) public view virtual returns (address) { + return _requireOwned(tokenId); + } + + /** + * @dev See {IERC721Metadata-name}. + */ + function name() public view virtual returns (string memory) { + return _name; + } + + /** + * @dev See {IERC721Metadata-symbol}. + */ + function symbol() public view virtual returns (string memory) { + return _symbol; + } + + /** + * @dev See {IERC721Metadata-tokenURI}. + */ + function tokenURI(uint256 tokenId) public view virtual returns (string memory) { + _requireOwned(tokenId); + + string memory baseURI = _baseURI(); + return bytes(baseURI).length > 0 ? string.concat(baseURI, tokenId.toString()) : ""; + } + + /** + * @dev Base URI for computing {tokenURI}. If set, the resulting URI for each + * token will be the concatenation of the `baseURI` and the `tokenId`. Empty + * by default, can be overridden in child contracts. + */ + function _baseURI() internal view virtual returns (string memory) { + return ""; + } + + /** + * @dev See {IERC721-approve}. + */ + function approve(address to, uint256 tokenId) public virtual { + _approve(to, tokenId, _msgSender()); + } + + /** + * @dev See {IERC721-getApproved}. + */ + function getApproved(uint256 tokenId) public view virtual returns (address) { + _requireOwned(tokenId); + + return _getApproved(tokenId); + } + + /** + * @dev See {IERC721-setApprovalForAll}. + */ + 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 returns (bool) { + return _operatorApprovals[owner][operator]; + } + + /** + * @dev See {IERC721-transferFrom}. + */ + function transferFrom(address from, address to, uint256 tokenId) public virtual { + if (to == address(0)) { + revert ERC721InvalidReceiver(address(0)); + } + // Setting an "auth" arguments enables the `_isAuthorized` check which verifies that the token exists + // (from != 0). Therefore, it is not needed to verify that the return value is not 0 here. + address previousOwner = _update(to, tokenId, _msgSender()); + if (previousOwner != from) { + revert ERC721IncorrectOwner(from, tokenId, previousOwner); + } + } + + /** + * @dev See {IERC721-safeTransferFrom}. + */ + function safeTransferFrom(address from, address to, uint256 tokenId) public { + safeTransferFrom(from, to, tokenId, ""); + } + + /** + * @dev See {IERC721-safeTransferFrom}. + */ + function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) public virtual { + transferFrom(from, to, tokenId); + _checkOnERC721Received(from, to, tokenId, data); + } + + /** + * @dev Returns the owner of the `tokenId`. Does NOT revert if token doesn't exist + * + * IMPORTANT: Any overrides to this function that add ownership of tokens not tracked by the + * core ERC721 logic MUST be matched with the use of {_increaseBalance} to keep balances + * consistent with ownership. The invariant to preserve is that for any address `a` the value returned by + * `balanceOf(a)` must be equal to the number of tokens such that `_ownerOf(tokenId)` is `a`. + */ + function _ownerOf(uint256 tokenId) internal view virtual returns (address) { + return _owners[tokenId]; + } + + /** + * @dev Returns the approved address for `tokenId`. Returns 0 if `tokenId` is not minted. + */ + function _getApproved(uint256 tokenId) internal view virtual returns (address) { + return _tokenApprovals[tokenId]; + } + + /** + * @dev Returns whether `spender` is allowed to manage `owner`'s tokens, or `tokenId` in + * particular (ignoring whether it is owned by `owner`). + * + * WARNING: This function assumes that `owner` is the actual owner of `tokenId` and does not verify this + * assumption. + */ + function _isAuthorized(address owner, address spender, uint256 tokenId) internal view virtual returns (bool) { + return + spender != address(0) && + (owner == spender || isApprovedForAll(owner, spender) || _getApproved(tokenId) == spender); + } + + /** + * @dev Checks if `spender` can operate on `tokenId`, assuming the provided `owner` is the actual owner. + * Reverts if `spender` does not have approval from the provided `owner` for the given token or for all its assets + * the `spender` for the specific `tokenId`. + * + * WARNING: This function assumes that `owner` is the actual owner of `tokenId` and does not verify this + * assumption. + */ + function _checkAuthorized(address owner, address spender, uint256 tokenId) internal view virtual { + if (!_isAuthorized(owner, spender, tokenId)) { + if (owner == address(0)) { + revert ERC721NonexistentToken(tokenId); + } else { + revert ERC721InsufficientApproval(spender, tokenId); + } + } + } + + /** + * @dev Unsafe write access to the balances, used by extensions that "mint" tokens using an {ownerOf} override. + * + * NOTE: the value is limited to type(uint128).max. This protect against _balance overflow. It is unrealistic that + * a uint256 would ever overflow from increments when these increments are bounded to uint128 values. + * + * WARNING: Increasing an account's balance using this function tends to be paired with an override of the + * {_ownerOf} function to resolve the ownership of the corresponding tokens so that balances and ownership + * remain consistent with one another. + */ + function _increaseBalance(address account, uint128 value) internal virtual { + unchecked { + _balances[account] += value; + } + } + + /** + * @dev Transfers `tokenId` from its current owner to `to`, or alternatively mints (or burns) if the current owner + * (or `to`) is the zero address. Returns the owner of the `tokenId` before the update. + * + * The `auth` argument is optional. If the value passed is non 0, then this function will check that + * `auth` is either the owner of the token, or approved to operate on the token (by the owner). + * + * Emits a {Transfer} event. + * + * NOTE: If overriding this function in a way that tracks balances, see also {_increaseBalance}. + */ + function _update(address to, uint256 tokenId, address auth) internal virtual returns (address) { + address from = _ownerOf(tokenId); + + // Perform (optional) operator check + if (auth != address(0)) { + _checkAuthorized(from, auth, tokenId); + } + + // Execute the update + if (from != address(0)) { + // Clear approval. No need to re-authorize or emit the Approval event + _approve(address(0), tokenId, address(0), false); + + unchecked { + _balances[from] -= 1; + } + } + + if (to != address(0)) { + unchecked { + _balances[to] += 1; + } + } + + _owners[tokenId] = to; + + emit Transfer(from, to, tokenId); + + return from; + } + + /** + * @dev Mints `tokenId` and transfers it to `to`. + * + * WARNING: Usage of this method is discouraged, use {_safeMint} whenever possible + * + * Requirements: + * + * - `tokenId` must not exist. + * - `to` cannot be the zero address. + * + * Emits a {Transfer} event. + */ + function _mint(address to, uint256 tokenId) internal { + if (to == address(0)) { + revert ERC721InvalidReceiver(address(0)); + } + address previousOwner = _update(to, tokenId, address(0)); + if (previousOwner != address(0)) { + revert ERC721InvalidSender(address(0)); + } + } + + /** + * @dev Mints `tokenId`, transfers it to `to` and checks for `to` acceptance. + * + * Requirements: + * + * - `tokenId` must not exist. + * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. + * + * Emits a {Transfer} event. + */ + function _safeMint(address to, uint256 tokenId) internal { + _safeMint(to, tokenId, ""); + } + + /** + * @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 { + _mint(to, tokenId); + _checkOnERC721Received(address(0), to, tokenId, data); + } + + /** + * @dev Destroys `tokenId`. + * The approval is cleared when the token is burned. + * This is an internal function that does not check if the sender is authorized to operate on the token. + * + * Requirements: + * + * - `tokenId` must exist. + * + * Emits a {Transfer} event. + */ + function _burn(uint256 tokenId) internal { + address previousOwner = _update(address(0), tokenId, address(0)); + if (previousOwner == address(0)) { + revert ERC721NonexistentToken(tokenId); + } + } + + /** + * @dev Transfers `tokenId` from `from` to `to`. + * As opposed to {transferFrom}, this imposes no restrictions on msg.sender. + * + * Requirements: + * + * - `to` cannot be the zero address. + * - `tokenId` token must be owned by `from`. + * + * Emits a {Transfer} event. + */ + function _transfer(address from, address to, uint256 tokenId) internal { + if (to == address(0)) { + revert ERC721InvalidReceiver(address(0)); + } + address previousOwner = _update(to, tokenId, address(0)); + if (previousOwner == address(0)) { + revert ERC721NonexistentToken(tokenId); + } else if (previousOwner != from) { + revert ERC721IncorrectOwner(from, tokenId, previousOwner); + } + } + + /** + * @dev Safely transfers `tokenId` token from `from` to `to`, checking that contract recipients + * are aware of the ERC721 standard to prevent tokens from being forever locked. + * + * `data` is additional data, it has no specified format and it is sent in call to `to`. + * + * This internal function is like {safeTransferFrom} in the sense that it invokes + * {IERC721Receiver-onERC721Received} on the receiver, and can be used to e.g. + * implement alternative mechanisms to perform token transfer, such as signature-based. + * + * Requirements: + * + * - `tokenId` token must exist and be owned by `from`. + * - `to` cannot be the zero address. + * - `from` cannot be the zero address. + * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. + * + * Emits a {Transfer} event. + */ + function _safeTransfer(address from, address to, uint256 tokenId) internal { + _safeTransfer(from, to, tokenId, ""); + } + + /** + * @dev Same as {xref-ERC721-_safeTransfer-address-address-uint256-}[`_safeTransfer`], with an additional `data` parameter which is + * forwarded in {IERC721Receiver-onERC721Received} to contract recipients. + */ + function _safeTransfer(address from, address to, uint256 tokenId, bytes memory data) internal virtual { + _transfer(from, to, tokenId); + _checkOnERC721Received(from, to, tokenId, data); + } + + /** + * @dev Approve `to` to operate on `tokenId` + * + * The `auth` argument is optional. If the value passed is non 0, then this function will check that `auth` is + * either the owner of the token, or approved to operate on all tokens held by this owner. + * + * Emits an {Approval} event. + * + * Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument. + */ + function _approve(address to, uint256 tokenId, address auth) internal { + _approve(to, tokenId, auth, true); + } + + /** + * @dev Variant of `_approve` with an optional flag to enable or disable the {Approval} event. The event is not + * emitted in the context of transfers. + */ + function _approve(address to, uint256 tokenId, address auth, bool emitEvent) internal virtual { + // Avoid reading the owner unless necessary + if (emitEvent || auth != address(0)) { + address owner = _requireOwned(tokenId); + + // We do not use _isAuthorized because single-token approvals should not be able to call approve + if (auth != address(0) && owner != auth && !isApprovedForAll(owner, auth)) { + revert ERC721InvalidApprover(auth); + } + + if (emitEvent) { + emit Approval(owner, to, tokenId); + } + } + + _tokenApprovals[tokenId] = to; + } + + /** + * @dev Approve `operator` to operate on all of `owner` tokens + * + * Requirements: + * - operator can't be the address zero. + * + * Emits an {ApprovalForAll} event. + */ + function _setApprovalForAll(address owner, address operator, bool approved) internal virtual { + if (operator == address(0)) { + revert ERC721InvalidOperator(operator); + } + _operatorApprovals[owner][operator] = approved; + emit ApprovalForAll(owner, operator, approved); + } + + /** + * @dev Reverts if the `tokenId` doesn't have a current owner (it hasn't been minted, or it has been burned). + * Returns the owner. + * + * Overrides to ownership logic should be done to {_ownerOf}. + */ + function _requireOwned(uint256 tokenId) internal view returns (address) { + address owner = _ownerOf(tokenId); + if (owner == address(0)) { + revert ERC721NonexistentToken(tokenId); + } + return owner; + } + + /** + * @dev Private function to invoke {IERC721Receiver-onERC721Received} on a target address. This will revert if the + * recipient doesn't accept the token transfer. 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 + * @param to target address that will receive the tokens + * @param tokenId uint256 ID of the token to be transferred + * @param data bytes optional data to send along with the call + */ + function _checkOnERC721Received(address from, address to, uint256 tokenId, bytes memory data) private { + if (to.code.length > 0) { + try IERC721Receiver(to).onERC721Received(_msgSender(), from, tokenId, data) returns (bytes4 retval) { + if (retval != IERC721Receiver.onERC721Received.selector) { + revert ERC721InvalidReceiver(to); + } + } catch (bytes memory reason) { + if (reason.length == 0) { + revert ERC721InvalidReceiver(to); + } else { + /// @solidity memory-safe-assembly + assembly { + revert(add(32, reason), mload(reason)) + } + } + } + } + } +} diff --git a/contracts/token/ERC721/IERC721.sol b/contracts/token/ERC721/IERC721.sol new file mode 100644 index 000000000..98a4213f9 --- /dev/null +++ b/contracts/token/ERC721/IERC721.sol @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (token/ERC721/IERC721.sol) + +pragma solidity ^0.8.20; + +import {IERC165} from "../../utils/introspection/IERC165.sol"; + +/** + * @dev Required interface of an ERC721 compliant contract. + */ +interface IERC721 is IERC165 { + /** + * @dev Emitted when `tokenId` token is transferred from `from` to `to`. + */ + event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); + + /** + * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token. + */ + event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); + + /** + * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets. + */ + event ApprovalForAll(address indexed owner, address indexed operator, bool approved); + + /** + * @dev Returns the number of tokens in ``owner``'s account. + */ + function balanceOf(address owner) external view returns (uint256 balance); + + /** + * @dev Returns the owner of the `tokenId` token. + * + * Requirements: + * + * - `tokenId` must exist. + */ + function ownerOf(uint256 tokenId) external view returns (address owner); + + /** + * @dev Safely transfers `tokenId` token from `from` to `to`. + * + * Requirements: + * + * - `from` cannot be the zero address. + * - `to` cannot be the zero address. + * - `tokenId` token must exist and be owned by `from`. + * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}. + * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon + * a safe transfer. + * + * Emits a {Transfer} event. + */ + 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 + * are aware of the ERC721 protocol to prevent tokens from being forever locked. + * + * Requirements: + * + * - `from` cannot be the zero address. + * - `to` cannot be the zero address. + * - `tokenId` token must exist and be owned by `from`. + * - If the caller is not `from`, it must have been allowed to move this token by either {approve} or + * {setApprovalForAll}. + * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon + * a safe transfer. + * + * Emits a {Transfer} event. + */ + function safeTransferFrom(address from, address to, uint256 tokenId) external; + + /** + * @dev Transfers `tokenId` token from `from` to `to`. + * + * WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC721 + * or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must + * understand this adds an external call which potentially creates a reentrancy vulnerability. + * + * Requirements: + * + * - `from` cannot be the zero address. + * - `to` cannot be the zero address. + * - `tokenId` token must be owned by `from`. + * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}. + * + * Emits a {Transfer} event. + */ + function transferFrom(address from, address to, uint256 tokenId) external; + + /** + * @dev Gives permission to `to` to transfer `tokenId` token to another account. + * The approval is cleared when the token is transferred. + * + * Only a single account can be approved at a time, so approving the zero address clears previous approvals. + * + * Requirements: + * + * - The caller must own the token or be an approved operator. + * - `tokenId` must exist. + * + * Emits an {Approval} event. + */ + function approve(address to, uint256 tokenId) external; + + /** + * @dev Approve or remove `operator` as an operator for the caller. + * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller. + * + * Requirements: + * + * - The `operator` cannot be the address zero. + * + * Emits an {ApprovalForAll} event. + */ + function setApprovalForAll(address operator, bool approved) external; + + /** + * @dev Returns the account approved for `tokenId` token. + * + * Requirements: + * + * - `tokenId` must exist. + */ + function getApproved(uint256 tokenId) external view returns (address operator); + + /** + * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`. + * + * See {setApprovalForAll} + */ + function isApprovedForAll(address owner, address operator) external view returns (bool); +} diff --git a/contracts/token/ERC721/IERC721Receiver.sol b/contracts/token/ERC721/IERC721Receiver.sol new file mode 100644 index 000000000..4d25a01d1 --- /dev/null +++ b/contracts/token/ERC721/IERC721Receiver.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (token/ERC721/IERC721Receiver.sol) + +pragma solidity ^0.8.20; + +/** + * @title ERC721 token receiver interface + * @dev Interface for any contract that wants to support safeTransfers + * from ERC721 asset contracts. + */ +interface IERC721Receiver { + /** + * @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom} + * by `operator` from `from`, this function is called. + * + * It must return its Solidity selector to confirm the token transfer. + * If any other value is returned or the interface is not implemented by the recipient, the transfer will be + * reverted. + * + * The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`. + */ + function onERC721Received( + address operator, + address from, + uint256 tokenId, + bytes calldata data + ) external returns (bytes4); +} diff --git a/contracts/token/ERC721/README.adoc b/contracts/token/ERC721/README.adoc new file mode 100644 index 000000000..40ae919d9 --- /dev/null +++ b/contracts/token/ERC721/README.adoc @@ -0,0 +1,67 @@ += ERC 721 + +[.readme-notice] +NOTE: This document is better viewed at https://docs.openzeppelin.com/contracts/api/token/erc721 + +This set of interfaces, contracts, and utilities are all related to the https://eips.ethereum.org/EIPS/eip-721[ERC721 Non-Fungible Token Standard]. + +TIP: For a walk through on how to create an ERC721 token read our xref:ROOT:erc721.adoc[ERC721 guide]. + +The EIP specifies four interfaces: + +* {IERC721}: Core functionality required in all compliant implementation. +* {IERC721Metadata}: Optional extension that adds name, symbol, and token URI, almost always included. +* {IERC721Enumerable}: Optional extension that allows enumerating the tokens on chain, often not included since it requires large gas overhead. +* {IERC721Receiver}: An interface that must be implemented by contracts if they want to accept tokens through `safeTransferFrom`. + +OpenZeppelin Contracts provides implementations of all four interfaces: + +* {ERC721}: The core and metadata extensions, with a base URI mechanism. +* {ERC721Enumerable}: The enumerable extension. +* {ERC721Holder}: A bare bones implementation of the receiver interface. + +Additionally there are a few of other extensions: + +* {ERC721Consecutive}: An implementation of https://eips.ethereum.org/EIPS/eip-2309[ERC2309] for minting batchs of tokens during construction, in accordance with ERC721. +* {ERC721URIStorage}: A more flexible but more expensive way of storing metadata. +* {ERC721Votes}: Support for voting and vote delegation. +* {ERC721Royalty}: A way to signal royalty information following ERC2981. +* {ERC721Pausable}: A primitive to pause contract operation. +* {ERC721Burnable}: A way for token holders to burn their own tokens. +* {ERC721Wrapper}: Wrapper to create an ERC721 backed by another ERC721, with deposit and withdraw methods. Useful in conjunction with {ERC721Votes}. + +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 + +{{IERC721}} + +{{IERC721Metadata}} + +{{IERC721Enumerable}} + +{{ERC721}} + +{{ERC721Enumerable}} + +{{IERC721Receiver}} + +== Extensions + +{{ERC721Pausable}} + +{{ERC721Burnable}} + +{{ERC721Consecutive}} + +{{ERC721URIStorage}} + +{{ERC721Votes}} + +{{ERC721Royalty}} + +{{ERC721Wrapper}} + +== Utilities + +{{ERC721Holder}} diff --git a/contracts/token/ERC721/extensions/ERC721Burnable.sol b/contracts/token/ERC721/extensions/ERC721Burnable.sol new file mode 100644 index 000000000..639661e66 --- /dev/null +++ b/contracts/token/ERC721/extensions/ERC721Burnable.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (token/ERC721/extensions/ERC721Burnable.sol) + +pragma solidity ^0.8.20; + +import {ERC721} from "../ERC721.sol"; +import {Context} from "../../../utils/Context.sol"; + +/** + * @title ERC721 Burnable Token + * @dev ERC721 Token that can be burned (destroyed). + */ +abstract contract ERC721Burnable is Context, ERC721 { + /** + * @dev Burns `tokenId`. See {ERC721-_burn}. + * + * Requirements: + * + * - The caller must own `tokenId` or be an approved operator. + */ + function burn(uint256 tokenId) public virtual { + // Setting an "auth" arguments enables the `_isAuthorized` check which verifies that the token exists + // (from != 0). Therefore, it is not needed to verify that the return value is not 0 here. + _update(address(0), tokenId, _msgSender()); + } +} diff --git a/contracts/token/ERC721/extensions/ERC721Consecutive.sol b/contracts/token/ERC721/extensions/ERC721Consecutive.sol new file mode 100644 index 000000000..8e670d573 --- /dev/null +++ b/contracts/token/ERC721/extensions/ERC721Consecutive.sol @@ -0,0 +1,176 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (token/ERC721/extensions/ERC721Consecutive.sol) + +pragma solidity ^0.8.20; + +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 + * https://eips.ethereum.org/EIPS/eip-2309[EIP-2309]. + * + * This extension allows the minting of large batches of tokens, during contract construction only. For upgradeable + * contracts this implies that batch minting is only available during proxy deployment, and not in subsequent upgrades. + * These batches are limited to 5000 tokens at a time by default to accommodate off-chain indexers. + * + * Using this extension removes the ability to mint single tokens during contract construction. This ability is + * regained after construction. During construction, only batch minting is allowed. + * + * IMPORTANT: This extension does not call the {_update} function for tokens minted in batch. Any logic added to this + * function through overrides will not be triggered when token are minted in batch. You may want to also override + * {_increaseBalance} or {_mintConsecutive} to account for these mints. + * + * IMPORTANT: When overriding {_mintConsecutive}, be careful about call ordering. {ownerOf} may return invalid + * values during the {_mintConsecutive} execution if the super call is not called first. To be safe, execute the + * super call before your custom logic. + */ +abstract contract ERC721Consecutive is IERC2309, ERC721 { + using BitMaps for BitMaps.BitMap; + using Checkpoints for Checkpoints.Trace160; + + 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 + * tokens. + * + * NOTE: Overriding the default value of 5000 will not cause on-chain issues, but may result in the asset not being + * correctly supported by off-chain indexing services (including marketplaces). + */ + function _maxBatchSize() internal view virtual returns (uint96) { + return 5000; + } + + /** + * @dev See {ERC721-_ownerOf}. Override that checks the sequential ownership structure for tokens that have + * been minted as part of a batch, and not yet transferred. + */ + function _ownerOf(uint256 tokenId) internal view virtual override returns (address) { + 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 || tokenId < _firstConsecutiveId()) { + return owner; + } + + // Otherwise, check the token was not burned, and fetch ownership from the anchors + // Note: no need for safe cast, we know that tokenId <= type(uint96).max + return _sequentialBurn.get(tokenId) ? address(0) : address(_sequentialOwnership.lowerLookup(uint96(tokenId))); + } + + /** + * @dev Mint a batch of tokens of length `batchSize` for `to`. Returns the token id of the first token minted in the + * batch; if `batchSize` is 0, returns the number of consecutive ids minted so far. + * + * Requirements: + * + * - `batchSize` must not be greater than {_maxBatchSize}. + * - The function is called in the constructor of the contract (directly or indirectly). + * + * CAUTION: Does not emit a `Transfer` event. This is ERC721 compliant as long as it is done inside of the + * constructor, which is enforced by this function. + * + * CAUTION: Does not invoke `onERC721Received` on the receiver. + * + * Emits a {IERC2309-ConsecutiveTransfer} event. + */ + function _mintConsecutive(address to, uint96 batchSize) internal virtual returns (uint96) { + uint96 next = _nextConsecutiveId(); + + // minting a batch of size 0 is a no-op + if (batchSize > 0) { + 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); + } + + // push an ownership checkpoint & emit event + 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`. + _increaseBalance(to, batchSize); + + emit ConsecutiveTransfer(next, last, address(0), to); + } + + return next; + } + + /** + * @dev See {ERC721-_update}. Override version that restricts normal minting to after construction. + * + * WARNING: Using {ERC721Consecutive} prevents minting during construction in favor of {_mintConsecutive}. + * After construction, {_mintConsecutive} is no longer available and minting through {_update} becomes available. + */ + function _update(address to, uint256 tokenId, address auth) internal virtual override returns (address) { + address previousOwner = super._update(to, tokenId, auth); + + // only mint after construction + if (previousOwner == address(0) && address(this).code.length == 0) { + revert ERC721ForbiddenMint(); + } + + // record burn + if ( + to == address(0) && // if we burn + tokenId < _nextConsecutiveId() && // and the tokenId was minted in a batch + !_sequentialBurn.get(tokenId) // and the token was never marked as burnt + ) { + _sequentialBurn.set(tokenId); + } + + return previousOwner; + } + + /** + * @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 : _firstConsecutiveId(); + } +} diff --git a/contracts/token/ERC721/extensions/ERC721Enumerable.sol b/contracts/token/ERC721/extensions/ERC721Enumerable.sol new file mode 100644 index 000000000..803a937f5 --- /dev/null +++ b/contracts/token/ERC721/extensions/ERC721Enumerable.sol @@ -0,0 +1,172 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (token/ERC721/extensions/ERC721Enumerable.sol) + +pragma solidity ^0.8.20; + +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 + * 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(address owner => mapping(uint256 index => uint256)) private _ownedTokens; + mapping(uint256 tokenId => uint256) private _ownedTokensIndex; + + uint256[] private _allTokens; + mapping(uint256 tokenId => 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}. + */ + function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC721) returns (bool) { + return interfaceId == type(IERC721Enumerable).interfaceId || super.supportsInterface(interfaceId); + } + + /** + * @dev See {IERC721Enumerable-tokenOfOwnerByIndex}. + */ + function tokenOfOwnerByIndex(address owner, uint256 index) public view virtual returns (uint256) { + if (index >= balanceOf(owner)) { + revert ERC721OutOfBoundsIndex(owner, index); + } + return _ownedTokens[owner][index]; + } + + /** + * @dev See {IERC721Enumerable-totalSupply}. + */ + function totalSupply() public view virtual returns (uint256) { + return _allTokens.length; + } + + /** + * @dev See {IERC721Enumerable-tokenByIndex}. + */ + function tokenByIndex(uint256 index) public view virtual returns (uint256) { + if (index >= totalSupply()) { + revert ERC721OutOfBoundsIndex(address(0), index); + } + return _allTokens[index]; + } + + /** + * @dev See {ERC721-_update}. + */ + function _update(address to, uint256 tokenId, address auth) internal virtual override returns (address) { + address previousOwner = super._update(to, tokenId, auth); + + if (previousOwner == address(0)) { + _addTokenToAllTokensEnumeration(tokenId); + } else if (previousOwner != to) { + _removeTokenFromOwnerEnumeration(previousOwner, tokenId); + } + if (to == address(0)) { + _removeTokenFromAllTokensEnumeration(tokenId); + } else if (previousOwner != to) { + _addTokenToOwnerEnumeration(to, tokenId); + } + + return previousOwner; + } + + /** + * @dev Private function to add a token to this extension's ownership-tracking data structures. + * @param to address representing the new owner of the given token ID + * @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 = balanceOf(to) - 1; + _ownedTokens[to][length] = tokenId; + _ownedTokensIndex[tokenId] = length; + } + + /** + * @dev Private function to add a token to this extension's token tracking data structures. + * @param tokenId uint256 ID of the token to be added to the tokens list + */ + function _addTokenToAllTokensEnumeration(uint256 tokenId) private { + _allTokensIndex[tokenId] = _allTokens.length; + _allTokens.push(tokenId); + } + + /** + * @dev Private function to remove a token from this extension's ownership-tracking data structures. Note that + * while the token is not assigned a new owner, the `_ownedTokensIndex` mapping is _not_ updated: this allows for + * gas optimizations e.g. when performing a transfer operation (avoiding double writes). + * This has O(1) time complexity, but alters the order of the _ownedTokens array. + * @param from address representing the previous owner of the given token ID + * @param tokenId uint256 ID of the token to be removed from the tokens list of the given address + */ + function _removeTokenFromOwnerEnumeration(address from, uint256 tokenId) private { + // 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 = balanceOf(from); + uint256 tokenIndex = _ownedTokensIndex[tokenId]; + + // When the token to delete is the last token, the swap operation is unnecessary + if (tokenIndex != lastTokenIndex) { + uint256 lastTokenId = _ownedTokens[from][lastTokenIndex]; + + _ownedTokens[from][tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token + _ownedTokensIndex[lastTokenId] = tokenIndex; // Update the moved token's index + } + + // This also deletes the contents at the last position of the array + delete _ownedTokensIndex[tokenId]; + delete _ownedTokens[from][lastTokenIndex]; + } + + /** + * @dev Private function to remove a token from this extension's token tracking data structures. + * This has O(1) time complexity, but alters the order of the _allTokens array. + * @param tokenId uint256 ID of the token to be removed from the tokens list + */ + function _removeTokenFromAllTokensEnumeration(uint256 tokenId) private { + // To prevent a gap in the 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 = _allTokens.length - 1; + uint256 tokenIndex = _allTokensIndex[tokenId]; + + // When the token to delete is the last token, the swap operation is unnecessary. However, since this occurs so + // rarely (when the last minted token is burnt) that we still do the swap here to avoid the gas cost of adding + // an 'if' statement (like in _removeTokenFromOwnerEnumeration) + uint256 lastTokenId = _allTokens[lastTokenIndex]; + + _allTokens[tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token + _allTokensIndex[lastTokenId] = tokenIndex; // Update the moved token's index + + // This also deletes the contents at the last position of the array + delete _allTokensIndex[tokenId]; + _allTokens.pop(); + } + + /** + * See {ERC721-_increaseBalance}. We need that to account tokens that were minted in batch + */ + function _increaseBalance(address account, uint128 amount) internal virtual override { + if (amount > 0) { + revert ERC721EnumerableForbiddenBatchMint(); + } + super._increaseBalance(account, amount); + } +} diff --git a/contracts/token/ERC721/extensions/ERC721Pausable.sol b/contracts/token/ERC721/extensions/ERC721Pausable.sol new file mode 100644 index 000000000..cc3d46bfb --- /dev/null +++ b/contracts/token/ERC721/extensions/ERC721Pausable.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (token/ERC721/extensions/ERC721Pausable.sol) + +pragma solidity ^0.8.20; + +import {ERC721} from "../ERC721.sol"; +import {Pausable} from "../../../utils/Pausable.sol"; + +/** + * @dev ERC721 token with pausable token transfers, minting and burning. + * + * Useful for scenarios such as preventing trades until the end of an evaluation + * period, or having an emergency switch for freezing all token transfers in the + * event of a large bug. + * + * IMPORTANT: This contract does not include public pause and unpause functions. In + * 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 pause mechanism of the contract unreachable, and thus unusable. + */ +abstract contract ERC721Pausable is ERC721, Pausable { + /** + * @dev See {ERC721-_update}. + * + * Requirements: + * + * - the contract must not be paused. + */ + function _update( + address to, + uint256 tokenId, + address auth + ) internal virtual override whenNotPaused returns (address) { + return super._update(to, tokenId, auth); + } +} diff --git a/contracts/token/ERC721/extensions/ERC721Royalty.sol b/contracts/token/ERC721/extensions/ERC721Royalty.sol new file mode 100644 index 000000000..e76a5123d --- /dev/null +++ b/contracts/token/ERC721/extensions/ERC721Royalty.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (token/ERC721/extensions/ERC721Royalty.sol) + +pragma solidity ^0.8.20; + +import {ERC721} from "../ERC721.sol"; +import {ERC2981} from "../../common/ERC2981.sol"; + +/** + * @dev Extension of ERC721 with the ERC2981 NFT Royalty Standard, a standardized way to retrieve royalty payment + * information. + * + * Royalty information can be specified globally for all token ids via {ERC2981-_setDefaultRoyalty}, and/or individually + * for specific token ids via {ERC2981-_setTokenRoyalty}. The latter takes precedence over the first. + * + * 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. + */ +abstract contract ERC721Royalty is ERC2981, ERC721 { + /** + * @dev See {IERC165-supportsInterface}. + */ + function supportsInterface(bytes4 interfaceId) public view virtual override(ERC721, ERC2981) returns (bool) { + return super.supportsInterface(interfaceId); + } +} diff --git a/contracts/token/ERC721/extensions/ERC721URIStorage.sol b/contracts/token/ERC721/extensions/ERC721URIStorage.sol new file mode 100644 index 000000000..d9948ea33 --- /dev/null +++ b/contracts/token/ERC721/extensions/ERC721URIStorage.sol @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (token/ERC721/extensions/ERC721URIStorage.sol) + +pragma solidity ^0.8.20; + +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. + */ +abstract contract ERC721URIStorage is IERC4906, ERC721 { + using Strings for uint256; + + // Interface ID as defined in ERC-4906. This does not correspond to a traditional interface ID as ERC-4906 only + // defines events and does not include any external function. + bytes4 private constant ERC4906_INTERFACE_ID = bytes4(0x49064906); + + // Optional mapping for token URIs + mapping(uint256 tokenId => string) private _tokenURIs; + + /** + * @dev See {IERC165-supportsInterface} + */ + function supportsInterface(bytes4 interfaceId) public view virtual override(ERC721, IERC165) returns (bool) { + return interfaceId == ERC4906_INTERFACE_ID || super.supportsInterface(interfaceId); + } + + /** + * @dev See {IERC721Metadata-tokenURI}. + */ + function tokenURI(uint256 tokenId) public view virtual override returns (string memory) { + _requireOwned(tokenId); + + string memory _tokenURI = _tokenURIs[tokenId]; + string memory base = _baseURI(); + + // If there is no base URI, return the token URI. + if (bytes(base).length == 0) { + return _tokenURI; + } + // If both are set, concatenate the baseURI and tokenURI (via string.concat). + if (bytes(_tokenURI).length > 0) { + return string.concat(base, _tokenURI); + } + + return super.tokenURI(tokenId); + } + + /** + * @dev Sets `_tokenURI` as the tokenURI of `tokenId`. + * + * Emits {MetadataUpdate}. + */ + function _setTokenURI(uint256 tokenId, string memory _tokenURI) internal virtual { + _tokenURIs[tokenId] = _tokenURI; + emit MetadataUpdate(tokenId); + } +} diff --git a/contracts/token/ERC721/extensions/ERC721Votes.sol b/contracts/token/ERC721/extensions/ERC721Votes.sol new file mode 100644 index 000000000..d87fb8fe3 --- /dev/null +++ b/contracts/token/ERC721/extensions/ERC721Votes.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (token/ERC721/extensions/ERC721Votes.sol) + +pragma solidity ^0.8.20; + +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 + * as 1 vote unit. + * + * 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. + */ +abstract contract ERC721Votes is ERC721, Votes { + /** + * @dev See {ERC721-_update}. Adjusts votes when tokens are transferred. + * + * Emits a {IVotes-DelegateVotesChanged} event. + */ + function _update(address to, uint256 tokenId, address auth) internal virtual override returns (address) { + address previousOwner = super._update(to, tokenId, auth); + + _transferVotingUnits(previousOwner, to, 1); + + return previousOwner; + } + + /** + * @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); + } + + /** + * @dev See {ERC721-_increaseBalance}. We need that to account tokens that were minted in batch. + */ + function _increaseBalance(address account, uint128 amount) internal virtual override { + super._increaseBalance(account, amount); + _transferVotingUnits(address(0), account, amount); + } +} diff --git a/contracts/token/ERC721/extensions/ERC721Wrapper.sol b/contracts/token/ERC721/extensions/ERC721Wrapper.sol new file mode 100644 index 000000000..8da6da6b6 --- /dev/null +++ b/contracts/token/ERC721/extensions/ERC721Wrapper.sol @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (token/ERC721/extensions/ERC721Wrapper.sol) + +pragma solidity ^0.8.20; + +import {IERC721, ERC721} from "../ERC721.sol"; +import {IERC721Receiver} from "../IERC721Receiver.sol"; + +/** + * @dev Extension of the ERC721 token contract to support token wrapping. + * + * 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. + */ +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; + } + + /** + * @dev Allow a user to deposit underlying tokens and mint the corresponding tokenIds. + */ + function depositFor(address account, uint256[] memory tokenIds) public virtual returns (bool) { + uint256 length = tokenIds.length; + for (uint256 i = 0; i < length; ++i) { + uint256 tokenId = tokenIds[i]; + + // This is an "unsafe" transfer that doesn't call any hook on the receiver. With underlying() being trusted + // (by design of this contract) and no other contracts expected to be called from there, we are safe. + // slither-disable-next-line reentrancy-no-eth + underlying().transferFrom(_msgSender(), address(this), tokenId); + _safeMint(account, tokenId); + } + + return true; + } + + /** + * @dev Allow a user to burn wrapped tokens and withdraw the corresponding tokenIds of the underlying tokens. + */ + function withdrawTo(address account, uint256[] memory tokenIds) public virtual returns (bool) { + uint256 length = tokenIds.length; + for (uint256 i = 0; i < length; ++i) { + uint256 tokenId = tokenIds[i]; + // Setting an "auth" arguments enables the `_isAuthorized` check which verifies that the token exists + // (from != 0). Therefore, it is not needed to verify that the return value is not 0 here. + _update(address(0), tokenId, _msgSender()); + // 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. + // slither-disable-next-line reentrancy-no-eth + underlying().safeTransferFrom(address(this), account, tokenId); + } + + return true; + } + + /** + * @dev Overrides {IERC721Receiver-onERC721Received} to allow minting on direct ERC721 transfers to + * this contract. + * + * In case there's data attached, it validates that the operator is this contract, so only trusted data + * is accepted from {depositFor}. + * + * 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 returns (bytes4) { + if (address(underlying()) != _msgSender()) { + revert ERC721UnsupportedToken(_msgSender()); + } + _safeMint(from, tokenId); + return IERC721Receiver.onERC721Received.selector; + } + + /** + * @dev Mint a wrapped token to cover any underlyingToken that would have been transferred by mistake. Internal + * function that can be exposed with access control if desired. + */ + function _recover(address account, uint256 tokenId) internal virtual returns (uint256) { + address owner = underlying().ownerOf(tokenId); + if (owner != address(this)) { + revert ERC721IncorrectOwner(address(this), tokenId, owner); + } + _safeMint(account, tokenId); + return tokenId; + } + + /** + * @dev Returns the underlying token. + */ + function underlying() public view virtual returns (IERC721) { + return _underlying; + } +} diff --git a/contracts/token/ERC721/extensions/IERC721Enumerable.sol b/contracts/token/ERC721/extensions/IERC721Enumerable.sol new file mode 100644 index 000000000..553a9364b --- /dev/null +++ b/contracts/token/ERC721/extensions/IERC721Enumerable.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (token/ERC721/extensions/IERC721Enumerable.sol) + +pragma solidity ^0.8.20; + +import {IERC721} from "../IERC721.sol"; + +/** + * @title ERC-721 Non-Fungible Token Standard, optional enumeration extension + * @dev See https://eips.ethereum.org/EIPS/eip-721 + */ +interface IERC721Enumerable is IERC721 { + /** + * @dev Returns the total amount of tokens stored by the contract. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns a token ID owned by `owner` at a given `index` of its token list. + * Use along with {balanceOf} to enumerate all of ``owner``'s tokens. + */ + function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256); + + /** + * @dev Returns a token ID at a given `index` of all the tokens stored by the contract. + * Use along with {totalSupply} to enumerate all tokens. + */ + function tokenByIndex(uint256 index) external view returns (uint256); +} diff --git a/contracts/token/ERC721/extensions/IERC721Metadata.sol b/contracts/token/ERC721/extensions/IERC721Metadata.sol new file mode 100644 index 000000000..a2d76fd69 --- /dev/null +++ b/contracts/token/ERC721/extensions/IERC721Metadata.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (token/ERC721/extensions/IERC721Metadata.sol) + +pragma solidity ^0.8.20; + +import {IERC721} from "../IERC721.sol"; + +/** + * @title ERC-721 Non-Fungible Token Standard, optional metadata extension + * @dev See https://eips.ethereum.org/EIPS/eip-721 + */ +interface IERC721Metadata is IERC721 { + /** + * @dev Returns the token collection name. + */ + function name() external view returns (string memory); + + /** + * @dev Returns the token collection symbol. + */ + function symbol() external view returns (string memory); + + /** + * @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token. + */ + function tokenURI(uint256 tokenId) external view returns (string memory); +} diff --git a/contracts/token/ERC721/utils/ERC721Holder.sol b/contracts/token/ERC721/utils/ERC721Holder.sol new file mode 100644 index 000000000..3943774d3 --- /dev/null +++ b/contracts/token/ERC721/utils/ERC721Holder.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (token/ERC721/utils/ERC721Holder.sol) + +pragma solidity ^0.8.20; + +import {IERC721Receiver} from "../IERC721Receiver.sol"; + +/** + * @dev Implementation of the {IERC721Receiver} interface. + * + * Accepts all token transfers. + * Make sure the contract is able to use its token with {IERC721-safeTransferFrom}, {IERC721-approve} or + * {IERC721-setApprovalForAll}. + */ +abstract contract ERC721Holder is IERC721Receiver { + /** + * @dev See {IERC721Receiver-onERC721Received}. + * + * Always returns `IERC721Receiver.onERC721Received.selector`. + */ + 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 new file mode 100644 index 000000000..a885e21b5 --- /dev/null +++ b/contracts/token/common/ERC2981.sol @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (token/common/ERC2981.sol) + +pragma solidity ^0.8.20; + +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. + * + * Royalty information can be specified globally for all token ids via {_setDefaultRoyalty}, and/or individually for + * specific token ids via {_setTokenRoyalty}. The latter takes precedence over the first. + * + * Royalty is specified as a fraction of sale price. {_feeDenominator} is overridable but defaults to 10000, meaning the + * fee is specified in basis points by default. + * + * 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. + */ +abstract contract ERC2981 is IERC2981, ERC165 { + struct RoyaltyInfo { + address receiver; + uint96 royaltyFraction; + } + + RoyaltyInfo private _defaultRoyaltyInfo; + mapping(uint256 tokenId => 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}. + */ + function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC165) returns (bool) { + return interfaceId == type(IERC2981).interfaceId || super.supportsInterface(interfaceId); + } + + /** + * @inheritdoc IERC2981 + */ + function royaltyInfo(uint256 tokenId, uint256 salePrice) public view virtual returns (address, uint256) { + RoyaltyInfo memory royalty = _tokenRoyaltyInfo[tokenId]; + + if (royalty.receiver == address(0)) { + royalty = _defaultRoyaltyInfo; + } + + uint256 royaltyAmount = (salePrice * royalty.royaltyFraction) / _feeDenominator(); + + return (royalty.receiver, royaltyAmount); + } + + /** + * @dev The denominator with which to interpret the fee set in {_setTokenRoyalty} and {_setDefaultRoyalty} as a + * fraction of the sale price. Defaults to 10000 so fees are expressed in basis points, but may be customized by an + * override. + */ + function _feeDenominator() internal pure virtual returns (uint96) { + return 10000; + } + + /** + * @dev Sets the royalty information that all ids in this contract will default to. + * + * Requirements: + * + * - `receiver` cannot be the zero address. + * - `feeNumerator` cannot be greater than the fee denominator. + */ + function _setDefaultRoyalty(address receiver, uint96 feeNumerator) internal virtual { + 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); + } + + /** + * @dev Removes default royalty information. + */ + function _deleteDefaultRoyalty() internal virtual { + delete _defaultRoyaltyInfo; + } + + /** + * @dev Sets the royalty information for a specific token id, overriding the global default. + * + * Requirements: + * + * - `receiver` cannot be the zero address. + * - `feeNumerator` cannot be greater than the fee denominator. + */ + function _setTokenRoyalty(uint256 tokenId, address receiver, uint96 feeNumerator) internal virtual { + 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); + } + + /** + * @dev Resets royalty information for the token id back to the global default. + */ + function _resetTokenRoyalty(uint256 tokenId) internal virtual { + delete _tokenRoyaltyInfo[tokenId]; + } +} diff --git a/contracts/token/common/README.adoc b/contracts/token/common/README.adoc new file mode 100644 index 000000000..af6167464 --- /dev/null +++ b/contracts/token/common/README.adoc @@ -0,0 +1,10 @@ += Common (Tokens) + +Functionality that is common to multiple token standards. + +* {ERC2981}: NFT Royalties compatible with both ERC721 and ERC1155. +** For ERC721 consider {ERC721Royalty} which clears the royalty information from storage on burn. + +== Contracts + +{{ERC2981}} diff --git a/contracts/utils/Address.sol b/contracts/utils/Address.sol new file mode 100644 index 000000000..f8c157a9b --- /dev/null +++ b/contracts/utils/Address.sol @@ -0,0 +1,159 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (utils/Address.sol) + +pragma solidity ^0.8.20; + +/** + * @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. + * + * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost + * of certain opcodes, possibly making contracts go over the 2300 gas limit + * imposed by `transfer`, making them unable to receive funds via + * `transfer`. {sendValue} removes this limitation. + * + * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more]. + * + * 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.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) { + revert AddressInsufficientBalance(address(this)); + } + + (bool success, ) = recipient.call{value: amount}(""); + if (!success) { + revert FailedInnerCall(); + } + } + + /** + * @dev Performs a Solidity function call using a low level `call`. A + * plain `call` is an unsafe replacement for a function call: use this + * function instead. + * + * 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`]. + * + * Requirements: + * + * - `target` must be a contract. + * - calling `target` with `data` must not revert. + */ + function functionCall(address target, bytes memory data) internal returns (bytes memory) { + return functionCallWithValue(target, data, 0); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but also transferring `value` wei to `target`. + * + * Requirements: + * + * - the calling contract must have an ETH balance of at least `value`. + * - the called Solidity function must be `payable`. + */ + function functionCallWithValue(address target, bytes memory data, uint256 value) 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); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but performing a static call. + */ + function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { + (bool success, bytes memory returndata) = target.staticcall(data); + return verifyCallResultFromTarget(target, success, returndata); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but performing a delegate call. + */ + function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { + (bool success, bytes memory returndata) = target.delegatecall(data); + return verifyCallResultFromTarget(target, success, returndata); + } + + /** + * @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 + ) internal view returns (bytes memory) { + 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; + } + } + + /** + * @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 pure returns (bytes memory) { + if (!success) { + _revert(returndata); + } else { + return returndata; + } + } + + /** + * @dev Reverts with returndata if present. Otherwise reverts with {FailedInnerCall}. + */ + 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 + /// @solidity memory-safe-assembly + assembly { + let returndata_size := mload(returndata) + revert(add(32, returndata), returndata_size) + } + } else { + revert FailedInnerCall(); + } + } +} diff --git a/contracts/utils/Arrays.sol b/contracts/utils/Arrays.sol new file mode 100644 index 000000000..a34d0a7d6 --- /dev/null +++ b/contracts/utils/Arrays.sol @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (utils/Arrays.sol) + +pragma solidity ^0.8.20; + +import {StorageSlot} from "./StorageSlot.sol"; +import {Math} from "./math/Math.sol"; + +/** + * @dev Collection of functions related to array types. + */ +library Arrays { + using StorageSlot for bytes32; + + /** + * @dev Searches a sorted `array` and returns the first index that contains + * a value greater or equal to `element`. If no such index exists (i.e. all + * values in the array are strictly less than `element`), the array length is + * returned. Time complexity O(log n). + * + * `array` is expected to be sorted in ascending order, and to contain no + * repeated elements. + */ + function findUpperBound(uint256[] storage array, uint256 element) internal view returns (uint256) { + uint256 low = 0; + uint256 high = array.length; + + if (high == 0) { + return 0; + } + + while (low < high) { + 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 towards zero (it does integer division with truncation). + if (unsafeAccess(array, mid).value > element) { + high = mid; + } else { + low = mid + 1; + } + } + + // At this point `low` is the exclusive upper bound. We will return the inclusive upper bound. + if (low > 0 && unsafeAccess(array, low - 1).value == element) { + return low - 1; + } else { + return low; + } + } + + /** + * @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 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.20/internals/layout_in_storage.html#mappings-and-dynamic-arrays. + + /// @solidity memory-safe-assembly + assembly { + mstore(0, arr.slot) + slot := add(keccak256(0, 0x20), pos) + } + return slot.getAddressSlot(); + } + + /** + * @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 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.20/internals/layout_in_storage.html#mappings-and-dynamic-arrays. + + /// @solidity memory-safe-assembly + assembly { + mstore(0, arr.slot) + slot := add(keccak256(0, 0x20), pos) + } + return slot.getBytes32Slot(); + } + + /** + * @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 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.20/internals/layout_in_storage.html#mappings-and-dynamic-arrays. + + /// @solidity memory-safe-assembly + assembly { + mstore(0, arr.slot) + slot := add(keccak256(0, 0x20), pos) + } + 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))) + } + } +} diff --git a/contracts/utils/Base64.sol b/contracts/utils/Base64.sol new file mode 100644 index 000000000..0b76284d0 --- /dev/null +++ b/contracts/utils/Base64.sol @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (utils/Base64.sol) + +pragma solidity ^0.8.20; + +/** + * @dev Provides a set of functions to operate with Base64 strings. + */ +library Base64 { + /** + * @dev Base64 Encoding/Decoding Table + */ + string internal constant _TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + /** + * @dev Converts a `bytes` to its Bytes64 `string` representation. + */ + function encode(bytes memory data) internal pure returns (string memory) { + /** + * Inspired by Brecht Devos (Brechtpd) implementation - MIT licence + * https://github.com/Brechtpd/base64/blob/e78d9fd951e7b0977ddca77d92dc85183770daf4/base64.sol + */ + if (data.length == 0) return ""; + + // Loads the table into memory + string memory table = _TABLE; + + // Encoding takes 3 bytes chunks of binary data from `bytes` data parameter + // and split into 4 numbers of 6 bits. + // The final Base64 length should be `bytes` data length multiplied by 4/3 rounded up + // - `data.length + 2` -> Round up + // - `/ 3` -> Number of 3-bytes chunks + // - `4 *` -> 4 characters for each chunk + string memory result = new string(4 * ((data.length + 2) / 3)); + + /// @solidity memory-safe-assembly + assembly { + // Prepare the lookup table (skip the first "length" byte) + let tablePtr := add(table, 1) + + // Prepare result pointer, jump over length + let resultPtr := add(result, 32) + + // Run over the input, 3 bytes at a time + for { + let dataPtr := data + let endPtr := add(data, mload(data)) + } lt(dataPtr, endPtr) { + + } { + // Advance 3 bytes + dataPtr := add(dataPtr, 3) + let input := mload(dataPtr) + + // To write each character, shift the 3 bytes (18 bits) chunk + // 4 times in blocks of 6 bits for each character (18, 12, 6, 0) + // and apply logical AND with 0x3F which is the number of + // the previous character in the ASCII table prior to the Base64 Table + // The result is then added to the table to get the character to write, + // and finally write it in the result pointer but with a left shift + // of 256 (1 byte) - 8 (1 ASCII char) = 248 bits + + mstore8(resultPtr, mload(add(tablePtr, and(shr(18, input), 0x3F)))) + resultPtr := add(resultPtr, 1) // Advance + + mstore8(resultPtr, mload(add(tablePtr, and(shr(12, input), 0x3F)))) + resultPtr := add(resultPtr, 1) // Advance + + mstore8(resultPtr, mload(add(tablePtr, and(shr(6, input), 0x3F)))) + resultPtr := add(resultPtr, 1) // Advance + + mstore8(resultPtr, mload(add(tablePtr, and(input, 0x3F)))) + resultPtr := add(resultPtr, 1) // Advance + } + + // When data `bytes` is not exactly 3 bytes long + // it is padded with `=` characters at the end + switch mod(mload(data), 3) + case 1 { + mstore8(sub(resultPtr, 1), 0x3d) + mstore8(sub(resultPtr, 2), 0x3d) + } + case 2 { + mstore8(sub(resultPtr, 1), 0x3d) + } + } + + return result; + } +} diff --git a/contracts/utils/Context.sol b/contracts/utils/Context.sol new file mode 100644 index 000000000..9c48a6b31 --- /dev/null +++ b/contracts/utils/Context.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (utils/Context.sol) + +pragma solidity ^0.8.20; + +/** + * @dev Provides information about the current execution context, including the + * sender of the transaction and its data. While these are generally available + * via msg.sender and msg.data, they should not be accessed in such a direct + * manner, since when dealing with meta-transactions the account sending and + * paying for execution may not be the actual sender (as far as an application + * is concerned). + * + * This contract is only required for intermediate, library-like contracts. + */ +abstract contract Context { + function _msgSender() internal view virtual returns (address) { + return msg.sender; + } + + function _msgData() internal view virtual returns (bytes calldata) { + return msg.data; + } +} diff --git a/contracts/utils/Create2.sol b/contracts/utils/Create2.sol new file mode 100644 index 000000000..99f2242bd --- /dev/null +++ b/contracts/utils/Create2.sol @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (utils/Create2.sol) + +pragma solidity ^0.8.20; + +/** + * @dev Helper to make usage of the `CREATE2` EVM opcode easier and safer. + * `CREATE2` can be used to compute in advance the address where a smart + * contract will be deployed, which allows for interesting new mechanisms known + * as 'counterfactual interactions'. + * + * See the https://eips.ethereum.org/EIPS/eip-1014#motivation[EIP] for more + * 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}. + * + * The bytecode for a contract can be obtained from Solidity with + * `type(contractName).creationCode`. + * + * Requirements: + * + * - `bytecode` must not be empty. + * - `salt` must have not been used for `bytecode` already. + * - 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) { + 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) + } + if (addr == address(0)) { + revert Create2FailedDeployment(); + } + } + + /** + * @dev Returns the address where a contract will be stored if deployed via {deploy}. Any change in the + * `bytecodeHash` or `salt` will result in a new destination address. + */ + function computeAddress(bytes32 salt, bytes32 bytecodeHash) internal view returns (address) { + return computeAddress(salt, bytecodeHash, address(this)); + } + + /** + * @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) { + /// @solidity memory-safe-assembly + assembly { + let ptr := mload(0x40) // Get free memory pointer + + // | | ↓ ptr ... ↓ ptr + 0x0B (start) ... ↓ ptr + 0x20 ... ↓ ptr + 0x40 ... | + // |-------------------|---------------------------------------------------------------------------| + // | bytecodeHash | CCCCCCCCCCCCC...CC | + // | salt | BBBBBBBBBBBBB...BB | + // | deployer | 000000...0000AAAAAAAAAAAAAAAAAAA...AA | + // | 0xFF | FF | + // |-------------------|---------------------------------------------------------------------------| + // | memory | 000000...00FFAAAAAAAAAAAAAAAAAAA...AABBBBBBBBBBBBB...BBCCCCCCCCCCCCC...CC | + // | keccak(start, 85) | ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ | + + mstore(add(ptr, 0x40), bytecodeHash) + mstore(add(ptr, 0x20), salt) + mstore(ptr, deployer) // Right-aligned with 12 preceding garbage bytes + let start := add(ptr, 0x0b) // The hashed data starts at the final garbage byte which we will set to 0xff + mstore8(start, 0xff) + addr := keccak256(start, 85) + } + } +} diff --git a/contracts/utils/Multicall.sol b/contracts/utils/Multicall.sol new file mode 100644 index 000000000..f6c8432d3 --- /dev/null +++ b/contracts/utils/Multicall.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (utils/Multicall.sol) + +pragma solidity ^0.8.20; + +import {Address} from "./Address.sol"; + +/** + * @dev Provides a function to batch together multiple calls in a single external call. + */ +abstract contract Multicall { + /** + * @dev Receives and executes a batch of function calls on this contract. + * @custom:oz-upgrades-unsafe-allow-reachable delegatecall + */ + function multicall(bytes[] calldata data) external virtual returns (bytes[] memory results) { + results = new bytes[](data.length); + for (uint256 i = 0; i < data.length; i++) { + results[i] = Address.functionDelegateCall(address(this), data[i]); + } + return results; + } +} diff --git a/contracts/utils/Nonces.sol b/contracts/utils/Nonces.sol new file mode 100644 index 000000000..500dcd9d4 --- /dev/null +++ b/contracts/utils/Nonces.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (utils/Nonces.sol) +pragma solidity ^0.8.20; + +/** + * @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 account => uint256) private _nonces; + + /** + * @dev Returns the next unused nonce for an address. + */ + function nonces(address owner) public view virtual returns (uint256) { + return _nonces[owner]; + } + + /** + * @dev Consumes a nonce. + * + * Returns the current value and increments nonce. + */ + 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]++; + } + } + + /** + * @dev Same as {_useNonce} but checking that `nonce` is the next valid for `owner`. + */ + function _useCheckedNonce(address owner, uint256 nonce) internal virtual { + uint256 current = _useNonce(owner); + if (nonce != current) { + revert InvalidAccountNonce(owner, current); + } + } +} diff --git a/contracts/utils/Pausable.sol b/contracts/utils/Pausable.sol new file mode 100644 index 000000000..5b3e72a6b --- /dev/null +++ b/contracts/utils/Pausable.sol @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (utils/Pausable.sol) + +pragma solidity ^0.8.20; + +import {Context} from "../utils/Context.sol"; + +/** + * @dev Contract module which allows children to implement an emergency stop + * mechanism that can be triggered by an authorized account. + * + * This module is used through inheritance. It will make available the + * modifiers `whenNotPaused` and `whenPaused`, which can be applied to + * the functions of your contract. Note that they will not be pausable by + * 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`. + */ + event Paused(address account); + + /** + * @dev Emitted when the pause is lifted by `account`. + */ + event Unpaused(address account); + + /** + * @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. + */ + constructor() { + _paused = false; + } + + /** + * @dev Modifier to make a function callable only when the contract is not paused. + * + * Requirements: + * + * - The contract must not be paused. + */ + modifier whenNotPaused() { + _requireNotPaused(); + _; + } + + /** + * @dev Modifier to make a function callable only when the contract is paused. + * + * Requirements: + * + * - The contract must be paused. + */ + modifier whenPaused() { + _requirePaused(); + _; + } + + /** + * @dev Returns true if the contract is paused, and false otherwise. + */ + function paused() public view virtual returns (bool) { + return _paused; + } + + /** + * @dev Throws if the contract is paused. + */ + function _requireNotPaused() internal view virtual { + if (paused()) { + revert EnforcedPause(); + } + } + + /** + * @dev Throws if the contract is not paused. + */ + function _requirePaused() internal view virtual { + if (!paused()) { + revert ExpectedPause(); + } + } + + /** + * @dev Triggers stopped state. + * + * Requirements: + * + * - The contract must not be paused. + */ + function _pause() internal virtual whenNotPaused { + _paused = true; + emit Paused(_msgSender()); + } + + /** + * @dev Returns to normal state. + * + * Requirements: + * + * - The contract must be paused. + */ + function _unpause() internal virtual whenPaused { + _paused = false; + emit Unpaused(_msgSender()); + } +} diff --git a/contracts/utils/README.adoc b/contracts/utils/README.adoc new file mode 100644 index 000000000..d88b00199 --- /dev/null +++ b/contracts/utils/README.adoc @@ -0,0 +1,88 @@ += Utilities + +[.readme-notice] +NOTE: This document is better viewed at https://docs.openzeppelin.com/contracts/api/utils + +Miscellaneous contracts and libraries containing utility functions you can use to improve security, work with new data types, or safely use low-level primitives. + + * {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. + * {SafeCast}: Checked downcasting functions to avoid silent truncation. + * {Math}, {SignedMath}: Implementation of various arithmetic functions. + * {Multicall}: Simple way to batch together multiple calls in a single external call. + * {Create2}: Wrapper around the https://blog.openzeppelin.com/getting-the-most-out-of-create2/[`CREATE2` EVM opcode] for safe use without having to deal with low-level assembly. + * {EnumerableMap}: A type like Solidity's https://solidity.readthedocs.io/en/latest/types.html#mapping-types[`mapping`], 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. + +[NOTE] +==== +Because Solidity does not support generic types, {EnumerableMap} and {EnumerableSet} are specialized to a limited number of key-value types. +==== + +== Math + +{{Math}} + +{{SignedMath}} + +{{SafeCast}} + +== Cryptography + +{{ECDSA}} + +{{MessageHashUtils}} + +{{SignatureChecker}} + +{{MerkleProof}} + +{{EIP712}} + +== Security + +{{ReentrancyGuard}} + +{{Pausable}} + +== 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. + +{{IERC165}} + +{{ERC165}} + +{{ERC165Checker}} + +== Data Structures + +{{BitMaps}} + +{{EnumerableMap}} + +{{EnumerableSet}} + +{{DoubleEndedQueue}} + +{{Checkpoints}} + +== Libraries + +{{Create2}} + +{{Address}} + +{{Arrays}} + +{{Base64}} + +{{Strings}} + +{{ShortStrings}} + +{{StorageSlot}} + +{{Multicall}} diff --git a/contracts/utils/ReentrancyGuard.sol b/contracts/utils/ReentrancyGuard.sol new file mode 100644 index 000000000..92108edff --- /dev/null +++ b/contracts/utils/ReentrancyGuard.sol @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (utils/ReentrancyGuard.sol) + +pragma solidity ^0.8.20; + +/** + * @dev Contract module that helps prevent reentrant calls to a function. + * + * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier + * available, which can be applied to functions to make sure there are no nested + * (reentrant) calls to them. + * + * Note that because there is a single `nonReentrant` guard, functions marked as + * `nonReentrant` may not call one another. This can be worked around by making + * those functions `private`, and then adding `external` `nonReentrant` entry + * points to them. + * + * 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]. + */ +abstract contract ReentrancyGuard { + // Booleans are more expensive than uint256 or any type that takes up a full + // word because each write operation emits an extra SLOAD to first read the + // slot's contents, replace the bits taken up by the boolean, and then write + // back. This is the compiler's defense against contract upgrades and + // pointer aliasing, and it cannot be disabled. + + // The values being non-zero value makes deployment a bit more expensive, + // but in exchange the refund on every call to nonReentrant will be lower in + // amount. Since refunds are capped to a percentage of the total + // transaction's gas, it is best to keep them low in cases like this one, to + // increase the likelihood of the full refund coming into effect. + uint256 private constant NOT_ENTERED = 1; + uint256 private constant ENTERED = 2; + + uint256 private _status; + + /** + * @dev Unauthorized reentrant call. + */ + error ReentrancyGuardReentrantCall(); + + constructor() { + _status = NOT_ENTERED; + } + + /** + * @dev Prevents a contract from calling itself, directly or indirectly. + * Calling a `nonReentrant` function from another `nonReentrant` + * function is not supported. It is possible to prevent this from happening + * by making the `nonReentrant` function external, and making it call a + * `private` function that does the actual work. + */ + modifier nonReentrant() { + _nonReentrantBefore(); + _; + _nonReentrantAfter(); + } + + function _nonReentrantBefore() private { + // On the first call to nonReentrant, _status will be NOT_ENTERED + if (_status == ENTERED) { + revert ReentrancyGuardReentrantCall(); + } + + // Any calls to nonReentrant after this point will fail + _status = ENTERED; + } + + function _nonReentrantAfter() private { + // By storing the original value once again, a refund is triggered (see + // https://eips.ethereum.org/EIPS/eip-2200) + _status = NOT_ENTERED; + } + + /** + * @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a + * `nonReentrant` function in the call stack. + */ + function _reentrancyGuardEntered() internal view returns (bool) { + return _status == ENTERED; + } +} diff --git a/contracts/utils/ShortStrings.sol b/contracts/utils/ShortStrings.sol new file mode 100644 index 000000000..87dd081bd --- /dev/null +++ b/contracts/utils/ShortStrings.sol @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (utils/ShortStrings.sol) + +pragma solidity ^0.8.20; + +import {StorageSlot} from "./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 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: + * + * ```solidity + * contract Named { + * using ShortStrings for *; + * + * ShortString private immutable _name; + * string private _nameFallback; + * + * constructor(string memory contractName) { + * _name = contractName.toShortStringWithFallback(_nameFallback); + * } + * + * function name() external view returns (string memory) { + * return _name.toStringWithFallback(_nameFallback); + * } + * } + * ``` + */ +library ShortStrings { + // Used as an identifier for strings longer than 31 bytes. + bytes32 private constant FALLBACK_SENTINEL = 0x00000000000000000000000000000000000000000000000000000000000000FF; + + error StringTooLong(string str); + error InvalidShortString(); + + /** + * @dev Encode a string of at most 31 chars into a `ShortString`. + * + * This will trigger a `StringTooLong` error is the input string is too long. + */ + function toShortString(string memory str) internal pure returns (ShortString) { + bytes memory bstr = bytes(str); + if (bstr.length > 31) { + revert StringTooLong(str); + } + return ShortString.wrap(bytes32(uint256(bytes32(bstr)) | bstr.length)); + } + + /** + * @dev Decode a `ShortString` back to a "normal" string. + */ + function toString(ShortString sstr) internal pure returns (string memory) { + uint256 len = byteLength(sstr); + // using `new string(len)` would work locally but is not memory safe. + string memory str = new string(32); + /// @solidity memory-safe-assembly + assembly { + mstore(str, len) + mstore(add(str, 0x20), sstr) + } + return str; + } + + /** + * @dev Return the length of a `ShortString`. + */ + function byteLength(ShortString sstr) internal pure returns (uint256) { + uint256 result = uint256(ShortString.unwrap(sstr)) & 0xFF; + if (result > 31) { + revert InvalidShortString(); + } + return result; + } + + /** + * @dev Encode a string into a `ShortString`, or write it to storage if it is too long. + */ + function toShortStringWithFallback(string memory value, string storage store) internal returns (ShortString) { + if (bytes(value).length < 32) { + return toShortString(value); + } else { + StorageSlot.getStringSlot(store).value = value; + return ShortString.wrap(FALLBACK_SENTINEL); + } + } + + /** + * @dev Decode a string that was encoded to `ShortString` or written to storage using {setWithFallback}. + */ + function toStringWithFallback(ShortString value, string storage store) internal pure returns (string memory) { + if (ShortString.unwrap(value) != FALLBACK_SENTINEL) { + return toString(value); + } else { + return store; + } + } + + /** + * @dev Return the length of a string that was encoded to `ShortString` or written to storage using + * {setWithFallback}. + * + * WARNING: This will return the "byte length" of the string. This may not reflect the actual length in terms of + * actual characters as the UTF-8 encoding of a single character can span over multiple bytes. + */ + function byteLengthWithFallback(ShortString value, string storage store) internal view returns (uint256) { + if (ShortString.unwrap(value) != FALLBACK_SENTINEL) { + return byteLength(value); + } else { + return bytes(store).length; + } + } +} diff --git a/contracts/utils/StorageSlot.sol b/contracts/utils/StorageSlot.sol new file mode 100644 index 000000000..4618c6550 --- /dev/null +++ b/contracts/utils/StorageSlot.sol @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (utils/StorageSlot.sol) +// This file was procedurally generated from scripts/generate/templates/StorageSlot.js. + +pragma solidity ^0.8.20; + +/** + * @dev Library for reading and writing primitive types to specific storage slots. + * + * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts. + * This library helps with reading and writing to such slots without the need for inline assembly. + * + * The functions in this library return Slot structs that contain a `value` member that can be used to read or write. + * + * Example usage to set ERC1967 implementation slot: + * ```solidity + * contract ERC1967 { + * bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; + * + * function _getImplementation() internal view returns (address) { + * return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value; + * } + * + * function _setImplementation(address newImplementation) internal { + * require(newImplementation.code.length > 0); + * StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation; + * } + * } + * ``` + */ +library StorageSlot { + struct AddressSlot { + address value; + } + + struct BooleanSlot { + bool value; + } + + struct Bytes32Slot { + bytes32 value; + } + + struct Uint256Slot { + uint256 value; + } + + struct StringSlot { + string value; + } + + struct BytesSlot { + bytes value; + } + + /** + * @dev Returns an `AddressSlot` with member `value` located at `slot`. + */ + function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) { + /// @solidity memory-safe-assembly + assembly { + r.slot := slot + } + } + + /** + * @dev Returns an `BooleanSlot` with member `value` located at `slot`. + */ + function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) { + /// @solidity memory-safe-assembly + assembly { + r.slot := slot + } + } + + /** + * @dev Returns an `Bytes32Slot` with member `value` located at `slot`. + */ + function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) { + /// @solidity memory-safe-assembly + assembly { + r.slot := slot + } + } + + /** + * @dev Returns an `Uint256Slot` with member `value` located at `slot`. + */ + function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) { + /// @solidity memory-safe-assembly + assembly { + r.slot := slot + } + } + + /** + * @dev Returns an `StringSlot` with member `value` located at `slot`. + */ + function getStringSlot(bytes32 slot) internal pure returns (StringSlot storage r) { + /// @solidity memory-safe-assembly + assembly { + r.slot := slot + } + } + + /** + * @dev Returns an `StringSlot` representation of the string storage pointer `store`. + */ + function getStringSlot(string storage store) internal pure returns (StringSlot storage r) { + /// @solidity memory-safe-assembly + assembly { + r.slot := store.slot + } + } + + /** + * @dev Returns an `BytesSlot` with member `value` located at `slot`. + */ + function getBytesSlot(bytes32 slot) internal pure returns (BytesSlot storage r) { + /// @solidity memory-safe-assembly + assembly { + r.slot := slot + } + } + + /** + * @dev Returns an `BytesSlot` representation of the bytes storage pointer `store`. + */ + function getBytesSlot(bytes storage store) internal pure returns (BytesSlot storage r) { + /// @solidity memory-safe-assembly + assembly { + r.slot := store.slot + } + } +} diff --git a/contracts/utils/Strings.sol b/contracts/utils/Strings.sol new file mode 100644 index 000000000..be42c0b13 --- /dev/null +++ b/contracts/utils/Strings.sol @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (utils/Strings.sol) + +pragma solidity ^0.8.20; + +import {Math} from "./math/Math.sol"; +import {SignedMath} from "./math/SignedMath.sol"; + +/** + * @dev String operations. + */ +library Strings { + bytes16 private constant HEX_DIGITS = "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. + */ + function toString(uint256 value) internal pure returns (string memory) { + unchecked { + uint256 length = Math.log10(value) + 1; + string memory buffer = new string(length); + uint256 ptr; + /// @solidity memory-safe-assembly + assembly { + ptr := add(buffer, add(32, length)) + } + while (true) { + ptr--; + /// @solidity memory-safe-assembly + assembly { + mstore8(ptr, byte(mod(value, 10), HEX_DIGITS)) + } + value /= 10; + if (value == 0) break; + } + return buffer; + } + } + + /** + * @dev Converts a `int256` to its ASCII `string` decimal representation. + */ + function toStringSigned(int256 value) internal pure returns (string memory) { + return string.concat(value < 0 ? "-" : "", toString(SignedMath.abs(value))); + } + + /** + * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation. + */ + function toHexString(uint256 value) internal pure returns (string memory) { + unchecked { + return toHexString(value, Math.log256(value) + 1); + } + } + + /** + * @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] = HEX_DIGITS[localValue & 0xf]; + localValue >>= 4; + } + if (localValue != 0) { + revert StringsInsufficientHexLength(value, length); + } + return string(buffer); + } + + /** + * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal + * representation. + */ + function toHexString(address addr) internal pure returns (string memory) { + return toHexString(uint256(uint160(addr)), ADDRESS_LENGTH); + } + + /** + * @dev Returns true if the two strings are equal. + */ + function equal(string memory a, string memory b) internal pure returns (bool) { + return bytes(a).length == bytes(b).length && keccak256(bytes(a)) == keccak256(bytes(b)); + } +} diff --git a/contracts/utils/cryptography/ECDSA.sol b/contracts/utils/cryptography/ECDSA.sol new file mode 100644 index 000000000..ea8127a92 --- /dev/null +++ b/contracts/utils/cryptography/ECDSA.sol @@ -0,0 +1,174 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (utils/cryptography/ECDSA.sol) + +pragma solidity ^0.8.20; + +/** + * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations. + * + * These functions can be used to verify that a message was signed by the holder + * of the private keys of a given address. + */ +library ECDSA { + enum RecoverError { + NoError, + InvalidSignature, + InvalidSignatureLength, + InvalidSignatureS + } + + /** + * @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); + + /** + * @dev Returns the address that signed a hashed message (`hash`) with `signature` or an error. This will not + * return address(0) without also returning an error description. Errors are documented using an enum (error type) + * and a bytes32 providing additional information about the error. + * + * If no error is returned, then the address can be used for verification purposes. + * + * 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. + * + * IMPORTANT: `hash` _must_ be the result of a hash operation for the + * 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 {MessageHashUtils-toEthSignedMessageHash} on it. + * + * 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] + */ + function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError, bytes32) { + if (signature.length == 65) { + bytes32 r; + bytes32 s; + uint8 v; + // ecrecover takes the signature parameters, and the only way to get them + // currently is to use assembly. + /// @solidity memory-safe-assembly + assembly { + r := mload(add(signature, 0x20)) + s := mload(add(signature, 0x40)) + v := byte(0, mload(add(signature, 0x60))) + } + return tryRecover(hash, v, r, s); + } else { + return (address(0), RecoverError.InvalidSignatureLength, bytes32(signature.length)); + } + } + + /** + * @dev Returns the address that signed a hashed message (`hash`) with + * `signature`. This address can then be used for verification purposes. + * + * 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. + * + * IMPORTANT: `hash` _must_ be the result of a hash operation for the + * 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 {MessageHashUtils-toEthSignedMessageHash} on it. + */ + function recover(bytes32 hash, bytes memory signature) internal pure returns (address) { + (address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, signature); + _throwError(error, errorArg); + return recovered; + } + + /** + * @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] + */ + 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. + uint8 v = uint8((uint256(vs) >> 255) + 27); + return tryRecover(hash, v, r, s); + } + } + + /** + * @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately. + */ + function recover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address) { + (address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, r, vs); + _throwError(error, errorArg); + return recovered; + } + + /** + * @dev Overload of {ECDSA-tryRecover} that receives the `v`, + * `r` and `s` signature fields separately. + */ + 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 + // signatures from current libraries generate a unique signature with an s-value in the lower half order. + // + // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value + // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or + // 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, 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, bytes32(0)); + } + + return (signer, RecoverError.NoError, bytes32(0)); + } + + /** + * @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) { + (address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, v, r, s); + _throwError(error, errorArg); + return recovered; + } + + /** + * @dev Optionally reverts with the corresponding custom error according to the `error` argument provided. + */ + 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); + } + } +} diff --git a/contracts/utils/cryptography/EIP712.sol b/contracts/utils/cryptography/EIP712.sol new file mode 100644 index 000000000..b29b08db6 --- /dev/null +++ b/contracts/utils/cryptography/EIP712.sol @@ -0,0 +1,160 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (utils/cryptography/EIP712.sol) + +pragma solidity ^0.8.20; + +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 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 + * ({_hashTypedDataV4}). + * + * The implementation of the domain separator was designed to be as efficient as possible while still properly updating + * the chain id to protect against replay attacks on an eventual fork of the chain. + * + * NOTE: This contract implements the version of the encoding known as "v4", as implemented by the JSON RPC method + * 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 from the immutable values, which is cheaper than accessing a cached version in cold storage. + * + * @custom:oz-upgrades-unsafe-allow state-variable-immutable + */ +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; + + bytes32 private immutable _hashedName; + bytes32 private immutable _hashedVersion; + + ShortString private immutable _name; + ShortString private immutable _version; + string private _nameFallback; + string private _versionFallback; + + /** + * @dev Initializes the domain separator and parameter caches. + * + * The meaning of `name` and `version` is specified in + * https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator[EIP 712]: + * + * - `name`: the user readable name of the signing domain, i.e. the name of the DApp or the protocol. + * - `version`: the current major version of the signing domain. + * + * NOTE: These parameters cannot be changed except through a xref:learn::upgrading-smart-contracts.adoc[smart + * 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); + } + + /** + * @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(); + } + } + + function _buildDomainSeparator() private view returns (bytes32) { + return keccak256(abi.encode(TYPE_HASH, _hashedName, _hashedVersion, block.chainid, address(this))); + } + + /** + * @dev Given an already https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct[hashed struct], this + * function returns the hash of the fully encoded EIP712 message for this domain. + * + * This hash can be used together with {ECDSA-recover} to obtain the signer of a message. For example: + * + * ```solidity + * bytes32 digest = _hashTypedDataV4(keccak256(abi.encode( + * keccak256("Mail(address to,string contents)"), + * mailTo, + * keccak256(bytes(mailContents)) + * ))); + * address signer = ECDSA.recover(digest, signature); + * ``` + */ + function _hashTypedDataV4(bytes32 structHash) internal view virtual returns (bytes32) { + return MessageHashUtils.toTypedDataHash(_domainSeparatorV4(), structHash); + } + + /** + * @dev See {IERC-5267}. + */ + function eip712Domain() + public + view + virtual + returns ( + bytes1 fields, + string memory name, + string memory version, + uint256 chainId, + address verifyingContract, + bytes32 salt, + uint256[] memory extensions + ) + { + return ( + hex"0f", // 01111 + _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). + */ + // 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). + */ + // solhint-disable-next-line func-name-mixedcase + function _EIP712Version() internal view returns (string memory) { + return _version.toStringWithFallback(_versionFallback); + } +} diff --git a/contracts/utils/cryptography/MerkleProof.sol b/contracts/utils/cryptography/MerkleProof.sol new file mode 100644 index 000000000..68293c4fe --- /dev/null +++ b/contracts/utils/cryptography/MerkleProof.sol @@ -0,0 +1,232 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (utils/cryptography/MerkleProof.sol) + +pragma solidity ^0.8.20; + +/** + * @dev These functions deal with verification of Merkle Tree proofs. + * + * The tree and the proofs can be generated using our + * https://github.com/OpenZeppelin/merkle-tree[JavaScript library]. + * You will find a quickstart guide in the readme. + * + * WARNING: You should avoid using leaf values that are 64 bytes long prior to + * hashing, or use a hash function other than keccak256 for hashing leaves. + * This is because the concatenation of a sorted pair of internal nodes in + * the Merkle tree could be reinterpreted as a leaf value. + * OpenZeppelin's JavaScript library generates Merkle trees that are safe + * 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 + * 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) { + return processProof(proof, leaf) == root; + } + + /** + * @dev Calldata version of {verify} + */ + function verifyCalldata(bytes32[] calldata proof, bytes32 root, bytes32 leaf) internal pure returns (bool) { + return processProofCalldata(proof, leaf) == root; + } + + /** + * @dev Returns the rebuilt hash obtained by traversing a Merkle tree up + * 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. + */ + function processProof(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32) { + bytes32 computedHash = leaf; + for (uint256 i = 0; i < proof.length; i++) { + computedHash = _hashPair(computedHash, proof[i]); + } + return computedHash; + } + + /** + * @dev Calldata version of {processProof} + */ + function processProofCalldata(bytes32[] calldata proof, bytes32 leaf) internal pure returns (bytes32) { + bytes32 computedHash = leaf; + for (uint256 i = 0; i < proof.length; i++) { + computedHash = _hashPair(computedHash, proof[i]); + } + return computedHash; + } + + /** + * @dev Returns true if the `leaves` can be simultaneously proven to be a part of a Merkle tree defined by + * `root`, according to `proof` and `proofFlags` as described in {processMultiProof}. + * + * CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details. + */ + function multiProofVerify( + bytes32[] memory proof, + bool[] memory proofFlags, + bytes32 root, + bytes32[] memory leaves + ) internal pure returns (bool) { + return processMultiProof(proof, proofFlags, leaves) == root; + } + + /** + * @dev Calldata version of {multiProofVerify} + * + * CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details. + */ + function multiProofVerifyCalldata( + bytes32[] calldata proof, + bool[] calldata proofFlags, + bytes32 root, + bytes32[] memory leaves + ) internal pure returns (bool) { + return processMultiProofCalldata(proof, proofFlags, leaves) == root; + } + + /** + * @dev Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction + * proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another + * leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false + * respectively. + * + * 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). + */ + function processMultiProof( + bytes32[] memory proof, + bool[] memory proofFlags, + bytes32[] memory leaves + ) internal pure returns (bytes32 merkleRoot) { + // This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by + // consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the + // `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 + proofLen != totalHashes + 1) { + 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". + bytes32[] memory hashes = new bytes32[](totalHashes); + uint256 leafPos = 0; + uint256 hashPos = 0; + uint256 proofPos = 0; + // At each step, we compute the next hash using two values: + // - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we + // get the next hash. + // - depending on the flag, either another value from the "main queue" (merging branches) or an element from the + // `proof` array. + for (uint256 i = 0; i < totalHashes; i++) { + bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++]; + bytes32 b = proofFlags[i] + ? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++]) + : proof[proofPos++]; + hashes[i] = _hashPair(a, b); + } + + if (totalHashes > 0) { + if (proofPos != proofLen) { + revert MerkleProofInvalidMultiproof(); + } + unchecked { + return hashes[totalHashes - 1]; + } + } else if (leavesLen > 0) { + return leaves[0]; + } else { + return proof[0]; + } + } + + /** + * @dev Calldata version of {processMultiProof}. + * + * CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details. + */ + function processMultiProofCalldata( + bytes32[] calldata proof, + bool[] calldata proofFlags, + bytes32[] memory leaves + ) internal pure returns (bytes32 merkleRoot) { + // This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by + // consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the + // `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 + proofLen != totalHashes + 1) { + 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". + bytes32[] memory hashes = new bytes32[](totalHashes); + uint256 leafPos = 0; + uint256 hashPos = 0; + uint256 proofPos = 0; + // At each step, we compute the next hash using two values: + // - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we + // get the next hash. + // - depending on the flag, either another value from the "main queue" (merging branches) or an element from the + // `proof` array. + for (uint256 i = 0; i < totalHashes; i++) { + bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++]; + bytes32 b = proofFlags[i] + ? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++]) + : proof[proofPos++]; + hashes[i] = _hashPair(a, b); + } + + if (totalHashes > 0) { + if (proofPos != proofLen) { + revert MerkleProofInvalidMultiproof(); + } + unchecked { + return hashes[totalHashes - 1]; + } + } else if (leavesLen > 0) { + return leaves[0]; + } else { + return proof[0]; + } + } + + /** + * @dev Sorts the pair (a, b) and hashes the result. + */ + function _hashPair(bytes32 a, bytes32 b) private pure returns (bytes32) { + return a < b ? _efficientHash(a, b) : _efficientHash(b, a); + } + + /** + * @dev Implementation of keccak256(abi.encode(a, b)) that doesn't allocate or expand memory. + */ + function _efficientHash(bytes32 a, bytes32 b) private pure returns (bytes32 value) { + /// @solidity memory-safe-assembly + assembly { + mstore(0x00, a) + mstore(0x20, b) + value := keccak256(0x00, 0x40) + } + } +} diff --git a/contracts/utils/cryptography/MessageHashUtils.sol b/contracts/utils/cryptography/MessageHashUtils.sol new file mode 100644 index 000000000..ef2397749 --- /dev/null +++ b/contracts/utils/cryptography/MessageHashUtils.sol @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (utils/cryptography/MessageHashUtils.sol) + +pragma solidity ^0.8.20; + +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 `messageHash` parameter is intended to be the result of hashing a raw message with + * keccak256, although 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) { + return + keccak256(bytes.concat("\x19Ethereum Signed Message:\n", bytes(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) { + 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/contracts/utils/cryptography/SignatureChecker.sol b/contracts/utils/cryptography/SignatureChecker.sol new file mode 100644 index 000000000..584e33dc9 --- /dev/null +++ b/contracts/utils/cryptography/SignatureChecker.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (utils/cryptography/SignatureChecker.sol) + +pragma solidity ^0.8.20; + +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 + * signatures from externally owned accounts (EOAs) as well as ERC1271 signatures from smart contract wallets like + * Argent and Safe Wallet (previously Gnosis Safe). + */ +library SignatureChecker { + /** + * @dev Checks if a signature is valid for a given signer and data hash. If the signer is a smart contract, the + * signature is validated against that smart contract using ERC1271, otherwise it's validated using `ECDSA.recover`. + * + * 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) { + (address recovered, ECDSA.RecoverError error, ) = ECDSA.tryRecover(hash, signature); + return + (error == ECDSA.RecoverError.NoError && recovered == signer) || + isValidERC1271SignatureNow(signer, hash, signature); + } + + /** + * @dev Checks if a signature is valid for a given signer and data hash. The signature is validated + * against the signer smart contract using ERC1271. + * + * 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 isValidERC1271SignatureNow( + address signer, + bytes32 hash, + bytes memory signature + ) internal view returns (bool) { + (bool success, bytes memory result) = signer.staticcall( + abi.encodeCall(IERC1271.isValidSignature, (hash, signature)) + ); + return (success && + result.length >= 32 && + abi.decode(result, (bytes32)) == bytes32(IERC1271.isValidSignature.selector)); + } +} diff --git a/contracts/utils/introspection/ERC165.sol b/contracts/utils/introspection/ERC165.sol new file mode 100644 index 000000000..8ce112f6f --- /dev/null +++ b/contracts/utils/introspection/ERC165.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (utils/introspection/ERC165.sol) + +pragma solidity ^0.8.20; + +import {IERC165} from "./IERC165.sol"; + +/** + * @dev Implementation of the {IERC165} interface. + * + * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check + * for the additional interface id that will be supported. For example: + * + * ```solidity + * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { + * return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId); + * } + * ``` + */ +abstract contract ERC165 is IERC165 { + /** + * @dev See {IERC165-supportsInterface}. + */ + function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) { + return interfaceId == type(IERC165).interfaceId; + } +} diff --git a/contracts/utils/introspection/ERC165Checker.sol b/contracts/utils/introspection/ERC165Checker.sol new file mode 100644 index 000000000..a04f9c012 --- /dev/null +++ b/contracts/utils/introspection/ERC165Checker.sol @@ -0,0 +1,124 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (utils/introspection/ERC165Checker.sol) + +pragma solidity ^0.8.20; + +import {IERC165} from "./IERC165.sol"; + +/** + * @dev Library used to query support of an interface declared via {IERC165}. + * + * Note that these functions return the actual result of the query: they do not + * `revert` if an interface is not supported. It is up to the caller to decide + * what to do in these cases. + */ +library ERC165Checker { + // As per the EIP-165 spec, no interface should ever match 0xffffffff + bytes4 private constant INTERFACE_ID_INVALID = 0xffffffff; + + /** + * @dev Returns true if `account` supports the {IERC165} interface. + */ + function supportsERC165(address account) internal view returns (bool) { + // Any contract that implements ERC165 must explicitly indicate support of + // InterfaceId_ERC165 and explicitly indicate non-support of InterfaceId_Invalid + return + supportsERC165InterfaceUnchecked(account, type(IERC165).interfaceId) && + !supportsERC165InterfaceUnchecked(account, INTERFACE_ID_INVALID); + } + + /** + * @dev Returns true if `account` supports the interface defined by + * `interfaceId`. Support for {IERC165} itself is queried automatically. + * + * See {IERC165-supportsInterface}. + */ + function supportsInterface(address account, bytes4 interfaceId) internal view returns (bool) { + // query support of both ERC165 as per the spec and support of _interfaceId + return supportsERC165(account) && supportsERC165InterfaceUnchecked(account, interfaceId); + } + + /** + * @dev Returns a boolean array where each value corresponds to the + * interfaces passed in and whether they're supported or not. This allows + * you to batch check interfaces for a contract where your expectation + * is that some interfaces may not be supported. + * + * See {IERC165-supportsInterface}. + */ + 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); + + // query support of ERC165 itself + if (supportsERC165(account)) { + // query support of each interface in interfaceIds + for (uint256 i = 0; i < interfaceIds.length; i++) { + interfaceIdsSupported[i] = supportsERC165InterfaceUnchecked(account, interfaceIds[i]); + } + } + + return interfaceIdsSupported; + } + + /** + * @dev Returns true if `account` supports all the interfaces defined in + * `interfaceIds`. Support for {IERC165} itself is queried automatically. + * + * Batch-querying can lead to gas savings by skipping repeated checks for + * {IERC165} support. + * + * See {IERC165-supportsInterface}. + */ + function supportsAllInterfaces(address account, bytes4[] memory interfaceIds) internal view returns (bool) { + // query support of ERC165 itself + if (!supportsERC165(account)) { + return false; + } + + // query support of each interface in interfaceIds + for (uint256 i = 0; i < interfaceIds.length; i++) { + if (!supportsERC165InterfaceUnchecked(account, interfaceIds[i])) { + return false; + } + } + + // all interfaces supported + return true; + } + + /** + * @notice Query if a contract implements an interface, does not check ERC165 support + * @param account The address of the contract to query for support of an interface + * @param interfaceId The interface identifier, as specified in ERC-165 + * @return true if the contract at account indicates support of the interface with + * identifier interfaceId, false otherwise + * @dev Assumes that account contains a contract that supports ERC165, otherwise + * the behavior of this method is undefined. This precondition can be checked + * with {supportsERC165}. + * + * Some precompiled contracts will falsely indicate support for a given interface, so caution + * should be exercised when using this function. + * + * Interface identification is specified in ERC-165. + */ + function supportsERC165InterfaceUnchecked(address account, bytes4 interfaceId) internal view returns (bool) { + // prepare call + bytes memory encodedParams = abi.encodeCall(IERC165.supportsInterface, (interfaceId)); + + // perform static call + bool success; + uint256 returnSize; + uint256 returnValue; + assembly { + success := staticcall(30000, account, add(encodedParams, 0x20), mload(encodedParams), 0x00, 0x20) + returnSize := returndatasize() + returnValue := mload(0x00) + } + + return success && returnSize >= 0x20 && returnValue > 0; + } +} diff --git a/contracts/utils/introspection/IERC165.sol b/contracts/utils/introspection/IERC165.sol new file mode 100644 index 000000000..cfc9a1991 --- /dev/null +++ b/contracts/utils/introspection/IERC165.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (utils/introspection/IERC165.sol) + +pragma solidity ^0.8.20; + +/** + * @dev Interface of the ERC165 standard, as defined in the + * https://eips.ethereum.org/EIPS/eip-165[EIP]. + * + * Implementers can declare support of contract interfaces, which can then be + * queried by others ({ERC165Checker}). + * + * For an implementation, see {ERC165}. + */ +interface IERC165 { + /** + * @dev Returns true if this contract implements the interface defined by + * `interfaceId`. See the corresponding + * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section] + * to learn more about how these ids are created. + * + * This function call must use less than 30 000 gas. + */ + function supportsInterface(bytes4 interfaceId) external view returns (bool); +} diff --git a/contracts/utils/math/Math.sol b/contracts/utils/math/Math.sol new file mode 100644 index 000000000..184dbd70d --- /dev/null +++ b/contracts/utils/math/Math.sol @@ -0,0 +1,415 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (utils/math/Math.sol) + +pragma solidity ^0.8.20; + +/** + * @dev Standard math utilities missing in the Solidity language. + */ +library Math { + /** + * @dev Muldiv operation overflow. + */ + error MathOverflowedMulDiv(); + + enum Rounding { + Floor, // Toward negative infinity + Ceil, // Toward positive infinity + Trunc, // Toward zero + Expand // Away from zero + } + + /** + * @dev Returns the addition of two unsigned integers, with an overflow flag. + */ + 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. + */ + 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. + */ + 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. + */ + 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. + */ + 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. + */ + function max(uint256 a, uint256 b) internal pure returns (uint256) { + return a > b ? a : b; + } + + /** + * @dev Returns the smallest of two numbers. + */ + function min(uint256 a, uint256 b) internal pure returns (uint256) { + return a < b ? a : b; + } + + /** + * @dev Returns the average of two numbers. The result is rounded towards + * zero. + */ + function average(uint256 a, uint256 b) internal pure returns (uint256) { + // (a + b) / 2 can overflow. + return (a & b) + (a ^ b) / 2; + } + + /** + * @dev Returns the ceiling of the division of two numbers. + * + * 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) { + // 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; + } + + /** + * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or + * denominator == 0. + * @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) { + 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 + // variables such that product = prod1 * 2^256 + prod0. + 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)) + prod1 := sub(sub(mm, prod0), lt(mm, prod0)) + } + + // Handle non-overflow cases, 256 by 256 division. + if (prod1 == 0) { + // Solidity will revert if denominator == 0, unlike the div opcode on its own. + // The surrounding unchecked block does not change this fact. + // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic. + return prod0 / denominator; + } + + // Make sure the result is less than 2^256. Also prevents denominator == 0. + if (denominator <= prod1) { + revert MathOverflowedMulDiv(); + } + + /////////////////////////////////////////////// + // 512 by 256 division. + /////////////////////////////////////////////// + + // Make division exact by subtracting the remainder from [prod1 prod0]. + uint256 remainder; + assembly { + // Compute remainder using mulmod. + remainder := mulmod(x, y, denominator) + + // Subtract 256 bit number from 512 bit number. + prod1 := sub(prod1, gt(remainder, prod0)) + prod0 := sub(prod0, remainder) + } + + // 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. + + uint256 twos = denominator & (0 - denominator); + assembly { + // Divide denominator by twos. + denominator := div(denominator, twos) + + // Divide [prod1 prod0] by twos. + prod0 := div(prod0, twos) + + // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one. + twos := add(div(sub(0, twos), twos), 1) + } + + // Shift in bits from prod1 into prod0. + prod0 |= prod1 * twos; + + // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such + // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for + // four bits. That is, denominator * inv = 1 mod 2^4. + uint256 inverse = (3 * denominator) ^ 2; + + // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also + // works in modular arithmetic, doubling the correct bits in each step. + inverse *= 2 - denominator * inverse; // inverse mod 2^8 + inverse *= 2 - denominator * inverse; // inverse mod 2^16 + inverse *= 2 - denominator * inverse; // inverse mod 2^32 + inverse *= 2 - denominator * inverse; // inverse mod 2^64 + inverse *= 2 - denominator * inverse; // inverse mod 2^128 + inverse *= 2 - denominator * inverse; // inverse mod 2^256 + + // Because the division is now exact we can divide by multiplying with the modular inverse of denominator. + // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is + // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1 + // is no longer required. + result = prod0 * inverse; + return result; + } + } + + /** + * @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) { + uint256 result = mulDiv(x, y, denominator); + 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 + * towards zero. + * + * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11). + */ + function sqrt(uint256 a) internal pure returns (uint256) { + if (a == 0) { + return 0; + } + + // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target. + // + // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have + // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`. + // + // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)` + // β†’ `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))` + // β†’ `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)` + // + // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit. + uint256 result = 1 << (log2(a) >> 1); + + // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128, + // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at + // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision + // into the expected uint128 result. + unchecked { + result = (result + a / result) >> 1; + result = (result + a / result) >> 1; + result = (result + a / result) >> 1; + result = (result + a / result) >> 1; + result = (result + a / result) >> 1; + result = (result + a / result) >> 1; + result = (result + a / result) >> 1; + return min(result, a / result); + } + } + + /** + * @notice Calculates sqrt(a), following the selected rounding direction. + */ + function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) { + unchecked { + uint256 result = sqrt(a); + return result + (unsignedRoundsUp(rounding) && result * result < a ? 1 : 0); + } + } + + /** + * @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) { + uint256 result = 0; + unchecked { + if (value >> 128 > 0) { + value >>= 128; + result += 128; + } + if (value >> 64 > 0) { + value >>= 64; + result += 64; + } + if (value >> 32 > 0) { + value >>= 32; + result += 32; + } + if (value >> 16 > 0) { + value >>= 16; + result += 16; + } + if (value >> 8 > 0) { + value >>= 8; + result += 8; + } + if (value >> 4 > 0) { + value >>= 4; + result += 4; + } + if (value >> 2 > 0) { + value >>= 2; + result += 2; + } + if (value >> 1 > 0) { + result += 1; + } + } + return result; + } + + /** + * @dev Return the log in base 2, following the selected rounding direction, of a positive value. + * Returns 0 if given 0. + */ + function log2(uint256 value, Rounding rounding) internal pure returns (uint256) { + unchecked { + uint256 result = log2(value); + return result + (unsignedRoundsUp(rounding) && 1 << result < value ? 1 : 0); + } + } + + /** + * @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) { + uint256 result = 0; + unchecked { + if (value >= 10 ** 64) { + value /= 10 ** 64; + result += 64; + } + if (value >= 10 ** 32) { + value /= 10 ** 32; + result += 32; + } + if (value >= 10 ** 16) { + value /= 10 ** 16; + result += 16; + } + if (value >= 10 ** 8) { + value /= 10 ** 8; + result += 8; + } + if (value >= 10 ** 4) { + value /= 10 ** 4; + result += 4; + } + if (value >= 10 ** 2) { + value /= 10 ** 2; + result += 2; + } + if (value >= 10 ** 1) { + result += 1; + } + } + return result; + } + + /** + * @dev Return the log in base 10, following the selected rounding direction, of a positive value. + * Returns 0 if given 0. + */ + function log10(uint256 value, Rounding rounding) internal pure returns (uint256) { + unchecked { + uint256 result = log10(value); + return result + (unsignedRoundsUp(rounding) && 10 ** result < value ? 1 : 0); + } + } + + /** + * @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. + */ + function log256(uint256 value) internal pure returns (uint256) { + uint256 result = 0; + unchecked { + if (value >> 128 > 0) { + value >>= 128; + result += 16; + } + if (value >> 64 > 0) { + value >>= 64; + result += 8; + } + if (value >> 32 > 0) { + value >>= 32; + result += 4; + } + if (value >> 16 > 0) { + value >>= 16; + result += 2; + } + if (value >> 8 > 0) { + result += 1; + } + } + return result; + } + + /** + * @dev Return the log in base 256, following the selected rounding direction, of a positive value. + * Returns 0 if given 0. + */ + function log256(uint256 value, Rounding rounding) internal pure returns (uint256) { + unchecked { + uint256 result = log256(value); + 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/contracts/utils/math/SafeCast.sol b/contracts/utils/math/SafeCast.sol new file mode 100644 index 000000000..117dfa386 --- /dev/null +++ b/contracts/utils/math/SafeCast.sol @@ -0,0 +1,1153 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (utils/math/SafeCast.sol) +// This file was procedurally generated from scripts/generate/templates/SafeCast.js. + +pragma solidity ^0.8.20; + +/** + * @dev Wrappers over Solidity's uintXX/intXX casting operators with added overflow + * checks. + * + * Downcasting from uint256/int256 in Solidity does not revert on overflow. This can + * easily result in undesired exploitation or bugs, since developers usually + * assume that overflows raise errors. `SafeCast` restores this intuition by + * reverting the transaction when such an operation overflows. + * + * Using this library instead of the unchecked operations eliminates an entire + * 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). + * + * Counterpart to Solidity's `uint248` operator. + * + * Requirements: + * + * - input must fit into 248 bits + */ + function toUint248(uint256 value) internal pure returns (uint248) { + if (value > type(uint248).max) { + revert SafeCastOverflowedUintDowncast(248, value); + } + return uint248(value); + } + + /** + * @dev Returns the downcasted uint240 from uint256, reverting on + * overflow (when the input is greater than largest uint240). + * + * Counterpart to Solidity's `uint240` operator. + * + * Requirements: + * + * - input must fit into 240 bits + */ + function toUint240(uint256 value) internal pure returns (uint240) { + if (value > type(uint240).max) { + revert SafeCastOverflowedUintDowncast(240, value); + } + return uint240(value); + } + + /** + * @dev Returns the downcasted uint232 from uint256, reverting on + * overflow (when the input is greater than largest uint232). + * + * Counterpart to Solidity's `uint232` operator. + * + * Requirements: + * + * - input must fit into 232 bits + */ + function toUint232(uint256 value) internal pure returns (uint232) { + if (value > type(uint232).max) { + revert SafeCastOverflowedUintDowncast(232, value); + } + return uint232(value); + } + + /** + * @dev Returns the downcasted uint224 from uint256, reverting on + * overflow (when the input is greater than largest uint224). + * + * Counterpart to Solidity's `uint224` operator. + * + * Requirements: + * + * - input must fit into 224 bits + */ + function toUint224(uint256 value) internal pure returns (uint224) { + if (value > type(uint224).max) { + revert SafeCastOverflowedUintDowncast(224, value); + } + return uint224(value); + } + + /** + * @dev Returns the downcasted uint216 from uint256, reverting on + * overflow (when the input is greater than largest uint216). + * + * Counterpart to Solidity's `uint216` operator. + * + * Requirements: + * + * - input must fit into 216 bits + */ + function toUint216(uint256 value) internal pure returns (uint216) { + if (value > type(uint216).max) { + revert SafeCastOverflowedUintDowncast(216, value); + } + return uint216(value); + } + + /** + * @dev Returns the downcasted uint208 from uint256, reverting on + * overflow (when the input is greater than largest uint208). + * + * Counterpart to Solidity's `uint208` operator. + * + * Requirements: + * + * - input must fit into 208 bits + */ + function toUint208(uint256 value) internal pure returns (uint208) { + if (value > type(uint208).max) { + revert SafeCastOverflowedUintDowncast(208, value); + } + return uint208(value); + } + + /** + * @dev Returns the downcasted uint200 from uint256, reverting on + * overflow (when the input is greater than largest uint200). + * + * Counterpart to Solidity's `uint200` operator. + * + * Requirements: + * + * - input must fit into 200 bits + */ + function toUint200(uint256 value) internal pure returns (uint200) { + if (value > type(uint200).max) { + revert SafeCastOverflowedUintDowncast(200, value); + } + return uint200(value); + } + + /** + * @dev Returns the downcasted uint192 from uint256, reverting on + * overflow (when the input is greater than largest uint192). + * + * Counterpart to Solidity's `uint192` operator. + * + * Requirements: + * + * - input must fit into 192 bits + */ + function toUint192(uint256 value) internal pure returns (uint192) { + if (value > type(uint192).max) { + revert SafeCastOverflowedUintDowncast(192, value); + } + return uint192(value); + } + + /** + * @dev Returns the downcasted uint184 from uint256, reverting on + * overflow (when the input is greater than largest uint184). + * + * Counterpart to Solidity's `uint184` operator. + * + * Requirements: + * + * - input must fit into 184 bits + */ + function toUint184(uint256 value) internal pure returns (uint184) { + if (value > type(uint184).max) { + revert SafeCastOverflowedUintDowncast(184, value); + } + return uint184(value); + } + + /** + * @dev Returns the downcasted uint176 from uint256, reverting on + * overflow (when the input is greater than largest uint176). + * + * Counterpart to Solidity's `uint176` operator. + * + * Requirements: + * + * - input must fit into 176 bits + */ + function toUint176(uint256 value) internal pure returns (uint176) { + if (value > type(uint176).max) { + revert SafeCastOverflowedUintDowncast(176, value); + } + return uint176(value); + } + + /** + * @dev Returns the downcasted uint168 from uint256, reverting on + * overflow (when the input is greater than largest uint168). + * + * Counterpart to Solidity's `uint168` operator. + * + * Requirements: + * + * - input must fit into 168 bits + */ + function toUint168(uint256 value) internal pure returns (uint168) { + if (value > type(uint168).max) { + revert SafeCastOverflowedUintDowncast(168, value); + } + return uint168(value); + } + + /** + * @dev Returns the downcasted uint160 from uint256, reverting on + * overflow (when the input is greater than largest uint160). + * + * Counterpart to Solidity's `uint160` operator. + * + * Requirements: + * + * - input must fit into 160 bits + */ + function toUint160(uint256 value) internal pure returns (uint160) { + if (value > type(uint160).max) { + revert SafeCastOverflowedUintDowncast(160, value); + } + return uint160(value); + } + + /** + * @dev Returns the downcasted uint152 from uint256, reverting on + * overflow (when the input is greater than largest uint152). + * + * Counterpart to Solidity's `uint152` operator. + * + * Requirements: + * + * - input must fit into 152 bits + */ + function toUint152(uint256 value) internal pure returns (uint152) { + if (value > type(uint152).max) { + revert SafeCastOverflowedUintDowncast(152, value); + } + return uint152(value); + } + + /** + * @dev Returns the downcasted uint144 from uint256, reverting on + * overflow (when the input is greater than largest uint144). + * + * Counterpart to Solidity's `uint144` operator. + * + * Requirements: + * + * - input must fit into 144 bits + */ + function toUint144(uint256 value) internal pure returns (uint144) { + if (value > type(uint144).max) { + revert SafeCastOverflowedUintDowncast(144, value); + } + return uint144(value); + } + + /** + * @dev Returns the downcasted uint136 from uint256, reverting on + * overflow (when the input is greater than largest uint136). + * + * Counterpart to Solidity's `uint136` operator. + * + * Requirements: + * + * - input must fit into 136 bits + */ + function toUint136(uint256 value) internal pure returns (uint136) { + if (value > type(uint136).max) { + revert SafeCastOverflowedUintDowncast(136, value); + } + return uint136(value); + } + + /** + * @dev Returns the downcasted uint128 from uint256, reverting on + * overflow (when the input is greater than largest uint128). + * + * Counterpart to Solidity's `uint128` operator. + * + * Requirements: + * + * - input must fit into 128 bits + */ + function toUint128(uint256 value) internal pure returns (uint128) { + if (value > type(uint128).max) { + revert SafeCastOverflowedUintDowncast(128, value); + } + return uint128(value); + } + + /** + * @dev Returns the downcasted uint120 from uint256, reverting on + * overflow (when the input is greater than largest uint120). + * + * Counterpart to Solidity's `uint120` operator. + * + * Requirements: + * + * - input must fit into 120 bits + */ + function toUint120(uint256 value) internal pure returns (uint120) { + if (value > type(uint120).max) { + revert SafeCastOverflowedUintDowncast(120, value); + } + return uint120(value); + } + + /** + * @dev Returns the downcasted uint112 from uint256, reverting on + * overflow (when the input is greater than largest uint112). + * + * Counterpart to Solidity's `uint112` operator. + * + * Requirements: + * + * - input must fit into 112 bits + */ + function toUint112(uint256 value) internal pure returns (uint112) { + if (value > type(uint112).max) { + revert SafeCastOverflowedUintDowncast(112, value); + } + return uint112(value); + } + + /** + * @dev Returns the downcasted uint104 from uint256, reverting on + * overflow (when the input is greater than largest uint104). + * + * Counterpart to Solidity's `uint104` operator. + * + * Requirements: + * + * - input must fit into 104 bits + */ + function toUint104(uint256 value) internal pure returns (uint104) { + if (value > type(uint104).max) { + revert SafeCastOverflowedUintDowncast(104, value); + } + return uint104(value); + } + + /** + * @dev Returns the downcasted uint96 from uint256, reverting on + * overflow (when the input is greater than largest uint96). + * + * Counterpart to Solidity's `uint96` operator. + * + * Requirements: + * + * - input must fit into 96 bits + */ + function toUint96(uint256 value) internal pure returns (uint96) { + if (value > type(uint96).max) { + revert SafeCastOverflowedUintDowncast(96, value); + } + return uint96(value); + } + + /** + * @dev Returns the downcasted uint88 from uint256, reverting on + * overflow (when the input is greater than largest uint88). + * + * Counterpart to Solidity's `uint88` operator. + * + * Requirements: + * + * - input must fit into 88 bits + */ + function toUint88(uint256 value) internal pure returns (uint88) { + if (value > type(uint88).max) { + revert SafeCastOverflowedUintDowncast(88, value); + } + return uint88(value); + } + + /** + * @dev Returns the downcasted uint80 from uint256, reverting on + * overflow (when the input is greater than largest uint80). + * + * Counterpart to Solidity's `uint80` operator. + * + * Requirements: + * + * - input must fit into 80 bits + */ + function toUint80(uint256 value) internal pure returns (uint80) { + if (value > type(uint80).max) { + revert SafeCastOverflowedUintDowncast(80, value); + } + return uint80(value); + } + + /** + * @dev Returns the downcasted uint72 from uint256, reverting on + * overflow (when the input is greater than largest uint72). + * + * Counterpart to Solidity's `uint72` operator. + * + * Requirements: + * + * - input must fit into 72 bits + */ + function toUint72(uint256 value) internal pure returns (uint72) { + if (value > type(uint72).max) { + revert SafeCastOverflowedUintDowncast(72, value); + } + return uint72(value); + } + + /** + * @dev Returns the downcasted uint64 from uint256, reverting on + * overflow (when the input is greater than largest uint64). + * + * Counterpart to Solidity's `uint64` operator. + * + * Requirements: + * + * - input must fit into 64 bits + */ + function toUint64(uint256 value) internal pure returns (uint64) { + if (value > type(uint64).max) { + revert SafeCastOverflowedUintDowncast(64, value); + } + return uint64(value); + } + + /** + * @dev Returns the downcasted uint56 from uint256, reverting on + * overflow (when the input is greater than largest uint56). + * + * Counterpart to Solidity's `uint56` operator. + * + * Requirements: + * + * - input must fit into 56 bits + */ + function toUint56(uint256 value) internal pure returns (uint56) { + if (value > type(uint56).max) { + revert SafeCastOverflowedUintDowncast(56, value); + } + return uint56(value); + } + + /** + * @dev Returns the downcasted uint48 from uint256, reverting on + * overflow (when the input is greater than largest uint48). + * + * Counterpart to Solidity's `uint48` operator. + * + * Requirements: + * + * - input must fit into 48 bits + */ + function toUint48(uint256 value) internal pure returns (uint48) { + if (value > type(uint48).max) { + revert SafeCastOverflowedUintDowncast(48, value); + } + return uint48(value); + } + + /** + * @dev Returns the downcasted uint40 from uint256, reverting on + * overflow (when the input is greater than largest uint40). + * + * Counterpart to Solidity's `uint40` operator. + * + * Requirements: + * + * - input must fit into 40 bits + */ + function toUint40(uint256 value) internal pure returns (uint40) { + if (value > type(uint40).max) { + revert SafeCastOverflowedUintDowncast(40, value); + } + return uint40(value); + } + + /** + * @dev Returns the downcasted uint32 from uint256, reverting on + * overflow (when the input is greater than largest uint32). + * + * Counterpart to Solidity's `uint32` operator. + * + * Requirements: + * + * - input must fit into 32 bits + */ + function toUint32(uint256 value) internal pure returns (uint32) { + if (value > type(uint32).max) { + revert SafeCastOverflowedUintDowncast(32, value); + } + return uint32(value); + } + + /** + * @dev Returns the downcasted uint24 from uint256, reverting on + * overflow (when the input is greater than largest uint24). + * + * Counterpart to Solidity's `uint24` operator. + * + * Requirements: + * + * - input must fit into 24 bits + */ + function toUint24(uint256 value) internal pure returns (uint24) { + if (value > type(uint24).max) { + revert SafeCastOverflowedUintDowncast(24, value); + } + return uint24(value); + } + + /** + * @dev Returns the downcasted uint16 from uint256, reverting on + * overflow (when the input is greater than largest uint16). + * + * Counterpart to Solidity's `uint16` operator. + * + * Requirements: + * + * - input must fit into 16 bits + */ + function toUint16(uint256 value) internal pure returns (uint16) { + if (value > type(uint16).max) { + revert SafeCastOverflowedUintDowncast(16, value); + } + return uint16(value); + } + + /** + * @dev Returns the downcasted uint8 from uint256, reverting on + * overflow (when the input is greater than largest uint8). + * + * Counterpart to Solidity's `uint8` operator. + * + * Requirements: + * + * - input must fit into 8 bits + */ + function toUint8(uint256 value) internal pure returns (uint8) { + if (value > type(uint8).max) { + revert SafeCastOverflowedUintDowncast(8, value); + } + return uint8(value); + } + + /** + * @dev Converts a signed int256 into an unsigned uint256. + * + * Requirements: + * + * - input must be greater than or equal to 0. + */ + function toUint256(int256 value) internal pure returns (uint256) { + if (value < 0) { + revert SafeCastOverflowedIntToUint(value); + } + return uint256(value); + } + + /** + * @dev Returns the downcasted int248 from int256, reverting on + * overflow (when the input is less than smallest int248 or + * greater than largest int248). + * + * Counterpart to Solidity's `int248` operator. + * + * Requirements: + * + * - input must fit into 248 bits + */ + function toInt248(int256 value) internal pure returns (int248 downcasted) { + downcasted = int248(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(248, value); + } + } + + /** + * @dev Returns the downcasted int240 from int256, reverting on + * overflow (when the input is less than smallest int240 or + * greater than largest int240). + * + * Counterpart to Solidity's `int240` operator. + * + * Requirements: + * + * - input must fit into 240 bits + */ + function toInt240(int256 value) internal pure returns (int240 downcasted) { + downcasted = int240(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(240, value); + } + } + + /** + * @dev Returns the downcasted int232 from int256, reverting on + * overflow (when the input is less than smallest int232 or + * greater than largest int232). + * + * Counterpart to Solidity's `int232` operator. + * + * Requirements: + * + * - input must fit into 232 bits + */ + function toInt232(int256 value) internal pure returns (int232 downcasted) { + downcasted = int232(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(232, value); + } + } + + /** + * @dev Returns the downcasted int224 from int256, reverting on + * overflow (when the input is less than smallest int224 or + * greater than largest int224). + * + * Counterpart to Solidity's `int224` operator. + * + * Requirements: + * + * - input must fit into 224 bits + */ + function toInt224(int256 value) internal pure returns (int224 downcasted) { + downcasted = int224(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(224, value); + } + } + + /** + * @dev Returns the downcasted int216 from int256, reverting on + * overflow (when the input is less than smallest int216 or + * greater than largest int216). + * + * Counterpart to Solidity's `int216` operator. + * + * Requirements: + * + * - input must fit into 216 bits + */ + function toInt216(int256 value) internal pure returns (int216 downcasted) { + downcasted = int216(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(216, value); + } + } + + /** + * @dev Returns the downcasted int208 from int256, reverting on + * overflow (when the input is less than smallest int208 or + * greater than largest int208). + * + * Counterpart to Solidity's `int208` operator. + * + * Requirements: + * + * - input must fit into 208 bits + */ + function toInt208(int256 value) internal pure returns (int208 downcasted) { + downcasted = int208(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(208, value); + } + } + + /** + * @dev Returns the downcasted int200 from int256, reverting on + * overflow (when the input is less than smallest int200 or + * greater than largest int200). + * + * Counterpart to Solidity's `int200` operator. + * + * Requirements: + * + * - input must fit into 200 bits + */ + function toInt200(int256 value) internal pure returns (int200 downcasted) { + downcasted = int200(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(200, value); + } + } + + /** + * @dev Returns the downcasted int192 from int256, reverting on + * overflow (when the input is less than smallest int192 or + * greater than largest int192). + * + * Counterpart to Solidity's `int192` operator. + * + * Requirements: + * + * - input must fit into 192 bits + */ + function toInt192(int256 value) internal pure returns (int192 downcasted) { + downcasted = int192(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(192, value); + } + } + + /** + * @dev Returns the downcasted int184 from int256, reverting on + * overflow (when the input is less than smallest int184 or + * greater than largest int184). + * + * Counterpart to Solidity's `int184` operator. + * + * Requirements: + * + * - input must fit into 184 bits + */ + function toInt184(int256 value) internal pure returns (int184 downcasted) { + downcasted = int184(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(184, value); + } + } + + /** + * @dev Returns the downcasted int176 from int256, reverting on + * overflow (when the input is less than smallest int176 or + * greater than largest int176). + * + * Counterpart to Solidity's `int176` operator. + * + * Requirements: + * + * - input must fit into 176 bits + */ + function toInt176(int256 value) internal pure returns (int176 downcasted) { + downcasted = int176(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(176, value); + } + } + + /** + * @dev Returns the downcasted int168 from int256, reverting on + * overflow (when the input is less than smallest int168 or + * greater than largest int168). + * + * Counterpart to Solidity's `int168` operator. + * + * Requirements: + * + * - input must fit into 168 bits + */ + function toInt168(int256 value) internal pure returns (int168 downcasted) { + downcasted = int168(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(168, value); + } + } + + /** + * @dev Returns the downcasted int160 from int256, reverting on + * overflow (when the input is less than smallest int160 or + * greater than largest int160). + * + * Counterpart to Solidity's `int160` operator. + * + * Requirements: + * + * - input must fit into 160 bits + */ + function toInt160(int256 value) internal pure returns (int160 downcasted) { + downcasted = int160(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(160, value); + } + } + + /** + * @dev Returns the downcasted int152 from int256, reverting on + * overflow (when the input is less than smallest int152 or + * greater than largest int152). + * + * Counterpart to Solidity's `int152` operator. + * + * Requirements: + * + * - input must fit into 152 bits + */ + function toInt152(int256 value) internal pure returns (int152 downcasted) { + downcasted = int152(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(152, value); + } + } + + /** + * @dev Returns the downcasted int144 from int256, reverting on + * overflow (when the input is less than smallest int144 or + * greater than largest int144). + * + * Counterpart to Solidity's `int144` operator. + * + * Requirements: + * + * - input must fit into 144 bits + */ + function toInt144(int256 value) internal pure returns (int144 downcasted) { + downcasted = int144(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(144, value); + } + } + + /** + * @dev Returns the downcasted int136 from int256, reverting on + * overflow (when the input is less than smallest int136 or + * greater than largest int136). + * + * Counterpart to Solidity's `int136` operator. + * + * Requirements: + * + * - input must fit into 136 bits + */ + function toInt136(int256 value) internal pure returns (int136 downcasted) { + downcasted = int136(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(136, value); + } + } + + /** + * @dev Returns the downcasted int128 from int256, reverting on + * overflow (when the input is less than smallest int128 or + * greater than largest int128). + * + * Counterpart to Solidity's `int128` operator. + * + * Requirements: + * + * - input must fit into 128 bits + */ + function toInt128(int256 value) internal pure returns (int128 downcasted) { + downcasted = int128(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(128, value); + } + } + + /** + * @dev Returns the downcasted int120 from int256, reverting on + * overflow (when the input is less than smallest int120 or + * greater than largest int120). + * + * Counterpart to Solidity's `int120` operator. + * + * Requirements: + * + * - input must fit into 120 bits + */ + function toInt120(int256 value) internal pure returns (int120 downcasted) { + downcasted = int120(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(120, value); + } + } + + /** + * @dev Returns the downcasted int112 from int256, reverting on + * overflow (when the input is less than smallest int112 or + * greater than largest int112). + * + * Counterpart to Solidity's `int112` operator. + * + * Requirements: + * + * - input must fit into 112 bits + */ + function toInt112(int256 value) internal pure returns (int112 downcasted) { + downcasted = int112(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(112, value); + } + } + + /** + * @dev Returns the downcasted int104 from int256, reverting on + * overflow (when the input is less than smallest int104 or + * greater than largest int104). + * + * Counterpart to Solidity's `int104` operator. + * + * Requirements: + * + * - input must fit into 104 bits + */ + function toInt104(int256 value) internal pure returns (int104 downcasted) { + downcasted = int104(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(104, value); + } + } + + /** + * @dev Returns the downcasted int96 from int256, reverting on + * overflow (when the input is less than smallest int96 or + * greater than largest int96). + * + * Counterpart to Solidity's `int96` operator. + * + * Requirements: + * + * - input must fit into 96 bits + */ + function toInt96(int256 value) internal pure returns (int96 downcasted) { + downcasted = int96(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(96, value); + } + } + + /** + * @dev Returns the downcasted int88 from int256, reverting on + * overflow (when the input is less than smallest int88 or + * greater than largest int88). + * + * Counterpart to Solidity's `int88` operator. + * + * Requirements: + * + * - input must fit into 88 bits + */ + function toInt88(int256 value) internal pure returns (int88 downcasted) { + downcasted = int88(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(88, value); + } + } + + /** + * @dev Returns the downcasted int80 from int256, reverting on + * overflow (when the input is less than smallest int80 or + * greater than largest int80). + * + * Counterpart to Solidity's `int80` operator. + * + * Requirements: + * + * - input must fit into 80 bits + */ + function toInt80(int256 value) internal pure returns (int80 downcasted) { + downcasted = int80(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(80, value); + } + } + + /** + * @dev Returns the downcasted int72 from int256, reverting on + * overflow (when the input is less than smallest int72 or + * greater than largest int72). + * + * Counterpart to Solidity's `int72` operator. + * + * Requirements: + * + * - input must fit into 72 bits + */ + function toInt72(int256 value) internal pure returns (int72 downcasted) { + downcasted = int72(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(72, value); + } + } + + /** + * @dev Returns the downcasted int64 from int256, reverting on + * overflow (when the input is less than smallest int64 or + * greater than largest int64). + * + * Counterpart to Solidity's `int64` operator. + * + * Requirements: + * + * - input must fit into 64 bits + */ + function toInt64(int256 value) internal pure returns (int64 downcasted) { + downcasted = int64(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(64, value); + } + } + + /** + * @dev Returns the downcasted int56 from int256, reverting on + * overflow (when the input is less than smallest int56 or + * greater than largest int56). + * + * Counterpart to Solidity's `int56` operator. + * + * Requirements: + * + * - input must fit into 56 bits + */ + function toInt56(int256 value) internal pure returns (int56 downcasted) { + downcasted = int56(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(56, value); + } + } + + /** + * @dev Returns the downcasted int48 from int256, reverting on + * overflow (when the input is less than smallest int48 or + * greater than largest int48). + * + * Counterpart to Solidity's `int48` operator. + * + * Requirements: + * + * - input must fit into 48 bits + */ + function toInt48(int256 value) internal pure returns (int48 downcasted) { + downcasted = int48(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(48, value); + } + } + + /** + * @dev Returns the downcasted int40 from int256, reverting on + * overflow (when the input is less than smallest int40 or + * greater than largest int40). + * + * Counterpart to Solidity's `int40` operator. + * + * Requirements: + * + * - input must fit into 40 bits + */ + function toInt40(int256 value) internal pure returns (int40 downcasted) { + downcasted = int40(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(40, value); + } + } + + /** + * @dev Returns the downcasted int32 from int256, reverting on + * overflow (when the input is less than smallest int32 or + * greater than largest int32). + * + * Counterpart to Solidity's `int32` operator. + * + * Requirements: + * + * - input must fit into 32 bits + */ + function toInt32(int256 value) internal pure returns (int32 downcasted) { + downcasted = int32(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(32, value); + } + } + + /** + * @dev Returns the downcasted int24 from int256, reverting on + * overflow (when the input is less than smallest int24 or + * greater than largest int24). + * + * Counterpart to Solidity's `int24` operator. + * + * Requirements: + * + * - input must fit into 24 bits + */ + function toInt24(int256 value) internal pure returns (int24 downcasted) { + downcasted = int24(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(24, value); + } + } + + /** + * @dev Returns the downcasted int16 from int256, reverting on + * overflow (when the input is less than smallest int16 or + * greater than largest int16). + * + * Counterpart to Solidity's `int16` operator. + * + * Requirements: + * + * - input must fit into 16 bits + */ + function toInt16(int256 value) internal pure returns (int16 downcasted) { + downcasted = int16(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(16, value); + } + } + + /** + * @dev Returns the downcasted int8 from int256, reverting on + * overflow (when the input is less than smallest int8 or + * greater than largest int8). + * + * Counterpart to Solidity's `int8` operator. + * + * Requirements: + * + * - input must fit into 8 bits + */ + function toInt8(int256 value) internal pure returns (int8 downcasted) { + downcasted = int8(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(8, value); + } + } + + /** + * @dev Converts an unsigned uint256 into a signed int256. + * + * Requirements: + * + * - input must be less than or equal to maxInt256. + */ + function toInt256(uint256 value) internal pure returns (int256) { + // Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive + if (value > uint256(type(int256).max)) { + revert SafeCastOverflowedUintToInt(value); + } + return int256(value); + } +} diff --git a/contracts/utils/math/SignedMath.sol b/contracts/utils/math/SignedMath.sol new file mode 100644 index 000000000..776b04527 --- /dev/null +++ b/contracts/utils/math/SignedMath.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (utils/math/SignedMath.sol) + +pragma solidity ^0.8.20; + +/** + * @dev Standard signed math utilities missing in the Solidity language. + */ +library SignedMath { + /** + * @dev Returns the largest of two signed numbers. + */ + function max(int256 a, int256 b) internal pure returns (int256) { + return a > b ? a : b; + } + + /** + * @dev Returns the smallest of two signed numbers. + */ + function min(int256 a, int256 b) internal pure returns (int256) { + return a < b ? a : b; + } + + /** + * @dev Returns the average of two signed numbers without overflow. + * The result is rounded towards zero. + */ + function average(int256 a, int256 b) internal pure returns (int256) { + // Formula from the book "Hacker's Delight" + int256 x = (a & b) + ((a ^ b) >> 1); + return x + (int256(uint256(x) >> 255) & (a ^ b)); + } + + /** + * @dev Returns the absolute unsigned value of a signed value. + */ + function abs(int256 n) internal pure returns (uint256) { + unchecked { + // must be unchecked in order to support `n = type(int256).min` + return uint256(n >= 0 ? n : -n); + } + } +} diff --git a/contracts/utils/structs/BitMaps.sol b/contracts/utils/structs/BitMaps.sol new file mode 100644 index 000000000..086653efb --- /dev/null +++ b/contracts/utils/structs/BitMaps.sol @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (utils/structs/BitMaps.sol) +pragma solidity ^0.8.20; + +/** + * @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 { + mapping(uint256 bucket => uint256) _data; + } + + /** + * @dev Returns whether the bit at `index` is set. + */ + function get(BitMap storage bitmap, uint256 index) internal view returns (bool) { + uint256 bucket = index >> 8; + uint256 mask = 1 << (index & 0xff); + return bitmap._data[bucket] & mask != 0; + } + + /** + * @dev Sets the bit at `index` to the boolean `value`. + */ + function setTo(BitMap storage bitmap, uint256 index, bool value) internal { + if (value) { + set(bitmap, index); + } else { + unset(bitmap, index); + } + } + + /** + * @dev Sets the bit at `index`. + */ + function set(BitMap storage bitmap, uint256 index) internal { + uint256 bucket = index >> 8; + uint256 mask = 1 << (index & 0xff); + bitmap._data[bucket] |= mask; + } + + /** + * @dev Unsets the bit at `index`. + */ + function unset(BitMap storage bitmap, uint256 index) internal { + uint256 bucket = index >> 8; + uint256 mask = 1 << (index & 0xff); + bitmap._data[bucket] &= ~mask; + } +} diff --git a/contracts/utils/structs/Checkpoints.sol b/contracts/utils/structs/Checkpoints.sol new file mode 100644 index 000000000..49d73ce21 --- /dev/null +++ b/contracts/utils/structs/Checkpoints.sol @@ -0,0 +1,603 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (utils/structs/Checkpoints.sol) +// This file was procedurally generated from scripts/generate/templates/Checkpoints.js. + +pragma solidity ^0.8.20; + +import {Math} from "../math/Math.sol"; + +/** + * @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.Trace*` in your contract, and store a new + * checkpoint for the current transaction block using the {push} function. + */ +library Checkpoints { + /** + * @dev A value was attempted to be inserted on a past checkpoint. + */ + error CheckpointUnorderedInsertion(); + + struct Trace224 { + Checkpoint224[] _checkpoints; + } + + struct Checkpoint224 { + uint32 _key; + uint224 _value; + } + + /** + * @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); + } + + /** + * @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; + uint256 pos = _lowerBinaryLookup(self._checkpoints, key, 0, len); + return pos == len ? 0 : _unsafeAccess(self._checkpoints, pos)._value; + } + + /** + * @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; + uint256 pos = _upperBinaryLookup(self._checkpoints, key, 0, len); + return pos == 0 ? 0 : _unsafeAccess(self._checkpoints, pos - 1)._value; + } + + /** + * @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). + */ + function upperLookupRecent(Trace224 storage self, uint32 key) internal view returns (uint224) { + 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)._key) { + 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 Returns the value in the most recent checkpoint, or zero if there are no checkpoints. + */ + function latest(Trace224 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(Trace224 storage self) internal view returns (bool exists, uint32 _key, uint224 _value) { + uint256 pos = self._checkpoints.length; + if (pos == 0) { + return (false, 0, 0); + } else { + Checkpoint224 memory ckpt = _unsafeAccess(self._checkpoints, pos - 1); + return (true, ckpt._key, ckpt._value); + } + } + + /** + * @dev Returns the number of checkpoint. + */ + function length(Trace224 storage self) internal view returns (uint256) { + return self._checkpoints.length; + } + + /** + * @dev Returns checkpoint at given position. + */ + function at(Trace224 storage self, uint32 pos) internal view returns (Checkpoint224 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(Checkpoint224[] storage self, uint32 key, uint224 value) private returns (uint224, uint224) { + uint256 pos = self.length; + + if (pos > 0) { + // Copying to memory is important here. + Checkpoint224 memory last = _unsafeAccess(self, pos - 1); + + // Checkpoint keys must be non-decreasing. + if (last._key > key) { + revert CheckpointUnorderedInsertion(); + } + + // Update or push new checkpoint + if (last._key == key) { + _unsafeAccess(self, pos - 1)._value = value; + } else { + self.push(Checkpoint224({_key: key, _value: value})); + } + return (last._value, value); + } else { + self.push(Checkpoint224({_key: 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( + Checkpoint224[] 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)._key > 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( + Checkpoint224[] 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)._key < 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( + Checkpoint224[] storage self, + uint256 pos + ) private pure returns (Checkpoint224 storage result) { + assembly { + mstore(0, self.slot) + result.slot := add(keccak256(0, 0x20), pos) + } + } + + struct Trace208 { + Checkpoint208[] _checkpoints; + } + + struct Checkpoint208 { + uint48 _key; + uint208 _value; + } + + /** + * @dev Pushes a (`key`, `value`) pair into a Trace208 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(uint48).max` key set will disable the + * library. + */ + function push(Trace208 storage self, uint48 key, uint208 value) internal returns (uint208, uint208) { + return _insert(self._checkpoints, key, value); + } + + /** + * @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(Trace208 storage self, uint48 key) internal view returns (uint208) { + uint256 len = self._checkpoints.length; + uint256 pos = _lowerBinaryLookup(self._checkpoints, key, 0, len); + return pos == len ? 0 : _unsafeAccess(self._checkpoints, pos)._value; + } + + /** + * @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(Trace208 storage self, uint48 key) internal view returns (uint208) { + 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 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). + */ + function upperLookupRecent(Trace208 storage self, uint48 key) internal view returns (uint208) { + 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)._key) { + 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 Returns the value in the most recent checkpoint, or zero if there are no checkpoints. + */ + function latest(Trace208 storage self) internal view returns (uint208) { + 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(Trace208 storage self) internal view returns (bool exists, uint48 _key, uint208 _value) { + uint256 pos = self._checkpoints.length; + if (pos == 0) { + return (false, 0, 0); + } else { + Checkpoint208 memory ckpt = _unsafeAccess(self._checkpoints, pos - 1); + return (true, ckpt._key, ckpt._value); + } + } + + /** + * @dev Returns the number of checkpoint. + */ + function length(Trace208 storage self) internal view returns (uint256) { + return self._checkpoints.length; + } + + /** + * @dev Returns checkpoint at given position. + */ + function at(Trace208 storage self, uint32 pos) internal view returns (Checkpoint208 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(Checkpoint208[] storage self, uint48 key, uint208 value) private returns (uint208, uint208) { + uint256 pos = self.length; + + if (pos > 0) { + // Copying to memory is important here. + Checkpoint208 memory last = _unsafeAccess(self, pos - 1); + + // Checkpoint keys must be non-decreasing. + if (last._key > key) { + revert CheckpointUnorderedInsertion(); + } + + // Update or push new checkpoint + if (last._key == key) { + _unsafeAccess(self, pos - 1)._value = value; + } else { + self.push(Checkpoint208({_key: key, _value: value})); + } + return (last._value, value); + } else { + self.push(Checkpoint208({_key: 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( + Checkpoint208[] storage self, + uint48 key, + uint256 low, + uint256 high + ) private view returns (uint256) { + while (low < high) { + uint256 mid = Math.average(low, high); + if (_unsafeAccess(self, mid)._key > 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( + Checkpoint208[] storage self, + uint48 key, + uint256 low, + uint256 high + ) private view returns (uint256) { + while (low < high) { + uint256 mid = Math.average(low, high); + if (_unsafeAccess(self, mid)._key < 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( + Checkpoint208[] storage self, + uint256 pos + ) private pure returns (Checkpoint208 storage result) { + assembly { + mstore(0, self.slot) + result.slot := add(keccak256(0, 0x20), pos) + } + } + + struct Trace160 { + Checkpoint160[] _checkpoints; + } + + struct Checkpoint160 { + uint96 _key; + uint160 _value; + } + + /** + * @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); + } + + /** + * @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; + uint256 pos = _lowerBinaryLookup(self._checkpoints, key, 0, len); + return pos == len ? 0 : _unsafeAccess(self._checkpoints, pos)._value; + } + + /** + * @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; + uint256 pos = _upperBinaryLookup(self._checkpoints, key, 0, len); + return pos == 0 ? 0 : _unsafeAccess(self._checkpoints, pos - 1)._value; + } + + /** + * @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). + */ + function upperLookupRecent(Trace160 storage self, uint96 key) internal view returns (uint160) { + 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)._key) { + 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 Returns the value in the most recent checkpoint, or zero if there are no checkpoints. + */ + function latest(Trace160 storage self) internal view returns (uint160) { + 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(Trace160 storage self) internal view returns (bool exists, uint96 _key, uint160 _value) { + uint256 pos = self._checkpoints.length; + if (pos == 0) { + return (false, 0, 0); + } else { + Checkpoint160 memory ckpt = _unsafeAccess(self._checkpoints, pos - 1); + return (true, ckpt._key, ckpt._value); + } + } + + /** + * @dev Returns the number of checkpoint. + */ + function length(Trace160 storage self) internal view returns (uint256) { + return self._checkpoints.length; + } + + /** + * @dev Returns checkpoint at given position. + */ + function at(Trace160 storage self, uint32 pos) internal view returns (Checkpoint160 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(Checkpoint160[] storage self, uint96 key, uint160 value) private returns (uint160, uint160) { + uint256 pos = self.length; + + if (pos > 0) { + // Copying to memory is important here. + Checkpoint160 memory last = _unsafeAccess(self, pos - 1); + + // Checkpoint keys must be non-decreasing. + if (last._key > key) { + revert CheckpointUnorderedInsertion(); + } + + // Update or push new checkpoint + if (last._key == key) { + _unsafeAccess(self, pos - 1)._value = value; + } else { + self.push(Checkpoint160({_key: key, _value: value})); + } + return (last._value, value); + } else { + self.push(Checkpoint160({_key: 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( + Checkpoint160[] storage self, + uint96 key, + uint256 low, + uint256 high + ) private view returns (uint256) { + while (low < high) { + uint256 mid = Math.average(low, high); + if (_unsafeAccess(self, mid)._key > 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( + Checkpoint160[] storage self, + uint96 key, + uint256 low, + uint256 high + ) private view returns (uint256) { + while (low < high) { + uint256 mid = Math.average(low, high); + if (_unsafeAccess(self, mid)._key < 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( + 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/structs/DoubleEndedQueue.sol b/contracts/utils/structs/DoubleEndedQueue.sol new file mode 100644 index 000000000..1050675ac --- /dev/null +++ b/contracts/utils/structs/DoubleEndedQueue.sol @@ -0,0 +1,169 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (utils/structs/DoubleEndedQueue.sol) +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 + * the sequence (called front and back). Among other access patterns, it can be used to implement efficient LIFO and + * FIFO queues. Storage use is optimized, and all operations are O(1) constant time. This includes {clear}, given that + * the existing queue contents are left in storage. + * + * The struct is called `Bytes32Deque`. Other types can be cast to and from `bytes32`. This data structure can only be + * used in storage, and not in memory. + * ```solidity + * DoubleEndedQueue.Bytes32Deque queue; + * ``` + */ +library DoubleEndedQueue { + /** + * @dev An operation (e.g. {front}) couldn't be completed due to the queue being empty. + */ + 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. + */ + error QueueOutOfBounds(); + + /** + * @dev Indices are 128 bits so begin and end are packed in a single storage slot for efficient access. + * + * Struct members have an underscore prefix indicating that they are "private" and should not be read or written to + * directly. Use the functions provided below instead. Modifying the struct manually may violate assumptions and + * lead to unexpected behavior. + * + * The first item is at data[begin] and the last item is at data[end - 1]. This range can wrap around. + */ + struct Bytes32Deque { + uint128 _begin; + uint128 _end; + mapping(uint128 index => bytes32) _data; + } + + /** + * @dev Inserts an item at the end of the queue. + * + * Reverts with {QueueFull} if the queue is full. + */ + function pushBack(Bytes32Deque storage deque, bytes32 value) internal { + unchecked { + uint128 backIndex = deque._end; + if (backIndex + 1 == deque._begin) revert QueueFull(); + deque._data[backIndex] = value; + deque._end = backIndex + 1; + } + } + + /** + * @dev Removes the item at the end of the queue and returns it. + * + * Reverts with {QueueEmpty} if the queue is empty. + */ + function popBack(Bytes32Deque storage deque) internal returns (bytes32 value) { + unchecked { + uint128 backIndex = deque._end; + if (backIndex == deque._begin) revert QueueEmpty(); + --backIndex; + value = deque._data[backIndex]; + delete deque._data[backIndex]; + deque._end = backIndex; + } + } + + /** + * @dev Inserts an item at the beginning of the queue. + * + * Reverts with {QueueFull} if the queue is full. + */ + function pushFront(Bytes32Deque storage deque, bytes32 value) internal { + unchecked { + uint128 frontIndex = deque._begin - 1; + if (frontIndex == deque._end) revert QueueFull(); + deque._data[frontIndex] = value; + deque._begin = frontIndex; + } + } + + /** + * @dev Removes the item at the beginning of the queue and returns it. + * + * Reverts with `QueueEmpty` if the queue is empty. + */ + function popFront(Bytes32Deque storage deque) internal returns (bytes32 value) { + unchecked { + uint128 frontIndex = deque._begin; + if (frontIndex == deque._end) revert QueueEmpty(); + value = deque._data[frontIndex]; + delete deque._data[frontIndex]; + deque._begin = frontIndex + 1; + } + } + + /** + * @dev Returns the item at the beginning of the queue. + * + * Reverts with `QueueEmpty` if the queue is empty. + */ + function front(Bytes32Deque storage deque) internal view returns (bytes32 value) { + if (empty(deque)) revert QueueEmpty(); + return deque._data[deque._begin]; + } + + /** + * @dev Returns the item at the end of the queue. + * + * Reverts with `QueueEmpty` if the queue is empty. + */ + function back(Bytes32Deque storage deque) internal view returns (bytes32 value) { + if (empty(deque)) revert QueueEmpty(); + unchecked { + return deque._data[deque._end - 1]; + } + } + + /** + * @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 `QueueOutOfBounds` if the index is out of bounds. + */ + function at(Bytes32Deque storage deque, uint256 index) internal view returns (bytes32 value) { + 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)]; + } + } + + /** + * @dev Resets the queue back to being empty. + * + * NOTE: The current items are left behind in storage. This does not affect the functioning of the queue, but misses + * out on potential gas refunds. + */ + function clear(Bytes32Deque storage deque) internal { + deque._begin = 0; + deque._end = 0; + } + + /** + * @dev Returns the number of items in the queue. + */ + function length(Bytes32Deque storage deque) internal view returns (uint256) { + unchecked { + return uint256(deque._end - deque._begin); + } + } + + /** + * @dev Returns true if the queue is empty. + */ + function empty(Bytes32Deque storage deque) internal view returns (bool) { + return deque._end == deque._begin; + } +} diff --git a/contracts/utils/structs/EnumerableMap.sol b/contracts/utils/structs/EnumerableMap.sol new file mode 100644 index 000000000..7736f5575 --- /dev/null +++ b/contracts/utils/structs/EnumerableMap.sol @@ -0,0 +1,533 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (utils/structs/EnumerableMap.sol) +// This file was procedurally generated from scripts/generate/templates/EnumerableMap.js. + +pragma solidity ^0.8.20; + +import {EnumerableSet} from "./EnumerableSet.sol"; + +/** + * @dev Library for managing an enumerable variant of Solidity's + * https://solidity.readthedocs.io/en/latest/types.html#mapping-types[`mapping`] + * type. + * + * Maps have the following properties: + * + * - Entries are added, removed, and checked for existence in constant time + * (O(1)). + * - Entries are enumerated in O(n). No guarantees are made on the ordering. + * + * ```solidity + * contract Example { + * // Add the library methods + * using EnumerableMap for EnumerableMap.UintToAddressMap; + * + * // Declare a set state variable + * EnumerableMap.UintToAddressMap private myMap; + * } + * ``` + * + * The following map types are supported: + * + * - `uint256 -> address` (`UintToAddressMap`) since v3.0.0 + * - `address -> uint256` (`AddressToUintMap`) since v4.6.0 + * - `bytes32 -> bytes32` (`Bytes32ToBytes32Map`) since v4.6.0 + * - `uint256 -> uint256` (`UintToUintMap`) since v4.7.0 + * - `bytes32 -> uint256` (`Bytes32ToUintMap`) since v4.7.0 + * + * [WARNING] + * ==== + * Trying to delete such a structure from storage will likely result in data corruption, rendering the structure + * unusable. + * See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info. + * + * In order to clean an EnumerableMap, you can either remove all elements one by one or create a fresh instance using an + * array of EnumerableMap. + * ==== + */ +library EnumerableMap { + using EnumerableSet for EnumerableSet.Bytes32Set; + + // To implement this library for multiple types with as little code repetition as possible, we write it in + // terms of a generic Map type with bytes32 keys and values. The Map implementation uses private functions, + // and user-facing implementations such as `UintToAddressMap` are just wrappers around the underlying Map. + // 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; + mapping(bytes32 key => bytes32) _values; + } + + /** + * @dev Adds a key-value pair to a map, or updates the value for an existing + * key. O(1). + * + * 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) { + map._values[key] = value; + return map._keys.add(key); + } + + /** + * @dev Removes a key-value pair from a map. O(1). + * + * Returns true if the key was removed from the map, that is if it was present. + */ + function remove(Bytes32ToBytes32Map storage map, bytes32 key) internal returns (bool) { + delete map._values[key]; + return map._keys.remove(key); + } + + /** + * @dev Returns true if the key is in the map. O(1). + */ + function contains(Bytes32ToBytes32Map storage map, bytes32 key) internal view returns (bool) { + return map._keys.contains(key); + } + + /** + * @dev Returns the number of key-value pairs in the map. O(1). + */ + function length(Bytes32ToBytes32Map storage map) internal view returns (uint256) { + return map._keys.length(); + } + + /** + * @dev Returns the key-value pair stored at position `index` in the map. O(1). + * + * Note that there are no guarantees on the ordering of entries inside the + * array, and it may change when more entries are added or removed. + * + * Requirements: + * + * - `index` must be strictly less than {length}. + */ + function at(Bytes32ToBytes32Map storage map, uint256 index) internal view returns (bytes32, bytes32) { + bytes32 key = map._keys.at(index); + return (key, map._values[key]); + } + + /** + * @dev Tries to returns the value associated with `key`. O(1). + * Does not revert if `key` is not in the map. + */ + function tryGet(Bytes32ToBytes32Map storage map, bytes32 key) internal view returns (bool, bytes32) { + bytes32 value = map._values[key]; + if (value == bytes32(0)) { + return (contains(map, key), bytes32(0)); + } else { + return (true, value); + } + } + + /** + * @dev Returns the value associated with `key`. O(1). + * + * Requirements: + * + * - `key` must be in the map. + */ + function get(Bytes32ToBytes32Map storage map, bytes32 key) internal view returns (bytes32) { + bytes32 value = map._values[key]; + if (value == 0 && !contains(map, key)) { + revert EnumerableMapNonexistentKey(key); + } + return value; + } + + /** + * @dev Return the an array containing all the keys + * + * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed + * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that + * this function has an unbounded cost, and using it as part of a state-changing function may render the function + * uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. + */ + function keys(Bytes32ToBytes32Map storage map) internal view returns (bytes32[] memory) { + return map._keys.values(); + } + + // UintToUintMap + + struct UintToUintMap { + Bytes32ToBytes32Map _inner; + } + + /** + * @dev Adds a key-value pair to a map, or updates the value for an existing + * key. O(1). + * + * 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) { + return set(map._inner, bytes32(key), bytes32(value)); + } + + /** + * @dev Removes a value from a map. O(1). + * + * Returns true if the key was removed from the map, that is if it was present. + */ + function remove(UintToUintMap storage map, uint256 key) internal returns (bool) { + return remove(map._inner, bytes32(key)); + } + + /** + * @dev Returns true if the key is in the map. O(1). + */ + function contains(UintToUintMap storage map, uint256 key) internal view returns (bool) { + return contains(map._inner, bytes32(key)); + } + + /** + * @dev Returns the number of elements in the map. O(1). + */ + function length(UintToUintMap storage map) internal view returns (uint256) { + return length(map._inner); + } + + /** + * @dev Returns the element stored at position `index` in the map. O(1). + * Note that there are no guarantees on the ordering of values inside the + * array, and it may change when more values are added or removed. + * + * Requirements: + * + * - `index` must be strictly less than {length}. + */ + function at(UintToUintMap storage map, uint256 index) internal view returns (uint256, uint256) { + (bytes32 key, bytes32 value) = at(map._inner, index); + return (uint256(key), uint256(value)); + } + + /** + * @dev Tries to returns the value associated with `key`. O(1). + * Does not revert if `key` is not in the map. + */ + function tryGet(UintToUintMap storage map, uint256 key) internal view returns (bool, uint256) { + (bool success, bytes32 value) = tryGet(map._inner, bytes32(key)); + return (success, uint256(value)); + } + + /** + * @dev Returns the value associated with `key`. O(1). + * + * Requirements: + * + * - `key` must be in the map. + */ + function get(UintToUintMap storage map, uint256 key) internal view returns (uint256) { + return uint256(get(map._inner, bytes32(key))); + } + + /** + * @dev Return the an array containing all the keys + * + * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed + * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that + * this function has an unbounded cost, and using it as part of a state-changing function may render the function + * uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. + */ + function keys(UintToUintMap storage map) internal view returns (uint256[] memory) { + bytes32[] memory store = keys(map._inner); + uint256[] memory result; + + /// @solidity memory-safe-assembly + assembly { + result := store + } + + return result; + } + + // UintToAddressMap + + struct UintToAddressMap { + Bytes32ToBytes32Map _inner; + } + + /** + * @dev Adds a key-value pair to a map, or updates the value for an existing + * key. O(1). + * + * 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) { + return set(map._inner, bytes32(key), bytes32(uint256(uint160(value)))); + } + + /** + * @dev Removes a value from a map. O(1). + * + * Returns true if the key was removed from the map, that is if it was present. + */ + function remove(UintToAddressMap storage map, uint256 key) internal returns (bool) { + return remove(map._inner, bytes32(key)); + } + + /** + * @dev Returns true if the key is in the map. O(1). + */ + function contains(UintToAddressMap storage map, uint256 key) internal view returns (bool) { + return contains(map._inner, bytes32(key)); + } + + /** + * @dev Returns the number of elements in the map. O(1). + */ + function length(UintToAddressMap storage map) internal view returns (uint256) { + return length(map._inner); + } + + /** + * @dev Returns the element stored at position `index` in the map. O(1). + * Note that there are no guarantees on the ordering of values inside the + * array, and it may change when more values are added or removed. + * + * Requirements: + * + * - `index` must be strictly less than {length}. + */ + function at(UintToAddressMap storage map, uint256 index) internal view returns (uint256, address) { + (bytes32 key, bytes32 value) = at(map._inner, index); + return (uint256(key), address(uint160(uint256(value)))); + } + + /** + * @dev Tries to returns the value associated with `key`. O(1). + * Does not revert if `key` is not in the map. + */ + function tryGet(UintToAddressMap storage map, uint256 key) internal view returns (bool, address) { + (bool success, bytes32 value) = tryGet(map._inner, bytes32(key)); + return (success, address(uint160(uint256(value)))); + } + + /** + * @dev Returns the value associated with `key`. O(1). + * + * Requirements: + * + * - `key` must be in the map. + */ + function get(UintToAddressMap storage map, uint256 key) internal view returns (address) { + return address(uint160(uint256(get(map._inner, bytes32(key))))); + } + + /** + * @dev Return the an array containing all the keys + * + * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed + * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that + * this function has an unbounded cost, and using it as part of a state-changing function may render the function + * uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. + */ + function keys(UintToAddressMap storage map) internal view returns (uint256[] memory) { + bytes32[] memory store = keys(map._inner); + uint256[] memory result; + + /// @solidity memory-safe-assembly + assembly { + result := store + } + + return result; + } + + // AddressToUintMap + + struct AddressToUintMap { + Bytes32ToBytes32Map _inner; + } + + /** + * @dev Adds a key-value pair to a map, or updates the value for an existing + * key. O(1). + * + * 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) { + return set(map._inner, bytes32(uint256(uint160(key))), bytes32(value)); + } + + /** + * @dev Removes a value from a map. O(1). + * + * Returns true if the key was removed from the map, that is if it was present. + */ + function remove(AddressToUintMap storage map, address key) internal returns (bool) { + return remove(map._inner, bytes32(uint256(uint160(key)))); + } + + /** + * @dev Returns true if the key is in the map. O(1). + */ + function contains(AddressToUintMap storage map, address key) internal view returns (bool) { + return contains(map._inner, bytes32(uint256(uint160(key)))); + } + + /** + * @dev Returns the number of elements in the map. O(1). + */ + function length(AddressToUintMap storage map) internal view returns (uint256) { + return length(map._inner); + } + + /** + * @dev Returns the element stored at position `index` in the map. O(1). + * Note that there are no guarantees on the ordering of values inside the + * array, and it may change when more values are added or removed. + * + * Requirements: + * + * - `index` must be strictly less than {length}. + */ + function at(AddressToUintMap storage map, uint256 index) internal view returns (address, uint256) { + (bytes32 key, bytes32 value) = at(map._inner, index); + return (address(uint160(uint256(key))), uint256(value)); + } + + /** + * @dev Tries to returns the value associated with `key`. O(1). + * Does not revert if `key` is not in the map. + */ + function tryGet(AddressToUintMap storage map, address key) internal view returns (bool, uint256) { + (bool success, bytes32 value) = tryGet(map._inner, bytes32(uint256(uint160(key)))); + return (success, uint256(value)); + } + + /** + * @dev Returns the value associated with `key`. O(1). + * + * Requirements: + * + * - `key` must be in the map. + */ + function get(AddressToUintMap storage map, address key) internal view returns (uint256) { + return uint256(get(map._inner, bytes32(uint256(uint160(key))))); + } + + /** + * @dev Return the an array containing all the keys + * + * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed + * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that + * this function has an unbounded cost, and using it as part of a state-changing function may render the function + * uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. + */ + function keys(AddressToUintMap storage map) internal view returns (address[] memory) { + bytes32[] memory store = keys(map._inner); + address[] memory result; + + /// @solidity memory-safe-assembly + assembly { + result := store + } + + return result; + } + + // Bytes32ToUintMap + + struct Bytes32ToUintMap { + Bytes32ToBytes32Map _inner; + } + + /** + * @dev Adds a key-value pair to a map, or updates the value for an existing + * key. O(1). + * + * 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) { + return set(map._inner, key, bytes32(value)); + } + + /** + * @dev Removes a value from a map. O(1). + * + * Returns true if the key was removed from the map, that is if it was present. + */ + function remove(Bytes32ToUintMap storage map, bytes32 key) internal returns (bool) { + return remove(map._inner, key); + } + + /** + * @dev Returns true if the key is in the map. O(1). + */ + function contains(Bytes32ToUintMap storage map, bytes32 key) internal view returns (bool) { + return contains(map._inner, key); + } + + /** + * @dev Returns the number of elements in the map. O(1). + */ + function length(Bytes32ToUintMap storage map) internal view returns (uint256) { + return length(map._inner); + } + + /** + * @dev Returns the element stored at position `index` in the map. O(1). + * Note that there are no guarantees on the ordering of values inside the + * array, and it may change when more values are added or removed. + * + * Requirements: + * + * - `index` must be strictly less than {length}. + */ + function at(Bytes32ToUintMap storage map, uint256 index) internal view returns (bytes32, uint256) { + (bytes32 key, bytes32 value) = at(map._inner, index); + return (key, uint256(value)); + } + + /** + * @dev Tries to returns the value associated with `key`. O(1). + * Does not revert if `key` is not in the map. + */ + function tryGet(Bytes32ToUintMap storage map, bytes32 key) internal view returns (bool, uint256) { + (bool success, bytes32 value) = tryGet(map._inner, key); + return (success, uint256(value)); + } + + /** + * @dev Returns the value associated with `key`. O(1). + * + * Requirements: + * + * - `key` must be in the map. + */ + function get(Bytes32ToUintMap storage map, bytes32 key) internal view returns (uint256) { + return uint256(get(map._inner, key)); + } + + /** + * @dev Return the an array containing all the keys + * + * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed + * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that + * this function has an unbounded cost, and using it as part of a state-changing function may render the function + * uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. + */ + function keys(Bytes32ToUintMap storage map) internal view returns (bytes32[] memory) { + bytes32[] memory store = keys(map._inner); + bytes32[] memory result; + + /// @solidity memory-safe-assembly + assembly { + result := store + } + + return result; + } +} diff --git a/contracts/utils/structs/EnumerableSet.sol b/contracts/utils/structs/EnumerableSet.sol new file mode 100644 index 000000000..7cdeb84d1 --- /dev/null +++ b/contracts/utils/structs/EnumerableSet.sol @@ -0,0 +1,378 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (utils/structs/EnumerableSet.sol) +// This file was procedurally generated from scripts/generate/templates/EnumerableSet.js. + +pragma solidity ^0.8.20; + +/** + * @dev Library for managing + * https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive + * types. + * + * Sets have the following properties: + * + * - Elements are added, removed, and checked for existence in constant time + * (O(1)). + * - Elements are enumerated in O(n). No guarantees are made on the ordering. + * + * ```solidity + * contract Example { + * // Add the library methods + * using EnumerableSet for EnumerableSet.AddressSet; + * + * // Declare a set state variable + * EnumerableSet.AddressSet private mySet; + * } + * ``` + * + * As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`) + * and `uint256` (`UintSet`) are supported. + * + * [WARNING] + * ==== + * Trying to delete such a structure from storage will likely result in data corruption, rendering the structure + * unusable. + * See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info. + * + * In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an + * array of EnumerableSet. + * ==== + */ +library EnumerableSet { + // To implement this library for multiple types with as little code + // repetition as possible, we write it in terms of a generic Set type with + // bytes32 values. + // The Set implementation uses private functions, and user-facing + // implementations (such as AddressSet) are just wrappers around the + // underlying Set. + // This means that we can only create new EnumerableSets for types that fit + // in bytes32. + + struct Set { + // Storage of set values + bytes32[] _values; + // Position is the index of the value in the `values` array plus 1. + // Position 0 is used to mean a value is not in the set. + mapping(bytes32 value => uint256) _positions; + } + + /** + * @dev Add a value to a set. O(1). + * + * Returns true if the value was added to the set, that is if it was not + * already present. + */ + function _add(Set storage set, bytes32 value) private returns (bool) { + if (!_contains(set, value)) { + set._values.push(value); + // The value is stored at length-1, but we add 1 to all indexes + // and use 0 as a sentinel value + set._positions[value] = set._values.length; + return true; + } else { + return false; + } + } + + /** + * @dev Removes a value from a set. O(1). + * + * Returns true if the value was removed from the set, that is if it was + * present. + */ + function _remove(Set storage set, bytes32 value) private returns (bool) { + // We cache the value's position to prevent multiple reads from the same storage slot + uint256 position = set._positions[value]; + + if (position != 0) { + // Equivalent to contains(set, value) + // To delete an element from the _values array in O(1), we swap the element to delete with the last one in + // the array, and then remove the last element (sometimes called as 'swap and pop'). + // This modifies the order of the array, as noted in {at}. + + uint256 valueIndex = position - 1; + uint256 lastIndex = set._values.length - 1; + + if (valueIndex != lastIndex) { + bytes32 lastValue = set._values[lastIndex]; + + // Move the lastValue to the index where the value to delete is + set._values[valueIndex] = lastValue; + // Update the tracked position of the lastValue (that was just moved) + set._positions[lastValue] = position; + } + + // Delete the slot where the moved value was stored + set._values.pop(); + + // Delete the tracked position for the deleted slot + delete set._positions[value]; + + return true; + } else { + return false; + } + } + + /** + * @dev Returns true if the value is in the set. O(1). + */ + function _contains(Set storage set, bytes32 value) private view returns (bool) { + return set._positions[value] != 0; + } + + /** + * @dev Returns the number of values on the set. O(1). + */ + function _length(Set storage set) private view returns (uint256) { + return set._values.length; + } + + /** + * @dev Returns the value stored at position `index` in the set. O(1). + * + * Note that there are no guarantees on the ordering of values inside the + * array, and it may change when more values are added or removed. + * + * Requirements: + * + * - `index` must be strictly less than {length}. + */ + function _at(Set storage set, uint256 index) private view returns (bytes32) { + return set._values[index]; + } + + /** + * @dev Return the entire set in an array + * + * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed + * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that + * this function has an unbounded cost, and using it as part of a state-changing function may render the function + * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. + */ + function _values(Set storage set) private view returns (bytes32[] memory) { + return set._values; + } + + // Bytes32Set + + struct Bytes32Set { + Set _inner; + } + + /** + * @dev Add a value to a set. O(1). + * + * Returns true if the value was added to the set, that is if it was not + * already present. + */ + function add(Bytes32Set storage set, bytes32 value) internal returns (bool) { + return _add(set._inner, value); + } + + /** + * @dev Removes a value from a set. O(1). + * + * Returns true if the value was removed from the set, that is if it was + * present. + */ + function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) { + return _remove(set._inner, value); + } + + /** + * @dev Returns true if the value is in the set. O(1). + */ + function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) { + return _contains(set._inner, value); + } + + /** + * @dev Returns the number of values in the set. O(1). + */ + function length(Bytes32Set storage set) internal view returns (uint256) { + return _length(set._inner); + } + + /** + * @dev Returns the value stored at position `index` in the set. O(1). + * + * Note that there are no guarantees on the ordering of values inside the + * array, and it may change when more values are added or removed. + * + * Requirements: + * + * - `index` must be strictly less than {length}. + */ + function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) { + return _at(set._inner, index); + } + + /** + * @dev Return the entire set in an array + * + * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed + * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that + * this function has an unbounded cost, and using it as part of a state-changing function may render the function + * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. + */ + function values(Bytes32Set storage set) internal view returns (bytes32[] memory) { + bytes32[] memory store = _values(set._inner); + bytes32[] memory result; + + /// @solidity memory-safe-assembly + assembly { + result := store + } + + return result; + } + + // AddressSet + + struct AddressSet { + Set _inner; + } + + /** + * @dev Add a value to a set. O(1). + * + * Returns true if the value was added to the set, that is if it was not + * already present. + */ + function add(AddressSet storage set, address value) internal returns (bool) { + return _add(set._inner, bytes32(uint256(uint160(value)))); + } + + /** + * @dev Removes a value from a set. O(1). + * + * Returns true if the value was removed from the set, that is if it was + * present. + */ + function remove(AddressSet storage set, address value) internal returns (bool) { + return _remove(set._inner, bytes32(uint256(uint160(value)))); + } + + /** + * @dev Returns true if the value is in the set. O(1). + */ + function contains(AddressSet storage set, address value) internal view returns (bool) { + return _contains(set._inner, bytes32(uint256(uint160(value)))); + } + + /** + * @dev Returns the number of values in the set. O(1). + */ + function length(AddressSet storage set) internal view returns (uint256) { + return _length(set._inner); + } + + /** + * @dev Returns the value stored at position `index` in the set. O(1). + * + * Note that there are no guarantees on the ordering of values inside the + * array, and it may change when more values are added or removed. + * + * Requirements: + * + * - `index` must be strictly less than {length}. + */ + function at(AddressSet storage set, uint256 index) internal view returns (address) { + return address(uint160(uint256(_at(set._inner, index)))); + } + + /** + * @dev Return the entire set in an array + * + * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed + * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that + * this function has an unbounded cost, and using it as part of a state-changing function may render the function + * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. + */ + function values(AddressSet storage set) internal view returns (address[] memory) { + bytes32[] memory store = _values(set._inner); + address[] memory result; + + /// @solidity memory-safe-assembly + assembly { + result := store + } + + return result; + } + + // UintSet + + struct UintSet { + Set _inner; + } + + /** + * @dev Add a value to a set. O(1). + * + * Returns true if the value was added to the set, that is if it was not + * already present. + */ + function add(UintSet storage set, uint256 value) internal returns (bool) { + return _add(set._inner, bytes32(value)); + } + + /** + * @dev Removes a value from a set. O(1). + * + * Returns true if the value was removed from the set, that is if it was + * present. + */ + function remove(UintSet storage set, uint256 value) internal returns (bool) { + return _remove(set._inner, bytes32(value)); + } + + /** + * @dev Returns true if the value is in the set. O(1). + */ + function contains(UintSet storage set, uint256 value) internal view returns (bool) { + return _contains(set._inner, bytes32(value)); + } + + /** + * @dev Returns the number of values in the set. O(1). + */ + function length(UintSet storage set) internal view returns (uint256) { + return _length(set._inner); + } + + /** + * @dev Returns the value stored at position `index` in the set. O(1). + * + * Note that there are no guarantees on the ordering of values inside the + * array, and it may change when more values are added or removed. + * + * Requirements: + * + * - `index` must be strictly less than {length}. + */ + function at(UintSet storage set, uint256 index) internal view returns (uint256) { + return uint256(_at(set._inner, index)); + } + + /** + * @dev Return the entire set in an array + * + * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed + * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that + * this function has an unbounded cost, and using it as part of a state-changing function may render the function + * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. + */ + function values(UintSet storage set) internal view returns (uint256[] memory) { + bytes32[] memory store = _values(set._inner); + uint256[] memory result; + + /// @solidity memory-safe-assembly + assembly { + result := store + } + + return result; + } +} diff --git a/contracts/utils/types/Time.sol b/contracts/utils/types/Time.sol new file mode 100644 index 000000000..df4d0af91 --- /dev/null +++ b/contracts/utils/types/Time.sol @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (utils/types/Time.sol) + +pragma solidity ^0.8.20; + +import {Math} from "../math/Math.sol"; +import {SafeCast} from "../math/SafeCast.sol"; + +/** + * @dev This library provides helpers for manipulating time-related objects. + * + * It uses the following types: + * - `uint48` for timepoints + * - `uint32` for durations + * + * While the library doesn't provide specific types for timepoints and duration, it does provide: + * - a `Delay` type to represent duration that can be programmed to change value automatically at a given point + * - additional helper functions + */ +library Time { + using Time for *; + + /** + * @dev Get the block timestamp as a Timepoint. + */ + function timestamp() internal view returns (uint48) { + return SafeCast.toUint48(block.timestamp); + } + + /** + * @dev Get the block number as a Timepoint. + */ + function blockNumber() internal view returns (uint48) { + return SafeCast.toUint48(block.number); + } + + // ==================================================== Delay ===================================================== + /** + * @dev A `Delay` is a uint32 duration that can be programmed to change value automatically at a given point in the + * future. The "effect" timepoint describes when the transitions happens from the "old" value to the "new" value. + * This allows updating the delay applied to some operation while keeping some guarantees. + * + * In particular, the {update} function guarantees that if the delay is reduced, the old delay still applies for + * some time. For example if the delay is currently 7 days to do an upgrade, the admin should not be able to set + * the delay to 0 and upgrade immediately. If the admin wants to reduce the delay, the old delay (7 days) should + * still apply for some time. + * + * + * The `Delay` type is 112 bits long, and packs the following: + * + * ``` + * | [uint48]: effect date (timepoint) + * | | [uint32]: value before (duration) + * ↓ ↓ ↓ [uint32]: value after (duration) + * 0xAAAAAAAAAAAABBBBBBBBCCCCCCCC + * ``` + * + * NOTE: The {get} and {withUpdate} functions operate using timestamps. Block number based delays are not currently + * supported. + */ + type Delay is uint112; + + /** + * @dev Wrap a duration into a Delay to add the one-step "update in the future" feature + */ + function toDelay(uint32 duration) internal pure returns (Delay) { + return Delay.wrap(duration); + } + + /** + * @dev Get the value at a given timepoint plus the pending value and effect timepoint if there is a scheduled + * change after this timepoint. If the effect timepoint is 0, then the pending value should not be considered. + */ + function _getFullAt(Delay self, uint48 timepoint) private pure returns (uint32, uint32, uint48) { + (uint32 valueBefore, uint32 valueAfter, uint48 effect) = self.unpack(); + return effect <= timepoint ? (valueAfter, 0, 0) : (valueBefore, valueAfter, effect); + } + + /** + * @dev Get the current value plus the pending value and effect timepoint if there is a scheduled change. If the + * effect timepoint is 0, then the pending value should not be considered. + */ + function getFull(Delay self) internal view returns (uint32, uint32, uint48) { + return _getFullAt(self, timestamp()); + } + + /** + * @dev Get the current value. + */ + function get(Delay self) internal view returns (uint32) { + (uint32 delay, , ) = self.getFull(); + return delay; + } + + /** + * @dev Update a Delay object so that it takes a new duration after a timepoint that is automatically computed to + * enforce the old delay at the moment of the update. Returns the updated Delay object and the timestamp when the + * new delay becomes effective. + */ + function withUpdate(Delay self, uint32 newValue, uint32 minSetback) internal view returns (Delay, uint48) { + uint32 value = self.get(); + uint32 setback = uint32(Math.max(minSetback, value > newValue ? value - newValue : 0)); + uint48 effect = timestamp() + setback; + return (pack(value, newValue, effect), effect); + } + + /** + * @dev Split a delay into its components: valueBefore, valueAfter and effect (transition timepoint). + */ + function unpack(Delay self) internal pure returns (uint32, uint32, uint48) { + uint112 raw = Delay.unwrap(self); + + uint32 valueAfter = uint32(raw); + uint32 valueBefore = uint32(raw >> 32); + uint48 effect = uint48(raw >> 64); + + return (valueBefore, valueAfter, effect); + } + + /** + * @dev pack the components into a Delay object. + */ + function pack(uint32 valueBefore, uint32 valueAfter, uint48 effect) internal pure returns (Delay) { + return Delay.wrap((uint112(effect) << 64) | (uint112(valueBefore) << 32) | uint112(valueAfter)); + } +} diff --git a/contracts/vendor/compound/ICompoundTimelock.sol b/contracts/vendor/compound/ICompoundTimelock.sol new file mode 100644 index 000000000..145e0a65f --- /dev/null +++ b/contracts/vendor/compound/ICompoundTimelock.sol @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0-rc.0) (vendor/compound/ICompoundTimelock.sol) + +pragma solidity ^0.8.20; + +/** + * https://github.com/compound-finance/compound-protocol/blob/master/contracts/Timelock.sol[Compound timelock] interface + */ +interface ICompoundTimelock { + event NewAdmin(address indexed newAdmin); + event NewPendingAdmin(address indexed newPendingAdmin); + event NewDelay(uint256 indexed newDelay); + event CancelTransaction( + bytes32 indexed txHash, + address indexed target, + uint256 value, + string signature, + bytes data, + uint256 eta + ); + event ExecuteTransaction( + bytes32 indexed txHash, + address indexed target, + uint256 value, + string signature, + bytes data, + uint256 eta + ); + event QueueTransaction( + bytes32 indexed txHash, + address indexed target, + uint256 value, + string signature, + bytes data, + uint256 eta + ); + + receive() external payable; + + // solhint-disable-next-line func-name-mixedcase + function GRACE_PERIOD() external view returns (uint256); + + // solhint-disable-next-line func-name-mixedcase + function MINIMUM_DELAY() external view returns (uint256); + + // solhint-disable-next-line func-name-mixedcase + function MAXIMUM_DELAY() external view returns (uint256); + + function admin() external view returns (address); + + function pendingAdmin() external view returns (address); + + function delay() external view returns (uint256); + + function queuedTransactions(bytes32) external view returns (bool); + + function setDelay(uint256) external; + + function acceptAdmin() external; + + function setPendingAdmin(address) external; + + function queueTransaction( + address target, + uint256 value, + string memory signature, + bytes memory data, + uint256 eta + ) external returns (bytes32); + + function cancelTransaction( + address target, + uint256 value, + string memory signature, + bytes memory data, + uint256 eta + ) external; + + function executeTransaction( + address target, + uint256 value, + string memory signature, + bytes memory data, + uint256 eta + ) external payable returns (bytes memory); +} diff --git a/contracts/vendor/compound/LICENSE b/contracts/vendor/compound/LICENSE new file mode 100644 index 000000000..7da232470 --- /dev/null +++ b/contracts/vendor/compound/LICENSE @@ -0,0 +1,11 @@ +Copyright 2020 Compound Labs, Inc. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 000000000..ca39e51db --- /dev/null +++ b/docs/README.md @@ -0,0 +1,16 @@ +Documentation is hosted at https://docs.openzeppelin.com/contracts. + +All of the content for the site is in this repository. The guides are in the +[docs](/docs) directory, and the API Reference is extracted from comments in +the source code. If you want to help improve the content, this is the +repository you should be contributing to. + +[`solidity-docgen`](https://github.com/OpenZeppelin/solidity-docgen) is the +program that extracts the API Reference from source code. + +The [`docs.openzeppelin.com`](https://github.com/OpenZeppelin/docs.openzeppelin.com) +repository hosts the configuration for the entire site, which includes +documentation for all of the OpenZeppelin projects. + +To run the docs locally you should run `npm run docs:watch` on this +repository. diff --git a/docs/antora.yml b/docs/antora.yml new file mode 100644 index 000000000..46c7444e0 --- /dev/null +++ b/docs/antora.yml @@ -0,0 +1,7 @@ +name: contracts +title: Contracts +version: 5.x-rc +prerelease: true +nav: + - modules/ROOT/nav.adoc + - modules/api/nav.adoc diff --git a/docs/config.js b/docs/config.js new file mode 100644 index 000000000..f0af66390 --- /dev/null +++ b/docs/config.js @@ -0,0 +1,21 @@ +const path = require('path'); +const fs = require('fs'); + +/** @type import('solidity-docgen/dist/config').UserConfig */ +module.exports = { + outputDir: 'docs/modules/api/pages', + templates: 'docs/templates', + exclude: ['mocks'], + pageExtension: '.adoc', + pages: (_, file, config) => { + // For each contract file, find the closest README.adoc and return its location as the output page path. + const sourcesDir = path.resolve(config.root, config.sourcesDir); + let dir = path.resolve(config.root, file.absolutePath); + while (dir.startsWith(sourcesDir)) { + dir = path.dirname(dir); + if (fs.existsSync(path.join(dir, 'README.adoc'))) { + return path.relative(sourcesDir, dir) + config.pageExtension; + } + } + }, +}; diff --git a/docs/modules/ROOT/images/erc4626-attack-3a.png b/docs/modules/ROOT/images/erc4626-attack-3a.png new file mode 100644 index 000000000..4cb52237d Binary files /dev/null and b/docs/modules/ROOT/images/erc4626-attack-3a.png differ diff --git a/docs/modules/ROOT/images/erc4626-attack-3b.png b/docs/modules/ROOT/images/erc4626-attack-3b.png new file mode 100644 index 000000000..3dc5256b5 Binary files /dev/null and b/docs/modules/ROOT/images/erc4626-attack-3b.png differ diff --git a/docs/modules/ROOT/images/erc4626-attack-6.png b/docs/modules/ROOT/images/erc4626-attack-6.png new file mode 100644 index 000000000..1587fb5c1 Binary files /dev/null and b/docs/modules/ROOT/images/erc4626-attack-6.png differ diff --git a/docs/modules/ROOT/images/erc4626-attack.png b/docs/modules/ROOT/images/erc4626-attack.png new file mode 100644 index 000000000..dc059b228 Binary files /dev/null and b/docs/modules/ROOT/images/erc4626-attack.png differ diff --git a/docs/modules/ROOT/images/erc4626-deposit.png b/docs/modules/ROOT/images/erc4626-deposit.png new file mode 100644 index 000000000..b6c75e679 Binary files /dev/null and b/docs/modules/ROOT/images/erc4626-deposit.png differ diff --git a/docs/modules/ROOT/images/erc4626-mint.png b/docs/modules/ROOT/images/erc4626-mint.png new file mode 100644 index 000000000..f89ab9007 Binary files /dev/null and b/docs/modules/ROOT/images/erc4626-mint.png differ diff --git a/docs/modules/ROOT/images/erc4626-rate-linear.png b/docs/modules/ROOT/images/erc4626-rate-linear.png new file mode 100644 index 000000000..09e8045e1 Binary files /dev/null and b/docs/modules/ROOT/images/erc4626-rate-linear.png differ diff --git a/docs/modules/ROOT/images/erc4626-rate-loglog.png b/docs/modules/ROOT/images/erc4626-rate-loglog.png new file mode 100644 index 000000000..4eb19efea Binary files /dev/null and b/docs/modules/ROOT/images/erc4626-rate-loglog.png differ diff --git a/docs/modules/ROOT/images/erc4626-rate-loglogext.png b/docs/modules/ROOT/images/erc4626-rate-loglogext.png new file mode 100644 index 000000000..127bc7f2f Binary files /dev/null and b/docs/modules/ROOT/images/erc4626-rate-loglogext.png differ diff --git a/docs/modules/ROOT/images/tally-exec.png b/docs/modules/ROOT/images/tally-exec.png new file mode 100644 index 000000000..e24a145d0 Binary files /dev/null and b/docs/modules/ROOT/images/tally-exec.png differ diff --git a/docs/modules/ROOT/images/tally-vote.png b/docs/modules/ROOT/images/tally-vote.png new file mode 100644 index 000000000..7d270fce0 Binary files /dev/null and b/docs/modules/ROOT/images/tally-vote.png differ diff --git a/docs/modules/ROOT/nav.adoc b/docs/modules/ROOT/nav.adoc new file mode 100644 index 000000000..fc38e8953 --- /dev/null +++ b/docs/modules/ROOT/nav.adoc @@ -0,0 +1,23 @@ +* xref:index.adoc[Overview] +* xref:wizard.adoc[Wizard] +* xref:extending-contracts.adoc[Extending Contracts] +* xref:upgradeable.adoc[Using with Upgrades] + +* xref:releases-stability.adoc[Releases & Stability] + +* xref:access-control.adoc[Access Control] + +* xref:tokens.adoc[Tokens] +** xref:erc20.adoc[ERC20] +*** xref:erc20-supply.adoc[Creating Supply] +** xref:erc721.adoc[ERC721] +** xref:erc1155.adoc[ERC1155] +** xref:erc4626.adoc[ERC4626] + +* xref:governance.adoc[Governance] + +* xref:utilities.adoc[Utilities] + +* xref:subgraphs::index.adoc[Subgraphs] + +* xref:faq.adoc[FAQ] diff --git a/docs/modules/ROOT/pages/access-control.adoc b/docs/modules/ROOT/pages/access-control.adoc new file mode 100644 index 000000000..a60a34388 --- /dev/null +++ b/docs/modules/ROOT/pages/access-control.adoc @@ -0,0 +1,219 @@ += Access Control + +Access controlβ€”that is, "who is allowed to do this thing"β€”is incredibly important in the world of smart contracts. The access control of your contract may govern who can mint tokens, vote on proposals, freeze transfers, and many other things. It is therefore *critical* to understand how you implement it, lest someone else https://blog.openzeppelin.com/on-the-parity-wallet-multisig-hack-405a8c12e8f7[steals your whole system]. + +[[ownership-and-ownable]] +== Ownership and `Ownable` + +The most common and basic form of access control is the concept of _ownership_: there's an account that is the `owner` of a contract and can do administrative tasks on it. This approach is perfectly reasonable for contracts that have a single administrative user. + +OpenZeppelin Contracts provides xref:api:access.adoc#Ownable[`Ownable`] for implementing ownership in your contracts. + +[source,solidity] +---- +// contracts/MyContract.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; + +contract MyContract is Ownable { + function normalThing() public { + // anyone can call this normalThing() + } + + function specialThing() public onlyOwner { + // only the owner can call specialThing()! + } +} +---- + +By default, the xref:api:access.adoc#Ownable-owner--[`owner`] of an `Ownable` contract is the account that deployed it, which is usually exactly what you want. + +Ownable also lets you: + +* xref:api:access.adoc#Ownable-transferOwnership-address-[`transferOwnership`] from the owner account to a new one, and +* xref:api:access.adoc#Ownable-renounceOwnership--[`renounceOwnership`] for the owner to relinquish this administrative privilege, a common pattern after an initial stage with centralized administration is over. + +WARNING: Removing the owner altogether will mean that administrative tasks that are protected by `onlyOwner` will no longer be callable! + +Note that *a contract can also be the owner of another one*! This opens the door to using, for example, a https://gnosis-safe.io[Gnosis Safe], an https://aragon.org[Aragon DAO], or a totally custom contract that _you_ create. + +In this way, you can use _composability_ to add additional layers of access control complexity to your contracts. Instead of having a single regular Ethereum account (Externally Owned Account, or EOA) as the owner, you could use a 2-of-3 multisig run by your project leads, for example. Prominent projects in the space, such as https://makerdao.com[MakerDAO], use systems similar to this one. + +[[role-based-access-control]] +== Role-Based Access Control + +While the simplicity of _ownership_ can be useful for simple systems or quick prototyping, different levels of authorization are often needed. You may want for an account to have permission to ban users from a system, but not create new tokens. https://en.wikipedia.org/wiki/Role-based_access_control[_Role-Based Access Control (RBAC)_] offers flexibility in this regard. + +In essence, we will be defining multiple _roles_, each allowed to perform different sets of actions. An account may have, for example, 'moderator', 'minter' or 'admin' roles, which you will then check for instead of simply using `onlyOwner`. This check can be enforced through the `onlyRole` modifier. Separately, you will be able to define rules for how accounts can be granted a role, have it revoked, and more. + +Most software uses access control systems that are role-based: some users are regular users, some may be supervisors or managers, and a few will often have administrative privileges. + +[[using-access-control]] +=== Using `AccessControl` + +OpenZeppelin Contracts provides xref:api:access.adoc#AccessControl[`AccessControl`] for implementing role-based access control. Its usage is straightforward: for each role that you want to define, +you will create a new _role identifier_ that is used to grant, revoke, and check if an account has that role. + +Here's a simple example of using `AccessControl` in an xref:tokens.adoc#ERC20[`ERC20` token] to define a 'minter' role, which allows accounts that have it create new tokens: + +[source,solidity] +---- +// contracts/MyToken.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +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 + bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); + + constructor(address minter) ERC20("MyToken", "TKN") { + // Grant the minter role to a specified account + _grantRole(MINTER_ROLE, minter); + } + + function mint(address to, uint256 amount) public { + // Check that the calling account has the minter role + require(hasRole(MINTER_ROLE, msg.sender), "Caller is not a minter"); + _mint(to, amount); + } +} +---- + +NOTE: Make sure you fully understand how xref:api:access.adoc#AccessControl[`AccessControl`] works before using it on your system, or copy-pasting the examples from this guide. + +While clear and explicit, this isn't anything we wouldn't have been able to achieve with `Ownable`. Indeed, where `AccessControl` shines is in scenarios where granular permissions are required, which can be implemented by defining _multiple_ roles. + +Let's augment our ERC20 token example by also defining a 'burner' role, which lets accounts destroy tokens, and by using the `onlyRole` modifier: + +[source,solidity] +---- +// contracts/MyToken.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +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"); + bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE"); + + constructor(address minter, address burner) ERC20("MyToken", "TKN") { + _grantRole(MINTER_ROLE, minter); + _grantRole(BURNER_ROLE, burner); + } + + function mint(address to, uint256 amount) public onlyRole(MINTER_ROLE) { + _mint(to, amount); + } + + function burn(address from, uint256 amount) public onlyRole(BURNER_ROLE) { + _burn(from, amount); + } +} +---- + +So clean! By splitting concerns this way, more granular levels of permission may be implemented than were possible with the simpler _ownership_ approach to access control. Limiting what each component of a system is able to do is known as the https://en.wikipedia.org/wiki/Principle_of_least_privilege[principle of least privilege], and is a good security practice. Note that each account may still have more than one role, if so desired. + +[[granting-and-revoking]] +=== Granting and Revoking Roles + +The ERC20 token example above uses `_grantRole`, an `internal` function that is useful when programmatically assigning roles (such as during construction). But what if we later want to grant the 'minter' role to additional accounts? + +By default, **accounts with a role cannot grant it or revoke it from other accounts**: all having a role does is making the `hasRole` check pass. To grant and revoke roles dynamically, you will need help from the _role's admin_. + +Every role has an associated admin role, which grants permission to call the `grantRole` and `revokeRole` functions. A role can be granted or revoked by using these if the calling account has the corresponding admin role. Multiple roles may have the same admin role to make management easier. A role's admin can even be the same role itself, which would cause accounts with that role to be able to also grant and revoke it. + +This mechanism can be used to create complex permissioning structures resembling organizational charts, but it also provides an easy way to manage simpler applications. `AccessControl` includes a special role, called `DEFAULT_ADMIN_ROLE`, which acts as the **default admin role for all roles**. An account with this role will be able to manage any other role, unless `_setRoleAdmin` is used to select a new admin role. + +Since it is the admin for all roles by default, and in fact it is also its own admin, this role carries significant risk. To mitigate this risk we provide xref:api:access.adoc#AccessControlDefaultAdminRules[`AccessControlDefaultAdminRules`], a recommended extension of `AccessControl` that adds a number of enforced security measures for this role: the admin is restricted to a single account, with a 2-step transfer procedure with a delay in between steps. + +Let's take a look at the ERC20 token example, this time taking advantage of the default admin role: + +[source,solidity] +---- +// contracts/MyToken.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +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"); + bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE"); + + constructor() ERC20("MyToken", "TKN") { + // Grant the contract deployer the default admin role: it will be able + // to grant and revoke any roles + _grantRole(DEFAULT_ADMIN_ROLE, msg.sender); + } + + function mint(address to, uint256 amount) public onlyRole(MINTER_ROLE) { + _mint(to, amount); + } + + function burn(address from, uint256 amount) public onlyRole(BURNER_ROLE) { + _burn(from, amount); + } +} +---- + +Note that, unlike the previous examples, no accounts are granted the 'minter' or 'burner' roles. However, because those roles' admin role is the default admin role, and _that_ role was granted to `msg.sender`, that same account can call `grantRole` to give minting or burning permission, and `revokeRole` to remove it. + +Dynamic role allocation is often a desirable property, for example in systems where trust in a participant may vary over time. It can also be used to support use cases such as https://en.wikipedia.org/wiki/Know_your_customer[KYC], where the list of role-bearers may not be known up-front, or may be prohibitively expensive to include in a single transaction. + +[[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 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. + +```javascript +const minterCount = await myToken.getRoleMemberCount(MINTER_ROLE); + +const members = []; +for (let i = 0; i < minterCount; ++i) { + members.push(await myToken.getRoleMember(MINTER_ROLE, i)); +} +``` + +== Delayed operation + +Access control is essential to prevent unauthorized access to critical functions. These functions may be used to mint tokens, freeze transfers or perform an upgrade that completely changes the smart contract logic. While xref:api:access.adoc#Ownable[`Ownable`] and xref:api:access.adoc#AccessControl[`AccessControl`] can prevent unauthorized access, they do not address the issue of a misbehaving administrator attacking their own system to the prejudice of their users. + +This is the issue the xref:api:governance.adoc#TimelockController[`TimelockController`] is addressing. + +The xref:api:governance.adoc#TimelockController[`TimelockController`] is a proxy that is governed by proposers and executors. When set as the owner/admin/controller of a smart contract, it ensures that whichever maintenance operation is ordered by the proposers is subject to a delay. This delay protects the users of the smart contract by giving them time to review the maintenance operation and exit the system if they consider it is in their best interest to do so. + +=== Using `TimelockController` + +By default, the address that deployed the xref:api:governance.adoc#TimelockController[`TimelockController`] gets administration privileges over the timelock. This role grants the right to assign proposers, executors, and other administrators. + +The first step in configuring the xref:api:governance.adoc#TimelockController[`TimelockController`] is to assign at least one proposer and one executor. These can be assigned during construction or later by anyone with the administrator role. These roles are not exclusive, meaning an account can have both roles. + +Roles are managed using the xref:api:access.adoc#AccessControl[`AccessControl`] interface and the `bytes32` values for each role are accessible through the `ADMIN_ROLE`, `PROPOSER_ROLE` and `EXECUTOR_ROLE` constants. + +There is an additional feature built on top of `AccessControl`: giving the executor role to `address(0)` opens access to anyone to execute a proposal once the timelock has expired. This feature, while useful, should be used with caution. + +At this point, with both a proposer and an executor assigned, the timelock can perform operations. + +An optional next step is for the deployer to renounce its administrative privileges and leave the timelock self-administered. If the deployer decides to do so, all further maintenance, including assigning new proposers/schedulers or changing the timelock duration will have to follow the timelock workflow. This links the governance of the timelock to the governance of contracts attached to the timelock, and enforce a delay on timelock maintenance operations. + +WARNING: If the deployer renounces administrative rights in favour of timelock itself, assigning new proposers or executors will require a timelocked operation. This means that if the accounts in charge of any of these two roles become unavailable, then the entire contract (and any contract it controls) becomes locked indefinitely. + +With both the proposer and executor roles assigned and the timelock in charge of its own administration, you can now transfer the ownership/control of any contract to the timelock. + +TIP: A recommended configuration is to grant both roles to a secure governance contract such as a DAO or a multisig, and to additionally grant the executor role to a few EOAs held by people in charge of helping with the maintenance operations. These wallets cannot take over control of the timelock but they can help smoothen the workflow. + +=== Minimum delay + +Operations executed by the xref:api:governance.adoc#TimelockController[`TimelockController`] are not subject to a fixed delay but rather a minimum delay. Some major updates might call for a longer delay. For example, if a delay of just a few days might be sufficient for users to audit a minting operation, it makes sense to use a delay of a few weeks, or even a few months, when scheduling a smart contract upgrade. + +The minimum delay (accessible through the xref:api:governance.adoc#TimelockController-getMinDelay--[`getMinDelay`] method) can be updated by calling the xref:api:governance.adoc#TimelockController-updateDelay-uint256-[`updateDelay`] function. Bear in mind that access to this function is only accessible by the timelock itself, meaning this maintenance operation has to go through the timelock itself. diff --git a/docs/modules/ROOT/pages/crowdsales.adoc b/docs/modules/ROOT/pages/crowdsales.adoc new file mode 100644 index 000000000..37579211d --- /dev/null +++ b/docs/modules/ROOT/pages/crowdsales.adoc @@ -0,0 +1,11 @@ += Crowdsales + +All crowdsale-related contracts were removed from the OpenZeppelin Contracts library on the https://forum.openzeppelin.com/t/openzeppelin-contracts-v3-0-beta-release/2256[v3.0.0 release] due to both a decline in their usage and the complexity associated with migrating them to Solidity v0.6. + +They are however still available on the v2.5 release of OpenZeppelin Contracts, which you can install by running: + +```console +$ npm install @openzeppelin/contracts@v2.5 +``` + +Refer to the https://docs.openzeppelin.com/contracts/2.x/crowdsales[v2.x documentation] when working with them. diff --git a/docs/modules/ROOT/pages/drafts.adoc b/docs/modules/ROOT/pages/drafts.adoc new file mode 100644 index 000000000..b2c1ae628 --- /dev/null +++ b/docs/modules/ROOT/pages/drafts.adoc @@ -0,0 +1,19 @@ += Drafts + +All draft contracts were either moved into a different directory or removed from the OpenZeppelin Contracts library on the https://forum.openzeppelin.com/t/openzeppelin-contracts-v3-0-beta-release/2256[v3.0.0 release]. + +* `ERC20Migrator`: removed. +* xref:api:token/ERC20.adoc#ERC20Snapshot[`ERC20Snapshot`]: moved to `token/ERC20`. +* `ERC20Detailed` and `ERC1046`: removed. +* `TokenVesting`: removed. Pending a replacement that is being discussed in https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1214[`#1214`]. +* xref:api:utils.adoc#Counters[`Counters`]: moved to xref:api:utils.adoc[`utils`]. +* xref:api:utils.adoc#Strings[`Strings`]: moved to xref:api:utils.adoc[`utils`]. +* xref:api:utils.adoc#SignedSafeMath[`SignedSafeMath`]: moved to xref:api:utils.adoc[`utils`]. + +Removed contracts are still available on the v2.5 release of OpenZeppelin Contracts, which you can install by running: + +```console +$ npm install @openzeppelin/contracts@v2.5 +``` + +Refer to the xref:2.x@contracts:api:drafts.adoc[v2.x documentation] when working with them. diff --git a/docs/modules/ROOT/pages/erc1155.adoc b/docs/modules/ROOT/pages/erc1155.adoc new file mode 100644 index 000000000..5a4c91670 --- /dev/null +++ b/docs/modules/ROOT/pages/erc1155.adoc @@ -0,0 +1,145 @@ += ERC1155 + +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 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 + +The distinctive feature of ERC1155 is that it uses a single smart contract to represent multiple tokens at once. This is why its xref:api:token/ERC1155.adoc#IERC1155-balanceOf-address-uint256-[`balanceOf`] function differs from ERC20's and ERC777's: it has an additional `id` argument for the identifier of the token that you want to query the balance of. + +This is similar to how ERC721 does things, but in that standard a token `id` has no concept of balance: each token is non-fungible and exists or doesn't. The ERC721 xref:api:token/ERC721.adoc#IERC721-balanceOf-address-[`balanceOf`] function refers to _how many different tokens_ an account has, not how many of each. On the other hand, in ERC1155 accounts have a distinct balance for each token `id`, and non-fungible tokens are implemented by simply minting a single one of them. + +This approach leads to massive gas savings for projects that require multiple tokens. Instead of deploying a new contract for each token type, a single ERC1155 token contract can hold the entire system state, reducing deployment costs and complexity. + +[[batch-operations]] +== Batch Operations + +Because all state is held in a single contract, it is possible to operate over multiple tokens in a single transaction very efficiently. The standard provides two functions, xref:api:token/ERC1155.adoc#IERC1155-balanceOfBatch-address---uint256---[`balanceOfBatch`] and xref:api:token/ERC1155.adoc#IERC1155-safeBatchTransferFrom-address-address-uint256---uint256---bytes-[`safeBatchTransferFrom`], that make querying multiple balances and transferring multiple tokens simpler and less gas-intensive. + +In the spirit of the standard, we've also included batch operations in the non-standard functions, such as xref:api:token/ERC1155.adoc#ERC1155-_mintBatch-address-uint256---uint256---bytes-[`_mintBatch`]. + +== 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! + +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]. + +Here's what a contract for tokenized items might look like: + +[source,solidity] +---- +// contracts/GameItems.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {ERC1155} from "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; + +contract GameItems is ERC1155 { + uint256 public constant GOLD = 0; + uint256 public constant SILVER = 1; + uint256 public constant THORS_HAMMER = 2; + uint256 public constant SWORD = 3; + uint256 public constant SHIELD = 4; + + constructor() ERC1155("https://game.example/api/item/{id}.json") { + _mint(msg.sender, GOLD, 10**18, ""); + _mint(msg.sender, SILVER, 10**27, ""); + _mint(msg.sender, THORS_HAMMER, 1, ""); + _mint(msg.sender, SWORD, 10**9, ""); + _mint(msg.sender, SHIELD, 10**9, ""); + } +} +---- + +Note that for our Game Items, Gold is a fungible token whilst Thor's Hammer is a non-fungible token as we minted only one. + +The xref:api:token/ERC1155.adoc#ERC1155[`ERC1155`] contract includes the optional extension xref:api:token/ERC1155.adoc#IERC1155MetadataURI[`IERC1155MetadataURI`]. That's where the xref:api:token/ERC1155.adoc#IERC1155MetadataURI-uri-uint256-[`uri`] function comes from: we use it to retrieve the metadata uri. + +Also note that, unlike ERC20, ERC1155 lacks a `decimals` field, since each token is distinct and cannot be partitioned. + +Once deployed, we will be able to query the deployer’s balance: +[source,javascript] +---- +> gameItems.balanceOf(deployerAddress,3) +1000000000 +---- + +We can transfer items to player accounts: +[source,javascript] +---- +> gameItems.safeTransferFrom(deployerAddress, playerAddress, 2, 1, "0x0") +> gameItems.balanceOf(playerAddress, 2) +1 +> gameItems.balanceOf(deployerAddress, 2) +0 +---- + +We can also batch transfer items to player accounts and get the balance of batches: +[source,javascript] +---- +> gameItems.safeBatchTransferFrom(deployerAddress, playerAddress, [0,1,3,4], [50,100,1,1], "0x0") +> gameItems.balanceOfBatch([playerAddress,playerAddress,playerAddress,playerAddress,playerAddress], [0,1,2,3,4]) +[50,100,1,1,1] +---- + +The metadata uri can be obtained: + +[source,javascript] +---- +> gameItems.uri(2) +"https://game.example/api/item/{id}.json" +---- + +The `uri` can include the string `++{id}++` which clients must replace with the actual token ID, in lowercase hexadecimal (with no 0x prefix) and leading zero padded to 64 hex characters. + +For token ID `2` and uri `++https://game.example/api/item/{id}.json++` clients would replace `++{id}++` with `0000000000000000000000000000000000000000000000000000000000000002` to retrieve JSON at `https://game.example/api/item/0000000000000000000000000000000000000000000000000000000000000002.json`. + +The JSON document for token ID 2 might look something like: + +[source,json] +---- +{ + "name": "Thor's hammer", + "description": "MjΓΆlnir, the legendary hammer of the Norse god of thunder.", + "image": "https://game.example/item-id-8u5h2m.png", + "strength": 20 +} +---- + +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! + +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 + +[[sending-to-contracts]] +== Sending Tokens to Contracts + +A key difference when using xref:api:token/ERC1155.adoc#IERC1155-safeTransferFrom-address-address-uint256-uint256-bytes-[`safeTransferFrom`] is that token transfers to other contracts may revert with the following message: + +[source,text] +---- +ERC1155: transfer to non ERC1155Receiver implementer +---- + +This is a good thing! It means that the recipient contract has not registered itself as aware of the ERC1155 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. + +In order for our contract to receive ERC1155 tokens we can inherit from the convenience contract xref:api:token/ERC1155.adoc#ERC1155Holder[`ERC1155Holder`] which handles the registering for us. Though we need to remember to implement functionality to allow tokens to be transferred out of our contract: + +[source,solidity] +---- +// contracts/MyContract.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {ERC1155Holder} from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol"; + +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. diff --git a/docs/modules/ROOT/pages/erc20-supply.adoc b/docs/modules/ROOT/pages/erc20-supply.adoc new file mode 100644 index 000000000..44cbd73dd --- /dev/null +++ b/docs/modules/ROOT/pages/erc20-supply.adoc @@ -0,0 +1,85 @@ += Creating ERC20 Supply + +In this guide, you will learn how to create an ERC20 token with a custom supply mechanism. We will showcase two idiomatic ways to use OpenZeppelin Contracts for this purpose that you will be able to apply to your smart contract development practice. + +The standard interface implemented by tokens built on Ethereum is called ERC20, and Contracts includes a widely used implementation of it: the aptly named xref:api:token/ERC20.adoc[`ERC20`] contract. This contract, like the standard itself, is quite simple and bare-bones. In fact, if you try to deploy an instance of `ERC20` as-is it will be quite literally useless... it will have no supply! What use is a token with no supply? + +The way that supply is created is not defined in the ERC20 document. Every token is free to experiment with its own mechanisms, ranging from the most decentralized to the most centralized, from the most naive to the most researched, and more. + +[[fixed-supply]] +== Fixed Supply + +Let's say we want a token with a fixed supply of 1000, initially allocated to the account that deploys the contract. If you've used Contracts v1, you may have written code like the following: + +[source,solidity] +---- +contract ERC20FixedSupply is ERC20 { + constructor() { + totalSupply += 1000; + balances[msg.sender] += 1000; + } +} +---- + +Starting with Contracts v2, this pattern is not only discouraged, but disallowed. The variables `totalSupply` and `balances` are now private implementation details of `ERC20`, and you can't directly write to them. Instead, there is an internal xref:api:token/ERC20.adoc#ERC20-_mint-address-uint256-[`_mint`] function that will do exactly this: + +[source,solidity] +---- +contract ERC20FixedSupply is ERC20 { + constructor() ERC20("Fixed", "FIX") { + _mint(msg.sender, 1000); + } +} +---- + +Encapsulating state like this makes it safer to extend contracts. For instance, in the first example we had to manually keep the `totalSupply` in sync with the modified balances, which is easy to forget. In fact, we omitted something else that is also easily forgotten: the `Transfer` event that is required by the standard, and which is relied on by some clients. The second example does not have this bug, because the internal `_mint` function takes care of it. + +[[rewarding-miners]] +== Rewarding Miners + +The internal xref:api:token/ERC20.adoc#ERC20-_mint-address-uint256-[`_mint`] function is the key building block that allows us to write ERC20 extensions that implement a supply mechanism. + +The mechanism we will implement is a token reward for the miners that produce Ethereum blocks. In Solidity, we can access the address of the current block's miner in the global variable `block.coinbase`. We will mint a token reward to this address whenever someone calls the function `mintMinerReward()` on our token. The mechanism may sound silly, but you never know what kind of dynamic this might result in, and it's worth analyzing and experimenting with! + +[source,solidity] +---- +contract ERC20WithMinerReward is ERC20 { + constructor() ERC20("Reward", "RWD") {} + + function mintMinerReward() public { + _mint(block.coinbase, 1000); + } +} +---- + +As we can see, `_mint` makes it super easy to do this correctly. + +[[automating-the-reward]] +== Automating the Reward + +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 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] +---- +contract ERC20WithAutoMinerReward is ERC20 { + constructor() ERC20("Reward", "RWD") {} + + function _mintMinerReward() internal { + _mint(block.coinbase, 1000); + } + + function _beforeTokenTransfer(address from, address to, uint256 value) internal virtual override { + if (!(from == address(0) && to == block.coinbase)) { + _mintMinerReward(); + } + super._beforeTokenTransfer(from, to, value); + } +} +---- + +[[wrapping-up]] +== Wrapping Up + +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 new file mode 100644 index 000000000..2b85070a6 --- /dev/null +++ b/docs/modules/ROOT/pages/erc20.adoc @@ -0,0 +1,77 @@ += ERC20 + +An ERC20 token contract keeps track of xref:tokens.adoc#different-kinds-of-tokens[_fungible_ tokens]: any one token is exactly equal to any other token; no tokens have special rights or behavior associated with them. This makes ERC20 tokens useful for things like a *medium of exchange currency*, *voting rights*, *staking*, and more. + +OpenZeppelin Contracts provides many ERC20-related contracts. On the xref:api:token/ERC20.adoc[`API reference`] you'll find detailed information on their properties and usage. + +[[constructing-an-erc20-token-contract]] +== Constructing an ERC20 Token Contract + +Using Contracts, we can easily create our own ERC20 token contract, which will be used to track _Gold_ (GLD), an internal currency in a hypothetical game. + +Here's what our GLD token might look like. + +[source,solidity] +---- +// contracts/GLDToken.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +contract GLDToken is ERC20 { + constructor(uint256 initialSupply) ERC20("Gold", "GLD") { + _mint(msg.sender, initialSupply); + } +} +---- + +Our contracts are often used via https://solidity.readthedocs.io/en/latest/contracts.html#inheritance[inheritance], and here we're reusing xref:api:token/ERC20.adoc#erc20[`ERC20`] for both the basic standard implementation and the xref:api:token/ERC20.adoc#ERC20-name--[`name`], xref:api:token/ERC20.adoc#ERC20-symbol--[`symbol`], and xref:api:token/ERC20.adoc#ERC20-decimals--[`decimals`] optional extensions. Additionally, we're creating an `initialSupply` of tokens, which will be assigned to the address that deploys the contract. + +TIP: For a more complete discussion of ERC20 supply mechanisms, see xref:erc20-supply.adoc[Creating ERC20 Supply]. + +That's it! Once deployed, we will be able to query the deployer's balance: + +[source,javascript] +---- +> GLDToken.balanceOf(deployerAddress) +1000000000000000000000 +---- + +We can also xref:api:token/ERC20.adoc#IERC20-transfer-address-uint256-[transfer] these tokens to other accounts: + +[source,javascript] +---- +> GLDToken.transfer(otherAddress, 300000000000000000000) +> GLDToken.balanceOf(otherAddress) +300000000000000000000 +> GLDToken.balanceOf(deployerAddress) +700000000000000000000 +---- + +[[a-note-on-decimals]] +== A Note on `decimals` + +Often, you'll want to be able to divide your tokens into arbitrary amounts: say, if you own `5 GLD`, you may want to send `1.5 GLD` to a friend, and keep `3.5 GLD` to yourself. Unfortunately, Solidity and the EVM do not support this behavior: only integer (whole) numbers can be used, which poses an issue. You may send `1` or `2` tokens, but not `1.5`. + +To work around this, xref:api:token/ERC20.adoc#ERC20[`ERC20`] provides a xref:api:token/ERC20.adoc#ERC20-decimals--[`decimals`] field, which is used to specify how many decimal places a token has. To be able to transfer `1.5 GLD`, `decimals` must be at least `1`, since that number has a single decimal place. + +How can this be achieved? It's actually very simple: a token contract can use larger integer values, so that a balance of `50` will represent `5 GLD`, a transfer of `15` will correspond to `1.5 GLD` being sent, and so on. + +It is important to understand that `decimals` is _only used for display purposes_. All arithmetic inside the contract is still performed on integers, and it is the different user interfaces (wallets, exchanges, etc.) that must adjust the displayed values according to `decimals`. The total token supply and balance of each account are not specified in `GLD`: you need to divide by `10 ** decimals` to get the actual `GLD` amount. + +You'll probably want to use a `decimals` value of `18`, just like Ether and most ERC20 token contracts in use, unless you have a very special reason not to. When minting tokens or transferring them around, you will be actually sending the number `num GLD * (10 ** decimals)`. + +NOTE: By default, `ERC20` uses a value of `18` for `decimals`. To use a different value, you will need to override the `decimals()` function in your contract. + +```solidity +function decimals() public view virtual override returns (uint8) { + return 16; +} +``` + +So if you want to send `5` tokens using a token contract with 18 decimals, the method to call will actually be: + +```solidity +transfer(recipient, 5 * (10 ** 18)); +``` diff --git a/docs/modules/ROOT/pages/erc4626.adoc b/docs/modules/ROOT/pages/erc4626.adoc new file mode 100644 index 000000000..43ec3ebb9 --- /dev/null +++ b/docs/modules/ROOT/pages/erc4626.adoc @@ -0,0 +1,214 @@ += ERC4626 +:stem: latexmath + +https://eips.ethereum.org/EIPS/eip-4626[ERC4626] is an extension of xref:erc20.adoc[ERC20] that proposes a standard interface for token vaults. This standard interface can be used by widely different contracts (including lending markets, aggregators, and intrinsically interest bearing tokens), which brings a number of subtleties. Navigating these potential issues is essential to implementing a compliant and composable token vault. + +We provide a base implementation of ERC4626 that includes a simple vault. This contract is designed in a way that allows developers to easily re-configure the vault's behavior, with minimal overrides, while staying compliant. In this guide, we will discuss some security considerations that affect ERC4626. We will also discuss common customizations of the vault. + +[[inflation-attack]] +== Security concern: Inflation attack + +=== Visualizing the vault + +In exchange for the assets deposited into an ERC4626 vault, a user receives shares. These shares can later be burned to redeem the corresponding underlying assets. The number of shares a user gets depends on the amount of assets they put in and on the exchange rate of the vault. This exchange rate is defined by the current liquidity held by the vault. + +- If a vault has 100 tokens to back 200 shares, then each share is worth 0.5 assets. +- If a vault has 200 tokens to back 100 shares, then each share is worth 2.0 assets. + +In other words, the exchange rate can be defined as the slope of the line that passes through the origin and the current number of assets and shares in the vault. Deposits and withdrawals move the vault in this line. + +image::erc4626-rate-linear.png[Exchange rates in linear scale] + +When plotted in log-log scale, the rate is defined similarly, but appears differently (because the point (0,0) is infinitely far away). Rates are represented by "diagonal" lines with different offsets. + +image::erc4626-rate-loglog.png[Exchange rates in logarithmic scale] + +In such a reprentation, widely different rates can be clearly visible in the same graph. This wouldn't be the case in linear scale. + +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 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. + +image::erc4626-deposit.png[Depositing assets] + +In the figure we can see that for a given deposit of 500 assets, the number of shares we get and the corresponding rounding losses depend on the exchange rate. If the exchange rate is that of the orange curve, we are getting less than a share, so we lose 100% of our deposit. However, if the exchange rate is that of the green curve, we get 5000 shares, which limits our rounding losses to at most 0.02%. + +image::erc4626-mint.png[Minting shares] + +Symmetrically, if we focus on limiting our losses to a maximum of 0.5%, we need to get at least 200 shares. With the green exchange rate that requires just 20 tokens, but with the orange rate that requires 200000 tokens. + +We can clearly see that that the blue and green curves correspond to vaults that are safer than the yellow and orange curves. + +The idea of an inflation attack is that an attacker can donate assets to the vault to move the rate curve to the right, and make the vault unsafe. + +image::erc4626-attack.png[Inflation attack without protection] + +Figure 6 shows how an attacker can manipulate the rate of an empty vault. First the attacker must deposit a small amount of tokens (1 token) and follow up with a donation of 1e5 tokens directly to the vault to move the exchange rate "right". This puts the vault in a state where any deposit smaller than 1e5 would be completely lost to the vault. Given that the attacker is the only share holder (from their donation), the attacker would steal all the tokens deposited. + +An attacker would typically wait for a user to do the first deposit into the vault, and would frontrun that operation with the attack described above. The risk is low, and the size of the "donation" required to manipulate the vault is equivalent to the size of the deposit that is being attacked. + +In math that gives: + +- stem:[a_0] the attacker deposit +- stem:[a_1] the attacker donation +- stem:[u] the user deposit + +[%header,cols=4*] +|=== +| +| Assets +| Shares +| Rate + +| initial +| stem:[0] +| stem:[0] +| - + +| after attacker's deposit +| stem:[a_0] +| stem:[a_0] +| stem:[1] + +| after attacker's donation +| stem:[a_0+a_1] +| stem:[a_0] +| stem:[\frac{a_0}{a_0+a_1}] +|=== + +This means a deposit of stem:[u] will give stem:[\frac{u \times a_0}{a_0 + a_1}] shares. + +For the attacker to dilute that deposit to 0 shares, causing the user to lose all its deposit, it must ensure that + +[stem] +++++ +\frac{u \times a_0}{a_0+a_1} < 1 \iff u < 1 + \frac{a_1}{a_0} +++++ + +Using stem:[a_0 = 1] and stem:[a_1 = u] is enough. So the attacker only needs stem:[u+1] assets to perform a successful attack. + +It is easy to generalize the above results to scenarios where the attacker is going after a smaller fraction of the user's deposit. In order to target stem:[\frac{u}{n}], the user needs to suffer rounding of a similar fraction, which means the user must receive at most stem:[n] shares. This results in: + +[stem] +++++ +\frac{u \times a_0}{a_0+a_1} < n \iff \frac{u}{n} < 1 + \frac{a_1}{a_0} +++++ + +In this scenario, the attack is stem:[n] times less powerful (in how much it is stealing) and costs stem:[n] times less to execute. In both cases, the amount of funds the attacker needs to commit is equivalent to its potential earnings. + +=== Defending with a virtual offset + +The defense we propose is based on the approach used in link:https://github.com/boringcrypto/YieldBox[YieldBox]. It consists of two parts: + +- Use an offset between the "precision" of the representation of shares and assets. Said otherwise, we use more decimal places to represent the shares than the underlying token does to represent the assets. +- Include virtual shares and virtual assets in the exchange rate computation. These virtual assets enforce the conversion rate when the vault is empty. + +These two parts work together in enforcing the security of the vault. First, the increased precision corresponds to a high rate, which we saw is safer as it reduces the rounding error when computing the amount of shares. Second, the virtual assets and shares (in addition to simplifying a lot of the computations) capture part of the donation, making it unprofitable for a developer to perform an attack. + +Following the previous math definitions, we have: + +- stem:[\delta] the vault offset +- stem:[a_0] the attacker deposit +- stem:[a_1] the attacker donation +- stem:[u] the user deposit + +[%header,cols=4*] +|=== +| +| Assets +| Shares +| Rate + +| initial +| stem:[1] +| stem:[10^\delta] +| stem:[10^\delta] + +| after attacker's deposit +| stem:[1+a_0] +| stem:[10^\delta \times (1+a_0)] +| stem:[10^\delta] + +| after attacker's donation +| stem:[1+a_0+a_1] +| stem:[10^\delta \times (1+a_0)] +| stem:[10^\delta \times \frac{1+a_0}{1+a_0+a_1}] +|=== + +One important thing to note is that the attacker only owns a fraction stem:[\frac{a_0}{1 + a_0}] of the shares, so when doing the donation, he will only be able to recover that fraction stem:[\frac{a_1 \times a_0}{1 + a_0}] of the donation. The remaining stem:[\frac{a_1}{1+a_0}] are captured by the vault. + +[stem] +++++ +\mathit{loss} = \frac{a_1}{1+a_0} +++++ + +When the user deposits stem:[u], he receives + +[stem] +++++ +10^\delta \times u \times \frac{1+a_0}{1+a_0+a_1} +++++ + +For the attacker to dilute that deposit to 0 shares, causing the user to lose all its deposit, it must ensure that + +[stem] +++++ +10^\delta \times u \times \frac{1+a_0}{1+a_0+a_1} < 1 +++++ + +[stem] +++++ +\iff 10^\delta \times u < \frac{1+a_0+a_1}{1+a_0} +++++ + +[stem] +++++ +\iff 10^\delta \times u < 1 + \frac{a_1}{1+a_0} +++++ + +[stem] +++++ +\iff 10^\delta \times u \le \mathit{loss} +++++ + +- If the offset is 0, the attacker loss is at least equal to the user's deposit. +- If the offset is greater than 0, the attacker will have to suffer losses that are orders of magnitude bigger than the amount of value that can hypothetically be stolen from the user. + +This shows that even with an offset of 0, the virtual shares and assets make this attack non profitable for the attacker. Bigger offsets increase the security even further by making any attack on the user extremely wasteful. + +The following figure shows how the offset impacts the initial rate and limits the ability of an attacker with limited funds to inflate it effectively. + +image::erc4626-attack-3a.png[Inflation attack without offset=3] +stem:[\delta = 3], stem:[a_0 = 1], stem:[a_1 = 10^5] + +image::erc4626-attack-3b.png[Inflation attack without offset=3 and an attacker deposit that limits its losses] +stem:[\delta = 3], stem:[a_0 = 100], stem:[a_1 = 10^5] + +image::erc4626-attack-6.png[Inflation attack without offset=6] +stem:[\delta = 6], stem:[a_0 = 1], stem:[a_1 = 10^5] + + +[[fees]] +== Custom behavior: Adding fees to the vault + +In an ERC4626 vaults, fees can be captured during the deposit/mint and/or during the withdraw/redeem steps. In both cases it is essential to remain compliant with the ERC4626 requirements with regard to the preview functions. + +For example, if calling `deposit(100, receiver)`, the caller should deposit exactly 100 underlying tokens, including fees, and the receiver should receive a number of shares that matches the value returned by `previewDeposit(100)`. Similarly, `previewMint` should account for the fees that the user will have to pay on top of share's cost. + +As for the `Deposit` event, while this is less clear in the EIP spec itself, there seems to be consensus that it should include the number of assets paid for by the user, including the fees. + +On the other hand, when withdrawing assets, the number given by the user should correspond to what he receives. Any fees should be added to the quote (in shares) performed by `previewWithdraw`. + +The `Withdraw` event should include the number of shares the user burns (including fees) and the number of assets the user actually receives (after fees are deducted). + +The consequence of this design is that both the `Deposit` and `Withdraw` events will describe two exchange rates. The spread between the "Buy-in" and the "Exit" prices correspond to the fees taken by the vault. + +The following example describes how fees proportional to the deposited/withdrawn amount can be implemented: + +```solidity +include::api:example$ERC4626Fees.sol[] +``` diff --git a/docs/modules/ROOT/pages/erc721.adoc b/docs/modules/ROOT/pages/erc721.adoc new file mode 100644 index 000000000..7481c6b62 --- /dev/null +++ b/docs/modules/ROOT/pages/erc721.adoc @@ -0,0 +1,79 @@ += ERC721 + +We've discussed how you can make a _fungible_ token using xref:erc20.adoc[ERC20], but what if not all tokens are alike? This comes up in situations like *real estate*, *voting rights*, or *collectibles*, where some items are valued more than others, due to their usefulness, rarity, etc. ERC721 is a standard for representing ownership of xref:tokens.adoc#different-kinds-of-tokens[_non-fungible_ tokens], that is, where each token is unique. + +ERC721 is a more complex standard than ERC20, with multiple optional extensions, and is split across a number of contracts. The OpenZeppelin Contracts provide flexibility regarding how these are combined, along with custom useful extensions. Check out the xref:api:token/ERC721.adoc[API Reference] to learn more about these. + +== Constructing an ERC721 Token Contract + +We'll use ERC721 to track items in our game, which will each have their own unique attributes. Whenever one is to be awarded to a player, it will be minted and sent to them. Players are free to keep their token or trade it with other people as they see fit, as they would any other asset on the blockchain! Please note any account can call `awardItem` to mint items. To restrict what accounts can mint items we can add xref:access-control.adoc[Access Control]. + +Here's what a contract for tokenized items might look like: + +[source,solidity] +---- +// contracts/GameItem.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {ERC721URIStorage} from "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; + +contract GameItem is ERC721URIStorage { + uint256 private _nextTokenId; + + constructor() ERC721("GameItem", "ITM") {} + + function awardItem(address player, string memory tokenURI) + public + returns (uint256) + { + uint256 tokenId = _nextTokenId++; + _mint(player, tokenId); + _setTokenURI(tokenId, tokenURI); + + return tokenId; + } +} +---- + +The xref:api:token/ERC721.adoc#ERC721URIStorage[`ERC721URIStorage`] contract is an implementation of ERC721 that includes the metadata standard extensions (xref:api:token/ERC721.adoc#IERC721Metadata[`IERC721Metadata`]) as well as a mechanism for per-token metadata. That's where the xref:api:token/ERC721.adoc#ERC721-_setTokenURI-uint256-string-[`_setTokenURI`] method comes from: we use it to store an item's metadata. + +Also note that, unlike ERC20, ERC721 lacks a `decimals` field, since each token is distinct and cannot be partitioned. + +New items can be created: + +[source,javascript] +---- +> gameItem.awardItem(playerAddress, "https://game.example/item-id-8u5h2m.json") +Transaction successful. Transaction hash: 0x... +Events emitted: + - Transfer(0x0000000000000000000000000000000000000000, playerAddress, 7) +---- + +And the owner and metadata of each item queried: + +[source,javascript] +---- +> gameItem.ownerOf(7) +playerAddress +> gameItem.tokenURI(7) +"https://game.example/item-id-8u5h2m.json" +---- + +This `tokenURI` should resolve to a JSON document that might look something like: + +[source,json] +---- +{ + "name": "Thor's hammer", + "description": "MjΓΆlnir, the legendary hammer of the Norse god of thunder.", + "image": "https://game.example/item-id-8u5h2m.png", + "strength": 20 +} +---- + +For more information about the `tokenURI` metadata JSON Schema, check out the https://eips.ethereum.org/EIPS/eip-721[ERC721 specification]. + +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. diff --git a/docs/modules/ROOT/pages/extending-contracts.adoc b/docs/modules/ROOT/pages/extending-contracts.adoc new file mode 100644 index 000000000..1c13d6b4b --- /dev/null +++ b/docs/modules/ROOT/pages/extending-contracts.adoc @@ -0,0 +1,131 @@ += Extending Contracts + +Most of the OpenZeppelin Contracts are expected to be used via https://solidity.readthedocs.io/en/latest/contracts.html#inheritance[inheritance]: you will _inherit_ from them when writing your own contracts. + +This is the commonly found `is` syntax, like in `contract MyToken is ERC20`. + +[NOTE] +==== +Unlike ``contract``s, Solidity ``library``s are not inherited from and instead rely on the https://solidity.readthedocs.io/en/latest/contracts.html#using-for[`using for`] syntax. + +OpenZeppelin Contracts has some ``library``s: most are in the xref:api:utils.adoc[Utils] directory. +==== + +== Overriding + +Inheritance is often used to add the parent contract's functionality to your own contract, but that's not all it can do. You can also _change_ how some parts of the parent behave using _overrides_. + +For example, imagine you want to change xref:api:access.adoc#AccessControl[`AccessControl`] so that xref:api:access.adoc#AccessControl-revokeRole-bytes32-address-[`revokeRole`] can no longer be called. This can be achieved using overrides: + +```solidity +// contracts/ModifiedAccessControl.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; + +contract ModifiedAccessControl is AccessControl { + // Override the revokeRole function + function revokeRole(bytes32, address) public override { + revert("ModifiedAccessControl: cannot revoke roles"); + } +} +``` + +The old `revokeRole` is then replaced by our override, and any calls to it will immediately revert. We cannot _remove_ the function from the contract, but reverting on all calls is good enough. + +=== Calling `super` + +Sometimes you want to _extend_ a parent's behavior, instead of outright changing it to something else. This is where `super` comes in. + +The `super` keyword will let you call functions defined in a parent contract, even if they are overridden. This mechanism can be used to add additional checks to a function, emit events, or otherwise add functionality as you see fit. + +TIP: For more information on how overrides work, head over to the https://solidity.readthedocs.io/en/latest/contracts.html#index-17[official Solidity documentation]. + +Here is a modified version of xref:api:access.adoc#AccessControl[`AccessControl`] where xref:api:access.adoc#AccessControl-revokeRole-bytes32-address-[`revokeRole`] cannot be used to revoke the `DEFAULT_ADMIN_ROLE`: + + +```solidity +// contracts/ModifiedAccessControl.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; + +contract ModifiedAccessControl is AccessControl { + function revokeRole(bytes32 role, address account) public override { + require( + role != DEFAULT_ADMIN_ROLE, + "ModifiedAccessControl: cannot revoke default admin role" + ); + + super.revokeRole(role, account); + } +} +``` + +The `super.revokeRole` statement at the end will invoke ``AccessControl``'s original version of `revokeRole`, the same code that would've run if there were no overrides in place. + +NOTE: The same rule is implemented and extended in xref:api:access.adoc#AccessControlDefaultAdminRules[`AccessControlDefaultAdminRules`], an extension that also adds enforced security measures for the `DEFAULT_ADMIN_ROLE`. + +[[using-hooks]] +== Using Hooks + +Sometimes, in order to extend a parent contract you will need to override multiple related functions, which leads to code duplication and increased likelihood of bugs. + +For example, consider implementing safe xref:api:token/ERC20.adoc#ERC20[`ERC20`] transfers in the style of xref:api:token/ERC721.adoc#IERC721Receiver[`IERC721Receiver`]. You may think overriding xref:api:token/ERC20.adoc#ERC20-transfer-address-uint256-[`transfer`] and xref:api:token/ERC20.adoc#ERC20-transferFrom-address-address-uint256-[`transferFrom`] would be enough, but what about xref:api:token/ERC20.adoc#ERC20-_transfer-address-address-uint256-[`_transfer`] and xref:api:token/ERC20.adoc#ERC20-_mint-address-uint256-[`_mint`]? To prevent you from having to deal with these details, we introduced **hooks**. + +Hooks are simply functions that are called before or after some action takes place. They provide a centralized point to _hook into_ and extend the original behavior. + +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.20; + +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +contract ERC20WithSafeTransfer is ERC20 { + function _beforeTokenTransfer(address from, address to, uint256 amount) + internal virtual override + { + super._beforeTokenTransfer(from, to, amount); + + require(_validRecipient(to), "ERC20WithSafeTransfer: invalid recipient"); + } + + function _validRecipient(address to) private view returns (bool) { + ... + } + + ... +} +``` + +Using hooks this way leads to cleaner and safer code, without having to rely on a deep understanding of the parent's internals. + +=== Rules of Hooks + +There's a few guidelines you should follow when writing code that uses hooks in order to prevent issues. They are very simple, but do make sure you follow them: + +1. Whenever you override a parent's hook, re-apply the `virtual` attribute to the hook. That will allow child contracts to add more functionality to the hook. +2. **Always** call the parent's hook in your override using `super`. This will make sure all hooks in the inheritance tree are called: contracts like xref:api:token/ERC20.adoc#ERC20Pausable[`ERC20Pausable`] rely on this behavior. + +```solidity +contract MyToken is ERC20 { + function _beforeTokenTransfer(address from, address to, uint256 amount) + internal virtual override // Add virtual here! + { + super._beforeTokenTransfer(from, to, amount); // Call parent hook + ... + } +} +``` +That's it! Enjoy simpler code using hooks! + +== Security + +The maintainers of OpenZeppelin Contracts are mainly concerned with the correctness and security of the code as published in the library, and the combinations of base contracts with the official extensions from the library. + +Custom overrides, and those of hooks in particular, may break some important assumptions and introduce vulnerabilities in otherwise secure code. While we try to ensure the contracts remain secure in the face of a wide range of potential customizations, this is done in a best-effort manner. While we try to document all important assumptions, this should not be relied upon. Custom overrides should be carefully reviewed and checked against the source code of the contract they are customizing so as to fully understand their impact and guarantee their security. + +The way functions interact internally should not be assumed to stay stable across releases of the library. For example, a function that is used in one context in a particular release may not be used in the same context in the next release. Contracts that override functions should revalidate their assumptions when updating the version of OpenZeppelin Contracts they are built on. 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/governance.adoc b/docs/modules/ROOT/pages/governance.adoc new file mode 100644 index 000000000..b8db64230 --- /dev/null +++ b/docs/modules/ROOT/pages/governance.adoc @@ -0,0 +1,240 @@ += How to set up on-chain governance + +In this guide we will learn how OpenZeppelin’s Governor contract works, how to set it up, and how to use it to create proposals, vote for them, and execute them, using tools provided by Ethers.js and Tally. + +NOTE: Find detailed contract documentation at xref:api:governance.adoc[Governance API]. + +== Introduction + +Decentralized protocols are in constant evolution from the moment they are publicly released. Often, the initial team retains control of this evolution in the first stages, but eventually delegates it to a community of stakeholders. The process by which this community makes decisions is called on-chain governance, and it has become a central component of decentralized protocols, fueling varied decisions such as parameter tweaking, smart contract upgrades, integrations with other protocols, treasury management, grants, etc. + +This governance protocol is generally implemented in a special-purpose contract called β€œGovernor”. The GovernorAlpha and GovernorBravo contracts designed by Compound have been very successful and popular so far, with the downside that projects with different requirements have had to fork the code to customize it for their needs, which can pose a high risk of introducing security issues. For OpenZeppelin Contracts, we set out to build a modular system of Governor contracts so that forking is not needed, and different requirements can be accommodated by writing small modules using Solidity inheritance. You will find the most common requirements out of the box in OpenZeppelin Contracts, but writing additional ones is simple, and we will be adding new features as requested by the community in future releases. Additionally, the design of OpenZeppelin Governor requires minimal use of storage and results in more gas efficient operation. + +== Compatibility + +OpenZeppelin’s Governor system was designed with a concern for compatibility with existing systems that were based on Compound’s GovernorAlpha and GovernorBravo. Because of this, you will find that many modules are presented in two variants, one of which is built for compatibility with those systems. + +=== ERC20Votes & ERC20VotesComp + +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 & GovernorStorage + +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, 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 + +When using a timelock with your Governor contract, you can use either OpenZeppelin’s TimelockController or Compound’s Timelock. Based on the choice of timelock, you should choose the corresponding Governor module: GovernorTimelockControl or GovernorTimelockCompound respectively. This allows you to migrate an existing GovernorAlpha instance to an OpenZeppelin-based Governor without changing the timelock in use. + +=== Tally + +https://www.tally.xyz[Tally] is a full-fledged application for user owned on-chain governance. It comprises a voting dashboard, proposal creation wizard, real time research and analysis, and educational content. + +For all of these options, the Governor will be compatible with Tally: users will be able to create proposals, visualize voting power and advocates, navigate proposals, and cast votes. For proposal creation in particular, projects can also use Defender Admin as an alternative interface. + +In the rest of this guide, we will focus on a fresh deploy of the vanilla OpenZeppelin Governor features without concern for compatibility with GovernorAlpha or Bravo. + +== Setup + +=== Token + +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 +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 +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`]. + +NOTE: The internal clock used by the token to store voting balances will dictate the operating mode of the Governor contract attached to it. By default, block numbers are used. Since v4.9, developers can override the xref:api:interfaces.adoc#IERC6372[IERC6372] clock to use timestamps instead of block numbers. + +=== Governor + +Initially, we will build a Governor without a timelock. The core logic is given by the Governor contract, but we still need to choose: 1) how voting power is determined, 2) how many votes are needed for quorum, 3) what options people have when casting a vote and how those votes are counted, and 4) what type of token should be used to vote. Each of these aspects is customizable by writing your own module, or more easily choosing one from OpenZeppelin Contracts. + +For 1) we will use the GovernorVotes module, which hooks to an IVotes instance to determine the voting power of an account based on the token balance they hold when a proposal becomes active. This module requires as a constructor parameter the address of the token. This module also discovers the clock mode (ERC6372) used by the token and applies it to the Governor. + +For 2) we will use GovernorVotesQuorumFraction which works together with ERC20Votes to define quorum as a percentage of the total supply at the block a proposal’s voting power is retrieved. This requires a constructor parameter to set the percentage. Most Governors nowadays use 4%, so we will initialize the module with parameter 4 (this indicates the percentage, resulting in 4%). + +For 3) we will use GovernorCountingSimple, a module that offers 3 options to voters: For, Against, and Abstain, and where only For and Abstain votes are counted towards quorum. + +Besides these modules, Governor itself has some parameters we must set. + +votingDelay: How long after a proposal is created should voting power be fixed. A large voting delay gives users time to unstake tokens if necessary. + +votingPeriod: How long does a proposal remain open to votes. + +These parameters are specified in the unit defined in the token's clock. Assuming the token uses block numbers, and assuming block time of around 12 seconds, we will have set votingDelay = 1 day = 7200 blocks, and votingPeriod = 1 week = 50400 blocks. + +We can optionally set a proposal threshold as well. This restricts proposal creation to accounts who have enough voting power. + +```solidity +include::api:example$governance/MyGovernor.sol[] +``` + +=== Timelock + +It is good practice to add a timelock to governance decisions. This allows users to exit the system if they disagree with a decision before it is executed. We will use OpenZeppelin’s TimelockController in combination with the GovernorTimelockControl module. + +IMPORTANT: When using a timelock, it is the timelock that will execute proposals and thus the timelock that should hold any funds, ownership, and access control roles. Before version 4.5 there was no way to recover funds in the Governor contract when using a timelock! Before version 4.3, when using the Compound Timelock, ETH in the timelock was not easily accessible. + +TimelockController uses an AccessControl setup that we need to understand in order to set up roles. + +- The Proposer role is in charge of queueing operations: this is the role the Governor instance should be granted, and it should likely be the only proposer in the system. +- The Executor role is in charge of executing already available operations: we can assign this role to the special zero address to allow anyone to execute (if operations can be particularly time sensitive, the Governor should be made Executor instead). +- Lastly, there is the Admin role, which can grant and revoke the two previous roles: this is a very sensitive role that will be granted automatically to the timelock itself, and optionally to a second account, which can be used for ease of setup but should promptly renounce the role. + +== Proposal Lifecycle + +Let’s walk through how to create and execute a proposal on our newly deployed Governor. + +A proposal is a sequence of actions that the Governor contract will perform if it passes. Each action consists of a target address, calldata encoding a function call, and an amount of ETH to include. Additionally, a proposal includes a human-readable description. + +=== Create a Proposal + +Let’s say we want to create a proposal to give a team a grant, in the form of ERC20 tokens from the governance treasury. This proposal will consist of a single action where the target is the ERC20 token, calldata is the encoded function call `transfer(, )`, and with 0 ETH attached. + +Generally a proposal will be created with the help of an interface such as Tally or Defender. Here we will show how to create the proposal using Ethers.js. + +First we get all the parameters necessary for the proposal action. + +```javascript +const tokenAddress = ...; +const token = await ethers.getContractAt(β€˜ERC20’, tokenAddress); + +const teamAddress = ...; +const grantAmount = ...; +const transferCalldata = token.interface.encodeFunctionData(β€˜transfer’, [teamAddress, grantAmount]); +``` + +Now we are ready to call the propose function of the Governor. Note that we don’t pass in one array of actions, but instead three arrays corresponding to the list of targets, the list of values, and the list of calldatas. In this case it’s a single action, so it’s simple: + +```javascript +await governor.propose( + [tokenAddress], + [0], + [transferCalldata], + β€œProposal #1: Give grant to team”, +); +``` + +This will create a new proposal, with a proposal id that is obtained by hashing together the proposal data, and which will also be found in an event in the logs of the transaction. + +=== Cast a Vote + +Once a proposal is active, delegates can cast their vote. Note that it is delegates who carry voting power: if a token holder wants to participate, they can set a trusted representative as their delegate, or they can become a delegate themselves by self-delegating their voting power. + +Votes are cast by interacting with the Governor contract through the `castVote` family of functions. Voters would generally invoke this from a governance UI such as Tally. + +image::tally-vote.png[Voting in Tally] + +=== Execute the Proposal + +Once the voting period is over, if quorum was reached (enough voting power participated) and the majority voted in favor, the proposal is considered successful and can proceed to be executed. Once a proposal passes, it can be queued and executed from the same place you voted. + +image::tally-exec.png[Administration Panel in Tally] + +We will see now how to do this manually using Ethers.js. + +If a timelock was set up, the first step to execution is queueing. You will notice that both the queue and execute functions require passing in the entire proposal parameters, as opposed to just the proposal id. This is necessary because this data is not stored on chain, as a measure to save gas. Note that these parameters can always be found in the events emitted by the contract. The only parameter that is not sent in its entirety is the description, since this is only needed in its hashed form to compute the proposal id. + +To queue, we call the queue function: + +```javascript +const descriptionHash = ethers.utils.id(β€œProposal #1: Give grant to team”); + +await governor.queue( + [tokenAddress], + [0], + [transferCalldata], + descriptionHash, +); +``` + +This will cause the Governor to interact with the timelock contract and queue the actions for execution after the required delay. + +After enough time has passed (according to the timelock parameters), the proposal can be executed. If there was no timelock to begin with, this step can be ran immediately after the proposal succeeds. + +```javascript +await governor.execute( + [tokenAddress], + [0], + [transferCalldata], + descriptionHash, +); +``` + +Executing the proposal will transfer the ERC20 tokens to the chosen recipient. To wrap up: we set up a system where a treasury is controlled by the collective decision of the token holders of a project, and all actions are executed via proposals enforced by on-chain votes. + +== Timestamp based governance + +=== Motivation + +It is sometimes difficult to deal with durations expressed in number of blocks because of inconsistent or unpredictable time between blocks. This is particularly true of some L2 networks where blocks are produced based on blockchain usage. Using number of blocks can also lead to the governance rules being affected by network upgrades that modify the expected time between blocks. + +The difficulty of replacing block numbers with timestamps is that the Governor and the token must both use the same format when querying past votes. If a token is designed around block numbers, it is not possible for a Governor to reliably do timestamp based lookups. + +Therefore, designing a timestamp based voting system starts with the token. + +=== 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 +include::api:example$governance/MyTokenTimestampBased.sol[] +``` + +=== Governor + +The Governor will automatically detect the clock mode used by the token and adapt to it. There is no need to override anything in the Governor contract. However, the clock mode does affect how some values are interpreted. It is therefore necessary to set the `votingDelay()` and `votingPeriod()` accordingly. + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {Governor} from "@openzeppelin/contracts/governance/Governor.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, GovernorCountingSimple, GovernorVotes, GovernorVotesQuorumFraction, GovernorTimelockControl { + constructor(IVotes _token, TimelockController _timelock) + Governor("MyGovernor") + GovernorVotes(_token) + GovernorVotesQuorumFraction(4) + GovernorTimelockControl(_timelock) + {} + + function votingDelay() public pure virtual override returns (uint256) { + return 1 days; + } + + function votingPeriod() public pure virtual override returns (uint256) { + return 1 weeks; + } + + function proposalThreshold() public pure virtual override returns (uint256) { + return 0; + } + + // ... +} + +``` + +=== Disclaimer + +Timestamp based voting is a recent feature that was formalized in EIP-6372 and EIP-5805, and introduced in v4.9. At the time this feature is released, governance tooling such as https://www.tally.xyz[Tally] does not support it yet. While support for timestamps should come soon, users can expect invalid reporting of deadlines & durations. This invalid reporting by offchain tools does not affect the onchain security and functionality of the governance contract. + +Governors with timestamp support (v4.9 and above) are compatible with old tokens (before v4.9) and will operate in "block number" mode (which is the mode all old tokens operate on). On the other hand, old Governor instances (before v4.9) are not compatible with new tokens operating using timestamps. If you update your token code to use timestamps, make sure to also update your Governor code. diff --git a/docs/modules/ROOT/pages/index.adoc b/docs/modules/ROOT/pages/index.adoc new file mode 100644 index 000000000..905ce231d --- /dev/null +++ b/docs/modules/ROOT/pages/index.adoc @@ -0,0 +1,79 @@ += Contracts + +*A library for secure smart contract development.* Build on a solid foundation of community-vetted code. + + * Implementations of standards like xref:erc20.adoc[ERC20] and xref:erc721.adoc[ERC721]. + * Flexible xref:access-control.adoc[role-based permissioning] scheme. + * Reusable xref:utilities.adoc[Solidity components] to build custom contracts and complex decentralized systems. + +== Overview + +[[install]] +=== Installation + +==== Hardhat, Truffle (npm) + +```console +$ npm install @openzeppelin/contracts +``` + +OpenZeppelin Contracts features a xref:releases-stability.adoc#api-stability[stable API], which means that your contracts won't break unexpectedly when upgrading to a newer minor version. + +==== 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. + +```console +$ forge install OpenZeppelin/openzeppelin-contracts +``` + +Add `@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/` in `remappings.txt.` + +[[usage]] +=== Usage + +Once installed, you can use the contracts in the library by importing them: + +[source,solidity] +---- +// contracts/MyNFT.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; + +contract MyNFT is ERC721 { + constructor() ERC721("MyNFT", "MNFT") { + } +} +---- + +TIP: If you're new to smart contract development, head to xref:learn::developing-smart-contracts.adoc[Developing Smart Contracts] to learn about creating a new project and compiling your contracts. + +To keep your system secure, you should **always** use the installed code as-is, and neither copy-paste it from online sources, nor modify it yourself. The library is designed so that only the contracts and functions you use are deployed, so you don't need to worry about it needlessly increasing gas costs. + +[[security]] +== Security + +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 + +The guides in the sidebar will teach about different concepts, and how to use the related contracts that OpenZeppelin Contracts provides: + +* xref:access-control.adoc[Access Control]: decide who can perform each of the actions on your system. +* xref:tokens.adoc[Tokens]: create tradable assets or collectibles, like the well known xref:erc20.adoc[ERC20] and xref:erc721.adoc[ERC721] standards. +* xref:utilities.adoc[Utilities]: generic useful tools, including non-overflowing math, signature verification, and trustless paying systems. + +The xref:api:token/ERC20.adoc[full API] is also thoroughly documented, and serves as a great reference when developing your smart contract application. You can also ask for help or follow Contracts' development in the https://forum.openzeppelin.com[community forum]. + +Finally, you may want to take a look at the https://blog.openzeppelin.com/guides/[guides on our blog], which cover several common use cases and good practices. The following articles provide great background reading, though please note, some of the referenced tools have changed as the tooling in the ecosystem continues to rapidly evolve. + +* https://blog.openzeppelin.com/the-hitchhikers-guide-to-smart-contracts-in-ethereum-848f08001f05[The Hitchhiker’s Guide to Smart Contracts in Ethereum] will help you get an overview of the various tools available for smart contract development, and help you set up your environment. +* https://blog.openzeppelin.com/a-gentle-introduction-to-ethereum-programming-part-1-783cc7796094[A Gentle Introduction to Ethereum Programming, Part 1] provides very useful information on an introductory level, including many basic concepts from the Ethereum platform. +* For a more in-depth dive, you may read the guide https://blog.openzeppelin.com/designing-the-architecture-for-your-ethereum-application-9cec086f8317[Designing the architecture for your Ethereum application], which discusses how to better structure your application and its relationship to the real world. diff --git a/docs/modules/ROOT/pages/releases-stability.adoc b/docs/modules/ROOT/pages/releases-stability.adoc new file mode 100644 index 000000000..9a3310377 --- /dev/null +++ b/docs/modules/ROOT/pages/releases-stability.adoc @@ -0,0 +1,85 @@ += New Releases and API Stability + +Developing smart contracts is hard, and a conservative approach towards dependencies is sometimes favored. However, it is also very important to stay on top of new releases: these may include bug fixes, or deprecate old patterns in favor of newer and better practices. + +Here we describe when you should expect new releases to come out, and how this affects you as a user of OpenZeppelin Contracts. + +[[release-schedule]] +== Release Schedule + +OpenZeppelin Contracts follows a <>. + +We aim for a new minor release every 1 or 2 months. + +[[minor-releases]] +=== Release Candidates + +Before every release, we publish a feature-frozen release candidate. The purpose of the release candidate is to have a period where the community can review the new code before the actual release. If important problems are discovered, several more release candidates may be required. After a week of no more changes to the release candidate, the new version is published. + +[[major-releases]] +=== Major Releases + +After several months or a year, a new major release may come out. These are not scheduled, but will be based on the need to release breaking changes such as a redesign of a core feature of the library (e.g. https://github.com/OpenZeppelin/openzeppelin-contracts/pulls/2112[access control] in 3.0). Since we value stability, we aim for these to happen infrequently (expect no less than six months between majors). However, we may be forced to release one when there are big changes to the Solidity language. + +[[api-stability]] +== API Stability + +On the https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v2.0.0[OpenZeppelin Contracts 2.0 release], we committed ourselves to keeping a stable API. We aim to more precisely define what we understand by _stable_ and _API_ here, so users of the library can understand these guarantees and be confident their project won't break unexpectedly. + +In a nutshell, the API being stable means _if your project is working today, it will continue to do so after a minor upgrade_. New contracts and features will be added in minor releases, but only in a backwards compatible way. + +[[versioning-scheme]] +=== Versioning Scheme + +We follow https://semver.org/[SemVer], which means API breakage may occur between major releases (which <>). + +[[solidity-functions]] +=== Solidity Functions + +While the internal implementation of functions may change, their semantics and signature will remain the same. The domain of their arguments will not be less restrictive (e.g. if transferring a value of 0 is disallowed, it will remain disallowed), nor will general state restrictions be lifted (e.g. `whenPaused` modifiers). + +If new functions are added to a contract, it will be in a backwards-compatible way: their usage won't be mandatory, and they won't extend functionality in ways that may foreseeably break an application (e.g. https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1512[an `internal` method may be added to make it easier to retrieve information that was already available]). + +[[internal]] +==== `internal` + +This extends not only to `external` and `public` functions, but also `internal` ones: many contracts are meant to be used by inheriting them (e.g. `Pausable`, `PullPayment`, `AccessControl`), and are therefore used by calling these functions. Similarly, since all OpenZeppelin Contracts state variables are `private`, they can only be accessed this way (e.g. to create new `ERC20` tokens, instead of manually modifying `totalSupply` and `balances`, `_mint` should be called). + +`private` functions have no guarantees on their behavior, usage, or existence. + +Finally, sometimes language limitations will force us to e.g. make `internal` a function that should be `private` in order to implement features the way we want to. These cases will be well documented, and the normal stability guarantees won't apply. + +[[libraries]] +=== Libraries + +Some of our Solidity libraries use ``struct``s to handle internal data that should not be accessed directly (e.g. `Counter`). There's an https://github.com/ethereum/solidity/issues/4637[open issue] in the Solidity repository requesting a language feature to prevent said access, but it looks like it won't be implemented any time soon. Because of this, we will use leading underscores and mark said `struct` s to make it clear to the user that its contents and layout are _not_ part of the API. + +[[events]] +=== Events + +No events will be removed, and their arguments won't be changed in any way. New events may be added in later versions, and existing events may be emitted under new, reasonable circumstances (e.g. https://github.com/OpenZeppelin/openzeppelin-contracts/issues/707[from 2.1 on, `ERC20` also emits `Approval` on `transferFrom` calls]). + +[[drafts]] +=== Drafts + +Some contracts implement EIPs that are still in Draft status, recognizable by a file name beginning with `draft-`, such as `utils/cryptography/draft-EIP712.sol`. Due to their nature as drafts, the details of these contracts may change and we cannot guarantee their stability. Minor releases of OpenZeppelin Contracts may contain breaking changes for the contracts labelled as Drafts, which will be duly announced in the https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/CHANGELOG.md[changelog]. The EIPs included are used by projects in production and this may make them less likely to change significantly. + +[[gas-costs]] +=== Gas Costs + +While attempts will generally be made to lower the gas costs of working with OpenZeppelin Contracts, there are no guarantees regarding this. In particular, users should not assume gas costs will not increase when upgrading library versions. + +[[bugfixes]] +=== Bug Fixes + +The API stability guarantees may need to be broken in order to fix a bug, and we will do so. This decision won't be made lightly however, and all options will be explored to make the change as non-disruptive as possible. When sufficient, contracts or functions which may result in unsafe behavior will be deprecated instead of removed (e.g. https://github.com/OpenZeppelin/openzeppelin-contracts/pull/1543[#1543] and https://github.com/OpenZeppelin/openzeppelin-contracts/pull/1550[#1550]). + +[[solidity-compiler-version]] +=== Solidity Compiler Version + +Starting on version 0.5.0, the Solidity team switched to a faster release cycle, with minor releases every few weeks (v0.5.0 was released on November 2018, and v0.5.5 on March 2019), and major, breaking-change releases every couple of months (with v0.6.0 released on December 2019 and v0.7.0 on July 2020). Including the compiler version in OpenZeppelin Contract's stability guarantees would therefore force the library to either stick to old compilers, or release frequent major updates simply to keep up with newer Solidity releases. + +Because of this, *the minimum required Solidity compiler version is not part of the stability guarantees*, and users may be required to upgrade their compiler when using newer versions of Contracts. Bug fixes will still be backported to past major releases so that all versions currently in use receive these updates. + +You can read more about the rationale behind this, the other options we considered and why we went down this path https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1498#issuecomment-449191611[here]. + diff --git a/docs/modules/ROOT/pages/tokens.adoc b/docs/modules/ROOT/pages/tokens.adoc new file mode 100644 index 000000000..10626f548 --- /dev/null +++ b/docs/modules/ROOT/pages/tokens.adoc @@ -0,0 +1,31 @@ += Tokens + +Ah, the "token": blockchain's most powerful and most misunderstood tool. + +A token is a _representation of something in the blockchain_. This something can be money, time, services, shares in a company, a virtual pet, anything. By representing things as tokens, we can allow smart contracts to interact with them, exchange them, create or destroy them. + +[[but_first_coffee_a_primer_on_token_contracts]] +== But First, [strikethrough]#Coffee# a Primer on Token Contracts + +Much of the confusion surrounding tokens comes from two concepts getting mixed up: _token contracts_ and the actual _tokens_. + +A _token contract_ is simply an Ethereum smart contract. "Sending tokens" actually means "calling a method on a smart contract that someone wrote and deployed". At the end of the day, a token contract is not much more than a mapping of addresses to balances, plus some methods to add and subtract from those balances. + +It is these balances that represent the _tokens_ themselves. Someone "has tokens" when their balance in the token contract is non-zero. That's it! These balances could be considered money, experience points in a game, deeds of ownership, or voting rights, and each of these tokens would be stored in different token contracts. + +[[different-kinds-of-tokens]] +== Different Kinds of Tokens + +Note that there's a big difference between having two voting rights and two deeds of ownership: each vote is equal to all others, but houses usually are not! This is called https://en.wikipedia.org/wiki/Fungibility[fungibility]. _Fungible goods_ are equivalent and interchangeable, like Ether, fiat currencies, and voting rights. _Non-fungible_ goods are unique and distinct, like deeds of ownership, or collectibles. + +In a nutshell, when dealing with non-fungibles (like your house) you care about _which ones_ you have, while in fungible assets (like your bank account statement) what matters is _how much_ you have. + +== Standards + +Even though the concept of a token is simple, they have a variety of complexities in the implementation. Because everything in Ethereum is just a smart contract, and there are no rules about what smart contracts have to do, the community has developed a variety of *standards* (called EIPs or ERCs) for documenting how a contract can interoperate with other contracts. + +You've probably heard of the ERC20 or ERC721 token standards, and that's why you're here. Head to our specialized guides to learn more about these: + + * 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: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/upgradeable.adoc b/docs/modules/ROOT/pages/upgradeable.adoc new file mode 100644 index 000000000..7324c9bad --- /dev/null +++ b/docs/modules/ROOT/pages/upgradeable.adoc @@ -0,0 +1,73 @@ += Using with Upgrades + +If your contract is going to be deployed with upgradeability, such as using the xref:upgrades-plugins::index.adoc[OpenZeppelin Upgrades Plugins], you will need to use the Upgradeable variant of OpenZeppelin Contracts. + +This variant is available as a separate package called `@openzeppelin/contracts-upgradeable`, which is hosted in the repository https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable[OpenZeppelin/openzeppelin-contracts-upgradeable]. + +It follows all of the rules for xref:upgrades-plugins::writing-upgradeable.adoc[Writing Upgradeable Contracts]: constructors are replaced by initializer functions, state variables are initialized in initializer functions, and we additionally check for storage incompatibilities across minor versions. + +TIP: OpenZeppelin provides a full suite of tools for deploying and securing upgradeable smart contracts. xref:openzeppelin::upgrades.adoc[Check out the full list of resources]. + +== Overview + +=== Installation + +```console +$ npm install @openzeppelin/contracts-upgradeable +``` + +=== Usage + +The package replicates the structure of the main OpenZeppelin Contracts package, but every file and contract has the suffix `Upgradeable`. + +```diff +-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 { +``` + +Constructors are replaced by internal initializer functions following the naming convention `+__{ContractName}_init+`. Since these are internal, you must always define your own public initializer function and call the parent initializer of the contract you extend. + +```diff +- constructor() ERC721("MyCollectible", "MCO") public { ++ function initialize() initializer public { ++ __ERC721_init("MyCollectible", "MCO"); + } +``` + +CAUTION: Use with multiple inheritance requires special attention. See the section below titled <>. + +Once this contract is set up and compiled, you can deploy it using the xref:upgrades-plugins::index.adoc[Upgrades Plugins]. The following snippet shows an example deployment script using Hardhat. + +```js +// scripts/deploy-my-collectible.js +const { ethers, upgrades } = require("hardhat"); + +async function main() { + const MyCollectible = await ethers.getContractFactory("MyCollectible"); + + const mc = await upgrades.deployProxy(MyCollectible); + + await mc.deployed(); + console.log("MyCollectible deployed to:", mc.address); +} + +main(); +``` + +== Further Notes + +[[multiple-inheritance]] +=== Multiple Inheritance + +Initializer functions are not linearized by the compiler like constructors. Because of this, each `+__{ContractName}_init+` function embeds the linearized calls to all parent initializers. As a consequence, calling two of these `init` functions can potentially initialize the same contract twice. + +The function `+__{ContractName}_init_unchained+` found in every contract is the initializer function minus the calls to parent initializers, and can be used to avoid the double initialization problem, but doing this manually is not recommended. We hope to be able to implement safety checks for this in future versions of the Upgrades Plugins. + +=== Storage Gaps + +You may notice that every contract includes a state variable named `+__gap+`. This is empty reserved space in storage that is put in place in Upgradeable contracts. It allows us to freely add new state variables in the future without compromising the storage compatibility with existing deployments. + +It isn't safe to simply add a state variable because it "shifts down" all of the state variables below in the inheritance chain. This makes the storage layouts incompatible, as explained in xref:upgrades-plugins::writing-upgradeable.adoc#modifying-your-contracts[Writing Upgradeable Contracts]. The size of the `+__gap+` array is calculated so that the amount of storage used by a contract always adds up to the same number (in this case 50 storage slots). diff --git a/docs/modules/ROOT/pages/utilities.adoc b/docs/modules/ROOT/pages/utilities.adoc new file mode 100644 index 000000000..487b47a80 --- /dev/null +++ b/docs/modules/ROOT/pages/utilities.adoc @@ -0,0 +1,185 @@ += Utilities + +The OpenZeppelin Contracts provide a ton of useful utilities that you can use in your project. Here are some of the more popular ones. + +[[cryptography]] +== Cryptography + +=== Checking Signatures On-Chain + +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#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 + .toEthSignedMessageHash() + .recover(signature) == account; +} +---- + +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 + +xref:api:utils.adoc#MerkleProof[`MerkleProof`] provides: + +* xref:api:utils.adoc#MerkleProof-verify-bytes32---bytes32-bytes32-[`verify`] - can prove that some value is part of a https://en.wikipedia.org/wiki/Merkle_tree[Merkle tree]. + +* xref:api:utils.adoc#MerkleProof-multiProofVerify-bytes32-bytes32---bytes32---bool---[`multiProofVerify`] - can prove multiple values are part of a Merkle tree. + +[[introspection]] +== Introspection + +In Solidity, it's frequently helpful to know whether or not a contract supports an interface you'd like to use. ERC165 is a standard that helps do runtime interface detection. Contracts provide helpers both for implementing ERC165 in your contracts and querying other contracts: + +* xref:api:utils.adoc#IERC165[`IERC165`] β€” this is the ERC165 interface that defines xref:api:utils.adoc#IERC165-supportsInterface-bytes4-[`supportsInterface`]. When implementing ERC165, you'll conform to this interface. +* xref:api:utils.adoc#ERC165[`ERC165`] β€” inherit this contract if you'd like to support interface detection using a lookup table in contract storage. You can register interfaces using xref:api:utils.adoc#ERC165-_registerInterface-bytes4-[`_registerInterface(bytes4)`]: check out example usage as part of the ERC721 implementation. +* xref:api:utils.adoc#ERC165Checker[`ERC165Checker`] β€” ERC165Checker simplifies the process of checking whether or not a contract supports an interface you care about. +* include with `using ERC165Checker for address;` +* xref:api:utils.adoc#ERC165Checker-_supportsInterface-address-bytes4-[`myAddress._supportsInterface(bytes4)`] +* xref:api:utils.adoc#ERC165Checker-_supportsAllInterfaces-address-bytes4---[`myAddress._supportsAllInterfaces(bytes4[\])`] + +[source,solidity] +---- +contract MyContract { + using ERC165Checker for address; + + bytes4 private InterfaceId_ERC721 = 0x80ac58cd; + + /** + * @dev transfer an ERC721 token from this contract to someone else + */ + function transferERC721( + address token, + address to, + uint256 tokenId + ) + public + { + require(token.supportsInterface(InterfaceId_ERC721), "IS_NOT_721_TOKEN"); + IERC721(token).transferFrom(address(this), to, tokenId); + } +} +---- + +[[math]] +== Math + +The most popular math related library OpenZeppelin Contracts provides is xref:api:utils.adoc#SafeMath[`SafeMath`], which provides mathematical functions that protect your contract from overflows and underflows. + +Include the contract with `using SafeMath for uint256;` and then call the functions: + +* `myNumber.add(otherNumber)` +* `myNumber.sub(otherNumber)` +* `myNumber.div(otherNumber)` +* `myNumber.mul(otherNumber)` +* `myNumber.mod(otherNumber)` + +Easy! + +[[payment]] +== Payment + +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. + +[[collections]] +== Collections + +If you need support for more powerful collections than Solidity's native arrays and mappings, take a look at xref:api:utils.adoc#EnumerableSet[`EnumerableSet`] and xref:api:utils.adoc#EnumerableMap[`EnumerableMap`]. They are similar to mappings in that they store and remove elements in constant time and don't allow for repeated entries, but they also support _enumeration_, which means you can easily query all stored entries both on and off-chain. + +[[misc]] +== Misc + +=== Base64 + +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. + +Here is an example to send JSON Metadata through a Base64 Data URI using an ERC721: + +[source, solidity] +---- +// contracts/My721Token.sol +// SPDX-License-Identifier: MIT + +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; + + constructor() ERC721("My721Token", "MTK") {} + + ... + + function tokenURI(uint256 tokenId) + public + pure + override + returns (string memory) + { + bytes memory dataURI = abi.encodePacked( + '{', + '"name": "My721Token #', tokenId.toString(), '"', + // Replace with extra ERC721 Metadata properties + '}' + ); + + return string( + abi.encodePacked( + "data:application/json;base64,", + Base64.encode(dataURI) + ) + ); + } +} +---- + +=== Multicall + +The `Multicall` abstract contract comes with a `multicall` function that bundles together multiple calls in a single external call. With it, external accounts may perform atomic operations comprising several function calls. This is not only useful for EOAs to make multiple calls in a single transaction, it's also a way to revert a previous call if a later one fails. + +Consider this dummy contract: + +[source,solidity] +---- +// contracts/Box.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "@openzeppelin/contracts/utils/Multicall.sol"; + +contract Box is Multicall { + function foo() public { + ... + } + + function bar() public { + ... + } +} +---- + +This is how to call the `multicall` function using Truffle, allowing `foo` and `bar` to be called in a single transaction: +[source,javascript] +---- +// scripts/foobar.js + +const Box = artifacts.require('Box'); +const instance = await Box.new(); + +await instance.multicall([ + instance.contract.methods.foo().encodeABI(), + instance.contract.methods.bar().encodeABI() +]); +---- diff --git a/docs/modules/ROOT/pages/wizard.adoc b/docs/modules/ROOT/pages/wizard.adoc new file mode 100644 index 000000000..262505378 --- /dev/null +++ b/docs/modules/ROOT/pages/wizard.adoc @@ -0,0 +1,15 @@ += Contracts Wizard +:page-notoc: + +Not sure where to start? Use the interactive generator below to bootstrap your +contract and learn about the components offered in OpenZeppelin Contracts. + +TIP: Place the resulting contract in your `contracts` directory in order to compile it with a tool like Hardhat or Truffle. Consider reading our guide on xref:learn::developing-smart-contracts.adoc[Developing Smart Contracts] for more guidance! + +++++ + + + +++++ + + diff --git a/docs/modules/api/examples/ERC4626Fees.sol b/docs/modules/api/examples/ERC4626Fees.sol new file mode 100644 index 000000000..b508efe4f --- /dev/null +++ b/docs/modules/api/examples/ERC4626Fees.sol @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {ERC4626} from "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol"; +import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import {Math} from "@openzeppelin/contracts/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; + + 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, _entryFeeBasisPoints()); + return super.previewDeposit(assets - fee); + } + + /// @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, _entryFeeBasisPoints()); + } + + /// @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, _exitFeeBasisPoints()); + return super.previewWithdraw(assets + fee); + } + + /// @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, _exitFeeBasisPoints()); + } + + /// @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, _entryFeeBasisPoints()); + address recipient = _entryFeeRecipient(); + + super._deposit(caller, receiver, assets, shares); + + if (fee > 0 && recipient != address(this)) { + SafeERC20.safeTransfer(IERC20(asset()), recipient, fee); + } + } + + /// @dev Send exit fee to {_exitFeeRecipient}. See {IERC4626-_deposit}. + function _withdraw( + address caller, + address receiver, + address owner, + uint256 assets, + uint256 shares + ) internal virtual override { + uint256 fee = _feeOnRaw(assets, _exitFeeBasisPoints()); + address recipient = _exitFeeRecipient(); + + super._withdraw(caller, receiver, owner, assets, shares); + + if (fee > 0 && recipient != address(this)) { + SafeERC20.safeTransfer(IERC20(asset()), recipient, fee); + } + } + + // === Fee configuration === + + function _entryFeeBasisPoints() internal view virtual returns (uint256) { + return 0; // replace with e.g. 100 for 1% + } + + function _exitFeeBasisPoints() internal view virtual returns (uint256) { + return 0; // replace with e.g. 100 for 1% + } + + 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); // replace with e.g. a treasury address + } + + // === 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); + } + + /// @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/docs/modules/api/examples/governance/MyGovernor.sol b/docs/modules/api/examples/governance/MyGovernor.sol new file mode 100644 index 000000000..b4e69d764 --- /dev/null +++ b/docs/modules/api/examples/governance/MyGovernor.sol @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {IGovernor, Governor} from "@openzeppelin/contracts/governance/Governor.sol"; +import {GovernorCountingSimple} from "@openzeppelin/contracts/governance/extensions/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"; +import {IERC165} from "@openzeppelin/contracts/interfaces/IERC165.sol"; + +contract MyGovernor is + Governor, + GovernorCountingSimple, + 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, GovernorTimelockControl) returns (ProposalState) { + return super.state(proposalId); + } + + function proposalNeedsQueuing( + uint256 proposalId + ) public view virtual override(Governor, GovernorTimelockControl) returns (bool) { + return super.proposalNeedsQueuing(proposalId); + } + + 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/docs/modules/api/examples/governance/MyToken.sol b/docs/modules/api/examples/governance/MyToken.sol new file mode 100644 index 000000000..e358ec207 --- /dev/null +++ b/docs/modules/api/examples/governance/MyToken.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import {ERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol"; +import {ERC20Votes} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol"; +import {Nonces} from "@openzeppelin/contracts/utils/Nonces.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/docs/modules/api/examples/governance/MyTokenTimestampBased.sol b/docs/modules/api/examples/governance/MyTokenTimestampBased.sol new file mode 100644 index 000000000..87d271062 --- /dev/null +++ b/docs/modules/api/examples/governance/MyTokenTimestampBased.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import {ERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol"; +import {ERC20Votes} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol"; +import {Nonces} from "@openzeppelin/contracts/utils/Nonces.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/docs/modules/api/examples/governance/MyTokenWrapped.sol b/docs/modules/api/examples/governance/MyTokenWrapped.sol new file mode 100644 index 000000000..bdcc1f02b --- /dev/null +++ b/docs/modules/api/examples/governance/MyTokenWrapped.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {IERC20, ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import {ERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol"; +import {ERC20Votes} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol"; +import {ERC20Wrapper} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Wrapper.sol"; +import {Nonces} from "@openzeppelin/contracts/utils/Nonces.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/docs/modules/api/nav.adoc b/docs/modules/api/nav.adoc new file mode 100644 index 000000000..ac2ab659c --- /dev/null +++ b/docs/modules/api/nav.adoc @@ -0,0 +1,12 @@ +.API +* xref:access.adoc[Access] +* xref:token/common.adoc[Common (Tokens)] +* xref:token/ERC20.adoc[ERC 20] +* xref:token/ERC721.adoc[ERC 721] +* xref:token/ERC1155.adoc[ERC 1155] +* xref:finance.adoc[Finance] +* xref:governance.adoc[Governance] +* xref:interfaces.adoc[Interfaces] +* xref:metatx.adoc[Meta Transactions] +* xref:proxy.adoc[Proxy] +* xref:utils.adoc[Utils] diff --git a/docs/modules/api/pages/access.adoc b/docs/modules/api/pages/access.adoc new file mode 100644 index 000000000..f96488ea7 --- /dev/null +++ b/docs/modules/api/pages/access.adoc @@ -0,0 +1,2582 @@ +:github-icon: pass:[] +:AccessControl: pass:normal[xref:access.adoc#AccessControl[`AccessControl`]] +:Ownable: pass:normal[xref:access.adoc#Ownable[`Ownable`]] +:xref-Ownable-onlyOwner--: xref:access.adoc#Ownable-onlyOwner-- +:xref-Ownable-constructor-address-: xref:access.adoc#Ownable-constructor-address- +:xref-Ownable-owner--: xref:access.adoc#Ownable-owner-- +:xref-Ownable-_checkOwner--: xref:access.adoc#Ownable-_checkOwner-- +:xref-Ownable-renounceOwnership--: xref:access.adoc#Ownable-renounceOwnership-- +:xref-Ownable-transferOwnership-address-: xref:access.adoc#Ownable-transferOwnership-address- +:xref-Ownable-_transferOwnership-address-: xref:access.adoc#Ownable-_transferOwnership-address- +:xref-Ownable-OwnershipTransferred-address-address-: xref:access.adoc#Ownable-OwnershipTransferred-address-address- +:xref-Ownable-OwnableUnauthorizedAccount-address-: xref:access.adoc#Ownable-OwnableUnauthorizedAccount-address- +:xref-Ownable-OwnableInvalidOwner-address-: xref:access.adoc#Ownable-OwnableInvalidOwner-address- +:xref-Ownable2Step-pendingOwner--: xref:access.adoc#Ownable2Step-pendingOwner-- +:xref-Ownable2Step-transferOwnership-address-: xref:access.adoc#Ownable2Step-transferOwnership-address- +:xref-Ownable2Step-_transferOwnership-address-: xref:access.adoc#Ownable2Step-_transferOwnership-address- +:xref-Ownable2Step-acceptOwnership--: xref:access.adoc#Ownable2Step-acceptOwnership-- +:xref-Ownable-owner--: xref:access.adoc#Ownable-owner-- +:xref-Ownable-_checkOwner--: xref:access.adoc#Ownable-_checkOwner-- +:xref-Ownable-renounceOwnership--: xref:access.adoc#Ownable-renounceOwnership-- +:xref-Ownable2Step-OwnershipTransferStarted-address-address-: xref:access.adoc#Ownable2Step-OwnershipTransferStarted-address-address- +:xref-Ownable-OwnershipTransferred-address-address-: xref:access.adoc#Ownable-OwnershipTransferred-address-address- +:xref-Ownable-OwnableUnauthorizedAccount-address-: xref:access.adoc#Ownable-OwnableUnauthorizedAccount-address- +:xref-Ownable-OwnableInvalidOwner-address-: xref:access.adoc#Ownable-OwnableInvalidOwner-address- +:xref-IAccessControl-hasRole-bytes32-address-: xref:access.adoc#IAccessControl-hasRole-bytes32-address- +:xref-IAccessControl-getRoleAdmin-bytes32-: xref:access.adoc#IAccessControl-getRoleAdmin-bytes32- +:xref-IAccessControl-grantRole-bytes32-address-: xref:access.adoc#IAccessControl-grantRole-bytes32-address- +:xref-IAccessControl-revokeRole-bytes32-address-: xref:access.adoc#IAccessControl-revokeRole-bytes32-address- +:xref-IAccessControl-renounceRole-bytes32-address-: xref:access.adoc#IAccessControl-renounceRole-bytes32-address- +:xref-IAccessControl-RoleAdminChanged-bytes32-bytes32-bytes32-: xref:access.adoc#IAccessControl-RoleAdminChanged-bytes32-bytes32-bytes32- +:xref-IAccessControl-RoleGranted-bytes32-address-address-: xref:access.adoc#IAccessControl-RoleGranted-bytes32-address-address- +:xref-IAccessControl-RoleRevoked-bytes32-address-address-: xref:access.adoc#IAccessControl-RoleRevoked-bytes32-address-address- +:xref-IAccessControl-AccessControlUnauthorizedAccount-address-bytes32-: xref:access.adoc#IAccessControl-AccessControlUnauthorizedAccount-address-bytes32- +:xref-IAccessControl-AccessControlBadConfirmation--: xref:access.adoc#IAccessControl-AccessControlBadConfirmation-- +:AccessControl-_setRoleAdmin: pass:normal[xref:access.adoc#AccessControl-_setRoleAdmin-bytes32-bytes32-[`AccessControl._setRoleAdmin`]] +:AccessControlEnumerable: pass:normal[xref:access.adoc#AccessControlEnumerable[`AccessControlEnumerable`]] +:AccessControlDefaultAdminRules: pass:normal[xref:access.adoc#AccessControlDefaultAdminRules[`AccessControlDefaultAdminRules`]] +:xref-AccessControl-onlyRole-bytes32-: xref:access.adoc#AccessControl-onlyRole-bytes32- +:xref-AccessControl-supportsInterface-bytes4-: xref:access.adoc#AccessControl-supportsInterface-bytes4- +:xref-AccessControl-hasRole-bytes32-address-: xref:access.adoc#AccessControl-hasRole-bytes32-address- +:xref-AccessControl-_checkRole-bytes32-: xref:access.adoc#AccessControl-_checkRole-bytes32- +:xref-AccessControl-_checkRole-bytes32-address-: xref:access.adoc#AccessControl-_checkRole-bytes32-address- +:xref-AccessControl-getRoleAdmin-bytes32-: xref:access.adoc#AccessControl-getRoleAdmin-bytes32- +:xref-AccessControl-grantRole-bytes32-address-: xref:access.adoc#AccessControl-grantRole-bytes32-address- +:xref-AccessControl-revokeRole-bytes32-address-: xref:access.adoc#AccessControl-revokeRole-bytes32-address- +:xref-AccessControl-renounceRole-bytes32-address-: xref:access.adoc#AccessControl-renounceRole-bytes32-address- +:xref-AccessControl-_setRoleAdmin-bytes32-bytes32-: xref:access.adoc#AccessControl-_setRoleAdmin-bytes32-bytes32- +:xref-AccessControl-_grantRole-bytes32-address-: xref:access.adoc#AccessControl-_grantRole-bytes32-address- +:xref-AccessControl-_revokeRole-bytes32-address-: xref:access.adoc#AccessControl-_revokeRole-bytes32-address- +:xref-IAccessControl-RoleAdminChanged-bytes32-bytes32-bytes32-: xref:access.adoc#IAccessControl-RoleAdminChanged-bytes32-bytes32-bytes32- +:xref-IAccessControl-RoleGranted-bytes32-address-address-: xref:access.adoc#IAccessControl-RoleGranted-bytes32-address-address- +:xref-IAccessControl-RoleRevoked-bytes32-address-address-: xref:access.adoc#IAccessControl-RoleRevoked-bytes32-address-address- +:xref-IAccessControl-AccessControlUnauthorizedAccount-address-bytes32-: xref:access.adoc#IAccessControl-AccessControlUnauthorizedAccount-address-bytes32- +:xref-IAccessControl-AccessControlBadConfirmation--: xref:access.adoc#IAccessControl-AccessControlBadConfirmation-- +:IERC165-supportsInterface: pass:normal[xref:utils.adoc#IERC165-supportsInterface-bytes4-[`IERC165.supportsInterface`]] +:xref-IAccessControlEnumerable-getRoleMember-bytes32-uint256-: xref:access.adoc#IAccessControlEnumerable-getRoleMember-bytes32-uint256- +:xref-IAccessControlEnumerable-getRoleMemberCount-bytes32-: xref:access.adoc#IAccessControlEnumerable-getRoleMemberCount-bytes32- +:xref-IAccessControl-hasRole-bytes32-address-: xref:access.adoc#IAccessControl-hasRole-bytes32-address- +:xref-IAccessControl-getRoleAdmin-bytes32-: xref:access.adoc#IAccessControl-getRoleAdmin-bytes32- +:xref-IAccessControl-grantRole-bytes32-address-: xref:access.adoc#IAccessControl-grantRole-bytes32-address- +:xref-IAccessControl-revokeRole-bytes32-address-: xref:access.adoc#IAccessControl-revokeRole-bytes32-address- +:xref-IAccessControl-renounceRole-bytes32-address-: xref:access.adoc#IAccessControl-renounceRole-bytes32-address- +:xref-IAccessControl-RoleAdminChanged-bytes32-bytes32-bytes32-: xref:access.adoc#IAccessControl-RoleAdminChanged-bytes32-bytes32-bytes32- +:xref-IAccessControl-RoleGranted-bytes32-address-address-: xref:access.adoc#IAccessControl-RoleGranted-bytes32-address-address- +:xref-IAccessControl-RoleRevoked-bytes32-address-address-: xref:access.adoc#IAccessControl-RoleRevoked-bytes32-address-address- +:xref-IAccessControl-AccessControlUnauthorizedAccount-address-bytes32-: xref:access.adoc#IAccessControl-AccessControlUnauthorizedAccount-address-bytes32- +:xref-IAccessControl-AccessControlBadConfirmation--: xref:access.adoc#IAccessControl-AccessControlBadConfirmation-- +:AccessControl: pass:normal[xref:access.adoc#AccessControl[`AccessControl`]] +:xref-AccessControlEnumerable-supportsInterface-bytes4-: xref:access.adoc#AccessControlEnumerable-supportsInterface-bytes4- +:xref-AccessControlEnumerable-getRoleMember-bytes32-uint256-: xref:access.adoc#AccessControlEnumerable-getRoleMember-bytes32-uint256- +:xref-AccessControlEnumerable-getRoleMemberCount-bytes32-: xref:access.adoc#AccessControlEnumerable-getRoleMemberCount-bytes32- +:xref-AccessControlEnumerable-_grantRole-bytes32-address-: xref:access.adoc#AccessControlEnumerable-_grantRole-bytes32-address- +:xref-AccessControlEnumerable-_revokeRole-bytes32-address-: xref:access.adoc#AccessControlEnumerable-_revokeRole-bytes32-address- +:xref-AccessControl-hasRole-bytes32-address-: xref:access.adoc#AccessControl-hasRole-bytes32-address- +:xref-AccessControl-_checkRole-bytes32-: xref:access.adoc#AccessControl-_checkRole-bytes32- +:xref-AccessControl-_checkRole-bytes32-address-: xref:access.adoc#AccessControl-_checkRole-bytes32-address- +:xref-AccessControl-getRoleAdmin-bytes32-: xref:access.adoc#AccessControl-getRoleAdmin-bytes32- +:xref-AccessControl-grantRole-bytes32-address-: xref:access.adoc#AccessControl-grantRole-bytes32-address- +:xref-AccessControl-revokeRole-bytes32-address-: xref:access.adoc#AccessControl-revokeRole-bytes32-address- +:xref-AccessControl-renounceRole-bytes32-address-: xref:access.adoc#AccessControl-renounceRole-bytes32-address- +:xref-AccessControl-_setRoleAdmin-bytes32-bytes32-: xref:access.adoc#AccessControl-_setRoleAdmin-bytes32-bytes32- +:xref-IAccessControl-RoleAdminChanged-bytes32-bytes32-bytes32-: xref:access.adoc#IAccessControl-RoleAdminChanged-bytes32-bytes32-bytes32- +:xref-IAccessControl-RoleGranted-bytes32-address-address-: xref:access.adoc#IAccessControl-RoleGranted-bytes32-address-address- +:xref-IAccessControl-RoleRevoked-bytes32-address-address-: xref:access.adoc#IAccessControl-RoleRevoked-bytes32-address-address- +:xref-IAccessControl-AccessControlUnauthorizedAccount-address-bytes32-: xref:access.adoc#IAccessControl-AccessControlUnauthorizedAccount-address-bytes32- +:xref-IAccessControl-AccessControlBadConfirmation--: xref:access.adoc#IAccessControl-AccessControlBadConfirmation-- +:IERC165-supportsInterface: pass:normal[xref:utils.adoc#IERC165-supportsInterface-bytes4-[`IERC165.supportsInterface`]] +:AccessControl-_grantRole: pass:normal[xref:access.adoc#AccessControl-_grantRole-bytes32-address-[`AccessControl._grantRole`]] +:AccessControl-_revokeRole: pass:normal[xref:access.adoc#AccessControl-_revokeRole-bytes32-address-[`AccessControl._revokeRole`]] +:xref-IAccessControlDefaultAdminRules-defaultAdmin--: xref:access.adoc#IAccessControlDefaultAdminRules-defaultAdmin-- +:xref-IAccessControlDefaultAdminRules-pendingDefaultAdmin--: xref:access.adoc#IAccessControlDefaultAdminRules-pendingDefaultAdmin-- +:xref-IAccessControlDefaultAdminRules-defaultAdminDelay--: xref:access.adoc#IAccessControlDefaultAdminRules-defaultAdminDelay-- +:xref-IAccessControlDefaultAdminRules-pendingDefaultAdminDelay--: xref:access.adoc#IAccessControlDefaultAdminRules-pendingDefaultAdminDelay-- +:xref-IAccessControlDefaultAdminRules-beginDefaultAdminTransfer-address-: xref:access.adoc#IAccessControlDefaultAdminRules-beginDefaultAdminTransfer-address- +:xref-IAccessControlDefaultAdminRules-cancelDefaultAdminTransfer--: xref:access.adoc#IAccessControlDefaultAdminRules-cancelDefaultAdminTransfer-- +:xref-IAccessControlDefaultAdminRules-acceptDefaultAdminTransfer--: xref:access.adoc#IAccessControlDefaultAdminRules-acceptDefaultAdminTransfer-- +:xref-IAccessControlDefaultAdminRules-changeDefaultAdminDelay-uint48-: xref:access.adoc#IAccessControlDefaultAdminRules-changeDefaultAdminDelay-uint48- +:xref-IAccessControlDefaultAdminRules-rollbackDefaultAdminDelay--: xref:access.adoc#IAccessControlDefaultAdminRules-rollbackDefaultAdminDelay-- +:xref-IAccessControlDefaultAdminRules-defaultAdminDelayIncreaseWait--: xref:access.adoc#IAccessControlDefaultAdminRules-defaultAdminDelayIncreaseWait-- +:xref-IAccessControl-hasRole-bytes32-address-: xref:access.adoc#IAccessControl-hasRole-bytes32-address- +:xref-IAccessControl-getRoleAdmin-bytes32-: xref:access.adoc#IAccessControl-getRoleAdmin-bytes32- +:xref-IAccessControl-grantRole-bytes32-address-: xref:access.adoc#IAccessControl-grantRole-bytes32-address- +:xref-IAccessControl-revokeRole-bytes32-address-: xref:access.adoc#IAccessControl-revokeRole-bytes32-address- +:xref-IAccessControl-renounceRole-bytes32-address-: xref:access.adoc#IAccessControl-renounceRole-bytes32-address- +:xref-IAccessControlDefaultAdminRules-DefaultAdminTransferScheduled-address-uint48-: xref:access.adoc#IAccessControlDefaultAdminRules-DefaultAdminTransferScheduled-address-uint48- +:xref-IAccessControlDefaultAdminRules-DefaultAdminTransferCanceled--: xref:access.adoc#IAccessControlDefaultAdminRules-DefaultAdminTransferCanceled-- +:xref-IAccessControlDefaultAdminRules-DefaultAdminDelayChangeScheduled-uint48-uint48-: xref:access.adoc#IAccessControlDefaultAdminRules-DefaultAdminDelayChangeScheduled-uint48-uint48- +:xref-IAccessControlDefaultAdminRules-DefaultAdminDelayChangeCanceled--: xref:access.adoc#IAccessControlDefaultAdminRules-DefaultAdminDelayChangeCanceled-- +:xref-IAccessControl-RoleAdminChanged-bytes32-bytes32-bytes32-: xref:access.adoc#IAccessControl-RoleAdminChanged-bytes32-bytes32-bytes32- +:xref-IAccessControl-RoleGranted-bytes32-address-address-: xref:access.adoc#IAccessControl-RoleGranted-bytes32-address-address- +:xref-IAccessControl-RoleRevoked-bytes32-address-address-: xref:access.adoc#IAccessControl-RoleRevoked-bytes32-address-address- +:xref-IAccessControlDefaultAdminRules-AccessControlInvalidDefaultAdmin-address-: xref:access.adoc#IAccessControlDefaultAdminRules-AccessControlInvalidDefaultAdmin-address- +:xref-IAccessControlDefaultAdminRules-AccessControlEnforcedDefaultAdminRules--: xref:access.adoc#IAccessControlDefaultAdminRules-AccessControlEnforcedDefaultAdminRules-- +:xref-IAccessControlDefaultAdminRules-AccessControlEnforcedDefaultAdminDelay-uint48-: xref:access.adoc#IAccessControlDefaultAdminRules-AccessControlEnforcedDefaultAdminDelay-uint48- +:xref-IAccessControl-AccessControlUnauthorizedAccount-address-bytes32-: xref:access.adoc#IAccessControl-AccessControlUnauthorizedAccount-address-bytes32- +:xref-IAccessControl-AccessControlBadConfirmation--: xref:access.adoc#IAccessControl-AccessControlBadConfirmation-- +:AccessControl: pass:normal[xref:access.adoc#AccessControl[`AccessControl`]] +:AccessControl: pass:normal[xref:access.adoc#AccessControl[`AccessControl`]] +:xref-AccessControlDefaultAdminRules-constructor-uint48-address-: xref:access.adoc#AccessControlDefaultAdminRules-constructor-uint48-address- +:xref-AccessControlDefaultAdminRules-supportsInterface-bytes4-: xref:access.adoc#AccessControlDefaultAdminRules-supportsInterface-bytes4- +:xref-AccessControlDefaultAdminRules-owner--: xref:access.adoc#AccessControlDefaultAdminRules-owner-- +:xref-AccessControlDefaultAdminRules-grantRole-bytes32-address-: xref:access.adoc#AccessControlDefaultAdminRules-grantRole-bytes32-address- +:xref-AccessControlDefaultAdminRules-revokeRole-bytes32-address-: xref:access.adoc#AccessControlDefaultAdminRules-revokeRole-bytes32-address- +:xref-AccessControlDefaultAdminRules-renounceRole-bytes32-address-: xref:access.adoc#AccessControlDefaultAdminRules-renounceRole-bytes32-address- +:xref-AccessControlDefaultAdminRules-_grantRole-bytes32-address-: xref:access.adoc#AccessControlDefaultAdminRules-_grantRole-bytes32-address- +:xref-AccessControlDefaultAdminRules-_revokeRole-bytes32-address-: xref:access.adoc#AccessControlDefaultAdminRules-_revokeRole-bytes32-address- +:xref-AccessControlDefaultAdminRules-_setRoleAdmin-bytes32-bytes32-: xref:access.adoc#AccessControlDefaultAdminRules-_setRoleAdmin-bytes32-bytes32- +:xref-AccessControlDefaultAdminRules-defaultAdmin--: xref:access.adoc#AccessControlDefaultAdminRules-defaultAdmin-- +:xref-AccessControlDefaultAdminRules-pendingDefaultAdmin--: xref:access.adoc#AccessControlDefaultAdminRules-pendingDefaultAdmin-- +:xref-AccessControlDefaultAdminRules-defaultAdminDelay--: xref:access.adoc#AccessControlDefaultAdminRules-defaultAdminDelay-- +:xref-AccessControlDefaultAdminRules-pendingDefaultAdminDelay--: xref:access.adoc#AccessControlDefaultAdminRules-pendingDefaultAdminDelay-- +:xref-AccessControlDefaultAdminRules-defaultAdminDelayIncreaseWait--: xref:access.adoc#AccessControlDefaultAdminRules-defaultAdminDelayIncreaseWait-- +:xref-AccessControlDefaultAdminRules-beginDefaultAdminTransfer-address-: xref:access.adoc#AccessControlDefaultAdminRules-beginDefaultAdminTransfer-address- +:xref-AccessControlDefaultAdminRules-_beginDefaultAdminTransfer-address-: xref:access.adoc#AccessControlDefaultAdminRules-_beginDefaultAdminTransfer-address- +:xref-AccessControlDefaultAdminRules-cancelDefaultAdminTransfer--: xref:access.adoc#AccessControlDefaultAdminRules-cancelDefaultAdminTransfer-- +:xref-AccessControlDefaultAdminRules-_cancelDefaultAdminTransfer--: xref:access.adoc#AccessControlDefaultAdminRules-_cancelDefaultAdminTransfer-- +:xref-AccessControlDefaultAdminRules-acceptDefaultAdminTransfer--: xref:access.adoc#AccessControlDefaultAdminRules-acceptDefaultAdminTransfer-- +:xref-AccessControlDefaultAdminRules-_acceptDefaultAdminTransfer--: xref:access.adoc#AccessControlDefaultAdminRules-_acceptDefaultAdminTransfer-- +:xref-AccessControlDefaultAdminRules-changeDefaultAdminDelay-uint48-: xref:access.adoc#AccessControlDefaultAdminRules-changeDefaultAdminDelay-uint48- +:xref-AccessControlDefaultAdminRules-_changeDefaultAdminDelay-uint48-: xref:access.adoc#AccessControlDefaultAdminRules-_changeDefaultAdminDelay-uint48- +:xref-AccessControlDefaultAdminRules-rollbackDefaultAdminDelay--: xref:access.adoc#AccessControlDefaultAdminRules-rollbackDefaultAdminDelay-- +:xref-AccessControlDefaultAdminRules-_rollbackDefaultAdminDelay--: xref:access.adoc#AccessControlDefaultAdminRules-_rollbackDefaultAdminDelay-- +:xref-AccessControlDefaultAdminRules-_delayChangeWait-uint48-: xref:access.adoc#AccessControlDefaultAdminRules-_delayChangeWait-uint48- +:xref-AccessControl-hasRole-bytes32-address-: xref:access.adoc#AccessControl-hasRole-bytes32-address- +:xref-AccessControl-_checkRole-bytes32-: xref:access.adoc#AccessControl-_checkRole-bytes32- +:xref-AccessControl-_checkRole-bytes32-address-: xref:access.adoc#AccessControl-_checkRole-bytes32-address- +:xref-AccessControl-getRoleAdmin-bytes32-: xref:access.adoc#AccessControl-getRoleAdmin-bytes32- +:xref-IAccessControlDefaultAdminRules-DefaultAdminTransferScheduled-address-uint48-: xref:access.adoc#IAccessControlDefaultAdminRules-DefaultAdminTransferScheduled-address-uint48- +:xref-IAccessControlDefaultAdminRules-DefaultAdminTransferCanceled--: xref:access.adoc#IAccessControlDefaultAdminRules-DefaultAdminTransferCanceled-- +:xref-IAccessControlDefaultAdminRules-DefaultAdminDelayChangeScheduled-uint48-uint48-: xref:access.adoc#IAccessControlDefaultAdminRules-DefaultAdminDelayChangeScheduled-uint48-uint48- +:xref-IAccessControlDefaultAdminRules-DefaultAdminDelayChangeCanceled--: xref:access.adoc#IAccessControlDefaultAdminRules-DefaultAdminDelayChangeCanceled-- +:xref-IAccessControl-RoleAdminChanged-bytes32-bytes32-bytes32-: xref:access.adoc#IAccessControl-RoleAdminChanged-bytes32-bytes32-bytes32- +:xref-IAccessControl-RoleGranted-bytes32-address-address-: xref:access.adoc#IAccessControl-RoleGranted-bytes32-address-address- +:xref-IAccessControl-RoleRevoked-bytes32-address-address-: xref:access.adoc#IAccessControl-RoleRevoked-bytes32-address-address- +:xref-IAccessControlDefaultAdminRules-AccessControlInvalidDefaultAdmin-address-: xref:access.adoc#IAccessControlDefaultAdminRules-AccessControlInvalidDefaultAdmin-address- +:xref-IAccessControlDefaultAdminRules-AccessControlEnforcedDefaultAdminRules--: xref:access.adoc#IAccessControlDefaultAdminRules-AccessControlEnforcedDefaultAdminRules-- +:xref-IAccessControlDefaultAdminRules-AccessControlEnforcedDefaultAdminDelay-uint48-: xref:access.adoc#IAccessControlDefaultAdminRules-AccessControlEnforcedDefaultAdminDelay-uint48- +:xref-IAccessControl-AccessControlUnauthorizedAccount-address-bytes32-: xref:access.adoc#IAccessControl-AccessControlUnauthorizedAccount-address-bytes32- +:xref-IAccessControl-AccessControlBadConfirmation--: xref:access.adoc#IAccessControl-AccessControlBadConfirmation-- +:IERC165-supportsInterface: pass:normal[xref:utils.adoc#IERC165-supportsInterface-bytes4-[`IERC165.supportsInterface`]] +:IERC5313-owner: pass:normal[xref:interfaces.adoc#IERC5313-owner--[`IERC5313.owner`]] +:AccessControl-grantRole: pass:normal[xref:access.adoc#AccessControl-grantRole-bytes32-address-[`AccessControl.grantRole`]] +:AccessControl-revokeRole: pass:normal[xref:access.adoc#AccessControl-revokeRole-bytes32-address-[`AccessControl.revokeRole`]] +:AccessControl-renounceRole: pass:normal[xref:access.adoc#AccessControl-renounceRole-bytes32-address-[`AccessControl.renounceRole`]] +:AccessControl-_grantRole: pass:normal[xref:access.adoc#AccessControl-_grantRole-bytes32-address-[`AccessControl._grantRole`]] +:AccessControl-_revokeRole: pass:normal[xref:access.adoc#AccessControl-_revokeRole-bytes32-address-[`AccessControl._revokeRole`]] +:AccessControl-_setRoleAdmin: pass:normal[xref:access.adoc#AccessControl-_setRoleAdmin-bytes32-bytes32-[`AccessControl._setRoleAdmin`]] +:xref-IAuthority-canCall-address-address-bytes4-: xref:access.adoc#IAuthority-canCall-address-address-bytes4- +:AccessManaged: pass:normal[xref:access.adoc#AccessManaged[`AccessManaged`]] +:IAuthority: pass:normal[xref:access.adoc#IAuthority[`IAuthority`]] +:Ownable: pass:normal[xref:access.adoc#Ownable[`Ownable`]] +:AccessManager: pass:normal[xref:access.adoc#AccessManager[`AccessManager`]] +:Ownable: pass:normal[xref:access.adoc#Ownable[`Ownable`]] +:AccessManager: pass:normal[xref:access.adoc#AccessManager[`AccessManager`]] +:AccessManager: pass:normal[xref:access.adoc#AccessManager[`AccessManager`]] +:AccessManager: pass:normal[xref:access.adoc#AccessManager[`AccessManager`]] +:Ownable: pass:normal[xref:access.adoc#Ownable[`Ownable`]] +:AccessControl: pass:normal[xref:access.adoc#AccessControl[`AccessControl`]] +:AccessManager: pass:normal[xref:access.adoc#AccessManager[`AccessManager`]] +:Ownable-renounceOwnership: pass:normal[xref:access.adoc#Ownable-renounceOwnership--[`Ownable.renounceOwnership`]] +:AccessControl-renounceRole: pass:normal[xref:access.adoc#AccessControl-renounceRole-bytes32-address-[`AccessControl.renounceRole`]] +:xref-AccessManager-onlyAuthorized--: xref:access.adoc#AccessManager-onlyAuthorized-- +:xref-AccessManager-constructor-address-: xref:access.adoc#AccessManager-constructor-address- +:xref-AccessManager-canCall-address-address-bytes4-: xref:access.adoc#AccessManager-canCall-address-address-bytes4- +:xref-AccessManager-expiration--: xref:access.adoc#AccessManager-expiration-- +:xref-AccessManager-minSetback--: xref:access.adoc#AccessManager-minSetback-- +:xref-AccessManager-isTargetClosed-address-: xref:access.adoc#AccessManager-isTargetClosed-address- +:xref-AccessManager-getTargetFunctionRole-address-bytes4-: xref:access.adoc#AccessManager-getTargetFunctionRole-address-bytes4- +:xref-AccessManager-getTargetAdminDelay-address-: xref:access.adoc#AccessManager-getTargetAdminDelay-address- +:xref-AccessManager-getRoleAdmin-uint64-: xref:access.adoc#AccessManager-getRoleAdmin-uint64- +:xref-AccessManager-getRoleGuardian-uint64-: xref:access.adoc#AccessManager-getRoleGuardian-uint64- +:xref-AccessManager-getRoleGrantDelay-uint64-: xref:access.adoc#AccessManager-getRoleGrantDelay-uint64- +:xref-AccessManager-getAccess-uint64-address-: xref:access.adoc#AccessManager-getAccess-uint64-address- +:xref-AccessManager-hasRole-uint64-address-: xref:access.adoc#AccessManager-hasRole-uint64-address- +:xref-AccessManager-labelRole-uint64-string-: xref:access.adoc#AccessManager-labelRole-uint64-string- +:xref-AccessManager-grantRole-uint64-address-uint32-: xref:access.adoc#AccessManager-grantRole-uint64-address-uint32- +:xref-AccessManager-revokeRole-uint64-address-: xref:access.adoc#AccessManager-revokeRole-uint64-address- +:xref-AccessManager-renounceRole-uint64-address-: xref:access.adoc#AccessManager-renounceRole-uint64-address- +:xref-AccessManager-setRoleAdmin-uint64-uint64-: xref:access.adoc#AccessManager-setRoleAdmin-uint64-uint64- +:xref-AccessManager-setRoleGuardian-uint64-uint64-: xref:access.adoc#AccessManager-setRoleGuardian-uint64-uint64- +:xref-AccessManager-setGrantDelay-uint64-uint32-: xref:access.adoc#AccessManager-setGrantDelay-uint64-uint32- +:xref-AccessManager-_grantRole-uint64-address-uint32-uint32-: xref:access.adoc#AccessManager-_grantRole-uint64-address-uint32-uint32- +:xref-AccessManager-_revokeRole-uint64-address-: xref:access.adoc#AccessManager-_revokeRole-uint64-address- +:xref-AccessManager-_setRoleAdmin-uint64-uint64-: xref:access.adoc#AccessManager-_setRoleAdmin-uint64-uint64- +:xref-AccessManager-_setRoleGuardian-uint64-uint64-: xref:access.adoc#AccessManager-_setRoleGuardian-uint64-uint64- +:xref-AccessManager-_setGrantDelay-uint64-uint32-: xref:access.adoc#AccessManager-_setGrantDelay-uint64-uint32- +:xref-AccessManager-setTargetFunctionRole-address-bytes4---uint64-: xref:access.adoc#AccessManager-setTargetFunctionRole-address-bytes4---uint64- +:xref-AccessManager-_setTargetFunctionRole-address-bytes4-uint64-: xref:access.adoc#AccessManager-_setTargetFunctionRole-address-bytes4-uint64- +:xref-AccessManager-setTargetAdminDelay-address-uint32-: xref:access.adoc#AccessManager-setTargetAdminDelay-address-uint32- +:xref-AccessManager-_setTargetAdminDelay-address-uint32-: xref:access.adoc#AccessManager-_setTargetAdminDelay-address-uint32- +:xref-AccessManager-setTargetClosed-address-bool-: xref:access.adoc#AccessManager-setTargetClosed-address-bool- +:xref-AccessManager-_setTargetClosed-address-bool-: xref:access.adoc#AccessManager-_setTargetClosed-address-bool- +:xref-AccessManager-getSchedule-bytes32-: xref:access.adoc#AccessManager-getSchedule-bytes32- +:xref-AccessManager-getNonce-bytes32-: xref:access.adoc#AccessManager-getNonce-bytes32- +:xref-AccessManager-schedule-address-bytes-uint48-: xref:access.adoc#AccessManager-schedule-address-bytes-uint48- +:xref-AccessManager-execute-address-bytes-: xref:access.adoc#AccessManager-execute-address-bytes- +:xref-AccessManager-consumeScheduledOp-address-bytes-: xref:access.adoc#AccessManager-consumeScheduledOp-address-bytes- +:xref-AccessManager-_consumeScheduledOp-bytes32-: xref:access.adoc#AccessManager-_consumeScheduledOp-bytes32- +:xref-AccessManager-cancel-address-address-bytes-: xref:access.adoc#AccessManager-cancel-address-address-bytes- +:xref-AccessManager-hashOperation-address-address-bytes-: xref:access.adoc#AccessManager-hashOperation-address-address-bytes- +:xref-AccessManager-updateAuthority-address-address-: xref:access.adoc#AccessManager-updateAuthority-address-address- +:xref-Multicall-multicall-bytes---: xref:utils.adoc#Multicall-multicall-bytes--- +:xref-IAccessManager-OperationScheduled-bytes32-uint32-uint48-address-address-bytes-: xref:access.adoc#IAccessManager-OperationScheduled-bytes32-uint32-uint48-address-address-bytes- +:xref-IAccessManager-OperationExecuted-bytes32-uint32-: xref:access.adoc#IAccessManager-OperationExecuted-bytes32-uint32- +:xref-IAccessManager-OperationCanceled-bytes32-uint32-: xref:access.adoc#IAccessManager-OperationCanceled-bytes32-uint32- +:xref-IAccessManager-RoleLabel-uint64-string-: xref:access.adoc#IAccessManager-RoleLabel-uint64-string- +:xref-IAccessManager-RoleGranted-uint64-address-uint32-uint48-bool-: xref:access.adoc#IAccessManager-RoleGranted-uint64-address-uint32-uint48-bool- +:xref-IAccessManager-RoleRevoked-uint64-address-: xref:access.adoc#IAccessManager-RoleRevoked-uint64-address- +:xref-IAccessManager-RoleAdminChanged-uint64-uint64-: xref:access.adoc#IAccessManager-RoleAdminChanged-uint64-uint64- +:xref-IAccessManager-RoleGuardianChanged-uint64-uint64-: xref:access.adoc#IAccessManager-RoleGuardianChanged-uint64-uint64- +:xref-IAccessManager-RoleGrantDelayChanged-uint64-uint32-uint48-: xref:access.adoc#IAccessManager-RoleGrantDelayChanged-uint64-uint32-uint48- +:xref-IAccessManager-TargetClosed-address-bool-: xref:access.adoc#IAccessManager-TargetClosed-address-bool- +:xref-IAccessManager-TargetFunctionRoleUpdated-address-bytes4-uint64-: xref:access.adoc#IAccessManager-TargetFunctionRoleUpdated-address-bytes4-uint64- +:xref-IAccessManager-TargetAdminDelayUpdated-address-uint32-uint48-: xref:access.adoc#IAccessManager-TargetAdminDelayUpdated-address-uint32-uint48- +:xref-IAccessManager-AccessManagerAlreadyScheduled-bytes32-: xref:access.adoc#IAccessManager-AccessManagerAlreadyScheduled-bytes32- +:xref-IAccessManager-AccessManagerNotScheduled-bytes32-: xref:access.adoc#IAccessManager-AccessManagerNotScheduled-bytes32- +:xref-IAccessManager-AccessManagerNotReady-bytes32-: xref:access.adoc#IAccessManager-AccessManagerNotReady-bytes32- +:xref-IAccessManager-AccessManagerExpired-bytes32-: xref:access.adoc#IAccessManager-AccessManagerExpired-bytes32- +:xref-IAccessManager-AccessManagerLockedAccount-address-: xref:access.adoc#IAccessManager-AccessManagerLockedAccount-address- +:xref-IAccessManager-AccessManagerLockedRole-uint64-: xref:access.adoc#IAccessManager-AccessManagerLockedRole-uint64- +:xref-IAccessManager-AccessManagerBadConfirmation--: xref:access.adoc#IAccessManager-AccessManagerBadConfirmation-- +:xref-IAccessManager-AccessManagerUnauthorizedAccount-address-uint64-: xref:access.adoc#IAccessManager-AccessManagerUnauthorizedAccount-address-uint64- +:xref-IAccessManager-AccessManagerUnauthorizedCall-address-address-bytes4-: xref:access.adoc#IAccessManager-AccessManagerUnauthorizedCall-address-address-bytes4- +:xref-IAccessManager-AccessManagerUnauthorizedConsume-address-: xref:access.adoc#IAccessManager-AccessManagerUnauthorizedConsume-address- +:xref-IAccessManager-AccessManagerUnauthorizedCancel-address-address-address-bytes4-: xref:access.adoc#IAccessManager-AccessManagerUnauthorizedCancel-address-address-address-bytes4- +:xref-IAccessManager-AccessManagerInvalidInitialAdmin-address-: xref:access.adoc#IAccessManager-AccessManagerInvalidInitialAdmin-address- +:AccessManager: pass:normal[xref:access.adoc#AccessManager[`AccessManager`]] +:IAuthority: pass:normal[xref:access.adoc#IAuthority[`IAuthority`]] +:xref-AccessManaged-restricted--: xref:access.adoc#AccessManaged-restricted-- +:xref-AccessManaged-constructor-address-: xref:access.adoc#AccessManaged-constructor-address- +:xref-AccessManaged-authority--: xref:access.adoc#AccessManaged-authority-- +:xref-AccessManaged-setAuthority-address-: xref:access.adoc#AccessManaged-setAuthority-address- +:xref-AccessManaged-isConsumingScheduledOp--: xref:access.adoc#AccessManaged-isConsumingScheduledOp-- +:xref-AccessManaged-_setAuthority-address-: xref:access.adoc#AccessManaged-_setAuthority-address- +:xref-AccessManaged-_checkCanCall-address-bytes-: xref:access.adoc#AccessManaged-_checkCanCall-address-bytes- +:xref-IAccessManaged-AuthorityUpdated-address-: xref:access.adoc#IAccessManaged-AuthorityUpdated-address- +:xref-IAccessManaged-AccessManagedUnauthorized-address-: xref:access.adoc#IAccessManaged-AccessManagedUnauthorized-address- +:xref-IAccessManaged-AccessManagedRequiredDelay-address-uint32-: xref:access.adoc#IAccessManaged-AccessManagedRequiredDelay-address-uint32- +:xref-IAccessManaged-AccessManagedInvalidAuthority-address-: xref:access.adoc#IAccessManaged-AccessManagedInvalidAuthority-address- += Access Control + +[.readme-notice] +NOTE: This document is better viewed at https://docs.openzeppelin.com/contracts/api/access + +This directory provides ways to restrict who can access the functions of a contract or when they can do it. + +- {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. + +== Core + +:OwnableUnauthorizedAccount: pass:normal[xref:#Ownable-OwnableUnauthorizedAccount-address-[`++OwnableUnauthorizedAccount++`]] +:OwnableInvalidOwner: pass:normal[xref:#Ownable-OwnableInvalidOwner-address-[`++OwnableInvalidOwner++`]] +:OwnershipTransferred: pass:normal[xref:#Ownable-OwnershipTransferred-address-address-[`++OwnershipTransferred++`]] +:constructor: pass:normal[xref:#Ownable-constructor-address-[`++constructor++`]] +:onlyOwner: pass:normal[xref:#Ownable-onlyOwner--[`++onlyOwner++`]] +:owner: pass:normal[xref:#Ownable-owner--[`++owner++`]] +:_checkOwner: pass:normal[xref:#Ownable-_checkOwner--[`++_checkOwner++`]] +:renounceOwnership: pass:normal[xref:#Ownable-renounceOwnership--[`++renounceOwnership++`]] +:transferOwnership: pass:normal[xref:#Ownable-transferOwnership-address-[`++transferOwnership++`]] +:_transferOwnership: pass:normal[xref:#Ownable-_transferOwnership-address-[`++_transferOwnership++`]] + +[.contract] +[[Ownable]] +=== `++Ownable++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/access/Ownable.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/access/Ownable.sol"; +``` + +Contract module which provides a basic access control mechanism, where +there is an account (an owner) that can be granted exclusive access to +specific functions. + +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 +the owner. + +[.contract-index] +.Modifiers +-- +* {xref-Ownable-onlyOwner--}[`++onlyOwner()++`] +-- + +[.contract-index] +.Functions +-- +* {xref-Ownable-constructor-address-}[`++constructor(initialOwner)++`] +* {xref-Ownable-owner--}[`++owner()++`] +* {xref-Ownable-_checkOwner--}[`++_checkOwner()++`] +* {xref-Ownable-renounceOwnership--}[`++renounceOwnership()++`] +* {xref-Ownable-transferOwnership-address-}[`++transferOwnership(newOwner)++`] +* {xref-Ownable-_transferOwnership-address-}[`++_transferOwnership(newOwner)++`] + +-- + +[.contract-index] +.Events +-- +* {xref-Ownable-OwnershipTransferred-address-address-}[`++OwnershipTransferred(previousOwner, newOwner)++`] + +-- + +[.contract-index] +.Errors +-- +* {xref-Ownable-OwnableUnauthorizedAccount-address-}[`++OwnableUnauthorizedAccount(account)++`] +* {xref-Ownable-OwnableInvalidOwner-address-}[`++OwnableInvalidOwner(owner)++`] + +-- + +[.contract-item] +[[Ownable-onlyOwner--]] +==== `[.contract-item-name]#++onlyOwner++#++()++` [.item-kind]#modifier# + +Throws if called by any account other than the owner. + +[.contract-item] +[[Ownable-constructor-address-]] +==== `[.contract-item-name]#++constructor++#++(address initialOwner)++` [.item-kind]#internal# + +Initializes the contract setting the address provided by the deployer as the initial owner. + +[.contract-item] +[[Ownable-owner--]] +==== `[.contract-item-name]#++owner++#++() β†’ address++` [.item-kind]#public# + +Returns the address of the current owner. + +[.contract-item] +[[Ownable-_checkOwner--]] +==== `[.contract-item-name]#++_checkOwner++#++()++` [.item-kind]#internal# + +Throws if the sender is not the owner. + +[.contract-item] +[[Ownable-renounceOwnership--]] +==== `[.contract-item-name]#++renounceOwnership++#++()++` [.item-kind]#public# + +Leaves the contract without owner. It will not be possible to call +`onlyOwner` functions. Can only be called by the current owner. + +NOTE: Renouncing ownership will leave the contract without an owner, +thereby disabling any functionality that is only available to the owner. + +[.contract-item] +[[Ownable-transferOwnership-address-]] +==== `[.contract-item-name]#++transferOwnership++#++(address newOwner)++` [.item-kind]#public# + +Transfers ownership of the contract to a new account (`newOwner`). +Can only be called by the current owner. + +[.contract-item] +[[Ownable-_transferOwnership-address-]] +==== `[.contract-item-name]#++_transferOwnership++#++(address newOwner)++` [.item-kind]#internal# + +Transfers ownership of the contract to a new account (`newOwner`). +Internal function without access restriction. + +[.contract-item] +[[Ownable-OwnershipTransferred-address-address-]] +==== `[.contract-item-name]#++OwnershipTransferred++#++(address indexed previousOwner, address indexed newOwner)++` [.item-kind]#event# + +[.contract-item] +[[Ownable-OwnableUnauthorizedAccount-address-]] +==== `[.contract-item-name]#++OwnableUnauthorizedAccount++#++(address account)++` [.item-kind]#error# + +The caller account is not authorized to perform an operation. + +[.contract-item] +[[Ownable-OwnableInvalidOwner-address-]] +==== `[.contract-item-name]#++OwnableInvalidOwner++#++(address owner)++` [.item-kind]#error# + +The owner is not a valid owner account. (eg. `address(0)`) + +:OwnershipTransferStarted: pass:normal[xref:#Ownable2Step-OwnershipTransferStarted-address-address-[`++OwnershipTransferStarted++`]] +:pendingOwner: pass:normal[xref:#Ownable2Step-pendingOwner--[`++pendingOwner++`]] +:transferOwnership: pass:normal[xref:#Ownable2Step-transferOwnership-address-[`++transferOwnership++`]] +:_transferOwnership: pass:normal[xref:#Ownable2Step-_transferOwnership-address-[`++_transferOwnership++`]] +:acceptOwnership: pass:normal[xref:#Ownable2Step-acceptOwnership--[`++acceptOwnership++`]] + +[.contract] +[[Ownable2Step]] +=== `++Ownable2Step++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/access/Ownable2Step.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/access/Ownable2Step.sol"; +``` + +Contract module which provides access control mechanism, where +there is an account (an owner) that can be granted exclusive access to +specific functions. + +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 parent (Ownable). + +[.contract-index] +.Functions +-- +* {xref-Ownable2Step-pendingOwner--}[`++pendingOwner()++`] +* {xref-Ownable2Step-transferOwnership-address-}[`++transferOwnership(newOwner)++`] +* {xref-Ownable2Step-_transferOwnership-address-}[`++_transferOwnership(newOwner)++`] +* {xref-Ownable2Step-acceptOwnership--}[`++acceptOwnership()++`] + +[.contract-subindex-inherited] +.Ownable +* {xref-Ownable-owner--}[`++owner()++`] +* {xref-Ownable-_checkOwner--}[`++_checkOwner()++`] +* {xref-Ownable-renounceOwnership--}[`++renounceOwnership()++`] + +-- + +[.contract-index] +.Events +-- +* {xref-Ownable2Step-OwnershipTransferStarted-address-address-}[`++OwnershipTransferStarted(previousOwner, newOwner)++`] + +[.contract-subindex-inherited] +.Ownable +* {xref-Ownable-OwnershipTransferred-address-address-}[`++OwnershipTransferred(previousOwner, newOwner)++`] + +-- + +[.contract-index] +.Errors +-- + +[.contract-subindex-inherited] +.Ownable +* {xref-Ownable-OwnableUnauthorizedAccount-address-}[`++OwnableUnauthorizedAccount(account)++`] +* {xref-Ownable-OwnableInvalidOwner-address-}[`++OwnableInvalidOwner(owner)++`] + +-- + +[.contract-item] +[[Ownable2Step-pendingOwner--]] +==== `[.contract-item-name]#++pendingOwner++#++() β†’ address++` [.item-kind]#public# + +Returns the address of the pending owner. + +[.contract-item] +[[Ownable2Step-transferOwnership-address-]] +==== `[.contract-item-name]#++transferOwnership++#++(address newOwner)++` [.item-kind]#public# + +Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one. +Can only be called by the current owner. + +[.contract-item] +[[Ownable2Step-_transferOwnership-address-]] +==== `[.contract-item-name]#++_transferOwnership++#++(address newOwner)++` [.item-kind]#internal# + +Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner. +Internal function without access restriction. + +[.contract-item] +[[Ownable2Step-acceptOwnership--]] +==== `[.contract-item-name]#++acceptOwnership++#++()++` [.item-kind]#public# + +The new owner accepts the ownership transfer. + +[.contract-item] +[[Ownable2Step-OwnershipTransferStarted-address-address-]] +==== `[.contract-item-name]#++OwnershipTransferStarted++#++(address indexed previousOwner, address indexed newOwner)++` [.item-kind]#event# + +:AccessControlUnauthorizedAccount: pass:normal[xref:#IAccessControl-AccessControlUnauthorizedAccount-address-bytes32-[`++AccessControlUnauthorizedAccount++`]] +:AccessControlBadConfirmation: pass:normal[xref:#IAccessControl-AccessControlBadConfirmation--[`++AccessControlBadConfirmation++`]] +:RoleAdminChanged: pass:normal[xref:#IAccessControl-RoleAdminChanged-bytes32-bytes32-bytes32-[`++RoleAdminChanged++`]] +:RoleGranted: pass:normal[xref:#IAccessControl-RoleGranted-bytes32-address-address-[`++RoleGranted++`]] +:RoleRevoked: pass:normal[xref:#IAccessControl-RoleRevoked-bytes32-address-address-[`++RoleRevoked++`]] +:hasRole: pass:normal[xref:#IAccessControl-hasRole-bytes32-address-[`++hasRole++`]] +:getRoleAdmin: pass:normal[xref:#IAccessControl-getRoleAdmin-bytes32-[`++getRoleAdmin++`]] +:grantRole: pass:normal[xref:#IAccessControl-grantRole-bytes32-address-[`++grantRole++`]] +:revokeRole: pass:normal[xref:#IAccessControl-revokeRole-bytes32-address-[`++revokeRole++`]] +:renounceRole: pass:normal[xref:#IAccessControl-renounceRole-bytes32-address-[`++renounceRole++`]] + +[.contract] +[[IAccessControl]] +=== `++IAccessControl++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/access/IAccessControl.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/access/IAccessControl.sol"; +``` + +External interface of AccessControl declared to support ERC165 detection. + +[.contract-index] +.Functions +-- +* {xref-IAccessControl-hasRole-bytes32-address-}[`++hasRole(role, account)++`] +* {xref-IAccessControl-getRoleAdmin-bytes32-}[`++getRoleAdmin(role)++`] +* {xref-IAccessControl-grantRole-bytes32-address-}[`++grantRole(role, account)++`] +* {xref-IAccessControl-revokeRole-bytes32-address-}[`++revokeRole(role, account)++`] +* {xref-IAccessControl-renounceRole-bytes32-address-}[`++renounceRole(role, callerConfirmation)++`] + +-- + +[.contract-index] +.Events +-- +* {xref-IAccessControl-RoleAdminChanged-bytes32-bytes32-bytes32-}[`++RoleAdminChanged(role, previousAdminRole, newAdminRole)++`] +* {xref-IAccessControl-RoleGranted-bytes32-address-address-}[`++RoleGranted(role, account, sender)++`] +* {xref-IAccessControl-RoleRevoked-bytes32-address-address-}[`++RoleRevoked(role, account, sender)++`] + +-- + +[.contract-index] +.Errors +-- +* {xref-IAccessControl-AccessControlUnauthorizedAccount-address-bytes32-}[`++AccessControlUnauthorizedAccount(account, neededRole)++`] +* {xref-IAccessControl-AccessControlBadConfirmation--}[`++AccessControlBadConfirmation()++`] + +-- + +[.contract-item] +[[IAccessControl-hasRole-bytes32-address-]] +==== `[.contract-item-name]#++hasRole++#++(bytes32 role, address account) β†’ bool++` [.item-kind]#external# + +Returns `true` if `account` has been granted `role`. + +[.contract-item] +[[IAccessControl-getRoleAdmin-bytes32-]] +==== `[.contract-item-name]#++getRoleAdmin++#++(bytes32 role) β†’ bytes32++` [.item-kind]#external# + +Returns the admin role that controls `role`. See {grantRole} and +{revokeRole}. + +To change a role's admin, use {AccessControl-_setRoleAdmin}. + +[.contract-item] +[[IAccessControl-grantRole-bytes32-address-]] +==== `[.contract-item-name]#++grantRole++#++(bytes32 role, address account)++` [.item-kind]#external# + +Grants `role` to `account`. + +If `account` had not been already granted `role`, emits a {RoleGranted} +event. + +Requirements: + +- the caller must have ``role``'s admin role. + +[.contract-item] +[[IAccessControl-revokeRole-bytes32-address-]] +==== `[.contract-item-name]#++revokeRole++#++(bytes32 role, address account)++` [.item-kind]#external# + +Revokes `role` from `account`. + +If `account` had been granted `role`, emits a {RoleRevoked} event. + +Requirements: + +- the caller must have ``role``'s admin role. + +[.contract-item] +[[IAccessControl-renounceRole-bytes32-address-]] +==== `[.contract-item-name]#++renounceRole++#++(bytes32 role, address callerConfirmation)++` [.item-kind]#external# + +Revokes `role` from the calling account. + +Roles are often managed via {grantRole} and {revokeRole}: this function's +purpose is to provide a mechanism for accounts to lose their privileges +if they are compromised (such as when a trusted device is misplaced). + +If the calling account had been granted `role`, emits a {RoleRevoked} +event. + +Requirements: + +- the caller must be `callerConfirmation`. + +[.contract-item] +[[IAccessControl-RoleAdminChanged-bytes32-bytes32-bytes32-]] +==== `[.contract-item-name]#++RoleAdminChanged++#++(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole)++` [.item-kind]#event# + +Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole` + +`DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite +{RoleAdminChanged} not being emitted signaling this. + +[.contract-item] +[[IAccessControl-RoleGranted-bytes32-address-address-]] +==== `[.contract-item-name]#++RoleGranted++#++(bytes32 indexed role, address indexed account, address indexed sender)++` [.item-kind]#event# + +Emitted when `account` is granted `role`. + +`sender` is the account that originated the contract call, an admin role +bearer except when using {AccessControl-_setupRole}. + +[.contract-item] +[[IAccessControl-RoleRevoked-bytes32-address-address-]] +==== `[.contract-item-name]#++RoleRevoked++#++(bytes32 indexed role, address indexed account, address indexed sender)++` [.item-kind]#event# + +Emitted when `account` is revoked `role`. + +`sender` is the account that originated the contract call: + - if using `revokeRole`, it is the admin role bearer + - if using `renounceRole`, it is the role bearer (i.e. `account`) + +[.contract-item] +[[IAccessControl-AccessControlUnauthorizedAccount-address-bytes32-]] +==== `[.contract-item-name]#++AccessControlUnauthorizedAccount++#++(address account, bytes32 neededRole)++` [.item-kind]#error# + +The `account` is missing a role. + +[.contract-item] +[[IAccessControl-AccessControlBadConfirmation--]] +==== `[.contract-item-name]#++AccessControlBadConfirmation++#++()++` [.item-kind]#error# + +The caller of a function is not the expected one. + +NOTE: Don't confuse with {AccessControlUnauthorizedAccount}. + +:RoleData: pass:normal[xref:#AccessControl-RoleData[`++RoleData++`]] +:DEFAULT_ADMIN_ROLE: pass:normal[xref:#AccessControl-DEFAULT_ADMIN_ROLE-bytes32[`++DEFAULT_ADMIN_ROLE++`]] +:onlyRole: pass:normal[xref:#AccessControl-onlyRole-bytes32-[`++onlyRole++`]] +:supportsInterface: pass:normal[xref:#AccessControl-supportsInterface-bytes4-[`++supportsInterface++`]] +:hasRole: pass:normal[xref:#AccessControl-hasRole-bytes32-address-[`++hasRole++`]] +:_checkRole: pass:normal[xref:#AccessControl-_checkRole-bytes32-[`++_checkRole++`]] +:_checkRole: pass:normal[xref:#AccessControl-_checkRole-bytes32-address-[`++_checkRole++`]] +:getRoleAdmin: pass:normal[xref:#AccessControl-getRoleAdmin-bytes32-[`++getRoleAdmin++`]] +:grantRole: pass:normal[xref:#AccessControl-grantRole-bytes32-address-[`++grantRole++`]] +:revokeRole: pass:normal[xref:#AccessControl-revokeRole-bytes32-address-[`++revokeRole++`]] +:renounceRole: pass:normal[xref:#AccessControl-renounceRole-bytes32-address-[`++renounceRole++`]] +:_setRoleAdmin: pass:normal[xref:#AccessControl-_setRoleAdmin-bytes32-bytes32-[`++_setRoleAdmin++`]] +:_grantRole: pass:normal[xref:#AccessControl-_grantRole-bytes32-address-[`++_grantRole++`]] +:_revokeRole: pass:normal[xref:#AccessControl-_revokeRole-bytes32-address-[`++_revokeRole++`]] + +[.contract] +[[AccessControl]] +=== `++AccessControl++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/access/AccessControl.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/access/AccessControl.sol"; +``` + +Contract module that allows children to implement role-based access +control mechanisms. This is a lightweight version that doesn't allow enumerating role +members except through off-chain means by accessing the contract event logs. Some +applications may benefit from on-chain enumerability, for those cases see +{AccessControlEnumerable}. + +Roles are referred to by their `bytes32` identifier. These should be exposed +in the external API and be unique. The best way to achieve this is by +using `public constant` hash digests: + +```solidity +bytes32 public constant MY_ROLE = keccak256("MY_ROLE"); +``` + +Roles can be used to represent a set of permissions. To restrict access to a +function call, use {hasRole}: + +```solidity +function foo() public { + require(hasRole(MY_ROLE, msg.sender)); + ... +} +``` + +Roles can be granted and revoked dynamically via the {grantRole} and +{revokeRole} functions. Each role has an associated admin role, and only +accounts that have a role's admin role can call {grantRole} and {revokeRole}. + +By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means +that only accounts with this role will be able to grant or revoke other +roles. More complex role relationships can be created by using +{_setRoleAdmin}. + +WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to +grant and revoke this role. Extra precautions should be taken to secure +accounts that have been granted it. We recommend using {AccessControlDefaultAdminRules} +to enforce additional security measures for this role. + +[.contract-index] +.Modifiers +-- +* {xref-AccessControl-onlyRole-bytes32-}[`++onlyRole(role)++`] +-- + +[.contract-index] +.Functions +-- +* {xref-AccessControl-supportsInterface-bytes4-}[`++supportsInterface(interfaceId)++`] +* {xref-AccessControl-hasRole-bytes32-address-}[`++hasRole(role, account)++`] +* {xref-AccessControl-_checkRole-bytes32-}[`++_checkRole(role)++`] +* {xref-AccessControl-_checkRole-bytes32-address-}[`++_checkRole(role, account)++`] +* {xref-AccessControl-getRoleAdmin-bytes32-}[`++getRoleAdmin(role)++`] +* {xref-AccessControl-grantRole-bytes32-address-}[`++grantRole(role, account)++`] +* {xref-AccessControl-revokeRole-bytes32-address-}[`++revokeRole(role, account)++`] +* {xref-AccessControl-renounceRole-bytes32-address-}[`++renounceRole(role, callerConfirmation)++`] +* {xref-AccessControl-_setRoleAdmin-bytes32-bytes32-}[`++_setRoleAdmin(role, adminRole)++`] +* {xref-AccessControl-_grantRole-bytes32-address-}[`++_grantRole(role, account)++`] +* {xref-AccessControl-_revokeRole-bytes32-address-}[`++_revokeRole(role, account)++`] + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +[.contract-subindex-inherited] +.IAccessControl + +-- + +[.contract-index] +.Events +-- + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +[.contract-subindex-inherited] +.IAccessControl +* {xref-IAccessControl-RoleAdminChanged-bytes32-bytes32-bytes32-}[`++RoleAdminChanged(role, previousAdminRole, newAdminRole)++`] +* {xref-IAccessControl-RoleGranted-bytes32-address-address-}[`++RoleGranted(role, account, sender)++`] +* {xref-IAccessControl-RoleRevoked-bytes32-address-address-}[`++RoleRevoked(role, account, sender)++`] + +-- + +[.contract-index] +.Errors +-- + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +[.contract-subindex-inherited] +.IAccessControl +* {xref-IAccessControl-AccessControlUnauthorizedAccount-address-bytes32-}[`++AccessControlUnauthorizedAccount(account, neededRole)++`] +* {xref-IAccessControl-AccessControlBadConfirmation--}[`++AccessControlBadConfirmation()++`] + +-- + +[.contract-item] +[[AccessControl-onlyRole-bytes32-]] +==== `[.contract-item-name]#++onlyRole++#++(bytes32 role)++` [.item-kind]#modifier# + +Modifier that checks that an account has a specific role. Reverts +with an {AccessControlUnauthorizedAccount} error including the required role. + +[.contract-item] +[[AccessControl-supportsInterface-bytes4-]] +==== `[.contract-item-name]#++supportsInterface++#++(bytes4 interfaceId) β†’ bool++` [.item-kind]#public# + +See {IERC165-supportsInterface}. + +[.contract-item] +[[AccessControl-hasRole-bytes32-address-]] +==== `[.contract-item-name]#++hasRole++#++(bytes32 role, address account) β†’ bool++` [.item-kind]#public# + +Returns `true` if `account` has been granted `role`. + +[.contract-item] +[[AccessControl-_checkRole-bytes32-]] +==== `[.contract-item-name]#++_checkRole++#++(bytes32 role)++` [.item-kind]#internal# + +Reverts with an {AccessControlUnauthorizedAccount} error if `_msgSender()` +is missing `role`. Overriding this function changes the behavior of the {onlyRole} modifier. + +[.contract-item] +[[AccessControl-_checkRole-bytes32-address-]] +==== `[.contract-item-name]#++_checkRole++#++(bytes32 role, address account)++` [.item-kind]#internal# + +Reverts with an {AccessControlUnauthorizedAccount} error if `account` +is missing `role`. + +[.contract-item] +[[AccessControl-getRoleAdmin-bytes32-]] +==== `[.contract-item-name]#++getRoleAdmin++#++(bytes32 role) β†’ bytes32++` [.item-kind]#public# + +Returns the admin role that controls `role`. See {grantRole} and +{revokeRole}. + +To change a role's admin, use {_setRoleAdmin}. + +[.contract-item] +[[AccessControl-grantRole-bytes32-address-]] +==== `[.contract-item-name]#++grantRole++#++(bytes32 role, address account)++` [.item-kind]#public# + +Grants `role` to `account`. + +If `account` had not been already granted `role`, emits a {RoleGranted} +event. + +Requirements: + +- the caller must have ``role``'s admin role. + +May emit a {RoleGranted} event. + +[.contract-item] +[[AccessControl-revokeRole-bytes32-address-]] +==== `[.contract-item-name]#++revokeRole++#++(bytes32 role, address account)++` [.item-kind]#public# + +Revokes `role` from `account`. + +If `account` had been granted `role`, emits a {RoleRevoked} event. + +Requirements: + +- the caller must have ``role``'s admin role. + +May emit a {RoleRevoked} event. + +[.contract-item] +[[AccessControl-renounceRole-bytes32-address-]] +==== `[.contract-item-name]#++renounceRole++#++(bytes32 role, address callerConfirmation)++` [.item-kind]#public# + +Revokes `role` from the calling account. + +Roles are often managed via {grantRole} and {revokeRole}: this function's +purpose is to provide a mechanism for accounts to lose their privileges +if they are compromised (such as when a trusted device is misplaced). + +If the calling account had been revoked `role`, emits a {RoleRevoked} +event. + +Requirements: + +- the caller must be `callerConfirmation`. + +May emit a {RoleRevoked} event. + +[.contract-item] +[[AccessControl-_setRoleAdmin-bytes32-bytes32-]] +==== `[.contract-item-name]#++_setRoleAdmin++#++(bytes32 role, bytes32 adminRole)++` [.item-kind]#internal# + +Sets `adminRole` as ``role``'s admin role. + +Emits a {RoleAdminChanged} event. + +[.contract-item] +[[AccessControl-_grantRole-bytes32-address-]] +==== `[.contract-item-name]#++_grantRole++#++(bytes32 role, address account) β†’ bool++` [.item-kind]#internal# + +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. + +[.contract-item] +[[AccessControl-_revokeRole-bytes32-address-]] +==== `[.contract-item-name]#++_revokeRole++#++(bytes32 role, address account) β†’ bool++` [.item-kind]#internal# + +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. + +== Extensions + +:getRoleMember: pass:normal[xref:#IAccessControlEnumerable-getRoleMember-bytes32-uint256-[`++getRoleMember++`]] +:getRoleMemberCount: pass:normal[xref:#IAccessControlEnumerable-getRoleMemberCount-bytes32-[`++getRoleMemberCount++`]] + +[.contract] +[[IAccessControlEnumerable]] +=== `++IAccessControlEnumerable++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/access/extensions/IAccessControlEnumerable.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/access/extensions/IAccessControlEnumerable.sol"; +``` + +External interface of AccessControlEnumerable declared to support ERC165 detection. + +[.contract-index] +.Functions +-- +* {xref-IAccessControlEnumerable-getRoleMember-bytes32-uint256-}[`++getRoleMember(role, index)++`] +* {xref-IAccessControlEnumerable-getRoleMemberCount-bytes32-}[`++getRoleMemberCount(role)++`] + +[.contract-subindex-inherited] +.IAccessControl +* {xref-IAccessControl-hasRole-bytes32-address-}[`++hasRole(role, account)++`] +* {xref-IAccessControl-getRoleAdmin-bytes32-}[`++getRoleAdmin(role)++`] +* {xref-IAccessControl-grantRole-bytes32-address-}[`++grantRole(role, account)++`] +* {xref-IAccessControl-revokeRole-bytes32-address-}[`++revokeRole(role, account)++`] +* {xref-IAccessControl-renounceRole-bytes32-address-}[`++renounceRole(role, callerConfirmation)++`] + +-- + +[.contract-index] +.Events +-- + +[.contract-subindex-inherited] +.IAccessControl +* {xref-IAccessControl-RoleAdminChanged-bytes32-bytes32-bytes32-}[`++RoleAdminChanged(role, previousAdminRole, newAdminRole)++`] +* {xref-IAccessControl-RoleGranted-bytes32-address-address-}[`++RoleGranted(role, account, sender)++`] +* {xref-IAccessControl-RoleRevoked-bytes32-address-address-}[`++RoleRevoked(role, account, sender)++`] + +-- + +[.contract-index] +.Errors +-- + +[.contract-subindex-inherited] +.IAccessControl +* {xref-IAccessControl-AccessControlUnauthorizedAccount-address-bytes32-}[`++AccessControlUnauthorizedAccount(account, neededRole)++`] +* {xref-IAccessControl-AccessControlBadConfirmation--}[`++AccessControlBadConfirmation()++`] + +-- + +[.contract-item] +[[IAccessControlEnumerable-getRoleMember-bytes32-uint256-]] +==== `[.contract-item-name]#++getRoleMember++#++(bytes32 role, uint256 index) β†’ address++` [.item-kind]#external# + +Returns one of the accounts that have `role`. `index` must be a +value between 0 and {getRoleMemberCount}, non-inclusive. + +Role bearers are not sorted in any particular way, and their ordering may +change at any point. + +WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure +you perform all queries on the same block. See the following +https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post] +for more information. + +[.contract-item] +[[IAccessControlEnumerable-getRoleMemberCount-bytes32-]] +==== `[.contract-item-name]#++getRoleMemberCount++#++(bytes32 role) β†’ uint256++` [.item-kind]#external# + +Returns the number of accounts that have `role`. Can be used +together with {getRoleMember} to enumerate all bearers of a role. + +:supportsInterface: pass:normal[xref:#AccessControlEnumerable-supportsInterface-bytes4-[`++supportsInterface++`]] +:getRoleMember: pass:normal[xref:#AccessControlEnumerable-getRoleMember-bytes32-uint256-[`++getRoleMember++`]] +:getRoleMemberCount: pass:normal[xref:#AccessControlEnumerable-getRoleMemberCount-bytes32-[`++getRoleMemberCount++`]] +:_grantRole: pass:normal[xref:#AccessControlEnumerable-_grantRole-bytes32-address-[`++_grantRole++`]] +:_revokeRole: pass:normal[xref:#AccessControlEnumerable-_revokeRole-bytes32-address-[`++_revokeRole++`]] + +[.contract] +[[AccessControlEnumerable]] +=== `++AccessControlEnumerable++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/access/extensions/AccessControlEnumerable.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/access/extensions/AccessControlEnumerable.sol"; +``` + +Extension of {AccessControl} that allows enumerating the members of each role. + +[.contract-index] +.Functions +-- +* {xref-AccessControlEnumerable-supportsInterface-bytes4-}[`++supportsInterface(interfaceId)++`] +* {xref-AccessControlEnumerable-getRoleMember-bytes32-uint256-}[`++getRoleMember(role, index)++`] +* {xref-AccessControlEnumerable-getRoleMemberCount-bytes32-}[`++getRoleMemberCount(role)++`] +* {xref-AccessControlEnumerable-_grantRole-bytes32-address-}[`++_grantRole(role, account)++`] +* {xref-AccessControlEnumerable-_revokeRole-bytes32-address-}[`++_revokeRole(role, account)++`] + +[.contract-subindex-inherited] +.AccessControl +* {xref-AccessControl-hasRole-bytes32-address-}[`++hasRole(role, account)++`] +* {xref-AccessControl-_checkRole-bytes32-}[`++_checkRole(role)++`] +* {xref-AccessControl-_checkRole-bytes32-address-}[`++_checkRole(role, account)++`] +* {xref-AccessControl-getRoleAdmin-bytes32-}[`++getRoleAdmin(role)++`] +* {xref-AccessControl-grantRole-bytes32-address-}[`++grantRole(role, account)++`] +* {xref-AccessControl-revokeRole-bytes32-address-}[`++revokeRole(role, account)++`] +* {xref-AccessControl-renounceRole-bytes32-address-}[`++renounceRole(role, callerConfirmation)++`] +* {xref-AccessControl-_setRoleAdmin-bytes32-bytes32-}[`++_setRoleAdmin(role, adminRole)++`] + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +[.contract-subindex-inherited] +.IAccessControlEnumerable + +[.contract-subindex-inherited] +.IAccessControl + +-- + +[.contract-index] +.Events +-- + +[.contract-subindex-inherited] +.AccessControl + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +[.contract-subindex-inherited] +.IAccessControlEnumerable + +[.contract-subindex-inherited] +.IAccessControl +* {xref-IAccessControl-RoleAdminChanged-bytes32-bytes32-bytes32-}[`++RoleAdminChanged(role, previousAdminRole, newAdminRole)++`] +* {xref-IAccessControl-RoleGranted-bytes32-address-address-}[`++RoleGranted(role, account, sender)++`] +* {xref-IAccessControl-RoleRevoked-bytes32-address-address-}[`++RoleRevoked(role, account, sender)++`] + +-- + +[.contract-index] +.Errors +-- + +[.contract-subindex-inherited] +.AccessControl + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +[.contract-subindex-inherited] +.IAccessControlEnumerable + +[.contract-subindex-inherited] +.IAccessControl +* {xref-IAccessControl-AccessControlUnauthorizedAccount-address-bytes32-}[`++AccessControlUnauthorizedAccount(account, neededRole)++`] +* {xref-IAccessControl-AccessControlBadConfirmation--}[`++AccessControlBadConfirmation()++`] + +-- + +[.contract-item] +[[AccessControlEnumerable-supportsInterface-bytes4-]] +==== `[.contract-item-name]#++supportsInterface++#++(bytes4 interfaceId) β†’ bool++` [.item-kind]#public# + +See {IERC165-supportsInterface}. + +[.contract-item] +[[AccessControlEnumerable-getRoleMember-bytes32-uint256-]] +==== `[.contract-item-name]#++getRoleMember++#++(bytes32 role, uint256 index) β†’ address++` [.item-kind]#public# + +Returns one of the accounts that have `role`. `index` must be a +value between 0 and {getRoleMemberCount}, non-inclusive. + +Role bearers are not sorted in any particular way, and their ordering may +change at any point. + +WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure +you perform all queries on the same block. See the following +https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post] +for more information. + +[.contract-item] +[[AccessControlEnumerable-getRoleMemberCount-bytes32-]] +==== `[.contract-item-name]#++getRoleMemberCount++#++(bytes32 role) β†’ uint256++` [.item-kind]#public# + +Returns the number of accounts that have `role`. Can be used +together with {getRoleMember} to enumerate all bearers of a role. + +[.contract-item] +[[AccessControlEnumerable-_grantRole-bytes32-address-]] +==== `[.contract-item-name]#++_grantRole++#++(bytes32 role, address account) β†’ bool++` [.item-kind]#internal# + +Overload {AccessControl-_grantRole} to track enumerable memberships + +[.contract-item] +[[AccessControlEnumerable-_revokeRole-bytes32-address-]] +==== `[.contract-item-name]#++_revokeRole++#++(bytes32 role, address account) β†’ bool++` [.item-kind]#internal# + +Overload {AccessControl-_revokeRole} to track enumerable memberships + +:AccessControlInvalidDefaultAdmin: pass:normal[xref:#IAccessControlDefaultAdminRules-AccessControlInvalidDefaultAdmin-address-[`++AccessControlInvalidDefaultAdmin++`]] +:AccessControlEnforcedDefaultAdminRules: pass:normal[xref:#IAccessControlDefaultAdminRules-AccessControlEnforcedDefaultAdminRules--[`++AccessControlEnforcedDefaultAdminRules++`]] +:AccessControlEnforcedDefaultAdminDelay: pass:normal[xref:#IAccessControlDefaultAdminRules-AccessControlEnforcedDefaultAdminDelay-uint48-[`++AccessControlEnforcedDefaultAdminDelay++`]] +:DefaultAdminTransferScheduled: pass:normal[xref:#IAccessControlDefaultAdminRules-DefaultAdminTransferScheduled-address-uint48-[`++DefaultAdminTransferScheduled++`]] +:DefaultAdminTransferCanceled: pass:normal[xref:#IAccessControlDefaultAdminRules-DefaultAdminTransferCanceled--[`++DefaultAdminTransferCanceled++`]] +:DefaultAdminDelayChangeScheduled: pass:normal[xref:#IAccessControlDefaultAdminRules-DefaultAdminDelayChangeScheduled-uint48-uint48-[`++DefaultAdminDelayChangeScheduled++`]] +:DefaultAdminDelayChangeCanceled: pass:normal[xref:#IAccessControlDefaultAdminRules-DefaultAdminDelayChangeCanceled--[`++DefaultAdminDelayChangeCanceled++`]] +:defaultAdmin: pass:normal[xref:#IAccessControlDefaultAdminRules-defaultAdmin--[`++defaultAdmin++`]] +:pendingDefaultAdmin: pass:normal[xref:#IAccessControlDefaultAdminRules-pendingDefaultAdmin--[`++pendingDefaultAdmin++`]] +:defaultAdminDelay: pass:normal[xref:#IAccessControlDefaultAdminRules-defaultAdminDelay--[`++defaultAdminDelay++`]] +:pendingDefaultAdminDelay: pass:normal[xref:#IAccessControlDefaultAdminRules-pendingDefaultAdminDelay--[`++pendingDefaultAdminDelay++`]] +:beginDefaultAdminTransfer: pass:normal[xref:#IAccessControlDefaultAdminRules-beginDefaultAdminTransfer-address-[`++beginDefaultAdminTransfer++`]] +:cancelDefaultAdminTransfer: pass:normal[xref:#IAccessControlDefaultAdminRules-cancelDefaultAdminTransfer--[`++cancelDefaultAdminTransfer++`]] +:acceptDefaultAdminTransfer: pass:normal[xref:#IAccessControlDefaultAdminRules-acceptDefaultAdminTransfer--[`++acceptDefaultAdminTransfer++`]] +:changeDefaultAdminDelay: pass:normal[xref:#IAccessControlDefaultAdminRules-changeDefaultAdminDelay-uint48-[`++changeDefaultAdminDelay++`]] +:rollbackDefaultAdminDelay: pass:normal[xref:#IAccessControlDefaultAdminRules-rollbackDefaultAdminDelay--[`++rollbackDefaultAdminDelay++`]] +:defaultAdminDelayIncreaseWait: pass:normal[xref:#IAccessControlDefaultAdminRules-defaultAdminDelayIncreaseWait--[`++defaultAdminDelayIncreaseWait++`]] + +[.contract] +[[IAccessControlDefaultAdminRules]] +=== `++IAccessControlDefaultAdminRules++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/access/extensions/IAccessControlDefaultAdminRules.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/access/extensions/IAccessControlDefaultAdminRules.sol"; +``` + +External interface of AccessControlDefaultAdminRules declared to support ERC165 detection. + +[.contract-index] +.Functions +-- +* {xref-IAccessControlDefaultAdminRules-defaultAdmin--}[`++defaultAdmin()++`] +* {xref-IAccessControlDefaultAdminRules-pendingDefaultAdmin--}[`++pendingDefaultAdmin()++`] +* {xref-IAccessControlDefaultAdminRules-defaultAdminDelay--}[`++defaultAdminDelay()++`] +* {xref-IAccessControlDefaultAdminRules-pendingDefaultAdminDelay--}[`++pendingDefaultAdminDelay()++`] +* {xref-IAccessControlDefaultAdminRules-beginDefaultAdminTransfer-address-}[`++beginDefaultAdminTransfer(newAdmin)++`] +* {xref-IAccessControlDefaultAdminRules-cancelDefaultAdminTransfer--}[`++cancelDefaultAdminTransfer()++`] +* {xref-IAccessControlDefaultAdminRules-acceptDefaultAdminTransfer--}[`++acceptDefaultAdminTransfer()++`] +* {xref-IAccessControlDefaultAdminRules-changeDefaultAdminDelay-uint48-}[`++changeDefaultAdminDelay(newDelay)++`] +* {xref-IAccessControlDefaultAdminRules-rollbackDefaultAdminDelay--}[`++rollbackDefaultAdminDelay()++`] +* {xref-IAccessControlDefaultAdminRules-defaultAdminDelayIncreaseWait--}[`++defaultAdminDelayIncreaseWait()++`] + +[.contract-subindex-inherited] +.IAccessControl +* {xref-IAccessControl-hasRole-bytes32-address-}[`++hasRole(role, account)++`] +* {xref-IAccessControl-getRoleAdmin-bytes32-}[`++getRoleAdmin(role)++`] +* {xref-IAccessControl-grantRole-bytes32-address-}[`++grantRole(role, account)++`] +* {xref-IAccessControl-revokeRole-bytes32-address-}[`++revokeRole(role, account)++`] +* {xref-IAccessControl-renounceRole-bytes32-address-}[`++renounceRole(role, callerConfirmation)++`] + +-- + +[.contract-index] +.Events +-- +* {xref-IAccessControlDefaultAdminRules-DefaultAdminTransferScheduled-address-uint48-}[`++DefaultAdminTransferScheduled(newAdmin, acceptSchedule)++`] +* {xref-IAccessControlDefaultAdminRules-DefaultAdminTransferCanceled--}[`++DefaultAdminTransferCanceled()++`] +* {xref-IAccessControlDefaultAdminRules-DefaultAdminDelayChangeScheduled-uint48-uint48-}[`++DefaultAdminDelayChangeScheduled(newDelay, effectSchedule)++`] +* {xref-IAccessControlDefaultAdminRules-DefaultAdminDelayChangeCanceled--}[`++DefaultAdminDelayChangeCanceled()++`] + +[.contract-subindex-inherited] +.IAccessControl +* {xref-IAccessControl-RoleAdminChanged-bytes32-bytes32-bytes32-}[`++RoleAdminChanged(role, previousAdminRole, newAdminRole)++`] +* {xref-IAccessControl-RoleGranted-bytes32-address-address-}[`++RoleGranted(role, account, sender)++`] +* {xref-IAccessControl-RoleRevoked-bytes32-address-address-}[`++RoleRevoked(role, account, sender)++`] + +-- + +[.contract-index] +.Errors +-- +* {xref-IAccessControlDefaultAdminRules-AccessControlInvalidDefaultAdmin-address-}[`++AccessControlInvalidDefaultAdmin(defaultAdmin)++`] +* {xref-IAccessControlDefaultAdminRules-AccessControlEnforcedDefaultAdminRules--}[`++AccessControlEnforcedDefaultAdminRules()++`] +* {xref-IAccessControlDefaultAdminRules-AccessControlEnforcedDefaultAdminDelay-uint48-}[`++AccessControlEnforcedDefaultAdminDelay(schedule)++`] + +[.contract-subindex-inherited] +.IAccessControl +* {xref-IAccessControl-AccessControlUnauthorizedAccount-address-bytes32-}[`++AccessControlUnauthorizedAccount(account, neededRole)++`] +* {xref-IAccessControl-AccessControlBadConfirmation--}[`++AccessControlBadConfirmation()++`] + +-- + +[.contract-item] +[[IAccessControlDefaultAdminRules-defaultAdmin--]] +==== `[.contract-item-name]#++defaultAdmin++#++() β†’ address++` [.item-kind]#external# + +Returns the address of the current `DEFAULT_ADMIN_ROLE` holder. + +[.contract-item] +[[IAccessControlDefaultAdminRules-pendingDefaultAdmin--]] +==== `[.contract-item-name]#++pendingDefaultAdmin++#++() β†’ address newAdmin, uint48 acceptSchedule++` [.item-kind]#external# + +Returns a tuple of a `newAdmin` and an accept schedule. + +After the `schedule` passes, the `newAdmin` will be able to accept the {defaultAdmin} role +by calling {acceptDefaultAdminTransfer}, completing the role transfer. + +A zero value only in `acceptSchedule` indicates no pending admin transfer. + +NOTE: A zero address `newAdmin` means that {defaultAdmin} is being renounced. + +[.contract-item] +[[IAccessControlDefaultAdminRules-defaultAdminDelay--]] +==== `[.contract-item-name]#++defaultAdminDelay++#++() β†’ uint48++` [.item-kind]#external# + +Returns the delay required to schedule the acceptance of a {defaultAdmin} transfer started. + +This delay will be added to the current timestamp when calling {beginDefaultAdminTransfer} to set +the acceptance schedule. + +NOTE: If a delay change has been scheduled, it will take effect as soon as the schedule passes, making this +function returns the new delay. See {changeDefaultAdminDelay}. + +[.contract-item] +[[IAccessControlDefaultAdminRules-pendingDefaultAdminDelay--]] +==== `[.contract-item-name]#++pendingDefaultAdminDelay++#++() β†’ uint48 newDelay, uint48 effectSchedule++` [.item-kind]#external# + +Returns a tuple of `newDelay` and an effect schedule. + +After the `schedule` passes, the `newDelay` will get into effect immediately for every +new {defaultAdmin} transfer started with {beginDefaultAdminTransfer}. + +A zero value only in `effectSchedule` indicates no pending delay change. + +NOTE: A zero value only for `newDelay` means that the next {defaultAdminDelay} +will be zero after the effect schedule. + +[.contract-item] +[[IAccessControlDefaultAdminRules-beginDefaultAdminTransfer-address-]] +==== `[.contract-item-name]#++beginDefaultAdminTransfer++#++(address newAdmin)++` [.item-kind]#external# + +Starts a {defaultAdmin} transfer by setting a {pendingDefaultAdmin} scheduled for acceptance +after the current timestamp plus a {defaultAdminDelay}. + +Requirements: + +- Only can be called by the current {defaultAdmin}. + +Emits a DefaultAdminRoleChangeStarted event. + +[.contract-item] +[[IAccessControlDefaultAdminRules-cancelDefaultAdminTransfer--]] +==== `[.contract-item-name]#++cancelDefaultAdminTransfer++#++()++` [.item-kind]#external# + +Cancels a {defaultAdmin} transfer previously started with {beginDefaultAdminTransfer}. + +A {pendingDefaultAdmin} not yet accepted can also be cancelled with this function. + +Requirements: + +- Only can be called by the current {defaultAdmin}. + +May emit a DefaultAdminTransferCanceled event. + +[.contract-item] +[[IAccessControlDefaultAdminRules-acceptDefaultAdminTransfer--]] +==== `[.contract-item-name]#++acceptDefaultAdminTransfer++#++()++` [.item-kind]#external# + +Completes a {defaultAdmin} transfer previously started with {beginDefaultAdminTransfer}. + +After calling the function: + +- `DEFAULT_ADMIN_ROLE` should be granted to the caller. +- `DEFAULT_ADMIN_ROLE` should be revoked from the previous holder. +- {pendingDefaultAdmin} should be reset to zero values. + +Requirements: + +- Only can be called by the {pendingDefaultAdmin}'s `newAdmin`. +- The {pendingDefaultAdmin}'s `acceptSchedule` should've passed. + +[.contract-item] +[[IAccessControlDefaultAdminRules-changeDefaultAdminDelay-uint48-]] +==== `[.contract-item-name]#++changeDefaultAdminDelay++#++(uint48 newDelay)++` [.item-kind]#external# + +Initiates a {defaultAdminDelay} update by setting a {pendingDefaultAdminDelay} scheduled for getting +into effect after the current timestamp plus a {defaultAdminDelay}. + +This function guarantees that any call to {beginDefaultAdminTransfer} done between the timestamp this +method is called and the {pendingDefaultAdminDelay} effect schedule will use the current {defaultAdminDelay} +set before calling. + +The {pendingDefaultAdminDelay}'s effect schedule is defined in a way that waiting until the schedule and then +calling {beginDefaultAdminTransfer} with the new delay will take at least the same as another {defaultAdmin} +complete transfer (including acceptance). + +The schedule is designed for two scenarios: + +- When the delay is changed for a larger one the schedule is `block.timestamp + newDelay` capped by +{defaultAdminDelayIncreaseWait}. +- When the delay is changed for a shorter one, the schedule is `block.timestamp + (current delay - new delay)`. + +A {pendingDefaultAdminDelay} that never got into effect will be canceled in favor of a new scheduled change. + +Requirements: + +- Only can be called by the current {defaultAdmin}. + +Emits a DefaultAdminDelayChangeScheduled event and may emit a DefaultAdminDelayChangeCanceled event. + +[.contract-item] +[[IAccessControlDefaultAdminRules-rollbackDefaultAdminDelay--]] +==== `[.contract-item-name]#++rollbackDefaultAdminDelay++#++()++` [.item-kind]#external# + +Cancels a scheduled {defaultAdminDelay} change. + +Requirements: + +- Only can be called by the current {defaultAdmin}. + +May emit a DefaultAdminDelayChangeCanceled event. + +[.contract-item] +[[IAccessControlDefaultAdminRules-defaultAdminDelayIncreaseWait--]] +==== `[.contract-item-name]#++defaultAdminDelayIncreaseWait++#++() β†’ uint48++` [.item-kind]#external# + +Maximum time in seconds for an increase to {defaultAdminDelay} (that is scheduled using {changeDefaultAdminDelay}) +to take effect. Default to 5 days. + +When the {defaultAdminDelay} is scheduled to be increased, it goes into effect after the new delay has passed with +the purpose of giving enough time for reverting any accidental change (i.e. using milliseconds instead of seconds) +that may lock the contract. However, to avoid excessive schedules, the wait is capped by this function and it can +be overrode for a custom {defaultAdminDelay} increase scheduling. + +IMPORTANT: Make sure to add a reasonable amount of time while overriding this value, otherwise, +there's a risk of setting a high new delay that goes into effect almost immediately without the +possibility of human intervention in the case of an input error (eg. set milliseconds instead of seconds). + +[.contract-item] +[[IAccessControlDefaultAdminRules-DefaultAdminTransferScheduled-address-uint48-]] +==== `[.contract-item-name]#++DefaultAdminTransferScheduled++#++(address indexed newAdmin, uint48 acceptSchedule)++` [.item-kind]#event# + +Emitted when a {defaultAdmin} transfer is started, setting `newAdmin` as the next +address to become the {defaultAdmin} by calling {acceptDefaultAdminTransfer} only after `acceptSchedule` +passes. + +[.contract-item] +[[IAccessControlDefaultAdminRules-DefaultAdminTransferCanceled--]] +==== `[.contract-item-name]#++DefaultAdminTransferCanceled++#++()++` [.item-kind]#event# + +Emitted when a {pendingDefaultAdmin} is reset if it was never accepted, regardless of its schedule. + +[.contract-item] +[[IAccessControlDefaultAdminRules-DefaultAdminDelayChangeScheduled-uint48-uint48-]] +==== `[.contract-item-name]#++DefaultAdminDelayChangeScheduled++#++(uint48 newDelay, uint48 effectSchedule)++` [.item-kind]#event# + +Emitted when a {defaultAdminDelay} change is started, setting `newDelay` as the next +delay to be applied between default admin transfer after `effectSchedule` has passed. + +[.contract-item] +[[IAccessControlDefaultAdminRules-DefaultAdminDelayChangeCanceled--]] +==== `[.contract-item-name]#++DefaultAdminDelayChangeCanceled++#++()++` [.item-kind]#event# + +Emitted when a {pendingDefaultAdminDelay} is reset if its schedule didn't pass. + +[.contract-item] +[[IAccessControlDefaultAdminRules-AccessControlInvalidDefaultAdmin-address-]] +==== `[.contract-item-name]#++AccessControlInvalidDefaultAdmin++#++(address defaultAdmin)++` [.item-kind]#error# + +The new default admin is not a valid default admin. + +[.contract-item] +[[IAccessControlDefaultAdminRules-AccessControlEnforcedDefaultAdminRules--]] +==== `[.contract-item-name]#++AccessControlEnforcedDefaultAdminRules++#++()++` [.item-kind]#error# + +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. + +[.contract-item] +[[IAccessControlDefaultAdminRules-AccessControlEnforcedDefaultAdminDelay-uint48-]] +==== `[.contract-item-name]#++AccessControlEnforcedDefaultAdminDelay++#++(uint48 schedule)++` [.item-kind]#error# + +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. + +:constructor: pass:normal[xref:#AccessControlDefaultAdminRules-constructor-uint48-address-[`++constructor++`]] +:supportsInterface: pass:normal[xref:#AccessControlDefaultAdminRules-supportsInterface-bytes4-[`++supportsInterface++`]] +:owner: pass:normal[xref:#AccessControlDefaultAdminRules-owner--[`++owner++`]] +:grantRole: pass:normal[xref:#AccessControlDefaultAdminRules-grantRole-bytes32-address-[`++grantRole++`]] +:revokeRole: pass:normal[xref:#AccessControlDefaultAdminRules-revokeRole-bytes32-address-[`++revokeRole++`]] +:renounceRole: pass:normal[xref:#AccessControlDefaultAdminRules-renounceRole-bytes32-address-[`++renounceRole++`]] +:_grantRole: pass:normal[xref:#AccessControlDefaultAdminRules-_grantRole-bytes32-address-[`++_grantRole++`]] +:_revokeRole: pass:normal[xref:#AccessControlDefaultAdminRules-_revokeRole-bytes32-address-[`++_revokeRole++`]] +:_setRoleAdmin: pass:normal[xref:#AccessControlDefaultAdminRules-_setRoleAdmin-bytes32-bytes32-[`++_setRoleAdmin++`]] +:defaultAdmin: pass:normal[xref:#AccessControlDefaultAdminRules-defaultAdmin--[`++defaultAdmin++`]] +:pendingDefaultAdmin: pass:normal[xref:#AccessControlDefaultAdminRules-pendingDefaultAdmin--[`++pendingDefaultAdmin++`]] +:defaultAdminDelay: pass:normal[xref:#AccessControlDefaultAdminRules-defaultAdminDelay--[`++defaultAdminDelay++`]] +:pendingDefaultAdminDelay: pass:normal[xref:#AccessControlDefaultAdminRules-pendingDefaultAdminDelay--[`++pendingDefaultAdminDelay++`]] +:defaultAdminDelayIncreaseWait: pass:normal[xref:#AccessControlDefaultAdminRules-defaultAdminDelayIncreaseWait--[`++defaultAdminDelayIncreaseWait++`]] +:beginDefaultAdminTransfer: pass:normal[xref:#AccessControlDefaultAdminRules-beginDefaultAdminTransfer-address-[`++beginDefaultAdminTransfer++`]] +:_beginDefaultAdminTransfer: pass:normal[xref:#AccessControlDefaultAdminRules-_beginDefaultAdminTransfer-address-[`++_beginDefaultAdminTransfer++`]] +:cancelDefaultAdminTransfer: pass:normal[xref:#AccessControlDefaultAdminRules-cancelDefaultAdminTransfer--[`++cancelDefaultAdminTransfer++`]] +:_cancelDefaultAdminTransfer: pass:normal[xref:#AccessControlDefaultAdminRules-_cancelDefaultAdminTransfer--[`++_cancelDefaultAdminTransfer++`]] +:acceptDefaultAdminTransfer: pass:normal[xref:#AccessControlDefaultAdminRules-acceptDefaultAdminTransfer--[`++acceptDefaultAdminTransfer++`]] +:_acceptDefaultAdminTransfer: pass:normal[xref:#AccessControlDefaultAdminRules-_acceptDefaultAdminTransfer--[`++_acceptDefaultAdminTransfer++`]] +:changeDefaultAdminDelay: pass:normal[xref:#AccessControlDefaultAdminRules-changeDefaultAdminDelay-uint48-[`++changeDefaultAdminDelay++`]] +:_changeDefaultAdminDelay: pass:normal[xref:#AccessControlDefaultAdminRules-_changeDefaultAdminDelay-uint48-[`++_changeDefaultAdminDelay++`]] +:rollbackDefaultAdminDelay: pass:normal[xref:#AccessControlDefaultAdminRules-rollbackDefaultAdminDelay--[`++rollbackDefaultAdminDelay++`]] +:_rollbackDefaultAdminDelay: pass:normal[xref:#AccessControlDefaultAdminRules-_rollbackDefaultAdminDelay--[`++_rollbackDefaultAdminDelay++`]] +:_delayChangeWait: pass:normal[xref:#AccessControlDefaultAdminRules-_delayChangeWait-uint48-[`++_delayChangeWait++`]] + +[.contract] +[[AccessControlDefaultAdminRules]] +=== `++AccessControlDefaultAdminRules++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/access/extensions/AccessControlDefaultAdminRules.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/access/extensions/AccessControlDefaultAdminRules.sol"; +``` + +Extension of {AccessControl} that allows specifying special rules to manage +the `DEFAULT_ADMIN_ROLE` holder, which is a sensitive role with special permissions +over other roles that may potentially have privileged rights in the system. + +If a specific role doesn't have an admin role assigned, the holder of the +`DEFAULT_ADMIN_ROLE` will have the ability to grant it and revoke it. + +This contract implements the following risk mitigations on top of {AccessControl}: + +* Only one account holds the `DEFAULT_ADMIN_ROLE` since deployment until it's potentially renounced. +* Enforces a 2-step process to transfer the `DEFAULT_ADMIN_ROLE` to another account. +* Enforces a configurable delay between the two steps, with the ability to cancel before the transfer is accepted. +* The delay can be changed by scheduling, see {changeDefaultAdminDelay}. +* It is not possible to use another role to manage the `DEFAULT_ADMIN_ROLE`. + +Example usage: + +```solidity +contract MyToken is AccessControlDefaultAdminRules { + constructor() AccessControlDefaultAdminRules( + 3 days, + msg.sender // Explicit initial `DEFAULT_ADMIN_ROLE` holder + ) {} +} +``` + +[.contract-index] +.Functions +-- +* {xref-AccessControlDefaultAdminRules-constructor-uint48-address-}[`++constructor(initialDelay, initialDefaultAdmin)++`] +* {xref-AccessControlDefaultAdminRules-supportsInterface-bytes4-}[`++supportsInterface(interfaceId)++`] +* {xref-AccessControlDefaultAdminRules-owner--}[`++owner()++`] +* {xref-AccessControlDefaultAdminRules-grantRole-bytes32-address-}[`++grantRole(role, account)++`] +* {xref-AccessControlDefaultAdminRules-revokeRole-bytes32-address-}[`++revokeRole(role, account)++`] +* {xref-AccessControlDefaultAdminRules-renounceRole-bytes32-address-}[`++renounceRole(role, account)++`] +* {xref-AccessControlDefaultAdminRules-_grantRole-bytes32-address-}[`++_grantRole(role, account)++`] +* {xref-AccessControlDefaultAdminRules-_revokeRole-bytes32-address-}[`++_revokeRole(role, account)++`] +* {xref-AccessControlDefaultAdminRules-_setRoleAdmin-bytes32-bytes32-}[`++_setRoleAdmin(role, adminRole)++`] +* {xref-AccessControlDefaultAdminRules-defaultAdmin--}[`++defaultAdmin()++`] +* {xref-AccessControlDefaultAdminRules-pendingDefaultAdmin--}[`++pendingDefaultAdmin()++`] +* {xref-AccessControlDefaultAdminRules-defaultAdminDelay--}[`++defaultAdminDelay()++`] +* {xref-AccessControlDefaultAdminRules-pendingDefaultAdminDelay--}[`++pendingDefaultAdminDelay()++`] +* {xref-AccessControlDefaultAdminRules-defaultAdminDelayIncreaseWait--}[`++defaultAdminDelayIncreaseWait()++`] +* {xref-AccessControlDefaultAdminRules-beginDefaultAdminTransfer-address-}[`++beginDefaultAdminTransfer(newAdmin)++`] +* {xref-AccessControlDefaultAdminRules-_beginDefaultAdminTransfer-address-}[`++_beginDefaultAdminTransfer(newAdmin)++`] +* {xref-AccessControlDefaultAdminRules-cancelDefaultAdminTransfer--}[`++cancelDefaultAdminTransfer()++`] +* {xref-AccessControlDefaultAdminRules-_cancelDefaultAdminTransfer--}[`++_cancelDefaultAdminTransfer()++`] +* {xref-AccessControlDefaultAdminRules-acceptDefaultAdminTransfer--}[`++acceptDefaultAdminTransfer()++`] +* {xref-AccessControlDefaultAdminRules-_acceptDefaultAdminTransfer--}[`++_acceptDefaultAdminTransfer()++`] +* {xref-AccessControlDefaultAdminRules-changeDefaultAdminDelay-uint48-}[`++changeDefaultAdminDelay(newDelay)++`] +* {xref-AccessControlDefaultAdminRules-_changeDefaultAdminDelay-uint48-}[`++_changeDefaultAdminDelay(newDelay)++`] +* {xref-AccessControlDefaultAdminRules-rollbackDefaultAdminDelay--}[`++rollbackDefaultAdminDelay()++`] +* {xref-AccessControlDefaultAdminRules-_rollbackDefaultAdminDelay--}[`++_rollbackDefaultAdminDelay()++`] +* {xref-AccessControlDefaultAdminRules-_delayChangeWait-uint48-}[`++_delayChangeWait(newDelay)++`] + +[.contract-subindex-inherited] +.AccessControl +* {xref-AccessControl-hasRole-bytes32-address-}[`++hasRole(role, account)++`] +* {xref-AccessControl-_checkRole-bytes32-}[`++_checkRole(role)++`] +* {xref-AccessControl-_checkRole-bytes32-address-}[`++_checkRole(role, account)++`] +* {xref-AccessControl-getRoleAdmin-bytes32-}[`++getRoleAdmin(role)++`] + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +[.contract-subindex-inherited] +.IERC5313 + +[.contract-subindex-inherited] +.IAccessControlDefaultAdminRules + +[.contract-subindex-inherited] +.IAccessControl + +-- + +[.contract-index] +.Events +-- + +[.contract-subindex-inherited] +.AccessControl + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +[.contract-subindex-inherited] +.IERC5313 + +[.contract-subindex-inherited] +.IAccessControlDefaultAdminRules +* {xref-IAccessControlDefaultAdminRules-DefaultAdminTransferScheduled-address-uint48-}[`++DefaultAdminTransferScheduled(newAdmin, acceptSchedule)++`] +* {xref-IAccessControlDefaultAdminRules-DefaultAdminTransferCanceled--}[`++DefaultAdminTransferCanceled()++`] +* {xref-IAccessControlDefaultAdminRules-DefaultAdminDelayChangeScheduled-uint48-uint48-}[`++DefaultAdminDelayChangeScheduled(newDelay, effectSchedule)++`] +* {xref-IAccessControlDefaultAdminRules-DefaultAdminDelayChangeCanceled--}[`++DefaultAdminDelayChangeCanceled()++`] + +[.contract-subindex-inherited] +.IAccessControl +* {xref-IAccessControl-RoleAdminChanged-bytes32-bytes32-bytes32-}[`++RoleAdminChanged(role, previousAdminRole, newAdminRole)++`] +* {xref-IAccessControl-RoleGranted-bytes32-address-address-}[`++RoleGranted(role, account, sender)++`] +* {xref-IAccessControl-RoleRevoked-bytes32-address-address-}[`++RoleRevoked(role, account, sender)++`] + +-- + +[.contract-index] +.Errors +-- + +[.contract-subindex-inherited] +.AccessControl + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +[.contract-subindex-inherited] +.IERC5313 + +[.contract-subindex-inherited] +.IAccessControlDefaultAdminRules +* {xref-IAccessControlDefaultAdminRules-AccessControlInvalidDefaultAdmin-address-}[`++AccessControlInvalidDefaultAdmin(defaultAdmin)++`] +* {xref-IAccessControlDefaultAdminRules-AccessControlEnforcedDefaultAdminRules--}[`++AccessControlEnforcedDefaultAdminRules()++`] +* {xref-IAccessControlDefaultAdminRules-AccessControlEnforcedDefaultAdminDelay-uint48-}[`++AccessControlEnforcedDefaultAdminDelay(schedule)++`] + +[.contract-subindex-inherited] +.IAccessControl +* {xref-IAccessControl-AccessControlUnauthorizedAccount-address-bytes32-}[`++AccessControlUnauthorizedAccount(account, neededRole)++`] +* {xref-IAccessControl-AccessControlBadConfirmation--}[`++AccessControlBadConfirmation()++`] + +-- + +[.contract-item] +[[AccessControlDefaultAdminRules-constructor-uint48-address-]] +==== `[.contract-item-name]#++constructor++#++(uint48 initialDelay, address initialDefaultAdmin)++` [.item-kind]#internal# + +Sets the initial values for {defaultAdminDelay} and {defaultAdmin} address. + +[.contract-item] +[[AccessControlDefaultAdminRules-supportsInterface-bytes4-]] +==== `[.contract-item-name]#++supportsInterface++#++(bytes4 interfaceId) β†’ bool++` [.item-kind]#public# + +See {IERC165-supportsInterface}. + +[.contract-item] +[[AccessControlDefaultAdminRules-owner--]] +==== `[.contract-item-name]#++owner++#++() β†’ address++` [.item-kind]#public# + +See {IERC5313-owner}. + +[.contract-item] +[[AccessControlDefaultAdminRules-grantRole-bytes32-address-]] +==== `[.contract-item-name]#++grantRole++#++(bytes32 role, address account)++` [.item-kind]#public# + +See {AccessControl-grantRole}. Reverts for `DEFAULT_ADMIN_ROLE`. + +[.contract-item] +[[AccessControlDefaultAdminRules-revokeRole-bytes32-address-]] +==== `[.contract-item-name]#++revokeRole++#++(bytes32 role, address account)++` [.item-kind]#public# + +See {AccessControl-revokeRole}. Reverts for `DEFAULT_ADMIN_ROLE`. + +[.contract-item] +[[AccessControlDefaultAdminRules-renounceRole-bytes32-address-]] +==== `[.contract-item-name]#++renounceRole++#++(bytes32 role, address account)++` [.item-kind]#public# + +See {AccessControl-renounceRole}. + +For the `DEFAULT_ADMIN_ROLE`, it only allows renouncing in two steps by first calling +{beginDefaultAdminTransfer} to the `address(0)`, so it's required that the {pendingDefaultAdmin} schedule +has also passed when calling this function. + +After its execution, it will not be possible to call `onlyRole(DEFAULT_ADMIN_ROLE)` functions. + +NOTE: Renouncing `DEFAULT_ADMIN_ROLE` will leave the contract without a {defaultAdmin}, +thereby disabling any functionality that is only available for it, and the possibility of reassigning a +non-administrated role. + +[.contract-item] +[[AccessControlDefaultAdminRules-_grantRole-bytes32-address-]] +==== `[.contract-item-name]#++_grantRole++#++(bytes32 role, address account) β†’ bool++` [.item-kind]#internal# + +See {AccessControl-_grantRole}. + +For `DEFAULT_ADMIN_ROLE`, it only allows granting if there isn't already a {defaultAdmin} or if the +role has been previously renounced. + +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. + +[.contract-item] +[[AccessControlDefaultAdminRules-_revokeRole-bytes32-address-]] +==== `[.contract-item-name]#++_revokeRole++#++(bytes32 role, address account) β†’ bool++` [.item-kind]#internal# + +See {AccessControl-_revokeRole}. + +[.contract-item] +[[AccessControlDefaultAdminRules-_setRoleAdmin-bytes32-bytes32-]] +==== `[.contract-item-name]#++_setRoleAdmin++#++(bytes32 role, bytes32 adminRole)++` [.item-kind]#internal# + +See {AccessControl-_setRoleAdmin}. Reverts for `DEFAULT_ADMIN_ROLE`. + +[.contract-item] +[[AccessControlDefaultAdminRules-defaultAdmin--]] +==== `[.contract-item-name]#++defaultAdmin++#++() β†’ address++` [.item-kind]#public# + +Returns the address of the current `DEFAULT_ADMIN_ROLE` holder. + +[.contract-item] +[[AccessControlDefaultAdminRules-pendingDefaultAdmin--]] +==== `[.contract-item-name]#++pendingDefaultAdmin++#++() β†’ address newAdmin, uint48 schedule++` [.item-kind]#public# + +Returns a tuple of a `newAdmin` and an accept schedule. + +After the `schedule` passes, the `newAdmin` will be able to accept the {defaultAdmin} role +by calling {acceptDefaultAdminTransfer}, completing the role transfer. + +A zero value only in `acceptSchedule` indicates no pending admin transfer. + +NOTE: A zero address `newAdmin` means that {defaultAdmin} is being renounced. + +[.contract-item] +[[AccessControlDefaultAdminRules-defaultAdminDelay--]] +==== `[.contract-item-name]#++defaultAdminDelay++#++() β†’ uint48++` [.item-kind]#public# + +Returns the delay required to schedule the acceptance of a {defaultAdmin} transfer started. + +This delay will be added to the current timestamp when calling {beginDefaultAdminTransfer} to set +the acceptance schedule. + +NOTE: If a delay change has been scheduled, it will take effect as soon as the schedule passes, making this +function returns the new delay. See {changeDefaultAdminDelay}. + +[.contract-item] +[[AccessControlDefaultAdminRules-pendingDefaultAdminDelay--]] +==== `[.contract-item-name]#++pendingDefaultAdminDelay++#++() β†’ uint48 newDelay, uint48 schedule++` [.item-kind]#public# + +Returns a tuple of `newDelay` and an effect schedule. + +After the `schedule` passes, the `newDelay` will get into effect immediately for every +new {defaultAdmin} transfer started with {beginDefaultAdminTransfer}. + +A zero value only in `effectSchedule` indicates no pending delay change. + +NOTE: A zero value only for `newDelay` means that the next {defaultAdminDelay} +will be zero after the effect schedule. + +[.contract-item] +[[AccessControlDefaultAdminRules-defaultAdminDelayIncreaseWait--]] +==== `[.contract-item-name]#++defaultAdminDelayIncreaseWait++#++() β†’ uint48++` [.item-kind]#public# + +Maximum time in seconds for an increase to {defaultAdminDelay} (that is scheduled using {changeDefaultAdminDelay}) +to take effect. Default to 5 days. + +When the {defaultAdminDelay} is scheduled to be increased, it goes into effect after the new delay has passed with +the purpose of giving enough time for reverting any accidental change (i.e. using milliseconds instead of seconds) +that may lock the contract. However, to avoid excessive schedules, the wait is capped by this function and it can +be overrode for a custom {defaultAdminDelay} increase scheduling. + +IMPORTANT: Make sure to add a reasonable amount of time while overriding this value, otherwise, +there's a risk of setting a high new delay that goes into effect almost immediately without the +possibility of human intervention in the case of an input error (eg. set milliseconds instead of seconds). + +[.contract-item] +[[AccessControlDefaultAdminRules-beginDefaultAdminTransfer-address-]] +==== `[.contract-item-name]#++beginDefaultAdminTransfer++#++(address newAdmin)++` [.item-kind]#public# + +Starts a {defaultAdmin} transfer by setting a {pendingDefaultAdmin} scheduled for acceptance +after the current timestamp plus a {defaultAdminDelay}. + +Requirements: + +- Only can be called by the current {defaultAdmin}. + +Emits a DefaultAdminRoleChangeStarted event. + +[.contract-item] +[[AccessControlDefaultAdminRules-_beginDefaultAdminTransfer-address-]] +==== `[.contract-item-name]#++_beginDefaultAdminTransfer++#++(address newAdmin)++` [.item-kind]#internal# + +See {beginDefaultAdminTransfer}. + +Internal function without access restriction. + +[.contract-item] +[[AccessControlDefaultAdminRules-cancelDefaultAdminTransfer--]] +==== `[.contract-item-name]#++cancelDefaultAdminTransfer++#++()++` [.item-kind]#public# + +Cancels a {defaultAdmin} transfer previously started with {beginDefaultAdminTransfer}. + +A {pendingDefaultAdmin} not yet accepted can also be cancelled with this function. + +Requirements: + +- Only can be called by the current {defaultAdmin}. + +May emit a DefaultAdminTransferCanceled event. + +[.contract-item] +[[AccessControlDefaultAdminRules-_cancelDefaultAdminTransfer--]] +==== `[.contract-item-name]#++_cancelDefaultAdminTransfer++#++()++` [.item-kind]#internal# + +See {cancelDefaultAdminTransfer}. + +Internal function without access restriction. + +[.contract-item] +[[AccessControlDefaultAdminRules-acceptDefaultAdminTransfer--]] +==== `[.contract-item-name]#++acceptDefaultAdminTransfer++#++()++` [.item-kind]#public# + +Completes a {defaultAdmin} transfer previously started with {beginDefaultAdminTransfer}. + +After calling the function: + +- `DEFAULT_ADMIN_ROLE` should be granted to the caller. +- `DEFAULT_ADMIN_ROLE` should be revoked from the previous holder. +- {pendingDefaultAdmin} should be reset to zero values. + +Requirements: + +- Only can be called by the {pendingDefaultAdmin}'s `newAdmin`. +- The {pendingDefaultAdmin}'s `acceptSchedule` should've passed. + +[.contract-item] +[[AccessControlDefaultAdminRules-_acceptDefaultAdminTransfer--]] +==== `[.contract-item-name]#++_acceptDefaultAdminTransfer++#++()++` [.item-kind]#internal# + +See {acceptDefaultAdminTransfer}. + +Internal function without access restriction. + +[.contract-item] +[[AccessControlDefaultAdminRules-changeDefaultAdminDelay-uint48-]] +==== `[.contract-item-name]#++changeDefaultAdminDelay++#++(uint48 newDelay)++` [.item-kind]#public# + +Initiates a {defaultAdminDelay} update by setting a {pendingDefaultAdminDelay} scheduled for getting +into effect after the current timestamp plus a {defaultAdminDelay}. + +This function guarantees that any call to {beginDefaultAdminTransfer} done between the timestamp this +method is called and the {pendingDefaultAdminDelay} effect schedule will use the current {defaultAdminDelay} +set before calling. + +The {pendingDefaultAdminDelay}'s effect schedule is defined in a way that waiting until the schedule and then +calling {beginDefaultAdminTransfer} with the new delay will take at least the same as another {defaultAdmin} +complete transfer (including acceptance). + +The schedule is designed for two scenarios: + +- When the delay is changed for a larger one the schedule is `block.timestamp + newDelay` capped by +{defaultAdminDelayIncreaseWait}. +- When the delay is changed for a shorter one, the schedule is `block.timestamp + (current delay - new delay)`. + +A {pendingDefaultAdminDelay} that never got into effect will be canceled in favor of a new scheduled change. + +Requirements: + +- Only can be called by the current {defaultAdmin}. + +Emits a DefaultAdminDelayChangeScheduled event and may emit a DefaultAdminDelayChangeCanceled event. + +[.contract-item] +[[AccessControlDefaultAdminRules-_changeDefaultAdminDelay-uint48-]] +==== `[.contract-item-name]#++_changeDefaultAdminDelay++#++(uint48 newDelay)++` [.item-kind]#internal# + +See {changeDefaultAdminDelay}. + +Internal function without access restriction. + +[.contract-item] +[[AccessControlDefaultAdminRules-rollbackDefaultAdminDelay--]] +==== `[.contract-item-name]#++rollbackDefaultAdminDelay++#++()++` [.item-kind]#public# + +Cancels a scheduled {defaultAdminDelay} change. + +Requirements: + +- Only can be called by the current {defaultAdmin}. + +May emit a DefaultAdminDelayChangeCanceled event. + +[.contract-item] +[[AccessControlDefaultAdminRules-_rollbackDefaultAdminDelay--]] +==== `[.contract-item-name]#++_rollbackDefaultAdminDelay++#++()++` [.item-kind]#internal# + +See {rollbackDefaultAdminDelay}. + +Internal function without access restriction. + +[.contract-item] +[[AccessControlDefaultAdminRules-_delayChangeWait-uint48-]] +==== `[.contract-item-name]#++_delayChangeWait++#++(uint48 newDelay) β†’ uint48++` [.item-kind]#internal# + +Returns the amount of seconds to wait after the `newDelay` will +become the new {defaultAdminDelay}. + +The value returned guarantees that if the delay is reduced, it will go into effect +after a wait that honors the previously set delay. + +See {defaultAdminDelayIncreaseWait}. + +== AccessManager + +:canCall: pass:normal[xref:#IAuthority-canCall-address-address-bytes4-[`++canCall++`]] + +[.contract] +[[IAuthority]] +=== `++IAuthority++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/access/manager/IAuthority.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/access/manager/IAuthority.sol"; +``` + +Standard interface for permissioning originally defined in Dappsys. + +[.contract-index] +.Functions +-- +* {xref-IAuthority-canCall-address-address-bytes4-}[`++canCall(caller, target, selector)++`] + +-- + +[.contract-item] +[[IAuthority-canCall-address-address-bytes4-]] +==== `[.contract-item-name]#++canCall++#++(address caller, address target, bytes4 selector) β†’ bool allowed++` [.item-kind]#external# + +Returns true if the caller can invoke on a target the function identified by a function selector. + +:TargetConfig: pass:normal[xref:#AccessManager-TargetConfig[`++TargetConfig++`]] +:Access: pass:normal[xref:#AccessManager-Access[`++Access++`]] +:Role: pass:normal[xref:#AccessManager-Role[`++Role++`]] +:Schedule: pass:normal[xref:#AccessManager-Schedule[`++Schedule++`]] +:ADMIN_ROLE: pass:normal[xref:#AccessManager-ADMIN_ROLE-uint64[`++ADMIN_ROLE++`]] +:PUBLIC_ROLE: pass:normal[xref:#AccessManager-PUBLIC_ROLE-uint64[`++PUBLIC_ROLE++`]] +:onlyAuthorized: pass:normal[xref:#AccessManager-onlyAuthorized--[`++onlyAuthorized++`]] +:constructor: pass:normal[xref:#AccessManager-constructor-address-[`++constructor++`]] +:canCall: pass:normal[xref:#AccessManager-canCall-address-address-bytes4-[`++canCall++`]] +:expiration: pass:normal[xref:#AccessManager-expiration--[`++expiration++`]] +:minSetback: pass:normal[xref:#AccessManager-minSetback--[`++minSetback++`]] +:isTargetClosed: pass:normal[xref:#AccessManager-isTargetClosed-address-[`++isTargetClosed++`]] +:getTargetFunctionRole: pass:normal[xref:#AccessManager-getTargetFunctionRole-address-bytes4-[`++getTargetFunctionRole++`]] +:getTargetAdminDelay: pass:normal[xref:#AccessManager-getTargetAdminDelay-address-[`++getTargetAdminDelay++`]] +:getRoleAdmin: pass:normal[xref:#AccessManager-getRoleAdmin-uint64-[`++getRoleAdmin++`]] +:getRoleGuardian: pass:normal[xref:#AccessManager-getRoleGuardian-uint64-[`++getRoleGuardian++`]] +:getRoleGrantDelay: pass:normal[xref:#AccessManager-getRoleGrantDelay-uint64-[`++getRoleGrantDelay++`]] +:getAccess: pass:normal[xref:#AccessManager-getAccess-uint64-address-[`++getAccess++`]] +:hasRole: pass:normal[xref:#AccessManager-hasRole-uint64-address-[`++hasRole++`]] +:labelRole: pass:normal[xref:#AccessManager-labelRole-uint64-string-[`++labelRole++`]] +:grantRole: pass:normal[xref:#AccessManager-grantRole-uint64-address-uint32-[`++grantRole++`]] +:revokeRole: pass:normal[xref:#AccessManager-revokeRole-uint64-address-[`++revokeRole++`]] +:renounceRole: pass:normal[xref:#AccessManager-renounceRole-uint64-address-[`++renounceRole++`]] +:setRoleAdmin: pass:normal[xref:#AccessManager-setRoleAdmin-uint64-uint64-[`++setRoleAdmin++`]] +:setRoleGuardian: pass:normal[xref:#AccessManager-setRoleGuardian-uint64-uint64-[`++setRoleGuardian++`]] +:setGrantDelay: pass:normal[xref:#AccessManager-setGrantDelay-uint64-uint32-[`++setGrantDelay++`]] +:_grantRole: pass:normal[xref:#AccessManager-_grantRole-uint64-address-uint32-uint32-[`++_grantRole++`]] +:_revokeRole: pass:normal[xref:#AccessManager-_revokeRole-uint64-address-[`++_revokeRole++`]] +:_setRoleAdmin: pass:normal[xref:#AccessManager-_setRoleAdmin-uint64-uint64-[`++_setRoleAdmin++`]] +:_setRoleGuardian: pass:normal[xref:#AccessManager-_setRoleGuardian-uint64-uint64-[`++_setRoleGuardian++`]] +:_setGrantDelay: pass:normal[xref:#AccessManager-_setGrantDelay-uint64-uint32-[`++_setGrantDelay++`]] +:setTargetFunctionRole: pass:normal[xref:#AccessManager-setTargetFunctionRole-address-bytes4---uint64-[`++setTargetFunctionRole++`]] +:_setTargetFunctionRole: pass:normal[xref:#AccessManager-_setTargetFunctionRole-address-bytes4-uint64-[`++_setTargetFunctionRole++`]] +:setTargetAdminDelay: pass:normal[xref:#AccessManager-setTargetAdminDelay-address-uint32-[`++setTargetAdminDelay++`]] +:_setTargetAdminDelay: pass:normal[xref:#AccessManager-_setTargetAdminDelay-address-uint32-[`++_setTargetAdminDelay++`]] +:setTargetClosed: pass:normal[xref:#AccessManager-setTargetClosed-address-bool-[`++setTargetClosed++`]] +:_setTargetClosed: pass:normal[xref:#AccessManager-_setTargetClosed-address-bool-[`++_setTargetClosed++`]] +:getSchedule: pass:normal[xref:#AccessManager-getSchedule-bytes32-[`++getSchedule++`]] +:getNonce: pass:normal[xref:#AccessManager-getNonce-bytes32-[`++getNonce++`]] +:schedule: pass:normal[xref:#AccessManager-schedule-address-bytes-uint48-[`++schedule++`]] +:execute: pass:normal[xref:#AccessManager-execute-address-bytes-[`++execute++`]] +:consumeScheduledOp: pass:normal[xref:#AccessManager-consumeScheduledOp-address-bytes-[`++consumeScheduledOp++`]] +:_consumeScheduledOp: pass:normal[xref:#AccessManager-_consumeScheduledOp-bytes32-[`++_consumeScheduledOp++`]] +:cancel: pass:normal[xref:#AccessManager-cancel-address-address-bytes-[`++cancel++`]] +:hashOperation: pass:normal[xref:#AccessManager-hashOperation-address-address-bytes-[`++hashOperation++`]] +:updateAuthority: pass:normal[xref:#AccessManager-updateAuthority-address-address-[`++updateAuthority++`]] + +[.contract] +[[AccessManager]] +=== `++AccessManager++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/access/manager/AccessManager.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/access/manager/AccessManager.sol"; +``` + +AccessManager is a central contract to store the permissions of a system. + +The smart contracts under the control of an AccessManager instance will have a set of "restricted" functions, and the +exact details of how access is restricted for each of those functions is configurable by the admins of the instance. +These restrictions are expressed in terms of "roles". + +An AccessManager instance will define a set of roles. Accounts can be added into any number of these roles. Each of +them defines a role, and may confer access to some of the restricted functions in the system, as configured by admins +through the use of {setFunctionAllowedRoles}. + +Note that a function in a target contract may become permissioned in this way only when: 1) said contract is +{AccessManaged} and is connected to this contract as its manager, and 2) said function is decorated with the +`restricted` modifier. + +There is a special role defined by default named "public" which all accounts automatically have. + +In addition to the access rules defined by each target's functions being assigned to roles, then entire target can +be "closed". This "closed" mode is set/unset by the admin using {setTargetClosed} and can be used to lock a contract +while permissions are being (re-)configured. + +Since all the permissions of the managed system can be modified by the admins of this instance, it is expected that +they will be highly secured (e.g., a multisig or a well-configured DAO). + +NOTE: This contract implements a form of the {IAuthority} interface, but {canCall} has additional return data so it +doesn't inherit `IAuthority`. It is however compatible with the `IAuthority` interface since the first 32 bytes of +the return data are a boolean as expected by that interface. + +NOTE: Systems that implement other access control mechanisms (for example using {Ownable}) can be paired with an +{AccessManager} by transferring permissions (ownership in the case of {Ownable}) directly to the {AccessManager}. +Users will be able to interact with these contracts through the {execute} function, following the access rules +registered in the {AccessManager}. Keep in mind that in that context, the msg.sender seen by restricted functions +will be {AccessManager} itself. + +WARNING: When granting permissions over an {Ownable} or {AccessControl} contract to an {AccessManager}, be very +mindful of the danger associated with functions such as {{Ownable-renounceOwnership}} or +{{AccessControl-renounceRole}}. + +[.contract-index] +.Modifiers +-- +* {xref-AccessManager-onlyAuthorized--}[`++onlyAuthorized()++`] +-- + +[.contract-index] +.Functions +-- +* {xref-AccessManager-constructor-address-}[`++constructor(initialAdmin)++`] +* {xref-AccessManager-canCall-address-address-bytes4-}[`++canCall(caller, target, selector)++`] +* {xref-AccessManager-expiration--}[`++expiration()++`] +* {xref-AccessManager-minSetback--}[`++minSetback()++`] +* {xref-AccessManager-isTargetClosed-address-}[`++isTargetClosed(target)++`] +* {xref-AccessManager-getTargetFunctionRole-address-bytes4-}[`++getTargetFunctionRole(target, selector)++`] +* {xref-AccessManager-getTargetAdminDelay-address-}[`++getTargetAdminDelay(target)++`] +* {xref-AccessManager-getRoleAdmin-uint64-}[`++getRoleAdmin(roleId)++`] +* {xref-AccessManager-getRoleGuardian-uint64-}[`++getRoleGuardian(roleId)++`] +* {xref-AccessManager-getRoleGrantDelay-uint64-}[`++getRoleGrantDelay(roleId)++`] +* {xref-AccessManager-getAccess-uint64-address-}[`++getAccess(roleId, account)++`] +* {xref-AccessManager-hasRole-uint64-address-}[`++hasRole(roleId, account)++`] +* {xref-AccessManager-labelRole-uint64-string-}[`++labelRole(roleId, label)++`] +* {xref-AccessManager-grantRole-uint64-address-uint32-}[`++grantRole(roleId, account, executionDelay)++`] +* {xref-AccessManager-revokeRole-uint64-address-}[`++revokeRole(roleId, account)++`] +* {xref-AccessManager-renounceRole-uint64-address-}[`++renounceRole(roleId, callerConfirmation)++`] +* {xref-AccessManager-setRoleAdmin-uint64-uint64-}[`++setRoleAdmin(roleId, admin)++`] +* {xref-AccessManager-setRoleGuardian-uint64-uint64-}[`++setRoleGuardian(roleId, guardian)++`] +* {xref-AccessManager-setGrantDelay-uint64-uint32-}[`++setGrantDelay(roleId, newDelay)++`] +* {xref-AccessManager-_grantRole-uint64-address-uint32-uint32-}[`++_grantRole(roleId, account, grantDelay, executionDelay)++`] +* {xref-AccessManager-_revokeRole-uint64-address-}[`++_revokeRole(roleId, account)++`] +* {xref-AccessManager-_setRoleAdmin-uint64-uint64-}[`++_setRoleAdmin(roleId, admin)++`] +* {xref-AccessManager-_setRoleGuardian-uint64-uint64-}[`++_setRoleGuardian(roleId, guardian)++`] +* {xref-AccessManager-_setGrantDelay-uint64-uint32-}[`++_setGrantDelay(roleId, newDelay)++`] +* {xref-AccessManager-setTargetFunctionRole-address-bytes4---uint64-}[`++setTargetFunctionRole(target, selectors, roleId)++`] +* {xref-AccessManager-_setTargetFunctionRole-address-bytes4-uint64-}[`++_setTargetFunctionRole(target, selector, roleId)++`] +* {xref-AccessManager-setTargetAdminDelay-address-uint32-}[`++setTargetAdminDelay(target, newDelay)++`] +* {xref-AccessManager-_setTargetAdminDelay-address-uint32-}[`++_setTargetAdminDelay(target, newDelay)++`] +* {xref-AccessManager-setTargetClosed-address-bool-}[`++setTargetClosed(target, closed)++`] +* {xref-AccessManager-_setTargetClosed-address-bool-}[`++_setTargetClosed(target, closed)++`] +* {xref-AccessManager-getSchedule-bytes32-}[`++getSchedule(id)++`] +* {xref-AccessManager-getNonce-bytes32-}[`++getNonce(id)++`] +* {xref-AccessManager-schedule-address-bytes-uint48-}[`++schedule(target, data, when)++`] +* {xref-AccessManager-execute-address-bytes-}[`++execute(target, data)++`] +* {xref-AccessManager-consumeScheduledOp-address-bytes-}[`++consumeScheduledOp(caller, data)++`] +* {xref-AccessManager-_consumeScheduledOp-bytes32-}[`++_consumeScheduledOp(operationId)++`] +* {xref-AccessManager-cancel-address-address-bytes-}[`++cancel(caller, target, data)++`] +* {xref-AccessManager-hashOperation-address-address-bytes-}[`++hashOperation(caller, target, data)++`] +* {xref-AccessManager-updateAuthority-address-address-}[`++updateAuthority(target, newAuthority)++`] + +[.contract-subindex-inherited] +.IAccessManager + +[.contract-subindex-inherited] +.Multicall +* {xref-Multicall-multicall-bytes---}[`++multicall(data)++`] + +-- + +[.contract-index] +.Events +-- + +[.contract-subindex-inherited] +.IAccessManager +* {xref-IAccessManager-OperationScheduled-bytes32-uint32-uint48-address-address-bytes-}[`++OperationScheduled(operationId, nonce, schedule, caller, target, data)++`] +* {xref-IAccessManager-OperationExecuted-bytes32-uint32-}[`++OperationExecuted(operationId, nonce)++`] +* {xref-IAccessManager-OperationCanceled-bytes32-uint32-}[`++OperationCanceled(operationId, nonce)++`] +* {xref-IAccessManager-RoleLabel-uint64-string-}[`++RoleLabel(roleId, label)++`] +* {xref-IAccessManager-RoleGranted-uint64-address-uint32-uint48-bool-}[`++RoleGranted(roleId, account, delay, since, newMember)++`] +* {xref-IAccessManager-RoleRevoked-uint64-address-}[`++RoleRevoked(roleId, account)++`] +* {xref-IAccessManager-RoleAdminChanged-uint64-uint64-}[`++RoleAdminChanged(roleId, admin)++`] +* {xref-IAccessManager-RoleGuardianChanged-uint64-uint64-}[`++RoleGuardianChanged(roleId, guardian)++`] +* {xref-IAccessManager-RoleGrantDelayChanged-uint64-uint32-uint48-}[`++RoleGrantDelayChanged(roleId, delay, since)++`] +* {xref-IAccessManager-TargetClosed-address-bool-}[`++TargetClosed(target, closed)++`] +* {xref-IAccessManager-TargetFunctionRoleUpdated-address-bytes4-uint64-}[`++TargetFunctionRoleUpdated(target, selector, roleId)++`] +* {xref-IAccessManager-TargetAdminDelayUpdated-address-uint32-uint48-}[`++TargetAdminDelayUpdated(target, delay, since)++`] + +[.contract-subindex-inherited] +.Multicall + +-- + +[.contract-index] +.Errors +-- + +[.contract-subindex-inherited] +.IAccessManager +* {xref-IAccessManager-AccessManagerAlreadyScheduled-bytes32-}[`++AccessManagerAlreadyScheduled(operationId)++`] +* {xref-IAccessManager-AccessManagerNotScheduled-bytes32-}[`++AccessManagerNotScheduled(operationId)++`] +* {xref-IAccessManager-AccessManagerNotReady-bytes32-}[`++AccessManagerNotReady(operationId)++`] +* {xref-IAccessManager-AccessManagerExpired-bytes32-}[`++AccessManagerExpired(operationId)++`] +* {xref-IAccessManager-AccessManagerLockedAccount-address-}[`++AccessManagerLockedAccount(account)++`] +* {xref-IAccessManager-AccessManagerLockedRole-uint64-}[`++AccessManagerLockedRole(roleId)++`] +* {xref-IAccessManager-AccessManagerBadConfirmation--}[`++AccessManagerBadConfirmation()++`] +* {xref-IAccessManager-AccessManagerUnauthorizedAccount-address-uint64-}[`++AccessManagerUnauthorizedAccount(msgsender, roleId)++`] +* {xref-IAccessManager-AccessManagerUnauthorizedCall-address-address-bytes4-}[`++AccessManagerUnauthorizedCall(caller, target, selector)++`] +* {xref-IAccessManager-AccessManagerUnauthorizedConsume-address-}[`++AccessManagerUnauthorizedConsume(target)++`] +* {xref-IAccessManager-AccessManagerUnauthorizedCancel-address-address-address-bytes4-}[`++AccessManagerUnauthorizedCancel(msgsender, caller, target, selector)++`] +* {xref-IAccessManager-AccessManagerInvalidInitialAdmin-address-}[`++AccessManagerInvalidInitialAdmin(initialAdmin)++`] + +[.contract-subindex-inherited] +.Multicall + +-- + +[.contract-item] +[[AccessManager-onlyAuthorized--]] +==== `[.contract-item-name]#++onlyAuthorized++#++()++` [.item-kind]#modifier# + +Check that the caller is authorized to perform the operation, following the restrictions encoded in +{_getAdminRestrictions}. + +[.contract-item] +[[AccessManager-constructor-address-]] +==== `[.contract-item-name]#++constructor++#++(address initialAdmin)++` [.item-kind]#public# + +[.contract-item] +[[AccessManager-canCall-address-address-bytes4-]] +==== `[.contract-item-name]#++canCall++#++(address caller, address target, bytes4 selector) β†’ bool, uint32++` [.item-kind]#public# + +Check if an address (`caller`) is authorised to call a given function on a given contract directly (with +no restriction). Additionally, it returns the delay needed to perform the call indirectly through the {schedule} +& {execute} workflow. + +This function is usually called by the targeted contract to control immediate execution of restricted functions. +Therefore we only return true is the call can be performed without any delay. If the call is subject to a delay, +then the function should return false, and the caller should schedule the operation for future execution. + +We may be able to hash the operation, and check if the call was scheduled, but we would not be able to cleanup +the schedule, leaving the possibility of multiple executions. Maybe this function should not be view? + +NOTE: The IAuthority interface does not include the `uint32` delay. This is an extension of that interface that +is backward compatible. Some contracts may thus ignore the second return argument. In that case they will fail +to identify the indirect workflow, and will consider calls that require a delay to be forbidden. + +[.contract-item] +[[AccessManager-expiration--]] +==== `[.contract-item-name]#++expiration++#++() β†’ uint32++` [.item-kind]#public# + +Expiration delay for scheduled proposals. Defaults to 1 week. + +[.contract-item] +[[AccessManager-minSetback--]] +==== `[.contract-item-name]#++minSetback++#++() β†’ uint32++` [.item-kind]#public# + +Minimum setback for all delay updates, with the exception of execution delays, which +can be increased without setback (and in the event of an accidental increase can be reset +via {revokeRole}). Defaults to 5 days. + +[.contract-item] +[[AccessManager-isTargetClosed-address-]] +==== `[.contract-item-name]#++isTargetClosed++#++(address target) β†’ bool++` [.item-kind]#public# + +Get the mode under which a contract is operating. + +[.contract-item] +[[AccessManager-getTargetFunctionRole-address-bytes4-]] +==== `[.contract-item-name]#++getTargetFunctionRole++#++(address target, bytes4 selector) β†’ uint64++` [.item-kind]#public# + +Get the role required to call a function. + +[.contract-item] +[[AccessManager-getTargetAdminDelay-address-]] +==== `[.contract-item-name]#++getTargetAdminDelay++#++(address target) β†’ uint32++` [.item-kind]#public# + +Get the admin delay for a target contract. Changes to contract configuration are subject to this delay. + +[.contract-item] +[[AccessManager-getRoleAdmin-uint64-]] +==== `[.contract-item-name]#++getRoleAdmin++#++(uint64 roleId) β†’ uint64++` [.item-kind]#public# + +Get the id of the role that acts as an admin for given role. + +The admin permission is required to grant the role, revoke the role and update the execution delay to execute +an operation that is restricted to this role. + +[.contract-item] +[[AccessManager-getRoleGuardian-uint64-]] +==== `[.contract-item-name]#++getRoleGuardian++#++(uint64 roleId) β†’ uint64++` [.item-kind]#public# + +Get the role that acts as a guardian for a given role. + +The guardian permission allows canceling operations that have been scheduled under the role. + +[.contract-item] +[[AccessManager-getRoleGrantDelay-uint64-]] +==== `[.contract-item-name]#++getRoleGrantDelay++#++(uint64 roleId) β†’ uint32++` [.item-kind]#public# + +Get the role current grant delay, that value may change at any point, without an event emitted, following +a call to {setGrantDelay}. Changes to this value, including effect timepoint are notified by the +{RoleGrantDelayChanged} event. + +[.contract-item] +[[AccessManager-getAccess-uint64-address-]] +==== `[.contract-item-name]#++getAccess++#++(uint64 roleId, address account) β†’ uint48, uint32, uint32, uint48++` [.item-kind]#public# + +Get the access details for a given account for a given role. These details include the timepoint at which +membership becomes active, and the delay applied to all operation by this user that requires this permission +level. + +Returns: +[0] Timestamp at which the account membership becomes valid. 0 means role is not granted. +[1] Current execution delay for the account. +[2] Pending execution delay for the account. +[3] Timestamp at which the pending execution delay will become active. 0 means no delay update is scheduled. + +[.contract-item] +[[AccessManager-hasRole-uint64-address-]] +==== `[.contract-item-name]#++hasRole++#++(uint64 roleId, address account) β†’ bool, uint32++` [.item-kind]#public# + +Check if a given account currently had the permission level corresponding to a given role. Note that this +permission might be associated with a delay. {getAccess} can provide more details. + +[.contract-item] +[[AccessManager-labelRole-uint64-string-]] +==== `[.contract-item-name]#++labelRole++#++(uint64 roleId, string label)++` [.item-kind]#public# + +Give a label to a role, for improved role discoverabily by UIs. + +Emits a {RoleLabel} event. + +[.contract-item] +[[AccessManager-grantRole-uint64-address-uint32-]] +==== `[.contract-item-name]#++grantRole++#++(uint64 roleId, address account, uint32 executionDelay)++` [.item-kind]#public# + +Add `account` to `roleId`, or change its execution delay. + +This gives the account the authorization to call any function that is restricted to this role. An optional +execution delay (in seconds) can be set. If that delay is non 0, the user is required to schedule any operation +that is restricted to members this role. The user will only be able to execute the operation after the delay has +passed, before it has expired. During this period, admin and guardians can cancel the operation (see {cancel}). + +If the account has already been granted this role, the execution delay will be updated. This update is not +immediate and follows the delay rules. For example, If a user currently has a delay of 3 hours, and this is +called to reduce that delay to 1 hour, the new delay will take some time to take effect, enforcing that any +operation executed in the 3 hours that follows this update was indeed scheduled before this update. + +Requirements: + +- the caller must be an admin for the role (see {getRoleAdmin}) + +Emits a {RoleGranted} event + +[.contract-item] +[[AccessManager-revokeRole-uint64-address-]] +==== `[.contract-item-name]#++revokeRole++#++(uint64 roleId, address account)++` [.item-kind]#public# + +Remove an account from a role, with immediate effect. If the account does not have the role, this call has +no effect. + +Requirements: + +- the caller must be an admin for the role (see {getRoleAdmin}) + +Emits a {RoleRevoked} event if the account had the role. + +[.contract-item] +[[AccessManager-renounceRole-uint64-address-]] +==== `[.contract-item-name]#++renounceRole++#++(uint64 roleId, address callerConfirmation)++` [.item-kind]#public# + +Renounce role permissions for the calling account, with immediate effect. If the sender is not in +the role, this call has no effect. + +Requirements: + +- the caller must be `callerConfirmation`. + +Emits a {RoleRevoked} event if the account had the role. + +[.contract-item] +[[AccessManager-setRoleAdmin-uint64-uint64-]] +==== `[.contract-item-name]#++setRoleAdmin++#++(uint64 roleId, uint64 admin)++` [.item-kind]#public# + +Change admin role for a given role. + +Requirements: + +- the caller must be a global admin + +Emits a {RoleAdminChanged} event + +[.contract-item] +[[AccessManager-setRoleGuardian-uint64-uint64-]] +==== `[.contract-item-name]#++setRoleGuardian++#++(uint64 roleId, uint64 guardian)++` [.item-kind]#public# + +Change guardian role for a given role. + +Requirements: + +- the caller must be a global admin + +Emits a {RoleGuardianChanged} event + +[.contract-item] +[[AccessManager-setGrantDelay-uint64-uint32-]] +==== `[.contract-item-name]#++setGrantDelay++#++(uint64 roleId, uint32 newDelay)++` [.item-kind]#public# + +Update the delay for granting a `roleId`. + +Requirements: + +- the caller must be a global admin + +Emits a {RoleGrantDelayChanged} event. + +[.contract-item] +[[AccessManager-_grantRole-uint64-address-uint32-uint32-]] +==== `[.contract-item-name]#++_grantRole++#++(uint64 roleId, address account, uint32 grantDelay, uint32 executionDelay) β†’ bool++` [.item-kind]#internal# + +Internal version of {grantRole} without access control. Returns true if the role was newly granted. + +Emits a {RoleGranted} event. + +[.contract-item] +[[AccessManager-_revokeRole-uint64-address-]] +==== `[.contract-item-name]#++_revokeRole++#++(uint64 roleId, address account) β†’ bool++` [.item-kind]#internal# + +Internal version of {revokeRole} without access control. This logic is also used by {renounceRole}. +Returns true if the role was previously granted. + +Emits a {RoleRevoked} event if the account had the role. + +[.contract-item] +[[AccessManager-_setRoleAdmin-uint64-uint64-]] +==== `[.contract-item-name]#++_setRoleAdmin++#++(uint64 roleId, uint64 admin)++` [.item-kind]#internal# + +Internal version of {setRoleAdmin} without access control. + +Emits a {RoleAdminChanged} event + +[.contract-item] +[[AccessManager-_setRoleGuardian-uint64-uint64-]] +==== `[.contract-item-name]#++_setRoleGuardian++#++(uint64 roleId, uint64 guardian)++` [.item-kind]#internal# + +Internal version of {setRoleGuardian} without access control. + +Emits a {RoleGuardianChanged} event + +[.contract-item] +[[AccessManager-_setGrantDelay-uint64-uint32-]] +==== `[.contract-item-name]#++_setGrantDelay++#++(uint64 roleId, uint32 newDelay)++` [.item-kind]#internal# + +Internal version of {setGrantDelay} without access control. + +Emits a {RoleGrantDelayChanged} event + +[.contract-item] +[[AccessManager-setTargetFunctionRole-address-bytes4---uint64-]] +==== `[.contract-item-name]#++setTargetFunctionRole++#++(address target, bytes4[] selectors, uint64 roleId)++` [.item-kind]#public# + +Set the role required to call functions identified by the `selectors` in the `target` contract. + +Requirements: + +- the caller must be a global admin + +Emits a {TargetFunctionRoleUpdated} event per selector. + +[.contract-item] +[[AccessManager-_setTargetFunctionRole-address-bytes4-uint64-]] +==== `[.contract-item-name]#++_setTargetFunctionRole++#++(address target, bytes4 selector, uint64 roleId)++` [.item-kind]#internal# + +Internal version of {setFunctionAllowedRole} without access control. + +Emits a {TargetFunctionRoleUpdated} event + +[.contract-item] +[[AccessManager-setTargetAdminDelay-address-uint32-]] +==== `[.contract-item-name]#++setTargetAdminDelay++#++(address target, uint32 newDelay)++` [.item-kind]#public# + +Set the delay for changing the configuration of a given target contract. + +Requirements: + +- the caller must be a global admin + +Emits a {TargetAdminDelayUpdated} event per selector + +[.contract-item] +[[AccessManager-_setTargetAdminDelay-address-uint32-]] +==== `[.contract-item-name]#++_setTargetAdminDelay++#++(address target, uint32 newDelay)++` [.item-kind]#internal# + +Internal version of {setTargetAdminDelay} without access control. + +Emits a {TargetAdminDelayUpdated} event + +[.contract-item] +[[AccessManager-setTargetClosed-address-bool-]] +==== `[.contract-item-name]#++setTargetClosed++#++(address target, bool closed)++` [.item-kind]#public# + +Set the closed flag for a contract. + +Requirements: + +- the caller must be a global admin + +Emits a {TargetClosed} event. + +[.contract-item] +[[AccessManager-_setTargetClosed-address-bool-]] +==== `[.contract-item-name]#++_setTargetClosed++#++(address target, bool closed)++` [.item-kind]#internal# + +Set the closed flag for a contract. This is an internal setter with no access restrictions. + +Emits a {TargetClosed} event. + +[.contract-item] +[[AccessManager-getSchedule-bytes32-]] +==== `[.contract-item-name]#++getSchedule++#++(bytes32 id) β†’ uint48++` [.item-kind]#public# + +Return the timepoint at which a scheduled operation will be ready for execution. This returns 0 if the +operation is not yet scheduled, has expired, was executed, or was canceled. + +[.contract-item] +[[AccessManager-getNonce-bytes32-]] +==== `[.contract-item-name]#++getNonce++#++(bytes32 id) β†’ uint32++` [.item-kind]#public# + +Return the nonce for the latest scheduled operation with a given id. Returns 0 if the operation has never +been scheduled. + +[.contract-item] +[[AccessManager-schedule-address-bytes-uint48-]] +==== `[.contract-item-name]#++schedule++#++(address target, bytes data, uint48 when) β†’ bytes32 operationId, uint32 nonce++` [.item-kind]#public# + +Schedule a delayed operation for future execution, and return the operation identifier. It is possible to +choose the timestamp at which the operation becomes executable as long as it satisfies the execution delays +required for the caller. The special value zero will automatically set the earliest possible time. + +Returns the `operationId` that was scheduled. Since this value is a hash of the parameters, it can reoccur when +the same parameters are used; if this is relevant, the returned `nonce` can be used to uniquely identify this +scheduled operation from other occurrences of the same `operationId` in invocations of {execute} and {cancel}. + +Emits a {OperationScheduled} event. + +NOTE: It is not possible to concurrently schedule more than one operation with the same `target` and `data`. If +this is necessary, a random byte can be appended to `data` to act as a salt that will be ignored by the target +contract if it is using standard Solidity ABI encoding. + +[.contract-item] +[[AccessManager-execute-address-bytes-]] +==== `[.contract-item-name]#++execute++#++(address target, bytes data) β†’ uint32++` [.item-kind]#public# + +Execute a function that is delay restricted, provided it was properly scheduled beforehand, or the +execution delay is 0. + +Returns the nonce that identifies the previously scheduled operation that is executed, or 0 if the +operation wasn't previously scheduled (if the caller doesn't have an execution delay). + +Emits an {OperationExecuted} event only if the call was scheduled and delayed. + +[.contract-item] +[[AccessManager-consumeScheduledOp-address-bytes-]] +==== `[.contract-item-name]#++consumeScheduledOp++#++(address caller, bytes data)++` [.item-kind]#public# + +Consume a scheduled operation targeting the caller. If such an operation exists, mark it as consumed +(emit an {OperationExecuted} event and clean the state). Otherwise, throw an error. + +This is useful for contract that want to enforce that calls targeting them were scheduled on the manager, +with all the verifications that it implies. + +Emit a {OperationExecuted} event + +[.contract-item] +[[AccessManager-_consumeScheduledOp-bytes32-]] +==== `[.contract-item-name]#++_consumeScheduledOp++#++(bytes32 operationId) β†’ uint32++` [.item-kind]#internal# + +Internal variant of {consumeScheduledOp} that operates on bytes32 operationId. + +Returns the nonce of the scheduled operation that is consumed. + +[.contract-item] +[[AccessManager-cancel-address-address-bytes-]] +==== `[.contract-item-name]#++cancel++#++(address caller, address target, bytes data) β†’ uint32++` [.item-kind]#public# + +Cancel a scheduled (delayed) operation. Returns the nonce that identifies the previously scheduled +operation that is cancelled. + +Requirements: + +- the caller must be the proposer, a guardian of the targeted function, or a global admin + +Emits a {OperationCanceled} event. + +[.contract-item] +[[AccessManager-hashOperation-address-address-bytes-]] +==== `[.contract-item-name]#++hashOperation++#++(address caller, address target, bytes data) β†’ bytes32++` [.item-kind]#public# + +Hashing function for delayed operations + +[.contract-item] +[[AccessManager-updateAuthority-address-address-]] +==== `[.contract-item-name]#++updateAuthority++#++(address target, address newAuthority)++` [.item-kind]#public# + +Change the AccessManager instance used by a contract that correctly uses this instance. + +Requirements: + +- the caller must be a global admin + +:constructor: pass:normal[xref:#AccessManaged-constructor-address-[`++constructor++`]] +:restricted: pass:normal[xref:#AccessManaged-restricted--[`++restricted++`]] +:authority: pass:normal[xref:#AccessManaged-authority--[`++authority++`]] +:setAuthority: pass:normal[xref:#AccessManaged-setAuthority-address-[`++setAuthority++`]] +:isConsumingScheduledOp: pass:normal[xref:#AccessManaged-isConsumingScheduledOp--[`++isConsumingScheduledOp++`]] +:_setAuthority: pass:normal[xref:#AccessManaged-_setAuthority-address-[`++_setAuthority++`]] +:_checkCanCall: pass:normal[xref:#AccessManaged-_checkCanCall-address-bytes-[`++_checkCanCall++`]] + +[.contract] +[[AccessManaged]] +=== `++AccessManaged++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/access/manager/AccessManaged.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/access/manager/AccessManaged.sol"; +``` + +This contract module makes available a {restricted} modifier. Functions decorated with this modifier will be +permissioned according to an "authority": a contract like {AccessManager} that follows the {IAuthority} interface, +implementing a policy that allows certain callers to access certain functions. + +IMPORTANT: The `restricted` modifier should never be used on `internal` functions, judiciously used in `public` +functions, and ideally only used in `external` functions. See {restricted}. + +[.contract-index] +.Modifiers +-- +* {xref-AccessManaged-restricted--}[`++restricted()++`] +-- + +[.contract-index] +.Functions +-- +* {xref-AccessManaged-constructor-address-}[`++constructor(initialAuthority)++`] +* {xref-AccessManaged-authority--}[`++authority()++`] +* {xref-AccessManaged-setAuthority-address-}[`++setAuthority(newAuthority)++`] +* {xref-AccessManaged-isConsumingScheduledOp--}[`++isConsumingScheduledOp()++`] +* {xref-AccessManaged-_setAuthority-address-}[`++_setAuthority(newAuthority)++`] +* {xref-AccessManaged-_checkCanCall-address-bytes-}[`++_checkCanCall(caller, data)++`] + +[.contract-subindex-inherited] +.IAccessManaged + +-- + +[.contract-index] +.Events +-- + +[.contract-subindex-inherited] +.IAccessManaged +* {xref-IAccessManaged-AuthorityUpdated-address-}[`++AuthorityUpdated(authority)++`] + +-- + +[.contract-index] +.Errors +-- + +[.contract-subindex-inherited] +.IAccessManaged +* {xref-IAccessManaged-AccessManagedUnauthorized-address-}[`++AccessManagedUnauthorized(caller)++`] +* {xref-IAccessManaged-AccessManagedRequiredDelay-address-uint32-}[`++AccessManagedRequiredDelay(caller, delay)++`] +* {xref-IAccessManaged-AccessManagedInvalidAuthority-address-}[`++AccessManagedInvalidAuthority(authority)++`] + +-- + +[.contract-item] +[[AccessManaged-restricted--]] +==== `[.contract-item-name]#++restricted++#++()++` [.item-kind]#modifier# + +Restricts access to a function as defined by the connected Authority for this contract and the +caller and selector of the function that entered the contract. + +[IMPORTANT] +==== +In general, this modifier should only be used on `external` functions. It is okay to use it on `public` +functions that are used as external entry points and are not called internally. Unless you know what you're +doing, it should never be used on `internal` functions. Failure to follow these rules can have critical security +implications! This is because the permissions are determined by the function that entered the contract, i.e. the +function at the bottom of the call stack, and not the function where the modifier is visible in the source code. +==== + +[NOTE] +==== +Selector collisions are mitigated by scoping permissions per contract, but some edge cases must be considered: + +* If the https://docs.soliditylang.org/en/v0.8.20/contracts.html#receive-ether-function[`receive()`] function +is restricted, any other function with a `0x00000000` selector will share permissions with `receive()`. +* Similarly, if there's no `receive()` function but a `fallback()` instead, the fallback might be called with +empty `calldata`, sharing the `0x00000000` selector permissions as well. +* For any other selector, if the restricted function is set on an upgradeable contract, an upgrade may remove +the restricted function and replace it with a new method whose selector replaces the last one, keeping the +previous permissions. +==== + +[.contract-item] +[[AccessManaged-constructor-address-]] +==== `[.contract-item-name]#++constructor++#++(address initialAuthority)++` [.item-kind]#internal# + +Initializes the contract connected to an initial authority. + +[.contract-item] +[[AccessManaged-authority--]] +==== `[.contract-item-name]#++authority++#++() β†’ address++` [.item-kind]#public# + +Returns the current authority. + +[.contract-item] +[[AccessManaged-setAuthority-address-]] +==== `[.contract-item-name]#++setAuthority++#++(address newAuthority)++` [.item-kind]#public# + +Transfers control to a new authority. The caller must be the current authority. + +[.contract-item] +[[AccessManaged-isConsumingScheduledOp--]] +==== `[.contract-item-name]#++isConsumingScheduledOp++#++() β†’ bytes4++` [.item-kind]#public# + +Returns true only in the context of a delayed restricted call, at the moment that the scheduled operation is +being consumed. Prevents denial of service for delayed restricted calls in the case that the contract performs +attacker controlled calls. + +[.contract-item] +[[AccessManaged-_setAuthority-address-]] +==== `[.contract-item-name]#++_setAuthority++#++(address newAuthority)++` [.item-kind]#internal# + +Transfers control to a new authority. Internal function with no access restriction. Allows bypassing the +permissions set by the current authority. + +[.contract-item] +[[AccessManaged-_checkCanCall-address-bytes-]] +==== `[.contract-item-name]#++_checkCanCall++#++(address caller, bytes data)++` [.item-kind]#internal# + +Reverts if the caller is not allowed to call the function identified by a selector. + diff --git a/docs/modules/api/pages/finance.adoc b/docs/modules/api/pages/finance.adoc new file mode 100644 index 000000000..c2f5f12dd --- /dev/null +++ b/docs/modules/api/pages/finance.adoc @@ -0,0 +1,234 @@ +:github-icon: pass:[] +:VestingWallet: pass:normal[xref:finance.adoc#VestingWallet[`VestingWallet`]] +:Ownable: pass:normal[xref:access.adoc#Ownable[`Ownable`]] +:xref-VestingWallet-constructor-address-uint64-uint64-: xref:finance.adoc#VestingWallet-constructor-address-uint64-uint64- +:xref-VestingWallet-receive--: xref:finance.adoc#VestingWallet-receive-- +:xref-VestingWallet-start--: xref:finance.adoc#VestingWallet-start-- +:xref-VestingWallet-duration--: xref:finance.adoc#VestingWallet-duration-- +:xref-VestingWallet-end--: xref:finance.adoc#VestingWallet-end-- +:xref-VestingWallet-released--: xref:finance.adoc#VestingWallet-released-- +:xref-VestingWallet-released-address-: xref:finance.adoc#VestingWallet-released-address- +:xref-VestingWallet-releasable--: xref:finance.adoc#VestingWallet-releasable-- +:xref-VestingWallet-releasable-address-: xref:finance.adoc#VestingWallet-releasable-address- +:xref-VestingWallet-release--: xref:finance.adoc#VestingWallet-release-- +:xref-VestingWallet-release-address-: xref:finance.adoc#VestingWallet-release-address- +:xref-VestingWallet-vestedAmount-uint64-: xref:finance.adoc#VestingWallet-vestedAmount-uint64- +:xref-VestingWallet-vestedAmount-address-uint64-: xref:finance.adoc#VestingWallet-vestedAmount-address-uint64- +:xref-VestingWallet-_vestingSchedule-uint256-uint64-: xref:finance.adoc#VestingWallet-_vestingSchedule-uint256-uint64- +:xref-Ownable-owner--: xref:access.adoc#Ownable-owner-- +:xref-Ownable-_checkOwner--: xref:access.adoc#Ownable-_checkOwner-- +:xref-Ownable-renounceOwnership--: xref:access.adoc#Ownable-renounceOwnership-- +:xref-Ownable-transferOwnership-address-: xref:access.adoc#Ownable-transferOwnership-address- +:xref-Ownable-_transferOwnership-address-: xref:access.adoc#Ownable-_transferOwnership-address- +:xref-VestingWallet-EtherReleased-uint256-: xref:finance.adoc#VestingWallet-EtherReleased-uint256- +:xref-VestingWallet-ERC20Released-address-uint256-: xref:finance.adoc#VestingWallet-ERC20Released-address-uint256- +:xref-Ownable-OwnershipTransferred-address-address-: xref:access.adoc#Ownable-OwnershipTransferred-address-address- +:xref-Ownable-OwnableUnauthorizedAccount-address-: xref:access.adoc#Ownable-OwnableUnauthorizedAccount-address- +:xref-Ownable-OwnableInvalidOwner-address-: xref:access.adoc#Ownable-OwnableInvalidOwner-address- += Finance + +[.readme-notice] +NOTE: This document is better viewed at https://docs.openzeppelin.com/contracts/api/finance + +This directory includes primitives for financial systems: + +- {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 + +:EtherReleased: pass:normal[xref:#VestingWallet-EtherReleased-uint256-[`++EtherReleased++`]] +:ERC20Released: pass:normal[xref:#VestingWallet-ERC20Released-address-uint256-[`++ERC20Released++`]] +:constructor: pass:normal[xref:#VestingWallet-constructor-address-uint64-uint64-[`++constructor++`]] +:receive: pass:normal[xref:#VestingWallet-receive--[`++receive++`]] +:start: pass:normal[xref:#VestingWallet-start--[`++start++`]] +:duration: pass:normal[xref:#VestingWallet-duration--[`++duration++`]] +:end: pass:normal[xref:#VestingWallet-end--[`++end++`]] +:released: pass:normal[xref:#VestingWallet-released--[`++released++`]] +:released: pass:normal[xref:#VestingWallet-released-address-[`++released++`]] +:releasable: pass:normal[xref:#VestingWallet-releasable--[`++releasable++`]] +:releasable: pass:normal[xref:#VestingWallet-releasable-address-[`++releasable++`]] +:release: pass:normal[xref:#VestingWallet-release--[`++release++`]] +:release: pass:normal[xref:#VestingWallet-release-address-[`++release++`]] +:vestedAmount: pass:normal[xref:#VestingWallet-vestedAmount-uint64-[`++vestedAmount++`]] +:vestedAmount: pass:normal[xref:#VestingWallet-vestedAmount-address-uint64-[`++vestedAmount++`]] +:_vestingSchedule: pass:normal[xref:#VestingWallet-_vestingSchedule-uint256-uint64-[`++_vestingSchedule++`]] + +[.contract] +[[VestingWallet]] +=== `++VestingWallet++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/finance/VestingWallet.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/finance/VestingWallet.sol"; +``` + +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 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-index] +.Functions +-- +* {xref-VestingWallet-constructor-address-uint64-uint64-}[`++constructor(beneficiary, startTimestamp, durationSeconds)++`] +* {xref-VestingWallet-receive--}[`++receive()++`] +* {xref-VestingWallet-start--}[`++start()++`] +* {xref-VestingWallet-duration--}[`++duration()++`] +* {xref-VestingWallet-end--}[`++end()++`] +* {xref-VestingWallet-released--}[`++released()++`] +* {xref-VestingWallet-released-address-}[`++released(token)++`] +* {xref-VestingWallet-releasable--}[`++releasable()++`] +* {xref-VestingWallet-releasable-address-}[`++releasable(token)++`] +* {xref-VestingWallet-release--}[`++release()++`] +* {xref-VestingWallet-release-address-}[`++release(token)++`] +* {xref-VestingWallet-vestedAmount-uint64-}[`++vestedAmount(timestamp)++`] +* {xref-VestingWallet-vestedAmount-address-uint64-}[`++vestedAmount(token, timestamp)++`] +* {xref-VestingWallet-_vestingSchedule-uint256-uint64-}[`++_vestingSchedule(totalAllocation, timestamp)++`] + +[.contract-subindex-inherited] +.Ownable +* {xref-Ownable-owner--}[`++owner()++`] +* {xref-Ownable-_checkOwner--}[`++_checkOwner()++`] +* {xref-Ownable-renounceOwnership--}[`++renounceOwnership()++`] +* {xref-Ownable-transferOwnership-address-}[`++transferOwnership(newOwner)++`] +* {xref-Ownable-_transferOwnership-address-}[`++_transferOwnership(newOwner)++`] + +-- + +[.contract-index] +.Events +-- +* {xref-VestingWallet-EtherReleased-uint256-}[`++EtherReleased(amount)++`] +* {xref-VestingWallet-ERC20Released-address-uint256-}[`++ERC20Released(token, amount)++`] + +[.contract-subindex-inherited] +.Ownable +* {xref-Ownable-OwnershipTransferred-address-address-}[`++OwnershipTransferred(previousOwner, newOwner)++`] + +-- + +[.contract-index] +.Errors +-- + +[.contract-subindex-inherited] +.Ownable +* {xref-Ownable-OwnableUnauthorizedAccount-address-}[`++OwnableUnauthorizedAccount(account)++`] +* {xref-Ownable-OwnableInvalidOwner-address-}[`++OwnableInvalidOwner(owner)++`] + +-- + +[.contract-item] +[[VestingWallet-constructor-address-uint64-uint64-]] +==== `[.contract-item-name]#++constructor++#++(address beneficiary, uint64 startTimestamp, uint64 durationSeconds)++` [.item-kind]#public# + +Sets the sender as the initial owner, the beneficiary as the pending owner, the start timestamp and the +vesting duration of the vesting wallet. + +[.contract-item] +[[VestingWallet-receive--]] +==== `[.contract-item-name]#++receive++#++()++` [.item-kind]#external# + +The contract should be able to receive Eth. + +[.contract-item] +[[VestingWallet-start--]] +==== `[.contract-item-name]#++start++#++() β†’ uint256++` [.item-kind]#public# + +Getter for the start timestamp. + +[.contract-item] +[[VestingWallet-duration--]] +==== `[.contract-item-name]#++duration++#++() β†’ uint256++` [.item-kind]#public# + +Getter for the vesting duration. + +[.contract-item] +[[VestingWallet-end--]] +==== `[.contract-item-name]#++end++#++() β†’ uint256++` [.item-kind]#public# + +Getter for the end timestamp. + +[.contract-item] +[[VestingWallet-released--]] +==== `[.contract-item-name]#++released++#++() β†’ uint256++` [.item-kind]#public# + +Amount of eth already released + +[.contract-item] +[[VestingWallet-released-address-]] +==== `[.contract-item-name]#++released++#++(address token) β†’ uint256++` [.item-kind]#public# + +Amount of token already released + +[.contract-item] +[[VestingWallet-releasable--]] +==== `[.contract-item-name]#++releasable++#++() β†’ uint256++` [.item-kind]#public# + +Getter for the amount of releasable eth. + +[.contract-item] +[[VestingWallet-releasable-address-]] +==== `[.contract-item-name]#++releasable++#++(address token) β†’ uint256++` [.item-kind]#public# + +Getter for the amount of releasable `token` tokens. `token` should be the address of an +IERC20 contract. + +[.contract-item] +[[VestingWallet-release--]] +==== `[.contract-item-name]#++release++#++()++` [.item-kind]#public# + +Release the native token (ether) that have already vested. + +Emits a {EtherReleased} event. + +[.contract-item] +[[VestingWallet-release-address-]] +==== `[.contract-item-name]#++release++#++(address token)++` [.item-kind]#public# + +Release the tokens that have already vested. + +Emits a {ERC20Released} event. + +[.contract-item] +[[VestingWallet-vestedAmount-uint64-]] +==== `[.contract-item-name]#++vestedAmount++#++(uint64 timestamp) β†’ uint256++` [.item-kind]#public# + +Calculates the amount of ether that has already vested. Default implementation is a linear vesting curve. + +[.contract-item] +[[VestingWallet-vestedAmount-address-uint64-]] +==== `[.contract-item-name]#++vestedAmount++#++(address token, uint64 timestamp) β†’ uint256++` [.item-kind]#public# + +Calculates the amount of tokens that has already vested. Default implementation is a linear vesting curve. + +[.contract-item] +[[VestingWallet-_vestingSchedule-uint256-uint64-]] +==== `[.contract-item-name]#++_vestingSchedule++#++(uint256 totalAllocation, uint64 timestamp) β†’ uint256++` [.item-kind]#internal# + +Virtual implementation of the vesting formula. This returns the amount vested, as a function of time, for +an asset given its total historical allocation. + +[.contract-item] +[[VestingWallet-EtherReleased-uint256-]] +==== `[.contract-item-name]#++EtherReleased++#++(uint256 amount)++` [.item-kind]#event# + +[.contract-item] +[[VestingWallet-ERC20Released-address-uint256-]] +==== `[.contract-item-name]#++ERC20Released++#++(address indexed token, uint256 amount)++` [.item-kind]#event# + diff --git a/docs/modules/api/pages/governance.adoc b/docs/modules/api/pages/governance.adoc new file mode 100644 index 000000000..fd3abcac0 --- /dev/null +++ b/docs/modules/api/pages/governance.adoc @@ -0,0 +1,5226 @@ +:github-icon: pass:[] +:Governor: pass:normal[xref:governance.adoc#Governor[`Governor`]] +:GovernorVotes: pass:normal[xref:governance.adoc#GovernorVotes[`GovernorVotes`]] +:ERC20Votes: pass:normal[xref:token/ERC20.adoc#ERC20Votes[`ERC20Votes`]] +:ERC721Votes: pass:normal[xref:token/ERC721.adoc#ERC721Votes[`ERC721Votes`]] +:GovernorVotesQuorumFraction: pass:normal[xref:governance.adoc#GovernorVotesQuorumFraction[`GovernorVotesQuorumFraction`]] +:GovernorCountingSimple: pass:normal[xref:governance.adoc#GovernorCountingSimple[`GovernorCountingSimple`]] +:GovernorTimelockControl: pass:normal[xref:governance.adoc#GovernorTimelockControl[`GovernorTimelockControl`]] +:TimelockController: pass:normal[xref:governance.adoc#TimelockController[`TimelockController`]] +:GovernorTimelockCompound: pass:normal[xref:governance.adoc#GovernorTimelockCompound[`GovernorTimelockCompound`]] +:GovernorStorage: pass:normal[xref:governance.adoc#GovernorStorage[`GovernorStorage`]] +:GovernorSettings: pass:normal[xref:governance.adoc#GovernorSettings[`GovernorSettings`]] +:GovernorPreventLateQuorum: pass:normal[xref:governance.adoc#GovernorPreventLateQuorum[`GovernorPreventLateQuorum`]] +:Governor-_cancel: pass:normal[xref:governance.adoc#Governor-_cancel-address---uint256---bytes---bytes32-[`Governor._cancel`]] +:Governor: pass:normal[xref:governance.adoc#Governor[`Governor`]] +:xref-IGovernor-name--: xref:governance.adoc#IGovernor-name-- +:xref-IGovernor-version--: xref:governance.adoc#IGovernor-version-- +:xref-IGovernor-COUNTING_MODE--: xref:governance.adoc#IGovernor-COUNTING_MODE-- +:xref-IGovernor-hashProposal-address---uint256---bytes---bytes32-: xref:governance.adoc#IGovernor-hashProposal-address---uint256---bytes---bytes32- +:xref-IGovernor-state-uint256-: xref:governance.adoc#IGovernor-state-uint256- +:xref-IGovernor-proposalThreshold--: xref:governance.adoc#IGovernor-proposalThreshold-- +:xref-IGovernor-proposalSnapshot-uint256-: xref:governance.adoc#IGovernor-proposalSnapshot-uint256- +:xref-IGovernor-proposalDeadline-uint256-: xref:governance.adoc#IGovernor-proposalDeadline-uint256- +:xref-IGovernor-proposalProposer-uint256-: xref:governance.adoc#IGovernor-proposalProposer-uint256- +:xref-IGovernor-proposalEta-uint256-: xref:governance.adoc#IGovernor-proposalEta-uint256- +:xref-IGovernor-proposalNeedsQueuing-uint256-: xref:governance.adoc#IGovernor-proposalNeedsQueuing-uint256- +:xref-IGovernor-votingDelay--: xref:governance.adoc#IGovernor-votingDelay-- +:xref-IGovernor-votingPeriod--: xref:governance.adoc#IGovernor-votingPeriod-- +:xref-IGovernor-quorum-uint256-: xref:governance.adoc#IGovernor-quorum-uint256- +:xref-IGovernor-getVotes-address-uint256-: xref:governance.adoc#IGovernor-getVotes-address-uint256- +:xref-IGovernor-getVotesWithParams-address-uint256-bytes-: xref:governance.adoc#IGovernor-getVotesWithParams-address-uint256-bytes- +:xref-IGovernor-hasVoted-uint256-address-: xref:governance.adoc#IGovernor-hasVoted-uint256-address- +:xref-IGovernor-propose-address---uint256---bytes---string-: xref:governance.adoc#IGovernor-propose-address---uint256---bytes---string- +:xref-IGovernor-queue-address---uint256---bytes---bytes32-: xref:governance.adoc#IGovernor-queue-address---uint256---bytes---bytes32- +:xref-IGovernor-execute-address---uint256---bytes---bytes32-: xref:governance.adoc#IGovernor-execute-address---uint256---bytes---bytes32- +:xref-IGovernor-cancel-address---uint256---bytes---bytes32-: xref:governance.adoc#IGovernor-cancel-address---uint256---bytes---bytes32- +:xref-IGovernor-castVote-uint256-uint8-: xref:governance.adoc#IGovernor-castVote-uint256-uint8- +:xref-IGovernor-castVoteWithReason-uint256-uint8-string-: xref:governance.adoc#IGovernor-castVoteWithReason-uint256-uint8-string- +:xref-IGovernor-castVoteWithReasonAndParams-uint256-uint8-string-bytes-: xref:governance.adoc#IGovernor-castVoteWithReasonAndParams-uint256-uint8-string-bytes- +:xref-IGovernor-castVoteBySig-uint256-uint8-address-bytes-: xref:governance.adoc#IGovernor-castVoteBySig-uint256-uint8-address-bytes- +:xref-IGovernor-castVoteWithReasonAndParamsBySig-uint256-uint8-address-string-bytes-bytes-: xref:governance.adoc#IGovernor-castVoteWithReasonAndParamsBySig-uint256-uint8-address-string-bytes-bytes- +:xref-IERC6372-clock--: xref:interfaces.adoc#IERC6372-clock-- +:xref-IERC6372-CLOCK_MODE--: xref:interfaces.adoc#IERC6372-CLOCK_MODE-- +:xref-IERC165-supportsInterface-bytes4-: xref:utils.adoc#IERC165-supportsInterface-bytes4- +:xref-IGovernor-ProposalCreated-uint256-address-address---uint256---string---bytes---uint256-uint256-string-: xref:governance.adoc#IGovernor-ProposalCreated-uint256-address-address---uint256---string---bytes---uint256-uint256-string- +:xref-IGovernor-ProposalQueued-uint256-uint256-: xref:governance.adoc#IGovernor-ProposalQueued-uint256-uint256- +:xref-IGovernor-ProposalExecuted-uint256-: xref:governance.adoc#IGovernor-ProposalExecuted-uint256- +:xref-IGovernor-ProposalCanceled-uint256-: xref:governance.adoc#IGovernor-ProposalCanceled-uint256- +:xref-IGovernor-VoteCast-address-uint256-uint8-uint256-string-: xref:governance.adoc#IGovernor-VoteCast-address-uint256-uint8-uint256-string- +:xref-IGovernor-VoteCastWithParams-address-uint256-uint8-uint256-string-bytes-: xref:governance.adoc#IGovernor-VoteCastWithParams-address-uint256-uint8-uint256-string-bytes- +:xref-IGovernor-GovernorInvalidProposalLength-uint256-uint256-uint256-: xref:governance.adoc#IGovernor-GovernorInvalidProposalLength-uint256-uint256-uint256- +:xref-IGovernor-GovernorAlreadyCastVote-address-: xref:governance.adoc#IGovernor-GovernorAlreadyCastVote-address- +:xref-IGovernor-GovernorDisabledDeposit--: xref:governance.adoc#IGovernor-GovernorDisabledDeposit-- +:xref-IGovernor-GovernorOnlyProposer-address-: xref:governance.adoc#IGovernor-GovernorOnlyProposer-address- +:xref-IGovernor-GovernorOnlyExecutor-address-: xref:governance.adoc#IGovernor-GovernorOnlyExecutor-address- +:xref-IGovernor-GovernorNonexistentProposal-uint256-: xref:governance.adoc#IGovernor-GovernorNonexistentProposal-uint256- +:xref-IGovernor-GovernorUnexpectedProposalState-uint256-enum-IGovernor-ProposalState-bytes32-: xref:governance.adoc#IGovernor-GovernorUnexpectedProposalState-uint256-enum-IGovernor-ProposalState-bytes32- +:xref-IGovernor-GovernorInvalidVotingPeriod-uint256-: xref:governance.adoc#IGovernor-GovernorInvalidVotingPeriod-uint256- +:xref-IGovernor-GovernorInsufficientProposerVotes-address-uint256-uint256-: xref:governance.adoc#IGovernor-GovernorInsufficientProposerVotes-address-uint256-uint256- +:xref-IGovernor-GovernorRestrictedProposer-address-: xref:governance.adoc#IGovernor-GovernorRestrictedProposer-address- +:xref-IGovernor-GovernorInvalidVoteType--: xref:governance.adoc#IGovernor-GovernorInvalidVoteType-- +:xref-IGovernor-GovernorQueueNotImplemented--: xref:governance.adoc#IGovernor-GovernorQueueNotImplemented-- +:xref-IGovernor-GovernorNotQueuedProposal-uint256-: xref:governance.adoc#IGovernor-GovernorNotQueuedProposal-uint256- +:xref-IGovernor-GovernorAlreadyQueuedProposal-uint256-: xref:governance.adoc#IGovernor-GovernorAlreadyQueuedProposal-uint256- +:xref-IGovernor-GovernorInvalidSignature-address-: xref:governance.adoc#IGovernor-GovernorInvalidSignature-address- +:IERC6372-clock: pass:normal[xref:interfaces.adoc#IERC6372-clock--[`IERC6372.clock`]] +:ERC20Votes: pass:normal[xref:token/ERC20.adoc#ERC20Votes[`ERC20Votes`]] +:ERC20Votes: pass:normal[xref:token/ERC20.adoc#ERC20Votes[`ERC20Votes`]] +:IGovernor-votingDelay: pass:normal[xref:governance.adoc#IGovernor-votingDelay--[`IGovernor.votingDelay`]] +:IGovernor-votingPeriod: pass:normal[xref:governance.adoc#IGovernor-votingPeriod--[`IGovernor.votingPeriod`]] +:Governor-_encodeStateBitmap: pass:normal[xref:governance.adoc#Governor-_encodeStateBitmap-enum-IGovernor-ProposalState-[`Governor._encodeStateBitmap`]] +:IERC1271-isValidSignature: pass:normal[xref:interfaces.adoc#IERC1271-isValidSignature-bytes32-bytes-[`IERC1271.isValidSignature`]] +:xref-Governor-onlyGovernance--: xref:governance.adoc#Governor-onlyGovernance-- +:xref-Governor-constructor-string-: xref:governance.adoc#Governor-constructor-string- +:xref-Governor-receive--: xref:governance.adoc#Governor-receive-- +:xref-Governor-supportsInterface-bytes4-: xref:governance.adoc#Governor-supportsInterface-bytes4- +:xref-Governor-name--: xref:governance.adoc#Governor-name-- +:xref-Governor-version--: xref:governance.adoc#Governor-version-- +:xref-Governor-hashProposal-address---uint256---bytes---bytes32-: xref:governance.adoc#Governor-hashProposal-address---uint256---bytes---bytes32- +:xref-Governor-state-uint256-: xref:governance.adoc#Governor-state-uint256- +:xref-Governor-proposalThreshold--: xref:governance.adoc#Governor-proposalThreshold-- +:xref-Governor-proposalSnapshot-uint256-: xref:governance.adoc#Governor-proposalSnapshot-uint256- +:xref-Governor-proposalDeadline-uint256-: xref:governance.adoc#Governor-proposalDeadline-uint256- +:xref-Governor-proposalProposer-uint256-: xref:governance.adoc#Governor-proposalProposer-uint256- +:xref-Governor-proposalEta-uint256-: xref:governance.adoc#Governor-proposalEta-uint256- +:xref-Governor-proposalNeedsQueuing-uint256-: xref:governance.adoc#Governor-proposalNeedsQueuing-uint256- +:xref-Governor-_checkGovernance--: xref:governance.adoc#Governor-_checkGovernance-- +:xref-Governor-_quorumReached-uint256-: xref:governance.adoc#Governor-_quorumReached-uint256- +:xref-Governor-_voteSucceeded-uint256-: xref:governance.adoc#Governor-_voteSucceeded-uint256- +:xref-Governor-_getVotes-address-uint256-bytes-: xref:governance.adoc#Governor-_getVotes-address-uint256-bytes- +:xref-Governor-_countVote-uint256-address-uint8-uint256-bytes-: xref:governance.adoc#Governor-_countVote-uint256-address-uint8-uint256-bytes- +:xref-Governor-_defaultParams--: xref:governance.adoc#Governor-_defaultParams-- +:xref-Governor-propose-address---uint256---bytes---string-: xref:governance.adoc#Governor-propose-address---uint256---bytes---string- +:xref-Governor-_propose-address---uint256---bytes---string-address-: xref:governance.adoc#Governor-_propose-address---uint256---bytes---string-address- +:xref-Governor-queue-address---uint256---bytes---bytes32-: xref:governance.adoc#Governor-queue-address---uint256---bytes---bytes32- +:xref-Governor-_queueOperations-uint256-address---uint256---bytes---bytes32-: xref:governance.adoc#Governor-_queueOperations-uint256-address---uint256---bytes---bytes32- +:xref-Governor-execute-address---uint256---bytes---bytes32-: xref:governance.adoc#Governor-execute-address---uint256---bytes---bytes32- +:xref-Governor-_executeOperations-uint256-address---uint256---bytes---bytes32-: xref:governance.adoc#Governor-_executeOperations-uint256-address---uint256---bytes---bytes32- +:xref-Governor-cancel-address---uint256---bytes---bytes32-: xref:governance.adoc#Governor-cancel-address---uint256---bytes---bytes32- +:xref-Governor-_cancel-address---uint256---bytes---bytes32-: xref:governance.adoc#Governor-_cancel-address---uint256---bytes---bytes32- +:xref-Governor-getVotes-address-uint256-: xref:governance.adoc#Governor-getVotes-address-uint256- +:xref-Governor-getVotesWithParams-address-uint256-bytes-: xref:governance.adoc#Governor-getVotesWithParams-address-uint256-bytes- +:xref-Governor-castVote-uint256-uint8-: xref:governance.adoc#Governor-castVote-uint256-uint8- +:xref-Governor-castVoteWithReason-uint256-uint8-string-: xref:governance.adoc#Governor-castVoteWithReason-uint256-uint8-string- +:xref-Governor-castVoteWithReasonAndParams-uint256-uint8-string-bytes-: xref:governance.adoc#Governor-castVoteWithReasonAndParams-uint256-uint8-string-bytes- +:xref-Governor-castVoteBySig-uint256-uint8-address-bytes-: xref:governance.adoc#Governor-castVoteBySig-uint256-uint8-address-bytes- +:xref-Governor-castVoteWithReasonAndParamsBySig-uint256-uint8-address-string-bytes-bytes-: xref:governance.adoc#Governor-castVoteWithReasonAndParamsBySig-uint256-uint8-address-string-bytes-bytes- +:xref-Governor-_castVote-uint256-address-uint8-string-: xref:governance.adoc#Governor-_castVote-uint256-address-uint8-string- +:xref-Governor-_castVote-uint256-address-uint8-string-bytes-: xref:governance.adoc#Governor-_castVote-uint256-address-uint8-string-bytes- +:xref-Governor-relay-address-uint256-bytes-: xref:governance.adoc#Governor-relay-address-uint256-bytes- +:xref-Governor-_executor--: xref:governance.adoc#Governor-_executor-- +:xref-Governor-onERC721Received-address-address-uint256-bytes-: xref:governance.adoc#Governor-onERC721Received-address-address-uint256-bytes- +:xref-Governor-onERC1155Received-address-address-uint256-uint256-bytes-: xref:governance.adoc#Governor-onERC1155Received-address-address-uint256-uint256-bytes- +:xref-Governor-onERC1155BatchReceived-address-address-uint256---uint256---bytes-: xref:governance.adoc#Governor-onERC1155BatchReceived-address-address-uint256---uint256---bytes- +:xref-Governor-_encodeStateBitmap-enum-IGovernor-ProposalState-: xref:governance.adoc#Governor-_encodeStateBitmap-enum-IGovernor-ProposalState- +:xref-Governor-_isValidDescriptionForProposer-address-string-: xref:governance.adoc#Governor-_isValidDescriptionForProposer-address-string- +:xref-Governor-clock--: xref:governance.adoc#Governor-clock-- +:xref-Governor-CLOCK_MODE--: xref:governance.adoc#Governor-CLOCK_MODE-- +:xref-Governor-votingDelay--: xref:governance.adoc#Governor-votingDelay-- +:xref-Governor-votingPeriod--: xref:governance.adoc#Governor-votingPeriod-- +:xref-Governor-quorum-uint256-: xref:governance.adoc#Governor-quorum-uint256- +:xref-IGovernor-COUNTING_MODE--: xref:governance.adoc#IGovernor-COUNTING_MODE-- +:xref-IGovernor-hasVoted-uint256-address-: xref:governance.adoc#IGovernor-hasVoted-uint256-address- +:xref-Nonces-nonces-address-: xref:utils.adoc#Nonces-nonces-address- +:xref-Nonces-_useNonce-address-: xref:utils.adoc#Nonces-_useNonce-address- +:xref-Nonces-_useCheckedNonce-address-uint256-: xref:utils.adoc#Nonces-_useCheckedNonce-address-uint256- +:xref-EIP712-_domainSeparatorV4--: xref:utils.adoc#EIP712-_domainSeparatorV4-- +:xref-EIP712-_hashTypedDataV4-bytes32-: xref:utils.adoc#EIP712-_hashTypedDataV4-bytes32- +:xref-EIP712-eip712Domain--: xref:utils.adoc#EIP712-eip712Domain-- +:xref-EIP712-_EIP712Name--: xref:utils.adoc#EIP712-_EIP712Name-- +:xref-EIP712-_EIP712Version--: xref:utils.adoc#EIP712-_EIP712Version-- +:xref-IGovernor-ProposalCreated-uint256-address-address---uint256---string---bytes---uint256-uint256-string-: xref:governance.adoc#IGovernor-ProposalCreated-uint256-address-address---uint256---string---bytes---uint256-uint256-string- +:xref-IGovernor-ProposalQueued-uint256-uint256-: xref:governance.adoc#IGovernor-ProposalQueued-uint256-uint256- +:xref-IGovernor-ProposalExecuted-uint256-: xref:governance.adoc#IGovernor-ProposalExecuted-uint256- +:xref-IGovernor-ProposalCanceled-uint256-: xref:governance.adoc#IGovernor-ProposalCanceled-uint256- +:xref-IGovernor-VoteCast-address-uint256-uint8-uint256-string-: xref:governance.adoc#IGovernor-VoteCast-address-uint256-uint8-uint256-string- +:xref-IGovernor-VoteCastWithParams-address-uint256-uint8-uint256-string-bytes-: xref:governance.adoc#IGovernor-VoteCastWithParams-address-uint256-uint8-uint256-string-bytes- +:xref-IERC5267-EIP712DomainChanged--: xref:interfaces.adoc#IERC5267-EIP712DomainChanged-- +:xref-IGovernor-GovernorInvalidProposalLength-uint256-uint256-uint256-: xref:governance.adoc#IGovernor-GovernorInvalidProposalLength-uint256-uint256-uint256- +:xref-IGovernor-GovernorAlreadyCastVote-address-: xref:governance.adoc#IGovernor-GovernorAlreadyCastVote-address- +:xref-IGovernor-GovernorDisabledDeposit--: xref:governance.adoc#IGovernor-GovernorDisabledDeposit-- +:xref-IGovernor-GovernorOnlyProposer-address-: xref:governance.adoc#IGovernor-GovernorOnlyProposer-address- +:xref-IGovernor-GovernorOnlyExecutor-address-: xref:governance.adoc#IGovernor-GovernorOnlyExecutor-address- +:xref-IGovernor-GovernorNonexistentProposal-uint256-: xref:governance.adoc#IGovernor-GovernorNonexistentProposal-uint256- +:xref-IGovernor-GovernorUnexpectedProposalState-uint256-enum-IGovernor-ProposalState-bytes32-: xref:governance.adoc#IGovernor-GovernorUnexpectedProposalState-uint256-enum-IGovernor-ProposalState-bytes32- +:xref-IGovernor-GovernorInvalidVotingPeriod-uint256-: xref:governance.adoc#IGovernor-GovernorInvalidVotingPeriod-uint256- +:xref-IGovernor-GovernorInsufficientProposerVotes-address-uint256-uint256-: xref:governance.adoc#IGovernor-GovernorInsufficientProposerVotes-address-uint256-uint256- +:xref-IGovernor-GovernorRestrictedProposer-address-: xref:governance.adoc#IGovernor-GovernorRestrictedProposer-address- +:xref-IGovernor-GovernorInvalidVoteType--: xref:governance.adoc#IGovernor-GovernorInvalidVoteType-- +:xref-IGovernor-GovernorQueueNotImplemented--: xref:governance.adoc#IGovernor-GovernorQueueNotImplemented-- +:xref-IGovernor-GovernorNotQueuedProposal-uint256-: xref:governance.adoc#IGovernor-GovernorNotQueuedProposal-uint256- +:xref-IGovernor-GovernorAlreadyQueuedProposal-uint256-: xref:governance.adoc#IGovernor-GovernorAlreadyQueuedProposal-uint256- +:xref-IGovernor-GovernorInvalidSignature-address-: xref:governance.adoc#IGovernor-GovernorInvalidSignature-address- +:xref-Nonces-InvalidAccountNonce-address-uint256-: xref:utils.adoc#Nonces-InvalidAccountNonce-address-uint256- +:GovernorSettings: pass:normal[xref:governance.adoc#GovernorSettings[`GovernorSettings`]] +:IERC165-supportsInterface: pass:normal[xref:utils.adoc#IERC165-supportsInterface-bytes4-[`IERC165.supportsInterface`]] +:IGovernor-name: pass:normal[xref:governance.adoc#IGovernor-name--[`IGovernor.name`]] +:IGovernor-version: pass:normal[xref:governance.adoc#IGovernor-version--[`IGovernor.version`]] +:IGovernor-hashProposal: pass:normal[xref:governance.adoc#IGovernor-hashProposal-address---uint256---bytes---bytes32-[`IGovernor.hashProposal`]] +:IGovernor-state: pass:normal[xref:governance.adoc#IGovernor-state-uint256-[`IGovernor.state`]] +:IGovernor-proposalThreshold: pass:normal[xref:governance.adoc#IGovernor-proposalThreshold--[`IGovernor.proposalThreshold`]] +:IGovernor-proposalSnapshot: pass:normal[xref:governance.adoc#IGovernor-proposalSnapshot-uint256-[`IGovernor.proposalSnapshot`]] +:IGovernor-proposalDeadline: pass:normal[xref:governance.adoc#IGovernor-proposalDeadline-uint256-[`IGovernor.proposalDeadline`]] +:IGovernor-proposalProposer: pass:normal[xref:governance.adoc#IGovernor-proposalProposer-uint256-[`IGovernor.proposalProposer`]] +:IGovernor-proposalEta: pass:normal[xref:governance.adoc#IGovernor-proposalEta-uint256-[`IGovernor.proposalEta`]] +:IGovernor-proposalNeedsQueuing: pass:normal[xref:governance.adoc#IGovernor-proposalNeedsQueuing-uint256-[`IGovernor.proposalNeedsQueuing`]] +:IGovernor-propose: pass:normal[xref:governance.adoc#IGovernor-propose-address---uint256---bytes---string-[`IGovernor.propose`]] +:IGovernor-ProposalCreated: pass:normal[xref:governance.adoc#IGovernor-ProposalCreated-uint256-address-address---uint256---string---bytes---uint256-uint256-string-[`IGovernor.ProposalCreated`]] +:IGovernor-queue: pass:normal[xref:governance.adoc#IGovernor-queue-address---uint256---bytes---bytes32-[`IGovernor.queue`]] +:IGovernor-execute: pass:normal[xref:governance.adoc#IGovernor-execute-address---uint256---bytes---bytes32-[`IGovernor.execute`]] +:IGovernor-cancel: pass:normal[xref:governance.adoc#IGovernor-cancel-address---uint256---bytes---bytes32-[`IGovernor.cancel`]] +:IGovernor-ProposalCanceled: pass:normal[xref:governance.adoc#IGovernor-ProposalCanceled-uint256-[`IGovernor.ProposalCanceled`]] +:IGovernor-getVotes: pass:normal[xref:governance.adoc#IGovernor-getVotes-address-uint256-[`IGovernor.getVotes`]] +:IGovernor-getVotesWithParams: pass:normal[xref:governance.adoc#IGovernor-getVotesWithParams-address-uint256-bytes-[`IGovernor.getVotesWithParams`]] +:IGovernor-castVote: pass:normal[xref:governance.adoc#IGovernor-castVote-uint256-uint8-[`IGovernor.castVote`]] +:IGovernor-castVoteWithReason: pass:normal[xref:governance.adoc#IGovernor-castVoteWithReason-uint256-uint8-string-[`IGovernor.castVoteWithReason`]] +:IGovernor-castVoteWithReasonAndParams: pass:normal[xref:governance.adoc#IGovernor-castVoteWithReasonAndParams-uint256-uint8-string-bytes-[`IGovernor.castVoteWithReasonAndParams`]] +:IGovernor-castVoteBySig: pass:normal[xref:governance.adoc#IGovernor-castVoteBySig-uint256-uint8-address-bytes-[`IGovernor.castVoteBySig`]] +:IGovernor-castVoteWithReasonAndParamsBySig: pass:normal[xref:governance.adoc#IGovernor-castVoteWithReasonAndParamsBySig-uint256-uint8-address-string-bytes-bytes-[`IGovernor.castVoteWithReasonAndParamsBySig`]] +:IGovernor-getVotes: pass:normal[xref:governance.adoc#IGovernor-getVotes-address-uint256-[`IGovernor.getVotes`]] +:IGovernor-VoteCast: pass:normal[xref:governance.adoc#IGovernor-VoteCast-address-uint256-uint8-uint256-string-[`IGovernor.VoteCast`]] +:IGovernor-getVotes: pass:normal[xref:governance.adoc#IGovernor-getVotes-address-uint256-[`IGovernor.getVotes`]] +:IGovernor-VoteCast: pass:normal[xref:governance.adoc#IGovernor-VoteCast-address-uint256-uint8-uint256-string-[`IGovernor.VoteCast`]] +:IERC721Receiver-onERC721Received: pass:normal[xref:token/ERC721.adoc#IERC721Receiver-onERC721Received-address-address-uint256-bytes-[`IERC721Receiver.onERC721Received`]] +:IERC1155Receiver-onERC1155Received: pass:normal[xref:token/ERC1155.adoc#IERC1155Receiver-onERC1155Received-address-address-uint256-uint256-bytes-[`IERC1155Receiver.onERC1155Received`]] +:IERC1155Receiver-onERC1155BatchReceived: pass:normal[xref:token/ERC1155.adoc#IERC1155Receiver-onERC1155BatchReceived-address-address-uint256---uint256---bytes-[`IERC1155Receiver.onERC1155BatchReceived`]] +:IERC6372-clock: pass:normal[xref:interfaces.adoc#IERC6372-clock--[`IERC6372.clock`]] +:ERC20Votes: pass:normal[xref:token/ERC20.adoc#ERC20Votes[`ERC20Votes`]] +:Governor: pass:normal[xref:governance.adoc#Governor[`Governor`]] +:xref-GovernorCountingSimple-COUNTING_MODE--: xref:governance.adoc#GovernorCountingSimple-COUNTING_MODE-- +:xref-GovernorCountingSimple-hasVoted-uint256-address-: xref:governance.adoc#GovernorCountingSimple-hasVoted-uint256-address- +:xref-GovernorCountingSimple-proposalVotes-uint256-: xref:governance.adoc#GovernorCountingSimple-proposalVotes-uint256- +:xref-GovernorCountingSimple-_quorumReached-uint256-: xref:governance.adoc#GovernorCountingSimple-_quorumReached-uint256- +:xref-GovernorCountingSimple-_voteSucceeded-uint256-: xref:governance.adoc#GovernorCountingSimple-_voteSucceeded-uint256- +:xref-GovernorCountingSimple-_countVote-uint256-address-uint8-uint256-bytes-: xref:governance.adoc#GovernorCountingSimple-_countVote-uint256-address-uint8-uint256-bytes- +:xref-Governor-receive--: xref:governance.adoc#Governor-receive-- +:xref-Governor-supportsInterface-bytes4-: xref:governance.adoc#Governor-supportsInterface-bytes4- +:xref-Governor-name--: xref:governance.adoc#Governor-name-- +:xref-Governor-version--: xref:governance.adoc#Governor-version-- +:xref-Governor-hashProposal-address---uint256---bytes---bytes32-: xref:governance.adoc#Governor-hashProposal-address---uint256---bytes---bytes32- +:xref-Governor-state-uint256-: xref:governance.adoc#Governor-state-uint256- +:xref-Governor-proposalThreshold--: xref:governance.adoc#Governor-proposalThreshold-- +:xref-Governor-proposalSnapshot-uint256-: xref:governance.adoc#Governor-proposalSnapshot-uint256- +:xref-Governor-proposalDeadline-uint256-: xref:governance.adoc#Governor-proposalDeadline-uint256- +:xref-Governor-proposalProposer-uint256-: xref:governance.adoc#Governor-proposalProposer-uint256- +:xref-Governor-proposalEta-uint256-: xref:governance.adoc#Governor-proposalEta-uint256- +:xref-Governor-proposalNeedsQueuing-uint256-: xref:governance.adoc#Governor-proposalNeedsQueuing-uint256- +:xref-Governor-_checkGovernance--: xref:governance.adoc#Governor-_checkGovernance-- +:xref-Governor-_getVotes-address-uint256-bytes-: xref:governance.adoc#Governor-_getVotes-address-uint256-bytes- +:xref-Governor-_defaultParams--: xref:governance.adoc#Governor-_defaultParams-- +:xref-Governor-propose-address---uint256---bytes---string-: xref:governance.adoc#Governor-propose-address---uint256---bytes---string- +:xref-Governor-_propose-address---uint256---bytes---string-address-: xref:governance.adoc#Governor-_propose-address---uint256---bytes---string-address- +:xref-Governor-queue-address---uint256---bytes---bytes32-: xref:governance.adoc#Governor-queue-address---uint256---bytes---bytes32- +:xref-Governor-_queueOperations-uint256-address---uint256---bytes---bytes32-: xref:governance.adoc#Governor-_queueOperations-uint256-address---uint256---bytes---bytes32- +:xref-Governor-execute-address---uint256---bytes---bytes32-: xref:governance.adoc#Governor-execute-address---uint256---bytes---bytes32- +:xref-Governor-_executeOperations-uint256-address---uint256---bytes---bytes32-: xref:governance.adoc#Governor-_executeOperations-uint256-address---uint256---bytes---bytes32- +:xref-Governor-cancel-address---uint256---bytes---bytes32-: xref:governance.adoc#Governor-cancel-address---uint256---bytes---bytes32- +:xref-Governor-_cancel-address---uint256---bytes---bytes32-: xref:governance.adoc#Governor-_cancel-address---uint256---bytes---bytes32- +:xref-Governor-getVotes-address-uint256-: xref:governance.adoc#Governor-getVotes-address-uint256- +:xref-Governor-getVotesWithParams-address-uint256-bytes-: xref:governance.adoc#Governor-getVotesWithParams-address-uint256-bytes- +:xref-Governor-castVote-uint256-uint8-: xref:governance.adoc#Governor-castVote-uint256-uint8- +:xref-Governor-castVoteWithReason-uint256-uint8-string-: xref:governance.adoc#Governor-castVoteWithReason-uint256-uint8-string- +:xref-Governor-castVoteWithReasonAndParams-uint256-uint8-string-bytes-: xref:governance.adoc#Governor-castVoteWithReasonAndParams-uint256-uint8-string-bytes- +:xref-Governor-castVoteBySig-uint256-uint8-address-bytes-: xref:governance.adoc#Governor-castVoteBySig-uint256-uint8-address-bytes- +:xref-Governor-castVoteWithReasonAndParamsBySig-uint256-uint8-address-string-bytes-bytes-: xref:governance.adoc#Governor-castVoteWithReasonAndParamsBySig-uint256-uint8-address-string-bytes-bytes- +:xref-Governor-_castVote-uint256-address-uint8-string-: xref:governance.adoc#Governor-_castVote-uint256-address-uint8-string- +:xref-Governor-_castVote-uint256-address-uint8-string-bytes-: xref:governance.adoc#Governor-_castVote-uint256-address-uint8-string-bytes- +:xref-Governor-relay-address-uint256-bytes-: xref:governance.adoc#Governor-relay-address-uint256-bytes- +:xref-Governor-_executor--: xref:governance.adoc#Governor-_executor-- +:xref-Governor-onERC721Received-address-address-uint256-bytes-: xref:governance.adoc#Governor-onERC721Received-address-address-uint256-bytes- +:xref-Governor-onERC1155Received-address-address-uint256-uint256-bytes-: xref:governance.adoc#Governor-onERC1155Received-address-address-uint256-uint256-bytes- +:xref-Governor-onERC1155BatchReceived-address-address-uint256---uint256---bytes-: xref:governance.adoc#Governor-onERC1155BatchReceived-address-address-uint256---uint256---bytes- +:xref-Governor-_encodeStateBitmap-enum-IGovernor-ProposalState-: xref:governance.adoc#Governor-_encodeStateBitmap-enum-IGovernor-ProposalState- +:xref-Governor-_isValidDescriptionForProposer-address-string-: xref:governance.adoc#Governor-_isValidDescriptionForProposer-address-string- +:xref-Governor-clock--: xref:governance.adoc#Governor-clock-- +:xref-Governor-CLOCK_MODE--: xref:governance.adoc#Governor-CLOCK_MODE-- +:xref-Governor-votingDelay--: xref:governance.adoc#Governor-votingDelay-- +:xref-Governor-votingPeriod--: xref:governance.adoc#Governor-votingPeriod-- +:xref-Governor-quorum-uint256-: xref:governance.adoc#Governor-quorum-uint256- +:xref-Nonces-nonces-address-: xref:utils.adoc#Nonces-nonces-address- +:xref-Nonces-_useNonce-address-: xref:utils.adoc#Nonces-_useNonce-address- +:xref-Nonces-_useCheckedNonce-address-uint256-: xref:utils.adoc#Nonces-_useCheckedNonce-address-uint256- +:xref-EIP712-_domainSeparatorV4--: xref:utils.adoc#EIP712-_domainSeparatorV4-- +:xref-EIP712-_hashTypedDataV4-bytes32-: xref:utils.adoc#EIP712-_hashTypedDataV4-bytes32- +:xref-EIP712-eip712Domain--: xref:utils.adoc#EIP712-eip712Domain-- +:xref-EIP712-_EIP712Name--: xref:utils.adoc#EIP712-_EIP712Name-- +:xref-EIP712-_EIP712Version--: xref:utils.adoc#EIP712-_EIP712Version-- +:xref-IGovernor-ProposalCreated-uint256-address-address---uint256---string---bytes---uint256-uint256-string-: xref:governance.adoc#IGovernor-ProposalCreated-uint256-address-address---uint256---string---bytes---uint256-uint256-string- +:xref-IGovernor-ProposalQueued-uint256-uint256-: xref:governance.adoc#IGovernor-ProposalQueued-uint256-uint256- +:xref-IGovernor-ProposalExecuted-uint256-: xref:governance.adoc#IGovernor-ProposalExecuted-uint256- +:xref-IGovernor-ProposalCanceled-uint256-: xref:governance.adoc#IGovernor-ProposalCanceled-uint256- +:xref-IGovernor-VoteCast-address-uint256-uint8-uint256-string-: xref:governance.adoc#IGovernor-VoteCast-address-uint256-uint8-uint256-string- +:xref-IGovernor-VoteCastWithParams-address-uint256-uint8-uint256-string-bytes-: xref:governance.adoc#IGovernor-VoteCastWithParams-address-uint256-uint8-uint256-string-bytes- +:xref-IERC5267-EIP712DomainChanged--: xref:interfaces.adoc#IERC5267-EIP712DomainChanged-- +:xref-IGovernor-GovernorInvalidProposalLength-uint256-uint256-uint256-: xref:governance.adoc#IGovernor-GovernorInvalidProposalLength-uint256-uint256-uint256- +:xref-IGovernor-GovernorAlreadyCastVote-address-: xref:governance.adoc#IGovernor-GovernorAlreadyCastVote-address- +:xref-IGovernor-GovernorDisabledDeposit--: xref:governance.adoc#IGovernor-GovernorDisabledDeposit-- +:xref-IGovernor-GovernorOnlyProposer-address-: xref:governance.adoc#IGovernor-GovernorOnlyProposer-address- +:xref-IGovernor-GovernorOnlyExecutor-address-: xref:governance.adoc#IGovernor-GovernorOnlyExecutor-address- +:xref-IGovernor-GovernorNonexistentProposal-uint256-: xref:governance.adoc#IGovernor-GovernorNonexistentProposal-uint256- +:xref-IGovernor-GovernorUnexpectedProposalState-uint256-enum-IGovernor-ProposalState-bytes32-: xref:governance.adoc#IGovernor-GovernorUnexpectedProposalState-uint256-enum-IGovernor-ProposalState-bytes32- +:xref-IGovernor-GovernorInvalidVotingPeriod-uint256-: xref:governance.adoc#IGovernor-GovernorInvalidVotingPeriod-uint256- +:xref-IGovernor-GovernorInsufficientProposerVotes-address-uint256-uint256-: xref:governance.adoc#IGovernor-GovernorInsufficientProposerVotes-address-uint256-uint256- +:xref-IGovernor-GovernorRestrictedProposer-address-: xref:governance.adoc#IGovernor-GovernorRestrictedProposer-address- +:xref-IGovernor-GovernorInvalidVoteType--: xref:governance.adoc#IGovernor-GovernorInvalidVoteType-- +:xref-IGovernor-GovernorQueueNotImplemented--: xref:governance.adoc#IGovernor-GovernorQueueNotImplemented-- +:xref-IGovernor-GovernorNotQueuedProposal-uint256-: xref:governance.adoc#IGovernor-GovernorNotQueuedProposal-uint256- +:xref-IGovernor-GovernorAlreadyQueuedProposal-uint256-: xref:governance.adoc#IGovernor-GovernorAlreadyQueuedProposal-uint256- +:xref-IGovernor-GovernorInvalidSignature-address-: xref:governance.adoc#IGovernor-GovernorInvalidSignature-address- +:xref-Nonces-InvalidAccountNonce-address-uint256-: xref:utils.adoc#Nonces-InvalidAccountNonce-address-uint256- +:IGovernor-COUNTING_MODE: pass:normal[xref:governance.adoc#IGovernor-COUNTING_MODE--[`IGovernor.COUNTING_MODE`]] +:IGovernor-hasVoted: pass:normal[xref:governance.adoc#IGovernor-hasVoted-uint256-address-[`IGovernor.hasVoted`]] +:Governor-_quorumReached: pass:normal[xref:governance.adoc#Governor-_quorumReached-uint256-[`Governor._quorumReached`]] +:Governor-_voteSucceeded: pass:normal[xref:governance.adoc#Governor-_voteSucceeded-uint256-[`Governor._voteSucceeded`]] +:Governor-_countVote: pass:normal[xref:governance.adoc#Governor-_countVote-uint256-address-uint8-uint256-bytes-[`Governor._countVote`]] +:Governor: pass:normal[xref:governance.adoc#Governor[`Governor`]] +:ERC20Votes: pass:normal[xref:token/ERC20.adoc#ERC20Votes[`ERC20Votes`]] +:ERC721Votes: pass:normal[xref:token/ERC721.adoc#ERC721Votes[`ERC721Votes`]] +:xref-GovernorVotes-constructor-contract-IVotes-: xref:governance.adoc#GovernorVotes-constructor-contract-IVotes- +:xref-GovernorVotes-token--: xref:governance.adoc#GovernorVotes-token-- +:xref-GovernorVotes-clock--: xref:governance.adoc#GovernorVotes-clock-- +:xref-GovernorVotes-CLOCK_MODE--: xref:governance.adoc#GovernorVotes-CLOCK_MODE-- +:xref-GovernorVotes-_getVotes-address-uint256-bytes-: xref:governance.adoc#GovernorVotes-_getVotes-address-uint256-bytes- +:xref-Governor-receive--: xref:governance.adoc#Governor-receive-- +:xref-Governor-supportsInterface-bytes4-: xref:governance.adoc#Governor-supportsInterface-bytes4- +:xref-Governor-name--: xref:governance.adoc#Governor-name-- +:xref-Governor-version--: xref:governance.adoc#Governor-version-- +:xref-Governor-hashProposal-address---uint256---bytes---bytes32-: xref:governance.adoc#Governor-hashProposal-address---uint256---bytes---bytes32- +:xref-Governor-state-uint256-: xref:governance.adoc#Governor-state-uint256- +:xref-Governor-proposalThreshold--: xref:governance.adoc#Governor-proposalThreshold-- +:xref-Governor-proposalSnapshot-uint256-: xref:governance.adoc#Governor-proposalSnapshot-uint256- +:xref-Governor-proposalDeadline-uint256-: xref:governance.adoc#Governor-proposalDeadline-uint256- +:xref-Governor-proposalProposer-uint256-: xref:governance.adoc#Governor-proposalProposer-uint256- +:xref-Governor-proposalEta-uint256-: xref:governance.adoc#Governor-proposalEta-uint256- +:xref-Governor-proposalNeedsQueuing-uint256-: xref:governance.adoc#Governor-proposalNeedsQueuing-uint256- +:xref-Governor-_checkGovernance--: xref:governance.adoc#Governor-_checkGovernance-- +:xref-Governor-_quorumReached-uint256-: xref:governance.adoc#Governor-_quorumReached-uint256- +:xref-Governor-_voteSucceeded-uint256-: xref:governance.adoc#Governor-_voteSucceeded-uint256- +:xref-Governor-_countVote-uint256-address-uint8-uint256-bytes-: xref:governance.adoc#Governor-_countVote-uint256-address-uint8-uint256-bytes- +:xref-Governor-_defaultParams--: xref:governance.adoc#Governor-_defaultParams-- +:xref-Governor-propose-address---uint256---bytes---string-: xref:governance.adoc#Governor-propose-address---uint256---bytes---string- +:xref-Governor-_propose-address---uint256---bytes---string-address-: xref:governance.adoc#Governor-_propose-address---uint256---bytes---string-address- +:xref-Governor-queue-address---uint256---bytes---bytes32-: xref:governance.adoc#Governor-queue-address---uint256---bytes---bytes32- +:xref-Governor-_queueOperations-uint256-address---uint256---bytes---bytes32-: xref:governance.adoc#Governor-_queueOperations-uint256-address---uint256---bytes---bytes32- +:xref-Governor-execute-address---uint256---bytes---bytes32-: xref:governance.adoc#Governor-execute-address---uint256---bytes---bytes32- +:xref-Governor-_executeOperations-uint256-address---uint256---bytes---bytes32-: xref:governance.adoc#Governor-_executeOperations-uint256-address---uint256---bytes---bytes32- +:xref-Governor-cancel-address---uint256---bytes---bytes32-: xref:governance.adoc#Governor-cancel-address---uint256---bytes---bytes32- +:xref-Governor-_cancel-address---uint256---bytes---bytes32-: xref:governance.adoc#Governor-_cancel-address---uint256---bytes---bytes32- +:xref-Governor-getVotes-address-uint256-: xref:governance.adoc#Governor-getVotes-address-uint256- +:xref-Governor-getVotesWithParams-address-uint256-bytes-: xref:governance.adoc#Governor-getVotesWithParams-address-uint256-bytes- +:xref-Governor-castVote-uint256-uint8-: xref:governance.adoc#Governor-castVote-uint256-uint8- +:xref-Governor-castVoteWithReason-uint256-uint8-string-: xref:governance.adoc#Governor-castVoteWithReason-uint256-uint8-string- +:xref-Governor-castVoteWithReasonAndParams-uint256-uint8-string-bytes-: xref:governance.adoc#Governor-castVoteWithReasonAndParams-uint256-uint8-string-bytes- +:xref-Governor-castVoteBySig-uint256-uint8-address-bytes-: xref:governance.adoc#Governor-castVoteBySig-uint256-uint8-address-bytes- +:xref-Governor-castVoteWithReasonAndParamsBySig-uint256-uint8-address-string-bytes-bytes-: xref:governance.adoc#Governor-castVoteWithReasonAndParamsBySig-uint256-uint8-address-string-bytes-bytes- +:xref-Governor-_castVote-uint256-address-uint8-string-: xref:governance.adoc#Governor-_castVote-uint256-address-uint8-string- +:xref-Governor-_castVote-uint256-address-uint8-string-bytes-: xref:governance.adoc#Governor-_castVote-uint256-address-uint8-string-bytes- +:xref-Governor-relay-address-uint256-bytes-: xref:governance.adoc#Governor-relay-address-uint256-bytes- +:xref-Governor-_executor--: xref:governance.adoc#Governor-_executor-- +:xref-Governor-onERC721Received-address-address-uint256-bytes-: xref:governance.adoc#Governor-onERC721Received-address-address-uint256-bytes- +:xref-Governor-onERC1155Received-address-address-uint256-uint256-bytes-: xref:governance.adoc#Governor-onERC1155Received-address-address-uint256-uint256-bytes- +:xref-Governor-onERC1155BatchReceived-address-address-uint256---uint256---bytes-: xref:governance.adoc#Governor-onERC1155BatchReceived-address-address-uint256---uint256---bytes- +:xref-Governor-_encodeStateBitmap-enum-IGovernor-ProposalState-: xref:governance.adoc#Governor-_encodeStateBitmap-enum-IGovernor-ProposalState- +:xref-Governor-_isValidDescriptionForProposer-address-string-: xref:governance.adoc#Governor-_isValidDescriptionForProposer-address-string- +:xref-Governor-votingDelay--: xref:governance.adoc#Governor-votingDelay-- +:xref-Governor-votingPeriod--: xref:governance.adoc#Governor-votingPeriod-- +:xref-Governor-quorum-uint256-: xref:governance.adoc#Governor-quorum-uint256- +:xref-IGovernor-COUNTING_MODE--: xref:governance.adoc#IGovernor-COUNTING_MODE-- +:xref-IGovernor-hasVoted-uint256-address-: xref:governance.adoc#IGovernor-hasVoted-uint256-address- +:xref-Nonces-nonces-address-: xref:utils.adoc#Nonces-nonces-address- +:xref-Nonces-_useNonce-address-: xref:utils.adoc#Nonces-_useNonce-address- +:xref-Nonces-_useCheckedNonce-address-uint256-: xref:utils.adoc#Nonces-_useCheckedNonce-address-uint256- +:xref-EIP712-_domainSeparatorV4--: xref:utils.adoc#EIP712-_domainSeparatorV4-- +:xref-EIP712-_hashTypedDataV4-bytes32-: xref:utils.adoc#EIP712-_hashTypedDataV4-bytes32- +:xref-EIP712-eip712Domain--: xref:utils.adoc#EIP712-eip712Domain-- +:xref-EIP712-_EIP712Name--: xref:utils.adoc#EIP712-_EIP712Name-- +:xref-EIP712-_EIP712Version--: xref:utils.adoc#EIP712-_EIP712Version-- +:xref-IGovernor-ProposalCreated-uint256-address-address---uint256---string---bytes---uint256-uint256-string-: xref:governance.adoc#IGovernor-ProposalCreated-uint256-address-address---uint256---string---bytes---uint256-uint256-string- +:xref-IGovernor-ProposalQueued-uint256-uint256-: xref:governance.adoc#IGovernor-ProposalQueued-uint256-uint256- +:xref-IGovernor-ProposalExecuted-uint256-: xref:governance.adoc#IGovernor-ProposalExecuted-uint256- +:xref-IGovernor-ProposalCanceled-uint256-: xref:governance.adoc#IGovernor-ProposalCanceled-uint256- +:xref-IGovernor-VoteCast-address-uint256-uint8-uint256-string-: xref:governance.adoc#IGovernor-VoteCast-address-uint256-uint8-uint256-string- +:xref-IGovernor-VoteCastWithParams-address-uint256-uint8-uint256-string-bytes-: xref:governance.adoc#IGovernor-VoteCastWithParams-address-uint256-uint8-uint256-string-bytes- +:xref-IERC5267-EIP712DomainChanged--: xref:interfaces.adoc#IERC5267-EIP712DomainChanged-- +:xref-IGovernor-GovernorInvalidProposalLength-uint256-uint256-uint256-: xref:governance.adoc#IGovernor-GovernorInvalidProposalLength-uint256-uint256-uint256- +:xref-IGovernor-GovernorAlreadyCastVote-address-: xref:governance.adoc#IGovernor-GovernorAlreadyCastVote-address- +:xref-IGovernor-GovernorDisabledDeposit--: xref:governance.adoc#IGovernor-GovernorDisabledDeposit-- +:xref-IGovernor-GovernorOnlyProposer-address-: xref:governance.adoc#IGovernor-GovernorOnlyProposer-address- +:xref-IGovernor-GovernorOnlyExecutor-address-: xref:governance.adoc#IGovernor-GovernorOnlyExecutor-address- +:xref-IGovernor-GovernorNonexistentProposal-uint256-: xref:governance.adoc#IGovernor-GovernorNonexistentProposal-uint256- +:xref-IGovernor-GovernorUnexpectedProposalState-uint256-enum-IGovernor-ProposalState-bytes32-: xref:governance.adoc#IGovernor-GovernorUnexpectedProposalState-uint256-enum-IGovernor-ProposalState-bytes32- +:xref-IGovernor-GovernorInvalidVotingPeriod-uint256-: xref:governance.adoc#IGovernor-GovernorInvalidVotingPeriod-uint256- +:xref-IGovernor-GovernorInsufficientProposerVotes-address-uint256-uint256-: xref:governance.adoc#IGovernor-GovernorInsufficientProposerVotes-address-uint256-uint256- +:xref-IGovernor-GovernorRestrictedProposer-address-: xref:governance.adoc#IGovernor-GovernorRestrictedProposer-address- +:xref-IGovernor-GovernorInvalidVoteType--: xref:governance.adoc#IGovernor-GovernorInvalidVoteType-- +:xref-IGovernor-GovernorQueueNotImplemented--: xref:governance.adoc#IGovernor-GovernorQueueNotImplemented-- +:xref-IGovernor-GovernorNotQueuedProposal-uint256-: xref:governance.adoc#IGovernor-GovernorNotQueuedProposal-uint256- +:xref-IGovernor-GovernorAlreadyQueuedProposal-uint256-: xref:governance.adoc#IGovernor-GovernorAlreadyQueuedProposal-uint256- +:xref-IGovernor-GovernorInvalidSignature-address-: xref:governance.adoc#IGovernor-GovernorInvalidSignature-address- +:xref-Nonces-InvalidAccountNonce-address-uint256-: xref:utils.adoc#Nonces-InvalidAccountNonce-address-uint256- +:Governor: pass:normal[xref:governance.adoc#Governor[`Governor`]] +:ERC20Votes: pass:normal[xref:token/ERC20.adoc#ERC20Votes[`ERC20Votes`]] +:xref-GovernorVotesQuorumFraction-constructor-uint256-: xref:governance.adoc#GovernorVotesQuorumFraction-constructor-uint256- +:xref-GovernorVotesQuorumFraction-quorumNumerator--: xref:governance.adoc#GovernorVotesQuorumFraction-quorumNumerator-- +:xref-GovernorVotesQuorumFraction-quorumNumerator-uint256-: xref:governance.adoc#GovernorVotesQuorumFraction-quorumNumerator-uint256- +:xref-GovernorVotesQuorumFraction-quorumDenominator--: xref:governance.adoc#GovernorVotesQuorumFraction-quorumDenominator-- +:xref-GovernorVotesQuorumFraction-quorum-uint256-: xref:governance.adoc#GovernorVotesQuorumFraction-quorum-uint256- +:xref-GovernorVotesQuorumFraction-updateQuorumNumerator-uint256-: xref:governance.adoc#GovernorVotesQuorumFraction-updateQuorumNumerator-uint256- +:xref-GovernorVotesQuorumFraction-_updateQuorumNumerator-uint256-: xref:governance.adoc#GovernorVotesQuorumFraction-_updateQuorumNumerator-uint256- +:xref-GovernorVotes-token--: xref:governance.adoc#GovernorVotes-token-- +:xref-GovernorVotes-clock--: xref:governance.adoc#GovernorVotes-clock-- +:xref-GovernorVotes-CLOCK_MODE--: xref:governance.adoc#GovernorVotes-CLOCK_MODE-- +:xref-GovernorVotes-_getVotes-address-uint256-bytes-: xref:governance.adoc#GovernorVotes-_getVotes-address-uint256-bytes- +:xref-Governor-receive--: xref:governance.adoc#Governor-receive-- +:xref-Governor-supportsInterface-bytes4-: xref:governance.adoc#Governor-supportsInterface-bytes4- +:xref-Governor-name--: xref:governance.adoc#Governor-name-- +:xref-Governor-version--: xref:governance.adoc#Governor-version-- +:xref-Governor-hashProposal-address---uint256---bytes---bytes32-: xref:governance.adoc#Governor-hashProposal-address---uint256---bytes---bytes32- +:xref-Governor-state-uint256-: xref:governance.adoc#Governor-state-uint256- +:xref-Governor-proposalThreshold--: xref:governance.adoc#Governor-proposalThreshold-- +:xref-Governor-proposalSnapshot-uint256-: xref:governance.adoc#Governor-proposalSnapshot-uint256- +:xref-Governor-proposalDeadline-uint256-: xref:governance.adoc#Governor-proposalDeadline-uint256- +:xref-Governor-proposalProposer-uint256-: xref:governance.adoc#Governor-proposalProposer-uint256- +:xref-Governor-proposalEta-uint256-: xref:governance.adoc#Governor-proposalEta-uint256- +:xref-Governor-proposalNeedsQueuing-uint256-: xref:governance.adoc#Governor-proposalNeedsQueuing-uint256- +:xref-Governor-_checkGovernance--: xref:governance.adoc#Governor-_checkGovernance-- +:xref-Governor-_quorumReached-uint256-: xref:governance.adoc#Governor-_quorumReached-uint256- +:xref-Governor-_voteSucceeded-uint256-: xref:governance.adoc#Governor-_voteSucceeded-uint256- +:xref-Governor-_countVote-uint256-address-uint8-uint256-bytes-: xref:governance.adoc#Governor-_countVote-uint256-address-uint8-uint256-bytes- +:xref-Governor-_defaultParams--: xref:governance.adoc#Governor-_defaultParams-- +:xref-Governor-propose-address---uint256---bytes---string-: xref:governance.adoc#Governor-propose-address---uint256---bytes---string- +:xref-Governor-_propose-address---uint256---bytes---string-address-: xref:governance.adoc#Governor-_propose-address---uint256---bytes---string-address- +:xref-Governor-queue-address---uint256---bytes---bytes32-: xref:governance.adoc#Governor-queue-address---uint256---bytes---bytes32- +:xref-Governor-_queueOperations-uint256-address---uint256---bytes---bytes32-: xref:governance.adoc#Governor-_queueOperations-uint256-address---uint256---bytes---bytes32- +:xref-Governor-execute-address---uint256---bytes---bytes32-: xref:governance.adoc#Governor-execute-address---uint256---bytes---bytes32- +:xref-Governor-_executeOperations-uint256-address---uint256---bytes---bytes32-: xref:governance.adoc#Governor-_executeOperations-uint256-address---uint256---bytes---bytes32- +:xref-Governor-cancel-address---uint256---bytes---bytes32-: xref:governance.adoc#Governor-cancel-address---uint256---bytes---bytes32- +:xref-Governor-_cancel-address---uint256---bytes---bytes32-: xref:governance.adoc#Governor-_cancel-address---uint256---bytes---bytes32- +:xref-Governor-getVotes-address-uint256-: xref:governance.adoc#Governor-getVotes-address-uint256- +:xref-Governor-getVotesWithParams-address-uint256-bytes-: xref:governance.adoc#Governor-getVotesWithParams-address-uint256-bytes- +:xref-Governor-castVote-uint256-uint8-: xref:governance.adoc#Governor-castVote-uint256-uint8- +:xref-Governor-castVoteWithReason-uint256-uint8-string-: xref:governance.adoc#Governor-castVoteWithReason-uint256-uint8-string- +:xref-Governor-castVoteWithReasonAndParams-uint256-uint8-string-bytes-: xref:governance.adoc#Governor-castVoteWithReasonAndParams-uint256-uint8-string-bytes- +:xref-Governor-castVoteBySig-uint256-uint8-address-bytes-: xref:governance.adoc#Governor-castVoteBySig-uint256-uint8-address-bytes- +:xref-Governor-castVoteWithReasonAndParamsBySig-uint256-uint8-address-string-bytes-bytes-: xref:governance.adoc#Governor-castVoteWithReasonAndParamsBySig-uint256-uint8-address-string-bytes-bytes- +:xref-Governor-_castVote-uint256-address-uint8-string-: xref:governance.adoc#Governor-_castVote-uint256-address-uint8-string- +:xref-Governor-_castVote-uint256-address-uint8-string-bytes-: xref:governance.adoc#Governor-_castVote-uint256-address-uint8-string-bytes- +:xref-Governor-relay-address-uint256-bytes-: xref:governance.adoc#Governor-relay-address-uint256-bytes- +:xref-Governor-_executor--: xref:governance.adoc#Governor-_executor-- +:xref-Governor-onERC721Received-address-address-uint256-bytes-: xref:governance.adoc#Governor-onERC721Received-address-address-uint256-bytes- +:xref-Governor-onERC1155Received-address-address-uint256-uint256-bytes-: xref:governance.adoc#Governor-onERC1155Received-address-address-uint256-uint256-bytes- +:xref-Governor-onERC1155BatchReceived-address-address-uint256---uint256---bytes-: xref:governance.adoc#Governor-onERC1155BatchReceived-address-address-uint256---uint256---bytes- +:xref-Governor-_encodeStateBitmap-enum-IGovernor-ProposalState-: xref:governance.adoc#Governor-_encodeStateBitmap-enum-IGovernor-ProposalState- +:xref-Governor-_isValidDescriptionForProposer-address-string-: xref:governance.adoc#Governor-_isValidDescriptionForProposer-address-string- +:xref-Governor-votingDelay--: xref:governance.adoc#Governor-votingDelay-- +:xref-Governor-votingPeriod--: xref:governance.adoc#Governor-votingPeriod-- +:xref-IGovernor-COUNTING_MODE--: xref:governance.adoc#IGovernor-COUNTING_MODE-- +:xref-IGovernor-hasVoted-uint256-address-: xref:governance.adoc#IGovernor-hasVoted-uint256-address- +:xref-Nonces-nonces-address-: xref:utils.adoc#Nonces-nonces-address- +:xref-Nonces-_useNonce-address-: xref:utils.adoc#Nonces-_useNonce-address- +:xref-Nonces-_useCheckedNonce-address-uint256-: xref:utils.adoc#Nonces-_useCheckedNonce-address-uint256- +:xref-EIP712-_domainSeparatorV4--: xref:utils.adoc#EIP712-_domainSeparatorV4-- +:xref-EIP712-_hashTypedDataV4-bytes32-: xref:utils.adoc#EIP712-_hashTypedDataV4-bytes32- +:xref-EIP712-eip712Domain--: xref:utils.adoc#EIP712-eip712Domain-- +:xref-EIP712-_EIP712Name--: xref:utils.adoc#EIP712-_EIP712Name-- +:xref-EIP712-_EIP712Version--: xref:utils.adoc#EIP712-_EIP712Version-- +:xref-GovernorVotesQuorumFraction-QuorumNumeratorUpdated-uint256-uint256-: xref:governance.adoc#GovernorVotesQuorumFraction-QuorumNumeratorUpdated-uint256-uint256- +:xref-IGovernor-ProposalCreated-uint256-address-address---uint256---string---bytes---uint256-uint256-string-: xref:governance.adoc#IGovernor-ProposalCreated-uint256-address-address---uint256---string---bytes---uint256-uint256-string- +:xref-IGovernor-ProposalQueued-uint256-uint256-: xref:governance.adoc#IGovernor-ProposalQueued-uint256-uint256- +:xref-IGovernor-ProposalExecuted-uint256-: xref:governance.adoc#IGovernor-ProposalExecuted-uint256- +:xref-IGovernor-ProposalCanceled-uint256-: xref:governance.adoc#IGovernor-ProposalCanceled-uint256- +:xref-IGovernor-VoteCast-address-uint256-uint8-uint256-string-: xref:governance.adoc#IGovernor-VoteCast-address-uint256-uint8-uint256-string- +:xref-IGovernor-VoteCastWithParams-address-uint256-uint8-uint256-string-bytes-: xref:governance.adoc#IGovernor-VoteCastWithParams-address-uint256-uint8-uint256-string-bytes- +:xref-IERC5267-EIP712DomainChanged--: xref:interfaces.adoc#IERC5267-EIP712DomainChanged-- +:xref-GovernorVotesQuorumFraction-GovernorInvalidQuorumFraction-uint256-uint256-: xref:governance.adoc#GovernorVotesQuorumFraction-GovernorInvalidQuorumFraction-uint256-uint256- +:xref-IGovernor-GovernorInvalidProposalLength-uint256-uint256-uint256-: xref:governance.adoc#IGovernor-GovernorInvalidProposalLength-uint256-uint256-uint256- +:xref-IGovernor-GovernorAlreadyCastVote-address-: xref:governance.adoc#IGovernor-GovernorAlreadyCastVote-address- +:xref-IGovernor-GovernorDisabledDeposit--: xref:governance.adoc#IGovernor-GovernorDisabledDeposit-- +:xref-IGovernor-GovernorOnlyProposer-address-: xref:governance.adoc#IGovernor-GovernorOnlyProposer-address- +:xref-IGovernor-GovernorOnlyExecutor-address-: xref:governance.adoc#IGovernor-GovernorOnlyExecutor-address- +:xref-IGovernor-GovernorNonexistentProposal-uint256-: xref:governance.adoc#IGovernor-GovernorNonexistentProposal-uint256- +:xref-IGovernor-GovernorUnexpectedProposalState-uint256-enum-IGovernor-ProposalState-bytes32-: xref:governance.adoc#IGovernor-GovernorUnexpectedProposalState-uint256-enum-IGovernor-ProposalState-bytes32- +:xref-IGovernor-GovernorInvalidVotingPeriod-uint256-: xref:governance.adoc#IGovernor-GovernorInvalidVotingPeriod-uint256- +:xref-IGovernor-GovernorInsufficientProposerVotes-address-uint256-uint256-: xref:governance.adoc#IGovernor-GovernorInsufficientProposerVotes-address-uint256-uint256- +:xref-IGovernor-GovernorRestrictedProposer-address-: xref:governance.adoc#IGovernor-GovernorRestrictedProposer-address- +:xref-IGovernor-GovernorInvalidVoteType--: xref:governance.adoc#IGovernor-GovernorInvalidVoteType-- +:xref-IGovernor-GovernorQueueNotImplemented--: xref:governance.adoc#IGovernor-GovernorQueueNotImplemented-- +:xref-IGovernor-GovernorNotQueuedProposal-uint256-: xref:governance.adoc#IGovernor-GovernorNotQueuedProposal-uint256- +:xref-IGovernor-GovernorAlreadyQueuedProposal-uint256-: xref:governance.adoc#IGovernor-GovernorAlreadyQueuedProposal-uint256- +:xref-IGovernor-GovernorInvalidSignature-address-: xref:governance.adoc#IGovernor-GovernorInvalidSignature-address- +:xref-Nonces-InvalidAccountNonce-address-uint256-: xref:utils.adoc#Nonces-InvalidAccountNonce-address-uint256- +:Governor: pass:normal[xref:governance.adoc#Governor[`Governor`]] +:TimelockController: pass:normal[xref:governance.adoc#TimelockController[`TimelockController`]] +:TimelockController: pass:normal[xref:governance.adoc#TimelockController[`TimelockController`]] +:Governor: pass:normal[xref:governance.adoc#Governor[`Governor`]] +:Governor: pass:normal[xref:governance.adoc#Governor[`Governor`]] +:TimelockController: pass:normal[xref:governance.adoc#TimelockController[`TimelockController`]] +:Governor: pass:normal[xref:governance.adoc#Governor[`Governor`]] +:TimelockController: pass:normal[xref:governance.adoc#TimelockController[`TimelockController`]] +:Governor: pass:normal[xref:governance.adoc#Governor[`Governor`]] +:Governor-relay: pass:normal[xref:governance.adoc#Governor-relay-address-uint256-bytes-[`Governor.relay`]] +:AccessManager-schedule: pass:normal[xref:access.adoc#AccessManager-schedule-address-bytes-uint48-[`AccessManager.schedule`]] +:xref-GovernorTimelockControl-constructor-contract-TimelockController-: xref:governance.adoc#GovernorTimelockControl-constructor-contract-TimelockController- +:xref-GovernorTimelockControl-state-uint256-: xref:governance.adoc#GovernorTimelockControl-state-uint256- +:xref-GovernorTimelockControl-timelock--: xref:governance.adoc#GovernorTimelockControl-timelock-- +:xref-GovernorTimelockControl-proposalNeedsQueuing-uint256-: xref:governance.adoc#GovernorTimelockControl-proposalNeedsQueuing-uint256- +:xref-GovernorTimelockControl-_queueOperations-uint256-address---uint256---bytes---bytes32-: xref:governance.adoc#GovernorTimelockControl-_queueOperations-uint256-address---uint256---bytes---bytes32- +:xref-GovernorTimelockControl-_executeOperations-uint256-address---uint256---bytes---bytes32-: xref:governance.adoc#GovernorTimelockControl-_executeOperations-uint256-address---uint256---bytes---bytes32- +:xref-GovernorTimelockControl-_cancel-address---uint256---bytes---bytes32-: xref:governance.adoc#GovernorTimelockControl-_cancel-address---uint256---bytes---bytes32- +:xref-GovernorTimelockControl-_executor--: xref:governance.adoc#GovernorTimelockControl-_executor-- +:xref-GovernorTimelockControl-updateTimelock-contract-TimelockController-: xref:governance.adoc#GovernorTimelockControl-updateTimelock-contract-TimelockController- +:xref-Governor-receive--: xref:governance.adoc#Governor-receive-- +:xref-Governor-supportsInterface-bytes4-: xref:governance.adoc#Governor-supportsInterface-bytes4- +:xref-Governor-name--: xref:governance.adoc#Governor-name-- +:xref-Governor-version--: xref:governance.adoc#Governor-version-- +:xref-Governor-hashProposal-address---uint256---bytes---bytes32-: xref:governance.adoc#Governor-hashProposal-address---uint256---bytes---bytes32- +:xref-Governor-proposalThreshold--: xref:governance.adoc#Governor-proposalThreshold-- +:xref-Governor-proposalSnapshot-uint256-: xref:governance.adoc#Governor-proposalSnapshot-uint256- +:xref-Governor-proposalDeadline-uint256-: xref:governance.adoc#Governor-proposalDeadline-uint256- +:xref-Governor-proposalProposer-uint256-: xref:governance.adoc#Governor-proposalProposer-uint256- +:xref-Governor-proposalEta-uint256-: xref:governance.adoc#Governor-proposalEta-uint256- +:xref-Governor-_checkGovernance--: xref:governance.adoc#Governor-_checkGovernance-- +:xref-Governor-_quorumReached-uint256-: xref:governance.adoc#Governor-_quorumReached-uint256- +:xref-Governor-_voteSucceeded-uint256-: xref:governance.adoc#Governor-_voteSucceeded-uint256- +:xref-Governor-_getVotes-address-uint256-bytes-: xref:governance.adoc#Governor-_getVotes-address-uint256-bytes- +:xref-Governor-_countVote-uint256-address-uint8-uint256-bytes-: xref:governance.adoc#Governor-_countVote-uint256-address-uint8-uint256-bytes- +:xref-Governor-_defaultParams--: xref:governance.adoc#Governor-_defaultParams-- +:xref-Governor-propose-address---uint256---bytes---string-: xref:governance.adoc#Governor-propose-address---uint256---bytes---string- +:xref-Governor-_propose-address---uint256---bytes---string-address-: xref:governance.adoc#Governor-_propose-address---uint256---bytes---string-address- +:xref-Governor-queue-address---uint256---bytes---bytes32-: xref:governance.adoc#Governor-queue-address---uint256---bytes---bytes32- +:xref-Governor-execute-address---uint256---bytes---bytes32-: xref:governance.adoc#Governor-execute-address---uint256---bytes---bytes32- +:xref-Governor-cancel-address---uint256---bytes---bytes32-: xref:governance.adoc#Governor-cancel-address---uint256---bytes---bytes32- +:xref-Governor-getVotes-address-uint256-: xref:governance.adoc#Governor-getVotes-address-uint256- +:xref-Governor-getVotesWithParams-address-uint256-bytes-: xref:governance.adoc#Governor-getVotesWithParams-address-uint256-bytes- +:xref-Governor-castVote-uint256-uint8-: xref:governance.adoc#Governor-castVote-uint256-uint8- +:xref-Governor-castVoteWithReason-uint256-uint8-string-: xref:governance.adoc#Governor-castVoteWithReason-uint256-uint8-string- +:xref-Governor-castVoteWithReasonAndParams-uint256-uint8-string-bytes-: xref:governance.adoc#Governor-castVoteWithReasonAndParams-uint256-uint8-string-bytes- +:xref-Governor-castVoteBySig-uint256-uint8-address-bytes-: xref:governance.adoc#Governor-castVoteBySig-uint256-uint8-address-bytes- +:xref-Governor-castVoteWithReasonAndParamsBySig-uint256-uint8-address-string-bytes-bytes-: xref:governance.adoc#Governor-castVoteWithReasonAndParamsBySig-uint256-uint8-address-string-bytes-bytes- +:xref-Governor-_castVote-uint256-address-uint8-string-: xref:governance.adoc#Governor-_castVote-uint256-address-uint8-string- +:xref-Governor-_castVote-uint256-address-uint8-string-bytes-: xref:governance.adoc#Governor-_castVote-uint256-address-uint8-string-bytes- +:xref-Governor-relay-address-uint256-bytes-: xref:governance.adoc#Governor-relay-address-uint256-bytes- +:xref-Governor-onERC721Received-address-address-uint256-bytes-: xref:governance.adoc#Governor-onERC721Received-address-address-uint256-bytes- +:xref-Governor-onERC1155Received-address-address-uint256-uint256-bytes-: xref:governance.adoc#Governor-onERC1155Received-address-address-uint256-uint256-bytes- +:xref-Governor-onERC1155BatchReceived-address-address-uint256---uint256---bytes-: xref:governance.adoc#Governor-onERC1155BatchReceived-address-address-uint256---uint256---bytes- +:xref-Governor-_encodeStateBitmap-enum-IGovernor-ProposalState-: xref:governance.adoc#Governor-_encodeStateBitmap-enum-IGovernor-ProposalState- +:xref-Governor-_isValidDescriptionForProposer-address-string-: xref:governance.adoc#Governor-_isValidDescriptionForProposer-address-string- +:xref-Governor-clock--: xref:governance.adoc#Governor-clock-- +:xref-Governor-CLOCK_MODE--: xref:governance.adoc#Governor-CLOCK_MODE-- +:xref-Governor-votingDelay--: xref:governance.adoc#Governor-votingDelay-- +:xref-Governor-votingPeriod--: xref:governance.adoc#Governor-votingPeriod-- +:xref-Governor-quorum-uint256-: xref:governance.adoc#Governor-quorum-uint256- +:xref-IGovernor-COUNTING_MODE--: xref:governance.adoc#IGovernor-COUNTING_MODE-- +:xref-IGovernor-hasVoted-uint256-address-: xref:governance.adoc#IGovernor-hasVoted-uint256-address- +:xref-Nonces-nonces-address-: xref:utils.adoc#Nonces-nonces-address- +:xref-Nonces-_useNonce-address-: xref:utils.adoc#Nonces-_useNonce-address- +:xref-Nonces-_useCheckedNonce-address-uint256-: xref:utils.adoc#Nonces-_useCheckedNonce-address-uint256- +:xref-EIP712-_domainSeparatorV4--: xref:utils.adoc#EIP712-_domainSeparatorV4-- +:xref-EIP712-_hashTypedDataV4-bytes32-: xref:utils.adoc#EIP712-_hashTypedDataV4-bytes32- +:xref-EIP712-eip712Domain--: xref:utils.adoc#EIP712-eip712Domain-- +:xref-EIP712-_EIP712Name--: xref:utils.adoc#EIP712-_EIP712Name-- +:xref-EIP712-_EIP712Version--: xref:utils.adoc#EIP712-_EIP712Version-- +:xref-GovernorTimelockControl-TimelockChange-address-address-: xref:governance.adoc#GovernorTimelockControl-TimelockChange-address-address- +:xref-IGovernor-ProposalCreated-uint256-address-address---uint256---string---bytes---uint256-uint256-string-: xref:governance.adoc#IGovernor-ProposalCreated-uint256-address-address---uint256---string---bytes---uint256-uint256-string- +:xref-IGovernor-ProposalQueued-uint256-uint256-: xref:governance.adoc#IGovernor-ProposalQueued-uint256-uint256- +:xref-IGovernor-ProposalExecuted-uint256-: xref:governance.adoc#IGovernor-ProposalExecuted-uint256- +:xref-IGovernor-ProposalCanceled-uint256-: xref:governance.adoc#IGovernor-ProposalCanceled-uint256- +:xref-IGovernor-VoteCast-address-uint256-uint8-uint256-string-: xref:governance.adoc#IGovernor-VoteCast-address-uint256-uint8-uint256-string- +:xref-IGovernor-VoteCastWithParams-address-uint256-uint8-uint256-string-bytes-: xref:governance.adoc#IGovernor-VoteCastWithParams-address-uint256-uint8-uint256-string-bytes- +:xref-IERC5267-EIP712DomainChanged--: xref:interfaces.adoc#IERC5267-EIP712DomainChanged-- +:xref-IGovernor-GovernorInvalidProposalLength-uint256-uint256-uint256-: xref:governance.adoc#IGovernor-GovernorInvalidProposalLength-uint256-uint256-uint256- +:xref-IGovernor-GovernorAlreadyCastVote-address-: xref:governance.adoc#IGovernor-GovernorAlreadyCastVote-address- +:xref-IGovernor-GovernorDisabledDeposit--: xref:governance.adoc#IGovernor-GovernorDisabledDeposit-- +:xref-IGovernor-GovernorOnlyProposer-address-: xref:governance.adoc#IGovernor-GovernorOnlyProposer-address- +:xref-IGovernor-GovernorOnlyExecutor-address-: xref:governance.adoc#IGovernor-GovernorOnlyExecutor-address- +:xref-IGovernor-GovernorNonexistentProposal-uint256-: xref:governance.adoc#IGovernor-GovernorNonexistentProposal-uint256- +:xref-IGovernor-GovernorUnexpectedProposalState-uint256-enum-IGovernor-ProposalState-bytes32-: xref:governance.adoc#IGovernor-GovernorUnexpectedProposalState-uint256-enum-IGovernor-ProposalState-bytes32- +:xref-IGovernor-GovernorInvalidVotingPeriod-uint256-: xref:governance.adoc#IGovernor-GovernorInvalidVotingPeriod-uint256- +:xref-IGovernor-GovernorInsufficientProposerVotes-address-uint256-uint256-: xref:governance.adoc#IGovernor-GovernorInsufficientProposerVotes-address-uint256-uint256- +:xref-IGovernor-GovernorRestrictedProposer-address-: xref:governance.adoc#IGovernor-GovernorRestrictedProposer-address- +:xref-IGovernor-GovernorInvalidVoteType--: xref:governance.adoc#IGovernor-GovernorInvalidVoteType-- +:xref-IGovernor-GovernorQueueNotImplemented--: xref:governance.adoc#IGovernor-GovernorQueueNotImplemented-- +:xref-IGovernor-GovernorNotQueuedProposal-uint256-: xref:governance.adoc#IGovernor-GovernorNotQueuedProposal-uint256- +:xref-IGovernor-GovernorAlreadyQueuedProposal-uint256-: xref:governance.adoc#IGovernor-GovernorAlreadyQueuedProposal-uint256- +:xref-IGovernor-GovernorInvalidSignature-address-: xref:governance.adoc#IGovernor-GovernorInvalidSignature-address- +:xref-Nonces-InvalidAccountNonce-address-uint256-: xref:utils.adoc#Nonces-InvalidAccountNonce-address-uint256- +:Governor-state: pass:normal[xref:governance.adoc#Governor-state-uint256-[`Governor.state`]] +:IGovernor-proposalNeedsQueuing: pass:normal[xref:governance.adoc#IGovernor-proposalNeedsQueuing-uint256-[`IGovernor.proposalNeedsQueuing`]] +:Governor-_executeOperations: pass:normal[xref:governance.adoc#Governor-_executeOperations-uint256-address---uint256---bytes---bytes32-[`Governor._executeOperations`]] +:Governor-_cancel: pass:normal[xref:governance.adoc#Governor-_cancel-address---uint256---bytes---bytes32-[`Governor._cancel`]] +:Governor: pass:normal[xref:governance.adoc#Governor[`Governor`]] +:Governor: pass:normal[xref:governance.adoc#Governor[`Governor`]] +:GovernorTimelockCompound-__acceptAdmin: pass:normal[xref:governance.adoc#GovernorTimelockCompound-__acceptAdmin--[`GovernorTimelockCompound.__acceptAdmin`]] +:TimelockController: pass:normal[xref:governance.adoc#TimelockController[`TimelockController`]] +:Governor: pass:normal[xref:governance.adoc#Governor[`Governor`]] +:TimelockController: pass:normal[xref:governance.adoc#TimelockController[`TimelockController`]] +:Governor: pass:normal[xref:governance.adoc#Governor[`Governor`]] +:xref-GovernorTimelockCompound-constructor-contract-ICompoundTimelock-: xref:governance.adoc#GovernorTimelockCompound-constructor-contract-ICompoundTimelock- +:xref-GovernorTimelockCompound-state-uint256-: xref:governance.adoc#GovernorTimelockCompound-state-uint256- +:xref-GovernorTimelockCompound-timelock--: xref:governance.adoc#GovernorTimelockCompound-timelock-- +:xref-GovernorTimelockCompound-proposalNeedsQueuing-uint256-: xref:governance.adoc#GovernorTimelockCompound-proposalNeedsQueuing-uint256- +:xref-GovernorTimelockCompound-_queueOperations-uint256-address---uint256---bytes---bytes32-: xref:governance.adoc#GovernorTimelockCompound-_queueOperations-uint256-address---uint256---bytes---bytes32- +:xref-GovernorTimelockCompound-_executeOperations-uint256-address---uint256---bytes---bytes32-: xref:governance.adoc#GovernorTimelockCompound-_executeOperations-uint256-address---uint256---bytes---bytes32- +:xref-GovernorTimelockCompound-_cancel-address---uint256---bytes---bytes32-: xref:governance.adoc#GovernorTimelockCompound-_cancel-address---uint256---bytes---bytes32- +:xref-GovernorTimelockCompound-_executor--: xref:governance.adoc#GovernorTimelockCompound-_executor-- +:xref-GovernorTimelockCompound-__acceptAdmin--: xref:governance.adoc#GovernorTimelockCompound-__acceptAdmin-- +:xref-GovernorTimelockCompound-updateTimelock-contract-ICompoundTimelock-: xref:governance.adoc#GovernorTimelockCompound-updateTimelock-contract-ICompoundTimelock- +:xref-Governor-receive--: xref:governance.adoc#Governor-receive-- +:xref-Governor-supportsInterface-bytes4-: xref:governance.adoc#Governor-supportsInterface-bytes4- +:xref-Governor-name--: xref:governance.adoc#Governor-name-- +:xref-Governor-version--: xref:governance.adoc#Governor-version-- +:xref-Governor-hashProposal-address---uint256---bytes---bytes32-: xref:governance.adoc#Governor-hashProposal-address---uint256---bytes---bytes32- +:xref-Governor-proposalThreshold--: xref:governance.adoc#Governor-proposalThreshold-- +:xref-Governor-proposalSnapshot-uint256-: xref:governance.adoc#Governor-proposalSnapshot-uint256- +:xref-Governor-proposalDeadline-uint256-: xref:governance.adoc#Governor-proposalDeadline-uint256- +:xref-Governor-proposalProposer-uint256-: xref:governance.adoc#Governor-proposalProposer-uint256- +:xref-Governor-proposalEta-uint256-: xref:governance.adoc#Governor-proposalEta-uint256- +:xref-Governor-_checkGovernance--: xref:governance.adoc#Governor-_checkGovernance-- +:xref-Governor-_quorumReached-uint256-: xref:governance.adoc#Governor-_quorumReached-uint256- +:xref-Governor-_voteSucceeded-uint256-: xref:governance.adoc#Governor-_voteSucceeded-uint256- +:xref-Governor-_getVotes-address-uint256-bytes-: xref:governance.adoc#Governor-_getVotes-address-uint256-bytes- +:xref-Governor-_countVote-uint256-address-uint8-uint256-bytes-: xref:governance.adoc#Governor-_countVote-uint256-address-uint8-uint256-bytes- +:xref-Governor-_defaultParams--: xref:governance.adoc#Governor-_defaultParams-- +:xref-Governor-propose-address---uint256---bytes---string-: xref:governance.adoc#Governor-propose-address---uint256---bytes---string- +:xref-Governor-_propose-address---uint256---bytes---string-address-: xref:governance.adoc#Governor-_propose-address---uint256---bytes---string-address- +:xref-Governor-queue-address---uint256---bytes---bytes32-: xref:governance.adoc#Governor-queue-address---uint256---bytes---bytes32- +:xref-Governor-execute-address---uint256---bytes---bytes32-: xref:governance.adoc#Governor-execute-address---uint256---bytes---bytes32- +:xref-Governor-cancel-address---uint256---bytes---bytes32-: xref:governance.adoc#Governor-cancel-address---uint256---bytes---bytes32- +:xref-Governor-getVotes-address-uint256-: xref:governance.adoc#Governor-getVotes-address-uint256- +:xref-Governor-getVotesWithParams-address-uint256-bytes-: xref:governance.adoc#Governor-getVotesWithParams-address-uint256-bytes- +:xref-Governor-castVote-uint256-uint8-: xref:governance.adoc#Governor-castVote-uint256-uint8- +:xref-Governor-castVoteWithReason-uint256-uint8-string-: xref:governance.adoc#Governor-castVoteWithReason-uint256-uint8-string- +:xref-Governor-castVoteWithReasonAndParams-uint256-uint8-string-bytes-: xref:governance.adoc#Governor-castVoteWithReasonAndParams-uint256-uint8-string-bytes- +:xref-Governor-castVoteBySig-uint256-uint8-address-bytes-: xref:governance.adoc#Governor-castVoteBySig-uint256-uint8-address-bytes- +:xref-Governor-castVoteWithReasonAndParamsBySig-uint256-uint8-address-string-bytes-bytes-: xref:governance.adoc#Governor-castVoteWithReasonAndParamsBySig-uint256-uint8-address-string-bytes-bytes- +:xref-Governor-_castVote-uint256-address-uint8-string-: xref:governance.adoc#Governor-_castVote-uint256-address-uint8-string- +:xref-Governor-_castVote-uint256-address-uint8-string-bytes-: xref:governance.adoc#Governor-_castVote-uint256-address-uint8-string-bytes- +:xref-Governor-relay-address-uint256-bytes-: xref:governance.adoc#Governor-relay-address-uint256-bytes- +:xref-Governor-onERC721Received-address-address-uint256-bytes-: xref:governance.adoc#Governor-onERC721Received-address-address-uint256-bytes- +:xref-Governor-onERC1155Received-address-address-uint256-uint256-bytes-: xref:governance.adoc#Governor-onERC1155Received-address-address-uint256-uint256-bytes- +:xref-Governor-onERC1155BatchReceived-address-address-uint256---uint256---bytes-: xref:governance.adoc#Governor-onERC1155BatchReceived-address-address-uint256---uint256---bytes- +:xref-Governor-_encodeStateBitmap-enum-IGovernor-ProposalState-: xref:governance.adoc#Governor-_encodeStateBitmap-enum-IGovernor-ProposalState- +:xref-Governor-_isValidDescriptionForProposer-address-string-: xref:governance.adoc#Governor-_isValidDescriptionForProposer-address-string- +:xref-Governor-clock--: xref:governance.adoc#Governor-clock-- +:xref-Governor-CLOCK_MODE--: xref:governance.adoc#Governor-CLOCK_MODE-- +:xref-Governor-votingDelay--: xref:governance.adoc#Governor-votingDelay-- +:xref-Governor-votingPeriod--: xref:governance.adoc#Governor-votingPeriod-- +:xref-Governor-quorum-uint256-: xref:governance.adoc#Governor-quorum-uint256- +:xref-IGovernor-COUNTING_MODE--: xref:governance.adoc#IGovernor-COUNTING_MODE-- +:xref-IGovernor-hasVoted-uint256-address-: xref:governance.adoc#IGovernor-hasVoted-uint256-address- +:xref-Nonces-nonces-address-: xref:utils.adoc#Nonces-nonces-address- +:xref-Nonces-_useNonce-address-: xref:utils.adoc#Nonces-_useNonce-address- +:xref-Nonces-_useCheckedNonce-address-uint256-: xref:utils.adoc#Nonces-_useCheckedNonce-address-uint256- +:xref-EIP712-_domainSeparatorV4--: xref:utils.adoc#EIP712-_domainSeparatorV4-- +:xref-EIP712-_hashTypedDataV4-bytes32-: xref:utils.adoc#EIP712-_hashTypedDataV4-bytes32- +:xref-EIP712-eip712Domain--: xref:utils.adoc#EIP712-eip712Domain-- +:xref-EIP712-_EIP712Name--: xref:utils.adoc#EIP712-_EIP712Name-- +:xref-EIP712-_EIP712Version--: xref:utils.adoc#EIP712-_EIP712Version-- +:xref-GovernorTimelockCompound-TimelockChange-address-address-: xref:governance.adoc#GovernorTimelockCompound-TimelockChange-address-address- +:xref-IGovernor-ProposalCreated-uint256-address-address---uint256---string---bytes---uint256-uint256-string-: xref:governance.adoc#IGovernor-ProposalCreated-uint256-address-address---uint256---string---bytes---uint256-uint256-string- +:xref-IGovernor-ProposalQueued-uint256-uint256-: xref:governance.adoc#IGovernor-ProposalQueued-uint256-uint256- +:xref-IGovernor-ProposalExecuted-uint256-: xref:governance.adoc#IGovernor-ProposalExecuted-uint256- +:xref-IGovernor-ProposalCanceled-uint256-: xref:governance.adoc#IGovernor-ProposalCanceled-uint256- +:xref-IGovernor-VoteCast-address-uint256-uint8-uint256-string-: xref:governance.adoc#IGovernor-VoteCast-address-uint256-uint8-uint256-string- +:xref-IGovernor-VoteCastWithParams-address-uint256-uint8-uint256-string-bytes-: xref:governance.adoc#IGovernor-VoteCastWithParams-address-uint256-uint8-uint256-string-bytes- +:xref-IERC5267-EIP712DomainChanged--: xref:interfaces.adoc#IERC5267-EIP712DomainChanged-- +:xref-IGovernor-GovernorInvalidProposalLength-uint256-uint256-uint256-: xref:governance.adoc#IGovernor-GovernorInvalidProposalLength-uint256-uint256-uint256- +:xref-IGovernor-GovernorAlreadyCastVote-address-: xref:governance.adoc#IGovernor-GovernorAlreadyCastVote-address- +:xref-IGovernor-GovernorDisabledDeposit--: xref:governance.adoc#IGovernor-GovernorDisabledDeposit-- +:xref-IGovernor-GovernorOnlyProposer-address-: xref:governance.adoc#IGovernor-GovernorOnlyProposer-address- +:xref-IGovernor-GovernorOnlyExecutor-address-: xref:governance.adoc#IGovernor-GovernorOnlyExecutor-address- +:xref-IGovernor-GovernorNonexistentProposal-uint256-: xref:governance.adoc#IGovernor-GovernorNonexistentProposal-uint256- +:xref-IGovernor-GovernorUnexpectedProposalState-uint256-enum-IGovernor-ProposalState-bytes32-: xref:governance.adoc#IGovernor-GovernorUnexpectedProposalState-uint256-enum-IGovernor-ProposalState-bytes32- +:xref-IGovernor-GovernorInvalidVotingPeriod-uint256-: xref:governance.adoc#IGovernor-GovernorInvalidVotingPeriod-uint256- +:xref-IGovernor-GovernorInsufficientProposerVotes-address-uint256-uint256-: xref:governance.adoc#IGovernor-GovernorInsufficientProposerVotes-address-uint256-uint256- +:xref-IGovernor-GovernorRestrictedProposer-address-: xref:governance.adoc#IGovernor-GovernorRestrictedProposer-address- +:xref-IGovernor-GovernorInvalidVoteType--: xref:governance.adoc#IGovernor-GovernorInvalidVoteType-- +:xref-IGovernor-GovernorQueueNotImplemented--: xref:governance.adoc#IGovernor-GovernorQueueNotImplemented-- +:xref-IGovernor-GovernorNotQueuedProposal-uint256-: xref:governance.adoc#IGovernor-GovernorNotQueuedProposal-uint256- +:xref-IGovernor-GovernorAlreadyQueuedProposal-uint256-: xref:governance.adoc#IGovernor-GovernorAlreadyQueuedProposal-uint256- +:xref-IGovernor-GovernorInvalidSignature-address-: xref:governance.adoc#IGovernor-GovernorInvalidSignature-address- +:xref-Nonces-InvalidAccountNonce-address-uint256-: xref:utils.adoc#Nonces-InvalidAccountNonce-address-uint256- +:Governor-state: pass:normal[xref:governance.adoc#Governor-state-uint256-[`Governor.state`]] +:IGovernor-proposalNeedsQueuing: pass:normal[xref:governance.adoc#IGovernor-proposalNeedsQueuing-uint256-[`IGovernor.proposalNeedsQueuing`]] +:Governor-_executeOperations: pass:normal[xref:governance.adoc#Governor-_executeOperations-uint256-address---uint256---bytes---bytes32-[`Governor._executeOperations`]] +:Governor-_cancel: pass:normal[xref:governance.adoc#Governor-_cancel-address---uint256---bytes---bytes32-[`Governor._cancel`]] +:Governor: pass:normal[xref:governance.adoc#Governor[`Governor`]] +:xref-GovernorSettings-constructor-uint48-uint32-uint256-: xref:governance.adoc#GovernorSettings-constructor-uint48-uint32-uint256- +:xref-GovernorSettings-votingDelay--: xref:governance.adoc#GovernorSettings-votingDelay-- +:xref-GovernorSettings-votingPeriod--: xref:governance.adoc#GovernorSettings-votingPeriod-- +:xref-GovernorSettings-proposalThreshold--: xref:governance.adoc#GovernorSettings-proposalThreshold-- +:xref-GovernorSettings-setVotingDelay-uint48-: xref:governance.adoc#GovernorSettings-setVotingDelay-uint48- +:xref-GovernorSettings-setVotingPeriod-uint32-: xref:governance.adoc#GovernorSettings-setVotingPeriod-uint32- +:xref-GovernorSettings-setProposalThreshold-uint256-: xref:governance.adoc#GovernorSettings-setProposalThreshold-uint256- +:xref-GovernorSettings-_setVotingDelay-uint48-: xref:governance.adoc#GovernorSettings-_setVotingDelay-uint48- +:xref-GovernorSettings-_setVotingPeriod-uint32-: xref:governance.adoc#GovernorSettings-_setVotingPeriod-uint32- +:xref-GovernorSettings-_setProposalThreshold-uint256-: xref:governance.adoc#GovernorSettings-_setProposalThreshold-uint256- +:xref-Governor-receive--: xref:governance.adoc#Governor-receive-- +:xref-Governor-supportsInterface-bytes4-: xref:governance.adoc#Governor-supportsInterface-bytes4- +:xref-Governor-name--: xref:governance.adoc#Governor-name-- +:xref-Governor-version--: xref:governance.adoc#Governor-version-- +:xref-Governor-hashProposal-address---uint256---bytes---bytes32-: xref:governance.adoc#Governor-hashProposal-address---uint256---bytes---bytes32- +:xref-Governor-state-uint256-: xref:governance.adoc#Governor-state-uint256- +:xref-Governor-proposalSnapshot-uint256-: xref:governance.adoc#Governor-proposalSnapshot-uint256- +:xref-Governor-proposalDeadline-uint256-: xref:governance.adoc#Governor-proposalDeadline-uint256- +:xref-Governor-proposalProposer-uint256-: xref:governance.adoc#Governor-proposalProposer-uint256- +:xref-Governor-proposalEta-uint256-: xref:governance.adoc#Governor-proposalEta-uint256- +:xref-Governor-proposalNeedsQueuing-uint256-: xref:governance.adoc#Governor-proposalNeedsQueuing-uint256- +:xref-Governor-_checkGovernance--: xref:governance.adoc#Governor-_checkGovernance-- +:xref-Governor-_quorumReached-uint256-: xref:governance.adoc#Governor-_quorumReached-uint256- +:xref-Governor-_voteSucceeded-uint256-: xref:governance.adoc#Governor-_voteSucceeded-uint256- +:xref-Governor-_getVotes-address-uint256-bytes-: xref:governance.adoc#Governor-_getVotes-address-uint256-bytes- +:xref-Governor-_countVote-uint256-address-uint8-uint256-bytes-: xref:governance.adoc#Governor-_countVote-uint256-address-uint8-uint256-bytes- +:xref-Governor-_defaultParams--: xref:governance.adoc#Governor-_defaultParams-- +:xref-Governor-propose-address---uint256---bytes---string-: xref:governance.adoc#Governor-propose-address---uint256---bytes---string- +:xref-Governor-_propose-address---uint256---bytes---string-address-: xref:governance.adoc#Governor-_propose-address---uint256---bytes---string-address- +:xref-Governor-queue-address---uint256---bytes---bytes32-: xref:governance.adoc#Governor-queue-address---uint256---bytes---bytes32- +:xref-Governor-_queueOperations-uint256-address---uint256---bytes---bytes32-: xref:governance.adoc#Governor-_queueOperations-uint256-address---uint256---bytes---bytes32- +:xref-Governor-execute-address---uint256---bytes---bytes32-: xref:governance.adoc#Governor-execute-address---uint256---bytes---bytes32- +:xref-Governor-_executeOperations-uint256-address---uint256---bytes---bytes32-: xref:governance.adoc#Governor-_executeOperations-uint256-address---uint256---bytes---bytes32- +:xref-Governor-cancel-address---uint256---bytes---bytes32-: xref:governance.adoc#Governor-cancel-address---uint256---bytes---bytes32- +:xref-Governor-_cancel-address---uint256---bytes---bytes32-: xref:governance.adoc#Governor-_cancel-address---uint256---bytes---bytes32- +:xref-Governor-getVotes-address-uint256-: xref:governance.adoc#Governor-getVotes-address-uint256- +:xref-Governor-getVotesWithParams-address-uint256-bytes-: xref:governance.adoc#Governor-getVotesWithParams-address-uint256-bytes- +:xref-Governor-castVote-uint256-uint8-: xref:governance.adoc#Governor-castVote-uint256-uint8- +:xref-Governor-castVoteWithReason-uint256-uint8-string-: xref:governance.adoc#Governor-castVoteWithReason-uint256-uint8-string- +:xref-Governor-castVoteWithReasonAndParams-uint256-uint8-string-bytes-: xref:governance.adoc#Governor-castVoteWithReasonAndParams-uint256-uint8-string-bytes- +:xref-Governor-castVoteBySig-uint256-uint8-address-bytes-: xref:governance.adoc#Governor-castVoteBySig-uint256-uint8-address-bytes- +:xref-Governor-castVoteWithReasonAndParamsBySig-uint256-uint8-address-string-bytes-bytes-: xref:governance.adoc#Governor-castVoteWithReasonAndParamsBySig-uint256-uint8-address-string-bytes-bytes- +:xref-Governor-_castVote-uint256-address-uint8-string-: xref:governance.adoc#Governor-_castVote-uint256-address-uint8-string- +:xref-Governor-_castVote-uint256-address-uint8-string-bytes-: xref:governance.adoc#Governor-_castVote-uint256-address-uint8-string-bytes- +:xref-Governor-relay-address-uint256-bytes-: xref:governance.adoc#Governor-relay-address-uint256-bytes- +:xref-Governor-_executor--: xref:governance.adoc#Governor-_executor-- +:xref-Governor-onERC721Received-address-address-uint256-bytes-: xref:governance.adoc#Governor-onERC721Received-address-address-uint256-bytes- +:xref-Governor-onERC1155Received-address-address-uint256-uint256-bytes-: xref:governance.adoc#Governor-onERC1155Received-address-address-uint256-uint256-bytes- +:xref-Governor-onERC1155BatchReceived-address-address-uint256---uint256---bytes-: xref:governance.adoc#Governor-onERC1155BatchReceived-address-address-uint256---uint256---bytes- +:xref-Governor-_encodeStateBitmap-enum-IGovernor-ProposalState-: xref:governance.adoc#Governor-_encodeStateBitmap-enum-IGovernor-ProposalState- +:xref-Governor-_isValidDescriptionForProposer-address-string-: xref:governance.adoc#Governor-_isValidDescriptionForProposer-address-string- +:xref-Governor-clock--: xref:governance.adoc#Governor-clock-- +:xref-Governor-CLOCK_MODE--: xref:governance.adoc#Governor-CLOCK_MODE-- +:xref-Governor-quorum-uint256-: xref:governance.adoc#Governor-quorum-uint256- +:xref-IGovernor-COUNTING_MODE--: xref:governance.adoc#IGovernor-COUNTING_MODE-- +:xref-IGovernor-hasVoted-uint256-address-: xref:governance.adoc#IGovernor-hasVoted-uint256-address- +:xref-Nonces-nonces-address-: xref:utils.adoc#Nonces-nonces-address- +:xref-Nonces-_useNonce-address-: xref:utils.adoc#Nonces-_useNonce-address- +:xref-Nonces-_useCheckedNonce-address-uint256-: xref:utils.adoc#Nonces-_useCheckedNonce-address-uint256- +:xref-EIP712-_domainSeparatorV4--: xref:utils.adoc#EIP712-_domainSeparatorV4-- +:xref-EIP712-_hashTypedDataV4-bytes32-: xref:utils.adoc#EIP712-_hashTypedDataV4-bytes32- +:xref-EIP712-eip712Domain--: xref:utils.adoc#EIP712-eip712Domain-- +:xref-EIP712-_EIP712Name--: xref:utils.adoc#EIP712-_EIP712Name-- +:xref-EIP712-_EIP712Version--: xref:utils.adoc#EIP712-_EIP712Version-- +:xref-GovernorSettings-VotingDelaySet-uint256-uint256-: xref:governance.adoc#GovernorSettings-VotingDelaySet-uint256-uint256- +:xref-GovernorSettings-VotingPeriodSet-uint256-uint256-: xref:governance.adoc#GovernorSettings-VotingPeriodSet-uint256-uint256- +:xref-GovernorSettings-ProposalThresholdSet-uint256-uint256-: xref:governance.adoc#GovernorSettings-ProposalThresholdSet-uint256-uint256- +:xref-IGovernor-ProposalCreated-uint256-address-address---uint256---string---bytes---uint256-uint256-string-: xref:governance.adoc#IGovernor-ProposalCreated-uint256-address-address---uint256---string---bytes---uint256-uint256-string- +:xref-IGovernor-ProposalQueued-uint256-uint256-: xref:governance.adoc#IGovernor-ProposalQueued-uint256-uint256- +:xref-IGovernor-ProposalExecuted-uint256-: xref:governance.adoc#IGovernor-ProposalExecuted-uint256- +:xref-IGovernor-ProposalCanceled-uint256-: xref:governance.adoc#IGovernor-ProposalCanceled-uint256- +:xref-IGovernor-VoteCast-address-uint256-uint8-uint256-string-: xref:governance.adoc#IGovernor-VoteCast-address-uint256-uint8-uint256-string- +:xref-IGovernor-VoteCastWithParams-address-uint256-uint8-uint256-string-bytes-: xref:governance.adoc#IGovernor-VoteCastWithParams-address-uint256-uint8-uint256-string-bytes- +:xref-IERC5267-EIP712DomainChanged--: xref:interfaces.adoc#IERC5267-EIP712DomainChanged-- +:xref-IGovernor-GovernorInvalidProposalLength-uint256-uint256-uint256-: xref:governance.adoc#IGovernor-GovernorInvalidProposalLength-uint256-uint256-uint256- +:xref-IGovernor-GovernorAlreadyCastVote-address-: xref:governance.adoc#IGovernor-GovernorAlreadyCastVote-address- +:xref-IGovernor-GovernorDisabledDeposit--: xref:governance.adoc#IGovernor-GovernorDisabledDeposit-- +:xref-IGovernor-GovernorOnlyProposer-address-: xref:governance.adoc#IGovernor-GovernorOnlyProposer-address- +:xref-IGovernor-GovernorOnlyExecutor-address-: xref:governance.adoc#IGovernor-GovernorOnlyExecutor-address- +:xref-IGovernor-GovernorNonexistentProposal-uint256-: xref:governance.adoc#IGovernor-GovernorNonexistentProposal-uint256- +:xref-IGovernor-GovernorUnexpectedProposalState-uint256-enum-IGovernor-ProposalState-bytes32-: xref:governance.adoc#IGovernor-GovernorUnexpectedProposalState-uint256-enum-IGovernor-ProposalState-bytes32- +:xref-IGovernor-GovernorInvalidVotingPeriod-uint256-: xref:governance.adoc#IGovernor-GovernorInvalidVotingPeriod-uint256- +:xref-IGovernor-GovernorInsufficientProposerVotes-address-uint256-uint256-: xref:governance.adoc#IGovernor-GovernorInsufficientProposerVotes-address-uint256-uint256- +:xref-IGovernor-GovernorRestrictedProposer-address-: xref:governance.adoc#IGovernor-GovernorRestrictedProposer-address- +:xref-IGovernor-GovernorInvalidVoteType--: xref:governance.adoc#IGovernor-GovernorInvalidVoteType-- +:xref-IGovernor-GovernorQueueNotImplemented--: xref:governance.adoc#IGovernor-GovernorQueueNotImplemented-- +:xref-IGovernor-GovernorNotQueuedProposal-uint256-: xref:governance.adoc#IGovernor-GovernorNotQueuedProposal-uint256- +:xref-IGovernor-GovernorAlreadyQueuedProposal-uint256-: xref:governance.adoc#IGovernor-GovernorAlreadyQueuedProposal-uint256- +:xref-IGovernor-GovernorInvalidSignature-address-: xref:governance.adoc#IGovernor-GovernorInvalidSignature-address- +:xref-Nonces-InvalidAccountNonce-address-uint256-: xref:utils.adoc#Nonces-InvalidAccountNonce-address-uint256- +:IGovernor-votingDelay: pass:normal[xref:governance.adoc#IGovernor-votingDelay--[`IGovernor.votingDelay`]] +:IGovernor-votingPeriod: pass:normal[xref:governance.adoc#IGovernor-votingPeriod--[`IGovernor.votingPeriod`]] +:Governor-proposalThreshold: pass:normal[xref:governance.adoc#Governor-proposalThreshold--[`Governor.proposalThreshold`]] +:xref-GovernorPreventLateQuorum-constructor-uint48-: xref:governance.adoc#GovernorPreventLateQuorum-constructor-uint48- +:xref-GovernorPreventLateQuorum-proposalDeadline-uint256-: xref:governance.adoc#GovernorPreventLateQuorum-proposalDeadline-uint256- +:xref-GovernorPreventLateQuorum-_castVote-uint256-address-uint8-string-bytes-: xref:governance.adoc#GovernorPreventLateQuorum-_castVote-uint256-address-uint8-string-bytes- +:xref-GovernorPreventLateQuorum-lateQuorumVoteExtension--: xref:governance.adoc#GovernorPreventLateQuorum-lateQuorumVoteExtension-- +:xref-GovernorPreventLateQuorum-setLateQuorumVoteExtension-uint48-: xref:governance.adoc#GovernorPreventLateQuorum-setLateQuorumVoteExtension-uint48- +:xref-GovernorPreventLateQuorum-_setLateQuorumVoteExtension-uint48-: xref:governance.adoc#GovernorPreventLateQuorum-_setLateQuorumVoteExtension-uint48- +:xref-Governor-receive--: xref:governance.adoc#Governor-receive-- +:xref-Governor-supportsInterface-bytes4-: xref:governance.adoc#Governor-supportsInterface-bytes4- +:xref-Governor-name--: xref:governance.adoc#Governor-name-- +:xref-Governor-version--: xref:governance.adoc#Governor-version-- +:xref-Governor-hashProposal-address---uint256---bytes---bytes32-: xref:governance.adoc#Governor-hashProposal-address---uint256---bytes---bytes32- +:xref-Governor-state-uint256-: xref:governance.adoc#Governor-state-uint256- +:xref-Governor-proposalThreshold--: xref:governance.adoc#Governor-proposalThreshold-- +:xref-Governor-proposalSnapshot-uint256-: xref:governance.adoc#Governor-proposalSnapshot-uint256- +:xref-Governor-proposalProposer-uint256-: xref:governance.adoc#Governor-proposalProposer-uint256- +:xref-Governor-proposalEta-uint256-: xref:governance.adoc#Governor-proposalEta-uint256- +:xref-Governor-proposalNeedsQueuing-uint256-: xref:governance.adoc#Governor-proposalNeedsQueuing-uint256- +:xref-Governor-_checkGovernance--: xref:governance.adoc#Governor-_checkGovernance-- +:xref-Governor-_quorumReached-uint256-: xref:governance.adoc#Governor-_quorumReached-uint256- +:xref-Governor-_voteSucceeded-uint256-: xref:governance.adoc#Governor-_voteSucceeded-uint256- +:xref-Governor-_getVotes-address-uint256-bytes-: xref:governance.adoc#Governor-_getVotes-address-uint256-bytes- +:xref-Governor-_countVote-uint256-address-uint8-uint256-bytes-: xref:governance.adoc#Governor-_countVote-uint256-address-uint8-uint256-bytes- +:xref-Governor-_defaultParams--: xref:governance.adoc#Governor-_defaultParams-- +:xref-Governor-propose-address---uint256---bytes---string-: xref:governance.adoc#Governor-propose-address---uint256---bytes---string- +:xref-Governor-_propose-address---uint256---bytes---string-address-: xref:governance.adoc#Governor-_propose-address---uint256---bytes---string-address- +:xref-Governor-queue-address---uint256---bytes---bytes32-: xref:governance.adoc#Governor-queue-address---uint256---bytes---bytes32- +:xref-Governor-_queueOperations-uint256-address---uint256---bytes---bytes32-: xref:governance.adoc#Governor-_queueOperations-uint256-address---uint256---bytes---bytes32- +:xref-Governor-execute-address---uint256---bytes---bytes32-: xref:governance.adoc#Governor-execute-address---uint256---bytes---bytes32- +:xref-Governor-_executeOperations-uint256-address---uint256---bytes---bytes32-: xref:governance.adoc#Governor-_executeOperations-uint256-address---uint256---bytes---bytes32- +:xref-Governor-cancel-address---uint256---bytes---bytes32-: xref:governance.adoc#Governor-cancel-address---uint256---bytes---bytes32- +:xref-Governor-_cancel-address---uint256---bytes---bytes32-: xref:governance.adoc#Governor-_cancel-address---uint256---bytes---bytes32- +:xref-Governor-getVotes-address-uint256-: xref:governance.adoc#Governor-getVotes-address-uint256- +:xref-Governor-getVotesWithParams-address-uint256-bytes-: xref:governance.adoc#Governor-getVotesWithParams-address-uint256-bytes- +:xref-Governor-castVote-uint256-uint8-: xref:governance.adoc#Governor-castVote-uint256-uint8- +:xref-Governor-castVoteWithReason-uint256-uint8-string-: xref:governance.adoc#Governor-castVoteWithReason-uint256-uint8-string- +:xref-Governor-castVoteWithReasonAndParams-uint256-uint8-string-bytes-: xref:governance.adoc#Governor-castVoteWithReasonAndParams-uint256-uint8-string-bytes- +:xref-Governor-castVoteBySig-uint256-uint8-address-bytes-: xref:governance.adoc#Governor-castVoteBySig-uint256-uint8-address-bytes- +:xref-Governor-castVoteWithReasonAndParamsBySig-uint256-uint8-address-string-bytes-bytes-: xref:governance.adoc#Governor-castVoteWithReasonAndParamsBySig-uint256-uint8-address-string-bytes-bytes- +:xref-Governor-_castVote-uint256-address-uint8-string-: xref:governance.adoc#Governor-_castVote-uint256-address-uint8-string- +:xref-Governor-relay-address-uint256-bytes-: xref:governance.adoc#Governor-relay-address-uint256-bytes- +:xref-Governor-_executor--: xref:governance.adoc#Governor-_executor-- +:xref-Governor-onERC721Received-address-address-uint256-bytes-: xref:governance.adoc#Governor-onERC721Received-address-address-uint256-bytes- +:xref-Governor-onERC1155Received-address-address-uint256-uint256-bytes-: xref:governance.adoc#Governor-onERC1155Received-address-address-uint256-uint256-bytes- +:xref-Governor-onERC1155BatchReceived-address-address-uint256---uint256---bytes-: xref:governance.adoc#Governor-onERC1155BatchReceived-address-address-uint256---uint256---bytes- +:xref-Governor-_encodeStateBitmap-enum-IGovernor-ProposalState-: xref:governance.adoc#Governor-_encodeStateBitmap-enum-IGovernor-ProposalState- +:xref-Governor-_isValidDescriptionForProposer-address-string-: xref:governance.adoc#Governor-_isValidDescriptionForProposer-address-string- +:xref-Governor-clock--: xref:governance.adoc#Governor-clock-- +:xref-Governor-CLOCK_MODE--: xref:governance.adoc#Governor-CLOCK_MODE-- +:xref-Governor-votingDelay--: xref:governance.adoc#Governor-votingDelay-- +:xref-Governor-votingPeriod--: xref:governance.adoc#Governor-votingPeriod-- +:xref-Governor-quorum-uint256-: xref:governance.adoc#Governor-quorum-uint256- +:xref-IGovernor-COUNTING_MODE--: xref:governance.adoc#IGovernor-COUNTING_MODE-- +:xref-IGovernor-hasVoted-uint256-address-: xref:governance.adoc#IGovernor-hasVoted-uint256-address- +:xref-Nonces-nonces-address-: xref:utils.adoc#Nonces-nonces-address- +:xref-Nonces-_useNonce-address-: xref:utils.adoc#Nonces-_useNonce-address- +:xref-Nonces-_useCheckedNonce-address-uint256-: xref:utils.adoc#Nonces-_useCheckedNonce-address-uint256- +:xref-EIP712-_domainSeparatorV4--: xref:utils.adoc#EIP712-_domainSeparatorV4-- +:xref-EIP712-_hashTypedDataV4-bytes32-: xref:utils.adoc#EIP712-_hashTypedDataV4-bytes32- +:xref-EIP712-eip712Domain--: xref:utils.adoc#EIP712-eip712Domain-- +:xref-EIP712-_EIP712Name--: xref:utils.adoc#EIP712-_EIP712Name-- +:xref-EIP712-_EIP712Version--: xref:utils.adoc#EIP712-_EIP712Version-- +:xref-GovernorPreventLateQuorum-ProposalExtended-uint256-uint64-: xref:governance.adoc#GovernorPreventLateQuorum-ProposalExtended-uint256-uint64- +:xref-GovernorPreventLateQuorum-LateQuorumVoteExtensionSet-uint64-uint64-: xref:governance.adoc#GovernorPreventLateQuorum-LateQuorumVoteExtensionSet-uint64-uint64- +:xref-IGovernor-ProposalCreated-uint256-address-address---uint256---string---bytes---uint256-uint256-string-: xref:governance.adoc#IGovernor-ProposalCreated-uint256-address-address---uint256---string---bytes---uint256-uint256-string- +:xref-IGovernor-ProposalQueued-uint256-uint256-: xref:governance.adoc#IGovernor-ProposalQueued-uint256-uint256- +:xref-IGovernor-ProposalExecuted-uint256-: xref:governance.adoc#IGovernor-ProposalExecuted-uint256- +:xref-IGovernor-ProposalCanceled-uint256-: xref:governance.adoc#IGovernor-ProposalCanceled-uint256- +:xref-IGovernor-VoteCast-address-uint256-uint8-uint256-string-: xref:governance.adoc#IGovernor-VoteCast-address-uint256-uint8-uint256-string- +:xref-IGovernor-VoteCastWithParams-address-uint256-uint8-uint256-string-bytes-: xref:governance.adoc#IGovernor-VoteCastWithParams-address-uint256-uint8-uint256-string-bytes- +:xref-IERC5267-EIP712DomainChanged--: xref:interfaces.adoc#IERC5267-EIP712DomainChanged-- +:xref-IGovernor-GovernorInvalidProposalLength-uint256-uint256-uint256-: xref:governance.adoc#IGovernor-GovernorInvalidProposalLength-uint256-uint256-uint256- +:xref-IGovernor-GovernorAlreadyCastVote-address-: xref:governance.adoc#IGovernor-GovernorAlreadyCastVote-address- +:xref-IGovernor-GovernorDisabledDeposit--: xref:governance.adoc#IGovernor-GovernorDisabledDeposit-- +:xref-IGovernor-GovernorOnlyProposer-address-: xref:governance.adoc#IGovernor-GovernorOnlyProposer-address- +:xref-IGovernor-GovernorOnlyExecutor-address-: xref:governance.adoc#IGovernor-GovernorOnlyExecutor-address- +:xref-IGovernor-GovernorNonexistentProposal-uint256-: xref:governance.adoc#IGovernor-GovernorNonexistentProposal-uint256- +:xref-IGovernor-GovernorUnexpectedProposalState-uint256-enum-IGovernor-ProposalState-bytes32-: xref:governance.adoc#IGovernor-GovernorUnexpectedProposalState-uint256-enum-IGovernor-ProposalState-bytes32- +:xref-IGovernor-GovernorInvalidVotingPeriod-uint256-: xref:governance.adoc#IGovernor-GovernorInvalidVotingPeriod-uint256- +:xref-IGovernor-GovernorInsufficientProposerVotes-address-uint256-uint256-: xref:governance.adoc#IGovernor-GovernorInsufficientProposerVotes-address-uint256-uint256- +:xref-IGovernor-GovernorRestrictedProposer-address-: xref:governance.adoc#IGovernor-GovernorRestrictedProposer-address- +:xref-IGovernor-GovernorInvalidVoteType--: xref:governance.adoc#IGovernor-GovernorInvalidVoteType-- +:xref-IGovernor-GovernorQueueNotImplemented--: xref:governance.adoc#IGovernor-GovernorQueueNotImplemented-- +:xref-IGovernor-GovernorNotQueuedProposal-uint256-: xref:governance.adoc#IGovernor-GovernorNotQueuedProposal-uint256- +:xref-IGovernor-GovernorAlreadyQueuedProposal-uint256-: xref:governance.adoc#IGovernor-GovernorAlreadyQueuedProposal-uint256- +:xref-IGovernor-GovernorInvalidSignature-address-: xref:governance.adoc#IGovernor-GovernorInvalidSignature-address- +:xref-Nonces-InvalidAccountNonce-address-uint256-: xref:utils.adoc#Nonces-InvalidAccountNonce-address-uint256- +:Governor-proposalDeadline: pass:normal[xref:governance.adoc#Governor-proposalDeadline-uint256-[`Governor.proposalDeadline`]] +:Governor-_castVote: pass:normal[xref:governance.adoc#Governor-_castVote-uint256-address-uint8-string-bytes-[`Governor._castVote`]] +:Governor: pass:normal[xref:governance.adoc#Governor[`Governor`]] +:Governor-queue: pass:normal[xref:governance.adoc#Governor-queue-address---uint256---bytes---bytes32-[`Governor.queue`]] +:Governor-execute: pass:normal[xref:governance.adoc#Governor-execute-address---uint256---bytes---bytes32-[`Governor.execute`]] +:xref-GovernorStorage-_propose-address---uint256---bytes---string-address-: xref:governance.adoc#GovernorStorage-_propose-address---uint256---bytes---string-address- +:xref-GovernorStorage-queue-uint256-: xref:governance.adoc#GovernorStorage-queue-uint256- +:xref-GovernorStorage-execute-uint256-: xref:governance.adoc#GovernorStorage-execute-uint256- +:xref-GovernorStorage-cancel-uint256-: xref:governance.adoc#GovernorStorage-cancel-uint256- +:xref-GovernorStorage-proposalCount--: xref:governance.adoc#GovernorStorage-proposalCount-- +:xref-GovernorStorage-proposalDetails-uint256-: xref:governance.adoc#GovernorStorage-proposalDetails-uint256- +:xref-GovernorStorage-proposalDetailsAt-uint256-: xref:governance.adoc#GovernorStorage-proposalDetailsAt-uint256- +:xref-Governor-receive--: xref:governance.adoc#Governor-receive-- +:xref-Governor-supportsInterface-bytes4-: xref:governance.adoc#Governor-supportsInterface-bytes4- +:xref-Governor-name--: xref:governance.adoc#Governor-name-- +:xref-Governor-version--: xref:governance.adoc#Governor-version-- +:xref-Governor-hashProposal-address---uint256---bytes---bytes32-: xref:governance.adoc#Governor-hashProposal-address---uint256---bytes---bytes32- +:xref-Governor-state-uint256-: xref:governance.adoc#Governor-state-uint256- +:xref-Governor-proposalThreshold--: xref:governance.adoc#Governor-proposalThreshold-- +:xref-Governor-proposalSnapshot-uint256-: xref:governance.adoc#Governor-proposalSnapshot-uint256- +:xref-Governor-proposalDeadline-uint256-: xref:governance.adoc#Governor-proposalDeadline-uint256- +:xref-Governor-proposalProposer-uint256-: xref:governance.adoc#Governor-proposalProposer-uint256- +:xref-Governor-proposalEta-uint256-: xref:governance.adoc#Governor-proposalEta-uint256- +:xref-Governor-proposalNeedsQueuing-uint256-: xref:governance.adoc#Governor-proposalNeedsQueuing-uint256- +:xref-Governor-_checkGovernance--: xref:governance.adoc#Governor-_checkGovernance-- +:xref-Governor-_quorumReached-uint256-: xref:governance.adoc#Governor-_quorumReached-uint256- +:xref-Governor-_voteSucceeded-uint256-: xref:governance.adoc#Governor-_voteSucceeded-uint256- +:xref-Governor-_getVotes-address-uint256-bytes-: xref:governance.adoc#Governor-_getVotes-address-uint256-bytes- +:xref-Governor-_countVote-uint256-address-uint8-uint256-bytes-: xref:governance.adoc#Governor-_countVote-uint256-address-uint8-uint256-bytes- +:xref-Governor-_defaultParams--: xref:governance.adoc#Governor-_defaultParams-- +:xref-Governor-propose-address---uint256---bytes---string-: xref:governance.adoc#Governor-propose-address---uint256---bytes---string- +:xref-Governor-queue-address---uint256---bytes---bytes32-: xref:governance.adoc#Governor-queue-address---uint256---bytes---bytes32- +:xref-Governor-_queueOperations-uint256-address---uint256---bytes---bytes32-: xref:governance.adoc#Governor-_queueOperations-uint256-address---uint256---bytes---bytes32- +:xref-Governor-execute-address---uint256---bytes---bytes32-: xref:governance.adoc#Governor-execute-address---uint256---bytes---bytes32- +:xref-Governor-_executeOperations-uint256-address---uint256---bytes---bytes32-: xref:governance.adoc#Governor-_executeOperations-uint256-address---uint256---bytes---bytes32- +:xref-Governor-cancel-address---uint256---bytes---bytes32-: xref:governance.adoc#Governor-cancel-address---uint256---bytes---bytes32- +:xref-Governor-_cancel-address---uint256---bytes---bytes32-: xref:governance.adoc#Governor-_cancel-address---uint256---bytes---bytes32- +:xref-Governor-getVotes-address-uint256-: xref:governance.adoc#Governor-getVotes-address-uint256- +:xref-Governor-getVotesWithParams-address-uint256-bytes-: xref:governance.adoc#Governor-getVotesWithParams-address-uint256-bytes- +:xref-Governor-castVote-uint256-uint8-: xref:governance.adoc#Governor-castVote-uint256-uint8- +:xref-Governor-castVoteWithReason-uint256-uint8-string-: xref:governance.adoc#Governor-castVoteWithReason-uint256-uint8-string- +:xref-Governor-castVoteWithReasonAndParams-uint256-uint8-string-bytes-: xref:governance.adoc#Governor-castVoteWithReasonAndParams-uint256-uint8-string-bytes- +:xref-Governor-castVoteBySig-uint256-uint8-address-bytes-: xref:governance.adoc#Governor-castVoteBySig-uint256-uint8-address-bytes- +:xref-Governor-castVoteWithReasonAndParamsBySig-uint256-uint8-address-string-bytes-bytes-: xref:governance.adoc#Governor-castVoteWithReasonAndParamsBySig-uint256-uint8-address-string-bytes-bytes- +:xref-Governor-_castVote-uint256-address-uint8-string-: xref:governance.adoc#Governor-_castVote-uint256-address-uint8-string- +:xref-Governor-_castVote-uint256-address-uint8-string-bytes-: xref:governance.adoc#Governor-_castVote-uint256-address-uint8-string-bytes- +:xref-Governor-relay-address-uint256-bytes-: xref:governance.adoc#Governor-relay-address-uint256-bytes- +:xref-Governor-_executor--: xref:governance.adoc#Governor-_executor-- +:xref-Governor-onERC721Received-address-address-uint256-bytes-: xref:governance.adoc#Governor-onERC721Received-address-address-uint256-bytes- +:xref-Governor-onERC1155Received-address-address-uint256-uint256-bytes-: xref:governance.adoc#Governor-onERC1155Received-address-address-uint256-uint256-bytes- +:xref-Governor-onERC1155BatchReceived-address-address-uint256---uint256---bytes-: xref:governance.adoc#Governor-onERC1155BatchReceived-address-address-uint256---uint256---bytes- +:xref-Governor-_encodeStateBitmap-enum-IGovernor-ProposalState-: xref:governance.adoc#Governor-_encodeStateBitmap-enum-IGovernor-ProposalState- +:xref-Governor-_isValidDescriptionForProposer-address-string-: xref:governance.adoc#Governor-_isValidDescriptionForProposer-address-string- +:xref-Governor-clock--: xref:governance.adoc#Governor-clock-- +:xref-Governor-CLOCK_MODE--: xref:governance.adoc#Governor-CLOCK_MODE-- +:xref-Governor-votingDelay--: xref:governance.adoc#Governor-votingDelay-- +:xref-Governor-votingPeriod--: xref:governance.adoc#Governor-votingPeriod-- +:xref-Governor-quorum-uint256-: xref:governance.adoc#Governor-quorum-uint256- +:xref-IGovernor-COUNTING_MODE--: xref:governance.adoc#IGovernor-COUNTING_MODE-- +:xref-IGovernor-hasVoted-uint256-address-: xref:governance.adoc#IGovernor-hasVoted-uint256-address- +:xref-Nonces-nonces-address-: xref:utils.adoc#Nonces-nonces-address- +:xref-Nonces-_useNonce-address-: xref:utils.adoc#Nonces-_useNonce-address- +:xref-Nonces-_useCheckedNonce-address-uint256-: xref:utils.adoc#Nonces-_useCheckedNonce-address-uint256- +:xref-EIP712-_domainSeparatorV4--: xref:utils.adoc#EIP712-_domainSeparatorV4-- +:xref-EIP712-_hashTypedDataV4-bytes32-: xref:utils.adoc#EIP712-_hashTypedDataV4-bytes32- +:xref-EIP712-eip712Domain--: xref:utils.adoc#EIP712-eip712Domain-- +:xref-EIP712-_EIP712Name--: xref:utils.adoc#EIP712-_EIP712Name-- +:xref-EIP712-_EIP712Version--: xref:utils.adoc#EIP712-_EIP712Version-- +:xref-IGovernor-ProposalCreated-uint256-address-address---uint256---string---bytes---uint256-uint256-string-: xref:governance.adoc#IGovernor-ProposalCreated-uint256-address-address---uint256---string---bytes---uint256-uint256-string- +:xref-IGovernor-ProposalQueued-uint256-uint256-: xref:governance.adoc#IGovernor-ProposalQueued-uint256-uint256- +:xref-IGovernor-ProposalExecuted-uint256-: xref:governance.adoc#IGovernor-ProposalExecuted-uint256- +:xref-IGovernor-ProposalCanceled-uint256-: xref:governance.adoc#IGovernor-ProposalCanceled-uint256- +:xref-IGovernor-VoteCast-address-uint256-uint8-uint256-string-: xref:governance.adoc#IGovernor-VoteCast-address-uint256-uint8-uint256-string- +:xref-IGovernor-VoteCastWithParams-address-uint256-uint8-uint256-string-bytes-: xref:governance.adoc#IGovernor-VoteCastWithParams-address-uint256-uint8-uint256-string-bytes- +:xref-IERC5267-EIP712DomainChanged--: xref:interfaces.adoc#IERC5267-EIP712DomainChanged-- +:xref-IGovernor-GovernorInvalidProposalLength-uint256-uint256-uint256-: xref:governance.adoc#IGovernor-GovernorInvalidProposalLength-uint256-uint256-uint256- +:xref-IGovernor-GovernorAlreadyCastVote-address-: xref:governance.adoc#IGovernor-GovernorAlreadyCastVote-address- +:xref-IGovernor-GovernorDisabledDeposit--: xref:governance.adoc#IGovernor-GovernorDisabledDeposit-- +:xref-IGovernor-GovernorOnlyProposer-address-: xref:governance.adoc#IGovernor-GovernorOnlyProposer-address- +:xref-IGovernor-GovernorOnlyExecutor-address-: xref:governance.adoc#IGovernor-GovernorOnlyExecutor-address- +:xref-IGovernor-GovernorNonexistentProposal-uint256-: xref:governance.adoc#IGovernor-GovernorNonexistentProposal-uint256- +:xref-IGovernor-GovernorUnexpectedProposalState-uint256-enum-IGovernor-ProposalState-bytes32-: xref:governance.adoc#IGovernor-GovernorUnexpectedProposalState-uint256-enum-IGovernor-ProposalState-bytes32- +:xref-IGovernor-GovernorInvalidVotingPeriod-uint256-: xref:governance.adoc#IGovernor-GovernorInvalidVotingPeriod-uint256- +:xref-IGovernor-GovernorInsufficientProposerVotes-address-uint256-uint256-: xref:governance.adoc#IGovernor-GovernorInsufficientProposerVotes-address-uint256-uint256- +:xref-IGovernor-GovernorRestrictedProposer-address-: xref:governance.adoc#IGovernor-GovernorRestrictedProposer-address- +:xref-IGovernor-GovernorInvalidVoteType--: xref:governance.adoc#IGovernor-GovernorInvalidVoteType-- +:xref-IGovernor-GovernorQueueNotImplemented--: xref:governance.adoc#IGovernor-GovernorQueueNotImplemented-- +:xref-IGovernor-GovernorNotQueuedProposal-uint256-: xref:governance.adoc#IGovernor-GovernorNotQueuedProposal-uint256- +:xref-IGovernor-GovernorAlreadyQueuedProposal-uint256-: xref:governance.adoc#IGovernor-GovernorAlreadyQueuedProposal-uint256- +:xref-IGovernor-GovernorInvalidSignature-address-: xref:governance.adoc#IGovernor-GovernorInvalidSignature-address- +:xref-Nonces-InvalidAccountNonce-address-uint256-: xref:utils.adoc#Nonces-InvalidAccountNonce-address-uint256- +:IGovernor-execute: pass:normal[xref:governance.adoc#IGovernor-execute-address---uint256---bytes---bytes32-[`IGovernor.execute`]] +:IGovernor-cancel: pass:normal[xref:governance.adoc#IGovernor-cancel-address---uint256---bytes---bytes32-[`IGovernor.cancel`]] +:ERC721Votes: pass:normal[xref:token/ERC721.adoc#ERC721Votes[`ERC721Votes`]] +:ERC721-balanceOf: pass:normal[xref:token/ERC721.adoc#ERC721-balanceOf-address-[`ERC721.balanceOf`]] +:xref-Votes-clock--: xref:governance.adoc#Votes-clock-- +:xref-Votes-CLOCK_MODE--: xref:governance.adoc#Votes-CLOCK_MODE-- +:xref-Votes-getVotes-address-: xref:governance.adoc#Votes-getVotes-address- +:xref-Votes-getPastVotes-address-uint256-: xref:governance.adoc#Votes-getPastVotes-address-uint256- +:xref-Votes-getPastTotalSupply-uint256-: xref:governance.adoc#Votes-getPastTotalSupply-uint256- +:xref-Votes-_getTotalSupply--: xref:governance.adoc#Votes-_getTotalSupply-- +:xref-Votes-delegates-address-: xref:governance.adoc#Votes-delegates-address- +:xref-Votes-delegate-address-: xref:governance.adoc#Votes-delegate-address- +:xref-Votes-delegateBySig-address-uint256-uint256-uint8-bytes32-bytes32-: xref:governance.adoc#Votes-delegateBySig-address-uint256-uint256-uint8-bytes32-bytes32- +:xref-Votes-_delegate-address-address-: xref:governance.adoc#Votes-_delegate-address-address- +:xref-Votes-_transferVotingUnits-address-address-uint256-: xref:governance.adoc#Votes-_transferVotingUnits-address-address-uint256- +:xref-Votes-_numCheckpoints-address-: xref:governance.adoc#Votes-_numCheckpoints-address- +:xref-Votes-_checkpoints-address-uint32-: xref:governance.adoc#Votes-_checkpoints-address-uint32- +:xref-Votes-_getVotingUnits-address-: xref:governance.adoc#Votes-_getVotingUnits-address- +:xref-Nonces-nonces-address-: xref:utils.adoc#Nonces-nonces-address- +:xref-Nonces-_useNonce-address-: xref:utils.adoc#Nonces-_useNonce-address- +:xref-Nonces-_useCheckedNonce-address-uint256-: xref:utils.adoc#Nonces-_useCheckedNonce-address-uint256- +:xref-EIP712-_domainSeparatorV4--: xref:utils.adoc#EIP712-_domainSeparatorV4-- +:xref-EIP712-_hashTypedDataV4-bytes32-: xref:utils.adoc#EIP712-_hashTypedDataV4-bytes32- +:xref-EIP712-eip712Domain--: xref:utils.adoc#EIP712-eip712Domain-- +:xref-EIP712-_EIP712Name--: xref:utils.adoc#EIP712-_EIP712Name-- +:xref-EIP712-_EIP712Version--: xref:utils.adoc#EIP712-_EIP712Version-- +:xref-IVotes-DelegateChanged-address-address-address-: xref:governance.adoc#IVotes-DelegateChanged-address-address-address- +:xref-IVotes-DelegateVotesChanged-address-uint256-uint256-: xref:governance.adoc#IVotes-DelegateVotesChanged-address-uint256-uint256- +:xref-IERC5267-EIP712DomainChanged--: xref:interfaces.adoc#IERC5267-EIP712DomainChanged-- +:xref-Votes-ERC6372InconsistentClock--: xref:governance.adoc#Votes-ERC6372InconsistentClock-- +:xref-Votes-ERC5805FutureLookup-uint256-uint48-: xref:governance.adoc#Votes-ERC5805FutureLookup-uint256-uint48- +:xref-IVotes-VotesExpiredSignature-uint256-: xref:governance.adoc#IVotes-VotesExpiredSignature-uint256- +:xref-Nonces-InvalidAccountNonce-address-uint256-: xref:utils.adoc#Nonces-InvalidAccountNonce-address-uint256- +:IVotes-DelegateChanged: pass:normal[xref:governance.adoc#IVotes-DelegateChanged-address-address-address-[`IVotes.DelegateChanged`]] +:IVotes-DelegateVotesChanged: pass:normal[xref:governance.adoc#IVotes-DelegateVotesChanged-address-uint256-uint256-[`IVotes.DelegateVotesChanged`]] +:TimelockController: pass:normal[xref:governance.adoc#TimelockController[`TimelockController`]] +:Governor: pass:normal[xref:governance.adoc#Governor[`Governor`]] +:TimelockController: pass:normal[xref:governance.adoc#TimelockController[`TimelockController`]] +:xref-TimelockController-onlyRoleOrOpenRole-bytes32-: xref:governance.adoc#TimelockController-onlyRoleOrOpenRole-bytes32- +:xref-TimelockController-constructor-uint256-address---address---address-: xref:governance.adoc#TimelockController-constructor-uint256-address---address---address- +:xref-TimelockController-receive--: xref:governance.adoc#TimelockController-receive-- +:xref-TimelockController-supportsInterface-bytes4-: xref:governance.adoc#TimelockController-supportsInterface-bytes4- +:xref-TimelockController-isOperation-bytes32-: xref:governance.adoc#TimelockController-isOperation-bytes32- +:xref-TimelockController-isOperationPending-bytes32-: xref:governance.adoc#TimelockController-isOperationPending-bytes32- +:xref-TimelockController-isOperationReady-bytes32-: xref:governance.adoc#TimelockController-isOperationReady-bytes32- +:xref-TimelockController-isOperationDone-bytes32-: xref:governance.adoc#TimelockController-isOperationDone-bytes32- +:xref-TimelockController-getTimestamp-bytes32-: xref:governance.adoc#TimelockController-getTimestamp-bytes32- +:xref-TimelockController-getOperationState-bytes32-: xref:governance.adoc#TimelockController-getOperationState-bytes32- +:xref-TimelockController-getMinDelay--: xref:governance.adoc#TimelockController-getMinDelay-- +:xref-TimelockController-hashOperation-address-uint256-bytes-bytes32-bytes32-: xref:governance.adoc#TimelockController-hashOperation-address-uint256-bytes-bytes32-bytes32- +:xref-TimelockController-hashOperationBatch-address---uint256---bytes---bytes32-bytes32-: xref:governance.adoc#TimelockController-hashOperationBatch-address---uint256---bytes---bytes32-bytes32- +:xref-TimelockController-schedule-address-uint256-bytes-bytes32-bytes32-uint256-: xref:governance.adoc#TimelockController-schedule-address-uint256-bytes-bytes32-bytes32-uint256- +:xref-TimelockController-scheduleBatch-address---uint256---bytes---bytes32-bytes32-uint256-: xref:governance.adoc#TimelockController-scheduleBatch-address---uint256---bytes---bytes32-bytes32-uint256- +:xref-TimelockController-cancel-bytes32-: xref:governance.adoc#TimelockController-cancel-bytes32- +:xref-TimelockController-execute-address-uint256-bytes-bytes32-bytes32-: xref:governance.adoc#TimelockController-execute-address-uint256-bytes-bytes32-bytes32- +:xref-TimelockController-executeBatch-address---uint256---bytes---bytes32-bytes32-: xref:governance.adoc#TimelockController-executeBatch-address---uint256---bytes---bytes32-bytes32- +:xref-TimelockController-_execute-address-uint256-bytes-: xref:governance.adoc#TimelockController-_execute-address-uint256-bytes- +:xref-TimelockController-updateDelay-uint256-: xref:governance.adoc#TimelockController-updateDelay-uint256- +:xref-TimelockController-_encodeStateBitmap-enum-TimelockController-OperationState-: xref:governance.adoc#TimelockController-_encodeStateBitmap-enum-TimelockController-OperationState- +:xref-ERC1155Holder-onERC1155Received-address-address-uint256-uint256-bytes-: xref:token/ERC1155.adoc#ERC1155Holder-onERC1155Received-address-address-uint256-uint256-bytes- +:xref-ERC1155Holder-onERC1155BatchReceived-address-address-uint256---uint256---bytes-: xref:token/ERC1155.adoc#ERC1155Holder-onERC1155BatchReceived-address-address-uint256---uint256---bytes- +:xref-ERC721Holder-onERC721Received-address-address-uint256-bytes-: xref:token/ERC721.adoc#ERC721Holder-onERC721Received-address-address-uint256-bytes- +:xref-AccessControl-hasRole-bytes32-address-: xref:access.adoc#AccessControl-hasRole-bytes32-address- +:xref-AccessControl-_checkRole-bytes32-: xref:access.adoc#AccessControl-_checkRole-bytes32- +:xref-AccessControl-_checkRole-bytes32-address-: xref:access.adoc#AccessControl-_checkRole-bytes32-address- +:xref-AccessControl-getRoleAdmin-bytes32-: xref:access.adoc#AccessControl-getRoleAdmin-bytes32- +:xref-AccessControl-grantRole-bytes32-address-: xref:access.adoc#AccessControl-grantRole-bytes32-address- +:xref-AccessControl-revokeRole-bytes32-address-: xref:access.adoc#AccessControl-revokeRole-bytes32-address- +:xref-AccessControl-renounceRole-bytes32-address-: xref:access.adoc#AccessControl-renounceRole-bytes32-address- +:xref-AccessControl-_setRoleAdmin-bytes32-bytes32-: xref:access.adoc#AccessControl-_setRoleAdmin-bytes32-bytes32- +:xref-AccessControl-_grantRole-bytes32-address-: xref:access.adoc#AccessControl-_grantRole-bytes32-address- +:xref-AccessControl-_revokeRole-bytes32-address-: xref:access.adoc#AccessControl-_revokeRole-bytes32-address- +:xref-TimelockController-CallScheduled-bytes32-uint256-address-uint256-bytes-bytes32-uint256-: xref:governance.adoc#TimelockController-CallScheduled-bytes32-uint256-address-uint256-bytes-bytes32-uint256- +:xref-TimelockController-CallExecuted-bytes32-uint256-address-uint256-bytes-: xref:governance.adoc#TimelockController-CallExecuted-bytes32-uint256-address-uint256-bytes- +:xref-TimelockController-CallSalt-bytes32-bytes32-: xref:governance.adoc#TimelockController-CallSalt-bytes32-bytes32- +:xref-TimelockController-Cancelled-bytes32-: xref:governance.adoc#TimelockController-Cancelled-bytes32- +:xref-TimelockController-MinDelayChange-uint256-uint256-: xref:governance.adoc#TimelockController-MinDelayChange-uint256-uint256- +:xref-IAccessControl-RoleAdminChanged-bytes32-bytes32-bytes32-: xref:access.adoc#IAccessControl-RoleAdminChanged-bytes32-bytes32-bytes32- +:xref-IAccessControl-RoleGranted-bytes32-address-address-: xref:access.adoc#IAccessControl-RoleGranted-bytes32-address-address- +:xref-IAccessControl-RoleRevoked-bytes32-address-address-: xref:access.adoc#IAccessControl-RoleRevoked-bytes32-address-address- +:xref-TimelockController-TimelockInvalidOperationLength-uint256-uint256-uint256-: xref:governance.adoc#TimelockController-TimelockInvalidOperationLength-uint256-uint256-uint256- +:xref-TimelockController-TimelockInsufficientDelay-uint256-uint256-: xref:governance.adoc#TimelockController-TimelockInsufficientDelay-uint256-uint256- +:xref-TimelockController-TimelockUnexpectedOperationState-bytes32-bytes32-: xref:governance.adoc#TimelockController-TimelockUnexpectedOperationState-bytes32-bytes32- +:xref-TimelockController-TimelockUnexecutedPredecessor-bytes32-: xref:governance.adoc#TimelockController-TimelockUnexecutedPredecessor-bytes32- +:xref-TimelockController-TimelockUnauthorizedCaller-address-: xref:governance.adoc#TimelockController-TimelockUnauthorizedCaller-address- +:xref-IAccessControl-AccessControlUnauthorizedAccount-address-bytes32-: xref:access.adoc#IAccessControl-AccessControlUnauthorizedAccount-address-bytes32- +:xref-IAccessControl-AccessControlBadConfirmation--: xref:access.adoc#IAccessControl-AccessControlBadConfirmation-- +:IERC165-supportsInterface: pass:normal[xref:utils.adoc#IERC165-supportsInterface-bytes4-[`IERC165.supportsInterface`]] +:AccessControl: pass:normal[xref:access.adoc#AccessControl[`AccessControl`]] += Governance + +[.readme-notice] +NOTE: This document is better viewed at https://docs.openzeppelin.com/contracts/api/governance + +This directory includes primitives for on-chain governance. + +== Governor + +This modular system of Governor contracts allows the deployment on-chain voting protocols similar to https://compound.finance/docs/governance[Compound's Governor Alpha & Bravo] and beyond, through the ability to easily customize multiple aspects of the protocol. + +[TIP] +==== +For a guided experience, set up your Governor contract using https://wizard.openzeppelin.com/#governor[Contracts Wizard]. + +For a written walkthrough, check out our guide on xref:ROOT:governance.adoc[How to set up on-chain governance]. +==== + +* {Governor}: The core contract that contains all the logic and primitives. It is abstract and requires choosing one of each of the modules below, or custom ones. + +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. + +* {GovernorVotesQuorumFraction}: Combines with `GovernorVotes` to set the quorum as a fraction of the total token supply. + +Counting modules determine valid voting options. + +* {GovernorCountingSimple}: Simple voting mechanism with 3 voting options: Against, For and Abstain. + +Timelock extensions add a delay for governance decisions to be executed. The workflow is extended to require a `queue` step before execution. With these modules, proposals are executed by the external timelock contract, thus it is the timelock that has to hold the assets that are being governed. + +* {GovernorTimelockControl}: Connects with an instance of {TimelockController}. Allows multiple proposers and executors, in addition to the Governor itself. + +* {GovernorTimelockCompound}: Connects with an instance of Compound's https://github.com/compound-finance/compound-protocol/blob/master/contracts/Timelock.sol[`Timelock`] contract. + +Other extensions can customize the behavior or interface in multiple ways. + +* {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. + +* {GovernorPreventLateQuorum}: Ensures there is a minimum voting period after quorum is reached as a security protection against large voters. + +In addition to modules and extensions, the core contract requires a few virtual functions to be implemented to your particular specifications: + +* <>: Delay (in EIP-6372 clock) since the proposal is submitted until voting power is fixed and voting starts. This can be used to enforce a delay after a proposal is published for users to buy tokens, or delegate their votes. +* <>: Delay (in EIP-6372 clock) since the proposal starts until voting ends. +* <>: Quorum required for a proposal to be successful. This function includes a `timepoint` argument (see EIP-6372) so the quorum can adapt through time, for example, to follow a token's `totalSupply`. + +NOTE: Functions of the `Governor` contract do not include access control. If you want to restrict access, you should add these checks by overloading the particular functions. Among these, {Governor-_cancel} is internal by default, and you will have to expose it (with the right access control mechanism) yourself if this function is needed. + +=== Core + +:ProposalState: pass:normal[xref:#IGovernor-ProposalState[`++ProposalState++`]] +:GovernorInvalidProposalLength: pass:normal[xref:#IGovernor-GovernorInvalidProposalLength-uint256-uint256-uint256-[`++GovernorInvalidProposalLength++`]] +:GovernorAlreadyCastVote: pass:normal[xref:#IGovernor-GovernorAlreadyCastVote-address-[`++GovernorAlreadyCastVote++`]] +:GovernorDisabledDeposit: pass:normal[xref:#IGovernor-GovernorDisabledDeposit--[`++GovernorDisabledDeposit++`]] +:GovernorOnlyProposer: pass:normal[xref:#IGovernor-GovernorOnlyProposer-address-[`++GovernorOnlyProposer++`]] +:GovernorOnlyExecutor: pass:normal[xref:#IGovernor-GovernorOnlyExecutor-address-[`++GovernorOnlyExecutor++`]] +:GovernorNonexistentProposal: pass:normal[xref:#IGovernor-GovernorNonexistentProposal-uint256-[`++GovernorNonexistentProposal++`]] +:GovernorUnexpectedProposalState: pass:normal[xref:#IGovernor-GovernorUnexpectedProposalState-uint256-enum-IGovernor-ProposalState-bytes32-[`++GovernorUnexpectedProposalState++`]] +:GovernorInvalidVotingPeriod: pass:normal[xref:#IGovernor-GovernorInvalidVotingPeriod-uint256-[`++GovernorInvalidVotingPeriod++`]] +:GovernorInsufficientProposerVotes: pass:normal[xref:#IGovernor-GovernorInsufficientProposerVotes-address-uint256-uint256-[`++GovernorInsufficientProposerVotes++`]] +:GovernorRestrictedProposer: pass:normal[xref:#IGovernor-GovernorRestrictedProposer-address-[`++GovernorRestrictedProposer++`]] +:GovernorInvalidVoteType: pass:normal[xref:#IGovernor-GovernorInvalidVoteType--[`++GovernorInvalidVoteType++`]] +:GovernorQueueNotImplemented: pass:normal[xref:#IGovernor-GovernorQueueNotImplemented--[`++GovernorQueueNotImplemented++`]] +:GovernorNotQueuedProposal: pass:normal[xref:#IGovernor-GovernorNotQueuedProposal-uint256-[`++GovernorNotQueuedProposal++`]] +:GovernorAlreadyQueuedProposal: pass:normal[xref:#IGovernor-GovernorAlreadyQueuedProposal-uint256-[`++GovernorAlreadyQueuedProposal++`]] +:GovernorInvalidSignature: pass:normal[xref:#IGovernor-GovernorInvalidSignature-address-[`++GovernorInvalidSignature++`]] +:ProposalCreated: pass:normal[xref:#IGovernor-ProposalCreated-uint256-address-address---uint256---string---bytes---uint256-uint256-string-[`++ProposalCreated++`]] +:ProposalQueued: pass:normal[xref:#IGovernor-ProposalQueued-uint256-uint256-[`++ProposalQueued++`]] +:ProposalExecuted: pass:normal[xref:#IGovernor-ProposalExecuted-uint256-[`++ProposalExecuted++`]] +:ProposalCanceled: pass:normal[xref:#IGovernor-ProposalCanceled-uint256-[`++ProposalCanceled++`]] +:VoteCast: pass:normal[xref:#IGovernor-VoteCast-address-uint256-uint8-uint256-string-[`++VoteCast++`]] +:VoteCastWithParams: pass:normal[xref:#IGovernor-VoteCastWithParams-address-uint256-uint8-uint256-string-bytes-[`++VoteCastWithParams++`]] +:name: pass:normal[xref:#IGovernor-name--[`++name++`]] +:version: pass:normal[xref:#IGovernor-version--[`++version++`]] +:COUNTING_MODE: pass:normal[xref:#IGovernor-COUNTING_MODE--[`++COUNTING_MODE++`]] +:hashProposal: pass:normal[xref:#IGovernor-hashProposal-address---uint256---bytes---bytes32-[`++hashProposal++`]] +:state: pass:normal[xref:#IGovernor-state-uint256-[`++state++`]] +:proposalThreshold: pass:normal[xref:#IGovernor-proposalThreshold--[`++proposalThreshold++`]] +:proposalSnapshot: pass:normal[xref:#IGovernor-proposalSnapshot-uint256-[`++proposalSnapshot++`]] +:proposalDeadline: pass:normal[xref:#IGovernor-proposalDeadline-uint256-[`++proposalDeadline++`]] +:proposalProposer: pass:normal[xref:#IGovernor-proposalProposer-uint256-[`++proposalProposer++`]] +:proposalEta: pass:normal[xref:#IGovernor-proposalEta-uint256-[`++proposalEta++`]] +:proposalNeedsQueuing: pass:normal[xref:#IGovernor-proposalNeedsQueuing-uint256-[`++proposalNeedsQueuing++`]] +:votingDelay: pass:normal[xref:#IGovernor-votingDelay--[`++votingDelay++`]] +:votingPeriod: pass:normal[xref:#IGovernor-votingPeriod--[`++votingPeriod++`]] +:quorum: pass:normal[xref:#IGovernor-quorum-uint256-[`++quorum++`]] +:getVotes: pass:normal[xref:#IGovernor-getVotes-address-uint256-[`++getVotes++`]] +:getVotesWithParams: pass:normal[xref:#IGovernor-getVotesWithParams-address-uint256-bytes-[`++getVotesWithParams++`]] +:hasVoted: pass:normal[xref:#IGovernor-hasVoted-uint256-address-[`++hasVoted++`]] +:propose: pass:normal[xref:#IGovernor-propose-address---uint256---bytes---string-[`++propose++`]] +:queue: pass:normal[xref:#IGovernor-queue-address---uint256---bytes---bytes32-[`++queue++`]] +:execute: pass:normal[xref:#IGovernor-execute-address---uint256---bytes---bytes32-[`++execute++`]] +:cancel: pass:normal[xref:#IGovernor-cancel-address---uint256---bytes---bytes32-[`++cancel++`]] +:castVote: pass:normal[xref:#IGovernor-castVote-uint256-uint8-[`++castVote++`]] +:castVoteWithReason: pass:normal[xref:#IGovernor-castVoteWithReason-uint256-uint8-string-[`++castVoteWithReason++`]] +:castVoteWithReasonAndParams: pass:normal[xref:#IGovernor-castVoteWithReasonAndParams-uint256-uint8-string-bytes-[`++castVoteWithReasonAndParams++`]] +:castVoteBySig: pass:normal[xref:#IGovernor-castVoteBySig-uint256-uint8-address-bytes-[`++castVoteBySig++`]] +:castVoteWithReasonAndParamsBySig: pass:normal[xref:#IGovernor-castVoteWithReasonAndParamsBySig-uint256-uint8-address-string-bytes-bytes-[`++castVoteWithReasonAndParamsBySig++`]] + +[.contract] +[[IGovernor]] +=== `++IGovernor++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/governance/IGovernor.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/governance/IGovernor.sol"; +``` + +Interface of the {Governor} core. + +[.contract-index] +.Functions +-- +* {xref-IGovernor-name--}[`++name()++`] +* {xref-IGovernor-version--}[`++version()++`] +* {xref-IGovernor-COUNTING_MODE--}[`++COUNTING_MODE()++`] +* {xref-IGovernor-hashProposal-address---uint256---bytes---bytes32-}[`++hashProposal(targets, values, calldatas, descriptionHash)++`] +* {xref-IGovernor-state-uint256-}[`++state(proposalId)++`] +* {xref-IGovernor-proposalThreshold--}[`++proposalThreshold()++`] +* {xref-IGovernor-proposalSnapshot-uint256-}[`++proposalSnapshot(proposalId)++`] +* {xref-IGovernor-proposalDeadline-uint256-}[`++proposalDeadline(proposalId)++`] +* {xref-IGovernor-proposalProposer-uint256-}[`++proposalProposer(proposalId)++`] +* {xref-IGovernor-proposalEta-uint256-}[`++proposalEta(proposalId)++`] +* {xref-IGovernor-proposalNeedsQueuing-uint256-}[`++proposalNeedsQueuing(proposalId)++`] +* {xref-IGovernor-votingDelay--}[`++votingDelay()++`] +* {xref-IGovernor-votingPeriod--}[`++votingPeriod()++`] +* {xref-IGovernor-quorum-uint256-}[`++quorum(timepoint)++`] +* {xref-IGovernor-getVotes-address-uint256-}[`++getVotes(account, timepoint)++`] +* {xref-IGovernor-getVotesWithParams-address-uint256-bytes-}[`++getVotesWithParams(account, timepoint, params)++`] +* {xref-IGovernor-hasVoted-uint256-address-}[`++hasVoted(proposalId, account)++`] +* {xref-IGovernor-propose-address---uint256---bytes---string-}[`++propose(targets, values, calldatas, description)++`] +* {xref-IGovernor-queue-address---uint256---bytes---bytes32-}[`++queue(targets, values, calldatas, descriptionHash)++`] +* {xref-IGovernor-execute-address---uint256---bytes---bytes32-}[`++execute(targets, values, calldatas, descriptionHash)++`] +* {xref-IGovernor-cancel-address---uint256---bytes---bytes32-}[`++cancel(targets, values, calldatas, descriptionHash)++`] +* {xref-IGovernor-castVote-uint256-uint8-}[`++castVote(proposalId, support)++`] +* {xref-IGovernor-castVoteWithReason-uint256-uint8-string-}[`++castVoteWithReason(proposalId, support, reason)++`] +* {xref-IGovernor-castVoteWithReasonAndParams-uint256-uint8-string-bytes-}[`++castVoteWithReasonAndParams(proposalId, support, reason, params)++`] +* {xref-IGovernor-castVoteBySig-uint256-uint8-address-bytes-}[`++castVoteBySig(proposalId, support, voter, signature)++`] +* {xref-IGovernor-castVoteWithReasonAndParamsBySig-uint256-uint8-address-string-bytes-bytes-}[`++castVoteWithReasonAndParamsBySig(proposalId, support, voter, reason, params, signature)++`] + +[.contract-subindex-inherited] +.IERC6372 +* {xref-IERC6372-clock--}[`++clock()++`] +* {xref-IERC6372-CLOCK_MODE--}[`++CLOCK_MODE()++`] + +[.contract-subindex-inherited] +.IERC165 +* {xref-IERC165-supportsInterface-bytes4-}[`++supportsInterface(interfaceId)++`] + +-- + +[.contract-index] +.Events +-- +* {xref-IGovernor-ProposalCreated-uint256-address-address---uint256---string---bytes---uint256-uint256-string-}[`++ProposalCreated(proposalId, proposer, targets, values, signatures, calldatas, voteStart, voteEnd, description)++`] +* {xref-IGovernor-ProposalQueued-uint256-uint256-}[`++ProposalQueued(proposalId, etaSeconds)++`] +* {xref-IGovernor-ProposalExecuted-uint256-}[`++ProposalExecuted(proposalId)++`] +* {xref-IGovernor-ProposalCanceled-uint256-}[`++ProposalCanceled(proposalId)++`] +* {xref-IGovernor-VoteCast-address-uint256-uint8-uint256-string-}[`++VoteCast(voter, proposalId, support, weight, reason)++`] +* {xref-IGovernor-VoteCastWithParams-address-uint256-uint8-uint256-string-bytes-}[`++VoteCastWithParams(voter, proposalId, support, weight, reason, params)++`] + +[.contract-subindex-inherited] +.IERC6372 + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-index] +.Errors +-- +* {xref-IGovernor-GovernorInvalidProposalLength-uint256-uint256-uint256-}[`++GovernorInvalidProposalLength(targets, calldatas, values)++`] +* {xref-IGovernor-GovernorAlreadyCastVote-address-}[`++GovernorAlreadyCastVote(voter)++`] +* {xref-IGovernor-GovernorDisabledDeposit--}[`++GovernorDisabledDeposit()++`] +* {xref-IGovernor-GovernorOnlyProposer-address-}[`++GovernorOnlyProposer(account)++`] +* {xref-IGovernor-GovernorOnlyExecutor-address-}[`++GovernorOnlyExecutor(account)++`] +* {xref-IGovernor-GovernorNonexistentProposal-uint256-}[`++GovernorNonexistentProposal(proposalId)++`] +* {xref-IGovernor-GovernorUnexpectedProposalState-uint256-enum-IGovernor-ProposalState-bytes32-}[`++GovernorUnexpectedProposalState(proposalId, current, expectedStates)++`] +* {xref-IGovernor-GovernorInvalidVotingPeriod-uint256-}[`++GovernorInvalidVotingPeriod(votingPeriod)++`] +* {xref-IGovernor-GovernorInsufficientProposerVotes-address-uint256-uint256-}[`++GovernorInsufficientProposerVotes(proposer, votes, threshold)++`] +* {xref-IGovernor-GovernorRestrictedProposer-address-}[`++GovernorRestrictedProposer(proposer)++`] +* {xref-IGovernor-GovernorInvalidVoteType--}[`++GovernorInvalidVoteType()++`] +* {xref-IGovernor-GovernorQueueNotImplemented--}[`++GovernorQueueNotImplemented()++`] +* {xref-IGovernor-GovernorNotQueuedProposal-uint256-}[`++GovernorNotQueuedProposal(proposalId)++`] +* {xref-IGovernor-GovernorAlreadyQueuedProposal-uint256-}[`++GovernorAlreadyQueuedProposal(proposalId)++`] +* {xref-IGovernor-GovernorInvalidSignature-address-}[`++GovernorInvalidSignature(voter)++`] + +[.contract-subindex-inherited] +.IERC6372 + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-item] +[[IGovernor-name--]] +==== `[.contract-item-name]#++name++#++() β†’ string++` [.item-kind]#external# + +Name of the governor instance (used in building the ERC712 domain separator). + +[.contract-item] +[[IGovernor-version--]] +==== `[.contract-item-name]#++version++#++() β†’ string++` [.item-kind]#external# + +Version of the governor instance (used in building the ERC712 domain separator). Default: "1" + +[.contract-item] +[[IGovernor-COUNTING_MODE--]] +==== `[.contract-item-name]#++COUNTING_MODE++#++() β†’ string++` [.item-kind]#external# + +A description of the possible `support` values for {castVote} and the way these votes are counted, meant to +be consumed by UIs to show correct vote options and interpret the results. The string is a URL-encoded sequence of +key-value pairs that each describe one aspect, for example `support=bravo&quorum=for,abstain`. + +There are 2 standard keys: `support` and `quorum`. + +- `support=bravo` refers to the vote options 0 = Against, 1 = For, 2 = Abstain, as in `GovernorBravo`. +- `quorum=bravo` means that only For votes are counted towards quorum. +- `quorum=for,abstain` means that both For and Abstain votes are counted towards quorum. + +If a counting module makes use of encoded `params`, it should include this under a `params` key with a unique +name that describes the behavior. For example: + +- `params=fractional` might refer to a scheme where votes are divided fractionally between for/against/abstain. +- `params=erc721` might refer to a scheme where specific NFTs are delegated to vote. + +NOTE: The string can be decoded by the standard +https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams[`URLSearchParams`] +JavaScript class. + +[.contract-item] +[[IGovernor-hashProposal-address---uint256---bytes---bytes32-]] +==== `[.contract-item-name]#++hashProposal++#++(address[] targets, uint256[] values, bytes[] calldatas, bytes32 descriptionHash) β†’ uint256++` [.item-kind]#external# + +Hashing function used to (re)build the proposal id from the proposal details.. + +[.contract-item] +[[IGovernor-state-uint256-]] +==== `[.contract-item-name]#++state++#++(uint256 proposalId) β†’ enum IGovernor.ProposalState++` [.item-kind]#external# + +Current state of a proposal, following Compound's convention + +[.contract-item] +[[IGovernor-proposalThreshold--]] +==== `[.contract-item-name]#++proposalThreshold++#++() β†’ uint256++` [.item-kind]#external# + +The number of votes required in order for a voter to become a proposer. + +[.contract-item] +[[IGovernor-proposalSnapshot-uint256-]] +==== `[.contract-item-name]#++proposalSnapshot++#++(uint256 proposalId) β†’ uint256++` [.item-kind]#external# + +Timepoint used to retrieve user's votes and quorum. If using block number (as per Compound's Comp), the +snapshot is performed at the end of this block. Hence, voting for this proposal starts at the beginning of the +following block. + +[.contract-item] +[[IGovernor-proposalDeadline-uint256-]] +==== `[.contract-item-name]#++proposalDeadline++#++(uint256 proposalId) β†’ uint256++` [.item-kind]#external# + +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. + +[.contract-item] +[[IGovernor-proposalProposer-uint256-]] +==== `[.contract-item-name]#++proposalProposer++#++(uint256 proposalId) β†’ address++` [.item-kind]#external# + +The account that created a proposal. + +[.contract-item] +[[IGovernor-proposalEta-uint256-]] +==== `[.contract-item-name]#++proposalEta++#++(uint256 proposalId) β†’ uint256++` [.item-kind]#external# + +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. + +[.contract-item] +[[IGovernor-proposalNeedsQueuing-uint256-]] +==== `[.contract-item-name]#++proposalNeedsQueuing++#++(uint256 proposalId) β†’ bool++` [.item-kind]#external# + +Whether a proposal needs to be queued before execution. + +[.contract-item] +[[IGovernor-votingDelay--]] +==== `[.contract-item-name]#++votingDelay++#++() β†’ uint256++` [.item-kind]#external# + +Delay, between the proposal is created and the vote starts. The unit this duration is expressed in depends +on the clock (see EIP-6372) this contract uses. + +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}. + +[.contract-item] +[[IGovernor-votingPeriod--]] +==== `[.contract-item-name]#++votingPeriod++#++() β†’ uint256++` [.item-kind]#external# + +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 +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. + +[.contract-item] +[[IGovernor-quorum-uint256-]] +==== `[.contract-item-name]#++quorum++#++(uint256 timepoint) β†’ uint256++` [.item-kind]#external# + +Minimum number of cast voted required for a proposal to be successful. + +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}). + +[.contract-item] +[[IGovernor-getVotes-address-uint256-]] +==== `[.contract-item-name]#++getVotes++#++(address account, uint256 timepoint) β†’ uint256++` [.item-kind]#external# + +Voting power of an `account` at a specific `timepoint`. + +Note: this can be implemented in a number of ways, for example by reading the delegated balance from one (or +multiple), {ERC20Votes} tokens. + +[.contract-item] +[[IGovernor-getVotesWithParams-address-uint256-bytes-]] +==== `[.contract-item-name]#++getVotesWithParams++#++(address account, uint256 timepoint, bytes params) β†’ uint256++` [.item-kind]#external# + +Voting power of an `account` at a specific `timepoint` given additional encoded parameters. + +[.contract-item] +[[IGovernor-hasVoted-uint256-address-]] +==== `[.contract-item-name]#++hasVoted++#++(uint256 proposalId, address account) β†’ bool++` [.item-kind]#external# + +Returns whether `account` has cast a vote on `proposalId`. + +[.contract-item] +[[IGovernor-propose-address---uint256---bytes---string-]] +==== `[.contract-item-name]#++propose++#++(address[] targets, uint256[] values, bytes[] calldatas, string description) β†’ uint256 proposalId++` [.item-kind]#external# + +Create a new proposal. Vote start after a delay specified by {IGovernor-votingDelay} and lasts for a +duration specified by {IGovernor-votingPeriod}. + +Emits a {ProposalCreated} event. + +[.contract-item] +[[IGovernor-queue-address---uint256---bytes---bytes32-]] +==== `[.contract-item-name]#++queue++#++(address[] targets, uint256[] values, bytes[] calldatas, bytes32 descriptionHash) β†’ uint256 proposalId++` [.item-kind]#external# + +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. + +[.contract-item] +[[IGovernor-execute-address---uint256---bytes---bytes32-]] +==== `[.contract-item-name]#++execute++#++(address[] targets, uint256[] values, bytes[] calldatas, bytes32 descriptionHash) β†’ uint256 proposalId++` [.item-kind]#external# + +Execute a successful proposal. This requires the quorum to be reached, the vote to be successful, and the +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 modules can modify the requirements for execution, for example by adding an additional timelock. + +[.contract-item] +[[IGovernor-cancel-address---uint256---bytes---bytes32-]] +==== `[.contract-item-name]#++cancel++#++(address[] targets, uint256[] values, bytes[] calldatas, bytes32 descriptionHash) β†’ uint256 proposalId++` [.item-kind]#external# + +Cancel a proposal. A proposal is cancellable by the proposer, but only while it is Pending state, i.e. +before the vote starts. + +Emits a {ProposalCanceled} event. + +[.contract-item] +[[IGovernor-castVote-uint256-uint8-]] +==== `[.contract-item-name]#++castVote++#++(uint256 proposalId, uint8 support) β†’ uint256 balance++` [.item-kind]#external# + +Cast a vote + +Emits a {VoteCast} event. + +[.contract-item] +[[IGovernor-castVoteWithReason-uint256-uint8-string-]] +==== `[.contract-item-name]#++castVoteWithReason++#++(uint256 proposalId, uint8 support, string reason) β†’ uint256 balance++` [.item-kind]#external# + +Cast a vote with a reason + +Emits a {VoteCast} event. + +[.contract-item] +[[IGovernor-castVoteWithReasonAndParams-uint256-uint8-string-bytes-]] +==== `[.contract-item-name]#++castVoteWithReasonAndParams++#++(uint256 proposalId, uint8 support, string reason, bytes params) β†’ uint256 balance++` [.item-kind]#external# + +Cast a vote with a reason and additional encoded parameters + +Emits a {VoteCast} or {VoteCastWithParams} event depending on the length of params. + +[.contract-item] +[[IGovernor-castVoteBySig-uint256-uint8-address-bytes-]] +==== `[.contract-item-name]#++castVoteBySig++#++(uint256 proposalId, uint8 support, address voter, bytes signature) β†’ uint256 balance++` [.item-kind]#external# + +Cast a vote using the voter's signature, including ERC-1271 signature support. + +Emits a {VoteCast} event. + +[.contract-item] +[[IGovernor-castVoteWithReasonAndParamsBySig-uint256-uint8-address-string-bytes-bytes-]] +==== `[.contract-item-name]#++castVoteWithReasonAndParamsBySig++#++(uint256 proposalId, uint8 support, address voter, string reason, bytes params, bytes signature) β†’ uint256 balance++` [.item-kind]#external# + +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. + +[.contract-item] +[[IGovernor-ProposalCreated-uint256-address-address---uint256---string---bytes---uint256-uint256-string-]] +==== `[.contract-item-name]#++ProposalCreated++#++(uint256 proposalId, address proposer, address[] targets, uint256[] values, string[] signatures, bytes[] calldatas, uint256 voteStart, uint256 voteEnd, string description)++` [.item-kind]#event# + +Emitted when a proposal is created. + +[.contract-item] +[[IGovernor-ProposalQueued-uint256-uint256-]] +==== `[.contract-item-name]#++ProposalQueued++#++(uint256 proposalId, uint256 etaSeconds)++` [.item-kind]#event# + +Emitted when a proposal is queued. + +[.contract-item] +[[IGovernor-ProposalExecuted-uint256-]] +==== `[.contract-item-name]#++ProposalExecuted++#++(uint256 proposalId)++` [.item-kind]#event# + +Emitted when a proposal is executed. + +[.contract-item] +[[IGovernor-ProposalCanceled-uint256-]] +==== `[.contract-item-name]#++ProposalCanceled++#++(uint256 proposalId)++` [.item-kind]#event# + +Emitted when a proposal is canceled. + +[.contract-item] +[[IGovernor-VoteCast-address-uint256-uint8-uint256-string-]] +==== `[.contract-item-name]#++VoteCast++#++(address indexed voter, uint256 proposalId, uint8 support, uint256 weight, string reason)++` [.item-kind]#event# + +Emitted when a vote is cast without params. + +Note: `support` values should be seen as buckets. Their interpretation depends on the voting module used. + +[.contract-item] +[[IGovernor-VoteCastWithParams-address-uint256-uint8-uint256-string-bytes-]] +==== `[.contract-item-name]#++VoteCastWithParams++#++(address indexed voter, uint256 proposalId, uint8 support, uint256 weight, string reason, bytes params)++` [.item-kind]#event# + +Emitted when a vote is cast with params. + +Note: `support` values should be seen as buckets. Their interpretation depends on the voting module used. +`params` are additional encoded parameters. Their interpepretation also depends on the voting module used. + +[.contract-item] +[[IGovernor-GovernorInvalidProposalLength-uint256-uint256-uint256-]] +==== `[.contract-item-name]#++GovernorInvalidProposalLength++#++(uint256 targets, uint256 calldatas, uint256 values)++` [.item-kind]#error# + +Empty proposal or a mismatch between the parameters length for a proposal call. + +[.contract-item] +[[IGovernor-GovernorAlreadyCastVote-address-]] +==== `[.contract-item-name]#++GovernorAlreadyCastVote++#++(address voter)++` [.item-kind]#error# + +The vote was already cast. + +[.contract-item] +[[IGovernor-GovernorDisabledDeposit--]] +==== `[.contract-item-name]#++GovernorDisabledDeposit++#++()++` [.item-kind]#error# + +Token deposits are disabled in this contract. + +[.contract-item] +[[IGovernor-GovernorOnlyProposer-address-]] +==== `[.contract-item-name]#++GovernorOnlyProposer++#++(address account)++` [.item-kind]#error# + +The `account` is not a proposer. + +[.contract-item] +[[IGovernor-GovernorOnlyExecutor-address-]] +==== `[.contract-item-name]#++GovernorOnlyExecutor++#++(address account)++` [.item-kind]#error# + +The `account` is not the governance executor. + +[.contract-item] +[[IGovernor-GovernorNonexistentProposal-uint256-]] +==== `[.contract-item-name]#++GovernorNonexistentProposal++#++(uint256 proposalId)++` [.item-kind]#error# + +The `proposalId` doesn't exist. + +[.contract-item] +[[IGovernor-GovernorUnexpectedProposalState-uint256-enum-IGovernor-ProposalState-bytes32-]] +==== `[.contract-item-name]#++GovernorUnexpectedProposalState++#++(uint256 proposalId, enum IGovernor.ProposalState current, bytes32 expectedStates)++` [.item-kind]#error# + +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}. + +[.contract-item] +[[IGovernor-GovernorInvalidVotingPeriod-uint256-]] +==== `[.contract-item-name]#++GovernorInvalidVotingPeriod++#++(uint256 votingPeriod)++` [.item-kind]#error# + +The voting period set is not a valid period. + +[.contract-item] +[[IGovernor-GovernorInsufficientProposerVotes-address-uint256-uint256-]] +==== `[.contract-item-name]#++GovernorInsufficientProposerVotes++#++(address proposer, uint256 votes, uint256 threshold)++` [.item-kind]#error# + +The `proposer` does not have the required votes to create a proposal. + +[.contract-item] +[[IGovernor-GovernorRestrictedProposer-address-]] +==== `[.contract-item-name]#++GovernorRestrictedProposer++#++(address proposer)++` [.item-kind]#error# + +The `proposer` is not allowed to create a proposal. + +[.contract-item] +[[IGovernor-GovernorInvalidVoteType--]] +==== `[.contract-item-name]#++GovernorInvalidVoteType++#++()++` [.item-kind]#error# + +The vote type used is not valid for the corresponding counting module. + +[.contract-item] +[[IGovernor-GovernorQueueNotImplemented--]] +==== `[.contract-item-name]#++GovernorQueueNotImplemented++#++()++` [.item-kind]#error# + +Queue operation is not implemented for this governor. Execute should be called directly. + +[.contract-item] +[[IGovernor-GovernorNotQueuedProposal-uint256-]] +==== `[.contract-item-name]#++GovernorNotQueuedProposal++#++(uint256 proposalId)++` [.item-kind]#error# + +The proposal hasn't been queued yet. + +[.contract-item] +[[IGovernor-GovernorAlreadyQueuedProposal-uint256-]] +==== `[.contract-item-name]#++GovernorAlreadyQueuedProposal++#++(uint256 proposalId)++` [.item-kind]#error# + +The proposal has already been queued. + +[.contract-item] +[[IGovernor-GovernorInvalidSignature-address-]] +==== `[.contract-item-name]#++GovernorInvalidSignature++#++(address voter)++` [.item-kind]#error# + +The provided signature is not valid for the expected `voter`. +If the `voter` is a contract, the signature is not valid using {IERC1271-isValidSignature}. + +:BALLOT_TYPEHASH: pass:normal[xref:#Governor-BALLOT_TYPEHASH-bytes32[`++BALLOT_TYPEHASH++`]] +:EXTENDED_BALLOT_TYPEHASH: pass:normal[xref:#Governor-EXTENDED_BALLOT_TYPEHASH-bytes32[`++EXTENDED_BALLOT_TYPEHASH++`]] +:ProposalCore: pass:normal[xref:#Governor-ProposalCore[`++ProposalCore++`]] +:onlyGovernance: pass:normal[xref:#Governor-onlyGovernance--[`++onlyGovernance++`]] +:constructor: pass:normal[xref:#Governor-constructor-string-[`++constructor++`]] +:receive: pass:normal[xref:#Governor-receive--[`++receive++`]] +:supportsInterface: pass:normal[xref:#Governor-supportsInterface-bytes4-[`++supportsInterface++`]] +:name: pass:normal[xref:#Governor-name--[`++name++`]] +:version: pass:normal[xref:#Governor-version--[`++version++`]] +:hashProposal: pass:normal[xref:#Governor-hashProposal-address---uint256---bytes---bytes32-[`++hashProposal++`]] +:state: pass:normal[xref:#Governor-state-uint256-[`++state++`]] +:proposalThreshold: pass:normal[xref:#Governor-proposalThreshold--[`++proposalThreshold++`]] +:proposalSnapshot: pass:normal[xref:#Governor-proposalSnapshot-uint256-[`++proposalSnapshot++`]] +:proposalDeadline: pass:normal[xref:#Governor-proposalDeadline-uint256-[`++proposalDeadline++`]] +:proposalProposer: pass:normal[xref:#Governor-proposalProposer-uint256-[`++proposalProposer++`]] +:proposalEta: pass:normal[xref:#Governor-proposalEta-uint256-[`++proposalEta++`]] +:proposalNeedsQueuing: pass:normal[xref:#Governor-proposalNeedsQueuing-uint256-[`++proposalNeedsQueuing++`]] +:_checkGovernance: pass:normal[xref:#Governor-_checkGovernance--[`++_checkGovernance++`]] +:_quorumReached: pass:normal[xref:#Governor-_quorumReached-uint256-[`++_quorumReached++`]] +:_voteSucceeded: pass:normal[xref:#Governor-_voteSucceeded-uint256-[`++_voteSucceeded++`]] +:_getVotes: pass:normal[xref:#Governor-_getVotes-address-uint256-bytes-[`++_getVotes++`]] +:_countVote: pass:normal[xref:#Governor-_countVote-uint256-address-uint8-uint256-bytes-[`++_countVote++`]] +:_defaultParams: pass:normal[xref:#Governor-_defaultParams--[`++_defaultParams++`]] +:propose: pass:normal[xref:#Governor-propose-address---uint256---bytes---string-[`++propose++`]] +:_propose: pass:normal[xref:#Governor-_propose-address---uint256---bytes---string-address-[`++_propose++`]] +:queue: pass:normal[xref:#Governor-queue-address---uint256---bytes---bytes32-[`++queue++`]] +:_queueOperations: pass:normal[xref:#Governor-_queueOperations-uint256-address---uint256---bytes---bytes32-[`++_queueOperations++`]] +:execute: pass:normal[xref:#Governor-execute-address---uint256---bytes---bytes32-[`++execute++`]] +:_executeOperations: pass:normal[xref:#Governor-_executeOperations-uint256-address---uint256---bytes---bytes32-[`++_executeOperations++`]] +:cancel: pass:normal[xref:#Governor-cancel-address---uint256---bytes---bytes32-[`++cancel++`]] +:_cancel: pass:normal[xref:#Governor-_cancel-address---uint256---bytes---bytes32-[`++_cancel++`]] +:getVotes: pass:normal[xref:#Governor-getVotes-address-uint256-[`++getVotes++`]] +:getVotesWithParams: pass:normal[xref:#Governor-getVotesWithParams-address-uint256-bytes-[`++getVotesWithParams++`]] +:castVote: pass:normal[xref:#Governor-castVote-uint256-uint8-[`++castVote++`]] +:castVoteWithReason: pass:normal[xref:#Governor-castVoteWithReason-uint256-uint8-string-[`++castVoteWithReason++`]] +:castVoteWithReasonAndParams: pass:normal[xref:#Governor-castVoteWithReasonAndParams-uint256-uint8-string-bytes-[`++castVoteWithReasonAndParams++`]] +:castVoteBySig: pass:normal[xref:#Governor-castVoteBySig-uint256-uint8-address-bytes-[`++castVoteBySig++`]] +:castVoteWithReasonAndParamsBySig: pass:normal[xref:#Governor-castVoteWithReasonAndParamsBySig-uint256-uint8-address-string-bytes-bytes-[`++castVoteWithReasonAndParamsBySig++`]] +:_castVote: pass:normal[xref:#Governor-_castVote-uint256-address-uint8-string-[`++_castVote++`]] +:_castVote: pass:normal[xref:#Governor-_castVote-uint256-address-uint8-string-bytes-[`++_castVote++`]] +:relay: pass:normal[xref:#Governor-relay-address-uint256-bytes-[`++relay++`]] +:_executor: pass:normal[xref:#Governor-_executor--[`++_executor++`]] +:onERC721Received: pass:normal[xref:#Governor-onERC721Received-address-address-uint256-bytes-[`++onERC721Received++`]] +:onERC1155Received: pass:normal[xref:#Governor-onERC1155Received-address-address-uint256-uint256-bytes-[`++onERC1155Received++`]] +:onERC1155BatchReceived: pass:normal[xref:#Governor-onERC1155BatchReceived-address-address-uint256---uint256---bytes-[`++onERC1155BatchReceived++`]] +:_encodeStateBitmap: pass:normal[xref:#Governor-_encodeStateBitmap-enum-IGovernor-ProposalState-[`++_encodeStateBitmap++`]] +:_isValidDescriptionForProposer: pass:normal[xref:#Governor-_isValidDescriptionForProposer-address-string-[`++_isValidDescriptionForProposer++`]] +:clock: pass:normal[xref:#Governor-clock--[`++clock++`]] +:CLOCK_MODE: pass:normal[xref:#Governor-CLOCK_MODE--[`++CLOCK_MODE++`]] +:votingDelay: pass:normal[xref:#Governor-votingDelay--[`++votingDelay++`]] +:votingPeriod: pass:normal[xref:#Governor-votingPeriod--[`++votingPeriod++`]] +:quorum: pass:normal[xref:#Governor-quorum-uint256-[`++quorum++`]] + +[.contract] +[[Governor]] +=== `++Governor++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/governance/Governor.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/governance/Governor.sol"; +``` + +Core of the governance system, designed to be extended through 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} +- Additionally, {votingPeriod} must also be implemented + +[.contract-index] +.Modifiers +-- +* {xref-Governor-onlyGovernance--}[`++onlyGovernance()++`] +-- + +[.contract-index] +.Functions +-- +* {xref-Governor-constructor-string-}[`++constructor(name_)++`] +* {xref-Governor-receive--}[`++receive()++`] +* {xref-Governor-supportsInterface-bytes4-}[`++supportsInterface(interfaceId)++`] +* {xref-Governor-name--}[`++name()++`] +* {xref-Governor-version--}[`++version()++`] +* {xref-Governor-hashProposal-address---uint256---bytes---bytes32-}[`++hashProposal(targets, values, calldatas, descriptionHash)++`] +* {xref-Governor-state-uint256-}[`++state(proposalId)++`] +* {xref-Governor-proposalThreshold--}[`++proposalThreshold()++`] +* {xref-Governor-proposalSnapshot-uint256-}[`++proposalSnapshot(proposalId)++`] +* {xref-Governor-proposalDeadline-uint256-}[`++proposalDeadline(proposalId)++`] +* {xref-Governor-proposalProposer-uint256-}[`++proposalProposer(proposalId)++`] +* {xref-Governor-proposalEta-uint256-}[`++proposalEta(proposalId)++`] +* {xref-Governor-proposalNeedsQueuing-uint256-}[`++proposalNeedsQueuing()++`] +* {xref-Governor-_checkGovernance--}[`++_checkGovernance()++`] +* {xref-Governor-_quorumReached-uint256-}[`++_quorumReached(proposalId)++`] +* {xref-Governor-_voteSucceeded-uint256-}[`++_voteSucceeded(proposalId)++`] +* {xref-Governor-_getVotes-address-uint256-bytes-}[`++_getVotes(account, timepoint, params)++`] +* {xref-Governor-_countVote-uint256-address-uint8-uint256-bytes-}[`++_countVote(proposalId, account, support, weight, params)++`] +* {xref-Governor-_defaultParams--}[`++_defaultParams()++`] +* {xref-Governor-propose-address---uint256---bytes---string-}[`++propose(targets, values, calldatas, description)++`] +* {xref-Governor-_propose-address---uint256---bytes---string-address-}[`++_propose(targets, values, calldatas, description, proposer)++`] +* {xref-Governor-queue-address---uint256---bytes---bytes32-}[`++queue(targets, values, calldatas, descriptionHash)++`] +* {xref-Governor-_queueOperations-uint256-address---uint256---bytes---bytes32-}[`++_queueOperations(, , , , )++`] +* {xref-Governor-execute-address---uint256---bytes---bytes32-}[`++execute(targets, values, calldatas, descriptionHash)++`] +* {xref-Governor-_executeOperations-uint256-address---uint256---bytes---bytes32-}[`++_executeOperations(, targets, values, calldatas, )++`] +* {xref-Governor-cancel-address---uint256---bytes---bytes32-}[`++cancel(targets, values, calldatas, descriptionHash)++`] +* {xref-Governor-_cancel-address---uint256---bytes---bytes32-}[`++_cancel(targets, values, calldatas, descriptionHash)++`] +* {xref-Governor-getVotes-address-uint256-}[`++getVotes(account, timepoint)++`] +* {xref-Governor-getVotesWithParams-address-uint256-bytes-}[`++getVotesWithParams(account, timepoint, params)++`] +* {xref-Governor-castVote-uint256-uint8-}[`++castVote(proposalId, support)++`] +* {xref-Governor-castVoteWithReason-uint256-uint8-string-}[`++castVoteWithReason(proposalId, support, reason)++`] +* {xref-Governor-castVoteWithReasonAndParams-uint256-uint8-string-bytes-}[`++castVoteWithReasonAndParams(proposalId, support, reason, params)++`] +* {xref-Governor-castVoteBySig-uint256-uint8-address-bytes-}[`++castVoteBySig(proposalId, support, voter, signature)++`] +* {xref-Governor-castVoteWithReasonAndParamsBySig-uint256-uint8-address-string-bytes-bytes-}[`++castVoteWithReasonAndParamsBySig(proposalId, support, voter, reason, params, signature)++`] +* {xref-Governor-_castVote-uint256-address-uint8-string-}[`++_castVote(proposalId, account, support, reason)++`] +* {xref-Governor-_castVote-uint256-address-uint8-string-bytes-}[`++_castVote(proposalId, account, support, reason, params)++`] +* {xref-Governor-relay-address-uint256-bytes-}[`++relay(target, value, data)++`] +* {xref-Governor-_executor--}[`++_executor()++`] +* {xref-Governor-onERC721Received-address-address-uint256-bytes-}[`++onERC721Received(, , , )++`] +* {xref-Governor-onERC1155Received-address-address-uint256-uint256-bytes-}[`++onERC1155Received(, , , , )++`] +* {xref-Governor-onERC1155BatchReceived-address-address-uint256---uint256---bytes-}[`++onERC1155BatchReceived(, , , , )++`] +* {xref-Governor-_encodeStateBitmap-enum-IGovernor-ProposalState-}[`++_encodeStateBitmap(proposalState)++`] +* {xref-Governor-_isValidDescriptionForProposer-address-string-}[`++_isValidDescriptionForProposer(proposer, description)++`] +* {xref-Governor-clock--}[`++clock()++`] +* {xref-Governor-CLOCK_MODE--}[`++CLOCK_MODE()++`] +* {xref-Governor-votingDelay--}[`++votingDelay()++`] +* {xref-Governor-votingPeriod--}[`++votingPeriod()++`] +* {xref-Governor-quorum-uint256-}[`++quorum(timepoint)++`] + +[.contract-subindex-inherited] +.IERC1155Receiver + +[.contract-subindex-inherited] +.IERC721Receiver + +[.contract-subindex-inherited] +.IGovernor +* {xref-IGovernor-COUNTING_MODE--}[`++COUNTING_MODE()++`] +* {xref-IGovernor-hasVoted-uint256-address-}[`++hasVoted(proposalId, account)++`] + +[.contract-subindex-inherited] +.IERC6372 + +[.contract-subindex-inherited] +.Nonces +* {xref-Nonces-nonces-address-}[`++nonces(owner)++`] +* {xref-Nonces-_useNonce-address-}[`++_useNonce(owner)++`] +* {xref-Nonces-_useCheckedNonce-address-uint256-}[`++_useCheckedNonce(owner, nonce)++`] + +[.contract-subindex-inherited] +.EIP712 +* {xref-EIP712-_domainSeparatorV4--}[`++_domainSeparatorV4()++`] +* {xref-EIP712-_hashTypedDataV4-bytes32-}[`++_hashTypedDataV4(structHash)++`] +* {xref-EIP712-eip712Domain--}[`++eip712Domain()++`] +* {xref-EIP712-_EIP712Name--}[`++_EIP712Name()++`] +* {xref-EIP712-_EIP712Version--}[`++_EIP712Version()++`] + +[.contract-subindex-inherited] +.IERC5267 + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-index] +.Events +-- + +[.contract-subindex-inherited] +.IERC1155Receiver + +[.contract-subindex-inherited] +.IERC721Receiver + +[.contract-subindex-inherited] +.IGovernor +* {xref-IGovernor-ProposalCreated-uint256-address-address---uint256---string---bytes---uint256-uint256-string-}[`++ProposalCreated(proposalId, proposer, targets, values, signatures, calldatas, voteStart, voteEnd, description)++`] +* {xref-IGovernor-ProposalQueued-uint256-uint256-}[`++ProposalQueued(proposalId, etaSeconds)++`] +* {xref-IGovernor-ProposalExecuted-uint256-}[`++ProposalExecuted(proposalId)++`] +* {xref-IGovernor-ProposalCanceled-uint256-}[`++ProposalCanceled(proposalId)++`] +* {xref-IGovernor-VoteCast-address-uint256-uint8-uint256-string-}[`++VoteCast(voter, proposalId, support, weight, reason)++`] +* {xref-IGovernor-VoteCastWithParams-address-uint256-uint8-uint256-string-bytes-}[`++VoteCastWithParams(voter, proposalId, support, weight, reason, params)++`] + +[.contract-subindex-inherited] +.IERC6372 + +[.contract-subindex-inherited] +.Nonces + +[.contract-subindex-inherited] +.EIP712 + +[.contract-subindex-inherited] +.IERC5267 +* {xref-IERC5267-EIP712DomainChanged--}[`++EIP712DomainChanged()++`] + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-index] +.Errors +-- + +[.contract-subindex-inherited] +.IERC1155Receiver + +[.contract-subindex-inherited] +.IERC721Receiver + +[.contract-subindex-inherited] +.IGovernor +* {xref-IGovernor-GovernorInvalidProposalLength-uint256-uint256-uint256-}[`++GovernorInvalidProposalLength(targets, calldatas, values)++`] +* {xref-IGovernor-GovernorAlreadyCastVote-address-}[`++GovernorAlreadyCastVote(voter)++`] +* {xref-IGovernor-GovernorDisabledDeposit--}[`++GovernorDisabledDeposit()++`] +* {xref-IGovernor-GovernorOnlyProposer-address-}[`++GovernorOnlyProposer(account)++`] +* {xref-IGovernor-GovernorOnlyExecutor-address-}[`++GovernorOnlyExecutor(account)++`] +* {xref-IGovernor-GovernorNonexistentProposal-uint256-}[`++GovernorNonexistentProposal(proposalId)++`] +* {xref-IGovernor-GovernorUnexpectedProposalState-uint256-enum-IGovernor-ProposalState-bytes32-}[`++GovernorUnexpectedProposalState(proposalId, current, expectedStates)++`] +* {xref-IGovernor-GovernorInvalidVotingPeriod-uint256-}[`++GovernorInvalidVotingPeriod(votingPeriod)++`] +* {xref-IGovernor-GovernorInsufficientProposerVotes-address-uint256-uint256-}[`++GovernorInsufficientProposerVotes(proposer, votes, threshold)++`] +* {xref-IGovernor-GovernorRestrictedProposer-address-}[`++GovernorRestrictedProposer(proposer)++`] +* {xref-IGovernor-GovernorInvalidVoteType--}[`++GovernorInvalidVoteType()++`] +* {xref-IGovernor-GovernorQueueNotImplemented--}[`++GovernorQueueNotImplemented()++`] +* {xref-IGovernor-GovernorNotQueuedProposal-uint256-}[`++GovernorNotQueuedProposal(proposalId)++`] +* {xref-IGovernor-GovernorAlreadyQueuedProposal-uint256-}[`++GovernorAlreadyQueuedProposal(proposalId)++`] +* {xref-IGovernor-GovernorInvalidSignature-address-}[`++GovernorInvalidSignature(voter)++`] + +[.contract-subindex-inherited] +.IERC6372 + +[.contract-subindex-inherited] +.Nonces +* {xref-Nonces-InvalidAccountNonce-address-uint256-}[`++InvalidAccountNonce(account, currentNonce)++`] + +[.contract-subindex-inherited] +.EIP712 + +[.contract-subindex-inherited] +.IERC5267 + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-item] +[[Governor-onlyGovernance--]] +==== `[.contract-item-name]#++onlyGovernance++#++()++` [.item-kind]#modifier# + +Restricts a function so it can only be executed through governance proposals. For example, governance +parameter setters in {GovernorSettings} are protected using this modifier. + +The governance executing address may be different from the Governor's own address, for example it could be a +timelock. This can be customized by modules by overriding {_executor}. The executor is only able to invoke these +functions during the execution of the governor's {execute} function, and not under any other circumstances. Thus, +for example, additional timelock proposers are not able to change governance parameters without going through the +governance protocol (since v4.6). + +[.contract-item] +[[Governor-constructor-string-]] +==== `[.contract-item-name]#++constructor++#++(string name_)++` [.item-kind]#internal# + +Sets the value for {name} and {version} + +[.contract-item] +[[Governor-receive--]] +==== `[.contract-item-name]#++receive++#++()++` [.item-kind]#external# + +Function to receive ETH that will be handled by the governor (disabled if executor is a third party contract) + +[.contract-item] +[[Governor-supportsInterface-bytes4-]] +==== `[.contract-item-name]#++supportsInterface++#++(bytes4 interfaceId) β†’ bool++` [.item-kind]#public# + +See {IERC165-supportsInterface}. + +[.contract-item] +[[Governor-name--]] +==== `[.contract-item-name]#++name++#++() β†’ string++` [.item-kind]#public# + +See {IGovernor-name}. + +[.contract-item] +[[Governor-version--]] +==== `[.contract-item-name]#++version++#++() β†’ string++` [.item-kind]#public# + +See {IGovernor-version}. + +[.contract-item] +[[Governor-hashProposal-address---uint256---bytes---bytes32-]] +==== `[.contract-item-name]#++hashProposal++#++(address[] targets, uint256[] values, bytes[] calldatas, bytes32 descriptionHash) β†’ uint256++` [.item-kind]#public# + +See {IGovernor-hashProposal}. + +The proposal id is produced by hashing the ABI encoded `targets` array, the `values` array, the `calldatas` array +and the descriptionHash (bytes32 which itself is the keccak256 hash of the description string). This proposal id +can be produced from the proposal data which is part of the {ProposalCreated} event. It can even be computed in +advance, before the proposal is submitted. + +Note that the chainId and the governor address are not part of the proposal id computation. Consequently, the +same proposal (with same operation and same description) will have the same id if submitted on multiple governors +across multiple networks. This also means that in order to execute the same operation twice (on the same +governor) the proposer will have to change the description in order to avoid proposal id conflicts. + +[.contract-item] +[[Governor-state-uint256-]] +==== `[.contract-item-name]#++state++#++(uint256 proposalId) β†’ enum IGovernor.ProposalState++` [.item-kind]#public# + +See {IGovernor-state}. + +[.contract-item] +[[Governor-proposalThreshold--]] +==== `[.contract-item-name]#++proposalThreshold++#++() β†’ uint256++` [.item-kind]#public# + +See {IGovernor-proposalThreshold}. + +[.contract-item] +[[Governor-proposalSnapshot-uint256-]] +==== `[.contract-item-name]#++proposalSnapshot++#++(uint256 proposalId) β†’ uint256++` [.item-kind]#public# + +See {IGovernor-proposalSnapshot}. + +[.contract-item] +[[Governor-proposalDeadline-uint256-]] +==== `[.contract-item-name]#++proposalDeadline++#++(uint256 proposalId) β†’ uint256++` [.item-kind]#public# + +See {IGovernor-proposalDeadline}. + +[.contract-item] +[[Governor-proposalProposer-uint256-]] +==== `[.contract-item-name]#++proposalProposer++#++(uint256 proposalId) β†’ address++` [.item-kind]#public# + +See {IGovernor-proposalProposer}. + +[.contract-item] +[[Governor-proposalEta-uint256-]] +==== `[.contract-item-name]#++proposalEta++#++(uint256 proposalId) β†’ uint256++` [.item-kind]#public# + +See {IGovernor-proposalEta}. + +[.contract-item] +[[Governor-proposalNeedsQueuing-uint256-]] +==== `[.contract-item-name]#++proposalNeedsQueuing++#++(uint256) β†’ bool++` [.item-kind]#public# + +See {IGovernor-proposalNeedsQueuing}. + +[.contract-item] +[[Governor-_checkGovernance--]] +==== `[.contract-item-name]#++_checkGovernance++#++()++` [.item-kind]#internal# + +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}. + +[.contract-item] +[[Governor-_quorumReached-uint256-]] +==== `[.contract-item-name]#++_quorumReached++#++(uint256 proposalId) β†’ bool++` [.item-kind]#internal# + +Amount of votes already cast passes the threshold limit. + +[.contract-item] +[[Governor-_voteSucceeded-uint256-]] +==== `[.contract-item-name]#++_voteSucceeded++#++(uint256 proposalId) β†’ bool++` [.item-kind]#internal# + +Is the proposal successful or not. + +[.contract-item] +[[Governor-_getVotes-address-uint256-bytes-]] +==== `[.contract-item-name]#++_getVotes++#++(address account, uint256 timepoint, bytes params) β†’ uint256++` [.item-kind]#internal# + +Get the voting weight of `account` at a specific `timepoint`, for a vote as described by `params`. + +[.contract-item] +[[Governor-_countVote-uint256-address-uint8-uint256-bytes-]] +==== `[.contract-item-name]#++_countVote++#++(uint256 proposalId, address account, uint8 support, uint256 weight, bytes params)++` [.item-kind]#internal# + +Register a vote for `proposalId` by `account` with a given `support`, voting `weight` and voting `params`. + +Note: Support is generic and can represent various things depending on the voting system used. + +[.contract-item] +[[Governor-_defaultParams--]] +==== `[.contract-item-name]#++_defaultParams++#++() β†’ bytes++` [.item-kind]#internal# + +Default additional encoded parameters used by castVote methods that don't include them + +Note: Should be overridden by specific implementations to use an appropriate value, the +meaning of the additional params, in the context of that implementation + +[.contract-item] +[[Governor-propose-address---uint256---bytes---string-]] +==== `[.contract-item-name]#++propose++#++(address[] targets, uint256[] values, bytes[] calldatas, string description) β†’ uint256++` [.item-kind]#public# + +See {IGovernor-propose}. This function has opt-in frontrunning protection, described in {_isValidDescriptionForProposer}. + +[.contract-item] +[[Governor-_propose-address---uint256---bytes---string-address-]] +==== `[.contract-item-name]#++_propose++#++(address[] targets, uint256[] values, bytes[] calldatas, string description, address proposer) β†’ uint256 proposalId++` [.item-kind]#internal# + +Internal propose mechanism. Can be overridden to add more logic on proposal creation. + +Emits a {IGovernor-ProposalCreated} event. + +[.contract-item] +[[Governor-queue-address---uint256---bytes---bytes32-]] +==== `[.contract-item-name]#++queue++#++(address[] targets, uint256[] values, bytes[] calldatas, bytes32 descriptionHash) β†’ uint256++` [.item-kind]#public# + +See {IGovernor-queue}. + +[.contract-item] +[[Governor-_queueOperations-uint256-address---uint256---bytes---bytes32-]] +==== `[.contract-item-name]#++_queueOperations++#++(uint256, address[], uint256[], bytes[], bytes32) β†’ uint48++` [.item-kind]#internal# + +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}. + +[.contract-item] +[[Governor-execute-address---uint256---bytes---bytes32-]] +==== `[.contract-item-name]#++execute++#++(address[] targets, uint256[] values, bytes[] calldatas, bytes32 descriptionHash) β†’ uint256++` [.item-kind]#public# + +See {IGovernor-execute}. + +[.contract-item] +[[Governor-_executeOperations-uint256-address---uint256---bytes---bytes32-]] +==== `[.contract-item-name]#++_executeOperations++#++(uint256, address[] targets, uint256[] values, bytes[] calldatas, bytes32)++` [.item-kind]#internal# + +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}. + +[.contract-item] +[[Governor-cancel-address---uint256---bytes---bytes32-]] +==== `[.contract-item-name]#++cancel++#++(address[] targets, uint256[] values, bytes[] calldatas, bytes32 descriptionHash) β†’ uint256++` [.item-kind]#public# + +See {IGovernor-cancel}. + +[.contract-item] +[[Governor-_cancel-address---uint256---bytes---bytes32-]] +==== `[.contract-item-name]#++_cancel++#++(address[] targets, uint256[] values, bytes[] calldatas, bytes32 descriptionHash) β†’ uint256++` [.item-kind]#internal# + +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. + +[.contract-item] +[[Governor-getVotes-address-uint256-]] +==== `[.contract-item-name]#++getVotes++#++(address account, uint256 timepoint) β†’ uint256++` [.item-kind]#public# + +See {IGovernor-getVotes}. + +[.contract-item] +[[Governor-getVotesWithParams-address-uint256-bytes-]] +==== `[.contract-item-name]#++getVotesWithParams++#++(address account, uint256 timepoint, bytes params) β†’ uint256++` [.item-kind]#public# + +See {IGovernor-getVotesWithParams}. + +[.contract-item] +[[Governor-castVote-uint256-uint8-]] +==== `[.contract-item-name]#++castVote++#++(uint256 proposalId, uint8 support) β†’ uint256++` [.item-kind]#public# + +See {IGovernor-castVote}. + +[.contract-item] +[[Governor-castVoteWithReason-uint256-uint8-string-]] +==== `[.contract-item-name]#++castVoteWithReason++#++(uint256 proposalId, uint8 support, string reason) β†’ uint256++` [.item-kind]#public# + +See {IGovernor-castVoteWithReason}. + +[.contract-item] +[[Governor-castVoteWithReasonAndParams-uint256-uint8-string-bytes-]] +==== `[.contract-item-name]#++castVoteWithReasonAndParams++#++(uint256 proposalId, uint8 support, string reason, bytes params) β†’ uint256++` [.item-kind]#public# + +See {IGovernor-castVoteWithReasonAndParams}. + +[.contract-item] +[[Governor-castVoteBySig-uint256-uint8-address-bytes-]] +==== `[.contract-item-name]#++castVoteBySig++#++(uint256 proposalId, uint8 support, address voter, bytes signature) β†’ uint256++` [.item-kind]#public# + +See {IGovernor-castVoteBySig}. + +[.contract-item] +[[Governor-castVoteWithReasonAndParamsBySig-uint256-uint8-address-string-bytes-bytes-]] +==== `[.contract-item-name]#++castVoteWithReasonAndParamsBySig++#++(uint256 proposalId, uint8 support, address voter, string reason, bytes params, bytes signature) β†’ uint256++` [.item-kind]#public# + +See {IGovernor-castVoteWithReasonAndParamsBySig}. + +[.contract-item] +[[Governor-_castVote-uint256-address-uint8-string-]] +==== `[.contract-item-name]#++_castVote++#++(uint256 proposalId, address account, uint8 support, string reason) β†’ uint256++` [.item-kind]#internal# + +Internal vote casting mechanism: Check that the vote is pending, that it has not been cast yet, retrieve +voting weight using {IGovernor-getVotes} and call the {_countVote} internal function. Uses the _defaultParams(). + +Emits a {IGovernor-VoteCast} event. + +[.contract-item] +[[Governor-_castVote-uint256-address-uint8-string-bytes-]] +==== `[.contract-item-name]#++_castVote++#++(uint256 proposalId, address account, uint8 support, string reason, bytes params) β†’ uint256++` [.item-kind]#internal# + +Internal vote casting mechanism: Check that the vote is pending, that it has not been cast yet, retrieve +voting weight using {IGovernor-getVotes} and call the {_countVote} internal function. + +Emits a {IGovernor-VoteCast} event. + +[.contract-item] +[[Governor-relay-address-uint256-bytes-]] +==== `[.contract-item-name]#++relay++#++(address target, uint256 value, bytes data)++` [.item-kind]#external# + +Relays a transaction or function call to an arbitrary target. In cases where the governance executor +is some contract other than the governor itself, like when using a timelock, this function can be invoked +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. + +[.contract-item] +[[Governor-_executor--]] +==== `[.contract-item-name]#++_executor++#++() β†’ address++` [.item-kind]#internal# + +Address through which the governor executes action. Will be overloaded by module that execute actions +through another contract such as a timelock. + +[.contract-item] +[[Governor-onERC721Received-address-address-uint256-bytes-]] +==== `[.contract-item-name]#++onERC721Received++#++(address, address, uint256, bytes) β†’ bytes4++` [.item-kind]#public# + +See {IERC721Receiver-onERC721Received}. +Receiving tokens is disabled if the governance executor is other than the governor itself (eg. when using with a timelock). + +[.contract-item] +[[Governor-onERC1155Received-address-address-uint256-uint256-bytes-]] +==== `[.contract-item-name]#++onERC1155Received++#++(address, address, uint256, uint256, bytes) β†’ bytes4++` [.item-kind]#public# + +See {IERC1155Receiver-onERC1155Received}. +Receiving tokens is disabled if the governance executor is other than the governor itself (eg. when using with a timelock). + +[.contract-item] +[[Governor-onERC1155BatchReceived-address-address-uint256---uint256---bytes-]] +==== `[.contract-item-name]#++onERC1155BatchReceived++#++(address, address, uint256[], uint256[], bytes) β†’ bytes4++` [.item-kind]#public# + +See {IERC1155Receiver-onERC1155BatchReceived}. +Receiving tokens is disabled if the governance executor is other than the governor itself (eg. when using with a timelock). + +[.contract-item] +[[Governor-_encodeStateBitmap-enum-IGovernor-ProposalState-]] +==== `[.contract-item-name]#++_encodeStateBitmap++#++(enum IGovernor.ProposalState proposalState) β†’ bytes32++` [.item-kind]#internal# + +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 + +[.contract-item] +[[Governor-_isValidDescriptionForProposer-address-string-]] +==== `[.contract-item-name]#++_isValidDescriptionForProposer++#++(address proposer, string description) β†’ bool++` [.item-kind]#internal# + +[.contract-item] +[[Governor-clock--]] +==== `[.contract-item-name]#++clock++#++() β†’ uint48++` [.item-kind]#public# + +Clock used for flagging checkpoints. Can be overridden to implement timestamp based checkpoints (and voting). + +[.contract-item] +[[Governor-CLOCK_MODE--]] +==== `[.contract-item-name]#++CLOCK_MODE++#++() β†’ string++` [.item-kind]#public# + +Description of the clock + +[.contract-item] +[[Governor-votingDelay--]] +==== `[.contract-item-name]#++votingDelay++#++() β†’ uint256++` [.item-kind]#public# + +Delay, between the proposal is created and the vote starts. The unit this duration is expressed in depends +on the clock (see EIP-6372) this contract uses. + +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}. + +[.contract-item] +[[Governor-votingPeriod--]] +==== `[.contract-item-name]#++votingPeriod++#++() β†’ uint256++` [.item-kind]#public# + +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 +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. + +[.contract-item] +[[Governor-quorum-uint256-]] +==== `[.contract-item-name]#++quorum++#++(uint256 timepoint) β†’ uint256++` [.item-kind]#public# + +Minimum number of cast voted required for a proposal to be successful. + +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}). + +=== Modules + +:VoteType: pass:normal[xref:#GovernorCountingSimple-VoteType[`++VoteType++`]] +:ProposalVote: pass:normal[xref:#GovernorCountingSimple-ProposalVote[`++ProposalVote++`]] +:COUNTING_MODE: pass:normal[xref:#GovernorCountingSimple-COUNTING_MODE--[`++COUNTING_MODE++`]] +:hasVoted: pass:normal[xref:#GovernorCountingSimple-hasVoted-uint256-address-[`++hasVoted++`]] +:proposalVotes: pass:normal[xref:#GovernorCountingSimple-proposalVotes-uint256-[`++proposalVotes++`]] +:_quorumReached: pass:normal[xref:#GovernorCountingSimple-_quorumReached-uint256-[`++_quorumReached++`]] +:_voteSucceeded: pass:normal[xref:#GovernorCountingSimple-_voteSucceeded-uint256-[`++_voteSucceeded++`]] +:_countVote: pass:normal[xref:#GovernorCountingSimple-_countVote-uint256-address-uint8-uint256-bytes-[`++_countVote++`]] + +[.contract] +[[GovernorCountingSimple]] +=== `++GovernorCountingSimple++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/governance/extensions/GovernorCountingSimple.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/governance/extensions/GovernorCountingSimple.sol"; +``` + +Extension of {Governor} for simple, 3 options, vote counting. + +[.contract-index] +.Functions +-- +* {xref-GovernorCountingSimple-COUNTING_MODE--}[`++COUNTING_MODE()++`] +* {xref-GovernorCountingSimple-hasVoted-uint256-address-}[`++hasVoted(proposalId, account)++`] +* {xref-GovernorCountingSimple-proposalVotes-uint256-}[`++proposalVotes(proposalId)++`] +* {xref-GovernorCountingSimple-_quorumReached-uint256-}[`++_quorumReached(proposalId)++`] +* {xref-GovernorCountingSimple-_voteSucceeded-uint256-}[`++_voteSucceeded(proposalId)++`] +* {xref-GovernorCountingSimple-_countVote-uint256-address-uint8-uint256-bytes-}[`++_countVote(proposalId, account, support, weight, )++`] + +[.contract-subindex-inherited] +.Governor +* {xref-Governor-receive--}[`++receive()++`] +* {xref-Governor-supportsInterface-bytes4-}[`++supportsInterface(interfaceId)++`] +* {xref-Governor-name--}[`++name()++`] +* {xref-Governor-version--}[`++version()++`] +* {xref-Governor-hashProposal-address---uint256---bytes---bytes32-}[`++hashProposal(targets, values, calldatas, descriptionHash)++`] +* {xref-Governor-state-uint256-}[`++state(proposalId)++`] +* {xref-Governor-proposalThreshold--}[`++proposalThreshold()++`] +* {xref-Governor-proposalSnapshot-uint256-}[`++proposalSnapshot(proposalId)++`] +* {xref-Governor-proposalDeadline-uint256-}[`++proposalDeadline(proposalId)++`] +* {xref-Governor-proposalProposer-uint256-}[`++proposalProposer(proposalId)++`] +* {xref-Governor-proposalEta-uint256-}[`++proposalEta(proposalId)++`] +* {xref-Governor-proposalNeedsQueuing-uint256-}[`++proposalNeedsQueuing()++`] +* {xref-Governor-_checkGovernance--}[`++_checkGovernance()++`] +* {xref-Governor-_getVotes-address-uint256-bytes-}[`++_getVotes(account, timepoint, params)++`] +* {xref-Governor-_defaultParams--}[`++_defaultParams()++`] +* {xref-Governor-propose-address---uint256---bytes---string-}[`++propose(targets, values, calldatas, description)++`] +* {xref-Governor-_propose-address---uint256---bytes---string-address-}[`++_propose(targets, values, calldatas, description, proposer)++`] +* {xref-Governor-queue-address---uint256---bytes---bytes32-}[`++queue(targets, values, calldatas, descriptionHash)++`] +* {xref-Governor-_queueOperations-uint256-address---uint256---bytes---bytes32-}[`++_queueOperations(, , , , )++`] +* {xref-Governor-execute-address---uint256---bytes---bytes32-}[`++execute(targets, values, calldatas, descriptionHash)++`] +* {xref-Governor-_executeOperations-uint256-address---uint256---bytes---bytes32-}[`++_executeOperations(, targets, values, calldatas, )++`] +* {xref-Governor-cancel-address---uint256---bytes---bytes32-}[`++cancel(targets, values, calldatas, descriptionHash)++`] +* {xref-Governor-_cancel-address---uint256---bytes---bytes32-}[`++_cancel(targets, values, calldatas, descriptionHash)++`] +* {xref-Governor-getVotes-address-uint256-}[`++getVotes(account, timepoint)++`] +* {xref-Governor-getVotesWithParams-address-uint256-bytes-}[`++getVotesWithParams(account, timepoint, params)++`] +* {xref-Governor-castVote-uint256-uint8-}[`++castVote(proposalId, support)++`] +* {xref-Governor-castVoteWithReason-uint256-uint8-string-}[`++castVoteWithReason(proposalId, support, reason)++`] +* {xref-Governor-castVoteWithReasonAndParams-uint256-uint8-string-bytes-}[`++castVoteWithReasonAndParams(proposalId, support, reason, params)++`] +* {xref-Governor-castVoteBySig-uint256-uint8-address-bytes-}[`++castVoteBySig(proposalId, support, voter, signature)++`] +* {xref-Governor-castVoteWithReasonAndParamsBySig-uint256-uint8-address-string-bytes-bytes-}[`++castVoteWithReasonAndParamsBySig(proposalId, support, voter, reason, params, signature)++`] +* {xref-Governor-_castVote-uint256-address-uint8-string-}[`++_castVote(proposalId, account, support, reason)++`] +* {xref-Governor-_castVote-uint256-address-uint8-string-bytes-}[`++_castVote(proposalId, account, support, reason, params)++`] +* {xref-Governor-relay-address-uint256-bytes-}[`++relay(target, value, data)++`] +* {xref-Governor-_executor--}[`++_executor()++`] +* {xref-Governor-onERC721Received-address-address-uint256-bytes-}[`++onERC721Received(, , , )++`] +* {xref-Governor-onERC1155Received-address-address-uint256-uint256-bytes-}[`++onERC1155Received(, , , , )++`] +* {xref-Governor-onERC1155BatchReceived-address-address-uint256---uint256---bytes-}[`++onERC1155BatchReceived(, , , , )++`] +* {xref-Governor-_encodeStateBitmap-enum-IGovernor-ProposalState-}[`++_encodeStateBitmap(proposalState)++`] +* {xref-Governor-_isValidDescriptionForProposer-address-string-}[`++_isValidDescriptionForProposer(proposer, description)++`] +* {xref-Governor-clock--}[`++clock()++`] +* {xref-Governor-CLOCK_MODE--}[`++CLOCK_MODE()++`] +* {xref-Governor-votingDelay--}[`++votingDelay()++`] +* {xref-Governor-votingPeriod--}[`++votingPeriod()++`] +* {xref-Governor-quorum-uint256-}[`++quorum(timepoint)++`] + +[.contract-subindex-inherited] +.IERC1155Receiver + +[.contract-subindex-inherited] +.IERC721Receiver + +[.contract-subindex-inherited] +.IGovernor + +[.contract-subindex-inherited] +.IERC6372 + +[.contract-subindex-inherited] +.Nonces +* {xref-Nonces-nonces-address-}[`++nonces(owner)++`] +* {xref-Nonces-_useNonce-address-}[`++_useNonce(owner)++`] +* {xref-Nonces-_useCheckedNonce-address-uint256-}[`++_useCheckedNonce(owner, nonce)++`] + +[.contract-subindex-inherited] +.EIP712 +* {xref-EIP712-_domainSeparatorV4--}[`++_domainSeparatorV4()++`] +* {xref-EIP712-_hashTypedDataV4-bytes32-}[`++_hashTypedDataV4(structHash)++`] +* {xref-EIP712-eip712Domain--}[`++eip712Domain()++`] +* {xref-EIP712-_EIP712Name--}[`++_EIP712Name()++`] +* {xref-EIP712-_EIP712Version--}[`++_EIP712Version()++`] + +[.contract-subindex-inherited] +.IERC5267 + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-index] +.Events +-- + +[.contract-subindex-inherited] +.Governor + +[.contract-subindex-inherited] +.IERC1155Receiver + +[.contract-subindex-inherited] +.IERC721Receiver + +[.contract-subindex-inherited] +.IGovernor +* {xref-IGovernor-ProposalCreated-uint256-address-address---uint256---string---bytes---uint256-uint256-string-}[`++ProposalCreated(proposalId, proposer, targets, values, signatures, calldatas, voteStart, voteEnd, description)++`] +* {xref-IGovernor-ProposalQueued-uint256-uint256-}[`++ProposalQueued(proposalId, etaSeconds)++`] +* {xref-IGovernor-ProposalExecuted-uint256-}[`++ProposalExecuted(proposalId)++`] +* {xref-IGovernor-ProposalCanceled-uint256-}[`++ProposalCanceled(proposalId)++`] +* {xref-IGovernor-VoteCast-address-uint256-uint8-uint256-string-}[`++VoteCast(voter, proposalId, support, weight, reason)++`] +* {xref-IGovernor-VoteCastWithParams-address-uint256-uint8-uint256-string-bytes-}[`++VoteCastWithParams(voter, proposalId, support, weight, reason, params)++`] + +[.contract-subindex-inherited] +.IERC6372 + +[.contract-subindex-inherited] +.Nonces + +[.contract-subindex-inherited] +.EIP712 + +[.contract-subindex-inherited] +.IERC5267 +* {xref-IERC5267-EIP712DomainChanged--}[`++EIP712DomainChanged()++`] + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-index] +.Errors +-- + +[.contract-subindex-inherited] +.Governor + +[.contract-subindex-inherited] +.IERC1155Receiver + +[.contract-subindex-inherited] +.IERC721Receiver + +[.contract-subindex-inherited] +.IGovernor +* {xref-IGovernor-GovernorInvalidProposalLength-uint256-uint256-uint256-}[`++GovernorInvalidProposalLength(targets, calldatas, values)++`] +* {xref-IGovernor-GovernorAlreadyCastVote-address-}[`++GovernorAlreadyCastVote(voter)++`] +* {xref-IGovernor-GovernorDisabledDeposit--}[`++GovernorDisabledDeposit()++`] +* {xref-IGovernor-GovernorOnlyProposer-address-}[`++GovernorOnlyProposer(account)++`] +* {xref-IGovernor-GovernorOnlyExecutor-address-}[`++GovernorOnlyExecutor(account)++`] +* {xref-IGovernor-GovernorNonexistentProposal-uint256-}[`++GovernorNonexistentProposal(proposalId)++`] +* {xref-IGovernor-GovernorUnexpectedProposalState-uint256-enum-IGovernor-ProposalState-bytes32-}[`++GovernorUnexpectedProposalState(proposalId, current, expectedStates)++`] +* {xref-IGovernor-GovernorInvalidVotingPeriod-uint256-}[`++GovernorInvalidVotingPeriod(votingPeriod)++`] +* {xref-IGovernor-GovernorInsufficientProposerVotes-address-uint256-uint256-}[`++GovernorInsufficientProposerVotes(proposer, votes, threshold)++`] +* {xref-IGovernor-GovernorRestrictedProposer-address-}[`++GovernorRestrictedProposer(proposer)++`] +* {xref-IGovernor-GovernorInvalidVoteType--}[`++GovernorInvalidVoteType()++`] +* {xref-IGovernor-GovernorQueueNotImplemented--}[`++GovernorQueueNotImplemented()++`] +* {xref-IGovernor-GovernorNotQueuedProposal-uint256-}[`++GovernorNotQueuedProposal(proposalId)++`] +* {xref-IGovernor-GovernorAlreadyQueuedProposal-uint256-}[`++GovernorAlreadyQueuedProposal(proposalId)++`] +* {xref-IGovernor-GovernorInvalidSignature-address-}[`++GovernorInvalidSignature(voter)++`] + +[.contract-subindex-inherited] +.IERC6372 + +[.contract-subindex-inherited] +.Nonces +* {xref-Nonces-InvalidAccountNonce-address-uint256-}[`++InvalidAccountNonce(account, currentNonce)++`] + +[.contract-subindex-inherited] +.EIP712 + +[.contract-subindex-inherited] +.IERC5267 + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-item] +[[GovernorCountingSimple-COUNTING_MODE--]] +==== `[.contract-item-name]#++COUNTING_MODE++#++() β†’ string++` [.item-kind]#public# + +See {IGovernor-COUNTING_MODE}. + +[.contract-item] +[[GovernorCountingSimple-hasVoted-uint256-address-]] +==== `[.contract-item-name]#++hasVoted++#++(uint256 proposalId, address account) β†’ bool++` [.item-kind]#public# + +See {IGovernor-hasVoted}. + +[.contract-item] +[[GovernorCountingSimple-proposalVotes-uint256-]] +==== `[.contract-item-name]#++proposalVotes++#++(uint256 proposalId) β†’ uint256 againstVotes, uint256 forVotes, uint256 abstainVotes++` [.item-kind]#public# + +Accessor to the internal vote counts. + +[.contract-item] +[[GovernorCountingSimple-_quorumReached-uint256-]] +==== `[.contract-item-name]#++_quorumReached++#++(uint256 proposalId) β†’ bool++` [.item-kind]#internal# + +See {Governor-_quorumReached}. + +[.contract-item] +[[GovernorCountingSimple-_voteSucceeded-uint256-]] +==== `[.contract-item-name]#++_voteSucceeded++#++(uint256 proposalId) β†’ bool++` [.item-kind]#internal# + +See {Governor-_voteSucceeded}. In this module, the forVotes must be strictly over the againstVotes. + +[.contract-item] +[[GovernorCountingSimple-_countVote-uint256-address-uint8-uint256-bytes-]] +==== `[.contract-item-name]#++_countVote++#++(uint256 proposalId, address account, uint8 support, uint256 weight, bytes)++` [.item-kind]#internal# + +See {Governor-_countVote}. In this module, the support follows the `VoteType` enum (from Governor Bravo). + +:constructor: pass:normal[xref:#GovernorVotes-constructor-contract-IVotes-[`++constructor++`]] +:token: pass:normal[xref:#GovernorVotes-token--[`++token++`]] +:clock: pass:normal[xref:#GovernorVotes-clock--[`++clock++`]] +:CLOCK_MODE: pass:normal[xref:#GovernorVotes-CLOCK_MODE--[`++CLOCK_MODE++`]] +:_getVotes: pass:normal[xref:#GovernorVotes-_getVotes-address-uint256-bytes-[`++_getVotes++`]] + +[.contract] +[[GovernorVotes]] +=== `++GovernorVotes++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/governance/extensions/GovernorVotes.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/governance/extensions/GovernorVotes.sol"; +``` + +Extension of {Governor} for voting weight extraction from an {ERC20Votes} token, or since v4.5 an {ERC721Votes} +token. + +[.contract-index] +.Functions +-- +* {xref-GovernorVotes-constructor-contract-IVotes-}[`++constructor(tokenAddress)++`] +* {xref-GovernorVotes-token--}[`++token()++`] +* {xref-GovernorVotes-clock--}[`++clock()++`] +* {xref-GovernorVotes-CLOCK_MODE--}[`++CLOCK_MODE()++`] +* {xref-GovernorVotes-_getVotes-address-uint256-bytes-}[`++_getVotes(account, timepoint, )++`] + +[.contract-subindex-inherited] +.Governor +* {xref-Governor-receive--}[`++receive()++`] +* {xref-Governor-supportsInterface-bytes4-}[`++supportsInterface(interfaceId)++`] +* {xref-Governor-name--}[`++name()++`] +* {xref-Governor-version--}[`++version()++`] +* {xref-Governor-hashProposal-address---uint256---bytes---bytes32-}[`++hashProposal(targets, values, calldatas, descriptionHash)++`] +* {xref-Governor-state-uint256-}[`++state(proposalId)++`] +* {xref-Governor-proposalThreshold--}[`++proposalThreshold()++`] +* {xref-Governor-proposalSnapshot-uint256-}[`++proposalSnapshot(proposalId)++`] +* {xref-Governor-proposalDeadline-uint256-}[`++proposalDeadline(proposalId)++`] +* {xref-Governor-proposalProposer-uint256-}[`++proposalProposer(proposalId)++`] +* {xref-Governor-proposalEta-uint256-}[`++proposalEta(proposalId)++`] +* {xref-Governor-proposalNeedsQueuing-uint256-}[`++proposalNeedsQueuing()++`] +* {xref-Governor-_checkGovernance--}[`++_checkGovernance()++`] +* {xref-Governor-_quorumReached-uint256-}[`++_quorumReached(proposalId)++`] +* {xref-Governor-_voteSucceeded-uint256-}[`++_voteSucceeded(proposalId)++`] +* {xref-Governor-_countVote-uint256-address-uint8-uint256-bytes-}[`++_countVote(proposalId, account, support, weight, params)++`] +* {xref-Governor-_defaultParams--}[`++_defaultParams()++`] +* {xref-Governor-propose-address---uint256---bytes---string-}[`++propose(targets, values, calldatas, description)++`] +* {xref-Governor-_propose-address---uint256---bytes---string-address-}[`++_propose(targets, values, calldatas, description, proposer)++`] +* {xref-Governor-queue-address---uint256---bytes---bytes32-}[`++queue(targets, values, calldatas, descriptionHash)++`] +* {xref-Governor-_queueOperations-uint256-address---uint256---bytes---bytes32-}[`++_queueOperations(, , , , )++`] +* {xref-Governor-execute-address---uint256---bytes---bytes32-}[`++execute(targets, values, calldatas, descriptionHash)++`] +* {xref-Governor-_executeOperations-uint256-address---uint256---bytes---bytes32-}[`++_executeOperations(, targets, values, calldatas, )++`] +* {xref-Governor-cancel-address---uint256---bytes---bytes32-}[`++cancel(targets, values, calldatas, descriptionHash)++`] +* {xref-Governor-_cancel-address---uint256---bytes---bytes32-}[`++_cancel(targets, values, calldatas, descriptionHash)++`] +* {xref-Governor-getVotes-address-uint256-}[`++getVotes(account, timepoint)++`] +* {xref-Governor-getVotesWithParams-address-uint256-bytes-}[`++getVotesWithParams(account, timepoint, params)++`] +* {xref-Governor-castVote-uint256-uint8-}[`++castVote(proposalId, support)++`] +* {xref-Governor-castVoteWithReason-uint256-uint8-string-}[`++castVoteWithReason(proposalId, support, reason)++`] +* {xref-Governor-castVoteWithReasonAndParams-uint256-uint8-string-bytes-}[`++castVoteWithReasonAndParams(proposalId, support, reason, params)++`] +* {xref-Governor-castVoteBySig-uint256-uint8-address-bytes-}[`++castVoteBySig(proposalId, support, voter, signature)++`] +* {xref-Governor-castVoteWithReasonAndParamsBySig-uint256-uint8-address-string-bytes-bytes-}[`++castVoteWithReasonAndParamsBySig(proposalId, support, voter, reason, params, signature)++`] +* {xref-Governor-_castVote-uint256-address-uint8-string-}[`++_castVote(proposalId, account, support, reason)++`] +* {xref-Governor-_castVote-uint256-address-uint8-string-bytes-}[`++_castVote(proposalId, account, support, reason, params)++`] +* {xref-Governor-relay-address-uint256-bytes-}[`++relay(target, value, data)++`] +* {xref-Governor-_executor--}[`++_executor()++`] +* {xref-Governor-onERC721Received-address-address-uint256-bytes-}[`++onERC721Received(, , , )++`] +* {xref-Governor-onERC1155Received-address-address-uint256-uint256-bytes-}[`++onERC1155Received(, , , , )++`] +* {xref-Governor-onERC1155BatchReceived-address-address-uint256---uint256---bytes-}[`++onERC1155BatchReceived(, , , , )++`] +* {xref-Governor-_encodeStateBitmap-enum-IGovernor-ProposalState-}[`++_encodeStateBitmap(proposalState)++`] +* {xref-Governor-_isValidDescriptionForProposer-address-string-}[`++_isValidDescriptionForProposer(proposer, description)++`] +* {xref-Governor-votingDelay--}[`++votingDelay()++`] +* {xref-Governor-votingPeriod--}[`++votingPeriod()++`] +* {xref-Governor-quorum-uint256-}[`++quorum(timepoint)++`] + +[.contract-subindex-inherited] +.IERC1155Receiver + +[.contract-subindex-inherited] +.IERC721Receiver + +[.contract-subindex-inherited] +.IGovernor +* {xref-IGovernor-COUNTING_MODE--}[`++COUNTING_MODE()++`] +* {xref-IGovernor-hasVoted-uint256-address-}[`++hasVoted(proposalId, account)++`] + +[.contract-subindex-inherited] +.IERC6372 + +[.contract-subindex-inherited] +.Nonces +* {xref-Nonces-nonces-address-}[`++nonces(owner)++`] +* {xref-Nonces-_useNonce-address-}[`++_useNonce(owner)++`] +* {xref-Nonces-_useCheckedNonce-address-uint256-}[`++_useCheckedNonce(owner, nonce)++`] + +[.contract-subindex-inherited] +.EIP712 +* {xref-EIP712-_domainSeparatorV4--}[`++_domainSeparatorV4()++`] +* {xref-EIP712-_hashTypedDataV4-bytes32-}[`++_hashTypedDataV4(structHash)++`] +* {xref-EIP712-eip712Domain--}[`++eip712Domain()++`] +* {xref-EIP712-_EIP712Name--}[`++_EIP712Name()++`] +* {xref-EIP712-_EIP712Version--}[`++_EIP712Version()++`] + +[.contract-subindex-inherited] +.IERC5267 + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-index] +.Events +-- + +[.contract-subindex-inherited] +.Governor + +[.contract-subindex-inherited] +.IERC1155Receiver + +[.contract-subindex-inherited] +.IERC721Receiver + +[.contract-subindex-inherited] +.IGovernor +* {xref-IGovernor-ProposalCreated-uint256-address-address---uint256---string---bytes---uint256-uint256-string-}[`++ProposalCreated(proposalId, proposer, targets, values, signatures, calldatas, voteStart, voteEnd, description)++`] +* {xref-IGovernor-ProposalQueued-uint256-uint256-}[`++ProposalQueued(proposalId, etaSeconds)++`] +* {xref-IGovernor-ProposalExecuted-uint256-}[`++ProposalExecuted(proposalId)++`] +* {xref-IGovernor-ProposalCanceled-uint256-}[`++ProposalCanceled(proposalId)++`] +* {xref-IGovernor-VoteCast-address-uint256-uint8-uint256-string-}[`++VoteCast(voter, proposalId, support, weight, reason)++`] +* {xref-IGovernor-VoteCastWithParams-address-uint256-uint8-uint256-string-bytes-}[`++VoteCastWithParams(voter, proposalId, support, weight, reason, params)++`] + +[.contract-subindex-inherited] +.IERC6372 + +[.contract-subindex-inherited] +.Nonces + +[.contract-subindex-inherited] +.EIP712 + +[.contract-subindex-inherited] +.IERC5267 +* {xref-IERC5267-EIP712DomainChanged--}[`++EIP712DomainChanged()++`] + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-index] +.Errors +-- + +[.contract-subindex-inherited] +.Governor + +[.contract-subindex-inherited] +.IERC1155Receiver + +[.contract-subindex-inherited] +.IERC721Receiver + +[.contract-subindex-inherited] +.IGovernor +* {xref-IGovernor-GovernorInvalidProposalLength-uint256-uint256-uint256-}[`++GovernorInvalidProposalLength(targets, calldatas, values)++`] +* {xref-IGovernor-GovernorAlreadyCastVote-address-}[`++GovernorAlreadyCastVote(voter)++`] +* {xref-IGovernor-GovernorDisabledDeposit--}[`++GovernorDisabledDeposit()++`] +* {xref-IGovernor-GovernorOnlyProposer-address-}[`++GovernorOnlyProposer(account)++`] +* {xref-IGovernor-GovernorOnlyExecutor-address-}[`++GovernorOnlyExecutor(account)++`] +* {xref-IGovernor-GovernorNonexistentProposal-uint256-}[`++GovernorNonexistentProposal(proposalId)++`] +* {xref-IGovernor-GovernorUnexpectedProposalState-uint256-enum-IGovernor-ProposalState-bytes32-}[`++GovernorUnexpectedProposalState(proposalId, current, expectedStates)++`] +* {xref-IGovernor-GovernorInvalidVotingPeriod-uint256-}[`++GovernorInvalidVotingPeriod(votingPeriod)++`] +* {xref-IGovernor-GovernorInsufficientProposerVotes-address-uint256-uint256-}[`++GovernorInsufficientProposerVotes(proposer, votes, threshold)++`] +* {xref-IGovernor-GovernorRestrictedProposer-address-}[`++GovernorRestrictedProposer(proposer)++`] +* {xref-IGovernor-GovernorInvalidVoteType--}[`++GovernorInvalidVoteType()++`] +* {xref-IGovernor-GovernorQueueNotImplemented--}[`++GovernorQueueNotImplemented()++`] +* {xref-IGovernor-GovernorNotQueuedProposal-uint256-}[`++GovernorNotQueuedProposal(proposalId)++`] +* {xref-IGovernor-GovernorAlreadyQueuedProposal-uint256-}[`++GovernorAlreadyQueuedProposal(proposalId)++`] +* {xref-IGovernor-GovernorInvalidSignature-address-}[`++GovernorInvalidSignature(voter)++`] + +[.contract-subindex-inherited] +.IERC6372 + +[.contract-subindex-inherited] +.Nonces +* {xref-Nonces-InvalidAccountNonce-address-uint256-}[`++InvalidAccountNonce(account, currentNonce)++`] + +[.contract-subindex-inherited] +.EIP712 + +[.contract-subindex-inherited] +.IERC5267 + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-item] +[[GovernorVotes-constructor-contract-IVotes-]] +==== `[.contract-item-name]#++constructor++#++(contract IVotes tokenAddress)++` [.item-kind]#internal# + +[.contract-item] +[[GovernorVotes-token--]] +==== `[.contract-item-name]#++token++#++() β†’ contract IERC5805++` [.item-kind]#public# + +The token that voting power is sourced from. + +[.contract-item] +[[GovernorVotes-clock--]] +==== `[.contract-item-name]#++clock++#++() β†’ uint48++` [.item-kind]#public# + +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. + +[.contract-item] +[[GovernorVotes-CLOCK_MODE--]] +==== `[.contract-item-name]#++CLOCK_MODE++#++() β†’ string++` [.item-kind]#public# + +Machine-readable description of the clock as specified in EIP-6372. + +[.contract-item] +[[GovernorVotes-_getVotes-address-uint256-bytes-]] +==== `[.contract-item-name]#++_getVotes++#++(address account, uint256 timepoint, bytes) β†’ uint256++` [.item-kind]#internal# + +:QuorumNumeratorUpdated: pass:normal[xref:#GovernorVotesQuorumFraction-QuorumNumeratorUpdated-uint256-uint256-[`++QuorumNumeratorUpdated++`]] +:GovernorInvalidQuorumFraction: pass:normal[xref:#GovernorVotesQuorumFraction-GovernorInvalidQuorumFraction-uint256-uint256-[`++GovernorInvalidQuorumFraction++`]] +:constructor: pass:normal[xref:#GovernorVotesQuorumFraction-constructor-uint256-[`++constructor++`]] +:quorumNumerator: pass:normal[xref:#GovernorVotesQuorumFraction-quorumNumerator--[`++quorumNumerator++`]] +:quorumNumerator: pass:normal[xref:#GovernorVotesQuorumFraction-quorumNumerator-uint256-[`++quorumNumerator++`]] +:quorumDenominator: pass:normal[xref:#GovernorVotesQuorumFraction-quorumDenominator--[`++quorumDenominator++`]] +:quorum: pass:normal[xref:#GovernorVotesQuorumFraction-quorum-uint256-[`++quorum++`]] +:updateQuorumNumerator: pass:normal[xref:#GovernorVotesQuorumFraction-updateQuorumNumerator-uint256-[`++updateQuorumNumerator++`]] +:_updateQuorumNumerator: pass:normal[xref:#GovernorVotesQuorumFraction-_updateQuorumNumerator-uint256-[`++_updateQuorumNumerator++`]] + +[.contract] +[[GovernorVotesQuorumFraction]] +=== `++GovernorVotesQuorumFraction++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/governance/extensions/GovernorVotesQuorumFraction.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/governance/extensions/GovernorVotesQuorumFraction.sol"; +``` + +Extension of {Governor} for voting weight extraction from an {ERC20Votes} token and a quorum expressed as a +fraction of the total supply. + +[.contract-index] +.Functions +-- +* {xref-GovernorVotesQuorumFraction-constructor-uint256-}[`++constructor(quorumNumeratorValue)++`] +* {xref-GovernorVotesQuorumFraction-quorumNumerator--}[`++quorumNumerator()++`] +* {xref-GovernorVotesQuorumFraction-quorumNumerator-uint256-}[`++quorumNumerator(timepoint)++`] +* {xref-GovernorVotesQuorumFraction-quorumDenominator--}[`++quorumDenominator()++`] +* {xref-GovernorVotesQuorumFraction-quorum-uint256-}[`++quorum(timepoint)++`] +* {xref-GovernorVotesQuorumFraction-updateQuorumNumerator-uint256-}[`++updateQuorumNumerator(newQuorumNumerator)++`] +* {xref-GovernorVotesQuorumFraction-_updateQuorumNumerator-uint256-}[`++_updateQuorumNumerator(newQuorumNumerator)++`] + +[.contract-subindex-inherited] +.GovernorVotes +* {xref-GovernorVotes-token--}[`++token()++`] +* {xref-GovernorVotes-clock--}[`++clock()++`] +* {xref-GovernorVotes-CLOCK_MODE--}[`++CLOCK_MODE()++`] +* {xref-GovernorVotes-_getVotes-address-uint256-bytes-}[`++_getVotes(account, timepoint, )++`] + +[.contract-subindex-inherited] +.Governor +* {xref-Governor-receive--}[`++receive()++`] +* {xref-Governor-supportsInterface-bytes4-}[`++supportsInterface(interfaceId)++`] +* {xref-Governor-name--}[`++name()++`] +* {xref-Governor-version--}[`++version()++`] +* {xref-Governor-hashProposal-address---uint256---bytes---bytes32-}[`++hashProposal(targets, values, calldatas, descriptionHash)++`] +* {xref-Governor-state-uint256-}[`++state(proposalId)++`] +* {xref-Governor-proposalThreshold--}[`++proposalThreshold()++`] +* {xref-Governor-proposalSnapshot-uint256-}[`++proposalSnapshot(proposalId)++`] +* {xref-Governor-proposalDeadline-uint256-}[`++proposalDeadline(proposalId)++`] +* {xref-Governor-proposalProposer-uint256-}[`++proposalProposer(proposalId)++`] +* {xref-Governor-proposalEta-uint256-}[`++proposalEta(proposalId)++`] +* {xref-Governor-proposalNeedsQueuing-uint256-}[`++proposalNeedsQueuing()++`] +* {xref-Governor-_checkGovernance--}[`++_checkGovernance()++`] +* {xref-Governor-_quorumReached-uint256-}[`++_quorumReached(proposalId)++`] +* {xref-Governor-_voteSucceeded-uint256-}[`++_voteSucceeded(proposalId)++`] +* {xref-Governor-_countVote-uint256-address-uint8-uint256-bytes-}[`++_countVote(proposalId, account, support, weight, params)++`] +* {xref-Governor-_defaultParams--}[`++_defaultParams()++`] +* {xref-Governor-propose-address---uint256---bytes---string-}[`++propose(targets, values, calldatas, description)++`] +* {xref-Governor-_propose-address---uint256---bytes---string-address-}[`++_propose(targets, values, calldatas, description, proposer)++`] +* {xref-Governor-queue-address---uint256---bytes---bytes32-}[`++queue(targets, values, calldatas, descriptionHash)++`] +* {xref-Governor-_queueOperations-uint256-address---uint256---bytes---bytes32-}[`++_queueOperations(, , , , )++`] +* {xref-Governor-execute-address---uint256---bytes---bytes32-}[`++execute(targets, values, calldatas, descriptionHash)++`] +* {xref-Governor-_executeOperations-uint256-address---uint256---bytes---bytes32-}[`++_executeOperations(, targets, values, calldatas, )++`] +* {xref-Governor-cancel-address---uint256---bytes---bytes32-}[`++cancel(targets, values, calldatas, descriptionHash)++`] +* {xref-Governor-_cancel-address---uint256---bytes---bytes32-}[`++_cancel(targets, values, calldatas, descriptionHash)++`] +* {xref-Governor-getVotes-address-uint256-}[`++getVotes(account, timepoint)++`] +* {xref-Governor-getVotesWithParams-address-uint256-bytes-}[`++getVotesWithParams(account, timepoint, params)++`] +* {xref-Governor-castVote-uint256-uint8-}[`++castVote(proposalId, support)++`] +* {xref-Governor-castVoteWithReason-uint256-uint8-string-}[`++castVoteWithReason(proposalId, support, reason)++`] +* {xref-Governor-castVoteWithReasonAndParams-uint256-uint8-string-bytes-}[`++castVoteWithReasonAndParams(proposalId, support, reason, params)++`] +* {xref-Governor-castVoteBySig-uint256-uint8-address-bytes-}[`++castVoteBySig(proposalId, support, voter, signature)++`] +* {xref-Governor-castVoteWithReasonAndParamsBySig-uint256-uint8-address-string-bytes-bytes-}[`++castVoteWithReasonAndParamsBySig(proposalId, support, voter, reason, params, signature)++`] +* {xref-Governor-_castVote-uint256-address-uint8-string-}[`++_castVote(proposalId, account, support, reason)++`] +* {xref-Governor-_castVote-uint256-address-uint8-string-bytes-}[`++_castVote(proposalId, account, support, reason, params)++`] +* {xref-Governor-relay-address-uint256-bytes-}[`++relay(target, value, data)++`] +* {xref-Governor-_executor--}[`++_executor()++`] +* {xref-Governor-onERC721Received-address-address-uint256-bytes-}[`++onERC721Received(, , , )++`] +* {xref-Governor-onERC1155Received-address-address-uint256-uint256-bytes-}[`++onERC1155Received(, , , , )++`] +* {xref-Governor-onERC1155BatchReceived-address-address-uint256---uint256---bytes-}[`++onERC1155BatchReceived(, , , , )++`] +* {xref-Governor-_encodeStateBitmap-enum-IGovernor-ProposalState-}[`++_encodeStateBitmap(proposalState)++`] +* {xref-Governor-_isValidDescriptionForProposer-address-string-}[`++_isValidDescriptionForProposer(proposer, description)++`] +* {xref-Governor-votingDelay--}[`++votingDelay()++`] +* {xref-Governor-votingPeriod--}[`++votingPeriod()++`] + +[.contract-subindex-inherited] +.IERC1155Receiver + +[.contract-subindex-inherited] +.IERC721Receiver + +[.contract-subindex-inherited] +.IGovernor +* {xref-IGovernor-COUNTING_MODE--}[`++COUNTING_MODE()++`] +* {xref-IGovernor-hasVoted-uint256-address-}[`++hasVoted(proposalId, account)++`] + +[.contract-subindex-inherited] +.IERC6372 + +[.contract-subindex-inherited] +.Nonces +* {xref-Nonces-nonces-address-}[`++nonces(owner)++`] +* {xref-Nonces-_useNonce-address-}[`++_useNonce(owner)++`] +* {xref-Nonces-_useCheckedNonce-address-uint256-}[`++_useCheckedNonce(owner, nonce)++`] + +[.contract-subindex-inherited] +.EIP712 +* {xref-EIP712-_domainSeparatorV4--}[`++_domainSeparatorV4()++`] +* {xref-EIP712-_hashTypedDataV4-bytes32-}[`++_hashTypedDataV4(structHash)++`] +* {xref-EIP712-eip712Domain--}[`++eip712Domain()++`] +* {xref-EIP712-_EIP712Name--}[`++_EIP712Name()++`] +* {xref-EIP712-_EIP712Version--}[`++_EIP712Version()++`] + +[.contract-subindex-inherited] +.IERC5267 + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-index] +.Events +-- +* {xref-GovernorVotesQuorumFraction-QuorumNumeratorUpdated-uint256-uint256-}[`++QuorumNumeratorUpdated(oldQuorumNumerator, newQuorumNumerator)++`] + +[.contract-subindex-inherited] +.GovernorVotes + +[.contract-subindex-inherited] +.Governor + +[.contract-subindex-inherited] +.IERC1155Receiver + +[.contract-subindex-inherited] +.IERC721Receiver + +[.contract-subindex-inherited] +.IGovernor +* {xref-IGovernor-ProposalCreated-uint256-address-address---uint256---string---bytes---uint256-uint256-string-}[`++ProposalCreated(proposalId, proposer, targets, values, signatures, calldatas, voteStart, voteEnd, description)++`] +* {xref-IGovernor-ProposalQueued-uint256-uint256-}[`++ProposalQueued(proposalId, etaSeconds)++`] +* {xref-IGovernor-ProposalExecuted-uint256-}[`++ProposalExecuted(proposalId)++`] +* {xref-IGovernor-ProposalCanceled-uint256-}[`++ProposalCanceled(proposalId)++`] +* {xref-IGovernor-VoteCast-address-uint256-uint8-uint256-string-}[`++VoteCast(voter, proposalId, support, weight, reason)++`] +* {xref-IGovernor-VoteCastWithParams-address-uint256-uint8-uint256-string-bytes-}[`++VoteCastWithParams(voter, proposalId, support, weight, reason, params)++`] + +[.contract-subindex-inherited] +.IERC6372 + +[.contract-subindex-inherited] +.Nonces + +[.contract-subindex-inherited] +.EIP712 + +[.contract-subindex-inherited] +.IERC5267 +* {xref-IERC5267-EIP712DomainChanged--}[`++EIP712DomainChanged()++`] + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-index] +.Errors +-- +* {xref-GovernorVotesQuorumFraction-GovernorInvalidQuorumFraction-uint256-uint256-}[`++GovernorInvalidQuorumFraction(quorumNumerator, quorumDenominator)++`] + +[.contract-subindex-inherited] +.GovernorVotes + +[.contract-subindex-inherited] +.Governor + +[.contract-subindex-inherited] +.IERC1155Receiver + +[.contract-subindex-inherited] +.IERC721Receiver + +[.contract-subindex-inherited] +.IGovernor +* {xref-IGovernor-GovernorInvalidProposalLength-uint256-uint256-uint256-}[`++GovernorInvalidProposalLength(targets, calldatas, values)++`] +* {xref-IGovernor-GovernorAlreadyCastVote-address-}[`++GovernorAlreadyCastVote(voter)++`] +* {xref-IGovernor-GovernorDisabledDeposit--}[`++GovernorDisabledDeposit()++`] +* {xref-IGovernor-GovernorOnlyProposer-address-}[`++GovernorOnlyProposer(account)++`] +* {xref-IGovernor-GovernorOnlyExecutor-address-}[`++GovernorOnlyExecutor(account)++`] +* {xref-IGovernor-GovernorNonexistentProposal-uint256-}[`++GovernorNonexistentProposal(proposalId)++`] +* {xref-IGovernor-GovernorUnexpectedProposalState-uint256-enum-IGovernor-ProposalState-bytes32-}[`++GovernorUnexpectedProposalState(proposalId, current, expectedStates)++`] +* {xref-IGovernor-GovernorInvalidVotingPeriod-uint256-}[`++GovernorInvalidVotingPeriod(votingPeriod)++`] +* {xref-IGovernor-GovernorInsufficientProposerVotes-address-uint256-uint256-}[`++GovernorInsufficientProposerVotes(proposer, votes, threshold)++`] +* {xref-IGovernor-GovernorRestrictedProposer-address-}[`++GovernorRestrictedProposer(proposer)++`] +* {xref-IGovernor-GovernorInvalidVoteType--}[`++GovernorInvalidVoteType()++`] +* {xref-IGovernor-GovernorQueueNotImplemented--}[`++GovernorQueueNotImplemented()++`] +* {xref-IGovernor-GovernorNotQueuedProposal-uint256-}[`++GovernorNotQueuedProposal(proposalId)++`] +* {xref-IGovernor-GovernorAlreadyQueuedProposal-uint256-}[`++GovernorAlreadyQueuedProposal(proposalId)++`] +* {xref-IGovernor-GovernorInvalidSignature-address-}[`++GovernorInvalidSignature(voter)++`] + +[.contract-subindex-inherited] +.IERC6372 + +[.contract-subindex-inherited] +.Nonces +* {xref-Nonces-InvalidAccountNonce-address-uint256-}[`++InvalidAccountNonce(account, currentNonce)++`] + +[.contract-subindex-inherited] +.EIP712 + +[.contract-subindex-inherited] +.IERC5267 + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-item] +[[GovernorVotesQuorumFraction-constructor-uint256-]] +==== `[.contract-item-name]#++constructor++#++(uint256 quorumNumeratorValue)++` [.item-kind]#internal# + +Initialize quorum as a fraction of the token's total supply. + +The fraction is specified as `numerator / denominator`. By default the denominator is 100, so quorum is +specified as a percent: a numerator of 10 corresponds to quorum being 10% of total supply. The denominator can be +customized by overriding {quorumDenominator}. + +[.contract-item] +[[GovernorVotesQuorumFraction-quorumNumerator--]] +==== `[.contract-item-name]#++quorumNumerator++#++() β†’ uint256++` [.item-kind]#public# + +Returns the current quorum numerator. See {quorumDenominator}. + +[.contract-item] +[[GovernorVotesQuorumFraction-quorumNumerator-uint256-]] +==== `[.contract-item-name]#++quorumNumerator++#++(uint256 timepoint) β†’ uint256++` [.item-kind]#public# + +Returns the quorum numerator at a specific timepoint. See {quorumDenominator}. + +[.contract-item] +[[GovernorVotesQuorumFraction-quorumDenominator--]] +==== `[.contract-item-name]#++quorumDenominator++#++() β†’ uint256++` [.item-kind]#public# + +Returns the quorum denominator. Defaults to 100, but may be overridden. + +[.contract-item] +[[GovernorVotesQuorumFraction-quorum-uint256-]] +==== `[.contract-item-name]#++quorum++#++(uint256 timepoint) β†’ uint256++` [.item-kind]#public# + +Returns the quorum for a timepoint, in terms of number of votes: `supply * numerator / denominator`. + +[.contract-item] +[[GovernorVotesQuorumFraction-updateQuorumNumerator-uint256-]] +==== `[.contract-item-name]#++updateQuorumNumerator++#++(uint256 newQuorumNumerator)++` [.item-kind]#external# + +Changes the quorum numerator. + +Emits a {QuorumNumeratorUpdated} event. + +Requirements: + +- Must be called through a governance proposal. +- New numerator must be smaller or equal to the denominator. + +[.contract-item] +[[GovernorVotesQuorumFraction-_updateQuorumNumerator-uint256-]] +==== `[.contract-item-name]#++_updateQuorumNumerator++#++(uint256 newQuorumNumerator)++` [.item-kind]#internal# + +Changes the quorum numerator. + +Emits a {QuorumNumeratorUpdated} event. + +Requirements: + +- New numerator must be smaller or equal to the denominator. + +[.contract-item] +[[GovernorVotesQuorumFraction-QuorumNumeratorUpdated-uint256-uint256-]] +==== `[.contract-item-name]#++QuorumNumeratorUpdated++#++(uint256 oldQuorumNumerator, uint256 newQuorumNumerator)++` [.item-kind]#event# + +[.contract-item] +[[GovernorVotesQuorumFraction-GovernorInvalidQuorumFraction-uint256-uint256-]] +==== `[.contract-item-name]#++GovernorInvalidQuorumFraction++#++(uint256 quorumNumerator, uint256 quorumDenominator)++` [.item-kind]#error# + +The quorum set is not a valid fraction. + +=== Extensions + +:TimelockChange: pass:normal[xref:#GovernorTimelockControl-TimelockChange-address-address-[`++TimelockChange++`]] +:constructor: pass:normal[xref:#GovernorTimelockControl-constructor-contract-TimelockController-[`++constructor++`]] +:state: pass:normal[xref:#GovernorTimelockControl-state-uint256-[`++state++`]] +:timelock: pass:normal[xref:#GovernorTimelockControl-timelock--[`++timelock++`]] +:proposalNeedsQueuing: pass:normal[xref:#GovernorTimelockControl-proposalNeedsQueuing-uint256-[`++proposalNeedsQueuing++`]] +:_queueOperations: pass:normal[xref:#GovernorTimelockControl-_queueOperations-uint256-address---uint256---bytes---bytes32-[`++_queueOperations++`]] +:_executeOperations: pass:normal[xref:#GovernorTimelockControl-_executeOperations-uint256-address---uint256---bytes---bytes32-[`++_executeOperations++`]] +:_cancel: pass:normal[xref:#GovernorTimelockControl-_cancel-address---uint256---bytes---bytes32-[`++_cancel++`]] +:_executor: pass:normal[xref:#GovernorTimelockControl-_executor--[`++_executor++`]] +:updateTimelock: pass:normal[xref:#GovernorTimelockControl-updateTimelock-contract-TimelockController-[`++updateTimelock++`]] + +[.contract] +[[GovernorTimelockControl]] +=== `++GovernorTimelockControl++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/governance/extensions/GovernorTimelockControl.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/governance/extensions/GovernorTimelockControl.sol"; +``` + +Extension of {Governor} that binds the execution process to an instance of {TimelockController}. This adds a +delay, enforced by the {TimelockController} to all successful proposal (in addition to the voting duration). The +{Governor} needs the proposer (and ideally the executor) roles for the {Governor} to work properly. + +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 from a proposal, unless executed via {Governor-relay}. + +WARNING: Setting up the TimelockController to have additional proposers or cancellers besides the governor is very +risky, as it grants them the ability to: 1) execute operations as the timelock, and thus possibly performing +operations or accessing funds that are expected to only be accessible through a vote, and 2) block governance +proposals that have been approved by the voters, effectively executing a Denial of Service attack. + +NOTE: `AccessManager` does not support scheduling more than one operation with the same target and calldata at +the same time. See {AccessManager-schedule} for a workaround. + +[.contract-index] +.Functions +-- +* {xref-GovernorTimelockControl-constructor-contract-TimelockController-}[`++constructor(timelockAddress)++`] +* {xref-GovernorTimelockControl-state-uint256-}[`++state(proposalId)++`] +* {xref-GovernorTimelockControl-timelock--}[`++timelock()++`] +* {xref-GovernorTimelockControl-proposalNeedsQueuing-uint256-}[`++proposalNeedsQueuing()++`] +* {xref-GovernorTimelockControl-_queueOperations-uint256-address---uint256---bytes---bytes32-}[`++_queueOperations(proposalId, targets, values, calldatas, descriptionHash)++`] +* {xref-GovernorTimelockControl-_executeOperations-uint256-address---uint256---bytes---bytes32-}[`++_executeOperations(proposalId, targets, values, calldatas, descriptionHash)++`] +* {xref-GovernorTimelockControl-_cancel-address---uint256---bytes---bytes32-}[`++_cancel(targets, values, calldatas, descriptionHash)++`] +* {xref-GovernorTimelockControl-_executor--}[`++_executor()++`] +* {xref-GovernorTimelockControl-updateTimelock-contract-TimelockController-}[`++updateTimelock(newTimelock)++`] + +[.contract-subindex-inherited] +.Governor +* {xref-Governor-receive--}[`++receive()++`] +* {xref-Governor-supportsInterface-bytes4-}[`++supportsInterface(interfaceId)++`] +* {xref-Governor-name--}[`++name()++`] +* {xref-Governor-version--}[`++version()++`] +* {xref-Governor-hashProposal-address---uint256---bytes---bytes32-}[`++hashProposal(targets, values, calldatas, descriptionHash)++`] +* {xref-Governor-proposalThreshold--}[`++proposalThreshold()++`] +* {xref-Governor-proposalSnapshot-uint256-}[`++proposalSnapshot(proposalId)++`] +* {xref-Governor-proposalDeadline-uint256-}[`++proposalDeadline(proposalId)++`] +* {xref-Governor-proposalProposer-uint256-}[`++proposalProposer(proposalId)++`] +* {xref-Governor-proposalEta-uint256-}[`++proposalEta(proposalId)++`] +* {xref-Governor-_checkGovernance--}[`++_checkGovernance()++`] +* {xref-Governor-_quorumReached-uint256-}[`++_quorumReached(proposalId)++`] +* {xref-Governor-_voteSucceeded-uint256-}[`++_voteSucceeded(proposalId)++`] +* {xref-Governor-_getVotes-address-uint256-bytes-}[`++_getVotes(account, timepoint, params)++`] +* {xref-Governor-_countVote-uint256-address-uint8-uint256-bytes-}[`++_countVote(proposalId, account, support, weight, params)++`] +* {xref-Governor-_defaultParams--}[`++_defaultParams()++`] +* {xref-Governor-propose-address---uint256---bytes---string-}[`++propose(targets, values, calldatas, description)++`] +* {xref-Governor-_propose-address---uint256---bytes---string-address-}[`++_propose(targets, values, calldatas, description, proposer)++`] +* {xref-Governor-queue-address---uint256---bytes---bytes32-}[`++queue(targets, values, calldatas, descriptionHash)++`] +* {xref-Governor-execute-address---uint256---bytes---bytes32-}[`++execute(targets, values, calldatas, descriptionHash)++`] +* {xref-Governor-cancel-address---uint256---bytes---bytes32-}[`++cancel(targets, values, calldatas, descriptionHash)++`] +* {xref-Governor-getVotes-address-uint256-}[`++getVotes(account, timepoint)++`] +* {xref-Governor-getVotesWithParams-address-uint256-bytes-}[`++getVotesWithParams(account, timepoint, params)++`] +* {xref-Governor-castVote-uint256-uint8-}[`++castVote(proposalId, support)++`] +* {xref-Governor-castVoteWithReason-uint256-uint8-string-}[`++castVoteWithReason(proposalId, support, reason)++`] +* {xref-Governor-castVoteWithReasonAndParams-uint256-uint8-string-bytes-}[`++castVoteWithReasonAndParams(proposalId, support, reason, params)++`] +* {xref-Governor-castVoteBySig-uint256-uint8-address-bytes-}[`++castVoteBySig(proposalId, support, voter, signature)++`] +* {xref-Governor-castVoteWithReasonAndParamsBySig-uint256-uint8-address-string-bytes-bytes-}[`++castVoteWithReasonAndParamsBySig(proposalId, support, voter, reason, params, signature)++`] +* {xref-Governor-_castVote-uint256-address-uint8-string-}[`++_castVote(proposalId, account, support, reason)++`] +* {xref-Governor-_castVote-uint256-address-uint8-string-bytes-}[`++_castVote(proposalId, account, support, reason, params)++`] +* {xref-Governor-relay-address-uint256-bytes-}[`++relay(target, value, data)++`] +* {xref-Governor-onERC721Received-address-address-uint256-bytes-}[`++onERC721Received(, , , )++`] +* {xref-Governor-onERC1155Received-address-address-uint256-uint256-bytes-}[`++onERC1155Received(, , , , )++`] +* {xref-Governor-onERC1155BatchReceived-address-address-uint256---uint256---bytes-}[`++onERC1155BatchReceived(, , , , )++`] +* {xref-Governor-_encodeStateBitmap-enum-IGovernor-ProposalState-}[`++_encodeStateBitmap(proposalState)++`] +* {xref-Governor-_isValidDescriptionForProposer-address-string-}[`++_isValidDescriptionForProposer(proposer, description)++`] +* {xref-Governor-clock--}[`++clock()++`] +* {xref-Governor-CLOCK_MODE--}[`++CLOCK_MODE()++`] +* {xref-Governor-votingDelay--}[`++votingDelay()++`] +* {xref-Governor-votingPeriod--}[`++votingPeriod()++`] +* {xref-Governor-quorum-uint256-}[`++quorum(timepoint)++`] + +[.contract-subindex-inherited] +.IERC1155Receiver + +[.contract-subindex-inherited] +.IERC721Receiver + +[.contract-subindex-inherited] +.IGovernor +* {xref-IGovernor-COUNTING_MODE--}[`++COUNTING_MODE()++`] +* {xref-IGovernor-hasVoted-uint256-address-}[`++hasVoted(proposalId, account)++`] + +[.contract-subindex-inherited] +.IERC6372 + +[.contract-subindex-inherited] +.Nonces +* {xref-Nonces-nonces-address-}[`++nonces(owner)++`] +* {xref-Nonces-_useNonce-address-}[`++_useNonce(owner)++`] +* {xref-Nonces-_useCheckedNonce-address-uint256-}[`++_useCheckedNonce(owner, nonce)++`] + +[.contract-subindex-inherited] +.EIP712 +* {xref-EIP712-_domainSeparatorV4--}[`++_domainSeparatorV4()++`] +* {xref-EIP712-_hashTypedDataV4-bytes32-}[`++_hashTypedDataV4(structHash)++`] +* {xref-EIP712-eip712Domain--}[`++eip712Domain()++`] +* {xref-EIP712-_EIP712Name--}[`++_EIP712Name()++`] +* {xref-EIP712-_EIP712Version--}[`++_EIP712Version()++`] + +[.contract-subindex-inherited] +.IERC5267 + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-index] +.Events +-- +* {xref-GovernorTimelockControl-TimelockChange-address-address-}[`++TimelockChange(oldTimelock, newTimelock)++`] + +[.contract-subindex-inherited] +.Governor + +[.contract-subindex-inherited] +.IERC1155Receiver + +[.contract-subindex-inherited] +.IERC721Receiver + +[.contract-subindex-inherited] +.IGovernor +* {xref-IGovernor-ProposalCreated-uint256-address-address---uint256---string---bytes---uint256-uint256-string-}[`++ProposalCreated(proposalId, proposer, targets, values, signatures, calldatas, voteStart, voteEnd, description)++`] +* {xref-IGovernor-ProposalQueued-uint256-uint256-}[`++ProposalQueued(proposalId, etaSeconds)++`] +* {xref-IGovernor-ProposalExecuted-uint256-}[`++ProposalExecuted(proposalId)++`] +* {xref-IGovernor-ProposalCanceled-uint256-}[`++ProposalCanceled(proposalId)++`] +* {xref-IGovernor-VoteCast-address-uint256-uint8-uint256-string-}[`++VoteCast(voter, proposalId, support, weight, reason)++`] +* {xref-IGovernor-VoteCastWithParams-address-uint256-uint8-uint256-string-bytes-}[`++VoteCastWithParams(voter, proposalId, support, weight, reason, params)++`] + +[.contract-subindex-inherited] +.IERC6372 + +[.contract-subindex-inherited] +.Nonces + +[.contract-subindex-inherited] +.EIP712 + +[.contract-subindex-inherited] +.IERC5267 +* {xref-IERC5267-EIP712DomainChanged--}[`++EIP712DomainChanged()++`] + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-index] +.Errors +-- + +[.contract-subindex-inherited] +.Governor + +[.contract-subindex-inherited] +.IERC1155Receiver + +[.contract-subindex-inherited] +.IERC721Receiver + +[.contract-subindex-inherited] +.IGovernor +* {xref-IGovernor-GovernorInvalidProposalLength-uint256-uint256-uint256-}[`++GovernorInvalidProposalLength(targets, calldatas, values)++`] +* {xref-IGovernor-GovernorAlreadyCastVote-address-}[`++GovernorAlreadyCastVote(voter)++`] +* {xref-IGovernor-GovernorDisabledDeposit--}[`++GovernorDisabledDeposit()++`] +* {xref-IGovernor-GovernorOnlyProposer-address-}[`++GovernorOnlyProposer(account)++`] +* {xref-IGovernor-GovernorOnlyExecutor-address-}[`++GovernorOnlyExecutor(account)++`] +* {xref-IGovernor-GovernorNonexistentProposal-uint256-}[`++GovernorNonexistentProposal(proposalId)++`] +* {xref-IGovernor-GovernorUnexpectedProposalState-uint256-enum-IGovernor-ProposalState-bytes32-}[`++GovernorUnexpectedProposalState(proposalId, current, expectedStates)++`] +* {xref-IGovernor-GovernorInvalidVotingPeriod-uint256-}[`++GovernorInvalidVotingPeriod(votingPeriod)++`] +* {xref-IGovernor-GovernorInsufficientProposerVotes-address-uint256-uint256-}[`++GovernorInsufficientProposerVotes(proposer, votes, threshold)++`] +* {xref-IGovernor-GovernorRestrictedProposer-address-}[`++GovernorRestrictedProposer(proposer)++`] +* {xref-IGovernor-GovernorInvalidVoteType--}[`++GovernorInvalidVoteType()++`] +* {xref-IGovernor-GovernorQueueNotImplemented--}[`++GovernorQueueNotImplemented()++`] +* {xref-IGovernor-GovernorNotQueuedProposal-uint256-}[`++GovernorNotQueuedProposal(proposalId)++`] +* {xref-IGovernor-GovernorAlreadyQueuedProposal-uint256-}[`++GovernorAlreadyQueuedProposal(proposalId)++`] +* {xref-IGovernor-GovernorInvalidSignature-address-}[`++GovernorInvalidSignature(voter)++`] + +[.contract-subindex-inherited] +.IERC6372 + +[.contract-subindex-inherited] +.Nonces +* {xref-Nonces-InvalidAccountNonce-address-uint256-}[`++InvalidAccountNonce(account, currentNonce)++`] + +[.contract-subindex-inherited] +.EIP712 + +[.contract-subindex-inherited] +.IERC5267 + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-item] +[[GovernorTimelockControl-constructor-contract-TimelockController-]] +==== `[.contract-item-name]#++constructor++#++(contract TimelockController timelockAddress)++` [.item-kind]#internal# + +Set the timelock. + +[.contract-item] +[[GovernorTimelockControl-state-uint256-]] +==== `[.contract-item-name]#++state++#++(uint256 proposalId) β†’ enum IGovernor.ProposalState++` [.item-kind]#public# + +Overridden version of the {Governor-state} function that considers the status reported by the timelock. + +[.contract-item] +[[GovernorTimelockControl-timelock--]] +==== `[.contract-item-name]#++timelock++#++() β†’ address++` [.item-kind]#public# + +Public accessor to check the address of the timelock + +[.contract-item] +[[GovernorTimelockControl-proposalNeedsQueuing-uint256-]] +==== `[.contract-item-name]#++proposalNeedsQueuing++#++(uint256) β†’ bool++` [.item-kind]#public# + +See {IGovernor-proposalNeedsQueuing}. + +[.contract-item] +[[GovernorTimelockControl-_queueOperations-uint256-address---uint256---bytes---bytes32-]] +==== `[.contract-item-name]#++_queueOperations++#++(uint256 proposalId, address[] targets, uint256[] values, bytes[] calldatas, bytes32 descriptionHash) β†’ uint48++` [.item-kind]#internal# + +Function to queue a proposal to the timelock. + +[.contract-item] +[[GovernorTimelockControl-_executeOperations-uint256-address---uint256---bytes---bytes32-]] +==== `[.contract-item-name]#++_executeOperations++#++(uint256 proposalId, address[] targets, uint256[] values, bytes[] calldatas, bytes32 descriptionHash)++` [.item-kind]#internal# + +Overridden version of the {Governor-_executeOperations} function that runs the already queued proposal +through the timelock. + +[.contract-item] +[[GovernorTimelockControl-_cancel-address---uint256---bytes---bytes32-]] +==== `[.contract-item-name]#++_cancel++#++(address[] targets, uint256[] values, bytes[] calldatas, bytes32 descriptionHash) β†’ uint256++` [.item-kind]#internal# + +Overridden version of the {Governor-_cancel} function to cancel the timelocked proposal if it has already +been queued. + +[.contract-item] +[[GovernorTimelockControl-_executor--]] +==== `[.contract-item-name]#++_executor++#++() β†’ address++` [.item-kind]#internal# + +Address through which the governor executes action. In this case, the timelock. + +[.contract-item] +[[GovernorTimelockControl-updateTimelock-contract-TimelockController-]] +==== `[.contract-item-name]#++updateTimelock++#++(contract TimelockController newTimelock)++` [.item-kind]#external# + +Public endpoint to update the underlying timelock instance. Restricted to the timelock itself, so updates +must be proposed, scheduled, and executed through governance proposals. + +CAUTION: It is not recommended to change the timelock while there are other queued governance proposals. + +[.contract-item] +[[GovernorTimelockControl-TimelockChange-address-address-]] +==== `[.contract-item-name]#++TimelockChange++#++(address oldTimelock, address newTimelock)++` [.item-kind]#event# + +Emitted when the timelock controller used for proposal execution is modified. + +:TimelockChange: pass:normal[xref:#GovernorTimelockCompound-TimelockChange-address-address-[`++TimelockChange++`]] +:constructor: pass:normal[xref:#GovernorTimelockCompound-constructor-contract-ICompoundTimelock-[`++constructor++`]] +:state: pass:normal[xref:#GovernorTimelockCompound-state-uint256-[`++state++`]] +:timelock: pass:normal[xref:#GovernorTimelockCompound-timelock--[`++timelock++`]] +:proposalNeedsQueuing: pass:normal[xref:#GovernorTimelockCompound-proposalNeedsQueuing-uint256-[`++proposalNeedsQueuing++`]] +:_queueOperations: pass:normal[xref:#GovernorTimelockCompound-_queueOperations-uint256-address---uint256---bytes---bytes32-[`++_queueOperations++`]] +:_executeOperations: pass:normal[xref:#GovernorTimelockCompound-_executeOperations-uint256-address---uint256---bytes---bytes32-[`++_executeOperations++`]] +:_cancel: pass:normal[xref:#GovernorTimelockCompound-_cancel-address---uint256---bytes---bytes32-[`++_cancel++`]] +:_executor: pass:normal[xref:#GovernorTimelockCompound-_executor--[`++_executor++`]] +:__acceptAdmin: pass:normal[xref:#GovernorTimelockCompound-__acceptAdmin--[`++__acceptAdmin++`]] +:updateTimelock: pass:normal[xref:#GovernorTimelockCompound-updateTimelock-contract-ICompoundTimelock-[`++updateTimelock++`]] + +[.contract] +[[GovernorTimelockCompound]] +=== `++GovernorTimelockCompound++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/governance/extensions/GovernorTimelockCompound.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/governance/extensions/GovernorTimelockCompound.sol"; +``` + +Extension of {Governor} that binds the execution process to a Compound Timelock. This adds a delay, enforced by +the external timelock to all successful proposal (in addition to the voting duration). The {Governor} needs to be +the admin of the timelock for any operation to be performed. A public, unrestricted, +{GovernorTimelockCompound-__acceptAdmin} is available to accept ownership of the timelock. + +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. + +[.contract-index] +.Functions +-- +* {xref-GovernorTimelockCompound-constructor-contract-ICompoundTimelock-}[`++constructor(timelockAddress)++`] +* {xref-GovernorTimelockCompound-state-uint256-}[`++state(proposalId)++`] +* {xref-GovernorTimelockCompound-timelock--}[`++timelock()++`] +* {xref-GovernorTimelockCompound-proposalNeedsQueuing-uint256-}[`++proposalNeedsQueuing()++`] +* {xref-GovernorTimelockCompound-_queueOperations-uint256-address---uint256---bytes---bytes32-}[`++_queueOperations(proposalId, targets, values, calldatas, )++`] +* {xref-GovernorTimelockCompound-_executeOperations-uint256-address---uint256---bytes---bytes32-}[`++_executeOperations(proposalId, targets, values, calldatas, )++`] +* {xref-GovernorTimelockCompound-_cancel-address---uint256---bytes---bytes32-}[`++_cancel(targets, values, calldatas, descriptionHash)++`] +* {xref-GovernorTimelockCompound-_executor--}[`++_executor()++`] +* {xref-GovernorTimelockCompound-__acceptAdmin--}[`++__acceptAdmin()++`] +* {xref-GovernorTimelockCompound-updateTimelock-contract-ICompoundTimelock-}[`++updateTimelock(newTimelock)++`] + +[.contract-subindex-inherited] +.Governor +* {xref-Governor-receive--}[`++receive()++`] +* {xref-Governor-supportsInterface-bytes4-}[`++supportsInterface(interfaceId)++`] +* {xref-Governor-name--}[`++name()++`] +* {xref-Governor-version--}[`++version()++`] +* {xref-Governor-hashProposal-address---uint256---bytes---bytes32-}[`++hashProposal(targets, values, calldatas, descriptionHash)++`] +* {xref-Governor-proposalThreshold--}[`++proposalThreshold()++`] +* {xref-Governor-proposalSnapshot-uint256-}[`++proposalSnapshot(proposalId)++`] +* {xref-Governor-proposalDeadline-uint256-}[`++proposalDeadline(proposalId)++`] +* {xref-Governor-proposalProposer-uint256-}[`++proposalProposer(proposalId)++`] +* {xref-Governor-proposalEta-uint256-}[`++proposalEta(proposalId)++`] +* {xref-Governor-_checkGovernance--}[`++_checkGovernance()++`] +* {xref-Governor-_quorumReached-uint256-}[`++_quorumReached(proposalId)++`] +* {xref-Governor-_voteSucceeded-uint256-}[`++_voteSucceeded(proposalId)++`] +* {xref-Governor-_getVotes-address-uint256-bytes-}[`++_getVotes(account, timepoint, params)++`] +* {xref-Governor-_countVote-uint256-address-uint8-uint256-bytes-}[`++_countVote(proposalId, account, support, weight, params)++`] +* {xref-Governor-_defaultParams--}[`++_defaultParams()++`] +* {xref-Governor-propose-address---uint256---bytes---string-}[`++propose(targets, values, calldatas, description)++`] +* {xref-Governor-_propose-address---uint256---bytes---string-address-}[`++_propose(targets, values, calldatas, description, proposer)++`] +* {xref-Governor-queue-address---uint256---bytes---bytes32-}[`++queue(targets, values, calldatas, descriptionHash)++`] +* {xref-Governor-execute-address---uint256---bytes---bytes32-}[`++execute(targets, values, calldatas, descriptionHash)++`] +* {xref-Governor-cancel-address---uint256---bytes---bytes32-}[`++cancel(targets, values, calldatas, descriptionHash)++`] +* {xref-Governor-getVotes-address-uint256-}[`++getVotes(account, timepoint)++`] +* {xref-Governor-getVotesWithParams-address-uint256-bytes-}[`++getVotesWithParams(account, timepoint, params)++`] +* {xref-Governor-castVote-uint256-uint8-}[`++castVote(proposalId, support)++`] +* {xref-Governor-castVoteWithReason-uint256-uint8-string-}[`++castVoteWithReason(proposalId, support, reason)++`] +* {xref-Governor-castVoteWithReasonAndParams-uint256-uint8-string-bytes-}[`++castVoteWithReasonAndParams(proposalId, support, reason, params)++`] +* {xref-Governor-castVoteBySig-uint256-uint8-address-bytes-}[`++castVoteBySig(proposalId, support, voter, signature)++`] +* {xref-Governor-castVoteWithReasonAndParamsBySig-uint256-uint8-address-string-bytes-bytes-}[`++castVoteWithReasonAndParamsBySig(proposalId, support, voter, reason, params, signature)++`] +* {xref-Governor-_castVote-uint256-address-uint8-string-}[`++_castVote(proposalId, account, support, reason)++`] +* {xref-Governor-_castVote-uint256-address-uint8-string-bytes-}[`++_castVote(proposalId, account, support, reason, params)++`] +* {xref-Governor-relay-address-uint256-bytes-}[`++relay(target, value, data)++`] +* {xref-Governor-onERC721Received-address-address-uint256-bytes-}[`++onERC721Received(, , , )++`] +* {xref-Governor-onERC1155Received-address-address-uint256-uint256-bytes-}[`++onERC1155Received(, , , , )++`] +* {xref-Governor-onERC1155BatchReceived-address-address-uint256---uint256---bytes-}[`++onERC1155BatchReceived(, , , , )++`] +* {xref-Governor-_encodeStateBitmap-enum-IGovernor-ProposalState-}[`++_encodeStateBitmap(proposalState)++`] +* {xref-Governor-_isValidDescriptionForProposer-address-string-}[`++_isValidDescriptionForProposer(proposer, description)++`] +* {xref-Governor-clock--}[`++clock()++`] +* {xref-Governor-CLOCK_MODE--}[`++CLOCK_MODE()++`] +* {xref-Governor-votingDelay--}[`++votingDelay()++`] +* {xref-Governor-votingPeriod--}[`++votingPeriod()++`] +* {xref-Governor-quorum-uint256-}[`++quorum(timepoint)++`] + +[.contract-subindex-inherited] +.IERC1155Receiver + +[.contract-subindex-inherited] +.IERC721Receiver + +[.contract-subindex-inherited] +.IGovernor +* {xref-IGovernor-COUNTING_MODE--}[`++COUNTING_MODE()++`] +* {xref-IGovernor-hasVoted-uint256-address-}[`++hasVoted(proposalId, account)++`] + +[.contract-subindex-inherited] +.IERC6372 + +[.contract-subindex-inherited] +.Nonces +* {xref-Nonces-nonces-address-}[`++nonces(owner)++`] +* {xref-Nonces-_useNonce-address-}[`++_useNonce(owner)++`] +* {xref-Nonces-_useCheckedNonce-address-uint256-}[`++_useCheckedNonce(owner, nonce)++`] + +[.contract-subindex-inherited] +.EIP712 +* {xref-EIP712-_domainSeparatorV4--}[`++_domainSeparatorV4()++`] +* {xref-EIP712-_hashTypedDataV4-bytes32-}[`++_hashTypedDataV4(structHash)++`] +* {xref-EIP712-eip712Domain--}[`++eip712Domain()++`] +* {xref-EIP712-_EIP712Name--}[`++_EIP712Name()++`] +* {xref-EIP712-_EIP712Version--}[`++_EIP712Version()++`] + +[.contract-subindex-inherited] +.IERC5267 + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-index] +.Events +-- +* {xref-GovernorTimelockCompound-TimelockChange-address-address-}[`++TimelockChange(oldTimelock, newTimelock)++`] + +[.contract-subindex-inherited] +.Governor + +[.contract-subindex-inherited] +.IERC1155Receiver + +[.contract-subindex-inherited] +.IERC721Receiver + +[.contract-subindex-inherited] +.IGovernor +* {xref-IGovernor-ProposalCreated-uint256-address-address---uint256---string---bytes---uint256-uint256-string-}[`++ProposalCreated(proposalId, proposer, targets, values, signatures, calldatas, voteStart, voteEnd, description)++`] +* {xref-IGovernor-ProposalQueued-uint256-uint256-}[`++ProposalQueued(proposalId, etaSeconds)++`] +* {xref-IGovernor-ProposalExecuted-uint256-}[`++ProposalExecuted(proposalId)++`] +* {xref-IGovernor-ProposalCanceled-uint256-}[`++ProposalCanceled(proposalId)++`] +* {xref-IGovernor-VoteCast-address-uint256-uint8-uint256-string-}[`++VoteCast(voter, proposalId, support, weight, reason)++`] +* {xref-IGovernor-VoteCastWithParams-address-uint256-uint8-uint256-string-bytes-}[`++VoteCastWithParams(voter, proposalId, support, weight, reason, params)++`] + +[.contract-subindex-inherited] +.IERC6372 + +[.contract-subindex-inherited] +.Nonces + +[.contract-subindex-inherited] +.EIP712 + +[.contract-subindex-inherited] +.IERC5267 +* {xref-IERC5267-EIP712DomainChanged--}[`++EIP712DomainChanged()++`] + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-index] +.Errors +-- + +[.contract-subindex-inherited] +.Governor + +[.contract-subindex-inherited] +.IERC1155Receiver + +[.contract-subindex-inherited] +.IERC721Receiver + +[.contract-subindex-inherited] +.IGovernor +* {xref-IGovernor-GovernorInvalidProposalLength-uint256-uint256-uint256-}[`++GovernorInvalidProposalLength(targets, calldatas, values)++`] +* {xref-IGovernor-GovernorAlreadyCastVote-address-}[`++GovernorAlreadyCastVote(voter)++`] +* {xref-IGovernor-GovernorDisabledDeposit--}[`++GovernorDisabledDeposit()++`] +* {xref-IGovernor-GovernorOnlyProposer-address-}[`++GovernorOnlyProposer(account)++`] +* {xref-IGovernor-GovernorOnlyExecutor-address-}[`++GovernorOnlyExecutor(account)++`] +* {xref-IGovernor-GovernorNonexistentProposal-uint256-}[`++GovernorNonexistentProposal(proposalId)++`] +* {xref-IGovernor-GovernorUnexpectedProposalState-uint256-enum-IGovernor-ProposalState-bytes32-}[`++GovernorUnexpectedProposalState(proposalId, current, expectedStates)++`] +* {xref-IGovernor-GovernorInvalidVotingPeriod-uint256-}[`++GovernorInvalidVotingPeriod(votingPeriod)++`] +* {xref-IGovernor-GovernorInsufficientProposerVotes-address-uint256-uint256-}[`++GovernorInsufficientProposerVotes(proposer, votes, threshold)++`] +* {xref-IGovernor-GovernorRestrictedProposer-address-}[`++GovernorRestrictedProposer(proposer)++`] +* {xref-IGovernor-GovernorInvalidVoteType--}[`++GovernorInvalidVoteType()++`] +* {xref-IGovernor-GovernorQueueNotImplemented--}[`++GovernorQueueNotImplemented()++`] +* {xref-IGovernor-GovernorNotQueuedProposal-uint256-}[`++GovernorNotQueuedProposal(proposalId)++`] +* {xref-IGovernor-GovernorAlreadyQueuedProposal-uint256-}[`++GovernorAlreadyQueuedProposal(proposalId)++`] +* {xref-IGovernor-GovernorInvalidSignature-address-}[`++GovernorInvalidSignature(voter)++`] + +[.contract-subindex-inherited] +.IERC6372 + +[.contract-subindex-inherited] +.Nonces +* {xref-Nonces-InvalidAccountNonce-address-uint256-}[`++InvalidAccountNonce(account, currentNonce)++`] + +[.contract-subindex-inherited] +.EIP712 + +[.contract-subindex-inherited] +.IERC5267 + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-item] +[[GovernorTimelockCompound-constructor-contract-ICompoundTimelock-]] +==== `[.contract-item-name]#++constructor++#++(contract ICompoundTimelock timelockAddress)++` [.item-kind]#internal# + +Set the timelock. + +[.contract-item] +[[GovernorTimelockCompound-state-uint256-]] +==== `[.contract-item-name]#++state++#++(uint256 proposalId) β†’ enum IGovernor.ProposalState++` [.item-kind]#public# + +Overridden version of the {Governor-state} function with added support for the `Expired` state. + +[.contract-item] +[[GovernorTimelockCompound-timelock--]] +==== `[.contract-item-name]#++timelock++#++() β†’ address++` [.item-kind]#public# + +Public accessor to check the address of the timelock + +[.contract-item] +[[GovernorTimelockCompound-proposalNeedsQueuing-uint256-]] +==== `[.contract-item-name]#++proposalNeedsQueuing++#++(uint256) β†’ bool++` [.item-kind]#public# + +See {IGovernor-proposalNeedsQueuing}. + +[.contract-item] +[[GovernorTimelockCompound-_queueOperations-uint256-address---uint256---bytes---bytes32-]] +==== `[.contract-item-name]#++_queueOperations++#++(uint256 proposalId, address[] targets, uint256[] values, bytes[] calldatas, bytes32) β†’ uint48++` [.item-kind]#internal# + +Function to queue a proposal to the timelock. + +[.contract-item] +[[GovernorTimelockCompound-_executeOperations-uint256-address---uint256---bytes---bytes32-]] +==== `[.contract-item-name]#++_executeOperations++#++(uint256 proposalId, address[] targets, uint256[] values, bytes[] calldatas, bytes32)++` [.item-kind]#internal# + +Overridden version of the {Governor-_executeOperations} function that run the already queued proposal +through the timelock. + +[.contract-item] +[[GovernorTimelockCompound-_cancel-address---uint256---bytes---bytes32-]] +==== `[.contract-item-name]#++_cancel++#++(address[] targets, uint256[] values, bytes[] calldatas, bytes32 descriptionHash) β†’ uint256++` [.item-kind]#internal# + +Overridden version of the {Governor-_cancel} function to cancel the timelocked proposal if it has already +been queued. + +[.contract-item] +[[GovernorTimelockCompound-_executor--]] +==== `[.contract-item-name]#++_executor++#++() β†’ address++` [.item-kind]#internal# + +Address through which the governor executes action. In this case, the timelock. + +[.contract-item] +[[GovernorTimelockCompound-__acceptAdmin--]] +==== `[.contract-item-name]#++__acceptAdmin++#++()++` [.item-kind]#public# + +Accept admin right over the timelock. + +[.contract-item] +[[GovernorTimelockCompound-updateTimelock-contract-ICompoundTimelock-]] +==== `[.contract-item-name]#++updateTimelock++#++(contract ICompoundTimelock newTimelock)++` [.item-kind]#external# + +Public endpoint to update the underlying timelock instance. Restricted to the timelock itself, so updates +must be proposed, scheduled, and executed through governance proposals. + +For security reasons, the timelock must be handed over to another admin before setting up a new one. The two +operations (hand over the timelock) and do the update can be batched in a single proposal. + +Note that if the timelock admin has been handed over in a previous operation, we refuse updates made through the +timelock if admin of the timelock has already been accepted and the operation is executed outside the scope of +governance. + +CAUTION: It is not recommended to change the timelock while there are other queued governance proposals. + +[.contract-item] +[[GovernorTimelockCompound-TimelockChange-address-address-]] +==== `[.contract-item-name]#++TimelockChange++#++(address oldTimelock, address newTimelock)++` [.item-kind]#event# + +Emitted when the timelock controller used for proposal execution is modified. + +:VotingDelaySet: pass:normal[xref:#GovernorSettings-VotingDelaySet-uint256-uint256-[`++VotingDelaySet++`]] +:VotingPeriodSet: pass:normal[xref:#GovernorSettings-VotingPeriodSet-uint256-uint256-[`++VotingPeriodSet++`]] +:ProposalThresholdSet: pass:normal[xref:#GovernorSettings-ProposalThresholdSet-uint256-uint256-[`++ProposalThresholdSet++`]] +:constructor: pass:normal[xref:#GovernorSettings-constructor-uint48-uint32-uint256-[`++constructor++`]] +:votingDelay: pass:normal[xref:#GovernorSettings-votingDelay--[`++votingDelay++`]] +:votingPeriod: pass:normal[xref:#GovernorSettings-votingPeriod--[`++votingPeriod++`]] +:proposalThreshold: pass:normal[xref:#GovernorSettings-proposalThreshold--[`++proposalThreshold++`]] +:setVotingDelay: pass:normal[xref:#GovernorSettings-setVotingDelay-uint48-[`++setVotingDelay++`]] +:setVotingPeriod: pass:normal[xref:#GovernorSettings-setVotingPeriod-uint32-[`++setVotingPeriod++`]] +:setProposalThreshold: pass:normal[xref:#GovernorSettings-setProposalThreshold-uint256-[`++setProposalThreshold++`]] +:_setVotingDelay: pass:normal[xref:#GovernorSettings-_setVotingDelay-uint48-[`++_setVotingDelay++`]] +:_setVotingPeriod: pass:normal[xref:#GovernorSettings-_setVotingPeriod-uint32-[`++_setVotingPeriod++`]] +:_setProposalThreshold: pass:normal[xref:#GovernorSettings-_setProposalThreshold-uint256-[`++_setProposalThreshold++`]] + +[.contract] +[[GovernorSettings]] +=== `++GovernorSettings++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/governance/extensions/GovernorSettings.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/governance/extensions/GovernorSettings.sol"; +``` + +Extension of {Governor} for settings updatable through governance. + +[.contract-index] +.Functions +-- +* {xref-GovernorSettings-constructor-uint48-uint32-uint256-}[`++constructor(initialVotingDelay, initialVotingPeriod, initialProposalThreshold)++`] +* {xref-GovernorSettings-votingDelay--}[`++votingDelay()++`] +* {xref-GovernorSettings-votingPeriod--}[`++votingPeriod()++`] +* {xref-GovernorSettings-proposalThreshold--}[`++proposalThreshold()++`] +* {xref-GovernorSettings-setVotingDelay-uint48-}[`++setVotingDelay(newVotingDelay)++`] +* {xref-GovernorSettings-setVotingPeriod-uint32-}[`++setVotingPeriod(newVotingPeriod)++`] +* {xref-GovernorSettings-setProposalThreshold-uint256-}[`++setProposalThreshold(newProposalThreshold)++`] +* {xref-GovernorSettings-_setVotingDelay-uint48-}[`++_setVotingDelay(newVotingDelay)++`] +* {xref-GovernorSettings-_setVotingPeriod-uint32-}[`++_setVotingPeriod(newVotingPeriod)++`] +* {xref-GovernorSettings-_setProposalThreshold-uint256-}[`++_setProposalThreshold(newProposalThreshold)++`] + +[.contract-subindex-inherited] +.Governor +* {xref-Governor-receive--}[`++receive()++`] +* {xref-Governor-supportsInterface-bytes4-}[`++supportsInterface(interfaceId)++`] +* {xref-Governor-name--}[`++name()++`] +* {xref-Governor-version--}[`++version()++`] +* {xref-Governor-hashProposal-address---uint256---bytes---bytes32-}[`++hashProposal(targets, values, calldatas, descriptionHash)++`] +* {xref-Governor-state-uint256-}[`++state(proposalId)++`] +* {xref-Governor-proposalSnapshot-uint256-}[`++proposalSnapshot(proposalId)++`] +* {xref-Governor-proposalDeadline-uint256-}[`++proposalDeadline(proposalId)++`] +* {xref-Governor-proposalProposer-uint256-}[`++proposalProposer(proposalId)++`] +* {xref-Governor-proposalEta-uint256-}[`++proposalEta(proposalId)++`] +* {xref-Governor-proposalNeedsQueuing-uint256-}[`++proposalNeedsQueuing()++`] +* {xref-Governor-_checkGovernance--}[`++_checkGovernance()++`] +* {xref-Governor-_quorumReached-uint256-}[`++_quorumReached(proposalId)++`] +* {xref-Governor-_voteSucceeded-uint256-}[`++_voteSucceeded(proposalId)++`] +* {xref-Governor-_getVotes-address-uint256-bytes-}[`++_getVotes(account, timepoint, params)++`] +* {xref-Governor-_countVote-uint256-address-uint8-uint256-bytes-}[`++_countVote(proposalId, account, support, weight, params)++`] +* {xref-Governor-_defaultParams--}[`++_defaultParams()++`] +* {xref-Governor-propose-address---uint256---bytes---string-}[`++propose(targets, values, calldatas, description)++`] +* {xref-Governor-_propose-address---uint256---bytes---string-address-}[`++_propose(targets, values, calldatas, description, proposer)++`] +* {xref-Governor-queue-address---uint256---bytes---bytes32-}[`++queue(targets, values, calldatas, descriptionHash)++`] +* {xref-Governor-_queueOperations-uint256-address---uint256---bytes---bytes32-}[`++_queueOperations(, , , , )++`] +* {xref-Governor-execute-address---uint256---bytes---bytes32-}[`++execute(targets, values, calldatas, descriptionHash)++`] +* {xref-Governor-_executeOperations-uint256-address---uint256---bytes---bytes32-}[`++_executeOperations(, targets, values, calldatas, )++`] +* {xref-Governor-cancel-address---uint256---bytes---bytes32-}[`++cancel(targets, values, calldatas, descriptionHash)++`] +* {xref-Governor-_cancel-address---uint256---bytes---bytes32-}[`++_cancel(targets, values, calldatas, descriptionHash)++`] +* {xref-Governor-getVotes-address-uint256-}[`++getVotes(account, timepoint)++`] +* {xref-Governor-getVotesWithParams-address-uint256-bytes-}[`++getVotesWithParams(account, timepoint, params)++`] +* {xref-Governor-castVote-uint256-uint8-}[`++castVote(proposalId, support)++`] +* {xref-Governor-castVoteWithReason-uint256-uint8-string-}[`++castVoteWithReason(proposalId, support, reason)++`] +* {xref-Governor-castVoteWithReasonAndParams-uint256-uint8-string-bytes-}[`++castVoteWithReasonAndParams(proposalId, support, reason, params)++`] +* {xref-Governor-castVoteBySig-uint256-uint8-address-bytes-}[`++castVoteBySig(proposalId, support, voter, signature)++`] +* {xref-Governor-castVoteWithReasonAndParamsBySig-uint256-uint8-address-string-bytes-bytes-}[`++castVoteWithReasonAndParamsBySig(proposalId, support, voter, reason, params, signature)++`] +* {xref-Governor-_castVote-uint256-address-uint8-string-}[`++_castVote(proposalId, account, support, reason)++`] +* {xref-Governor-_castVote-uint256-address-uint8-string-bytes-}[`++_castVote(proposalId, account, support, reason, params)++`] +* {xref-Governor-relay-address-uint256-bytes-}[`++relay(target, value, data)++`] +* {xref-Governor-_executor--}[`++_executor()++`] +* {xref-Governor-onERC721Received-address-address-uint256-bytes-}[`++onERC721Received(, , , )++`] +* {xref-Governor-onERC1155Received-address-address-uint256-uint256-bytes-}[`++onERC1155Received(, , , , )++`] +* {xref-Governor-onERC1155BatchReceived-address-address-uint256---uint256---bytes-}[`++onERC1155BatchReceived(, , , , )++`] +* {xref-Governor-_encodeStateBitmap-enum-IGovernor-ProposalState-}[`++_encodeStateBitmap(proposalState)++`] +* {xref-Governor-_isValidDescriptionForProposer-address-string-}[`++_isValidDescriptionForProposer(proposer, description)++`] +* {xref-Governor-clock--}[`++clock()++`] +* {xref-Governor-CLOCK_MODE--}[`++CLOCK_MODE()++`] +* {xref-Governor-quorum-uint256-}[`++quorum(timepoint)++`] + +[.contract-subindex-inherited] +.IERC1155Receiver + +[.contract-subindex-inherited] +.IERC721Receiver + +[.contract-subindex-inherited] +.IGovernor +* {xref-IGovernor-COUNTING_MODE--}[`++COUNTING_MODE()++`] +* {xref-IGovernor-hasVoted-uint256-address-}[`++hasVoted(proposalId, account)++`] + +[.contract-subindex-inherited] +.IERC6372 + +[.contract-subindex-inherited] +.Nonces +* {xref-Nonces-nonces-address-}[`++nonces(owner)++`] +* {xref-Nonces-_useNonce-address-}[`++_useNonce(owner)++`] +* {xref-Nonces-_useCheckedNonce-address-uint256-}[`++_useCheckedNonce(owner, nonce)++`] + +[.contract-subindex-inherited] +.EIP712 +* {xref-EIP712-_domainSeparatorV4--}[`++_domainSeparatorV4()++`] +* {xref-EIP712-_hashTypedDataV4-bytes32-}[`++_hashTypedDataV4(structHash)++`] +* {xref-EIP712-eip712Domain--}[`++eip712Domain()++`] +* {xref-EIP712-_EIP712Name--}[`++_EIP712Name()++`] +* {xref-EIP712-_EIP712Version--}[`++_EIP712Version()++`] + +[.contract-subindex-inherited] +.IERC5267 + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-index] +.Events +-- +* {xref-GovernorSettings-VotingDelaySet-uint256-uint256-}[`++VotingDelaySet(oldVotingDelay, newVotingDelay)++`] +* {xref-GovernorSettings-VotingPeriodSet-uint256-uint256-}[`++VotingPeriodSet(oldVotingPeriod, newVotingPeriod)++`] +* {xref-GovernorSettings-ProposalThresholdSet-uint256-uint256-}[`++ProposalThresholdSet(oldProposalThreshold, newProposalThreshold)++`] + +[.contract-subindex-inherited] +.Governor + +[.contract-subindex-inherited] +.IERC1155Receiver + +[.contract-subindex-inherited] +.IERC721Receiver + +[.contract-subindex-inherited] +.IGovernor +* {xref-IGovernor-ProposalCreated-uint256-address-address---uint256---string---bytes---uint256-uint256-string-}[`++ProposalCreated(proposalId, proposer, targets, values, signatures, calldatas, voteStart, voteEnd, description)++`] +* {xref-IGovernor-ProposalQueued-uint256-uint256-}[`++ProposalQueued(proposalId, etaSeconds)++`] +* {xref-IGovernor-ProposalExecuted-uint256-}[`++ProposalExecuted(proposalId)++`] +* {xref-IGovernor-ProposalCanceled-uint256-}[`++ProposalCanceled(proposalId)++`] +* {xref-IGovernor-VoteCast-address-uint256-uint8-uint256-string-}[`++VoteCast(voter, proposalId, support, weight, reason)++`] +* {xref-IGovernor-VoteCastWithParams-address-uint256-uint8-uint256-string-bytes-}[`++VoteCastWithParams(voter, proposalId, support, weight, reason, params)++`] + +[.contract-subindex-inherited] +.IERC6372 + +[.contract-subindex-inherited] +.Nonces + +[.contract-subindex-inherited] +.EIP712 + +[.contract-subindex-inherited] +.IERC5267 +* {xref-IERC5267-EIP712DomainChanged--}[`++EIP712DomainChanged()++`] + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-index] +.Errors +-- + +[.contract-subindex-inherited] +.Governor + +[.contract-subindex-inherited] +.IERC1155Receiver + +[.contract-subindex-inherited] +.IERC721Receiver + +[.contract-subindex-inherited] +.IGovernor +* {xref-IGovernor-GovernorInvalidProposalLength-uint256-uint256-uint256-}[`++GovernorInvalidProposalLength(targets, calldatas, values)++`] +* {xref-IGovernor-GovernorAlreadyCastVote-address-}[`++GovernorAlreadyCastVote(voter)++`] +* {xref-IGovernor-GovernorDisabledDeposit--}[`++GovernorDisabledDeposit()++`] +* {xref-IGovernor-GovernorOnlyProposer-address-}[`++GovernorOnlyProposer(account)++`] +* {xref-IGovernor-GovernorOnlyExecutor-address-}[`++GovernorOnlyExecutor(account)++`] +* {xref-IGovernor-GovernorNonexistentProposal-uint256-}[`++GovernorNonexistentProposal(proposalId)++`] +* {xref-IGovernor-GovernorUnexpectedProposalState-uint256-enum-IGovernor-ProposalState-bytes32-}[`++GovernorUnexpectedProposalState(proposalId, current, expectedStates)++`] +* {xref-IGovernor-GovernorInvalidVotingPeriod-uint256-}[`++GovernorInvalidVotingPeriod(votingPeriod)++`] +* {xref-IGovernor-GovernorInsufficientProposerVotes-address-uint256-uint256-}[`++GovernorInsufficientProposerVotes(proposer, votes, threshold)++`] +* {xref-IGovernor-GovernorRestrictedProposer-address-}[`++GovernorRestrictedProposer(proposer)++`] +* {xref-IGovernor-GovernorInvalidVoteType--}[`++GovernorInvalidVoteType()++`] +* {xref-IGovernor-GovernorQueueNotImplemented--}[`++GovernorQueueNotImplemented()++`] +* {xref-IGovernor-GovernorNotQueuedProposal-uint256-}[`++GovernorNotQueuedProposal(proposalId)++`] +* {xref-IGovernor-GovernorAlreadyQueuedProposal-uint256-}[`++GovernorAlreadyQueuedProposal(proposalId)++`] +* {xref-IGovernor-GovernorInvalidSignature-address-}[`++GovernorInvalidSignature(voter)++`] + +[.contract-subindex-inherited] +.IERC6372 + +[.contract-subindex-inherited] +.Nonces +* {xref-Nonces-InvalidAccountNonce-address-uint256-}[`++InvalidAccountNonce(account, currentNonce)++`] + +[.contract-subindex-inherited] +.EIP712 + +[.contract-subindex-inherited] +.IERC5267 + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-item] +[[GovernorSettings-constructor-uint48-uint32-uint256-]] +==== `[.contract-item-name]#++constructor++#++(uint48 initialVotingDelay, uint32 initialVotingPeriod, uint256 initialProposalThreshold)++` [.item-kind]#internal# + +Initialize the governance parameters. + +[.contract-item] +[[GovernorSettings-votingDelay--]] +==== `[.contract-item-name]#++votingDelay++#++() β†’ uint256++` [.item-kind]#public# + +See {IGovernor-votingDelay}. + +[.contract-item] +[[GovernorSettings-votingPeriod--]] +==== `[.contract-item-name]#++votingPeriod++#++() β†’ uint256++` [.item-kind]#public# + +See {IGovernor-votingPeriod}. + +[.contract-item] +[[GovernorSettings-proposalThreshold--]] +==== `[.contract-item-name]#++proposalThreshold++#++() β†’ uint256++` [.item-kind]#public# + +See {Governor-proposalThreshold}. + +[.contract-item] +[[GovernorSettings-setVotingDelay-uint48-]] +==== `[.contract-item-name]#++setVotingDelay++#++(uint48 newVotingDelay)++` [.item-kind]#public# + +Update the voting delay. This operation can only be performed through a governance proposal. + +Emits a {VotingDelaySet} event. + +[.contract-item] +[[GovernorSettings-setVotingPeriod-uint32-]] +==== `[.contract-item-name]#++setVotingPeriod++#++(uint32 newVotingPeriod)++` [.item-kind]#public# + +Update the voting period. This operation can only be performed through a governance proposal. + +Emits a {VotingPeriodSet} event. + +[.contract-item] +[[GovernorSettings-setProposalThreshold-uint256-]] +==== `[.contract-item-name]#++setProposalThreshold++#++(uint256 newProposalThreshold)++` [.item-kind]#public# + +Update the proposal threshold. This operation can only be performed through a governance proposal. + +Emits a {ProposalThresholdSet} event. + +[.contract-item] +[[GovernorSettings-_setVotingDelay-uint48-]] +==== `[.contract-item-name]#++_setVotingDelay++#++(uint48 newVotingDelay)++` [.item-kind]#internal# + +Internal setter for the voting delay. + +Emits a {VotingDelaySet} event. + +[.contract-item] +[[GovernorSettings-_setVotingPeriod-uint32-]] +==== `[.contract-item-name]#++_setVotingPeriod++#++(uint32 newVotingPeriod)++` [.item-kind]#internal# + +Internal setter for the voting period. + +Emits a {VotingPeriodSet} event. + +[.contract-item] +[[GovernorSettings-_setProposalThreshold-uint256-]] +==== `[.contract-item-name]#++_setProposalThreshold++#++(uint256 newProposalThreshold)++` [.item-kind]#internal# + +Internal setter for the proposal threshold. + +Emits a {ProposalThresholdSet} event. + +[.contract-item] +[[GovernorSettings-VotingDelaySet-uint256-uint256-]] +==== `[.contract-item-name]#++VotingDelaySet++#++(uint256 oldVotingDelay, uint256 newVotingDelay)++` [.item-kind]#event# + +[.contract-item] +[[GovernorSettings-VotingPeriodSet-uint256-uint256-]] +==== `[.contract-item-name]#++VotingPeriodSet++#++(uint256 oldVotingPeriod, uint256 newVotingPeriod)++` [.item-kind]#event# + +[.contract-item] +[[GovernorSettings-ProposalThresholdSet-uint256-uint256-]] +==== `[.contract-item-name]#++ProposalThresholdSet++#++(uint256 oldProposalThreshold, uint256 newProposalThreshold)++` [.item-kind]#event# + +:ProposalExtended: pass:normal[xref:#GovernorPreventLateQuorum-ProposalExtended-uint256-uint64-[`++ProposalExtended++`]] +:LateQuorumVoteExtensionSet: pass:normal[xref:#GovernorPreventLateQuorum-LateQuorumVoteExtensionSet-uint64-uint64-[`++LateQuorumVoteExtensionSet++`]] +:constructor: pass:normal[xref:#GovernorPreventLateQuorum-constructor-uint48-[`++constructor++`]] +:proposalDeadline: pass:normal[xref:#GovernorPreventLateQuorum-proposalDeadline-uint256-[`++proposalDeadline++`]] +:_castVote: pass:normal[xref:#GovernorPreventLateQuorum-_castVote-uint256-address-uint8-string-bytes-[`++_castVote++`]] +:lateQuorumVoteExtension: pass:normal[xref:#GovernorPreventLateQuorum-lateQuorumVoteExtension--[`++lateQuorumVoteExtension++`]] +:setLateQuorumVoteExtension: pass:normal[xref:#GovernorPreventLateQuorum-setLateQuorumVoteExtension-uint48-[`++setLateQuorumVoteExtension++`]] +:_setLateQuorumVoteExtension: pass:normal[xref:#GovernorPreventLateQuorum-_setLateQuorumVoteExtension-uint48-[`++_setLateQuorumVoteExtension++`]] + +[.contract] +[[GovernorPreventLateQuorum]] +=== `++GovernorPreventLateQuorum++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/governance/extensions/GovernorPreventLateQuorum.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/governance/extensions/GovernorPreventLateQuorum.sol"; +``` + +A module that ensures there is a minimum voting period after quorum is reached. This prevents a large voter from +swaying a vote and triggering quorum at the last minute, by ensuring there is always time for other voters to react +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 specified time has passed (the "vote extension" parameter). This parameter can be set through a governance +proposal. + +[.contract-index] +.Functions +-- +* {xref-GovernorPreventLateQuorum-constructor-uint48-}[`++constructor(initialVoteExtension)++`] +* {xref-GovernorPreventLateQuorum-proposalDeadline-uint256-}[`++proposalDeadline(proposalId)++`] +* {xref-GovernorPreventLateQuorum-_castVote-uint256-address-uint8-string-bytes-}[`++_castVote(proposalId, account, support, reason, params)++`] +* {xref-GovernorPreventLateQuorum-lateQuorumVoteExtension--}[`++lateQuorumVoteExtension()++`] +* {xref-GovernorPreventLateQuorum-setLateQuorumVoteExtension-uint48-}[`++setLateQuorumVoteExtension(newVoteExtension)++`] +* {xref-GovernorPreventLateQuorum-_setLateQuorumVoteExtension-uint48-}[`++_setLateQuorumVoteExtension(newVoteExtension)++`] + +[.contract-subindex-inherited] +.Governor +* {xref-Governor-receive--}[`++receive()++`] +* {xref-Governor-supportsInterface-bytes4-}[`++supportsInterface(interfaceId)++`] +* {xref-Governor-name--}[`++name()++`] +* {xref-Governor-version--}[`++version()++`] +* {xref-Governor-hashProposal-address---uint256---bytes---bytes32-}[`++hashProposal(targets, values, calldatas, descriptionHash)++`] +* {xref-Governor-state-uint256-}[`++state(proposalId)++`] +* {xref-Governor-proposalThreshold--}[`++proposalThreshold()++`] +* {xref-Governor-proposalSnapshot-uint256-}[`++proposalSnapshot(proposalId)++`] +* {xref-Governor-proposalProposer-uint256-}[`++proposalProposer(proposalId)++`] +* {xref-Governor-proposalEta-uint256-}[`++proposalEta(proposalId)++`] +* {xref-Governor-proposalNeedsQueuing-uint256-}[`++proposalNeedsQueuing()++`] +* {xref-Governor-_checkGovernance--}[`++_checkGovernance()++`] +* {xref-Governor-_quorumReached-uint256-}[`++_quorumReached(proposalId)++`] +* {xref-Governor-_voteSucceeded-uint256-}[`++_voteSucceeded(proposalId)++`] +* {xref-Governor-_getVotes-address-uint256-bytes-}[`++_getVotes(account, timepoint, params)++`] +* {xref-Governor-_countVote-uint256-address-uint8-uint256-bytes-}[`++_countVote(proposalId, account, support, weight, params)++`] +* {xref-Governor-_defaultParams--}[`++_defaultParams()++`] +* {xref-Governor-propose-address---uint256---bytes---string-}[`++propose(targets, values, calldatas, description)++`] +* {xref-Governor-_propose-address---uint256---bytes---string-address-}[`++_propose(targets, values, calldatas, description, proposer)++`] +* {xref-Governor-queue-address---uint256---bytes---bytes32-}[`++queue(targets, values, calldatas, descriptionHash)++`] +* {xref-Governor-_queueOperations-uint256-address---uint256---bytes---bytes32-}[`++_queueOperations(, , , , )++`] +* {xref-Governor-execute-address---uint256---bytes---bytes32-}[`++execute(targets, values, calldatas, descriptionHash)++`] +* {xref-Governor-_executeOperations-uint256-address---uint256---bytes---bytes32-}[`++_executeOperations(, targets, values, calldatas, )++`] +* {xref-Governor-cancel-address---uint256---bytes---bytes32-}[`++cancel(targets, values, calldatas, descriptionHash)++`] +* {xref-Governor-_cancel-address---uint256---bytes---bytes32-}[`++_cancel(targets, values, calldatas, descriptionHash)++`] +* {xref-Governor-getVotes-address-uint256-}[`++getVotes(account, timepoint)++`] +* {xref-Governor-getVotesWithParams-address-uint256-bytes-}[`++getVotesWithParams(account, timepoint, params)++`] +* {xref-Governor-castVote-uint256-uint8-}[`++castVote(proposalId, support)++`] +* {xref-Governor-castVoteWithReason-uint256-uint8-string-}[`++castVoteWithReason(proposalId, support, reason)++`] +* {xref-Governor-castVoteWithReasonAndParams-uint256-uint8-string-bytes-}[`++castVoteWithReasonAndParams(proposalId, support, reason, params)++`] +* {xref-Governor-castVoteBySig-uint256-uint8-address-bytes-}[`++castVoteBySig(proposalId, support, voter, signature)++`] +* {xref-Governor-castVoteWithReasonAndParamsBySig-uint256-uint8-address-string-bytes-bytes-}[`++castVoteWithReasonAndParamsBySig(proposalId, support, voter, reason, params, signature)++`] +* {xref-Governor-_castVote-uint256-address-uint8-string-}[`++_castVote(proposalId, account, support, reason)++`] +* {xref-Governor-relay-address-uint256-bytes-}[`++relay(target, value, data)++`] +* {xref-Governor-_executor--}[`++_executor()++`] +* {xref-Governor-onERC721Received-address-address-uint256-bytes-}[`++onERC721Received(, , , )++`] +* {xref-Governor-onERC1155Received-address-address-uint256-uint256-bytes-}[`++onERC1155Received(, , , , )++`] +* {xref-Governor-onERC1155BatchReceived-address-address-uint256---uint256---bytes-}[`++onERC1155BatchReceived(, , , , )++`] +* {xref-Governor-_encodeStateBitmap-enum-IGovernor-ProposalState-}[`++_encodeStateBitmap(proposalState)++`] +* {xref-Governor-_isValidDescriptionForProposer-address-string-}[`++_isValidDescriptionForProposer(proposer, description)++`] +* {xref-Governor-clock--}[`++clock()++`] +* {xref-Governor-CLOCK_MODE--}[`++CLOCK_MODE()++`] +* {xref-Governor-votingDelay--}[`++votingDelay()++`] +* {xref-Governor-votingPeriod--}[`++votingPeriod()++`] +* {xref-Governor-quorum-uint256-}[`++quorum(timepoint)++`] + +[.contract-subindex-inherited] +.IERC1155Receiver + +[.contract-subindex-inherited] +.IERC721Receiver + +[.contract-subindex-inherited] +.IGovernor +* {xref-IGovernor-COUNTING_MODE--}[`++COUNTING_MODE()++`] +* {xref-IGovernor-hasVoted-uint256-address-}[`++hasVoted(proposalId, account)++`] + +[.contract-subindex-inherited] +.IERC6372 + +[.contract-subindex-inherited] +.Nonces +* {xref-Nonces-nonces-address-}[`++nonces(owner)++`] +* {xref-Nonces-_useNonce-address-}[`++_useNonce(owner)++`] +* {xref-Nonces-_useCheckedNonce-address-uint256-}[`++_useCheckedNonce(owner, nonce)++`] + +[.contract-subindex-inherited] +.EIP712 +* {xref-EIP712-_domainSeparatorV4--}[`++_domainSeparatorV4()++`] +* {xref-EIP712-_hashTypedDataV4-bytes32-}[`++_hashTypedDataV4(structHash)++`] +* {xref-EIP712-eip712Domain--}[`++eip712Domain()++`] +* {xref-EIP712-_EIP712Name--}[`++_EIP712Name()++`] +* {xref-EIP712-_EIP712Version--}[`++_EIP712Version()++`] + +[.contract-subindex-inherited] +.IERC5267 + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-index] +.Events +-- +* {xref-GovernorPreventLateQuorum-ProposalExtended-uint256-uint64-}[`++ProposalExtended(proposalId, extendedDeadline)++`] +* {xref-GovernorPreventLateQuorum-LateQuorumVoteExtensionSet-uint64-uint64-}[`++LateQuorumVoteExtensionSet(oldVoteExtension, newVoteExtension)++`] + +[.contract-subindex-inherited] +.Governor + +[.contract-subindex-inherited] +.IERC1155Receiver + +[.contract-subindex-inherited] +.IERC721Receiver + +[.contract-subindex-inherited] +.IGovernor +* {xref-IGovernor-ProposalCreated-uint256-address-address---uint256---string---bytes---uint256-uint256-string-}[`++ProposalCreated(proposalId, proposer, targets, values, signatures, calldatas, voteStart, voteEnd, description)++`] +* {xref-IGovernor-ProposalQueued-uint256-uint256-}[`++ProposalQueued(proposalId, etaSeconds)++`] +* {xref-IGovernor-ProposalExecuted-uint256-}[`++ProposalExecuted(proposalId)++`] +* {xref-IGovernor-ProposalCanceled-uint256-}[`++ProposalCanceled(proposalId)++`] +* {xref-IGovernor-VoteCast-address-uint256-uint8-uint256-string-}[`++VoteCast(voter, proposalId, support, weight, reason)++`] +* {xref-IGovernor-VoteCastWithParams-address-uint256-uint8-uint256-string-bytes-}[`++VoteCastWithParams(voter, proposalId, support, weight, reason, params)++`] + +[.contract-subindex-inherited] +.IERC6372 + +[.contract-subindex-inherited] +.Nonces + +[.contract-subindex-inherited] +.EIP712 + +[.contract-subindex-inherited] +.IERC5267 +* {xref-IERC5267-EIP712DomainChanged--}[`++EIP712DomainChanged()++`] + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-index] +.Errors +-- + +[.contract-subindex-inherited] +.Governor + +[.contract-subindex-inherited] +.IERC1155Receiver + +[.contract-subindex-inherited] +.IERC721Receiver + +[.contract-subindex-inherited] +.IGovernor +* {xref-IGovernor-GovernorInvalidProposalLength-uint256-uint256-uint256-}[`++GovernorInvalidProposalLength(targets, calldatas, values)++`] +* {xref-IGovernor-GovernorAlreadyCastVote-address-}[`++GovernorAlreadyCastVote(voter)++`] +* {xref-IGovernor-GovernorDisabledDeposit--}[`++GovernorDisabledDeposit()++`] +* {xref-IGovernor-GovernorOnlyProposer-address-}[`++GovernorOnlyProposer(account)++`] +* {xref-IGovernor-GovernorOnlyExecutor-address-}[`++GovernorOnlyExecutor(account)++`] +* {xref-IGovernor-GovernorNonexistentProposal-uint256-}[`++GovernorNonexistentProposal(proposalId)++`] +* {xref-IGovernor-GovernorUnexpectedProposalState-uint256-enum-IGovernor-ProposalState-bytes32-}[`++GovernorUnexpectedProposalState(proposalId, current, expectedStates)++`] +* {xref-IGovernor-GovernorInvalidVotingPeriod-uint256-}[`++GovernorInvalidVotingPeriod(votingPeriod)++`] +* {xref-IGovernor-GovernorInsufficientProposerVotes-address-uint256-uint256-}[`++GovernorInsufficientProposerVotes(proposer, votes, threshold)++`] +* {xref-IGovernor-GovernorRestrictedProposer-address-}[`++GovernorRestrictedProposer(proposer)++`] +* {xref-IGovernor-GovernorInvalidVoteType--}[`++GovernorInvalidVoteType()++`] +* {xref-IGovernor-GovernorQueueNotImplemented--}[`++GovernorQueueNotImplemented()++`] +* {xref-IGovernor-GovernorNotQueuedProposal-uint256-}[`++GovernorNotQueuedProposal(proposalId)++`] +* {xref-IGovernor-GovernorAlreadyQueuedProposal-uint256-}[`++GovernorAlreadyQueuedProposal(proposalId)++`] +* {xref-IGovernor-GovernorInvalidSignature-address-}[`++GovernorInvalidSignature(voter)++`] + +[.contract-subindex-inherited] +.IERC6372 + +[.contract-subindex-inherited] +.Nonces +* {xref-Nonces-InvalidAccountNonce-address-uint256-}[`++InvalidAccountNonce(account, currentNonce)++`] + +[.contract-subindex-inherited] +.EIP712 + +[.contract-subindex-inherited] +.IERC5267 + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-item] +[[GovernorPreventLateQuorum-constructor-uint48-]] +==== `[.contract-item-name]#++constructor++#++(uint48 initialVoteExtension)++` [.item-kind]#internal# + +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. + +[.contract-item] +[[GovernorPreventLateQuorum-proposalDeadline-uint256-]] +==== `[.contract-item-name]#++proposalDeadline++#++(uint256 proposalId) β†’ uint256++` [.item-kind]#public# + +Returns the proposal deadline, which may have been extended beyond that set at proposal creation, if the +proposal reached quorum late in the voting period. See {Governor-proposalDeadline}. + +[.contract-item] +[[GovernorPreventLateQuorum-_castVote-uint256-address-uint8-string-bytes-]] +==== `[.contract-item-name]#++_castVote++#++(uint256 proposalId, address account, uint8 support, string reason, bytes params) β†’ uint256++` [.item-kind]#internal# + +Casts a vote and detects if it caused quorum to be reached, potentially extending the voting period. See +{Governor-_castVote}. + +May emit a {ProposalExtended} event. + +[.contract-item] +[[GovernorPreventLateQuorum-lateQuorumVoteExtension--]] +==== `[.contract-item-name]#++lateQuorumVoteExtension++#++() β†’ uint48++` [.item-kind]#public# + +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. + +[.contract-item] +[[GovernorPreventLateQuorum-setLateQuorumVoteExtension-uint48-]] +==== `[.contract-item-name]#++setLateQuorumVoteExtension++#++(uint48 newVoteExtension)++` [.item-kind]#public# + +Changes the {lateQuorumVoteExtension}. This operation can only be performed by the governance executor, +generally through a governance proposal. + +Emits a {LateQuorumVoteExtensionSet} event. + +[.contract-item] +[[GovernorPreventLateQuorum-_setLateQuorumVoteExtension-uint48-]] +==== `[.contract-item-name]#++_setLateQuorumVoteExtension++#++(uint48 newVoteExtension)++` [.item-kind]#internal# + +Changes the {lateQuorumVoteExtension}. This is an internal function that can be exposed in a public function +like {setLateQuorumVoteExtension} if another access control mechanism is needed. + +Emits a {LateQuorumVoteExtensionSet} event. + +[.contract-item] +[[GovernorPreventLateQuorum-ProposalExtended-uint256-uint64-]] +==== `[.contract-item-name]#++ProposalExtended++#++(uint256 indexed proposalId, uint64 extendedDeadline)++` [.item-kind]#event# + +Emitted when a proposal deadline is pushed back due to reaching quorum late in its voting period. + +[.contract-item] +[[GovernorPreventLateQuorum-LateQuorumVoteExtensionSet-uint64-uint64-]] +==== `[.contract-item-name]#++LateQuorumVoteExtensionSet++#++(uint64 oldVoteExtension, uint64 newVoteExtension)++` [.item-kind]#event# + +Emitted when the {lateQuorumVoteExtension} parameter is changed. + +:ProposalDetails: pass:normal[xref:#GovernorStorage-ProposalDetails[`++ProposalDetails++`]] +:_propose: pass:normal[xref:#GovernorStorage-_propose-address---uint256---bytes---string-address-[`++_propose++`]] +:queue: pass:normal[xref:#GovernorStorage-queue-uint256-[`++queue++`]] +:execute: pass:normal[xref:#GovernorStorage-execute-uint256-[`++execute++`]] +:cancel: pass:normal[xref:#GovernorStorage-cancel-uint256-[`++cancel++`]] +:proposalCount: pass:normal[xref:#GovernorStorage-proposalCount--[`++proposalCount++`]] +:proposalDetails: pass:normal[xref:#GovernorStorage-proposalDetails-uint256-[`++proposalDetails++`]] +:proposalDetailsAt: pass:normal[xref:#GovernorStorage-proposalDetailsAt-uint256-[`++proposalDetailsAt++`]] + +[.contract] +[[GovernorStorage]] +=== `++GovernorStorage++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/governance/extensions/GovernorStorage.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/governance/extensions/GovernorStorage.sol"; +``` + +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. + +[.contract-index] +.Functions +-- +* {xref-GovernorStorage-_propose-address---uint256---bytes---string-address-}[`++_propose(targets, values, calldatas, description, proposer)++`] +* {xref-GovernorStorage-queue-uint256-}[`++queue(proposalId)++`] +* {xref-GovernorStorage-execute-uint256-}[`++execute(proposalId)++`] +* {xref-GovernorStorage-cancel-uint256-}[`++cancel(proposalId)++`] +* {xref-GovernorStorage-proposalCount--}[`++proposalCount()++`] +* {xref-GovernorStorage-proposalDetails-uint256-}[`++proposalDetails(proposalId)++`] +* {xref-GovernorStorage-proposalDetailsAt-uint256-}[`++proposalDetailsAt(index)++`] + +[.contract-subindex-inherited] +.Governor +* {xref-Governor-receive--}[`++receive()++`] +* {xref-Governor-supportsInterface-bytes4-}[`++supportsInterface(interfaceId)++`] +* {xref-Governor-name--}[`++name()++`] +* {xref-Governor-version--}[`++version()++`] +* {xref-Governor-hashProposal-address---uint256---bytes---bytes32-}[`++hashProposal(targets, values, calldatas, descriptionHash)++`] +* {xref-Governor-state-uint256-}[`++state(proposalId)++`] +* {xref-Governor-proposalThreshold--}[`++proposalThreshold()++`] +* {xref-Governor-proposalSnapshot-uint256-}[`++proposalSnapshot(proposalId)++`] +* {xref-Governor-proposalDeadline-uint256-}[`++proposalDeadline(proposalId)++`] +* {xref-Governor-proposalProposer-uint256-}[`++proposalProposer(proposalId)++`] +* {xref-Governor-proposalEta-uint256-}[`++proposalEta(proposalId)++`] +* {xref-Governor-proposalNeedsQueuing-uint256-}[`++proposalNeedsQueuing()++`] +* {xref-Governor-_checkGovernance--}[`++_checkGovernance()++`] +* {xref-Governor-_quorumReached-uint256-}[`++_quorumReached(proposalId)++`] +* {xref-Governor-_voteSucceeded-uint256-}[`++_voteSucceeded(proposalId)++`] +* {xref-Governor-_getVotes-address-uint256-bytes-}[`++_getVotes(account, timepoint, params)++`] +* {xref-Governor-_countVote-uint256-address-uint8-uint256-bytes-}[`++_countVote(proposalId, account, support, weight, params)++`] +* {xref-Governor-_defaultParams--}[`++_defaultParams()++`] +* {xref-Governor-propose-address---uint256---bytes---string-}[`++propose(targets, values, calldatas, description)++`] +* {xref-Governor-queue-address---uint256---bytes---bytes32-}[`++queue(targets, values, calldatas, descriptionHash)++`] +* {xref-Governor-_queueOperations-uint256-address---uint256---bytes---bytes32-}[`++_queueOperations(, , , , )++`] +* {xref-Governor-execute-address---uint256---bytes---bytes32-}[`++execute(targets, values, calldatas, descriptionHash)++`] +* {xref-Governor-_executeOperations-uint256-address---uint256---bytes---bytes32-}[`++_executeOperations(, targets, values, calldatas, )++`] +* {xref-Governor-cancel-address---uint256---bytes---bytes32-}[`++cancel(targets, values, calldatas, descriptionHash)++`] +* {xref-Governor-_cancel-address---uint256---bytes---bytes32-}[`++_cancel(targets, values, calldatas, descriptionHash)++`] +* {xref-Governor-getVotes-address-uint256-}[`++getVotes(account, timepoint)++`] +* {xref-Governor-getVotesWithParams-address-uint256-bytes-}[`++getVotesWithParams(account, timepoint, params)++`] +* {xref-Governor-castVote-uint256-uint8-}[`++castVote(proposalId, support)++`] +* {xref-Governor-castVoteWithReason-uint256-uint8-string-}[`++castVoteWithReason(proposalId, support, reason)++`] +* {xref-Governor-castVoteWithReasonAndParams-uint256-uint8-string-bytes-}[`++castVoteWithReasonAndParams(proposalId, support, reason, params)++`] +* {xref-Governor-castVoteBySig-uint256-uint8-address-bytes-}[`++castVoteBySig(proposalId, support, voter, signature)++`] +* {xref-Governor-castVoteWithReasonAndParamsBySig-uint256-uint8-address-string-bytes-bytes-}[`++castVoteWithReasonAndParamsBySig(proposalId, support, voter, reason, params, signature)++`] +* {xref-Governor-_castVote-uint256-address-uint8-string-}[`++_castVote(proposalId, account, support, reason)++`] +* {xref-Governor-_castVote-uint256-address-uint8-string-bytes-}[`++_castVote(proposalId, account, support, reason, params)++`] +* {xref-Governor-relay-address-uint256-bytes-}[`++relay(target, value, data)++`] +* {xref-Governor-_executor--}[`++_executor()++`] +* {xref-Governor-onERC721Received-address-address-uint256-bytes-}[`++onERC721Received(, , , )++`] +* {xref-Governor-onERC1155Received-address-address-uint256-uint256-bytes-}[`++onERC1155Received(, , , , )++`] +* {xref-Governor-onERC1155BatchReceived-address-address-uint256---uint256---bytes-}[`++onERC1155BatchReceived(, , , , )++`] +* {xref-Governor-_encodeStateBitmap-enum-IGovernor-ProposalState-}[`++_encodeStateBitmap(proposalState)++`] +* {xref-Governor-_isValidDescriptionForProposer-address-string-}[`++_isValidDescriptionForProposer(proposer, description)++`] +* {xref-Governor-clock--}[`++clock()++`] +* {xref-Governor-CLOCK_MODE--}[`++CLOCK_MODE()++`] +* {xref-Governor-votingDelay--}[`++votingDelay()++`] +* {xref-Governor-votingPeriod--}[`++votingPeriod()++`] +* {xref-Governor-quorum-uint256-}[`++quorum(timepoint)++`] + +[.contract-subindex-inherited] +.IERC1155Receiver + +[.contract-subindex-inherited] +.IERC721Receiver + +[.contract-subindex-inherited] +.IGovernor +* {xref-IGovernor-COUNTING_MODE--}[`++COUNTING_MODE()++`] +* {xref-IGovernor-hasVoted-uint256-address-}[`++hasVoted(proposalId, account)++`] + +[.contract-subindex-inherited] +.IERC6372 + +[.contract-subindex-inherited] +.Nonces +* {xref-Nonces-nonces-address-}[`++nonces(owner)++`] +* {xref-Nonces-_useNonce-address-}[`++_useNonce(owner)++`] +* {xref-Nonces-_useCheckedNonce-address-uint256-}[`++_useCheckedNonce(owner, nonce)++`] + +[.contract-subindex-inherited] +.EIP712 +* {xref-EIP712-_domainSeparatorV4--}[`++_domainSeparatorV4()++`] +* {xref-EIP712-_hashTypedDataV4-bytes32-}[`++_hashTypedDataV4(structHash)++`] +* {xref-EIP712-eip712Domain--}[`++eip712Domain()++`] +* {xref-EIP712-_EIP712Name--}[`++_EIP712Name()++`] +* {xref-EIP712-_EIP712Version--}[`++_EIP712Version()++`] + +[.contract-subindex-inherited] +.IERC5267 + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-index] +.Events +-- + +[.contract-subindex-inherited] +.Governor + +[.contract-subindex-inherited] +.IERC1155Receiver + +[.contract-subindex-inherited] +.IERC721Receiver + +[.contract-subindex-inherited] +.IGovernor +* {xref-IGovernor-ProposalCreated-uint256-address-address---uint256---string---bytes---uint256-uint256-string-}[`++ProposalCreated(proposalId, proposer, targets, values, signatures, calldatas, voteStart, voteEnd, description)++`] +* {xref-IGovernor-ProposalQueued-uint256-uint256-}[`++ProposalQueued(proposalId, etaSeconds)++`] +* {xref-IGovernor-ProposalExecuted-uint256-}[`++ProposalExecuted(proposalId)++`] +* {xref-IGovernor-ProposalCanceled-uint256-}[`++ProposalCanceled(proposalId)++`] +* {xref-IGovernor-VoteCast-address-uint256-uint8-uint256-string-}[`++VoteCast(voter, proposalId, support, weight, reason)++`] +* {xref-IGovernor-VoteCastWithParams-address-uint256-uint8-uint256-string-bytes-}[`++VoteCastWithParams(voter, proposalId, support, weight, reason, params)++`] + +[.contract-subindex-inherited] +.IERC6372 + +[.contract-subindex-inherited] +.Nonces + +[.contract-subindex-inherited] +.EIP712 + +[.contract-subindex-inherited] +.IERC5267 +* {xref-IERC5267-EIP712DomainChanged--}[`++EIP712DomainChanged()++`] + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-index] +.Errors +-- + +[.contract-subindex-inherited] +.Governor + +[.contract-subindex-inherited] +.IERC1155Receiver + +[.contract-subindex-inherited] +.IERC721Receiver + +[.contract-subindex-inherited] +.IGovernor +* {xref-IGovernor-GovernorInvalidProposalLength-uint256-uint256-uint256-}[`++GovernorInvalidProposalLength(targets, calldatas, values)++`] +* {xref-IGovernor-GovernorAlreadyCastVote-address-}[`++GovernorAlreadyCastVote(voter)++`] +* {xref-IGovernor-GovernorDisabledDeposit--}[`++GovernorDisabledDeposit()++`] +* {xref-IGovernor-GovernorOnlyProposer-address-}[`++GovernorOnlyProposer(account)++`] +* {xref-IGovernor-GovernorOnlyExecutor-address-}[`++GovernorOnlyExecutor(account)++`] +* {xref-IGovernor-GovernorNonexistentProposal-uint256-}[`++GovernorNonexistentProposal(proposalId)++`] +* {xref-IGovernor-GovernorUnexpectedProposalState-uint256-enum-IGovernor-ProposalState-bytes32-}[`++GovernorUnexpectedProposalState(proposalId, current, expectedStates)++`] +* {xref-IGovernor-GovernorInvalidVotingPeriod-uint256-}[`++GovernorInvalidVotingPeriod(votingPeriod)++`] +* {xref-IGovernor-GovernorInsufficientProposerVotes-address-uint256-uint256-}[`++GovernorInsufficientProposerVotes(proposer, votes, threshold)++`] +* {xref-IGovernor-GovernorRestrictedProposer-address-}[`++GovernorRestrictedProposer(proposer)++`] +* {xref-IGovernor-GovernorInvalidVoteType--}[`++GovernorInvalidVoteType()++`] +* {xref-IGovernor-GovernorQueueNotImplemented--}[`++GovernorQueueNotImplemented()++`] +* {xref-IGovernor-GovernorNotQueuedProposal-uint256-}[`++GovernorNotQueuedProposal(proposalId)++`] +* {xref-IGovernor-GovernorAlreadyQueuedProposal-uint256-}[`++GovernorAlreadyQueuedProposal(proposalId)++`] +* {xref-IGovernor-GovernorInvalidSignature-address-}[`++GovernorInvalidSignature(voter)++`] + +[.contract-subindex-inherited] +.IERC6372 + +[.contract-subindex-inherited] +.Nonces +* {xref-Nonces-InvalidAccountNonce-address-uint256-}[`++InvalidAccountNonce(account, currentNonce)++`] + +[.contract-subindex-inherited] +.EIP712 + +[.contract-subindex-inherited] +.IERC5267 + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-item] +[[GovernorStorage-_propose-address---uint256---bytes---string-address-]] +==== `[.contract-item-name]#++_propose++#++(address[] targets, uint256[] values, bytes[] calldatas, string description, address proposer) β†’ uint256++` [.item-kind]#internal# + +Hook into the proposing mechanism + +[.contract-item] +[[GovernorStorage-queue-uint256-]] +==== `[.contract-item-name]#++queue++#++(uint256 proposalId)++` [.item-kind]#public# + +Version of {IGovernorTimelock-queue} with only `proposalId` as an argument. + +[.contract-item] +[[GovernorStorage-execute-uint256-]] +==== `[.contract-item-name]#++execute++#++(uint256 proposalId)++` [.item-kind]#public# + +Version of {IGovernor-execute} with only `proposalId` as an argument. + +[.contract-item] +[[GovernorStorage-cancel-uint256-]] +==== `[.contract-item-name]#++cancel++#++(uint256 proposalId)++` [.item-kind]#public# + +ProposalId version of {IGovernor-cancel}. + +[.contract-item] +[[GovernorStorage-proposalCount--]] +==== `[.contract-item-name]#++proposalCount++#++() β†’ uint256++` [.item-kind]#public# + +Returns the number of stored proposals. + +[.contract-item] +[[GovernorStorage-proposalDetails-uint256-]] +==== `[.contract-item-name]#++proposalDetails++#++(uint256 proposalId) β†’ address[], uint256[], bytes[], bytes32++` [.item-kind]#public# + +Returns the details of a proposalId. Reverts if `proposalId` is not a known proposal. + +[.contract-item] +[[GovernorStorage-proposalDetailsAt-uint256-]] +==== `[.contract-item-name]#++proposalDetailsAt++#++(uint256 index) β†’ uint256, address[], uint256[], bytes[], bytes32++` [.item-kind]#public# + +Returns the details (including the proposalId) of a proposal given its sequential index. + +== Utils + +:ERC6372InconsistentClock: pass:normal[xref:#Votes-ERC6372InconsistentClock--[`++ERC6372InconsistentClock++`]] +:ERC5805FutureLookup: pass:normal[xref:#Votes-ERC5805FutureLookup-uint256-uint48-[`++ERC5805FutureLookup++`]] +:clock: pass:normal[xref:#Votes-clock--[`++clock++`]] +:CLOCK_MODE: pass:normal[xref:#Votes-CLOCK_MODE--[`++CLOCK_MODE++`]] +:getVotes: pass:normal[xref:#Votes-getVotes-address-[`++getVotes++`]] +:getPastVotes: pass:normal[xref:#Votes-getPastVotes-address-uint256-[`++getPastVotes++`]] +:getPastTotalSupply: pass:normal[xref:#Votes-getPastTotalSupply-uint256-[`++getPastTotalSupply++`]] +:_getTotalSupply: pass:normal[xref:#Votes-_getTotalSupply--[`++_getTotalSupply++`]] +:delegates: pass:normal[xref:#Votes-delegates-address-[`++delegates++`]] +:delegate: pass:normal[xref:#Votes-delegate-address-[`++delegate++`]] +:delegateBySig: pass:normal[xref:#Votes-delegateBySig-address-uint256-uint256-uint8-bytes32-bytes32-[`++delegateBySig++`]] +:_delegate: pass:normal[xref:#Votes-_delegate-address-address-[`++_delegate++`]] +:_transferVotingUnits: pass:normal[xref:#Votes-_transferVotingUnits-address-address-uint256-[`++_transferVotingUnits++`]] +:_numCheckpoints: pass:normal[xref:#Votes-_numCheckpoints-address-[`++_numCheckpoints++`]] +:_checkpoints: pass:normal[xref:#Votes-_checkpoints-address-uint32-[`++_checkpoints++`]] +:_getVotingUnits: pass:normal[xref:#Votes-_getVotingUnits-address-[`++_getVotingUnits++`]] + +[.contract] +[[Votes]] +=== `++Votes++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/governance/utils/Votes.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/governance/utils/Votes.sol"; +``` + +This is a base abstract contract that tracks voting units, which are a measure of voting power that can be +transferred, and provides a system of vote delegation, where an account can delegate its voting units to a sort of +"representative" that will pool delegated voting units from different accounts and can then use it to vote in +decisions. In fact, voting units _must_ be delegated in order to count as actual votes, and an account has to +delegate those votes to itself if it wishes to participate in decisions and does not have a trusted representative. + +This contract is often combined with a token contract such that voting units correspond to token units. For an +example, see {ERC721Votes}. + +The full history of delegate votes is tracked on-chain so that governance protocols can consider votes as distributed +at a particular block number to protect against flash loans and double voting. The opt-in delegate system makes the +cost of this history tracking optional. + +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}). + +[.contract-index] +.Functions +-- +* {xref-Votes-clock--}[`++clock()++`] +* {xref-Votes-CLOCK_MODE--}[`++CLOCK_MODE()++`] +* {xref-Votes-getVotes-address-}[`++getVotes(account)++`] +* {xref-Votes-getPastVotes-address-uint256-}[`++getPastVotes(account, timepoint)++`] +* {xref-Votes-getPastTotalSupply-uint256-}[`++getPastTotalSupply(timepoint)++`] +* {xref-Votes-_getTotalSupply--}[`++_getTotalSupply()++`] +* {xref-Votes-delegates-address-}[`++delegates(account)++`] +* {xref-Votes-delegate-address-}[`++delegate(delegatee)++`] +* {xref-Votes-delegateBySig-address-uint256-uint256-uint8-bytes32-bytes32-}[`++delegateBySig(delegatee, nonce, expiry, v, r, s)++`] +* {xref-Votes-_delegate-address-address-}[`++_delegate(account, delegatee)++`] +* {xref-Votes-_transferVotingUnits-address-address-uint256-}[`++_transferVotingUnits(from, to, amount)++`] +* {xref-Votes-_numCheckpoints-address-}[`++_numCheckpoints(account)++`] +* {xref-Votes-_checkpoints-address-uint32-}[`++_checkpoints(account, pos)++`] +* {xref-Votes-_getVotingUnits-address-}[`++_getVotingUnits()++`] + +[.contract-subindex-inherited] +.IERC5805 + +[.contract-subindex-inherited] +.IVotes + +[.contract-subindex-inherited] +.IERC6372 + +[.contract-subindex-inherited] +.Nonces +* {xref-Nonces-nonces-address-}[`++nonces(owner)++`] +* {xref-Nonces-_useNonce-address-}[`++_useNonce(owner)++`] +* {xref-Nonces-_useCheckedNonce-address-uint256-}[`++_useCheckedNonce(owner, nonce)++`] + +[.contract-subindex-inherited] +.EIP712 +* {xref-EIP712-_domainSeparatorV4--}[`++_domainSeparatorV4()++`] +* {xref-EIP712-_hashTypedDataV4-bytes32-}[`++_hashTypedDataV4(structHash)++`] +* {xref-EIP712-eip712Domain--}[`++eip712Domain()++`] +* {xref-EIP712-_EIP712Name--}[`++_EIP712Name()++`] +* {xref-EIP712-_EIP712Version--}[`++_EIP712Version()++`] + +[.contract-subindex-inherited] +.IERC5267 + +-- + +[.contract-index] +.Events +-- + +[.contract-subindex-inherited] +.IERC5805 + +[.contract-subindex-inherited] +.IVotes +* {xref-IVotes-DelegateChanged-address-address-address-}[`++DelegateChanged(delegator, fromDelegate, toDelegate)++`] +* {xref-IVotes-DelegateVotesChanged-address-uint256-uint256-}[`++DelegateVotesChanged(delegate, previousVotes, newVotes)++`] + +[.contract-subindex-inherited] +.IERC6372 + +[.contract-subindex-inherited] +.Nonces + +[.contract-subindex-inherited] +.EIP712 + +[.contract-subindex-inherited] +.IERC5267 +* {xref-IERC5267-EIP712DomainChanged--}[`++EIP712DomainChanged()++`] + +-- + +[.contract-index] +.Errors +-- +* {xref-Votes-ERC6372InconsistentClock--}[`++ERC6372InconsistentClock()++`] +* {xref-Votes-ERC5805FutureLookup-uint256-uint48-}[`++ERC5805FutureLookup(timepoint, clock)++`] + +[.contract-subindex-inherited] +.IERC5805 + +[.contract-subindex-inherited] +.IVotes +* {xref-IVotes-VotesExpiredSignature-uint256-}[`++VotesExpiredSignature(expiry)++`] + +[.contract-subindex-inherited] +.IERC6372 + +[.contract-subindex-inherited] +.Nonces +* {xref-Nonces-InvalidAccountNonce-address-uint256-}[`++InvalidAccountNonce(account, currentNonce)++`] + +[.contract-subindex-inherited] +.EIP712 + +[.contract-subindex-inherited] +.IERC5267 + +-- + +[.contract-item] +[[Votes-clock--]] +==== `[.contract-item-name]#++clock++#++() β†’ uint48++` [.item-kind]#public# + +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. + +[.contract-item] +[[Votes-CLOCK_MODE--]] +==== `[.contract-item-name]#++CLOCK_MODE++#++() β†’ string++` [.item-kind]#public# + +Machine-readable description of the clock as specified in EIP-6372. + +[.contract-item] +[[Votes-getVotes-address-]] +==== `[.contract-item-name]#++getVotes++#++(address account) β†’ uint256++` [.item-kind]#public# + +Returns the current amount of votes that `account` has. + +[.contract-item] +[[Votes-getPastVotes-address-uint256-]] +==== `[.contract-item-name]#++getPastVotes++#++(address account, uint256 timepoint) β†’ uint256++` [.item-kind]#public# + +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 at the end of the corresponding block. + +Requirements: + +- `timepoint` must be in the past. If operating using block numbers, the block must be already mined. + +[.contract-item] +[[Votes-getPastTotalSupply-uint256-]] +==== `[.contract-item-name]#++getPastTotalSupply++#++(uint256 timepoint) β†’ uint256++` [.item-kind]#public# + +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 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 +vote. + +Requirements: + +- `timepoint` must be in the past. If operating using block numbers, the block must be already mined. + +[.contract-item] +[[Votes-_getTotalSupply--]] +==== `[.contract-item-name]#++_getTotalSupply++#++() β†’ uint256++` [.item-kind]#internal# + +Returns the current total supply of votes. + +[.contract-item] +[[Votes-delegates-address-]] +==== `[.contract-item-name]#++delegates++#++(address account) β†’ address++` [.item-kind]#public# + +Returns the delegate that `account` has chosen. + +[.contract-item] +[[Votes-delegate-address-]] +==== `[.contract-item-name]#++delegate++#++(address delegatee)++` [.item-kind]#public# + +Delegates votes from the sender to `delegatee`. + +[.contract-item] +[[Votes-delegateBySig-address-uint256-uint256-uint8-bytes32-bytes32-]] +==== `[.contract-item-name]#++delegateBySig++#++(address delegatee, uint256 nonce, uint256 expiry, uint8 v, bytes32 r, bytes32 s)++` [.item-kind]#public# + +Delegates votes from signer to `delegatee`. + +[.contract-item] +[[Votes-_delegate-address-address-]] +==== `[.contract-item-name]#++_delegate++#++(address account, address delegatee)++` [.item-kind]#internal# + +Delegate all of `account`'s voting units to `delegatee`. + +Emits events {IVotes-DelegateChanged} and {IVotes-DelegateVotesChanged}. + +[.contract-item] +[[Votes-_transferVotingUnits-address-address-uint256-]] +==== `[.contract-item-name]#++_transferVotingUnits++#++(address from, address to, uint256 amount)++` [.item-kind]#internal# + +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. + +[.contract-item] +[[Votes-_numCheckpoints-address-]] +==== `[.contract-item-name]#++_numCheckpoints++#++(address account) β†’ uint32++` [.item-kind]#internal# + +Get number of checkpoints for `account`. + +[.contract-item] +[[Votes-_checkpoints-address-uint32-]] +==== `[.contract-item-name]#++_checkpoints++#++(address account, uint32 pos) β†’ struct Checkpoints.Checkpoint208++` [.item-kind]#internal# + +Get the `pos`-th checkpoint for `account`. + +[.contract-item] +[[Votes-_getVotingUnits-address-]] +==== `[.contract-item-name]#++_getVotingUnits++#++(address) β†’ uint256++` [.item-kind]#internal# + +Must return the voting units held by an account. + +[.contract-item] +[[Votes-ERC6372InconsistentClock--]] +==== `[.contract-item-name]#++ERC6372InconsistentClock++#++()++` [.item-kind]#error# + +The clock was incorrectly modified. + +[.contract-item] +[[Votes-ERC5805FutureLookup-uint256-uint48-]] +==== `[.contract-item-name]#++ERC5805FutureLookup++#++(uint256 timepoint, uint48 clock)++` [.item-kind]#error# + +Lookup to future votes is not available. + +== Timelock + +In a governance system, the {TimelockController} contract is in charge of introducing a delay between a proposal and its execution. It can be used with or without a {Governor}. + +:PROPOSER_ROLE: pass:normal[xref:#TimelockController-PROPOSER_ROLE-bytes32[`++PROPOSER_ROLE++`]] +:EXECUTOR_ROLE: pass:normal[xref:#TimelockController-EXECUTOR_ROLE-bytes32[`++EXECUTOR_ROLE++`]] +:CANCELLER_ROLE: pass:normal[xref:#TimelockController-CANCELLER_ROLE-bytes32[`++CANCELLER_ROLE++`]] +:_DONE_TIMESTAMP: pass:normal[xref:#TimelockController-_DONE_TIMESTAMP-uint256[`++_DONE_TIMESTAMP++`]] +:OperationState: pass:normal[xref:#TimelockController-OperationState[`++OperationState++`]] +:TimelockInvalidOperationLength: pass:normal[xref:#TimelockController-TimelockInvalidOperationLength-uint256-uint256-uint256-[`++TimelockInvalidOperationLength++`]] +:TimelockInsufficientDelay: pass:normal[xref:#TimelockController-TimelockInsufficientDelay-uint256-uint256-[`++TimelockInsufficientDelay++`]] +:TimelockUnexpectedOperationState: pass:normal[xref:#TimelockController-TimelockUnexpectedOperationState-bytes32-bytes32-[`++TimelockUnexpectedOperationState++`]] +:TimelockUnexecutedPredecessor: pass:normal[xref:#TimelockController-TimelockUnexecutedPredecessor-bytes32-[`++TimelockUnexecutedPredecessor++`]] +:TimelockUnauthorizedCaller: pass:normal[xref:#TimelockController-TimelockUnauthorizedCaller-address-[`++TimelockUnauthorizedCaller++`]] +:CallScheduled: pass:normal[xref:#TimelockController-CallScheduled-bytes32-uint256-address-uint256-bytes-bytes32-uint256-[`++CallScheduled++`]] +:CallExecuted: pass:normal[xref:#TimelockController-CallExecuted-bytes32-uint256-address-uint256-bytes-[`++CallExecuted++`]] +:CallSalt: pass:normal[xref:#TimelockController-CallSalt-bytes32-bytes32-[`++CallSalt++`]] +:Cancelled: pass:normal[xref:#TimelockController-Cancelled-bytes32-[`++Cancelled++`]] +:MinDelayChange: pass:normal[xref:#TimelockController-MinDelayChange-uint256-uint256-[`++MinDelayChange++`]] +:constructor: pass:normal[xref:#TimelockController-constructor-uint256-address---address---address-[`++constructor++`]] +:onlyRoleOrOpenRole: pass:normal[xref:#TimelockController-onlyRoleOrOpenRole-bytes32-[`++onlyRoleOrOpenRole++`]] +:receive: pass:normal[xref:#TimelockController-receive--[`++receive++`]] +:supportsInterface: pass:normal[xref:#TimelockController-supportsInterface-bytes4-[`++supportsInterface++`]] +:isOperation: pass:normal[xref:#TimelockController-isOperation-bytes32-[`++isOperation++`]] +:isOperationPending: pass:normal[xref:#TimelockController-isOperationPending-bytes32-[`++isOperationPending++`]] +:isOperationReady: pass:normal[xref:#TimelockController-isOperationReady-bytes32-[`++isOperationReady++`]] +:isOperationDone: pass:normal[xref:#TimelockController-isOperationDone-bytes32-[`++isOperationDone++`]] +:getTimestamp: pass:normal[xref:#TimelockController-getTimestamp-bytes32-[`++getTimestamp++`]] +:getOperationState: pass:normal[xref:#TimelockController-getOperationState-bytes32-[`++getOperationState++`]] +:getMinDelay: pass:normal[xref:#TimelockController-getMinDelay--[`++getMinDelay++`]] +:hashOperation: pass:normal[xref:#TimelockController-hashOperation-address-uint256-bytes-bytes32-bytes32-[`++hashOperation++`]] +:hashOperationBatch: pass:normal[xref:#TimelockController-hashOperationBatch-address---uint256---bytes---bytes32-bytes32-[`++hashOperationBatch++`]] +:schedule: pass:normal[xref:#TimelockController-schedule-address-uint256-bytes-bytes32-bytes32-uint256-[`++schedule++`]] +:scheduleBatch: pass:normal[xref:#TimelockController-scheduleBatch-address---uint256---bytes---bytes32-bytes32-uint256-[`++scheduleBatch++`]] +:cancel: pass:normal[xref:#TimelockController-cancel-bytes32-[`++cancel++`]] +:execute: pass:normal[xref:#TimelockController-execute-address-uint256-bytes-bytes32-bytes32-[`++execute++`]] +:executeBatch: pass:normal[xref:#TimelockController-executeBatch-address---uint256---bytes---bytes32-bytes32-[`++executeBatch++`]] +:_execute: pass:normal[xref:#TimelockController-_execute-address-uint256-bytes-[`++_execute++`]] +:updateDelay: pass:normal[xref:#TimelockController-updateDelay-uint256-[`++updateDelay++`]] +:_encodeStateBitmap: pass:normal[xref:#TimelockController-_encodeStateBitmap-enum-TimelockController-OperationState-[`++_encodeStateBitmap++`]] + +[.contract] +[[TimelockController]] +=== `++TimelockController++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/governance/TimelockController.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/governance/TimelockController.sol"; +``` + +Contract module which acts as a timelocked controller. When set as the +owner of an `Ownable` smart contract, it enforces a timelock on all +`onlyOwner` maintenance operations. This gives time for users of the +controlled contract to exit before a potentially dangerous maintenance +operation is applied. + +By default, this contract is self administered, meaning administration tasks +have to go through the timelock process. The proposer (resp executor) role +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. + +[.contract-index] +.Modifiers +-- +* {xref-TimelockController-onlyRoleOrOpenRole-bytes32-}[`++onlyRoleOrOpenRole(role)++`] +-- + +[.contract-index] +.Functions +-- +* {xref-TimelockController-constructor-uint256-address---address---address-}[`++constructor(minDelay, proposers, executors, admin)++`] +* {xref-TimelockController-receive--}[`++receive()++`] +* {xref-TimelockController-supportsInterface-bytes4-}[`++supportsInterface(interfaceId)++`] +* {xref-TimelockController-isOperation-bytes32-}[`++isOperation(id)++`] +* {xref-TimelockController-isOperationPending-bytes32-}[`++isOperationPending(id)++`] +* {xref-TimelockController-isOperationReady-bytes32-}[`++isOperationReady(id)++`] +* {xref-TimelockController-isOperationDone-bytes32-}[`++isOperationDone(id)++`] +* {xref-TimelockController-getTimestamp-bytes32-}[`++getTimestamp(id)++`] +* {xref-TimelockController-getOperationState-bytes32-}[`++getOperationState(id)++`] +* {xref-TimelockController-getMinDelay--}[`++getMinDelay()++`] +* {xref-TimelockController-hashOperation-address-uint256-bytes-bytes32-bytes32-}[`++hashOperation(target, value, data, predecessor, salt)++`] +* {xref-TimelockController-hashOperationBatch-address---uint256---bytes---bytes32-bytes32-}[`++hashOperationBatch(targets, values, payloads, predecessor, salt)++`] +* {xref-TimelockController-schedule-address-uint256-bytes-bytes32-bytes32-uint256-}[`++schedule(target, value, data, predecessor, salt, delay)++`] +* {xref-TimelockController-scheduleBatch-address---uint256---bytes---bytes32-bytes32-uint256-}[`++scheduleBatch(targets, values, payloads, predecessor, salt, delay)++`] +* {xref-TimelockController-cancel-bytes32-}[`++cancel(id)++`] +* {xref-TimelockController-execute-address-uint256-bytes-bytes32-bytes32-}[`++execute(target, value, payload, predecessor, salt)++`] +* {xref-TimelockController-executeBatch-address---uint256---bytes---bytes32-bytes32-}[`++executeBatch(targets, values, payloads, predecessor, salt)++`] +* {xref-TimelockController-_execute-address-uint256-bytes-}[`++_execute(target, value, data)++`] +* {xref-TimelockController-updateDelay-uint256-}[`++updateDelay(newDelay)++`] +* {xref-TimelockController-_encodeStateBitmap-enum-TimelockController-OperationState-}[`++_encodeStateBitmap(operationState)++`] + +[.contract-subindex-inherited] +.ERC1155Holder +* {xref-ERC1155Holder-onERC1155Received-address-address-uint256-uint256-bytes-}[`++onERC1155Received(, , , , )++`] +* {xref-ERC1155Holder-onERC1155BatchReceived-address-address-uint256---uint256---bytes-}[`++onERC1155BatchReceived(, , , , )++`] + +[.contract-subindex-inherited] +.IERC1155Receiver + +[.contract-subindex-inherited] +.ERC721Holder +* {xref-ERC721Holder-onERC721Received-address-address-uint256-bytes-}[`++onERC721Received(, , , )++`] + +[.contract-subindex-inherited] +.IERC721Receiver + +[.contract-subindex-inherited] +.AccessControl +* {xref-AccessControl-hasRole-bytes32-address-}[`++hasRole(role, account)++`] +* {xref-AccessControl-_checkRole-bytes32-}[`++_checkRole(role)++`] +* {xref-AccessControl-_checkRole-bytes32-address-}[`++_checkRole(role, account)++`] +* {xref-AccessControl-getRoleAdmin-bytes32-}[`++getRoleAdmin(role)++`] +* {xref-AccessControl-grantRole-bytes32-address-}[`++grantRole(role, account)++`] +* {xref-AccessControl-revokeRole-bytes32-address-}[`++revokeRole(role, account)++`] +* {xref-AccessControl-renounceRole-bytes32-address-}[`++renounceRole(role, callerConfirmation)++`] +* {xref-AccessControl-_setRoleAdmin-bytes32-bytes32-}[`++_setRoleAdmin(role, adminRole)++`] +* {xref-AccessControl-_grantRole-bytes32-address-}[`++_grantRole(role, account)++`] +* {xref-AccessControl-_revokeRole-bytes32-address-}[`++_revokeRole(role, account)++`] + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +[.contract-subindex-inherited] +.IAccessControl + +-- + +[.contract-index] +.Events +-- +* {xref-TimelockController-CallScheduled-bytes32-uint256-address-uint256-bytes-bytes32-uint256-}[`++CallScheduled(id, index, target, value, data, predecessor, delay)++`] +* {xref-TimelockController-CallExecuted-bytes32-uint256-address-uint256-bytes-}[`++CallExecuted(id, index, target, value, data)++`] +* {xref-TimelockController-CallSalt-bytes32-bytes32-}[`++CallSalt(id, salt)++`] +* {xref-TimelockController-Cancelled-bytes32-}[`++Cancelled(id)++`] +* {xref-TimelockController-MinDelayChange-uint256-uint256-}[`++MinDelayChange(oldDuration, newDuration)++`] + +[.contract-subindex-inherited] +.ERC1155Holder + +[.contract-subindex-inherited] +.IERC1155Receiver + +[.contract-subindex-inherited] +.ERC721Holder + +[.contract-subindex-inherited] +.IERC721Receiver + +[.contract-subindex-inherited] +.AccessControl + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +[.contract-subindex-inherited] +.IAccessControl +* {xref-IAccessControl-RoleAdminChanged-bytes32-bytes32-bytes32-}[`++RoleAdminChanged(role, previousAdminRole, newAdminRole)++`] +* {xref-IAccessControl-RoleGranted-bytes32-address-address-}[`++RoleGranted(role, account, sender)++`] +* {xref-IAccessControl-RoleRevoked-bytes32-address-address-}[`++RoleRevoked(role, account, sender)++`] + +-- + +[.contract-index] +.Errors +-- +* {xref-TimelockController-TimelockInvalidOperationLength-uint256-uint256-uint256-}[`++TimelockInvalidOperationLength(targets, payloads, values)++`] +* {xref-TimelockController-TimelockInsufficientDelay-uint256-uint256-}[`++TimelockInsufficientDelay(delay, minDelay)++`] +* {xref-TimelockController-TimelockUnexpectedOperationState-bytes32-bytes32-}[`++TimelockUnexpectedOperationState(operationId, expectedStates)++`] +* {xref-TimelockController-TimelockUnexecutedPredecessor-bytes32-}[`++TimelockUnexecutedPredecessor(predecessorId)++`] +* {xref-TimelockController-TimelockUnauthorizedCaller-address-}[`++TimelockUnauthorizedCaller(caller)++`] + +[.contract-subindex-inherited] +.ERC1155Holder + +[.contract-subindex-inherited] +.IERC1155Receiver + +[.contract-subindex-inherited] +.ERC721Holder + +[.contract-subindex-inherited] +.IERC721Receiver + +[.contract-subindex-inherited] +.AccessControl + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +[.contract-subindex-inherited] +.IAccessControl +* {xref-IAccessControl-AccessControlUnauthorizedAccount-address-bytes32-}[`++AccessControlUnauthorizedAccount(account, neededRole)++`] +* {xref-IAccessControl-AccessControlBadConfirmation--}[`++AccessControlBadConfirmation()++`] + +-- + +[.contract-item] +[[TimelockController-onlyRoleOrOpenRole-bytes32-]] +==== `[.contract-item-name]#++onlyRoleOrOpenRole++#++(bytes32 role)++` [.item-kind]#modifier# + +Modifier to make a function callable only by a certain role. In +addition to checking the sender's role, `address(0)` 's role is also +considered. Granting a role to `address(0)` is equivalent to enabling +this role for everyone. + +[.contract-item] +[[TimelockController-constructor-uint256-address---address---address-]] +==== `[.contract-item-name]#++constructor++#++(uint256 minDelay, address[] proposers, address[] executors, address admin)++` [.item-kind]#public# + +Initializes the contract with the following parameters: + +- `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 + +IMPORTANT: The optional admin can aid with initial configuration of roles after deployment +without being subject to delay, but this role should be subsequently renounced in favor of +administration through timelocked proposals. Previous versions of this contract would assign +this admin to the deployer automatically and should be renounced as well. + +[.contract-item] +[[TimelockController-receive--]] +==== `[.contract-item-name]#++receive++#++()++` [.item-kind]#external# + +Contract might receive/hold ETH as part of the maintenance process. + +[.contract-item] +[[TimelockController-supportsInterface-bytes4-]] +==== `[.contract-item-name]#++supportsInterface++#++(bytes4 interfaceId) β†’ bool++` [.item-kind]#public# + +See {IERC165-supportsInterface}. + +[.contract-item] +[[TimelockController-isOperation-bytes32-]] +==== `[.contract-item-name]#++isOperation++#++(bytes32 id) β†’ bool++` [.item-kind]#public# + +Returns whether an id corresponds to a registered operation. This +includes both Waiting, Ready, and Done operations. + +[.contract-item] +[[TimelockController-isOperationPending-bytes32-]] +==== `[.contract-item-name]#++isOperationPending++#++(bytes32 id) β†’ bool++` [.item-kind]#public# + +Returns whether an operation is pending or not. Note that a "pending" operation may also be "ready". + +[.contract-item] +[[TimelockController-isOperationReady-bytes32-]] +==== `[.contract-item-name]#++isOperationReady++#++(bytes32 id) β†’ bool++` [.item-kind]#public# + +Returns whether an operation is ready for execution. Note that a "ready" operation is also "pending". + +[.contract-item] +[[TimelockController-isOperationDone-bytes32-]] +==== `[.contract-item-name]#++isOperationDone++#++(bytes32 id) β†’ bool++` [.item-kind]#public# + +Returns whether an operation is done or not. + +[.contract-item] +[[TimelockController-getTimestamp-bytes32-]] +==== `[.contract-item-name]#++getTimestamp++#++(bytes32 id) β†’ uint256++` [.item-kind]#public# + +Returns the timestamp at which an operation becomes ready (0 for +unset operations, 1 for done operations). + +[.contract-item] +[[TimelockController-getOperationState-bytes32-]] +==== `[.contract-item-name]#++getOperationState++#++(bytes32 id) β†’ enum TimelockController.OperationState++` [.item-kind]#public# + +Returns operation state. + +[.contract-item] +[[TimelockController-getMinDelay--]] +==== `[.contract-item-name]#++getMinDelay++#++() β†’ uint256++` [.item-kind]#public# + +Returns the minimum delay in seconds for an operation to become valid. + +This value can be changed by executing an operation that calls `updateDelay`. + +[.contract-item] +[[TimelockController-hashOperation-address-uint256-bytes-bytes32-bytes32-]] +==== `[.contract-item-name]#++hashOperation++#++(address target, uint256 value, bytes data, bytes32 predecessor, bytes32 salt) β†’ bytes32++` [.item-kind]#public# + +Returns the identifier of an operation containing a single +transaction. + +[.contract-item] +[[TimelockController-hashOperationBatch-address---uint256---bytes---bytes32-bytes32-]] +==== `[.contract-item-name]#++hashOperationBatch++#++(address[] targets, uint256[] values, bytes[] payloads, bytes32 predecessor, bytes32 salt) β†’ bytes32++` [.item-kind]#public# + +Returns the identifier of an operation containing a batch of +transactions. + +[.contract-item] +[[TimelockController-schedule-address-uint256-bytes-bytes32-bytes32-uint256-]] +==== `[.contract-item-name]#++schedule++#++(address target, uint256 value, bytes data, bytes32 predecessor, bytes32 salt, uint256 delay)++` [.item-kind]#public# + +Schedule an operation containing a single transaction. + +Emits {CallSalt} if salt is nonzero, and {CallScheduled}. + +Requirements: + +- the caller must have the 'proposer' role. + +[.contract-item] +[[TimelockController-scheduleBatch-address---uint256---bytes---bytes32-bytes32-uint256-]] +==== `[.contract-item-name]#++scheduleBatch++#++(address[] targets, uint256[] values, bytes[] payloads, bytes32 predecessor, bytes32 salt, uint256 delay)++` [.item-kind]#public# + +Schedule an operation containing a batch of transactions. + +Emits {CallSalt} if salt is nonzero, and one {CallScheduled} event per transaction in the batch. + +Requirements: + +- the caller must have the 'proposer' role. + +[.contract-item] +[[TimelockController-cancel-bytes32-]] +==== `[.contract-item-name]#++cancel++#++(bytes32 id)++` [.item-kind]#public# + +Cancel an operation. + +Requirements: + +- the caller must have the 'canceller' role. + +[.contract-item] +[[TimelockController-execute-address-uint256-bytes-bytes32-bytes32-]] +==== `[.contract-item-name]#++execute++#++(address target, uint256 value, bytes payload, bytes32 predecessor, bytes32 salt)++` [.item-kind]#public# + +Execute an (ready) operation containing a single transaction. + +Emits a {CallExecuted} event. + +Requirements: + +- the caller must have the 'executor' role. + +[.contract-item] +[[TimelockController-executeBatch-address---uint256---bytes---bytes32-bytes32-]] +==== `[.contract-item-name]#++executeBatch++#++(address[] targets, uint256[] values, bytes[] payloads, bytes32 predecessor, bytes32 salt)++` [.item-kind]#public# + +Execute an (ready) operation containing a batch of transactions. + +Emits one {CallExecuted} event per transaction in the batch. + +Requirements: + +- the caller must have the 'executor' role. + +[.contract-item] +[[TimelockController-_execute-address-uint256-bytes-]] +==== `[.contract-item-name]#++_execute++#++(address target, uint256 value, bytes data)++` [.item-kind]#internal# + +Execute an operation's call. + +[.contract-item] +[[TimelockController-updateDelay-uint256-]] +==== `[.contract-item-name]#++updateDelay++#++(uint256 newDelay)++` [.item-kind]#external# + +Changes the minimum timelock duration for future operations. + +Emits a {MinDelayChange} event. + +Requirements: + +- the caller must be the timelock itself. This can only be achieved by scheduling and later executing +an operation where the timelock is the target and the data is the ABI-encoded call to this function. + +[.contract-item] +[[TimelockController-_encodeStateBitmap-enum-TimelockController-OperationState-]] +==== `[.contract-item-name]#++_encodeStateBitmap++#++(enum TimelockController.OperationState operationState) β†’ bytes32++` [.item-kind]#internal# + +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 + +[.contract-item] +[[TimelockController-CallScheduled-bytes32-uint256-address-uint256-bytes-bytes32-uint256-]] +==== `[.contract-item-name]#++CallScheduled++#++(bytes32 indexed id, uint256 indexed index, address target, uint256 value, bytes data, bytes32 predecessor, uint256 delay)++` [.item-kind]#event# + +Emitted when a call is scheduled as part of operation `id`. + +[.contract-item] +[[TimelockController-CallExecuted-bytes32-uint256-address-uint256-bytes-]] +==== `[.contract-item-name]#++CallExecuted++#++(bytes32 indexed id, uint256 indexed index, address target, uint256 value, bytes data)++` [.item-kind]#event# + +Emitted when a call is performed as part of operation `id`. + +[.contract-item] +[[TimelockController-CallSalt-bytes32-bytes32-]] +==== `[.contract-item-name]#++CallSalt++#++(bytes32 indexed id, bytes32 salt)++` [.item-kind]#event# + +Emitted when new proposal is scheduled with non-zero salt. + +[.contract-item] +[[TimelockController-Cancelled-bytes32-]] +==== `[.contract-item-name]#++Cancelled++#++(bytes32 indexed id)++` [.item-kind]#event# + +Emitted when operation `id` is cancelled. + +[.contract-item] +[[TimelockController-MinDelayChange-uint256-uint256-]] +==== `[.contract-item-name]#++MinDelayChange++#++(uint256 oldDuration, uint256 newDuration)++` [.item-kind]#event# + +Emitted when the minimum delay for future operations is modified. + +[.contract-item] +[[TimelockController-TimelockInvalidOperationLength-uint256-uint256-uint256-]] +==== `[.contract-item-name]#++TimelockInvalidOperationLength++#++(uint256 targets, uint256 payloads, uint256 values)++` [.item-kind]#error# + +Mismatch between the parameters length for an operation call. + +[.contract-item] +[[TimelockController-TimelockInsufficientDelay-uint256-uint256-]] +==== `[.contract-item-name]#++TimelockInsufficientDelay++#++(uint256 delay, uint256 minDelay)++` [.item-kind]#error# + +The schedule operation doesn't meet the minimum delay. + +[.contract-item] +[[TimelockController-TimelockUnexpectedOperationState-bytes32-bytes32-]] +==== `[.contract-item-name]#++TimelockUnexpectedOperationState++#++(bytes32 operationId, bytes32 expectedStates)++` [.item-kind]#error# + +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}. + +[.contract-item] +[[TimelockController-TimelockUnexecutedPredecessor-bytes32-]] +==== `[.contract-item-name]#++TimelockUnexecutedPredecessor++#++(bytes32 predecessorId)++` [.item-kind]#error# + +The predecessor to an operation not yet done. + +[.contract-item] +[[TimelockController-TimelockUnauthorizedCaller-address-]] +==== `[.contract-item-name]#++TimelockUnauthorizedCaller++#++(address caller)++` [.item-kind]#error# + +The caller account is not authorized. + +[[timelock-terminology]] +==== Terminology + +* *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. +** *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*: +** *Admin:* An address (smart contract or EOA) that is in charge of granting the roles of Proposer and Executor. +** *Proposer:* An address (smart contract or EOA) that is in charge of scheduling (and cancelling) operations. +** *Executor:* An address (smart contract or EOA) that is in charge of executing operations once the timelock has expired. This role can be given to the zero address to allow anyone to execute operations. + +[[timelock-operation]] +==== Operation structure + +Operation executed by the xref:api:governance.adoc#TimelockController[`TimelockController`] can contain one or multiple subsequent calls. Depending on whether you need to multiple calls to be executed atomically, you can either use simple or batched operations. + +Both operations contain: + +* *Target*, the address of the smart contract that the timelock should operate on. +* *Value*, in wei, that should be sent with the transaction. Most of the time this will be 0. Ether can be deposited before-end or passed along when executing the transaction. +* *Data*, containing the encoded function selector and parameters of the call. This can be produced using a number of tools. For example, a maintenance operation granting role `ROLE` to `ACCOUNT` can be encoded using web3js as follows: + +```javascript +const data = timelock.contract.methods.grantRole(ROLE, ACCOUNT).encodeABI() +``` + +* *Predecessor*, that specifies a dependency between operations. This dependency is optional. Use `bytes32(0)` if the operation does not have any dependency. +* *Salt*, used to disambiguate two otherwise identical operations. This can be any random value. + +In the case of batched operations, `target`, `value` and `data` are specified as arrays, which must be of the same length. + +[[timelock-operation-lifecycle]] +==== Operation lifecycle + +Timelocked operations are identified by a unique id (their hash) and follow a specific lifecycle: + +`Unset` -> `Pending` -> `Pending` + `Ready` -> `Done` + +* By calling xref:api:governance.adoc#TimelockController-schedule-address-uint256-bytes-bytes32-bytes32-uint256-[`schedule`] (or xref:api:governance.adoc#TimelockController-scheduleBatch-address---uint256---bytes---bytes32-bytes32-uint256-[`scheduleBatch`]), a proposer moves the operation from the `Unset` to the `Pending` state. This starts a timer that must be longer than the minimum delay. The timer expires at a timestamp accessible through the xref:api:governance.adoc#TimelockController-getTimestamp-bytes32-[`getTimestamp`] method. +* Once the timer expires, the operation automatically gets the `Ready` state. At this point, it can be executed. +* By calling xref:api:governance.adoc#TimelockController-TimelockController-execute-address-uint256-bytes-bytes32-bytes32-[`execute`] (or xref:api:governance.adoc#TimelockController-executeBatch-address---uint256---bytes---bytes32-bytes32-[`executeBatch`]), an executor triggers the operation's underlying transactions and moves it to the `Done` state. If the operation has a predecessor, it has to be in the `Done` state for this transition to succeed. +* xref:api:governance.adoc#TimelockController-TimelockController-cancel-bytes32-[`cancel`] allows proposers to cancel any `Pending` operation. This resets the operation to the `Unset` state. It is thus possible for a proposer to re-schedule an operation that has been cancelled. In this case, the timer restarts when the operation is re-scheduled. + +Operations status can be queried using the functions: + +* xref:api:governance.adoc#TimelockController-isOperationPending-bytes32-[`isOperationPending(bytes32)`] +* xref:api:governance.adoc#TimelockController-isOperationReady-bytes32-[`isOperationReady(bytes32)`] +* xref:api:governance.adoc#TimelockController-isOperationDone-bytes32-[`isOperationDone(bytes32)`] + +[[timelock-roles]] +==== Roles + +[[timelock-admin]] +===== Admin + +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. + +[[timelock-proposer]] +===== Proposer + +The proposers are in charge of scheduling (and cancelling) operations. This is a critical role, that should be given to governing entities. This could be an EOA, a multisig, or a DAO. + +WARNING: *Proposer fight:* Having multiple proposers, while providing redundancy in case one becomes unavailable, can be dangerous. As proposer have their say on all operations, they could cancel operations they disagree with, including operations to remove them for the proposers. + +This role is identified by the *PROPOSER_ROLE* value: `0xb09aa5aeb3702cfd50b6b62bc4532604938f21248a27a1d5ca736082b6819cc1` + +[[timelock-executor]] +===== Executor + +The executors are in charge of executing the operations scheduled by the proposers once the timelock expires. Logic dictates that multisig or DAO that are proposers should also be executors in order to guarantee operations that have been scheduled will eventually be executed. However, having additional executors can reduce the cost (the executing transaction does not require validation by the multisig or DAO that proposed it), while ensuring whoever is in charge of execution cannot trigger actions that have not been scheduled by the proposers. Alternatively, it is possible to allow _any_ address to execute a proposal once the timelock has expired by granting the executor role to the zero address. + +This role is identified by the *EXECUTOR_ROLE* value: `0xd8aa0f3194971a2a116679f7c2090f6939c8d4e01a2a8d7e41d55e5351469e63` + +WARNING: A live contract without at least one proposer and one executor is locked. Make sure these roles are filled by reliable entities before the deployer renounces its administrative rights in favour of the timelock contract itself. See the {AccessControl} documentation to learn more about role management. + diff --git a/docs/modules/api/pages/interfaces.adoc b/docs/modules/api/pages/interfaces.adoc new file mode 100644 index 000000000..870109f76 --- /dev/null +++ b/docs/modules/api/pages/interfaces.adoc @@ -0,0 +1,1410 @@ +:github-icon: pass:[] +:IERC20: pass:normal[xref:token/ERC20.adoc#IERC20[`IERC20`]] +:IERC20Errors: pass:normal[xref:interfaces.adoc#IERC20Errors[`IERC20Errors`]] +:IERC20Metadata: pass:normal[xref:token/ERC20.adoc#IERC20Metadata[`IERC20Metadata`]] +:IERC165: pass:normal[xref:utils.adoc#IERC165[`IERC165`]] +:IERC721: pass:normal[xref:token/ERC721.adoc#IERC721[`IERC721`]] +:IERC721Receiver: pass:normal[xref:token/ERC721.adoc#IERC721Receiver[`IERC721Receiver`]] +:IERC721Enumerable: pass:normal[xref:token/ERC721.adoc#IERC721Enumerable[`IERC721Enumerable`]] +:IERC721Metadata: pass:normal[xref:token/ERC721.adoc#IERC721Metadata[`IERC721Metadata`]] +:IERC721Errors: pass:normal[xref:interfaces.adoc#IERC721Errors[`IERC721Errors`]] +:IERC777: pass:normal[xref:interfaces.adoc#IERC777[`IERC777`]] +:IERC777Recipient: pass:normal[xref:interfaces.adoc#IERC777Recipient[`IERC777Recipient`]] +:IERC777Sender: pass:normal[xref:interfaces.adoc#IERC777Sender[`IERC777Sender`]] +:IERC1155: pass:normal[xref:token/ERC1155.adoc#IERC1155[`IERC1155`]] +:IERC1155Receiver: pass:normal[xref:token/ERC1155.adoc#IERC1155Receiver[`IERC1155Receiver`]] +:IERC1155MetadataURI: pass:normal[xref:token/ERC1155.adoc#IERC1155MetadataURI[`IERC1155MetadataURI`]] +:IERC1155Errors: pass:normal[xref:interfaces.adoc#IERC1155Errors[`IERC1155Errors`]] +:IERC1271: pass:normal[xref:interfaces.adoc#IERC1271[`IERC1271`]] +:IERC1363: pass:normal[xref:interfaces.adoc#IERC1363[`IERC1363`]] +:IERC1363Receiver: pass:normal[xref:interfaces.adoc#IERC1363Receiver[`IERC1363Receiver`]] +:IERC1363Spender: pass:normal[xref:interfaces.adoc#IERC1363Spender[`IERC1363Spender`]] +:IERC1820Implementer: pass:normal[xref:interfaces.adoc#IERC1820Implementer[`IERC1820Implementer`]] +:IERC1820Registry: pass:normal[xref:interfaces.adoc#IERC1820Registry[`IERC1820Registry`]] +:IERC1822Proxiable: pass:normal[xref:interfaces.adoc#IERC1822Proxiable[`IERC1822Proxiable`]] +:IERC2612: pass:normal[xref:interfaces.adoc#IERC2612[`IERC2612`]] +:IERC2981: pass:normal[xref:interfaces.adoc#IERC2981[`IERC2981`]] +:IERC3156FlashLender: pass:normal[xref:interfaces.adoc#IERC3156FlashLender[`IERC3156FlashLender`]] +:IERC3156FlashBorrower: pass:normal[xref:interfaces.adoc#IERC3156FlashBorrower[`IERC3156FlashBorrower`]] +:IERC4626: pass:normal[xref:interfaces.adoc#IERC4626[`IERC4626`]] +:IERC4906: pass:normal[xref:interfaces.adoc#IERC4906[`IERC4906`]] +:IERC5267: pass:normal[xref:interfaces.adoc#IERC5267[`IERC5267`]] +:IERC5313: pass:normal[xref:interfaces.adoc#IERC5313[`IERC5313`]] +:IERC5805: pass:normal[xref:interfaces.adoc#IERC5805[`IERC5805`]] +:IERC6372: pass:normal[xref:interfaces.adoc#IERC6372[`IERC6372`]] +:xref-IERC20Errors-ERC20InsufficientBalance-address-uint256-uint256-: xref:interfaces.adoc#IERC20Errors-ERC20InsufficientBalance-address-uint256-uint256- +:xref-IERC20Errors-ERC20InvalidSender-address-: xref:interfaces.adoc#IERC20Errors-ERC20InvalidSender-address- +:xref-IERC20Errors-ERC20InvalidReceiver-address-: xref:interfaces.adoc#IERC20Errors-ERC20InvalidReceiver-address- +:xref-IERC20Errors-ERC20InsufficientAllowance-address-uint256-uint256-: xref:interfaces.adoc#IERC20Errors-ERC20InsufficientAllowance-address-uint256-uint256- +:xref-IERC20Errors-ERC20InvalidApprover-address-: xref:interfaces.adoc#IERC20Errors-ERC20InvalidApprover-address- +:xref-IERC20Errors-ERC20InvalidSpender-address-: xref:interfaces.adoc#IERC20Errors-ERC20InvalidSpender-address- +:xref-IERC721Errors-ERC721InvalidOwner-address-: xref:interfaces.adoc#IERC721Errors-ERC721InvalidOwner-address- +:xref-IERC721Errors-ERC721NonexistentToken-uint256-: xref:interfaces.adoc#IERC721Errors-ERC721NonexistentToken-uint256- +:xref-IERC721Errors-ERC721IncorrectOwner-address-uint256-address-: xref:interfaces.adoc#IERC721Errors-ERC721IncorrectOwner-address-uint256-address- +:xref-IERC721Errors-ERC721InvalidSender-address-: xref:interfaces.adoc#IERC721Errors-ERC721InvalidSender-address- +:xref-IERC721Errors-ERC721InvalidReceiver-address-: xref:interfaces.adoc#IERC721Errors-ERC721InvalidReceiver-address- +:xref-IERC721Errors-ERC721InsufficientApproval-address-uint256-: xref:interfaces.adoc#IERC721Errors-ERC721InsufficientApproval-address-uint256- +:xref-IERC721Errors-ERC721InvalidApprover-address-: xref:interfaces.adoc#IERC721Errors-ERC721InvalidApprover-address- +:xref-IERC721Errors-ERC721InvalidOperator-address-: xref:interfaces.adoc#IERC721Errors-ERC721InvalidOperator-address- +:xref-IERC1155Errors-ERC1155InsufficientBalance-address-uint256-uint256-uint256-: xref:interfaces.adoc#IERC1155Errors-ERC1155InsufficientBalance-address-uint256-uint256-uint256- +:xref-IERC1155Errors-ERC1155InvalidSender-address-: xref:interfaces.adoc#IERC1155Errors-ERC1155InvalidSender-address- +:xref-IERC1155Errors-ERC1155InvalidReceiver-address-: xref:interfaces.adoc#IERC1155Errors-ERC1155InvalidReceiver-address- +:xref-IERC1155Errors-ERC1155MissingApprovalForAll-address-address-: xref:interfaces.adoc#IERC1155Errors-ERC1155MissingApprovalForAll-address-address- +:xref-IERC1155Errors-ERC1155InvalidApprover-address-: xref:interfaces.adoc#IERC1155Errors-ERC1155InvalidApprover-address- +:xref-IERC1155Errors-ERC1155InvalidOperator-address-: xref:interfaces.adoc#IERC1155Errors-ERC1155InvalidOperator-address- +:xref-IERC1155Errors-ERC1155InvalidArrayLength-uint256-uint256-: xref:interfaces.adoc#IERC1155Errors-ERC1155InvalidArrayLength-uint256-uint256- +:xref-IERC1271-isValidSignature-bytes32-bytes-: xref:interfaces.adoc#IERC1271-isValidSignature-bytes32-bytes- +:xref-IERC1363-transferAndCall-address-uint256-: xref:interfaces.adoc#IERC1363-transferAndCall-address-uint256- +:xref-IERC1363-transferAndCall-address-uint256-bytes-: xref:interfaces.adoc#IERC1363-transferAndCall-address-uint256-bytes- +:xref-IERC1363-transferFromAndCall-address-address-uint256-: xref:interfaces.adoc#IERC1363-transferFromAndCall-address-address-uint256- +:xref-IERC1363-transferFromAndCall-address-address-uint256-bytes-: xref:interfaces.adoc#IERC1363-transferFromAndCall-address-address-uint256-bytes- +:xref-IERC1363-approveAndCall-address-uint256-: xref:interfaces.adoc#IERC1363-approveAndCall-address-uint256- +:xref-IERC1363-approveAndCall-address-uint256-bytes-: xref:interfaces.adoc#IERC1363-approveAndCall-address-uint256-bytes- +:xref-IERC20-totalSupply--: xref:token/ERC20.adoc#IERC20-totalSupply-- +:xref-IERC20-balanceOf-address-: xref:token/ERC20.adoc#IERC20-balanceOf-address- +:xref-IERC20-transfer-address-uint256-: xref:token/ERC20.adoc#IERC20-transfer-address-uint256- +:xref-IERC20-allowance-address-address-: xref:token/ERC20.adoc#IERC20-allowance-address-address- +:xref-IERC20-approve-address-uint256-: xref:token/ERC20.adoc#IERC20-approve-address-uint256- +:xref-IERC20-transferFrom-address-address-uint256-: xref:token/ERC20.adoc#IERC20-transferFrom-address-address-uint256- +:xref-IERC165-supportsInterface-bytes4-: xref:utils.adoc#IERC165-supportsInterface-bytes4- +:xref-IERC20-Transfer-address-address-uint256-: xref:token/ERC20.adoc#IERC20-Transfer-address-address-uint256- +:xref-IERC20-Approval-address-address-uint256-: xref:token/ERC20.adoc#IERC20-Approval-address-address-uint256- +:IERC1363-transferAndCall: pass:normal[xref:interfaces.adoc#IERC1363-transferAndCall-address-uint256-bytes-[`IERC1363.transferAndCall`]] +:IERC1363-transferFromAndCall: pass:normal[xref:interfaces.adoc#IERC1363-transferFromAndCall-address-address-uint256-bytes-[`IERC1363.transferFromAndCall`]] +:xref-IERC1363Receiver-onTransferReceived-address-address-uint256-bytes-: xref:interfaces.adoc#IERC1363Receiver-onTransferReceived-address-address-uint256-bytes- +:IERC1363-approveAndCall: pass:normal[xref:interfaces.adoc#IERC1363-approveAndCall-address-uint256-bytes-[`IERC1363.approveAndCall`]] +:xref-IERC1363Spender-onApprovalReceived-address-uint256-bytes-: xref:interfaces.adoc#IERC1363Spender-onApprovalReceived-address-uint256-bytes- +:IERC1820Registry: pass:normal[xref:interfaces.adoc#IERC1820Registry[`IERC1820Registry`]] +:xref-IERC1820Implementer-canImplementInterfaceForAddress-bytes32-address-: xref:interfaces.adoc#IERC1820Implementer-canImplementInterfaceForAddress-bytes32-address- +:IERC1820Registry-setInterfaceImplementer: pass:normal[xref:interfaces.adoc#IERC1820Registry-setInterfaceImplementer-address-bytes32-address-[`IERC1820Registry.setInterfaceImplementer`]] +:IERC165: pass:normal[xref:utils.adoc#IERC165[`IERC165`]] +:xref-IERC1820Registry-setManager-address-address-: xref:interfaces.adoc#IERC1820Registry-setManager-address-address- +:xref-IERC1820Registry-getManager-address-: xref:interfaces.adoc#IERC1820Registry-getManager-address- +:xref-IERC1820Registry-setInterfaceImplementer-address-bytes32-address-: xref:interfaces.adoc#IERC1820Registry-setInterfaceImplementer-address-bytes32-address- +:xref-IERC1820Registry-getInterfaceImplementer-address-bytes32-: xref:interfaces.adoc#IERC1820Registry-getInterfaceImplementer-address-bytes32- +:xref-IERC1820Registry-interfaceHash-string-: xref:interfaces.adoc#IERC1820Registry-interfaceHash-string- +:xref-IERC1820Registry-updateERC165Cache-address-bytes4-: xref:interfaces.adoc#IERC1820Registry-updateERC165Cache-address-bytes4- +:xref-IERC1820Registry-implementsERC165Interface-address-bytes4-: xref:interfaces.adoc#IERC1820Registry-implementsERC165Interface-address-bytes4- +:xref-IERC1820Registry-implementsERC165InterfaceNoCache-address-bytes4-: xref:interfaces.adoc#IERC1820Registry-implementsERC165InterfaceNoCache-address-bytes4- +:xref-IERC1820Registry-InterfaceImplementerSet-address-bytes32-address-: xref:interfaces.adoc#IERC1820Registry-InterfaceImplementerSet-address-bytes32-address- +:xref-IERC1820Registry-ManagerChanged-address-address-: xref:interfaces.adoc#IERC1820Registry-ManagerChanged-address-address- +:IERC165: pass:normal[xref:utils.adoc#IERC165[`IERC165`]] +:IERC1820Implementer: pass:normal[xref:interfaces.adoc#IERC1820Implementer[`IERC1820Implementer`]] +:IERC1820Implementer-canImplementInterfaceForAddress: pass:normal[xref:interfaces.adoc#IERC1820Implementer-canImplementInterfaceForAddress-bytes32-address-[`IERC1820Implementer.canImplementInterfaceForAddress`]] +:IERC165: pass:normal[xref:utils.adoc#IERC165[`IERC165`]] +:xref-IERC1822Proxiable-proxiableUUID--: xref:interfaces.adoc#IERC1822Proxiable-proxiableUUID-- +:xref-IERC20Permit-permit-address-address-uint256-uint256-uint8-bytes32-bytes32-: xref:token/ERC20.adoc#IERC20Permit-permit-address-address-uint256-uint256-uint8-bytes32-bytes32- +:xref-IERC20Permit-nonces-address-: xref:token/ERC20.adoc#IERC20Permit-nonces-address- +:xref-IERC20Permit-DOMAIN_SEPARATOR--: xref:token/ERC20.adoc#IERC20Permit-DOMAIN_SEPARATOR-- +:xref-IERC2981-royaltyInfo-uint256-uint256-: xref:interfaces.adoc#IERC2981-royaltyInfo-uint256-uint256- +:xref-IERC165-supportsInterface-bytes4-: xref:utils.adoc#IERC165-supportsInterface-bytes4- +:xref-IERC3156FlashLender-maxFlashLoan-address-: xref:interfaces.adoc#IERC3156FlashLender-maxFlashLoan-address- +:xref-IERC3156FlashLender-flashFee-address-uint256-: xref:interfaces.adoc#IERC3156FlashLender-flashFee-address-uint256- +:xref-IERC3156FlashLender-flashLoan-contract-IERC3156FlashBorrower-address-uint256-bytes-: xref:interfaces.adoc#IERC3156FlashLender-flashLoan-contract-IERC3156FlashBorrower-address-uint256-bytes- +:xref-IERC3156FlashBorrower-onFlashLoan-address-address-uint256-uint256-bytes-: xref:interfaces.adoc#IERC3156FlashBorrower-onFlashLoan-address-address-uint256-uint256-bytes- +:xref-IERC4626-asset--: xref:interfaces.adoc#IERC4626-asset-- +:xref-IERC4626-totalAssets--: xref:interfaces.adoc#IERC4626-totalAssets-- +:xref-IERC4626-convertToShares-uint256-: xref:interfaces.adoc#IERC4626-convertToShares-uint256- +:xref-IERC4626-convertToAssets-uint256-: xref:interfaces.adoc#IERC4626-convertToAssets-uint256- +:xref-IERC4626-maxDeposit-address-: xref:interfaces.adoc#IERC4626-maxDeposit-address- +:xref-IERC4626-previewDeposit-uint256-: xref:interfaces.adoc#IERC4626-previewDeposit-uint256- +:xref-IERC4626-deposit-uint256-address-: xref:interfaces.adoc#IERC4626-deposit-uint256-address- +:xref-IERC4626-maxMint-address-: xref:interfaces.adoc#IERC4626-maxMint-address- +:xref-IERC4626-previewMint-uint256-: xref:interfaces.adoc#IERC4626-previewMint-uint256- +:xref-IERC4626-mint-uint256-address-: xref:interfaces.adoc#IERC4626-mint-uint256-address- +:xref-IERC4626-maxWithdraw-address-: xref:interfaces.adoc#IERC4626-maxWithdraw-address- +:xref-IERC4626-previewWithdraw-uint256-: xref:interfaces.adoc#IERC4626-previewWithdraw-uint256- +:xref-IERC4626-withdraw-uint256-address-address-: xref:interfaces.adoc#IERC4626-withdraw-uint256-address-address- +:xref-IERC4626-maxRedeem-address-: xref:interfaces.adoc#IERC4626-maxRedeem-address- +:xref-IERC4626-previewRedeem-uint256-: xref:interfaces.adoc#IERC4626-previewRedeem-uint256- +:xref-IERC4626-redeem-uint256-address-address-: xref:interfaces.adoc#IERC4626-redeem-uint256-address-address- +:xref-IERC20Metadata-name--: xref:token/ERC20.adoc#IERC20Metadata-name-- +:xref-IERC20Metadata-symbol--: xref:token/ERC20.adoc#IERC20Metadata-symbol-- +:xref-IERC20Metadata-decimals--: xref:token/ERC20.adoc#IERC20Metadata-decimals-- +:xref-IERC20-totalSupply--: xref:token/ERC20.adoc#IERC20-totalSupply-- +:xref-IERC20-balanceOf-address-: xref:token/ERC20.adoc#IERC20-balanceOf-address- +:xref-IERC20-transfer-address-uint256-: xref:token/ERC20.adoc#IERC20-transfer-address-uint256- +:xref-IERC20-allowance-address-address-: xref:token/ERC20.adoc#IERC20-allowance-address-address- +:xref-IERC20-approve-address-uint256-: xref:token/ERC20.adoc#IERC20-approve-address-uint256- +:xref-IERC20-transferFrom-address-address-uint256-: xref:token/ERC20.adoc#IERC20-transferFrom-address-address-uint256- +:xref-IERC4626-Deposit-address-address-uint256-uint256-: xref:interfaces.adoc#IERC4626-Deposit-address-address-uint256-uint256- +:xref-IERC4626-Withdraw-address-address-address-uint256-uint256-: xref:interfaces.adoc#IERC4626-Withdraw-address-address-address-uint256-uint256- +:xref-IERC20-Transfer-address-address-uint256-: xref:token/ERC20.adoc#IERC20-Transfer-address-address-uint256- +:xref-IERC20-Approval-address-address-uint256-: xref:token/ERC20.adoc#IERC20-Approval-address-address-uint256- +:xref-IERC5313-owner--: xref:interfaces.adoc#IERC5313-owner-- +:xref-IERC5267-eip712Domain--: xref:interfaces.adoc#IERC5267-eip712Domain-- +:xref-IERC5267-EIP712DomainChanged--: xref:interfaces.adoc#IERC5267-EIP712DomainChanged-- +:xref-IVotes-getVotes-address-: xref:governance.adoc#IVotes-getVotes-address- +:xref-IVotes-getPastVotes-address-uint256-: xref:governance.adoc#IVotes-getPastVotes-address-uint256- +:xref-IVotes-getPastTotalSupply-uint256-: xref:governance.adoc#IVotes-getPastTotalSupply-uint256- +:xref-IVotes-delegates-address-: xref:governance.adoc#IVotes-delegates-address- +:xref-IVotes-delegate-address-: xref:governance.adoc#IVotes-delegate-address- +:xref-IVotes-delegateBySig-address-uint256-uint256-uint8-bytes32-bytes32-: xref:governance.adoc#IVotes-delegateBySig-address-uint256-uint256-uint8-bytes32-bytes32- +:xref-IERC6372-clock--: xref:interfaces.adoc#IERC6372-clock-- +:xref-IERC6372-CLOCK_MODE--: xref:interfaces.adoc#IERC6372-CLOCK_MODE-- +:xref-IVotes-DelegateChanged-address-address-address-: xref:governance.adoc#IVotes-DelegateChanged-address-address-address- +:xref-IVotes-DelegateVotesChanged-address-uint256-uint256-: xref:governance.adoc#IVotes-DelegateVotesChanged-address-uint256-uint256- +:xref-IVotes-VotesExpiredSignature-uint256-: xref:governance.adoc#IVotes-VotesExpiredSignature-uint256- +:xref-IERC6372-clock--: xref:interfaces.adoc#IERC6372-clock-- +:xref-IERC6372-CLOCK_MODE--: xref:interfaces.adoc#IERC6372-CLOCK_MODE-- += Interfaces + +[.readme-notice] +NOTE: This document is better viewed at https://docs.openzeppelin.com/contracts/api/interfaces + +== List of standardized interfaces +These interfaces are available as `.sol` files, and also as compiler `.json` ABI files (through the npm package). These +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} +- {IERC1363Spender} +- {IERC1820Implementer} +- {IERC1820Registry} +- {IERC1822Proxiable} +- {IERC2612} +- {IERC2981} +- {IERC3156FlashLender} +- {IERC3156FlashBorrower} +- {IERC4626} +- {IERC4906} +- {IERC5267} +- {IERC5313} +- {IERC5805} +- {IERC6372} + +== Detailed ABI + +:ERC20InsufficientBalance: pass:normal[xref:#IERC20Errors-ERC20InsufficientBalance-address-uint256-uint256-[`++ERC20InsufficientBalance++`]] +:ERC20InvalidSender: pass:normal[xref:#IERC20Errors-ERC20InvalidSender-address-[`++ERC20InvalidSender++`]] +:ERC20InvalidReceiver: pass:normal[xref:#IERC20Errors-ERC20InvalidReceiver-address-[`++ERC20InvalidReceiver++`]] +:ERC20InsufficientAllowance: pass:normal[xref:#IERC20Errors-ERC20InsufficientAllowance-address-uint256-uint256-[`++ERC20InsufficientAllowance++`]] +:ERC20InvalidApprover: pass:normal[xref:#IERC20Errors-ERC20InvalidApprover-address-[`++ERC20InvalidApprover++`]] +:ERC20InvalidSpender: pass:normal[xref:#IERC20Errors-ERC20InvalidSpender-address-[`++ERC20InvalidSpender++`]] + +[.contract] +[[IERC20Errors]] +=== `++IERC20Errors++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/interfaces/draft-IERC6093.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/interfaces/draft-IERC6093.sol"; +``` + +Standard ERC20 Errors +Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC20 tokens. + +[.contract-index] +.Errors +-- +* {xref-IERC20Errors-ERC20InsufficientBalance-address-uint256-uint256-}[`++ERC20InsufficientBalance(sender, balance, needed)++`] +* {xref-IERC20Errors-ERC20InvalidSender-address-}[`++ERC20InvalidSender(sender)++`] +* {xref-IERC20Errors-ERC20InvalidReceiver-address-}[`++ERC20InvalidReceiver(receiver)++`] +* {xref-IERC20Errors-ERC20InsufficientAllowance-address-uint256-uint256-}[`++ERC20InsufficientAllowance(spender, allowance, needed)++`] +* {xref-IERC20Errors-ERC20InvalidApprover-address-}[`++ERC20InvalidApprover(approver)++`] +* {xref-IERC20Errors-ERC20InvalidSpender-address-}[`++ERC20InvalidSpender(spender)++`] + +-- + +[.contract-item] +[[IERC20Errors-ERC20InsufficientBalance-address-uint256-uint256-]] +==== `[.contract-item-name]#++ERC20InsufficientBalance++#++(address sender, uint256 balance, uint256 needed)++` [.item-kind]#error# + +Indicates an error related to the current `balance` of a `sender`. Used in transfers. + +[.contract-item] +[[IERC20Errors-ERC20InvalidSender-address-]] +==== `[.contract-item-name]#++ERC20InvalidSender++#++(address sender)++` [.item-kind]#error# + +Indicates a failure with the token `sender`. Used in transfers. + +[.contract-item] +[[IERC20Errors-ERC20InvalidReceiver-address-]] +==== `[.contract-item-name]#++ERC20InvalidReceiver++#++(address receiver)++` [.item-kind]#error# + +Indicates a failure with the token `receiver`. Used in transfers. + +[.contract-item] +[[IERC20Errors-ERC20InsufficientAllowance-address-uint256-uint256-]] +==== `[.contract-item-name]#++ERC20InsufficientAllowance++#++(address spender, uint256 allowance, uint256 needed)++` [.item-kind]#error# + +Indicates a failure with the `spender`’s `allowance`. Used in transfers. + +[.contract-item] +[[IERC20Errors-ERC20InvalidApprover-address-]] +==== `[.contract-item-name]#++ERC20InvalidApprover++#++(address approver)++` [.item-kind]#error# + +Indicates a failure with the `approver` of a token to be approved. Used in approvals. + +[.contract-item] +[[IERC20Errors-ERC20InvalidSpender-address-]] +==== `[.contract-item-name]#++ERC20InvalidSpender++#++(address spender)++` [.item-kind]#error# + +Indicates a failure with the `spender` to be approved. Used in approvals. + +:ERC721InvalidOwner: pass:normal[xref:#IERC721Errors-ERC721InvalidOwner-address-[`++ERC721InvalidOwner++`]] +:ERC721NonexistentToken: pass:normal[xref:#IERC721Errors-ERC721NonexistentToken-uint256-[`++ERC721NonexistentToken++`]] +:ERC721IncorrectOwner: pass:normal[xref:#IERC721Errors-ERC721IncorrectOwner-address-uint256-address-[`++ERC721IncorrectOwner++`]] +:ERC721InvalidSender: pass:normal[xref:#IERC721Errors-ERC721InvalidSender-address-[`++ERC721InvalidSender++`]] +:ERC721InvalidReceiver: pass:normal[xref:#IERC721Errors-ERC721InvalidReceiver-address-[`++ERC721InvalidReceiver++`]] +:ERC721InsufficientApproval: pass:normal[xref:#IERC721Errors-ERC721InsufficientApproval-address-uint256-[`++ERC721InsufficientApproval++`]] +:ERC721InvalidApprover: pass:normal[xref:#IERC721Errors-ERC721InvalidApprover-address-[`++ERC721InvalidApprover++`]] +:ERC721InvalidOperator: pass:normal[xref:#IERC721Errors-ERC721InvalidOperator-address-[`++ERC721InvalidOperator++`]] + +[.contract] +[[IERC721Errors]] +=== `++IERC721Errors++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/interfaces/draft-IERC6093.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/interfaces/draft-IERC6093.sol"; +``` + +Standard ERC721 Errors +Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC721 tokens. + +[.contract-index] +.Errors +-- +* {xref-IERC721Errors-ERC721InvalidOwner-address-}[`++ERC721InvalidOwner(owner)++`] +* {xref-IERC721Errors-ERC721NonexistentToken-uint256-}[`++ERC721NonexistentToken(tokenId)++`] +* {xref-IERC721Errors-ERC721IncorrectOwner-address-uint256-address-}[`++ERC721IncorrectOwner(sender, tokenId, owner)++`] +* {xref-IERC721Errors-ERC721InvalidSender-address-}[`++ERC721InvalidSender(sender)++`] +* {xref-IERC721Errors-ERC721InvalidReceiver-address-}[`++ERC721InvalidReceiver(receiver)++`] +* {xref-IERC721Errors-ERC721InsufficientApproval-address-uint256-}[`++ERC721InsufficientApproval(operator, tokenId)++`] +* {xref-IERC721Errors-ERC721InvalidApprover-address-}[`++ERC721InvalidApprover(approver)++`] +* {xref-IERC721Errors-ERC721InvalidOperator-address-}[`++ERC721InvalidOperator(operator)++`] + +-- + +[.contract-item] +[[IERC721Errors-ERC721InvalidOwner-address-]] +==== `[.contract-item-name]#++ERC721InvalidOwner++#++(address owner)++` [.item-kind]#error# + +Indicates that an address can't be an owner. For example, `address(0)` is a forbidden owner in EIP-20. +Used in balance queries. + +[.contract-item] +[[IERC721Errors-ERC721NonexistentToken-uint256-]] +==== `[.contract-item-name]#++ERC721NonexistentToken++#++(uint256 tokenId)++` [.item-kind]#error# + +Indicates a `tokenId` whose `owner` is the zero address. + +[.contract-item] +[[IERC721Errors-ERC721IncorrectOwner-address-uint256-address-]] +==== `[.contract-item-name]#++ERC721IncorrectOwner++#++(address sender, uint256 tokenId, address owner)++` [.item-kind]#error# + +Indicates an error related to the ownership over a particular token. Used in transfers. + +[.contract-item] +[[IERC721Errors-ERC721InvalidSender-address-]] +==== `[.contract-item-name]#++ERC721InvalidSender++#++(address sender)++` [.item-kind]#error# + +Indicates a failure with the token `sender`. Used in transfers. + +[.contract-item] +[[IERC721Errors-ERC721InvalidReceiver-address-]] +==== `[.contract-item-name]#++ERC721InvalidReceiver++#++(address receiver)++` [.item-kind]#error# + +Indicates a failure with the token `receiver`. Used in transfers. + +[.contract-item] +[[IERC721Errors-ERC721InsufficientApproval-address-uint256-]] +==== `[.contract-item-name]#++ERC721InsufficientApproval++#++(address operator, uint256 tokenId)++` [.item-kind]#error# + +Indicates a failure with the `operator`’s approval. Used in transfers. + +[.contract-item] +[[IERC721Errors-ERC721InvalidApprover-address-]] +==== `[.contract-item-name]#++ERC721InvalidApprover++#++(address approver)++` [.item-kind]#error# + +Indicates a failure with the `approver` of a token to be approved. Used in approvals. + +[.contract-item] +[[IERC721Errors-ERC721InvalidOperator-address-]] +==== `[.contract-item-name]#++ERC721InvalidOperator++#++(address operator)++` [.item-kind]#error# + +Indicates a failure with the `operator` to be approved. Used in approvals. + +:ERC1155InsufficientBalance: pass:normal[xref:#IERC1155Errors-ERC1155InsufficientBalance-address-uint256-uint256-uint256-[`++ERC1155InsufficientBalance++`]] +:ERC1155InvalidSender: pass:normal[xref:#IERC1155Errors-ERC1155InvalidSender-address-[`++ERC1155InvalidSender++`]] +:ERC1155InvalidReceiver: pass:normal[xref:#IERC1155Errors-ERC1155InvalidReceiver-address-[`++ERC1155InvalidReceiver++`]] +:ERC1155MissingApprovalForAll: pass:normal[xref:#IERC1155Errors-ERC1155MissingApprovalForAll-address-address-[`++ERC1155MissingApprovalForAll++`]] +:ERC1155InvalidApprover: pass:normal[xref:#IERC1155Errors-ERC1155InvalidApprover-address-[`++ERC1155InvalidApprover++`]] +:ERC1155InvalidOperator: pass:normal[xref:#IERC1155Errors-ERC1155InvalidOperator-address-[`++ERC1155InvalidOperator++`]] +:ERC1155InvalidArrayLength: pass:normal[xref:#IERC1155Errors-ERC1155InvalidArrayLength-uint256-uint256-[`++ERC1155InvalidArrayLength++`]] + +[.contract] +[[IERC1155Errors]] +=== `++IERC1155Errors++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/interfaces/draft-IERC6093.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/interfaces/draft-IERC6093.sol"; +``` + +Standard ERC1155 Errors +Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC1155 tokens. + +[.contract-index] +.Errors +-- +* {xref-IERC1155Errors-ERC1155InsufficientBalance-address-uint256-uint256-uint256-}[`++ERC1155InsufficientBalance(sender, balance, needed, tokenId)++`] +* {xref-IERC1155Errors-ERC1155InvalidSender-address-}[`++ERC1155InvalidSender(sender)++`] +* {xref-IERC1155Errors-ERC1155InvalidReceiver-address-}[`++ERC1155InvalidReceiver(receiver)++`] +* {xref-IERC1155Errors-ERC1155MissingApprovalForAll-address-address-}[`++ERC1155MissingApprovalForAll(operator, owner)++`] +* {xref-IERC1155Errors-ERC1155InvalidApprover-address-}[`++ERC1155InvalidApprover(approver)++`] +* {xref-IERC1155Errors-ERC1155InvalidOperator-address-}[`++ERC1155InvalidOperator(operator)++`] +* {xref-IERC1155Errors-ERC1155InvalidArrayLength-uint256-uint256-}[`++ERC1155InvalidArrayLength(idsLength, valuesLength)++`] + +-- + +[.contract-item] +[[IERC1155Errors-ERC1155InsufficientBalance-address-uint256-uint256-uint256-]] +==== `[.contract-item-name]#++ERC1155InsufficientBalance++#++(address sender, uint256 balance, uint256 needed, uint256 tokenId)++` [.item-kind]#error# + +Indicates an error related to the current `balance` of a `sender`. Used in transfers. + +[.contract-item] +[[IERC1155Errors-ERC1155InvalidSender-address-]] +==== `[.contract-item-name]#++ERC1155InvalidSender++#++(address sender)++` [.item-kind]#error# + +Indicates a failure with the token `sender`. Used in transfers. + +[.contract-item] +[[IERC1155Errors-ERC1155InvalidReceiver-address-]] +==== `[.contract-item-name]#++ERC1155InvalidReceiver++#++(address receiver)++` [.item-kind]#error# + +Indicates a failure with the token `receiver`. Used in transfers. + +[.contract-item] +[[IERC1155Errors-ERC1155MissingApprovalForAll-address-address-]] +==== `[.contract-item-name]#++ERC1155MissingApprovalForAll++#++(address operator, address owner)++` [.item-kind]#error# + +Indicates a failure with the `operator`’s approval. Used in transfers. + +[.contract-item] +[[IERC1155Errors-ERC1155InvalidApprover-address-]] +==== `[.contract-item-name]#++ERC1155InvalidApprover++#++(address approver)++` [.item-kind]#error# + +Indicates a failure with the `approver` of a token to be approved. Used in approvals. + +[.contract-item] +[[IERC1155Errors-ERC1155InvalidOperator-address-]] +==== `[.contract-item-name]#++ERC1155InvalidOperator++#++(address operator)++` [.item-kind]#error# + +Indicates a failure with the `operator` to be approved. Used in approvals. + +[.contract-item] +[[IERC1155Errors-ERC1155InvalidArrayLength-uint256-uint256-]] +==== `[.contract-item-name]#++ERC1155InvalidArrayLength++#++(uint256 idsLength, uint256 valuesLength)++` [.item-kind]#error# + +Indicates an array length mismatch between ids and values in a safeBatchTransferFrom operation. +Used in batch transfers. + +:isValidSignature: pass:normal[xref:#IERC1271-isValidSignature-bytes32-bytes-[`++isValidSignature++`]] + +[.contract] +[[IERC1271]] +=== `++IERC1271++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/interfaces/IERC1271.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/interfaces/IERC1271.sol"; +``` + +Interface of the ERC1271 standard signature validation method for +contracts as defined in https://eips.ethereum.org/EIPS/eip-1271[ERC-1271]. + +[.contract-index] +.Functions +-- +* {xref-IERC1271-isValidSignature-bytes32-bytes-}[`++isValidSignature(hash, signature)++`] + +-- + +[.contract-item] +[[IERC1271-isValidSignature-bytes32-bytes-]] +==== `[.contract-item-name]#++isValidSignature++#++(bytes32 hash, bytes signature) β†’ bytes4 magicValue++` [.item-kind]#external# + +Should return whether the signature provided is valid for the provided data + +:transferAndCall: pass:normal[xref:#IERC1363-transferAndCall-address-uint256-[`++transferAndCall++`]] +:transferAndCall: pass:normal[xref:#IERC1363-transferAndCall-address-uint256-bytes-[`++transferAndCall++`]] +:transferFromAndCall: pass:normal[xref:#IERC1363-transferFromAndCall-address-address-uint256-[`++transferFromAndCall++`]] +:transferFromAndCall: pass:normal[xref:#IERC1363-transferFromAndCall-address-address-uint256-bytes-[`++transferFromAndCall++`]] +:approveAndCall: pass:normal[xref:#IERC1363-approveAndCall-address-uint256-[`++approveAndCall++`]] +:approveAndCall: pass:normal[xref:#IERC1363-approveAndCall-address-uint256-bytes-[`++approveAndCall++`]] + +[.contract] +[[IERC1363]] +=== `++IERC1363++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/interfaces/IERC1363.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/interfaces/IERC1363.sol"; +``` + +Interface of an ERC1363 compliant contract, as defined in the +https://eips.ethereum.org/EIPS/eip-1363[EIP]. + +Defines a interface for ERC20 tokens that supports executing recipient +code after `transfer` or `transferFrom`, or spender code after `approve`. + +[.contract-index] +.Functions +-- +* {xref-IERC1363-transferAndCall-address-uint256-}[`++transferAndCall(to, amount)++`] +* {xref-IERC1363-transferAndCall-address-uint256-bytes-}[`++transferAndCall(to, amount, data)++`] +* {xref-IERC1363-transferFromAndCall-address-address-uint256-}[`++transferFromAndCall(from, to, amount)++`] +* {xref-IERC1363-transferFromAndCall-address-address-uint256-bytes-}[`++transferFromAndCall(from, to, amount, data)++`] +* {xref-IERC1363-approveAndCall-address-uint256-}[`++approveAndCall(spender, amount)++`] +* {xref-IERC1363-approveAndCall-address-uint256-bytes-}[`++approveAndCall(spender, amount, data)++`] + +[.contract-subindex-inherited] +.IERC20 +* {xref-IERC20-totalSupply--}[`++totalSupply()++`] +* {xref-IERC20-balanceOf-address-}[`++balanceOf(account)++`] +* {xref-IERC20-transfer-address-uint256-}[`++transfer(to, value)++`] +* {xref-IERC20-allowance-address-address-}[`++allowance(owner, spender)++`] +* {xref-IERC20-approve-address-uint256-}[`++approve(spender, value)++`] +* {xref-IERC20-transferFrom-address-address-uint256-}[`++transferFrom(from, to, value)++`] + +[.contract-subindex-inherited] +.IERC165 +* {xref-IERC165-supportsInterface-bytes4-}[`++supportsInterface(interfaceId)++`] + +-- + +[.contract-index] +.Events +-- + +[.contract-subindex-inherited] +.IERC20 +* {xref-IERC20-Transfer-address-address-uint256-}[`++Transfer(from, to, value)++`] +* {xref-IERC20-Approval-address-address-uint256-}[`++Approval(owner, spender, value)++`] + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-item] +[[IERC1363-transferAndCall-address-uint256-]] +==== `[.contract-item-name]#++transferAndCall++#++(address to, uint256 amount) β†’ bool++` [.item-kind]#external# + +Transfer tokens from `msg.sender` to another address and then call `onTransferReceived` on receiver + +[.contract-item] +[[IERC1363-transferAndCall-address-uint256-bytes-]] +==== `[.contract-item-name]#++transferAndCall++#++(address to, uint256 amount, bytes data) β†’ bool++` [.item-kind]#external# + +Transfer tokens from `msg.sender` to another address and then call `onTransferReceived` on receiver + +[.contract-item] +[[IERC1363-transferFromAndCall-address-address-uint256-]] +==== `[.contract-item-name]#++transferFromAndCall++#++(address from, address to, uint256 amount) β†’ bool++` [.item-kind]#external# + +Transfer tokens from one address to another and then call `onTransferReceived` on receiver + +[.contract-item] +[[IERC1363-transferFromAndCall-address-address-uint256-bytes-]] +==== `[.contract-item-name]#++transferFromAndCall++#++(address from, address to, uint256 amount, bytes data) β†’ bool++` [.item-kind]#external# + +Transfer tokens from one address to another and then call `onTransferReceived` on receiver + +[.contract-item] +[[IERC1363-approveAndCall-address-uint256-]] +==== `[.contract-item-name]#++approveAndCall++#++(address spender, uint256 amount) β†’ bool++` [.item-kind]#external# + +Approve the passed address to spend the specified amount of tokens on behalf of msg.sender +and then call `onApprovalReceived` on spender. + +[.contract-item] +[[IERC1363-approveAndCall-address-uint256-bytes-]] +==== `[.contract-item-name]#++approveAndCall++#++(address spender, uint256 amount, bytes data) β†’ bool++` [.item-kind]#external# + +Approve the passed address to spend the specified amount of tokens on behalf of msg.sender +and then call `onApprovalReceived` on spender. + +:onTransferReceived: pass:normal[xref:#IERC1363Receiver-onTransferReceived-address-address-uint256-bytes-[`++onTransferReceived++`]] + +[.contract] +[[IERC1363Receiver]] +=== `++IERC1363Receiver++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/interfaces/IERC1363Receiver.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/interfaces/IERC1363Receiver.sol"; +``` + +Interface for any contract that wants to support {IERC1363-transferAndCall} +or {IERC1363-transferFromAndCall} from {ERC1363} token contracts. + +[.contract-index] +.Functions +-- +* {xref-IERC1363Receiver-onTransferReceived-address-address-uint256-bytes-}[`++onTransferReceived(operator, from, amount, data)++`] + +-- + +[.contract-item] +[[IERC1363Receiver-onTransferReceived-address-address-uint256-bytes-]] +==== `[.contract-item-name]#++onTransferReceived++#++(address operator, address from, uint256 amount, bytes data) β†’ bytes4++` [.item-kind]#external# + +Any ERC1363 smart contract calls this function on the recipient +after a `transfer` or a `transferFrom`. This function MAY throw to revert and reject the +transfer. Return of other than the magic value MUST result in the +transaction being reverted. +Note: the token contract address is always the message sender. + +:onApprovalReceived: pass:normal[xref:#IERC1363Spender-onApprovalReceived-address-uint256-bytes-[`++onApprovalReceived++`]] + +[.contract] +[[IERC1363Spender]] +=== `++IERC1363Spender++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/interfaces/IERC1363Spender.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/interfaces/IERC1363Spender.sol"; +``` + +Interface for any contract that wants to support {IERC1363-approveAndCall} +from {ERC1363} token contracts. + +[.contract-index] +.Functions +-- +* {xref-IERC1363Spender-onApprovalReceived-address-uint256-bytes-}[`++onApprovalReceived(owner, amount, data)++`] + +-- + +[.contract-item] +[[IERC1363Spender-onApprovalReceived-address-uint256-bytes-]] +==== `[.contract-item-name]#++onApprovalReceived++#++(address owner, uint256 amount, bytes data) β†’ bytes4++` [.item-kind]#external# + +Any ERC1363 smart contract calls this function on the recipient +after an `approve`. This function MAY throw to revert and reject the +approval. Return of other than the magic value MUST result in the +transaction being reverted. +Note: the token contract address is always the message sender. + +:canImplementInterfaceForAddress: pass:normal[xref:#IERC1820Implementer-canImplementInterfaceForAddress-bytes32-address-[`++canImplementInterfaceForAddress++`]] + +[.contract] +[[IERC1820Implementer]] +=== `++IERC1820Implementer++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/interfaces/IERC1820Implementer.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/interfaces/IERC1820Implementer.sol"; +``` + +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}. + +[.contract-index] +.Functions +-- +* {xref-IERC1820Implementer-canImplementInterfaceForAddress-bytes32-address-}[`++canImplementInterfaceForAddress(interfaceHash, account)++`] + +-- + +[.contract-item] +[[IERC1820Implementer-canImplementInterfaceForAddress-bytes32-address-]] +==== `[.contract-item-name]#++canImplementInterfaceForAddress++#++(bytes32 interfaceHash, address account) β†’ bytes32++` [.item-kind]#external# + +Returns a special value (`ERC1820_ACCEPT_MAGIC`) if this contract +implements `interfaceHash` for `account`. + +See {IERC1820Registry-setInterfaceImplementer}. + +:InterfaceImplementerSet: pass:normal[xref:#IERC1820Registry-InterfaceImplementerSet-address-bytes32-address-[`++InterfaceImplementerSet++`]] +:ManagerChanged: pass:normal[xref:#IERC1820Registry-ManagerChanged-address-address-[`++ManagerChanged++`]] +:setManager: pass:normal[xref:#IERC1820Registry-setManager-address-address-[`++setManager++`]] +:getManager: pass:normal[xref:#IERC1820Registry-getManager-address-[`++getManager++`]] +:setInterfaceImplementer: pass:normal[xref:#IERC1820Registry-setInterfaceImplementer-address-bytes32-address-[`++setInterfaceImplementer++`]] +:getInterfaceImplementer: pass:normal[xref:#IERC1820Registry-getInterfaceImplementer-address-bytes32-[`++getInterfaceImplementer++`]] +:interfaceHash: pass:normal[xref:#IERC1820Registry-interfaceHash-string-[`++interfaceHash++`]] +:updateERC165Cache: pass:normal[xref:#IERC1820Registry-updateERC165Cache-address-bytes4-[`++updateERC165Cache++`]] +:implementsERC165Interface: pass:normal[xref:#IERC1820Registry-implementsERC165Interface-address-bytes4-[`++implementsERC165Interface++`]] +:implementsERC165InterfaceNoCache: pass:normal[xref:#IERC1820Registry-implementsERC165InterfaceNoCache-address-bytes4-[`++implementsERC165InterfaceNoCache++`]] + +[.contract] +[[IERC1820Registry]] +=== `++IERC1820Registry++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/interfaces/IERC1820Registry.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/interfaces/IERC1820Registry.sol"; +``` + +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. + +[.contract-index] +.Functions +-- +* {xref-IERC1820Registry-setManager-address-address-}[`++setManager(account, newManager)++`] +* {xref-IERC1820Registry-getManager-address-}[`++getManager(account)++`] +* {xref-IERC1820Registry-setInterfaceImplementer-address-bytes32-address-}[`++setInterfaceImplementer(account, _interfaceHash, implementer)++`] +* {xref-IERC1820Registry-getInterfaceImplementer-address-bytes32-}[`++getInterfaceImplementer(account, _interfaceHash)++`] +* {xref-IERC1820Registry-interfaceHash-string-}[`++interfaceHash(interfaceName)++`] +* {xref-IERC1820Registry-updateERC165Cache-address-bytes4-}[`++updateERC165Cache(account, interfaceId)++`] +* {xref-IERC1820Registry-implementsERC165Interface-address-bytes4-}[`++implementsERC165Interface(account, interfaceId)++`] +* {xref-IERC1820Registry-implementsERC165InterfaceNoCache-address-bytes4-}[`++implementsERC165InterfaceNoCache(account, interfaceId)++`] + +-- + +[.contract-index] +.Events +-- +* {xref-IERC1820Registry-InterfaceImplementerSet-address-bytes32-address-}[`++InterfaceImplementerSet(account, interfaceHash, implementer)++`] +* {xref-IERC1820Registry-ManagerChanged-address-address-}[`++ManagerChanged(account, newManager)++`] + +-- + +[.contract-item] +[[IERC1820Registry-setManager-address-address-]] +==== `[.contract-item-name]#++setManager++#++(address account, address newManager)++` [.item-kind]#external# + +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`. + +[.contract-item] +[[IERC1820Registry-getManager-address-]] +==== `[.contract-item-name]#++getManager++#++(address account) β†’ address++` [.item-kind]#external# + +Returns the manager for `account`. + +See {setManager}. + +[.contract-item] +[[IERC1820Registry-setInterfaceImplementer-address-bytes32-address-]] +==== `[.contract-item-name]#++setInterfaceImplementer++#++(address account, bytes32 _interfaceHash, address implementer)++` [.item-kind]#external# + +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}. + +[.contract-item] +[[IERC1820Registry-getInterfaceImplementer-address-bytes32-]] +==== `[.contract-item-name]#++getInterfaceImplementer++#++(address account, bytes32 _interfaceHash) β†’ address++` [.item-kind]#external# + +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. + +[.contract-item] +[[IERC1820Registry-interfaceHash-string-]] +==== `[.contract-item-name]#++interfaceHash++#++(string interfaceName) β†’ bytes32++` [.item-kind]#external# + +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]. + +[.contract-item] +[[IERC1820Registry-updateERC165Cache-address-bytes4-]] +==== `[.contract-item-name]#++updateERC165Cache++#++(address account, bytes4 interfaceId)++` [.item-kind]#external# + +[.contract-item] +[[IERC1820Registry-implementsERC165Interface-address-bytes4-]] +==== `[.contract-item-name]#++implementsERC165Interface++#++(address account, bytes4 interfaceId) β†’ bool++` [.item-kind]#external# + +[.contract-item] +[[IERC1820Registry-implementsERC165InterfaceNoCache-address-bytes4-]] +==== `[.contract-item-name]#++implementsERC165InterfaceNoCache++#++(address account, bytes4 interfaceId) β†’ bool++` [.item-kind]#external# + +[.contract-item] +[[IERC1820Registry-InterfaceImplementerSet-address-bytes32-address-]] +==== `[.contract-item-name]#++InterfaceImplementerSet++#++(address indexed account, bytes32 indexed interfaceHash, address indexed implementer)++` [.item-kind]#event# + +[.contract-item] +[[IERC1820Registry-ManagerChanged-address-address-]] +==== `[.contract-item-name]#++ManagerChanged++#++(address indexed account, address indexed newManager)++` [.item-kind]#event# + +:proxiableUUID: pass:normal[xref:#IERC1822Proxiable-proxiableUUID--[`++proxiableUUID++`]] + +[.contract] +[[IERC1822Proxiable]] +=== `++IERC1822Proxiable++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/interfaces/draft-IERC1822.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/interfaces/draft-IERC1822.sol"; +``` + +ERC1822: Universal Upgradeable Proxy Standard (UUPS) documents a method for upgradeability through a simplified +proxy whose upgrades are fully controlled by the current implementation. + +[.contract-index] +.Functions +-- +* {xref-IERC1822Proxiable-proxiableUUID--}[`++proxiableUUID()++`] + +-- + +[.contract-item] +[[IERC1822Proxiable-proxiableUUID--]] +==== `[.contract-item-name]#++proxiableUUID++#++() β†’ bytes32++` [.item-kind]#external# + +Returns the storage slot that the proxiable contract assumes is being used to store the implementation +address. + +IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks +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. + +[.contract] +[[IERC2612]] +=== `++IERC2612++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/interfaces/IERC2612.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/interfaces/IERC2612.sol"; +``` + +[.contract-index] +.Functions +-- + +[.contract-subindex-inherited] +.IERC20Permit +* {xref-IERC20Permit-permit-address-address-uint256-uint256-uint8-bytes32-bytes32-}[`++permit(owner, spender, value, deadline, v, r, s)++`] +* {xref-IERC20Permit-nonces-address-}[`++nonces(owner)++`] +* {xref-IERC20Permit-DOMAIN_SEPARATOR--}[`++DOMAIN_SEPARATOR()++`] + +-- + +:royaltyInfo: pass:normal[xref:#IERC2981-royaltyInfo-uint256-uint256-[`++royaltyInfo++`]] + +[.contract] +[[IERC2981]] +=== `++IERC2981++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/interfaces/IERC2981.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/interfaces/IERC2981.sol"; +``` + +Interface for the NFT Royalty Standard. + +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. + +[.contract-index] +.Functions +-- +* {xref-IERC2981-royaltyInfo-uint256-uint256-}[`++royaltyInfo(tokenId, salePrice)++`] + +[.contract-subindex-inherited] +.IERC165 +* {xref-IERC165-supportsInterface-bytes4-}[`++supportsInterface(interfaceId)++`] + +-- + +[.contract-item] +[[IERC2981-royaltyInfo-uint256-uint256-]] +==== `[.contract-item-name]#++royaltyInfo++#++(uint256 tokenId, uint256 salePrice) β†’ address receiver, uint256 royaltyAmount++` [.item-kind]#external# + +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. + +:maxFlashLoan: pass:normal[xref:#IERC3156FlashLender-maxFlashLoan-address-[`++maxFlashLoan++`]] +:flashFee: pass:normal[xref:#IERC3156FlashLender-flashFee-address-uint256-[`++flashFee++`]] +:flashLoan: pass:normal[xref:#IERC3156FlashLender-flashLoan-contract-IERC3156FlashBorrower-address-uint256-bytes-[`++flashLoan++`]] + +[.contract] +[[IERC3156FlashLender]] +=== `++IERC3156FlashLender++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/interfaces/IERC3156FlashLender.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/interfaces/IERC3156FlashLender.sol"; +``` + +Interface of the ERC3156 FlashLender, as defined in +https://eips.ethereum.org/EIPS/eip-3156[ERC-3156]. + +[.contract-index] +.Functions +-- +* {xref-IERC3156FlashLender-maxFlashLoan-address-}[`++maxFlashLoan(token)++`] +* {xref-IERC3156FlashLender-flashFee-address-uint256-}[`++flashFee(token, amount)++`] +* {xref-IERC3156FlashLender-flashLoan-contract-IERC3156FlashBorrower-address-uint256-bytes-}[`++flashLoan(receiver, token, amount, data)++`] + +-- + +[.contract-item] +[[IERC3156FlashLender-maxFlashLoan-address-]] +==== `[.contract-item-name]#++maxFlashLoan++#++(address token) β†’ uint256++` [.item-kind]#external# + +The amount of currency available to be lended. + +[.contract-item] +[[IERC3156FlashLender-flashFee-address-uint256-]] +==== `[.contract-item-name]#++flashFee++#++(address token, uint256 amount) β†’ uint256++` [.item-kind]#external# + +The fee to be charged for a given loan. + +[.contract-item] +[[IERC3156FlashLender-flashLoan-contract-IERC3156FlashBorrower-address-uint256-bytes-]] +==== `[.contract-item-name]#++flashLoan++#++(contract IERC3156FlashBorrower receiver, address token, uint256 amount, bytes data) β†’ bool++` [.item-kind]#external# + +Initiate a flash loan. + +:onFlashLoan: pass:normal[xref:#IERC3156FlashBorrower-onFlashLoan-address-address-uint256-uint256-bytes-[`++onFlashLoan++`]] + +[.contract] +[[IERC3156FlashBorrower]] +=== `++IERC3156FlashBorrower++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/interfaces/IERC3156FlashBorrower.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/interfaces/IERC3156FlashBorrower.sol"; +``` + +Interface of the ERC3156 FlashBorrower, as defined in +https://eips.ethereum.org/EIPS/eip-3156[ERC-3156]. + +[.contract-index] +.Functions +-- +* {xref-IERC3156FlashBorrower-onFlashLoan-address-address-uint256-uint256-bytes-}[`++onFlashLoan(initiator, token, amount, fee, data)++`] + +-- + +[.contract-item] +[[IERC3156FlashBorrower-onFlashLoan-address-address-uint256-uint256-bytes-]] +==== `[.contract-item-name]#++onFlashLoan++#++(address initiator, address token, uint256 amount, uint256 fee, bytes data) β†’ bytes32++` [.item-kind]#external# + +Receive a flash loan. + +:Deposit: pass:normal[xref:#IERC4626-Deposit-address-address-uint256-uint256-[`++Deposit++`]] +:Withdraw: pass:normal[xref:#IERC4626-Withdraw-address-address-address-uint256-uint256-[`++Withdraw++`]] +:asset: pass:normal[xref:#IERC4626-asset--[`++asset++`]] +:totalAssets: pass:normal[xref:#IERC4626-totalAssets--[`++totalAssets++`]] +:convertToShares: pass:normal[xref:#IERC4626-convertToShares-uint256-[`++convertToShares++`]] +:convertToAssets: pass:normal[xref:#IERC4626-convertToAssets-uint256-[`++convertToAssets++`]] +:maxDeposit: pass:normal[xref:#IERC4626-maxDeposit-address-[`++maxDeposit++`]] +:previewDeposit: pass:normal[xref:#IERC4626-previewDeposit-uint256-[`++previewDeposit++`]] +:deposit: pass:normal[xref:#IERC4626-deposit-uint256-address-[`++deposit++`]] +:maxMint: pass:normal[xref:#IERC4626-maxMint-address-[`++maxMint++`]] +:previewMint: pass:normal[xref:#IERC4626-previewMint-uint256-[`++previewMint++`]] +:mint: pass:normal[xref:#IERC4626-mint-uint256-address-[`++mint++`]] +:maxWithdraw: pass:normal[xref:#IERC4626-maxWithdraw-address-[`++maxWithdraw++`]] +:previewWithdraw: pass:normal[xref:#IERC4626-previewWithdraw-uint256-[`++previewWithdraw++`]] +:withdraw: pass:normal[xref:#IERC4626-withdraw-uint256-address-address-[`++withdraw++`]] +:maxRedeem: pass:normal[xref:#IERC4626-maxRedeem-address-[`++maxRedeem++`]] +:previewRedeem: pass:normal[xref:#IERC4626-previewRedeem-uint256-[`++previewRedeem++`]] +:redeem: pass:normal[xref:#IERC4626-redeem-uint256-address-address-[`++redeem++`]] + +[.contract] +[[IERC4626]] +=== `++IERC4626++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/interfaces/IERC4626.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/interfaces/IERC4626.sol"; +``` + +Interface of the ERC4626 "Tokenized Vault Standard", as defined in +https://eips.ethereum.org/EIPS/eip-4626[ERC-4626]. + +[.contract-index] +.Functions +-- +* {xref-IERC4626-asset--}[`++asset()++`] +* {xref-IERC4626-totalAssets--}[`++totalAssets()++`] +* {xref-IERC4626-convertToShares-uint256-}[`++convertToShares(assets)++`] +* {xref-IERC4626-convertToAssets-uint256-}[`++convertToAssets(shares)++`] +* {xref-IERC4626-maxDeposit-address-}[`++maxDeposit(receiver)++`] +* {xref-IERC4626-previewDeposit-uint256-}[`++previewDeposit(assets)++`] +* {xref-IERC4626-deposit-uint256-address-}[`++deposit(assets, receiver)++`] +* {xref-IERC4626-maxMint-address-}[`++maxMint(receiver)++`] +* {xref-IERC4626-previewMint-uint256-}[`++previewMint(shares)++`] +* {xref-IERC4626-mint-uint256-address-}[`++mint(shares, receiver)++`] +* {xref-IERC4626-maxWithdraw-address-}[`++maxWithdraw(owner)++`] +* {xref-IERC4626-previewWithdraw-uint256-}[`++previewWithdraw(assets)++`] +* {xref-IERC4626-withdraw-uint256-address-address-}[`++withdraw(assets, receiver, owner)++`] +* {xref-IERC4626-maxRedeem-address-}[`++maxRedeem(owner)++`] +* {xref-IERC4626-previewRedeem-uint256-}[`++previewRedeem(shares)++`] +* {xref-IERC4626-redeem-uint256-address-address-}[`++redeem(shares, receiver, owner)++`] + +[.contract-subindex-inherited] +.IERC20Metadata +* {xref-IERC20Metadata-name--}[`++name()++`] +* {xref-IERC20Metadata-symbol--}[`++symbol()++`] +* {xref-IERC20Metadata-decimals--}[`++decimals()++`] + +[.contract-subindex-inherited] +.IERC20 +* {xref-IERC20-totalSupply--}[`++totalSupply()++`] +* {xref-IERC20-balanceOf-address-}[`++balanceOf(account)++`] +* {xref-IERC20-transfer-address-uint256-}[`++transfer(to, value)++`] +* {xref-IERC20-allowance-address-address-}[`++allowance(owner, spender)++`] +* {xref-IERC20-approve-address-uint256-}[`++approve(spender, value)++`] +* {xref-IERC20-transferFrom-address-address-uint256-}[`++transferFrom(from, to, value)++`] + +-- + +[.contract-index] +.Events +-- +* {xref-IERC4626-Deposit-address-address-uint256-uint256-}[`++Deposit(sender, owner, assets, shares)++`] +* {xref-IERC4626-Withdraw-address-address-address-uint256-uint256-}[`++Withdraw(sender, receiver, owner, assets, shares)++`] + +[.contract-subindex-inherited] +.IERC20Metadata + +[.contract-subindex-inherited] +.IERC20 +* {xref-IERC20-Transfer-address-address-uint256-}[`++Transfer(from, to, value)++`] +* {xref-IERC20-Approval-address-address-uint256-}[`++Approval(owner, spender, value)++`] + +-- + +[.contract-item] +[[IERC4626-asset--]] +==== `[.contract-item-name]#++asset++#++() β†’ address assetTokenAddress++` [.item-kind]#external# + +Returns the address of the underlying token used for the Vault for accounting, depositing, and withdrawing. + +- MUST be an ERC-20 token contract. +- MUST NOT revert. + +[.contract-item] +[[IERC4626-totalAssets--]] +==== `[.contract-item-name]#++totalAssets++#++() β†’ uint256 totalManagedAssets++` [.item-kind]#external# + +Returns the total amount of the underlying asset that is β€œmanaged” by Vault. + +- SHOULD include any compounding that occurs from yield. +- MUST be inclusive of any fees that are charged against assets in the Vault. +- MUST NOT revert. + +[.contract-item] +[[IERC4626-convertToShares-uint256-]] +==== `[.contract-item-name]#++convertToShares++#++(uint256 assets) β†’ uint256 shares++` [.item-kind]#external# + +Returns the amount of shares that the Vault would exchange for the amount of assets provided, in an ideal +scenario where all the conditions are met. + +- MUST NOT be inclusive of any fees that are charged against assets in the Vault. +- MUST NOT show any variations depending on the caller. +- MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange. +- MUST NOT revert. + +NOTE: This calculation MAY NOT reflect the β€œper-user” price-per-share, and instead should reflect the +β€œaverage-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and +from. + +[.contract-item] +[[IERC4626-convertToAssets-uint256-]] +==== `[.contract-item-name]#++convertToAssets++#++(uint256 shares) β†’ uint256 assets++` [.item-kind]#external# + +Returns the amount of assets that the Vault would exchange for the amount of shares provided, in an ideal +scenario where all the conditions are met. + +- MUST NOT be inclusive of any fees that are charged against assets in the Vault. +- MUST NOT show any variations depending on the caller. +- MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange. +- MUST NOT revert. + +NOTE: This calculation MAY NOT reflect the β€œper-user” price-per-share, and instead should reflect the +β€œaverage-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and +from. + +[.contract-item] +[[IERC4626-maxDeposit-address-]] +==== `[.contract-item-name]#++maxDeposit++#++(address receiver) β†’ uint256 maxAssets++` [.item-kind]#external# + +Returns the maximum amount of the underlying asset that can be deposited into the Vault for the receiver, +through a deposit call. + +- MUST return a limited value if receiver is subject to some deposit limit. +- MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of assets that may be deposited. +- MUST NOT revert. + +[.contract-item] +[[IERC4626-previewDeposit-uint256-]] +==== `[.contract-item-name]#++previewDeposit++#++(uint256 assets) β†’ uint256 shares++` [.item-kind]#external# + +Allows an on-chain or off-chain user to simulate the effects of their deposit at the current block, given +current on-chain conditions. + +- MUST return as close to and no more than the exact amount of Vault shares that would be minted in a deposit + call in the same transaction. I.e. deposit should return the same or more shares as previewDeposit if called + in the same transaction. +- MUST NOT account for deposit limits like those returned from maxDeposit and should always act as though the + deposit would be accepted, regardless if the user has enough tokens approved, etc. +- MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees. +- MUST NOT revert. + +NOTE: any unfavorable discrepancy between convertToShares and previewDeposit SHOULD be considered slippage in +share price or some other type of condition, meaning the depositor will lose assets by depositing. + +[.contract-item] +[[IERC4626-deposit-uint256-address-]] +==== `[.contract-item-name]#++deposit++#++(uint256 assets, address receiver) β†’ uint256 shares++` [.item-kind]#external# + +Mints shares Vault shares to receiver by depositing exactly amount of underlying tokens. + +- MUST emit the Deposit event. +- MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the + deposit execution, and are accounted for during deposit. +- MUST revert if all of assets cannot be deposited (due to deposit limit being reached, slippage, the user not + approving enough underlying tokens to the Vault contract, etc). + +NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token. + +[.contract-item] +[[IERC4626-maxMint-address-]] +==== `[.contract-item-name]#++maxMint++#++(address receiver) β†’ uint256 maxShares++` [.item-kind]#external# + +Returns the maximum amount of the Vault shares that can be minted for the receiver, through a mint call. +- MUST return a limited value if receiver is subject to some mint limit. +- MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of shares that may be minted. +- MUST NOT revert. + +[.contract-item] +[[IERC4626-previewMint-uint256-]] +==== `[.contract-item-name]#++previewMint++#++(uint256 shares) β†’ uint256 assets++` [.item-kind]#external# + +Allows an on-chain or off-chain user to simulate the effects of their mint at the current block, given +current on-chain conditions. + +- MUST return as close to and no fewer than the exact amount of assets that would be deposited in a mint call + in the same transaction. I.e. mint should return the same or fewer assets as previewMint if called in the + same transaction. +- MUST NOT account for mint limits like those returned from maxMint and should always act as though the mint + would be accepted, regardless if the user has enough tokens approved, etc. +- MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees. +- MUST NOT revert. + +NOTE: any unfavorable discrepancy between convertToAssets and previewMint SHOULD be considered slippage in +share price or some other type of condition, meaning the depositor will lose assets by minting. + +[.contract-item] +[[IERC4626-mint-uint256-address-]] +==== `[.contract-item-name]#++mint++#++(uint256 shares, address receiver) β†’ uint256 assets++` [.item-kind]#external# + +Mints exactly shares Vault shares to receiver by depositing amount of underlying tokens. + +- MUST emit the Deposit event. +- MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the mint + execution, and are accounted for during mint. +- MUST revert if all of shares cannot be minted (due to deposit limit being reached, slippage, the user not + approving enough underlying tokens to the Vault contract, etc). + +NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token. + +[.contract-item] +[[IERC4626-maxWithdraw-address-]] +==== `[.contract-item-name]#++maxWithdraw++#++(address owner) β†’ uint256 maxAssets++` [.item-kind]#external# + +Returns the maximum amount of the underlying asset that can be withdrawn from the owner balance in the +Vault, through a withdraw call. + +- MUST return a limited value if owner is subject to some withdrawal limit or timelock. +- MUST NOT revert. + +[.contract-item] +[[IERC4626-previewWithdraw-uint256-]] +==== `[.contract-item-name]#++previewWithdraw++#++(uint256 assets) β†’ uint256 shares++` [.item-kind]#external# + +Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the current block, +given current on-chain conditions. + +- MUST return as close to and no fewer than the exact amount of Vault shares that would be burned in a withdraw + call in the same transaction. I.e. withdraw should return the same or fewer shares as previewWithdraw if + called + in the same transaction. +- MUST NOT account for withdrawal limits like those returned from maxWithdraw and should always act as though + the withdrawal would be accepted, regardless if the user has enough shares, etc. +- MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees. +- MUST NOT revert. + +NOTE: any unfavorable discrepancy between convertToShares and previewWithdraw SHOULD be considered slippage in +share price or some other type of condition, meaning the depositor will lose assets by depositing. + +[.contract-item] +[[IERC4626-withdraw-uint256-address-address-]] +==== `[.contract-item-name]#++withdraw++#++(uint256 assets, address receiver, address owner) β†’ uint256 shares++` [.item-kind]#external# + +Burns shares from owner and sends exactly assets of underlying tokens to receiver. + +- MUST emit the Withdraw event. +- MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the + withdraw execution, and are accounted for during withdraw. +- MUST revert if all of assets cannot be withdrawn (due to withdrawal limit being reached, slippage, the owner + not having enough shares, etc). + +Note that some implementations will require pre-requesting to the Vault before a withdrawal may be performed. +Those methods should be performed separately. + +[.contract-item] +[[IERC4626-maxRedeem-address-]] +==== `[.contract-item-name]#++maxRedeem++#++(address owner) β†’ uint256 maxShares++` [.item-kind]#external# + +Returns the maximum amount of Vault shares that can be redeemed from the owner balance in the Vault, +through a redeem call. + +- MUST return a limited value if owner is subject to some withdrawal limit or timelock. +- MUST return balanceOf(owner) if owner is not subject to any withdrawal limit or timelock. +- MUST NOT revert. + +[.contract-item] +[[IERC4626-previewRedeem-uint256-]] +==== `[.contract-item-name]#++previewRedeem++#++(uint256 shares) β†’ uint256 assets++` [.item-kind]#external# + +Allows an on-chain or off-chain user to simulate the effects of their redeemption at the current block, +given current on-chain conditions. + +- MUST return as close to and no more than the exact amount of assets that would be withdrawn in a redeem call + in the same transaction. I.e. redeem should return the same or more assets as previewRedeem if called in the + same transaction. +- MUST NOT account for redemption limits like those returned from maxRedeem and should always act as though the + redemption would be accepted, regardless if the user has enough shares, etc. +- MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees. +- MUST NOT revert. + +NOTE: any unfavorable discrepancy between convertToAssets and previewRedeem SHOULD be considered slippage in +share price or some other type of condition, meaning the depositor will lose assets by redeeming. + +[.contract-item] +[[IERC4626-redeem-uint256-address-address-]] +==== `[.contract-item-name]#++redeem++#++(uint256 shares, address receiver, address owner) β†’ uint256 assets++` [.item-kind]#external# + +Burns exactly shares from owner and sends assets of underlying tokens to receiver. + +- MUST emit the Withdraw event. +- MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the + redeem execution, and are accounted for during redeem. +- MUST revert if all of shares cannot be redeemed (due to withdrawal limit being reached, slippage, the owner + not having enough shares, etc). + +NOTE: some implementations will require pre-requesting to the Vault before a withdrawal may be performed. +Those methods should be performed separately. + +[.contract-item] +[[IERC4626-Deposit-address-address-uint256-uint256-]] +==== `[.contract-item-name]#++Deposit++#++(address indexed sender, address indexed owner, uint256 assets, uint256 shares)++` [.item-kind]#event# + +[.contract-item] +[[IERC4626-Withdraw-address-address-address-uint256-uint256-]] +==== `[.contract-item-name]#++Withdraw++#++(address indexed sender, address indexed receiver, address indexed owner, uint256 assets, uint256 shares)++` [.item-kind]#event# + +:owner: pass:normal[xref:#IERC5313-owner--[`++owner++`]] + +[.contract] +[[IERC5313]] +=== `++IERC5313++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/interfaces/IERC5313.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/interfaces/IERC5313.sol"; +``` + +Interface for the Light Contract Ownership Standard. + +A standardized minimal interface required to identify an account that controls a contract + +[.contract-index] +.Functions +-- +* {xref-IERC5313-owner--}[`++owner()++`] + +-- + +[.contract-item] +[[IERC5313-owner--]] +==== `[.contract-item-name]#++owner++#++() β†’ address++` [.item-kind]#external# + +Gets the address of the owner. + +:EIP712DomainChanged: pass:normal[xref:#IERC5267-EIP712DomainChanged--[`++EIP712DomainChanged++`]] +:eip712Domain: pass:normal[xref:#IERC5267-eip712Domain--[`++eip712Domain++`]] + +[.contract] +[[IERC5267]] +=== `++IERC5267++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/interfaces/IERC5267.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/interfaces/IERC5267.sol"; +``` + +[.contract-index] +.Functions +-- +* {xref-IERC5267-eip712Domain--}[`++eip712Domain()++`] + +-- + +[.contract-index] +.Events +-- +* {xref-IERC5267-EIP712DomainChanged--}[`++EIP712DomainChanged()++`] + +-- + +[.contract-item] +[[IERC5267-eip712Domain--]] +==== `[.contract-item-name]#++eip712Domain++#++() β†’ bytes1 fields, string name, string version, uint256 chainId, address verifyingContract, bytes32 salt, uint256[] extensions++` [.item-kind]#external# + +returns the fields and values that describe the domain separator used by this contract for EIP-712 +signature. + +[.contract-item] +[[IERC5267-EIP712DomainChanged--]] +==== `[.contract-item-name]#++EIP712DomainChanged++#++()++` [.item-kind]#event# + +MAY be emitted to signal that the domain could have changed. + +[.contract] +[[IERC5805]] +=== `++IERC5805++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/interfaces/IERC5805.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/interfaces/IERC5805.sol"; +``` + +[.contract-index] +.Functions +-- + +[.contract-subindex-inherited] +.IVotes +* {xref-IVotes-getVotes-address-}[`++getVotes(account)++`] +* {xref-IVotes-getPastVotes-address-uint256-}[`++getPastVotes(account, timepoint)++`] +* {xref-IVotes-getPastTotalSupply-uint256-}[`++getPastTotalSupply(timepoint)++`] +* {xref-IVotes-delegates-address-}[`++delegates(account)++`] +* {xref-IVotes-delegate-address-}[`++delegate(delegatee)++`] +* {xref-IVotes-delegateBySig-address-uint256-uint256-uint8-bytes32-bytes32-}[`++delegateBySig(delegatee, nonce, expiry, v, r, s)++`] + +[.contract-subindex-inherited] +.IERC6372 +* {xref-IERC6372-clock--}[`++clock()++`] +* {xref-IERC6372-CLOCK_MODE--}[`++CLOCK_MODE()++`] + +-- + +[.contract-index] +.Events +-- + +[.contract-subindex-inherited] +.IVotes +* {xref-IVotes-DelegateChanged-address-address-address-}[`++DelegateChanged(delegator, fromDelegate, toDelegate)++`] +* {xref-IVotes-DelegateVotesChanged-address-uint256-uint256-}[`++DelegateVotesChanged(delegate, previousVotes, newVotes)++`] + +[.contract-subindex-inherited] +.IERC6372 + +-- + +[.contract-index] +.Errors +-- + +[.contract-subindex-inherited] +.IVotes +* {xref-IVotes-VotesExpiredSignature-uint256-}[`++VotesExpiredSignature(expiry)++`] + +[.contract-subindex-inherited] +.IERC6372 + +-- + +:clock: pass:normal[xref:#IERC6372-clock--[`++clock++`]] +:CLOCK_MODE: pass:normal[xref:#IERC6372-CLOCK_MODE--[`++CLOCK_MODE++`]] + +[.contract] +[[IERC6372]] +=== `++IERC6372++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/interfaces/IERC6372.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/interfaces/IERC6372.sol"; +``` + +[.contract-index] +.Functions +-- +* {xref-IERC6372-clock--}[`++clock()++`] +* {xref-IERC6372-CLOCK_MODE--}[`++CLOCK_MODE()++`] + +-- + +[.contract-item] +[[IERC6372-clock--]] +==== `[.contract-item-name]#++clock++#++() β†’ uint48++` [.item-kind]#external# + +Clock used for flagging checkpoints. Can be overridden to implement timestamp based checkpoints (and voting). + +[.contract-item] +[[IERC6372-CLOCK_MODE--]] +==== `[.contract-item-name]#++CLOCK_MODE++#++() β†’ string++` [.item-kind]#external# + +Description of the clock + diff --git a/docs/modules/api/pages/metatx.adoc b/docs/modules/api/pages/metatx.adoc new file mode 100644 index 000000000..eeaaf542d --- /dev/null +++ b/docs/modules/api/pages/metatx.adoc @@ -0,0 +1,359 @@ +:github-icon: pass:[] +:xref-ERC2771Context-constructor-address-: xref:metatx.adoc#ERC2771Context-constructor-address- +:xref-ERC2771Context-trustedForwarder--: xref:metatx.adoc#ERC2771Context-trustedForwarder-- +:xref-ERC2771Context-isTrustedForwarder-address-: xref:metatx.adoc#ERC2771Context-isTrustedForwarder-address- +:xref-ERC2771Context-_msgSender--: xref:metatx.adoc#ERC2771Context-_msgSender-- +:xref-ERC2771Context-_msgData--: xref:metatx.adoc#ERC2771Context-_msgData-- +:ERC2771Context: pass:normal[xref:metatx.adoc#ERC2771Context[`ERC2771Context`]] +:xref-ERC2771Forwarder-constructor-string-: xref:metatx.adoc#ERC2771Forwarder-constructor-string- +:xref-ERC2771Forwarder-verify-struct-ERC2771Forwarder-ForwardRequestData-: xref:metatx.adoc#ERC2771Forwarder-verify-struct-ERC2771Forwarder-ForwardRequestData- +:xref-ERC2771Forwarder-execute-struct-ERC2771Forwarder-ForwardRequestData-: xref:metatx.adoc#ERC2771Forwarder-execute-struct-ERC2771Forwarder-ForwardRequestData- +:xref-ERC2771Forwarder-executeBatch-struct-ERC2771Forwarder-ForwardRequestData---address-payable-: xref:metatx.adoc#ERC2771Forwarder-executeBatch-struct-ERC2771Forwarder-ForwardRequestData---address-payable- +:xref-ERC2771Forwarder-_validate-struct-ERC2771Forwarder-ForwardRequestData-: xref:metatx.adoc#ERC2771Forwarder-_validate-struct-ERC2771Forwarder-ForwardRequestData- +:xref-ERC2771Forwarder-_recoverForwardRequestSigner-struct-ERC2771Forwarder-ForwardRequestData-: xref:metatx.adoc#ERC2771Forwarder-_recoverForwardRequestSigner-struct-ERC2771Forwarder-ForwardRequestData- +:xref-ERC2771Forwarder-_execute-struct-ERC2771Forwarder-ForwardRequestData-bool-: xref:metatx.adoc#ERC2771Forwarder-_execute-struct-ERC2771Forwarder-ForwardRequestData-bool- +:xref-Nonces-nonces-address-: xref:utils.adoc#Nonces-nonces-address- +:xref-Nonces-_useNonce-address-: xref:utils.adoc#Nonces-_useNonce-address- +:xref-Nonces-_useCheckedNonce-address-uint256-: xref:utils.adoc#Nonces-_useCheckedNonce-address-uint256- +:xref-EIP712-_domainSeparatorV4--: xref:utils.adoc#EIP712-_domainSeparatorV4-- +:xref-EIP712-_hashTypedDataV4-bytes32-: xref:utils.adoc#EIP712-_hashTypedDataV4-bytes32- +:xref-EIP712-eip712Domain--: xref:utils.adoc#EIP712-eip712Domain-- +:xref-EIP712-_EIP712Name--: xref:utils.adoc#EIP712-_EIP712Name-- +:xref-EIP712-_EIP712Version--: xref:utils.adoc#EIP712-_EIP712Version-- +:xref-ERC2771Forwarder-ExecutedForwardRequest-address-uint256-bool-: xref:metatx.adoc#ERC2771Forwarder-ExecutedForwardRequest-address-uint256-bool- +:xref-IERC5267-EIP712DomainChanged--: xref:interfaces.adoc#IERC5267-EIP712DomainChanged-- +:xref-ERC2771Forwarder-ERC2771ForwarderInvalidSigner-address-address-: xref:metatx.adoc#ERC2771Forwarder-ERC2771ForwarderInvalidSigner-address-address- +:xref-ERC2771Forwarder-ERC2771ForwarderMismatchedValue-uint256-uint256-: xref:metatx.adoc#ERC2771Forwarder-ERC2771ForwarderMismatchedValue-uint256-uint256- +:xref-ERC2771Forwarder-ERC2771ForwarderExpiredRequest-uint48-: xref:metatx.adoc#ERC2771Forwarder-ERC2771ForwarderExpiredRequest-uint48- +:xref-ERC2771Forwarder-ERC2771UntrustfulTarget-address-address-: xref:metatx.adoc#ERC2771Forwarder-ERC2771UntrustfulTarget-address-address- +:xref-Nonces-InvalidAccountNonce-address-uint256-: xref:utils.adoc#Nonces-InvalidAccountNonce-address-uint256- +:EIP712-constructor: pass:normal[xref:utils.adoc#EIP712-constructor-string-string-[`EIP712.constructor`]] +:ECDSA-tryRecover: pass:normal[xref:utils.adoc#ECDSA-tryRecover-bytes32-uint8-bytes32-bytes32-[`ECDSA.tryRecover`]] += Meta Transactions + +[.readme-notice] +NOTE: This document is better viewed at https://docs.openzeppelin.com/contracts/api/metatx + +== Core + +:constructor: pass:normal[xref:#ERC2771Context-constructor-address-[`++constructor++`]] +:trustedForwarder: pass:normal[xref:#ERC2771Context-trustedForwarder--[`++trustedForwarder++`]] +:isTrustedForwarder: pass:normal[xref:#ERC2771Context-isTrustedForwarder-address-[`++isTrustedForwarder++`]] +:_msgSender: pass:normal[xref:#ERC2771Context-_msgSender--[`++_msgSender++`]] +:_msgData: pass:normal[xref:#ERC2771Context-_msgData--[`++_msgData++`]] + +[.contract] +[[ERC2771Context]] +=== `++ERC2771Context++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/metatx/ERC2771Context.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/metatx/ERC2771Context.sol"; +``` + +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`. + +[.contract-index] +.Functions +-- +* {xref-ERC2771Context-constructor-address-}[`++constructor(trustedForwarder_)++`] +* {xref-ERC2771Context-trustedForwarder--}[`++trustedForwarder()++`] +* {xref-ERC2771Context-isTrustedForwarder-address-}[`++isTrustedForwarder(forwarder)++`] +* {xref-ERC2771Context-_msgSender--}[`++_msgSender()++`] +* {xref-ERC2771Context-_msgData--}[`++_msgData()++`] + +-- + +[.contract-item] +[[ERC2771Context-constructor-address-]] +==== `[.contract-item-name]#++constructor++#++(address trustedForwarder_)++` [.item-kind]#internal# + +[.contract-item] +[[ERC2771Context-trustedForwarder--]] +==== `[.contract-item-name]#++trustedForwarder++#++() β†’ address++` [.item-kind]#public# + +Returns the address of the trusted forwarder. + +[.contract-item] +[[ERC2771Context-isTrustedForwarder-address-]] +==== `[.contract-item-name]#++isTrustedForwarder++#++(address forwarder) β†’ bool++` [.item-kind]#public# + +Indicates whether any particular address is the trusted forwarder. + +[.contract-item] +[[ERC2771Context-_msgSender--]] +==== `[.contract-item-name]#++_msgSender++#++() β†’ address sender++` [.item-kind]#internal# + +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). + +[.contract-item] +[[ERC2771Context-_msgData--]] +==== `[.contract-item-name]#++_msgData++#++() β†’ bytes++` [.item-kind]#internal# + +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). + +== Utils + +:ForwardRequestData: pass:normal[xref:#ERC2771Forwarder-ForwardRequestData[`++ForwardRequestData++`]] +:_FORWARD_REQUEST_TYPEHASH: pass:normal[xref:#ERC2771Forwarder-_FORWARD_REQUEST_TYPEHASH-bytes32[`++_FORWARD_REQUEST_TYPEHASH++`]] +:ExecutedForwardRequest: pass:normal[xref:#ERC2771Forwarder-ExecutedForwardRequest-address-uint256-bool-[`++ExecutedForwardRequest++`]] +:ERC2771ForwarderInvalidSigner: pass:normal[xref:#ERC2771Forwarder-ERC2771ForwarderInvalidSigner-address-address-[`++ERC2771ForwarderInvalidSigner++`]] +:ERC2771ForwarderMismatchedValue: pass:normal[xref:#ERC2771Forwarder-ERC2771ForwarderMismatchedValue-uint256-uint256-[`++ERC2771ForwarderMismatchedValue++`]] +:ERC2771ForwarderExpiredRequest: pass:normal[xref:#ERC2771Forwarder-ERC2771ForwarderExpiredRequest-uint48-[`++ERC2771ForwarderExpiredRequest++`]] +:ERC2771UntrustfulTarget: pass:normal[xref:#ERC2771Forwarder-ERC2771UntrustfulTarget-address-address-[`++ERC2771UntrustfulTarget++`]] +:constructor: pass:normal[xref:#ERC2771Forwarder-constructor-string-[`++constructor++`]] +:verify: pass:normal[xref:#ERC2771Forwarder-verify-struct-ERC2771Forwarder-ForwardRequestData-[`++verify++`]] +:execute: pass:normal[xref:#ERC2771Forwarder-execute-struct-ERC2771Forwarder-ForwardRequestData-[`++execute++`]] +:executeBatch: pass:normal[xref:#ERC2771Forwarder-executeBatch-struct-ERC2771Forwarder-ForwardRequestData---address-payable-[`++executeBatch++`]] +:_validate: pass:normal[xref:#ERC2771Forwarder-_validate-struct-ERC2771Forwarder-ForwardRequestData-[`++_validate++`]] +:_recoverForwardRequestSigner: pass:normal[xref:#ERC2771Forwarder-_recoverForwardRequestSigner-struct-ERC2771Forwarder-ForwardRequestData-[`++_recoverForwardRequestSigner++`]] +:_execute: pass:normal[xref:#ERC2771Forwarder-_execute-struct-ERC2771Forwarder-ForwardRequestData-bool-[`++_execute++`]] + +[.contract] +[[ERC2771Forwarder]] +=== `++ERC2771Forwarder++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/metatx/ERC2771Forwarder.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/metatx/ERC2771Forwarder.sol"; +``` + +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. + +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. + +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 +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-index] +.Functions +-- +* {xref-ERC2771Forwarder-constructor-string-}[`++constructor(name)++`] +* {xref-ERC2771Forwarder-verify-struct-ERC2771Forwarder-ForwardRequestData-}[`++verify(request)++`] +* {xref-ERC2771Forwarder-execute-struct-ERC2771Forwarder-ForwardRequestData-}[`++execute(request)++`] +* {xref-ERC2771Forwarder-executeBatch-struct-ERC2771Forwarder-ForwardRequestData---address-payable-}[`++executeBatch(requests, refundReceiver)++`] +* {xref-ERC2771Forwarder-_validate-struct-ERC2771Forwarder-ForwardRequestData-}[`++_validate(request)++`] +* {xref-ERC2771Forwarder-_recoverForwardRequestSigner-struct-ERC2771Forwarder-ForwardRequestData-}[`++_recoverForwardRequestSigner(request)++`] +* {xref-ERC2771Forwarder-_execute-struct-ERC2771Forwarder-ForwardRequestData-bool-}[`++_execute(request, requireValidRequest)++`] + +[.contract-subindex-inherited] +.Nonces +* {xref-Nonces-nonces-address-}[`++nonces(owner)++`] +* {xref-Nonces-_useNonce-address-}[`++_useNonce(owner)++`] +* {xref-Nonces-_useCheckedNonce-address-uint256-}[`++_useCheckedNonce(owner, nonce)++`] + +[.contract-subindex-inherited] +.EIP712 +* {xref-EIP712-_domainSeparatorV4--}[`++_domainSeparatorV4()++`] +* {xref-EIP712-_hashTypedDataV4-bytes32-}[`++_hashTypedDataV4(structHash)++`] +* {xref-EIP712-eip712Domain--}[`++eip712Domain()++`] +* {xref-EIP712-_EIP712Name--}[`++_EIP712Name()++`] +* {xref-EIP712-_EIP712Version--}[`++_EIP712Version()++`] + +[.contract-subindex-inherited] +.IERC5267 + +-- + +[.contract-index] +.Events +-- +* {xref-ERC2771Forwarder-ExecutedForwardRequest-address-uint256-bool-}[`++ExecutedForwardRequest(signer, nonce, success)++`] + +[.contract-subindex-inherited] +.Nonces + +[.contract-subindex-inherited] +.EIP712 + +[.contract-subindex-inherited] +.IERC5267 +* {xref-IERC5267-EIP712DomainChanged--}[`++EIP712DomainChanged()++`] + +-- + +[.contract-index] +.Errors +-- +* {xref-ERC2771Forwarder-ERC2771ForwarderInvalidSigner-address-address-}[`++ERC2771ForwarderInvalidSigner(signer, from)++`] +* {xref-ERC2771Forwarder-ERC2771ForwarderMismatchedValue-uint256-uint256-}[`++ERC2771ForwarderMismatchedValue(requestedValue, msgValue)++`] +* {xref-ERC2771Forwarder-ERC2771ForwarderExpiredRequest-uint48-}[`++ERC2771ForwarderExpiredRequest(deadline)++`] +* {xref-ERC2771Forwarder-ERC2771UntrustfulTarget-address-address-}[`++ERC2771UntrustfulTarget(target, forwarder)++`] + +[.contract-subindex-inherited] +.Nonces +* {xref-Nonces-InvalidAccountNonce-address-uint256-}[`++InvalidAccountNonce(account, currentNonce)++`] + +[.contract-subindex-inherited] +.EIP712 + +[.contract-subindex-inherited] +.IERC5267 + +-- + +[.contract-item] +[[ERC2771Forwarder-constructor-string-]] +==== `[.contract-item-name]#++constructor++#++(string name)++` [.item-kind]#public# + +See {EIP712-constructor}. + +[.contract-item] +[[ERC2771Forwarder-verify-struct-ERC2771Forwarder-ForwardRequestData-]] +==== `[.contract-item-name]#++verify++#++(struct ERC2771Forwarder.ForwardRequestData request) β†’ bool++` [.item-kind]#public# + +Returns `true` if a request is valid for a provided `signature` at the current block timestamp. + +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. + +[.contract-item] +[[ERC2771Forwarder-execute-struct-ERC2771Forwarder-ForwardRequestData-]] +==== `[.contract-item-name]#++execute++#++(struct ERC2771Forwarder.ForwardRequestData request)++` [.item-kind]#public# + +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}. + +[.contract-item] +[[ERC2771Forwarder-executeBatch-struct-ERC2771Forwarder-ForwardRequestData---address-payable-]] +==== `[.contract-item-name]#++executeBatch++#++(struct ERC2771Forwarder.ForwardRequestData[] requests, address payable refundReceiver)++` [.item-kind]#public# + +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. + +[.contract-item] +[[ERC2771Forwarder-_validate-struct-ERC2771Forwarder-ForwardRequestData-]] +==== `[.contract-item-name]#++_validate++#++(struct ERC2771Forwarder.ForwardRequestData request) β†’ bool isTrustedForwarder, bool active, bool signerMatch, address signer++` [.item-kind]#internal# + +Validates if the provided request can be executed at current block timestamp with +the given `request.signature` on behalf of `request.signer`. + +[.contract-item] +[[ERC2771Forwarder-_recoverForwardRequestSigner-struct-ERC2771Forwarder-ForwardRequestData-]] +==== `[.contract-item-name]#++_recoverForwardRequestSigner++#++(struct ERC2771Forwarder.ForwardRequestData request) β†’ bool, address++` [.item-kind]#internal# + +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. + +[.contract-item] +[[ERC2771Forwarder-_execute-struct-ERC2771Forwarder-ForwardRequestData-bool-]] +==== `[.contract-item-name]#++_execute++#++(struct ERC2771Forwarder.ForwardRequestData request, bool requireValidRequest) β†’ bool success++` [.item-kind]#internal# + +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. + +[.contract-item] +[[ERC2771Forwarder-ExecutedForwardRequest-address-uint256-bool-]] +==== `[.contract-item-name]#++ExecutedForwardRequest++#++(address indexed signer, uint256 nonce, bool success)++` [.item-kind]#event# + +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. + +[.contract-item] +[[ERC2771Forwarder-ERC2771ForwarderInvalidSigner-address-address-]] +==== `[.contract-item-name]#++ERC2771ForwarderInvalidSigner++#++(address signer, address from)++` [.item-kind]#error# + +The request `from` doesn't match with the recovered `signer`. + +[.contract-item] +[[ERC2771Forwarder-ERC2771ForwarderMismatchedValue-uint256-uint256-]] +==== `[.contract-item-name]#++ERC2771ForwarderMismatchedValue++#++(uint256 requestedValue, uint256 msgValue)++` [.item-kind]#error# + +The `requestedValue` doesn't match with the available `msgValue`. + +[.contract-item] +[[ERC2771Forwarder-ERC2771ForwarderExpiredRequest-uint48-]] +==== `[.contract-item-name]#++ERC2771ForwarderExpiredRequest++#++(uint48 deadline)++` [.item-kind]#error# + +The request `deadline` has expired. + +[.contract-item] +[[ERC2771Forwarder-ERC2771UntrustfulTarget-address-address-]] +==== `[.contract-item-name]#++ERC2771UntrustfulTarget++#++(address target, address forwarder)++` [.item-kind]#error# + +The request target doesn't trust the `forwarder`. + diff --git a/docs/modules/api/pages/proxy.adoc b/docs/modules/api/pages/proxy.adoc new file mode 100644 index 000000000..775612ce7 --- /dev/null +++ b/docs/modules/api/pages/proxy.adoc @@ -0,0 +1,1251 @@ +:github-icon: pass:[] +:Proxy: pass:normal[xref:proxy.adoc#Proxy[`Proxy`]] +:ERC1967Utils: pass:normal[xref:proxy.adoc#ERC1967Utils[`ERC1967Utils`]] +:ERC1967Proxy: pass:normal[xref:proxy.adoc#ERC1967Proxy[`ERC1967Proxy`]] +:TransparentUpgradeableProxy: pass:normal[xref:proxy.adoc#TransparentUpgradeableProxy[`TransparentUpgradeableProxy`]] +:UUPSUpgradeable: pass:normal[xref:proxy.adoc#UUPSUpgradeable[`UUPSUpgradeable`]] +:BeaconProxy: pass:normal[xref:proxy.adoc#BeaconProxy[`BeaconProxy`]] +:UpgradeableBeacon: pass:normal[xref:proxy.adoc#UpgradeableBeacon[`UpgradeableBeacon`]] +:BeaconProxy: pass:normal[xref:proxy.adoc#BeaconProxy[`BeaconProxy`]] +:Clones: pass:normal[xref:proxy.adoc#Clones[`Clones`]] +:TransparentUpgradeableProxy: pass:normal[xref:proxy.adoc#TransparentUpgradeableProxy[`TransparentUpgradeableProxy`]] +:ERC1967Proxy: pass:normal[xref:proxy.adoc#ERC1967Proxy[`ERC1967Proxy`]] +:UUPSUpgradeable: pass:normal[xref:proxy.adoc#UUPSUpgradeable[`UUPSUpgradeable`]] +:xref-UUPSUpgradeable-_authorizeUpgrade-address-: xref:proxy.adoc#UUPSUpgradeable-_authorizeUpgrade-address- +:TransparentUpgradeableProxy: pass:normal[xref:proxy.adoc#TransparentUpgradeableProxy[`TransparentUpgradeableProxy`]] +:UUPSUpgradeable: pass:normal[xref:proxy.adoc#UUPSUpgradeable[`UUPSUpgradeable`]] +:xref-Proxy-_delegate-address-: xref:proxy.adoc#Proxy-_delegate-address- +:xref-Proxy-_implementation--: xref:proxy.adoc#Proxy-_implementation-- +:xref-Proxy-_fallback--: xref:proxy.adoc#Proxy-_fallback-- +:xref-Proxy-fallback--: xref:proxy.adoc#Proxy-fallback-- +:xref-ERC1967Proxy-constructor-address-bytes-: xref:proxy.adoc#ERC1967Proxy-constructor-address-bytes- +:xref-ERC1967Proxy-_implementation--: xref:proxy.adoc#ERC1967Proxy-_implementation-- +:xref-Proxy-_delegate-address-: xref:proxy.adoc#Proxy-_delegate-address- +:xref-Proxy-_fallback--: xref:proxy.adoc#Proxy-_fallback-- +:xref-Proxy-fallback--: xref:proxy.adoc#Proxy-fallback-- +:xref-ERC1967Utils-getImplementation--: xref:proxy.adoc#ERC1967Utils-getImplementation-- +:xref-ERC1967Utils-upgradeToAndCall-address-bytes-: xref:proxy.adoc#ERC1967Utils-upgradeToAndCall-address-bytes- +:xref-ERC1967Utils-getAdmin--: xref:proxy.adoc#ERC1967Utils-getAdmin-- +:xref-ERC1967Utils-changeAdmin-address-: xref:proxy.adoc#ERC1967Utils-changeAdmin-address- +:xref-ERC1967Utils-getBeacon--: xref:proxy.adoc#ERC1967Utils-getBeacon-- +:xref-ERC1967Utils-upgradeBeaconToAndCall-address-bytes-: xref:proxy.adoc#ERC1967Utils-upgradeBeaconToAndCall-address-bytes- +:xref-ERC1967Utils-Upgraded-address-: xref:proxy.adoc#ERC1967Utils-Upgraded-address- +:xref-ERC1967Utils-AdminChanged-address-address-: xref:proxy.adoc#ERC1967Utils-AdminChanged-address-address- +:xref-ERC1967Utils-BeaconUpgraded-address-: xref:proxy.adoc#ERC1967Utils-BeaconUpgraded-address- +:xref-ERC1967Utils-ERC1967InvalidImplementation-address-: xref:proxy.adoc#ERC1967Utils-ERC1967InvalidImplementation-address- +:xref-ERC1967Utils-ERC1967InvalidAdmin-address-: xref:proxy.adoc#ERC1967Utils-ERC1967InvalidAdmin-address- +:xref-ERC1967Utils-ERC1967InvalidBeacon-address-: xref:proxy.adoc#ERC1967Utils-ERC1967InvalidBeacon-address- +:xref-ERC1967Utils-ERC1967NonPayable--: xref:proxy.adoc#ERC1967Utils-ERC1967NonPayable-- +:IERC1967-Upgraded: pass:normal[xref:interfaces.adoc#IERC1967-Upgraded-address-[`IERC1967.Upgraded`]] +:IERC1967-AdminChanged: pass:normal[xref:interfaces.adoc#IERC1967-AdminChanged-address-address-[`IERC1967.AdminChanged`]] +:IERC1967-BeaconUpgraded: pass:normal[xref:interfaces.adoc#IERC1967-BeaconUpgraded-address-[`IERC1967.BeaconUpgraded`]] +:BeaconProxy: pass:normal[xref:proxy.adoc#BeaconProxy[`BeaconProxy`]] +:ProxyAdmin: pass:normal[xref:proxy.adoc#ProxyAdmin[`ProxyAdmin`]] +:ITransparentUpgradeableProxy-upgradeToAndCall: pass:normal[xref:proxy.adoc#ITransparentUpgradeableProxy-upgradeToAndCall-address-bytes-[`ITransparentUpgradeableProxy.upgradeToAndCall`]] +:ProxyAdmin: pass:normal[xref:proxy.adoc#ProxyAdmin[`ProxyAdmin`]] +:Context: pass:normal[xref:utils.adoc#Context[`Context`]] +:ProxyAdmin: pass:normal[xref:proxy.adoc#ProxyAdmin[`ProxyAdmin`]] +:ITransparentUpgradeableProxy: pass:normal[xref:proxy.adoc#ITransparentUpgradeableProxy[`ITransparentUpgradeableProxy`]] +:xref-TransparentUpgradeableProxy-constructor-address-address-bytes-: xref:proxy.adoc#TransparentUpgradeableProxy-constructor-address-address-bytes- +:xref-TransparentUpgradeableProxy-_proxyAdmin--: xref:proxy.adoc#TransparentUpgradeableProxy-_proxyAdmin-- +:xref-TransparentUpgradeableProxy-_fallback--: xref:proxy.adoc#TransparentUpgradeableProxy-_fallback-- +:xref-ERC1967Proxy-_implementation--: xref:proxy.adoc#ERC1967Proxy-_implementation-- +:xref-Proxy-_delegate-address-: xref:proxy.adoc#Proxy-_delegate-address- +:xref-Proxy-fallback--: xref:proxy.adoc#Proxy-fallback-- +:xref-TransparentUpgradeableProxy-ProxyDeniedAdminAccess--: xref:proxy.adoc#TransparentUpgradeableProxy-ProxyDeniedAdminAccess-- +:ProxyAdmin: pass:normal[xref:proxy.adoc#ProxyAdmin[`ProxyAdmin`]] +:ERC1967Proxy-constructor: pass:normal[xref:proxy.adoc#ERC1967Proxy-constructor-address-bytes-[`ERC1967Proxy.constructor`]] +:TransparentUpgradeableProxy: pass:normal[xref:proxy.adoc#TransparentUpgradeableProxy[`TransparentUpgradeableProxy`]] +:TransparentUpgradeableProxy: pass:normal[xref:proxy.adoc#TransparentUpgradeableProxy[`TransparentUpgradeableProxy`]] +:xref-ProxyAdmin-constructor-address-: xref:proxy.adoc#ProxyAdmin-constructor-address- +:xref-ProxyAdmin-upgradeAndCall-contract-ITransparentUpgradeableProxy-address-bytes-: xref:proxy.adoc#ProxyAdmin-upgradeAndCall-contract-ITransparentUpgradeableProxy-address-bytes- +:xref-Ownable-owner--: xref:access.adoc#Ownable-owner-- +:xref-Ownable-_checkOwner--: xref:access.adoc#Ownable-_checkOwner-- +:xref-Ownable-renounceOwnership--: xref:access.adoc#Ownable-renounceOwnership-- +:xref-Ownable-transferOwnership-address-: xref:access.adoc#Ownable-transferOwnership-address- +:xref-Ownable-_transferOwnership-address-: xref:access.adoc#Ownable-_transferOwnership-address- +:xref-Ownable-OwnershipTransferred-address-address-: xref:access.adoc#Ownable-OwnershipTransferred-address-address- +:xref-Ownable-OwnableUnauthorizedAccount-address-: xref:access.adoc#Ownable-OwnableUnauthorizedAccount-address- +:xref-Ownable-OwnableInvalidOwner-address-: xref:access.adoc#Ownable-OwnableInvalidOwner-address- +:TransparentUpgradeableProxy-_dispatchUpgradeToAndCall: pass:normal[xref:proxy.adoc#TransparentUpgradeableProxy-_dispatchUpgradeToAndCall--[`TransparentUpgradeableProxy._dispatchUpgradeToAndCall`]] +:UpgradeableBeacon: pass:normal[xref:proxy.adoc#UpgradeableBeacon[`UpgradeableBeacon`]] +:xref-BeaconProxy-constructor-address-bytes-: xref:proxy.adoc#BeaconProxy-constructor-address-bytes- +:xref-BeaconProxy-_implementation--: xref:proxy.adoc#BeaconProxy-_implementation-- +:xref-BeaconProxy-_getBeacon--: xref:proxy.adoc#BeaconProxy-_getBeacon-- +:xref-Proxy-_delegate-address-: xref:proxy.adoc#Proxy-_delegate-address- +:xref-Proxy-_fallback--: xref:proxy.adoc#Proxy-_fallback-- +:xref-Proxy-fallback--: xref:proxy.adoc#Proxy-fallback-- +:IBeacon: pass:normal[xref:proxy.adoc#IBeacon[`IBeacon`]] +:BeaconProxy: pass:normal[xref:proxy.adoc#BeaconProxy[`BeaconProxy`]] +:xref-IBeacon-implementation--: xref:proxy.adoc#IBeacon-implementation-- +:UpgradeableBeacon: pass:normal[xref:proxy.adoc#UpgradeableBeacon[`UpgradeableBeacon`]] +:BeaconProxy: pass:normal[xref:proxy.adoc#BeaconProxy[`BeaconProxy`]] +:xref-UpgradeableBeacon-constructor-address-address-: xref:proxy.adoc#UpgradeableBeacon-constructor-address-address- +:xref-UpgradeableBeacon-implementation--: xref:proxy.adoc#UpgradeableBeacon-implementation-- +:xref-UpgradeableBeacon-upgradeTo-address-: xref:proxy.adoc#UpgradeableBeacon-upgradeTo-address- +:xref-Ownable-owner--: xref:access.adoc#Ownable-owner-- +:xref-Ownable-_checkOwner--: xref:access.adoc#Ownable-_checkOwner-- +:xref-Ownable-renounceOwnership--: xref:access.adoc#Ownable-renounceOwnership-- +:xref-Ownable-transferOwnership-address-: xref:access.adoc#Ownable-transferOwnership-address- +:xref-Ownable-_transferOwnership-address-: xref:access.adoc#Ownable-_transferOwnership-address- +:xref-UpgradeableBeacon-Upgraded-address-: xref:proxy.adoc#UpgradeableBeacon-Upgraded-address- +:xref-Ownable-OwnershipTransferred-address-address-: xref:access.adoc#Ownable-OwnershipTransferred-address-address- +:xref-UpgradeableBeacon-BeaconInvalidImplementation-address-: xref:proxy.adoc#UpgradeableBeacon-BeaconInvalidImplementation-address- +:xref-Ownable-OwnableUnauthorizedAccount-address-: xref:access.adoc#Ownable-OwnableUnauthorizedAccount-address- +:xref-Ownable-OwnableInvalidOwner-address-: xref:access.adoc#Ownable-OwnableInvalidOwner-address- +:xref-Clones-clone-address-: xref:proxy.adoc#Clones-clone-address- +:xref-Clones-cloneDeterministic-address-bytes32-: xref:proxy.adoc#Clones-cloneDeterministic-address-bytes32- +:xref-Clones-predictDeterministicAddress-address-bytes32-address-: xref:proxy.adoc#Clones-predictDeterministicAddress-address-bytes32-address- +:xref-Clones-predictDeterministicAddress-address-bytes32-: xref:proxy.adoc#Clones-predictDeterministicAddress-address-bytes32- +:xref-Clones-ERC1167FailedCreateClone--: xref:proxy.adoc#Clones-ERC1167FailedCreateClone-- +:Clones-cloneDeterministic: pass:normal[xref:proxy.adoc#Clones-cloneDeterministic-address-bytes32-[`Clones.cloneDeterministic`]] +:Clones-cloneDeterministic: pass:normal[xref:proxy.adoc#Clones-cloneDeterministic-address-bytes32-[`Clones.cloneDeterministic`]] +:ERC1967Proxy-constructor: pass:normal[xref:proxy.adoc#ERC1967Proxy-constructor-address-bytes-[`ERC1967Proxy.constructor`]] +:xref-Initializable-initializer--: xref:proxy.adoc#Initializable-initializer-- +:xref-Initializable-reinitializer-uint64-: xref:proxy.adoc#Initializable-reinitializer-uint64- +:xref-Initializable-onlyInitializing--: xref:proxy.adoc#Initializable-onlyInitializing-- +:xref-Initializable-_checkInitializing--: xref:proxy.adoc#Initializable-_checkInitializing-- +:xref-Initializable-_disableInitializers--: xref:proxy.adoc#Initializable-_disableInitializers-- +:xref-Initializable-_getInitializedVersion--: xref:proxy.adoc#Initializable-_getInitializedVersion-- +:xref-Initializable-_isInitializing--: xref:proxy.adoc#Initializable-_isInitializing-- +:xref-Initializable-Initialized-uint64-: xref:proxy.adoc#Initializable-Initialized-uint64- +:xref-Initializable-InvalidInitialization--: xref:proxy.adoc#Initializable-InvalidInitialization-- +:xref-Initializable-NotInitializing--: xref:proxy.adoc#Initializable-NotInitializing-- +:ERC1967Proxy: pass:normal[xref:proxy.adoc#ERC1967Proxy[`ERC1967Proxy`]] +:xref-UUPSUpgradeable-onlyProxy--: xref:proxy.adoc#UUPSUpgradeable-onlyProxy-- +:xref-UUPSUpgradeable-notDelegated--: xref:proxy.adoc#UUPSUpgradeable-notDelegated-- +:xref-UUPSUpgradeable-proxiableUUID--: xref:proxy.adoc#UUPSUpgradeable-proxiableUUID-- +:xref-UUPSUpgradeable-upgradeToAndCall-address-bytes-: xref:proxy.adoc#UUPSUpgradeable-upgradeToAndCall-address-bytes- +:xref-UUPSUpgradeable-_checkProxy--: xref:proxy.adoc#UUPSUpgradeable-_checkProxy-- +:xref-UUPSUpgradeable-_checkNotDelegated--: xref:proxy.adoc#UUPSUpgradeable-_checkNotDelegated-- +:xref-UUPSUpgradeable-_authorizeUpgrade-address-: xref:proxy.adoc#UUPSUpgradeable-_authorizeUpgrade-address- +:xref-UUPSUpgradeable-UUPSUnauthorizedCallContext--: xref:proxy.adoc#UUPSUpgradeable-UUPSUnauthorizedCallContext-- +:xref-UUPSUpgradeable-UUPSUnsupportedProxiableUUID-bytes32-: xref:proxy.adoc#UUPSUpgradeable-UUPSUnsupportedProxiableUUID-bytes32- +:Ownable-onlyOwner: pass:normal[xref:access.adoc#Ownable-onlyOwner--[`Ownable.onlyOwner`]] += Proxies + +[.readme-notice] +NOTE: This document is better viewed at https://docs.openzeppelin.com/contracts/api/proxy + +This is a low-level set of contracts implementing different proxy patterns with and without upgradeability. For an in-depth overview of this pattern check out the xref:upgrades-plugins::proxies.adoc[Proxy Upgrade Pattern] page. + +Most of the proxies below are built on an abstract base contract. + +- {Proxy}: Abstract contract implementing the core delegation functionality. + +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. + +- {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 <>. + +- {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. + +A different family of proxies are beacon proxies. This pattern, popularized by Dharma, allows multiple proxies to be upgraded to a different implementation in a single transaction. + +- {BeaconProxy}: A proxy that retrieves its implementation from a beacon contract. +- {UpgradeableBeacon}: A beacon contract with a built in admin that can upgrade the {BeaconProxy} pointing to it. + +In this pattern, the proxy contract doesn't hold the implementation address in storage like an ERC1967 proxy. Instead, the address is stored in a separate beacon contract. The `upgrade` operations are sent to the beacon instead of to the proxy contract, and all proxies that follow that beacon are automatically upgraded. + +Outside the realm of upgradeability, proxies can also be useful to make cheap contract clones, such as those created by an on-chain factory contract that creates many instances of the same contract. These instances are designed to be both cheap to deploy, and cheap to call. + +- {Clones}: A library that can deploy cheap minimal non-upgradeable proxies. + +[[transparent-vs-uups]] +== Transparent vs UUPS Proxies + +The original proxies included in OpenZeppelin followed the https://blog.openzeppelin.com/the-transparent-proxy-pattern/[Transparent Proxy Pattern]. While this pattern is still provided, our recommendation is now shifting towards UUPS proxies, which are both lightweight and versatile. The name UUPS comes from https://eips.ethereum.org/EIPS/eip-1822[EIP1822], which first documented the pattern. + +While both of these share the same interface for upgrades, in UUPS proxies the upgrade is handled by the implementation, and can eventually be removed. Transparent proxies, on the other hand, include the upgrade and admin logic in the proxy itself. This means {TransparentUpgradeableProxy} is more expensive to deploy than what is possible with UUPS proxies. + +UUPS proxies are implemented using an {ERC1967Proxy}. Note that this proxy is not by itself upgradeable. It is the role of the implementation to include, alongside the contract's logic, all the code necessary to update the implementation's address that is stored at a specific slot in the proxy's storage space. This is where the {UUPSUpgradeable} contract comes in. Inheriting from it (and overriding the {xref-UUPSUpgradeable-_authorizeUpgrade-address-}[`_authorizeUpgrade`] function with the relevant access control mechanism) will turn your contract into a UUPS compliant implementation. + +Note that since both proxies use the same storage slot for the implementation address, using a UUPS compliant implementation with a {TransparentUpgradeableProxy} might allow non-admins to perform upgrade operations. + +By default, the upgrade functionality included in {UUPSUpgradeable} contains a security mechanism that will prevent any upgrades to a non UUPS compliant implementation. This prevents upgrades to an implementation contract that wouldn't contain the necessary upgrade mechanism, as it would lock the upgradeability of the proxy forever. This security mechanism can be bypassed by either of: + +- Adding a flag mechanism in the implementation that will disable the upgrade function when triggered. +- Upgrading to an implementation that features an upgrade mechanism without the additional security check, and then upgrading again to another implementation without the upgrade mechanism. + +The current implementation of this security mechanism uses https://eips.ethereum.org/EIPS/eip-1822[EIP1822] to detect the storage slot used by the implementation. A previous implementation, now deprecated, relied on a rollback check. It is possible to upgrade from a contract using the old mechanism to a new one. The inverse is however not possible, as old implementations (before version 4.5) did not include the `ERC1822` interface. + +== Core + +:_delegate: pass:normal[xref:#Proxy-_delegate-address-[`++_delegate++`]] +:_implementation: pass:normal[xref:#Proxy-_implementation--[`++_implementation++`]] +:_fallback: pass:normal[xref:#Proxy-_fallback--[`++_fallback++`]] +:fallback: pass:normal[xref:#Proxy-fallback--[`++fallback++`]] + +[.contract] +[[Proxy]] +=== `++Proxy++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/proxy/Proxy.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/proxy/Proxy.sol"; +``` + +This abstract contract provides a fallback function that delegates all calls to another contract using the EVM +instruction `delegatecall`. We refer to the second contract as the _implementation_ behind the proxy, and it has to +be specified by overriding the virtual {_implementation} function. + +Additionally, delegation to the implementation can be triggered manually through the {_fallback} function, or to a +different contract through the {_delegate} function. + +The success and return data of the delegated call will be returned back to the caller of the proxy. + +[.contract-index] +.Functions +-- +* {xref-Proxy-_delegate-address-}[`++_delegate(implementation)++`] +* {xref-Proxy-_implementation--}[`++_implementation()++`] +* {xref-Proxy-_fallback--}[`++_fallback()++`] +* {xref-Proxy-fallback--}[`++fallback()++`] + +-- + +[.contract-item] +[[Proxy-_delegate-address-]] +==== `[.contract-item-name]#++_delegate++#++(address implementation)++` [.item-kind]#internal# + +Delegates the current call to `implementation`. + +This function does not return to its internal call site, it will return directly to the external caller. + +[.contract-item] +[[Proxy-_implementation--]] +==== `[.contract-item-name]#++_implementation++#++() β†’ address++` [.item-kind]#internal# + +This is a virtual function that should be overridden so it returns the address to which the fallback +function and {_fallback} should delegate. + +[.contract-item] +[[Proxy-_fallback--]] +==== `[.contract-item-name]#++_fallback++#++()++` [.item-kind]#internal# + +Delegates the current call to the address returned by `_implementation()`. + +This function does not return to its internal call site, it will return directly to the external caller. + +[.contract-item] +[[Proxy-fallback--]] +==== `[.contract-item-name]#++fallback++#++()++` [.item-kind]#external# + +Fallback function that delegates calls to the address returned by `_implementation()`. Will run if no other +function in the contract matches the call data. + +== ERC1967 + +:constructor: pass:normal[xref:#ERC1967Proxy-constructor-address-bytes-[`++constructor++`]] +:_implementation: pass:normal[xref:#ERC1967Proxy-_implementation--[`++_implementation++`]] + +[.contract] +[[ERC1967Proxy]] +=== `++ERC1967Proxy++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/proxy/ERC1967/ERC1967Proxy.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +``` + +This contract implements an upgradeable proxy. It is upgradeable because calls are delegated to an +implementation address that can be changed. This address is stored in storage in the location specified by +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-index] +.Functions +-- +* {xref-ERC1967Proxy-constructor-address-bytes-}[`++constructor(implementation, _data)++`] +* {xref-ERC1967Proxy-_implementation--}[`++_implementation()++`] + +[.contract-subindex-inherited] +.Proxy +* {xref-Proxy-_delegate-address-}[`++_delegate(implementation)++`] +* {xref-Proxy-_fallback--}[`++_fallback()++`] +* {xref-Proxy-fallback--}[`++fallback()++`] + +-- + +[.contract-item] +[[ERC1967Proxy-constructor-address-bytes-]] +==== `[.contract-item-name]#++constructor++#++(address implementation, bytes _data)++` [.item-kind]#public# + +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 `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. + +[.contract-item] +[[ERC1967Proxy-_implementation--]] +==== `[.contract-item-name]#++_implementation++#++() β†’ address++` [.item-kind]#internal# + +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` + +:Upgraded: pass:normal[xref:#ERC1967Utils-Upgraded-address-[`++Upgraded++`]] +:AdminChanged: pass:normal[xref:#ERC1967Utils-AdminChanged-address-address-[`++AdminChanged++`]] +:BeaconUpgraded: pass:normal[xref:#ERC1967Utils-BeaconUpgraded-address-[`++BeaconUpgraded++`]] +:IMPLEMENTATION_SLOT: pass:normal[xref:#ERC1967Utils-IMPLEMENTATION_SLOT-bytes32[`++IMPLEMENTATION_SLOT++`]] +:ERC1967InvalidImplementation: pass:normal[xref:#ERC1967Utils-ERC1967InvalidImplementation-address-[`++ERC1967InvalidImplementation++`]] +:ERC1967InvalidAdmin: pass:normal[xref:#ERC1967Utils-ERC1967InvalidAdmin-address-[`++ERC1967InvalidAdmin++`]] +:ERC1967InvalidBeacon: pass:normal[xref:#ERC1967Utils-ERC1967InvalidBeacon-address-[`++ERC1967InvalidBeacon++`]] +:ERC1967NonPayable: pass:normal[xref:#ERC1967Utils-ERC1967NonPayable--[`++ERC1967NonPayable++`]] +:getImplementation: pass:normal[xref:#ERC1967Utils-getImplementation--[`++getImplementation++`]] +:upgradeToAndCall: pass:normal[xref:#ERC1967Utils-upgradeToAndCall-address-bytes-[`++upgradeToAndCall++`]] +:ADMIN_SLOT: pass:normal[xref:#ERC1967Utils-ADMIN_SLOT-bytes32[`++ADMIN_SLOT++`]] +:getAdmin: pass:normal[xref:#ERC1967Utils-getAdmin--[`++getAdmin++`]] +:changeAdmin: pass:normal[xref:#ERC1967Utils-changeAdmin-address-[`++changeAdmin++`]] +:BEACON_SLOT: pass:normal[xref:#ERC1967Utils-BEACON_SLOT-bytes32[`++BEACON_SLOT++`]] +:getBeacon: pass:normal[xref:#ERC1967Utils-getBeacon--[`++getBeacon++`]] +:upgradeBeaconToAndCall: pass:normal[xref:#ERC1967Utils-upgradeBeaconToAndCall-address-bytes-[`++upgradeBeaconToAndCall++`]] + +[.contract] +[[ERC1967Utils]] +=== `++ERC1967Utils++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/proxy/ERC1967/ERC1967Utils.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol"; +``` + +This abstract contract provides getters and event emitting update functions for +https://eips.ethereum.org/EIPS/eip-1967[EIP1967] slots. + +[.contract-index] +.Functions +-- +* {xref-ERC1967Utils-getImplementation--}[`++getImplementation()++`] +* {xref-ERC1967Utils-upgradeToAndCall-address-bytes-}[`++upgradeToAndCall(newImplementation, data)++`] +* {xref-ERC1967Utils-getAdmin--}[`++getAdmin()++`] +* {xref-ERC1967Utils-changeAdmin-address-}[`++changeAdmin(newAdmin)++`] +* {xref-ERC1967Utils-getBeacon--}[`++getBeacon()++`] +* {xref-ERC1967Utils-upgradeBeaconToAndCall-address-bytes-}[`++upgradeBeaconToAndCall(newBeacon, data)++`] + +-- + +[.contract-index] +.Events +-- +* {xref-ERC1967Utils-Upgraded-address-}[`++Upgraded(implementation)++`] +* {xref-ERC1967Utils-AdminChanged-address-address-}[`++AdminChanged(previousAdmin, newAdmin)++`] +* {xref-ERC1967Utils-BeaconUpgraded-address-}[`++BeaconUpgraded(beacon)++`] + +-- + +[.contract-index] +.Errors +-- +* {xref-ERC1967Utils-ERC1967InvalidImplementation-address-}[`++ERC1967InvalidImplementation(implementation)++`] +* {xref-ERC1967Utils-ERC1967InvalidAdmin-address-}[`++ERC1967InvalidAdmin(admin)++`] +* {xref-ERC1967Utils-ERC1967InvalidBeacon-address-}[`++ERC1967InvalidBeacon(beacon)++`] +* {xref-ERC1967Utils-ERC1967NonPayable--}[`++ERC1967NonPayable()++`] + +-- + +[.contract-item] +[[ERC1967Utils-getImplementation--]] +==== `[.contract-item-name]#++getImplementation++#++() β†’ address++` [.item-kind]#internal# + +Returns the current implementation address. + +[.contract-item] +[[ERC1967Utils-upgradeToAndCall-address-bytes-]] +==== `[.contract-item-name]#++upgradeToAndCall++#++(address newImplementation, bytes data)++` [.item-kind]#internal# + +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. + +[.contract-item] +[[ERC1967Utils-getAdmin--]] +==== `[.contract-item-name]#++getAdmin++#++() β†’ address++` [.item-kind]#internal# + +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` + +[.contract-item] +[[ERC1967Utils-changeAdmin-address-]] +==== `[.contract-item-name]#++changeAdmin++#++(address newAdmin)++` [.item-kind]#internal# + +Changes the admin of the proxy. + +Emits an {IERC1967-AdminChanged} event. + +[.contract-item] +[[ERC1967Utils-getBeacon--]] +==== `[.contract-item-name]#++getBeacon++#++() β†’ address++` [.item-kind]#internal# + +Returns the current beacon. + +[.contract-item] +[[ERC1967Utils-upgradeBeaconToAndCall-address-bytes-]] +==== `[.contract-item-name]#++upgradeBeaconToAndCall++#++(address newBeacon, bytes data)++` [.item-kind]#internal# + +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. + +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. + +[.contract-item] +[[ERC1967Utils-Upgraded-address-]] +==== `[.contract-item-name]#++Upgraded++#++(address indexed implementation)++` [.item-kind]#event# + +Emitted when the implementation is upgraded. + +[.contract-item] +[[ERC1967Utils-AdminChanged-address-address-]] +==== `[.contract-item-name]#++AdminChanged++#++(address previousAdmin, address newAdmin)++` [.item-kind]#event# + +Emitted when the admin account has changed. + +[.contract-item] +[[ERC1967Utils-BeaconUpgraded-address-]] +==== `[.contract-item-name]#++BeaconUpgraded++#++(address indexed beacon)++` [.item-kind]#event# + +Emitted when the beacon is changed. + +[.contract-item] +[[ERC1967Utils-ERC1967InvalidImplementation-address-]] +==== `[.contract-item-name]#++ERC1967InvalidImplementation++#++(address implementation)++` [.item-kind]#error# + +The `implementation` of the proxy is invalid. + +[.contract-item] +[[ERC1967Utils-ERC1967InvalidAdmin-address-]] +==== `[.contract-item-name]#++ERC1967InvalidAdmin++#++(address admin)++` [.item-kind]#error# + +The `admin` of the proxy is invalid. + +[.contract-item] +[[ERC1967Utils-ERC1967InvalidBeacon-address-]] +==== `[.contract-item-name]#++ERC1967InvalidBeacon++#++(address beacon)++` [.item-kind]#error# + +The `beacon` of the proxy is invalid. + +[.contract-item] +[[ERC1967Utils-ERC1967NonPayable--]] +==== `[.contract-item-name]#++ERC1967NonPayable++#++()++` [.item-kind]#error# + +An upgrade function sees `msg.value > 0` that may be lost. + +== Transparent Proxy + +:ProxyDeniedAdminAccess: pass:normal[xref:#TransparentUpgradeableProxy-ProxyDeniedAdminAccess--[`++ProxyDeniedAdminAccess++`]] +:constructor: pass:normal[xref:#TransparentUpgradeableProxy-constructor-address-address-bytes-[`++constructor++`]] +:_proxyAdmin: pass:normal[xref:#TransparentUpgradeableProxy-_proxyAdmin--[`++_proxyAdmin++`]] +:_fallback: pass:normal[xref:#TransparentUpgradeableProxy-_fallback--[`++_fallback++`]] + +[.contract] +[[TransparentUpgradeableProxy]] +=== `++TransparentUpgradeableProxy++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/proxy/transparent/TransparentUpgradeableProxy.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +``` + +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 +https://blog.openzeppelin.com/the-transparent-proxy-pattern/[transparent proxy pattern]. This pattern implies two +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 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. 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 `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. + +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 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 `upgradeToAndCall` function inaccessible, preventing upgradeability and compromising transparency. + +[.contract-index] +.Functions +-- +* {xref-TransparentUpgradeableProxy-constructor-address-address-bytes-}[`++constructor(_logic, initialOwner, _data)++`] +* {xref-TransparentUpgradeableProxy-_proxyAdmin--}[`++_proxyAdmin()++`] +* {xref-TransparentUpgradeableProxy-_fallback--}[`++_fallback()++`] + +[.contract-subindex-inherited] +.ERC1967Proxy +* {xref-ERC1967Proxy-_implementation--}[`++_implementation()++`] + +[.contract-subindex-inherited] +.Proxy +* {xref-Proxy-_delegate-address-}[`++_delegate(implementation)++`] +* {xref-Proxy-fallback--}[`++fallback()++`] + +-- + +[.contract-index] +.Errors +-- +* {xref-TransparentUpgradeableProxy-ProxyDeniedAdminAccess--}[`++ProxyDeniedAdminAccess()++`] + +[.contract-subindex-inherited] +.ERC1967Proxy + +[.contract-subindex-inherited] +.Proxy + +-- + +[.contract-item] +[[TransparentUpgradeableProxy-constructor-address-address-bytes-]] +==== `[.contract-item-name]#++constructor++#++(address _logic, address initialOwner, bytes _data)++` [.item-kind]#public# + +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}. + +[.contract-item] +[[TransparentUpgradeableProxy-_proxyAdmin--]] +==== `[.contract-item-name]#++_proxyAdmin++#++() β†’ address++` [.item-kind]#internal# + +Returns the admin of this proxy. + +[.contract-item] +[[TransparentUpgradeableProxy-_fallback--]] +==== `[.contract-item-name]#++_fallback++#++()++` [.item-kind]#internal# + +If caller is the admin process the call internally, otherwise transparently fallback to the proxy behavior. + +[.contract-item] +[[TransparentUpgradeableProxy-ProxyDeniedAdminAccess--]] +==== `[.contract-item-name]#++ProxyDeniedAdminAccess++#++()++` [.item-kind]#error# + +The proxy caller is the current admin, and can't fallback to the proxy target. + +:UPGRADE_INTERFACE_VERSION: pass:normal[xref:#ProxyAdmin-UPGRADE_INTERFACE_VERSION-string[`++UPGRADE_INTERFACE_VERSION++`]] +:constructor: pass:normal[xref:#ProxyAdmin-constructor-address-[`++constructor++`]] +:upgradeAndCall: pass:normal[xref:#ProxyAdmin-upgradeAndCall-contract-ITransparentUpgradeableProxy-address-bytes-[`++upgradeAndCall++`]] + +[.contract] +[[ProxyAdmin]] +=== `++ProxyAdmin++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/proxy/transparent/ProxyAdmin.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; +``` + +This is an auxiliary contract meant to be assigned as the admin of a {TransparentUpgradeableProxy}. For an +explanation of why you would want to use this see the documentation for {TransparentUpgradeableProxy}. + +[.contract-index] +.Functions +-- +* {xref-ProxyAdmin-constructor-address-}[`++constructor(initialOwner)++`] +* {xref-ProxyAdmin-upgradeAndCall-contract-ITransparentUpgradeableProxy-address-bytes-}[`++upgradeAndCall(proxy, implementation, data)++`] + +[.contract-subindex-inherited] +.Ownable +* {xref-Ownable-owner--}[`++owner()++`] +* {xref-Ownable-_checkOwner--}[`++_checkOwner()++`] +* {xref-Ownable-renounceOwnership--}[`++renounceOwnership()++`] +* {xref-Ownable-transferOwnership-address-}[`++transferOwnership(newOwner)++`] +* {xref-Ownable-_transferOwnership-address-}[`++_transferOwnership(newOwner)++`] + +-- + +[.contract-index] +.Events +-- + +[.contract-subindex-inherited] +.Ownable +* {xref-Ownable-OwnershipTransferred-address-address-}[`++OwnershipTransferred(previousOwner, newOwner)++`] + +-- + +[.contract-index] +.Errors +-- + +[.contract-subindex-inherited] +.Ownable +* {xref-Ownable-OwnableUnauthorizedAccount-address-}[`++OwnableUnauthorizedAccount(account)++`] +* {xref-Ownable-OwnableInvalidOwner-address-}[`++OwnableInvalidOwner(owner)++`] + +-- + +[.contract-item] +[[ProxyAdmin-constructor-address-]] +==== `[.contract-item-name]#++constructor++#++(address initialOwner)++` [.item-kind]#public# + +Sets the initial owner who can perform upgrades. + +[.contract-item] +[[ProxyAdmin-upgradeAndCall-contract-ITransparentUpgradeableProxy-address-bytes-]] +==== `[.contract-item-name]#++upgradeAndCall++#++(contract ITransparentUpgradeableProxy proxy, address implementation, bytes data)++` [.item-kind]#public# + +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. + +== Beacon + +:constructor: pass:normal[xref:#BeaconProxy-constructor-address-bytes-[`++constructor++`]] +:_implementation: pass:normal[xref:#BeaconProxy-_implementation--[`++_implementation++`]] +:_getBeacon: pass:normal[xref:#BeaconProxy-_getBeacon--[`++_getBeacon++`]] + +[.contract] +[[BeaconProxy]] +=== `++BeaconProxy++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/proxy/beacon/BeaconProxy.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol"; +``` + +This contract implements a proxy that gets the implementation address for each call from an {UpgradeableBeacon}. + +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: 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-index] +.Functions +-- +* {xref-BeaconProxy-constructor-address-bytes-}[`++constructor(beacon, data)++`] +* {xref-BeaconProxy-_implementation--}[`++_implementation()++`] +* {xref-BeaconProxy-_getBeacon--}[`++_getBeacon()++`] + +[.contract-subindex-inherited] +.Proxy +* {xref-Proxy-_delegate-address-}[`++_delegate(implementation)++`] +* {xref-Proxy-_fallback--}[`++_fallback()++`] +* {xref-Proxy-fallback--}[`++fallback()++`] + +-- + +[.contract-item] +[[BeaconProxy-constructor-address-bytes-]] +==== `[.contract-item-name]#++constructor++#++(address beacon, bytes data)++` [.item-kind]#public# + +Initializes the proxy with `beacon`. + +If `data` is nonempty, it's used as data in a delegate call to the implementation returned by the beacon. This +will typically be an encoded function call, and allows initializing the storage of the proxy like a Solidity +constructor. + +Requirements: + +- `beacon` must be a contract with the interface {IBeacon}. +- If `data` is empty, `msg.value` must be zero. + +[.contract-item] +[[BeaconProxy-_implementation--]] +==== `[.contract-item-name]#++_implementation++#++() β†’ address++` [.item-kind]#internal# + +Returns the current implementation address of the associated beacon. + +[.contract-item] +[[BeaconProxy-_getBeacon--]] +==== `[.contract-item-name]#++_getBeacon++#++() β†’ address++` [.item-kind]#internal# + +Returns the beacon. + +:implementation: pass:normal[xref:#IBeacon-implementation--[`++implementation++`]] + +[.contract] +[[IBeacon]] +=== `++IBeacon++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/proxy/beacon/IBeacon.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/proxy/beacon/IBeacon.sol"; +``` + +This is the interface that {BeaconProxy} expects of its beacon. + +[.contract-index] +.Functions +-- +* {xref-IBeacon-implementation--}[`++implementation()++`] + +-- + +[.contract-item] +[[IBeacon-implementation--]] +==== `[.contract-item-name]#++implementation++#++() β†’ address++` [.item-kind]#external# + +Must return an address that can be used as a delegate call target. + +{UpgradeableBeacon} will check that this address is a contract. + +:BeaconInvalidImplementation: pass:normal[xref:#UpgradeableBeacon-BeaconInvalidImplementation-address-[`++BeaconInvalidImplementation++`]] +:Upgraded: pass:normal[xref:#UpgradeableBeacon-Upgraded-address-[`++Upgraded++`]] +:constructor: pass:normal[xref:#UpgradeableBeacon-constructor-address-address-[`++constructor++`]] +:implementation: pass:normal[xref:#UpgradeableBeacon-implementation--[`++implementation++`]] +:upgradeTo: pass:normal[xref:#UpgradeableBeacon-upgradeTo-address-[`++upgradeTo++`]] + +[.contract] +[[UpgradeableBeacon]] +=== `++UpgradeableBeacon++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/proxy/beacon/UpgradeableBeacon.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol"; +``` + +This contract is used in conjunction with one or more instances of {BeaconProxy} to determine their +implementation contract, which is where they will delegate all function calls. + +An owner is able to change the implementation the beacon points to, thus upgrading the proxies that use this beacon. + +[.contract-index] +.Functions +-- +* {xref-UpgradeableBeacon-constructor-address-address-}[`++constructor(implementation_, initialOwner)++`] +* {xref-UpgradeableBeacon-implementation--}[`++implementation()++`] +* {xref-UpgradeableBeacon-upgradeTo-address-}[`++upgradeTo(newImplementation)++`] + +[.contract-subindex-inherited] +.Ownable +* {xref-Ownable-owner--}[`++owner()++`] +* {xref-Ownable-_checkOwner--}[`++_checkOwner()++`] +* {xref-Ownable-renounceOwnership--}[`++renounceOwnership()++`] +* {xref-Ownable-transferOwnership-address-}[`++transferOwnership(newOwner)++`] +* {xref-Ownable-_transferOwnership-address-}[`++_transferOwnership(newOwner)++`] + +[.contract-subindex-inherited] +.IBeacon + +-- + +[.contract-index] +.Events +-- +* {xref-UpgradeableBeacon-Upgraded-address-}[`++Upgraded(implementation)++`] + +[.contract-subindex-inherited] +.Ownable +* {xref-Ownable-OwnershipTransferred-address-address-}[`++OwnershipTransferred(previousOwner, newOwner)++`] + +[.contract-subindex-inherited] +.IBeacon + +-- + +[.contract-index] +.Errors +-- +* {xref-UpgradeableBeacon-BeaconInvalidImplementation-address-}[`++BeaconInvalidImplementation(implementation)++`] + +[.contract-subindex-inherited] +.Ownable +* {xref-Ownable-OwnableUnauthorizedAccount-address-}[`++OwnableUnauthorizedAccount(account)++`] +* {xref-Ownable-OwnableInvalidOwner-address-}[`++OwnableInvalidOwner(owner)++`] + +[.contract-subindex-inherited] +.IBeacon + +-- + +[.contract-item] +[[UpgradeableBeacon-constructor-address-address-]] +==== `[.contract-item-name]#++constructor++#++(address implementation_, address initialOwner)++` [.item-kind]#public# + +Sets the address of the initial implementation, and the initial owner who can upgrade the beacon. + +[.contract-item] +[[UpgradeableBeacon-implementation--]] +==== `[.contract-item-name]#++implementation++#++() β†’ address++` [.item-kind]#public# + +Returns the current implementation address. + +[.contract-item] +[[UpgradeableBeacon-upgradeTo-address-]] +==== `[.contract-item-name]#++upgradeTo++#++(address newImplementation)++` [.item-kind]#public# + +Upgrades the beacon to a new implementation. + +Emits an {Upgraded} event. + +Requirements: + +- msg.sender must be the owner of the contract. +- `newImplementation` must be a contract. + +[.contract-item] +[[UpgradeableBeacon-Upgraded-address-]] +==== `[.contract-item-name]#++Upgraded++#++(address indexed implementation)++` [.item-kind]#event# + +Emitted when the implementation returned by the beacon is changed. + +[.contract-item] +[[UpgradeableBeacon-BeaconInvalidImplementation-address-]] +==== `[.contract-item-name]#++BeaconInvalidImplementation++#++(address implementation)++` [.item-kind]#error# + +The `implementation` of the beacon is invalid. + +== Minimal Clones + +:ERC1167FailedCreateClone: pass:normal[xref:#Clones-ERC1167FailedCreateClone--[`++ERC1167FailedCreateClone++`]] +:clone: pass:normal[xref:#Clones-clone-address-[`++clone++`]] +:cloneDeterministic: pass:normal[xref:#Clones-cloneDeterministic-address-bytes32-[`++cloneDeterministic++`]] +:predictDeterministicAddress: pass:normal[xref:#Clones-predictDeterministicAddress-address-bytes32-address-[`++predictDeterministicAddress++`]] +:predictDeterministicAddress: pass:normal[xref:#Clones-predictDeterministicAddress-address-bytes32-[`++predictDeterministicAddress++`]] + +[.contract] +[[Clones]] +=== `++Clones++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/proxy/Clones.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/proxy/Clones.sol"; +``` + +https://eips.ethereum.org/EIPS/eip-1167[EIP 1167] is a standard for +deploying minimal proxy contracts, also known as "clones". + +> To simply and cheaply clone contract functionality in an immutable way, this standard specifies +> a minimal bytecode implementation that delegates all calls to a known, fixed address. + +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. + +[.contract-index] +.Functions +-- +* {xref-Clones-clone-address-}[`++clone(implementation)++`] +* {xref-Clones-cloneDeterministic-address-bytes32-}[`++cloneDeterministic(implementation, salt)++`] +* {xref-Clones-predictDeterministicAddress-address-bytes32-address-}[`++predictDeterministicAddress(implementation, salt, deployer)++`] +* {xref-Clones-predictDeterministicAddress-address-bytes32-}[`++predictDeterministicAddress(implementation, salt)++`] + +-- + +[.contract-index] +.Errors +-- +* {xref-Clones-ERC1167FailedCreateClone--}[`++ERC1167FailedCreateClone()++`] + +-- + +[.contract-item] +[[Clones-clone-address-]] +==== `[.contract-item-name]#++clone++#++(address implementation) β†’ address instance++` [.item-kind]#internal# + +Deploys and returns the address of a clone that mimics the behaviour of `implementation`. + +This function uses the create opcode, which should never revert. + +[.contract-item] +[[Clones-cloneDeterministic-address-bytes32-]] +==== `[.contract-item-name]#++cloneDeterministic++#++(address implementation, bytes32 salt) β†’ address instance++` [.item-kind]#internal# + +Deploys and returns the address of a clone that mimics the behaviour of `implementation`. + +This function uses the create2 opcode and a `salt` to deterministically deploy +the clone. Using the same `implementation` and `salt` multiple time will revert, since +the clones cannot be deployed twice at the same address. + +[.contract-item] +[[Clones-predictDeterministicAddress-address-bytes32-address-]] +==== `[.contract-item-name]#++predictDeterministicAddress++#++(address implementation, bytes32 salt, address deployer) β†’ address predicted++` [.item-kind]#internal# + +Computes the address of a clone deployed using {Clones-cloneDeterministic}. + +[.contract-item] +[[Clones-predictDeterministicAddress-address-bytes32-]] +==== `[.contract-item-name]#++predictDeterministicAddress++#++(address implementation, bytes32 salt) β†’ address predicted++` [.item-kind]#internal# + +Computes the address of a clone deployed using {Clones-cloneDeterministic}. + +[.contract-item] +[[Clones-ERC1167FailedCreateClone--]] +==== `[.contract-item-name]#++ERC1167FailedCreateClone++#++()++` [.item-kind]#error# + +A clone instance deployment failed. + +== Utils + +:InitializableStorage: pass:normal[xref:#Initializable-InitializableStorage[`++InitializableStorage++`]] +:InvalidInitialization: pass:normal[xref:#Initializable-InvalidInitialization--[`++InvalidInitialization++`]] +:NotInitializing: pass:normal[xref:#Initializable-NotInitializing--[`++NotInitializing++`]] +:Initialized: pass:normal[xref:#Initializable-Initialized-uint64-[`++Initialized++`]] +:initializer: pass:normal[xref:#Initializable-initializer--[`++initializer++`]] +:reinitializer: pass:normal[xref:#Initializable-reinitializer-uint64-[`++reinitializer++`]] +:onlyInitializing: pass:normal[xref:#Initializable-onlyInitializing--[`++onlyInitializing++`]] +:_checkInitializing: pass:normal[xref:#Initializable-_checkInitializing--[`++_checkInitializing++`]] +:_disableInitializers: pass:normal[xref:#Initializable-_disableInitializers--[`++_disableInitializers++`]] +:_getInitializedVersion: pass:normal[xref:#Initializable-_getInitializedVersion--[`++_getInitializedVersion++`]] +:_isInitializing: pass:normal[xref:#Initializable-_isInitializing--[`++_isInitializing++`]] + +[.contract] +[[Initializable]] +=== `++Initializable++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/proxy/utils/Initializable.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; +``` + +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 +external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer +function so it can only be called once. The {initializer} modifier provided by this contract will have this effect. + +The initialization functions use a version number. Once a version number is used, it is consumed and cannot be +reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in +case an upgrade adds a module that needs to be initialized. + +For example: + +[.hljs-theme-light.nopadding] +```solidity +contract MyToken is ERC20Upgradeable { + function initialize() initializer public { + __ERC20_init("MyToken", "MTK"); + } +} + +contract MyTokenV2 is MyToken, ERC20PermitUpgradeable { + function initializeV2() reinitializer(2) public { + __ERC20Permit_init("MyToken"); + } +} +``` + +TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as +possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}. + +CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure +that all initializers are idempotent. This is not verified automatically as constructors are by Solidity. + +[CAUTION] +==== +Avoid leaving a contract uninitialized. + +An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation +contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke +the {_disableInitializers} function in the constructor to automatically lock it when it is deployed: + +[.hljs-theme-light.nopadding] +``` +/// @custom:oz-upgrades-unsafe-allow constructor +constructor() { + _disableInitializers(); +} +``` +==== + +[.contract-index] +.Modifiers +-- +* {xref-Initializable-initializer--}[`++initializer()++`] +* {xref-Initializable-reinitializer-uint64-}[`++reinitializer(version)++`] +* {xref-Initializable-onlyInitializing--}[`++onlyInitializing()++`] +-- + +[.contract-index] +.Functions +-- +* {xref-Initializable-_checkInitializing--}[`++_checkInitializing()++`] +* {xref-Initializable-_disableInitializers--}[`++_disableInitializers()++`] +* {xref-Initializable-_getInitializedVersion--}[`++_getInitializedVersion()++`] +* {xref-Initializable-_isInitializing--}[`++_isInitializing()++`] + +-- + +[.contract-index] +.Events +-- +* {xref-Initializable-Initialized-uint64-}[`++Initialized(version)++`] + +-- + +[.contract-index] +.Errors +-- +* {xref-Initializable-InvalidInitialization--}[`++InvalidInitialization()++`] +* {xref-Initializable-NotInitializing--}[`++NotInitializing()++`] + +-- + +[.contract-item] +[[Initializable-initializer--]] +==== `[.contract-item-name]#++initializer++#++()++` [.item-kind]#modifier# + +A modifier that defines a protected initializer function that can be invoked at most once. In its scope, +`onlyInitializing` functions can be used to initialize parent contracts. + +Similar to `reinitializer(1)`, except that in the context of a constructor an `initializer` may be invoked any +number of times. This behavior in the constructor can be useful during testing and is not expected to be used in +production. + +Emits an {Initialized} event. + +[.contract-item] +[[Initializable-reinitializer-uint64-]] +==== `[.contract-item-name]#++reinitializer++#++(uint64 version)++` [.item-kind]#modifier# + +A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the +contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be +used to initialize parent contracts. + +A reinitializer may be used after the original initialization step. This is essential to configure modules that +are added through upgrades and that require initialization. + +When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer` +cannot be nested. If one is invoked in the context of another, execution will revert. + +Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in +a contract, executing them in the right order is up to the developer or operator. + +WARNING: Setting the version to 2**64 - 1 will prevent any future reinitialization. + +Emits an {Initialized} event. + +[.contract-item] +[[Initializable-onlyInitializing--]] +==== `[.contract-item-name]#++onlyInitializing++#++()++` [.item-kind]#modifier# + +Modifier to protect an initialization function so that it can only be invoked by functions with the +{initializer} and {reinitializer} modifiers, directly or indirectly. + +[.contract-item] +[[Initializable-_checkInitializing--]] +==== `[.contract-item-name]#++_checkInitializing++#++()++` [.item-kind]#internal# + +Reverts if the contract is not in an initializing state. See {onlyInitializing}. + +[.contract-item] +[[Initializable-_disableInitializers--]] +==== `[.contract-item-name]#++_disableInitializers++#++()++` [.item-kind]#internal# + +Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call. +Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized +to any version. It is recommended to use this to lock implementation contracts that are designed to be called +through proxies. + +Emits an {Initialized} event the first time it is successfully executed. + +[.contract-item] +[[Initializable-_getInitializedVersion--]] +==== `[.contract-item-name]#++_getInitializedVersion++#++() β†’ uint64++` [.item-kind]#internal# + +Returns the highest version that has been initialized. See {reinitializer}. + +[.contract-item] +[[Initializable-_isInitializing--]] +==== `[.contract-item-name]#++_isInitializing++#++() β†’ bool++` [.item-kind]#internal# + +Returns `true` if the contract is currently initializing. See {onlyInitializing}. + +[.contract-item] +[[Initializable-Initialized-uint64-]] +==== `[.contract-item-name]#++Initialized++#++(uint64 version)++` [.item-kind]#event# + +Triggered when the contract has been initialized or reinitialized. + +[.contract-item] +[[Initializable-InvalidInitialization--]] +==== `[.contract-item-name]#++InvalidInitialization++#++()++` [.item-kind]#error# + +The contract is already initialized. + +[.contract-item] +[[Initializable-NotInitializing--]] +==== `[.contract-item-name]#++NotInitializing++#++()++` [.item-kind]#error# + +The contract is not initializing. + +:UPGRADE_INTERFACE_VERSION: pass:normal[xref:#UUPSUpgradeable-UPGRADE_INTERFACE_VERSION-string[`++UPGRADE_INTERFACE_VERSION++`]] +:UUPSUnauthorizedCallContext: pass:normal[xref:#UUPSUpgradeable-UUPSUnauthorizedCallContext--[`++UUPSUnauthorizedCallContext++`]] +:UUPSUnsupportedProxiableUUID: pass:normal[xref:#UUPSUpgradeable-UUPSUnsupportedProxiableUUID-bytes32-[`++UUPSUnsupportedProxiableUUID++`]] +:onlyProxy: pass:normal[xref:#UUPSUpgradeable-onlyProxy--[`++onlyProxy++`]] +:notDelegated: pass:normal[xref:#UUPSUpgradeable-notDelegated--[`++notDelegated++`]] +:proxiableUUID: pass:normal[xref:#UUPSUpgradeable-proxiableUUID--[`++proxiableUUID++`]] +:upgradeToAndCall: pass:normal[xref:#UUPSUpgradeable-upgradeToAndCall-address-bytes-[`++upgradeToAndCall++`]] +:_checkProxy: pass:normal[xref:#UUPSUpgradeable-_checkProxy--[`++_checkProxy++`]] +:_checkNotDelegated: pass:normal[xref:#UUPSUpgradeable-_checkNotDelegated--[`++_checkNotDelegated++`]] +:_authorizeUpgrade: pass:normal[xref:#UUPSUpgradeable-_authorizeUpgrade-address-[`++_authorizeUpgrade++`]] + +[.contract] +[[UUPSUpgradeable]] +=== `++UUPSUpgradeable++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/proxy/utils/UUPSUpgradeable.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; +``` + +An upgradeability mechanism designed for UUPS proxies. The functions included here can perform an upgrade of an +{ERC1967Proxy}, when this contract is set as the implementation behind such a proxy. + +A security mechanism ensures that an upgrade does not turn off upgradeability accidentally, although this risk is +reinstated if the upgrade retains upgradeability but removes the security mechanism, e.g. by replacing +`UUPSUpgradeable` with a custom implementation of upgrades. + +The {_authorizeUpgrade} function must be overridden to include access restriction to the upgrade mechanism. + +[.contract-index] +.Modifiers +-- +* {xref-UUPSUpgradeable-onlyProxy--}[`++onlyProxy()++`] +* {xref-UUPSUpgradeable-notDelegated--}[`++notDelegated()++`] +-- + +[.contract-index] +.Functions +-- +* {xref-UUPSUpgradeable-proxiableUUID--}[`++proxiableUUID()++`] +* {xref-UUPSUpgradeable-upgradeToAndCall-address-bytes-}[`++upgradeToAndCall(newImplementation, data)++`] +* {xref-UUPSUpgradeable-_checkProxy--}[`++_checkProxy()++`] +* {xref-UUPSUpgradeable-_checkNotDelegated--}[`++_checkNotDelegated()++`] +* {xref-UUPSUpgradeable-_authorizeUpgrade-address-}[`++_authorizeUpgrade(newImplementation)++`] + +[.contract-subindex-inherited] +.IERC1822Proxiable + +-- + +[.contract-index] +.Errors +-- +* {xref-UUPSUpgradeable-UUPSUnauthorizedCallContext--}[`++UUPSUnauthorizedCallContext()++`] +* {xref-UUPSUpgradeable-UUPSUnsupportedProxiableUUID-bytes32-}[`++UUPSUnsupportedProxiableUUID(slot)++`] + +[.contract-subindex-inherited] +.IERC1822Proxiable + +-- + +[.contract-item] +[[UUPSUpgradeable-onlyProxy--]] +==== `[.contract-item-name]#++onlyProxy++#++()++` [.item-kind]#modifier# + +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 +for UUPS and transparent proxies that are using the current contract as their implementation. Execution of a +function through ERC1167 minimal proxies (clones) would not normally pass this test, but is not guaranteed to +fail. + +[.contract-item] +[[UUPSUpgradeable-notDelegated--]] +==== `[.contract-item-name]#++notDelegated++#++()++` [.item-kind]#modifier# + +Check that the execution is not being performed through a delegate call. This allows a function to be +callable on the implementing contract but not through proxies. + +[.contract-item] +[[UUPSUpgradeable-proxiableUUID--]] +==== `[.contract-item-name]#++proxiableUUID++#++() β†’ bytes32++` [.item-kind]#external# + +Implementation of the ERC1822 {proxiableUUID} function. This returns the storage slot used by the +implementation. It is used to validate the implementation's compatibility when performing an upgrade. + +IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks +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. + +[.contract-item] +[[UUPSUpgradeable-upgradeToAndCall-address-bytes-]] +==== `[.contract-item-name]#++upgradeToAndCall++#++(address newImplementation, bytes data)++` [.item-kind]#public# + +Upgrade the implementation of the proxy to `newImplementation`, and subsequently execute the function call +encoded in `data`. + +Calls {_authorizeUpgrade}. + +Emits an {Upgraded} event. + +[.contract-item] +[[UUPSUpgradeable-_checkProxy--]] +==== `[.contract-item-name]#++_checkProxy++#++()++` [.item-kind]#internal# + +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}. + +[.contract-item] +[[UUPSUpgradeable-_checkNotDelegated--]] +==== `[.contract-item-name]#++_checkNotDelegated++#++()++` [.item-kind]#internal# + +Reverts if the execution is performed via delegatecall. +See {notDelegated}. + +[.contract-item] +[[UUPSUpgradeable-_authorizeUpgrade-address-]] +==== `[.contract-item-name]#++_authorizeUpgrade++#++(address newImplementation)++` [.item-kind]#internal# + +Function that should revert when `msg.sender` is not authorized to upgrade the contract. Called by +{upgradeToAndCall}. + +Normally, this function will use an xref:access.adoc[access control] modifier such as {Ownable-onlyOwner}. + +```solidity +function _authorizeUpgrade(address) internal onlyOwner {} +``` + +[.contract-item] +[[UUPSUpgradeable-UUPSUnauthorizedCallContext--]] +==== `[.contract-item-name]#++UUPSUnauthorizedCallContext++#++()++` [.item-kind]#error# + +The call is from an unauthorized context. + +[.contract-item] +[[UUPSUpgradeable-UUPSUnsupportedProxiableUUID-bytes32-]] +==== `[.contract-item-name]#++UUPSUnsupportedProxiableUUID++#++(bytes32 slot)++` [.item-kind]#error# + +The storage `slot` is unsupported as a UUID. + diff --git a/docs/modules/api/pages/token/ERC1155.adoc b/docs/modules/api/pages/token/ERC1155.adoc new file mode 100644 index 000000000..45a88cde3 --- /dev/null +++ b/docs/modules/api/pages/token/ERC1155.adoc @@ -0,0 +1,1496 @@ +:github-icon: pass:[] +:IERC1155: pass:normal[xref:token/ERC1155.adoc#IERC1155[`IERC1155`]] +:IERC1155MetadataURI: pass:normal[xref:token/ERC1155.adoc#IERC1155MetadataURI[`IERC1155MetadataURI`]] +:IERC1155Receiver: pass:normal[xref:token/ERC1155.adoc#IERC1155Receiver[`IERC1155Receiver`]] +:ERC1155: pass:normal[xref:token/ERC1155.adoc#ERC1155[`ERC1155`]] +:IERC1155: pass:normal[xref:token/ERC1155.adoc#IERC1155[`IERC1155`]] +:IERC1155MetadataURI: pass:normal[xref:token/ERC1155.adoc#IERC1155MetadataURI[`IERC1155MetadataURI`]] +:ERC1155Pausable: pass:normal[xref:token/ERC1155.adoc#ERC1155Pausable[`ERC1155Pausable`]] +:ERC1155Burnable: pass:normal[xref:token/ERC1155.adoc#ERC1155Burnable[`ERC1155Burnable`]] +:xref-IERC1155-balanceOf-address-uint256-: xref:token/ERC1155.adoc#IERC1155-balanceOf-address-uint256- +:xref-IERC1155-balanceOfBatch-address---uint256---: xref:token/ERC1155.adoc#IERC1155-balanceOfBatch-address---uint256--- +:xref-IERC1155-setApprovalForAll-address-bool-: xref:token/ERC1155.adoc#IERC1155-setApprovalForAll-address-bool- +:xref-IERC1155-isApprovedForAll-address-address-: xref:token/ERC1155.adoc#IERC1155-isApprovedForAll-address-address- +:xref-IERC1155-safeTransferFrom-address-address-uint256-uint256-bytes-: xref:token/ERC1155.adoc#IERC1155-safeTransferFrom-address-address-uint256-uint256-bytes- +:xref-IERC1155-safeBatchTransferFrom-address-address-uint256---uint256---bytes-: xref:token/ERC1155.adoc#IERC1155-safeBatchTransferFrom-address-address-uint256---uint256---bytes- +:xref-IERC165-supportsInterface-bytes4-: xref:utils.adoc#IERC165-supportsInterface-bytes4- +:xref-IERC1155-TransferSingle-address-address-address-uint256-uint256-: xref:token/ERC1155.adoc#IERC1155-TransferSingle-address-address-address-uint256-uint256- +:xref-IERC1155-TransferBatch-address-address-address-uint256---uint256---: xref:token/ERC1155.adoc#IERC1155-TransferBatch-address-address-address-uint256---uint256--- +:xref-IERC1155-ApprovalForAll-address-address-bool-: xref:token/ERC1155.adoc#IERC1155-ApprovalForAll-address-address-bool- +:xref-IERC1155-URI-string-uint256-: xref:token/ERC1155.adoc#IERC1155-URI-string-uint256- +:IERC1155Receiver-onERC1155Received: pass:normal[xref:token/ERC1155.adoc#IERC1155Receiver-onERC1155Received-address-address-uint256-uint256-bytes-[`IERC1155Receiver.onERC1155Received`]] +:IERC1155Receiver-onERC1155BatchReceived: pass:normal[xref:token/ERC1155.adoc#IERC1155Receiver-onERC1155BatchReceived-address-address-uint256---uint256---bytes-[`IERC1155Receiver.onERC1155BatchReceived`]] +:IERC1155MetadataURI-uri: pass:normal[xref:token/ERC1155.adoc#IERC1155MetadataURI-uri-uint256-[`IERC1155MetadataURI.uri`]] +:xref-IERC1155MetadataURI-uri-uint256-: xref:token/ERC1155.adoc#IERC1155MetadataURI-uri-uint256- +:xref-IERC1155-balanceOf-address-uint256-: xref:token/ERC1155.adoc#IERC1155-balanceOf-address-uint256- +:xref-IERC1155-balanceOfBatch-address---uint256---: xref:token/ERC1155.adoc#IERC1155-balanceOfBatch-address---uint256--- +:xref-IERC1155-setApprovalForAll-address-bool-: xref:token/ERC1155.adoc#IERC1155-setApprovalForAll-address-bool- +:xref-IERC1155-isApprovedForAll-address-address-: xref:token/ERC1155.adoc#IERC1155-isApprovedForAll-address-address- +:xref-IERC1155-safeTransferFrom-address-address-uint256-uint256-bytes-: xref:token/ERC1155.adoc#IERC1155-safeTransferFrom-address-address-uint256-uint256-bytes- +:xref-IERC1155-safeBatchTransferFrom-address-address-uint256---uint256---bytes-: xref:token/ERC1155.adoc#IERC1155-safeBatchTransferFrom-address-address-uint256---uint256---bytes- +:xref-IERC165-supportsInterface-bytes4-: xref:utils.adoc#IERC165-supportsInterface-bytes4- +:xref-IERC1155-TransferSingle-address-address-address-uint256-uint256-: xref:token/ERC1155.adoc#IERC1155-TransferSingle-address-address-address-uint256-uint256- +:xref-IERC1155-TransferBatch-address-address-address-uint256---uint256---: xref:token/ERC1155.adoc#IERC1155-TransferBatch-address-address-address-uint256---uint256--- +:xref-IERC1155-ApprovalForAll-address-address-bool-: xref:token/ERC1155.adoc#IERC1155-ApprovalForAll-address-address-bool- +:xref-IERC1155-URI-string-uint256-: xref:token/ERC1155.adoc#IERC1155-URI-string-uint256- +:xref-ERC1155-constructor-string-: xref:token/ERC1155.adoc#ERC1155-constructor-string- +:xref-ERC1155-supportsInterface-bytes4-: xref:token/ERC1155.adoc#ERC1155-supportsInterface-bytes4- +:xref-ERC1155-uri-uint256-: xref:token/ERC1155.adoc#ERC1155-uri-uint256- +:xref-ERC1155-balanceOf-address-uint256-: xref:token/ERC1155.adoc#ERC1155-balanceOf-address-uint256- +:xref-ERC1155-balanceOfBatch-address---uint256---: xref:token/ERC1155.adoc#ERC1155-balanceOfBatch-address---uint256--- +:xref-ERC1155-setApprovalForAll-address-bool-: xref:token/ERC1155.adoc#ERC1155-setApprovalForAll-address-bool- +:xref-ERC1155-isApprovedForAll-address-address-: xref:token/ERC1155.adoc#ERC1155-isApprovedForAll-address-address- +:xref-ERC1155-safeTransferFrom-address-address-uint256-uint256-bytes-: xref:token/ERC1155.adoc#ERC1155-safeTransferFrom-address-address-uint256-uint256-bytes- +:xref-ERC1155-safeBatchTransferFrom-address-address-uint256---uint256---bytes-: xref:token/ERC1155.adoc#ERC1155-safeBatchTransferFrom-address-address-uint256---uint256---bytes- +:xref-ERC1155-_update-address-address-uint256---uint256---: xref:token/ERC1155.adoc#ERC1155-_update-address-address-uint256---uint256--- +:xref-ERC1155-_updateWithAcceptanceCheck-address-address-uint256---uint256---bytes-: xref:token/ERC1155.adoc#ERC1155-_updateWithAcceptanceCheck-address-address-uint256---uint256---bytes- +:xref-ERC1155-_safeTransferFrom-address-address-uint256-uint256-bytes-: xref:token/ERC1155.adoc#ERC1155-_safeTransferFrom-address-address-uint256-uint256-bytes- +:xref-ERC1155-_safeBatchTransferFrom-address-address-uint256---uint256---bytes-: xref:token/ERC1155.adoc#ERC1155-_safeBatchTransferFrom-address-address-uint256---uint256---bytes- +:xref-ERC1155-_setURI-string-: xref:token/ERC1155.adoc#ERC1155-_setURI-string- +:xref-ERC1155-_mint-address-uint256-uint256-bytes-: xref:token/ERC1155.adoc#ERC1155-_mint-address-uint256-uint256-bytes- +:xref-ERC1155-_mintBatch-address-uint256---uint256---bytes-: xref:token/ERC1155.adoc#ERC1155-_mintBatch-address-uint256---uint256---bytes- +:xref-ERC1155-_burn-address-uint256-uint256-: xref:token/ERC1155.adoc#ERC1155-_burn-address-uint256-uint256- +:xref-ERC1155-_burnBatch-address-uint256---uint256---: xref:token/ERC1155.adoc#ERC1155-_burnBatch-address-uint256---uint256--- +:xref-ERC1155-_setApprovalForAll-address-address-bool-: xref:token/ERC1155.adoc#ERC1155-_setApprovalForAll-address-address-bool- +:xref-IERC1155-TransferSingle-address-address-address-uint256-uint256-: xref:token/ERC1155.adoc#IERC1155-TransferSingle-address-address-address-uint256-uint256- +:xref-IERC1155-TransferBatch-address-address-address-uint256---uint256---: xref:token/ERC1155.adoc#IERC1155-TransferBatch-address-address-address-uint256---uint256--- +:xref-IERC1155-ApprovalForAll-address-address-bool-: xref:token/ERC1155.adoc#IERC1155-ApprovalForAll-address-address-bool- +:xref-IERC1155-URI-string-uint256-: xref:token/ERC1155.adoc#IERC1155-URI-string-uint256- +:xref-IERC1155Errors-ERC1155InsufficientBalance-address-uint256-uint256-uint256-: xref:interfaces.adoc#IERC1155Errors-ERC1155InsufficientBalance-address-uint256-uint256-uint256- +:xref-IERC1155Errors-ERC1155InvalidSender-address-: xref:interfaces.adoc#IERC1155Errors-ERC1155InvalidSender-address- +:xref-IERC1155Errors-ERC1155InvalidReceiver-address-: xref:interfaces.adoc#IERC1155Errors-ERC1155InvalidReceiver-address- +:xref-IERC1155Errors-ERC1155MissingApprovalForAll-address-address-: xref:interfaces.adoc#IERC1155Errors-ERC1155MissingApprovalForAll-address-address- +:xref-IERC1155Errors-ERC1155InvalidApprover-address-: xref:interfaces.adoc#IERC1155Errors-ERC1155InvalidApprover-address- +:xref-IERC1155Errors-ERC1155InvalidOperator-address-: xref:interfaces.adoc#IERC1155Errors-ERC1155InvalidOperator-address- +:xref-IERC1155Errors-ERC1155InvalidArrayLength-uint256-uint256-: xref:interfaces.adoc#IERC1155Errors-ERC1155InvalidArrayLength-uint256-uint256- +:IERC165-supportsInterface: pass:normal[xref:utils.adoc#IERC165-supportsInterface-bytes4-[`IERC165.supportsInterface`]] +:IERC1155MetadataURI-uri: pass:normal[xref:token/ERC1155.adoc#IERC1155MetadataURI-uri-uint256-[`IERC1155MetadataURI.uri`]] +:IERC1155-balanceOf: pass:normal[xref:token/ERC1155.adoc#IERC1155-balanceOf-address-uint256-[`IERC1155.balanceOf`]] +:IERC1155-balanceOfBatch: pass:normal[xref:token/ERC1155.adoc#IERC1155-balanceOfBatch-address---uint256---[`IERC1155.balanceOfBatch`]] +:IERC1155-setApprovalForAll: pass:normal[xref:token/ERC1155.adoc#IERC1155-setApprovalForAll-address-bool-[`IERC1155.setApprovalForAll`]] +:IERC1155-isApprovedForAll: pass:normal[xref:token/ERC1155.adoc#IERC1155-isApprovedForAll-address-address-[`IERC1155.isApprovedForAll`]] +:IERC1155-safeTransferFrom: pass:normal[xref:token/ERC1155.adoc#IERC1155-safeTransferFrom-address-address-uint256-uint256-bytes-[`IERC1155.safeTransferFrom`]] +:IERC1155-safeBatchTransferFrom: pass:normal[xref:token/ERC1155.adoc#IERC1155-safeBatchTransferFrom-address-address-uint256---uint256---bytes-[`IERC1155.safeBatchTransferFrom`]] +:IERC1155Receiver-onERC1155Received: pass:normal[xref:token/ERC1155.adoc#IERC1155Receiver-onERC1155Received-address-address-uint256-uint256-bytes-[`IERC1155Receiver.onERC1155Received`]] +:IERC1155Receiver-onERC1155BatchReceived: pass:normal[xref:token/ERC1155.adoc#IERC1155Receiver-onERC1155BatchReceived-address-address-uint256---uint256---bytes-[`IERC1155Receiver.onERC1155BatchReceived`]] +:IERC1155Receiver-onERC1155Received: pass:normal[xref:token/ERC1155.adoc#IERC1155Receiver-onERC1155Received-address-address-uint256-uint256-bytes-[`IERC1155Receiver.onERC1155Received`]] +:IERC1155Receiver-onERC1155BatchReceived: pass:normal[xref:token/ERC1155.adoc#IERC1155Receiver-onERC1155BatchReceived-address-address-uint256---uint256---bytes-[`IERC1155Receiver.onERC1155BatchReceived`]] +:IERC1155Receiver-onERC1155Received: pass:normal[xref:token/ERC1155.adoc#IERC1155Receiver-onERC1155Received-address-address-uint256-uint256-bytes-[`IERC1155Receiver.onERC1155Received`]] +:IERC1155Receiver-onERC1155BatchReceived: pass:normal[xref:token/ERC1155.adoc#IERC1155Receiver-onERC1155BatchReceived-address-address-uint256---uint256---bytes-[`IERC1155Receiver.onERC1155BatchReceived`]] +:IERC1155Receiver-onERC1155Received: pass:normal[xref:token/ERC1155.adoc#IERC1155Receiver-onERC1155Received-address-address-uint256-uint256-bytes-[`IERC1155Receiver.onERC1155Received`]] +:IERC1155Receiver-onERC1155BatchReceived: pass:normal[xref:token/ERC1155.adoc#IERC1155Receiver-onERC1155BatchReceived-address-address-uint256---uint256---bytes-[`IERC1155Receiver.onERC1155BatchReceived`]] +:xref-IERC1155Receiver-onERC1155Received-address-address-uint256-uint256-bytes-: xref:token/ERC1155.adoc#IERC1155Receiver-onERC1155Received-address-address-uint256-uint256-bytes- +:xref-IERC1155Receiver-onERC1155BatchReceived-address-address-uint256---uint256---bytes-: xref:token/ERC1155.adoc#IERC1155Receiver-onERC1155BatchReceived-address-address-uint256---uint256---bytes- +:xref-IERC165-supportsInterface-bytes4-: xref:utils.adoc#IERC165-supportsInterface-bytes4- +:Pausable-_pause: pass:normal[xref:utils.adoc#Pausable-_pause--[`Pausable._pause`]] +:Pausable-_unpause: pass:normal[xref:utils.adoc#Pausable-_unpause--[`Pausable._unpause`]] +:AccessControl: pass:normal[xref:access.adoc#AccessControl[`AccessControl`]] +:Ownable: pass:normal[xref:access.adoc#Ownable[`Ownable`]] +:xref-ERC1155Pausable-_update-address-address-uint256---uint256---: xref:token/ERC1155.adoc#ERC1155Pausable-_update-address-address-uint256---uint256--- +:xref-Pausable-paused--: xref:utils.adoc#Pausable-paused-- +:xref-Pausable-_requireNotPaused--: xref:utils.adoc#Pausable-_requireNotPaused-- +:xref-Pausable-_requirePaused--: xref:utils.adoc#Pausable-_requirePaused-- +:xref-Pausable-_pause--: xref:utils.adoc#Pausable-_pause-- +:xref-Pausable-_unpause--: xref:utils.adoc#Pausable-_unpause-- +:xref-ERC1155-supportsInterface-bytes4-: xref:token/ERC1155.adoc#ERC1155-supportsInterface-bytes4- +:xref-ERC1155-uri-uint256-: xref:token/ERC1155.adoc#ERC1155-uri-uint256- +:xref-ERC1155-balanceOf-address-uint256-: xref:token/ERC1155.adoc#ERC1155-balanceOf-address-uint256- +:xref-ERC1155-balanceOfBatch-address---uint256---: xref:token/ERC1155.adoc#ERC1155-balanceOfBatch-address---uint256--- +:xref-ERC1155-setApprovalForAll-address-bool-: xref:token/ERC1155.adoc#ERC1155-setApprovalForAll-address-bool- +:xref-ERC1155-isApprovedForAll-address-address-: xref:token/ERC1155.adoc#ERC1155-isApprovedForAll-address-address- +:xref-ERC1155-safeTransferFrom-address-address-uint256-uint256-bytes-: xref:token/ERC1155.adoc#ERC1155-safeTransferFrom-address-address-uint256-uint256-bytes- +:xref-ERC1155-safeBatchTransferFrom-address-address-uint256---uint256---bytes-: xref:token/ERC1155.adoc#ERC1155-safeBatchTransferFrom-address-address-uint256---uint256---bytes- +:xref-ERC1155-_updateWithAcceptanceCheck-address-address-uint256---uint256---bytes-: xref:token/ERC1155.adoc#ERC1155-_updateWithAcceptanceCheck-address-address-uint256---uint256---bytes- +:xref-ERC1155-_safeTransferFrom-address-address-uint256-uint256-bytes-: xref:token/ERC1155.adoc#ERC1155-_safeTransferFrom-address-address-uint256-uint256-bytes- +:xref-ERC1155-_safeBatchTransferFrom-address-address-uint256---uint256---bytes-: xref:token/ERC1155.adoc#ERC1155-_safeBatchTransferFrom-address-address-uint256---uint256---bytes- +:xref-ERC1155-_setURI-string-: xref:token/ERC1155.adoc#ERC1155-_setURI-string- +:xref-ERC1155-_mint-address-uint256-uint256-bytes-: xref:token/ERC1155.adoc#ERC1155-_mint-address-uint256-uint256-bytes- +:xref-ERC1155-_mintBatch-address-uint256---uint256---bytes-: xref:token/ERC1155.adoc#ERC1155-_mintBatch-address-uint256---uint256---bytes- +:xref-ERC1155-_burn-address-uint256-uint256-: xref:token/ERC1155.adoc#ERC1155-_burn-address-uint256-uint256- +:xref-ERC1155-_burnBatch-address-uint256---uint256---: xref:token/ERC1155.adoc#ERC1155-_burnBatch-address-uint256---uint256--- +:xref-ERC1155-_setApprovalForAll-address-address-bool-: xref:token/ERC1155.adoc#ERC1155-_setApprovalForAll-address-address-bool- +:xref-Pausable-Paused-address-: xref:utils.adoc#Pausable-Paused-address- +:xref-Pausable-Unpaused-address-: xref:utils.adoc#Pausable-Unpaused-address- +:xref-IERC1155-TransferSingle-address-address-address-uint256-uint256-: xref:token/ERC1155.adoc#IERC1155-TransferSingle-address-address-address-uint256-uint256- +:xref-IERC1155-TransferBatch-address-address-address-uint256---uint256---: xref:token/ERC1155.adoc#IERC1155-TransferBatch-address-address-address-uint256---uint256--- +:xref-IERC1155-ApprovalForAll-address-address-bool-: xref:token/ERC1155.adoc#IERC1155-ApprovalForAll-address-address-bool- +:xref-IERC1155-URI-string-uint256-: xref:token/ERC1155.adoc#IERC1155-URI-string-uint256- +:xref-Pausable-EnforcedPause--: xref:utils.adoc#Pausable-EnforcedPause-- +:xref-Pausable-ExpectedPause--: xref:utils.adoc#Pausable-ExpectedPause-- +:xref-IERC1155Errors-ERC1155InsufficientBalance-address-uint256-uint256-uint256-: xref:interfaces.adoc#IERC1155Errors-ERC1155InsufficientBalance-address-uint256-uint256-uint256- +:xref-IERC1155Errors-ERC1155InvalidSender-address-: xref:interfaces.adoc#IERC1155Errors-ERC1155InvalidSender-address- +:xref-IERC1155Errors-ERC1155InvalidReceiver-address-: xref:interfaces.adoc#IERC1155Errors-ERC1155InvalidReceiver-address- +:xref-IERC1155Errors-ERC1155MissingApprovalForAll-address-address-: xref:interfaces.adoc#IERC1155Errors-ERC1155MissingApprovalForAll-address-address- +:xref-IERC1155Errors-ERC1155InvalidApprover-address-: xref:interfaces.adoc#IERC1155Errors-ERC1155InvalidApprover-address- +:xref-IERC1155Errors-ERC1155InvalidOperator-address-: xref:interfaces.adoc#IERC1155Errors-ERC1155InvalidOperator-address- +:xref-IERC1155Errors-ERC1155InvalidArrayLength-uint256-uint256-: xref:interfaces.adoc#IERC1155Errors-ERC1155InvalidArrayLength-uint256-uint256- +:ERC1155-_update: pass:normal[xref:token/ERC1155.adoc#ERC1155-_update-address-address-uint256---uint256---[`ERC1155._update`]] +:ERC1155: pass:normal[xref:token/ERC1155.adoc#ERC1155[`ERC1155`]] +:xref-ERC1155Burnable-burn-address-uint256-uint256-: xref:token/ERC1155.adoc#ERC1155Burnable-burn-address-uint256-uint256- +:xref-ERC1155Burnable-burnBatch-address-uint256---uint256---: xref:token/ERC1155.adoc#ERC1155Burnable-burnBatch-address-uint256---uint256--- +:xref-ERC1155-supportsInterface-bytes4-: xref:token/ERC1155.adoc#ERC1155-supportsInterface-bytes4- +:xref-ERC1155-uri-uint256-: xref:token/ERC1155.adoc#ERC1155-uri-uint256- +:xref-ERC1155-balanceOf-address-uint256-: xref:token/ERC1155.adoc#ERC1155-balanceOf-address-uint256- +:xref-ERC1155-balanceOfBatch-address---uint256---: xref:token/ERC1155.adoc#ERC1155-balanceOfBatch-address---uint256--- +:xref-ERC1155-setApprovalForAll-address-bool-: xref:token/ERC1155.adoc#ERC1155-setApprovalForAll-address-bool- +:xref-ERC1155-isApprovedForAll-address-address-: xref:token/ERC1155.adoc#ERC1155-isApprovedForAll-address-address- +:xref-ERC1155-safeTransferFrom-address-address-uint256-uint256-bytes-: xref:token/ERC1155.adoc#ERC1155-safeTransferFrom-address-address-uint256-uint256-bytes- +:xref-ERC1155-safeBatchTransferFrom-address-address-uint256---uint256---bytes-: xref:token/ERC1155.adoc#ERC1155-safeBatchTransferFrom-address-address-uint256---uint256---bytes- +:xref-ERC1155-_update-address-address-uint256---uint256---: xref:token/ERC1155.adoc#ERC1155-_update-address-address-uint256---uint256--- +:xref-ERC1155-_updateWithAcceptanceCheck-address-address-uint256---uint256---bytes-: xref:token/ERC1155.adoc#ERC1155-_updateWithAcceptanceCheck-address-address-uint256---uint256---bytes- +:xref-ERC1155-_safeTransferFrom-address-address-uint256-uint256-bytes-: xref:token/ERC1155.adoc#ERC1155-_safeTransferFrom-address-address-uint256-uint256-bytes- +:xref-ERC1155-_safeBatchTransferFrom-address-address-uint256---uint256---bytes-: xref:token/ERC1155.adoc#ERC1155-_safeBatchTransferFrom-address-address-uint256---uint256---bytes- +:xref-ERC1155-_setURI-string-: xref:token/ERC1155.adoc#ERC1155-_setURI-string- +:xref-ERC1155-_mint-address-uint256-uint256-bytes-: xref:token/ERC1155.adoc#ERC1155-_mint-address-uint256-uint256-bytes- +:xref-ERC1155-_mintBatch-address-uint256---uint256---bytes-: xref:token/ERC1155.adoc#ERC1155-_mintBatch-address-uint256---uint256---bytes- +:xref-ERC1155-_burn-address-uint256-uint256-: xref:token/ERC1155.adoc#ERC1155-_burn-address-uint256-uint256- +:xref-ERC1155-_burnBatch-address-uint256---uint256---: xref:token/ERC1155.adoc#ERC1155-_burnBatch-address-uint256---uint256--- +:xref-ERC1155-_setApprovalForAll-address-address-bool-: xref:token/ERC1155.adoc#ERC1155-_setApprovalForAll-address-address-bool- +:xref-IERC1155-TransferSingle-address-address-address-uint256-uint256-: xref:token/ERC1155.adoc#IERC1155-TransferSingle-address-address-address-uint256-uint256- +:xref-IERC1155-TransferBatch-address-address-address-uint256---uint256---: xref:token/ERC1155.adoc#IERC1155-TransferBatch-address-address-address-uint256---uint256--- +:xref-IERC1155-ApprovalForAll-address-address-bool-: xref:token/ERC1155.adoc#IERC1155-ApprovalForAll-address-address-bool- +:xref-IERC1155-URI-string-uint256-: xref:token/ERC1155.adoc#IERC1155-URI-string-uint256- +:xref-IERC1155Errors-ERC1155InsufficientBalance-address-uint256-uint256-uint256-: xref:interfaces.adoc#IERC1155Errors-ERC1155InsufficientBalance-address-uint256-uint256-uint256- +:xref-IERC1155Errors-ERC1155InvalidSender-address-: xref:interfaces.adoc#IERC1155Errors-ERC1155InvalidSender-address- +:xref-IERC1155Errors-ERC1155InvalidReceiver-address-: xref:interfaces.adoc#IERC1155Errors-ERC1155InvalidReceiver-address- +:xref-IERC1155Errors-ERC1155MissingApprovalForAll-address-address-: xref:interfaces.adoc#IERC1155Errors-ERC1155MissingApprovalForAll-address-address- +:xref-IERC1155Errors-ERC1155InvalidApprover-address-: xref:interfaces.adoc#IERC1155Errors-ERC1155InvalidApprover-address- +:xref-IERC1155Errors-ERC1155InvalidOperator-address-: xref:interfaces.adoc#IERC1155Errors-ERC1155InvalidOperator-address- +:xref-IERC1155Errors-ERC1155InvalidArrayLength-uint256-uint256-: xref:interfaces.adoc#IERC1155Errors-ERC1155InvalidArrayLength-uint256-uint256- +:xref-ERC1155Supply-totalSupply-uint256-: xref:token/ERC1155.adoc#ERC1155Supply-totalSupply-uint256- +:xref-ERC1155Supply-totalSupply--: xref:token/ERC1155.adoc#ERC1155Supply-totalSupply-- +:xref-ERC1155Supply-exists-uint256-: xref:token/ERC1155.adoc#ERC1155Supply-exists-uint256- +:xref-ERC1155Supply-_update-address-address-uint256---uint256---: xref:token/ERC1155.adoc#ERC1155Supply-_update-address-address-uint256---uint256--- +:xref-ERC1155-supportsInterface-bytes4-: xref:token/ERC1155.adoc#ERC1155-supportsInterface-bytes4- +:xref-ERC1155-uri-uint256-: xref:token/ERC1155.adoc#ERC1155-uri-uint256- +:xref-ERC1155-balanceOf-address-uint256-: xref:token/ERC1155.adoc#ERC1155-balanceOf-address-uint256- +:xref-ERC1155-balanceOfBatch-address---uint256---: xref:token/ERC1155.adoc#ERC1155-balanceOfBatch-address---uint256--- +:xref-ERC1155-setApprovalForAll-address-bool-: xref:token/ERC1155.adoc#ERC1155-setApprovalForAll-address-bool- +:xref-ERC1155-isApprovedForAll-address-address-: xref:token/ERC1155.adoc#ERC1155-isApprovedForAll-address-address- +:xref-ERC1155-safeTransferFrom-address-address-uint256-uint256-bytes-: xref:token/ERC1155.adoc#ERC1155-safeTransferFrom-address-address-uint256-uint256-bytes- +:xref-ERC1155-safeBatchTransferFrom-address-address-uint256---uint256---bytes-: xref:token/ERC1155.adoc#ERC1155-safeBatchTransferFrom-address-address-uint256---uint256---bytes- +:xref-ERC1155-_updateWithAcceptanceCheck-address-address-uint256---uint256---bytes-: xref:token/ERC1155.adoc#ERC1155-_updateWithAcceptanceCheck-address-address-uint256---uint256---bytes- +:xref-ERC1155-_safeTransferFrom-address-address-uint256-uint256-bytes-: xref:token/ERC1155.adoc#ERC1155-_safeTransferFrom-address-address-uint256-uint256-bytes- +:xref-ERC1155-_safeBatchTransferFrom-address-address-uint256---uint256---bytes-: xref:token/ERC1155.adoc#ERC1155-_safeBatchTransferFrom-address-address-uint256---uint256---bytes- +:xref-ERC1155-_setURI-string-: xref:token/ERC1155.adoc#ERC1155-_setURI-string- +:xref-ERC1155-_mint-address-uint256-uint256-bytes-: xref:token/ERC1155.adoc#ERC1155-_mint-address-uint256-uint256-bytes- +:xref-ERC1155-_mintBatch-address-uint256---uint256---bytes-: xref:token/ERC1155.adoc#ERC1155-_mintBatch-address-uint256---uint256---bytes- +:xref-ERC1155-_burn-address-uint256-uint256-: xref:token/ERC1155.adoc#ERC1155-_burn-address-uint256-uint256- +:xref-ERC1155-_burnBatch-address-uint256---uint256---: xref:token/ERC1155.adoc#ERC1155-_burnBatch-address-uint256---uint256--- +:xref-ERC1155-_setApprovalForAll-address-address-bool-: xref:token/ERC1155.adoc#ERC1155-_setApprovalForAll-address-address-bool- +:xref-IERC1155-TransferSingle-address-address-address-uint256-uint256-: xref:token/ERC1155.adoc#IERC1155-TransferSingle-address-address-address-uint256-uint256- +:xref-IERC1155-TransferBatch-address-address-address-uint256---uint256---: xref:token/ERC1155.adoc#IERC1155-TransferBatch-address-address-address-uint256---uint256--- +:xref-IERC1155-ApprovalForAll-address-address-bool-: xref:token/ERC1155.adoc#IERC1155-ApprovalForAll-address-address-bool- +:xref-IERC1155-URI-string-uint256-: xref:token/ERC1155.adoc#IERC1155-URI-string-uint256- +:xref-IERC1155Errors-ERC1155InsufficientBalance-address-uint256-uint256-uint256-: xref:interfaces.adoc#IERC1155Errors-ERC1155InsufficientBalance-address-uint256-uint256-uint256- +:xref-IERC1155Errors-ERC1155InvalidSender-address-: xref:interfaces.adoc#IERC1155Errors-ERC1155InvalidSender-address- +:xref-IERC1155Errors-ERC1155InvalidReceiver-address-: xref:interfaces.adoc#IERC1155Errors-ERC1155InvalidReceiver-address- +:xref-IERC1155Errors-ERC1155MissingApprovalForAll-address-address-: xref:interfaces.adoc#IERC1155Errors-ERC1155MissingApprovalForAll-address-address- +:xref-IERC1155Errors-ERC1155InvalidApprover-address-: xref:interfaces.adoc#IERC1155Errors-ERC1155InvalidApprover-address- +:xref-IERC1155Errors-ERC1155InvalidOperator-address-: xref:interfaces.adoc#IERC1155Errors-ERC1155InvalidOperator-address- +:xref-IERC1155Errors-ERC1155InvalidArrayLength-uint256-uint256-: xref:interfaces.adoc#IERC1155Errors-ERC1155InvalidArrayLength-uint256-uint256- +:ERC1155-_update: pass:normal[xref:token/ERC1155.adoc#ERC1155-_update-address-address-uint256---uint256---[`ERC1155._update`]] +:xref-ERC1155URIStorage-uri-uint256-: xref:token/ERC1155.adoc#ERC1155URIStorage-uri-uint256- +:xref-ERC1155URIStorage-_setURI-uint256-string-: xref:token/ERC1155.adoc#ERC1155URIStorage-_setURI-uint256-string- +:xref-ERC1155URIStorage-_setBaseURI-string-: xref:token/ERC1155.adoc#ERC1155URIStorage-_setBaseURI-string- +:xref-ERC1155-supportsInterface-bytes4-: xref:token/ERC1155.adoc#ERC1155-supportsInterface-bytes4- +:xref-ERC1155-balanceOf-address-uint256-: xref:token/ERC1155.adoc#ERC1155-balanceOf-address-uint256- +:xref-ERC1155-balanceOfBatch-address---uint256---: xref:token/ERC1155.adoc#ERC1155-balanceOfBatch-address---uint256--- +:xref-ERC1155-setApprovalForAll-address-bool-: xref:token/ERC1155.adoc#ERC1155-setApprovalForAll-address-bool- +:xref-ERC1155-isApprovedForAll-address-address-: xref:token/ERC1155.adoc#ERC1155-isApprovedForAll-address-address- +:xref-ERC1155-safeTransferFrom-address-address-uint256-uint256-bytes-: xref:token/ERC1155.adoc#ERC1155-safeTransferFrom-address-address-uint256-uint256-bytes- +:xref-ERC1155-safeBatchTransferFrom-address-address-uint256---uint256---bytes-: xref:token/ERC1155.adoc#ERC1155-safeBatchTransferFrom-address-address-uint256---uint256---bytes- +:xref-ERC1155-_update-address-address-uint256---uint256---: xref:token/ERC1155.adoc#ERC1155-_update-address-address-uint256---uint256--- +:xref-ERC1155-_updateWithAcceptanceCheck-address-address-uint256---uint256---bytes-: xref:token/ERC1155.adoc#ERC1155-_updateWithAcceptanceCheck-address-address-uint256---uint256---bytes- +:xref-ERC1155-_safeTransferFrom-address-address-uint256-uint256-bytes-: xref:token/ERC1155.adoc#ERC1155-_safeTransferFrom-address-address-uint256-uint256-bytes- +:xref-ERC1155-_safeBatchTransferFrom-address-address-uint256---uint256---bytes-: xref:token/ERC1155.adoc#ERC1155-_safeBatchTransferFrom-address-address-uint256---uint256---bytes- +:xref-ERC1155-_setURI-string-: xref:token/ERC1155.adoc#ERC1155-_setURI-string- +:xref-ERC1155-_mint-address-uint256-uint256-bytes-: xref:token/ERC1155.adoc#ERC1155-_mint-address-uint256-uint256-bytes- +:xref-ERC1155-_mintBatch-address-uint256---uint256---bytes-: xref:token/ERC1155.adoc#ERC1155-_mintBatch-address-uint256---uint256---bytes- +:xref-ERC1155-_burn-address-uint256-uint256-: xref:token/ERC1155.adoc#ERC1155-_burn-address-uint256-uint256- +:xref-ERC1155-_burnBatch-address-uint256---uint256---: xref:token/ERC1155.adoc#ERC1155-_burnBatch-address-uint256---uint256--- +:xref-ERC1155-_setApprovalForAll-address-address-bool-: xref:token/ERC1155.adoc#ERC1155-_setApprovalForAll-address-address-bool- +:xref-IERC1155-TransferSingle-address-address-address-uint256-uint256-: xref:token/ERC1155.adoc#IERC1155-TransferSingle-address-address-address-uint256-uint256- +:xref-IERC1155-TransferBatch-address-address-address-uint256---uint256---: xref:token/ERC1155.adoc#IERC1155-TransferBatch-address-address-address-uint256---uint256--- +:xref-IERC1155-ApprovalForAll-address-address-bool-: xref:token/ERC1155.adoc#IERC1155-ApprovalForAll-address-address-bool- +:xref-IERC1155-URI-string-uint256-: xref:token/ERC1155.adoc#IERC1155-URI-string-uint256- +:xref-IERC1155Errors-ERC1155InsufficientBalance-address-uint256-uint256-uint256-: xref:interfaces.adoc#IERC1155Errors-ERC1155InsufficientBalance-address-uint256-uint256-uint256- +:xref-IERC1155Errors-ERC1155InvalidSender-address-: xref:interfaces.adoc#IERC1155Errors-ERC1155InvalidSender-address- +:xref-IERC1155Errors-ERC1155InvalidReceiver-address-: xref:interfaces.adoc#IERC1155Errors-ERC1155InvalidReceiver-address- +:xref-IERC1155Errors-ERC1155MissingApprovalForAll-address-address-: xref:interfaces.adoc#IERC1155Errors-ERC1155MissingApprovalForAll-address-address- +:xref-IERC1155Errors-ERC1155InvalidApprover-address-: xref:interfaces.adoc#IERC1155Errors-ERC1155InvalidApprover-address- +:xref-IERC1155Errors-ERC1155InvalidOperator-address-: xref:interfaces.adoc#IERC1155Errors-ERC1155InvalidOperator-address- +:xref-IERC1155Errors-ERC1155InvalidArrayLength-uint256-uint256-: xref:interfaces.adoc#IERC1155Errors-ERC1155InvalidArrayLength-uint256-uint256- +:IERC1155MetadataURI-uri: pass:normal[xref:token/ERC1155.adoc#IERC1155MetadataURI-uri-uint256-[`IERC1155MetadataURI.uri`]] +:xref-ERC1155Holder-supportsInterface-bytes4-: xref:token/ERC1155.adoc#ERC1155Holder-supportsInterface-bytes4- +:xref-ERC1155Holder-onERC1155Received-address-address-uint256-uint256-bytes-: xref:token/ERC1155.adoc#ERC1155Holder-onERC1155Received-address-address-uint256-uint256-bytes- +:xref-ERC1155Holder-onERC1155BatchReceived-address-address-uint256---uint256---bytes-: xref:token/ERC1155.adoc#ERC1155Holder-onERC1155BatchReceived-address-address-uint256---uint256---bytes- +:IERC165-supportsInterface: pass:normal[xref:utils.adoc#IERC165-supportsInterface-bytes4-[`IERC165.supportsInterface`]] += ERC 1155 + +[.readme-notice] +NOTE: This document is better viewed at https://docs.openzeppelin.com/contracts/api/token/erc1155 + +This set of interfaces and contracts are all related to the https://eips.ethereum.org/EIPS/eip-1155[ERC1155 Multi Token Standard]. + +The EIP consists of three interfaces which fulfill different roles, found here as {IERC1155}, {IERC1155MetadataURI} and {IERC1155Receiver}. + +{ERC1155} implements the mandatory {IERC1155} interface, as well as the optional extension {IERC1155MetadataURI}, by relying on the substitution mechanism to use the same URI for all token types, dramatically reducing gas costs. + +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. + +== Core + +:TransferSingle: pass:normal[xref:#IERC1155-TransferSingle-address-address-address-uint256-uint256-[`++TransferSingle++`]] +:TransferBatch: pass:normal[xref:#IERC1155-TransferBatch-address-address-address-uint256---uint256---[`++TransferBatch++`]] +:ApprovalForAll: pass:normal[xref:#IERC1155-ApprovalForAll-address-address-bool-[`++ApprovalForAll++`]] +:URI: pass:normal[xref:#IERC1155-URI-string-uint256-[`++URI++`]] +:balanceOf: pass:normal[xref:#IERC1155-balanceOf-address-uint256-[`++balanceOf++`]] +:balanceOfBatch: pass:normal[xref:#IERC1155-balanceOfBatch-address---uint256---[`++balanceOfBatch++`]] +:setApprovalForAll: pass:normal[xref:#IERC1155-setApprovalForAll-address-bool-[`++setApprovalForAll++`]] +:isApprovedForAll: pass:normal[xref:#IERC1155-isApprovedForAll-address-address-[`++isApprovedForAll++`]] +:safeTransferFrom: pass:normal[xref:#IERC1155-safeTransferFrom-address-address-uint256-uint256-bytes-[`++safeTransferFrom++`]] +:safeBatchTransferFrom: pass:normal[xref:#IERC1155-safeBatchTransferFrom-address-address-uint256---uint256---bytes-[`++safeBatchTransferFrom++`]] + +[.contract] +[[IERC1155]] +=== `++IERC1155++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/token/ERC1155/IERC1155.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/token/ERC1155/IERC1155.sol"; +``` + +Required interface of an ERC1155 compliant contract, as defined in the +https://eips.ethereum.org/EIPS/eip-1155[EIP]. + +[.contract-index] +.Functions +-- +* {xref-IERC1155-balanceOf-address-uint256-}[`++balanceOf(account, id)++`] +* {xref-IERC1155-balanceOfBatch-address---uint256---}[`++balanceOfBatch(accounts, ids)++`] +* {xref-IERC1155-setApprovalForAll-address-bool-}[`++setApprovalForAll(operator, approved)++`] +* {xref-IERC1155-isApprovedForAll-address-address-}[`++isApprovedForAll(account, operator)++`] +* {xref-IERC1155-safeTransferFrom-address-address-uint256-uint256-bytes-}[`++safeTransferFrom(from, to, id, value, data)++`] +* {xref-IERC1155-safeBatchTransferFrom-address-address-uint256---uint256---bytes-}[`++safeBatchTransferFrom(from, to, ids, values, data)++`] + +[.contract-subindex-inherited] +.IERC165 +* {xref-IERC165-supportsInterface-bytes4-}[`++supportsInterface(interfaceId)++`] + +-- + +[.contract-index] +.Events +-- +* {xref-IERC1155-TransferSingle-address-address-address-uint256-uint256-}[`++TransferSingle(operator, from, to, id, value)++`] +* {xref-IERC1155-TransferBatch-address-address-address-uint256---uint256---}[`++TransferBatch(operator, from, to, ids, values)++`] +* {xref-IERC1155-ApprovalForAll-address-address-bool-}[`++ApprovalForAll(account, operator, approved)++`] +* {xref-IERC1155-URI-string-uint256-}[`++URI(value, id)++`] + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-item] +[[IERC1155-balanceOf-address-uint256-]] +==== `[.contract-item-name]#++balanceOf++#++(address account, uint256 id) β†’ uint256++` [.item-kind]#external# + +Returns the value of tokens of token type `id` owned by `account`. + +Requirements: + +- `account` cannot be the zero address. + +[.contract-item] +[[IERC1155-balanceOfBatch-address---uint256---]] +==== `[.contract-item-name]#++balanceOfBatch++#++(address[] accounts, uint256[] ids) β†’ uint256[]++` [.item-kind]#external# + +xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {balanceOf}. + +Requirements: + +- `accounts` and `ids` must have the same length. + +[.contract-item] +[[IERC1155-setApprovalForAll-address-bool-]] +==== `[.contract-item-name]#++setApprovalForAll++#++(address operator, bool approved)++` [.item-kind]#external# + +Grants or revokes permission to `operator` to transfer the caller's tokens, according to `approved`, + +Emits an {ApprovalForAll} event. + +Requirements: + +- `operator` cannot be the caller. + +[.contract-item] +[[IERC1155-isApprovedForAll-address-address-]] +==== `[.contract-item-name]#++isApprovedForAll++#++(address account, address operator) β†’ bool++` [.item-kind]#external# + +Returns true if `operator` is approved to transfer ``account``'s tokens. + +See {setApprovalForAll}. + +[.contract-item] +[[IERC1155-safeTransferFrom-address-address-uint256-uint256-bytes-]] +==== `[.contract-item-name]#++safeTransferFrom++#++(address from, address to, uint256 id, uint256 value, bytes data)++` [.item-kind]#external# + +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. +Ensure to follow the checks-effects-interactions pattern and consider employing +reentrancy guards when interacting with untrusted contracts. + +Emits a {TransferSingle} event. + +Requirements: + +- `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 `value` amount. +- If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the +acceptance magic value. + +[.contract-item] +[[IERC1155-safeBatchTransferFrom-address-address-uint256---uint256---bytes-]] +==== `[.contract-item-name]#++safeBatchTransferFrom++#++(address from, address to, uint256[] ids, uint256[] values, bytes data)++` [.item-kind]#external# + +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 {onERC1155BatchReceived} 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: + +- `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. + +[.contract-item] +[[IERC1155-TransferSingle-address-address-address-uint256-uint256-]] +==== `[.contract-item-name]#++TransferSingle++#++(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value)++` [.item-kind]#event# + +Emitted when `value` amount of tokens of type `id` are transferred from `from` to `to` by `operator`. + +[.contract-item] +[[IERC1155-TransferBatch-address-address-address-uint256---uint256---]] +==== `[.contract-item-name]#++TransferBatch++#++(address indexed operator, address indexed from, address indexed to, uint256[] ids, uint256[] values)++` [.item-kind]#event# + +Equivalent to multiple {TransferSingle} events, where `operator`, `from` and `to` are the same for all +transfers. + +[.contract-item] +[[IERC1155-ApprovalForAll-address-address-bool-]] +==== `[.contract-item-name]#++ApprovalForAll++#++(address indexed account, address indexed operator, bool approved)++` [.item-kind]#event# + +Emitted when `account` grants or revokes permission to `operator` to transfer their tokens, according to +`approved`. + +[.contract-item] +[[IERC1155-URI-string-uint256-]] +==== `[.contract-item-name]#++URI++#++(string value, uint256 indexed id)++` [.item-kind]#event# + +Emitted when the URI for token type `id` changes to `value`, if it is a non-programmatic URI. + +If an {URI} event was emitted for `id`, the standard +https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions[guarantees] that `value` will equal the value +returned by {IERC1155MetadataURI-uri}. + +:uri: pass:normal[xref:#IERC1155MetadataURI-uri-uint256-[`++uri++`]] + +[.contract] +[[IERC1155MetadataURI]] +=== `++IERC1155MetadataURI++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol"; +``` + +Interface of the optional ERC1155MetadataExtension interface, as defined +in the https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions[EIP]. + +[.contract-index] +.Functions +-- +* {xref-IERC1155MetadataURI-uri-uint256-}[`++uri(id)++`] + +[.contract-subindex-inherited] +.IERC1155 +* {xref-IERC1155-balanceOf-address-uint256-}[`++balanceOf(account, id)++`] +* {xref-IERC1155-balanceOfBatch-address---uint256---}[`++balanceOfBatch(accounts, ids)++`] +* {xref-IERC1155-setApprovalForAll-address-bool-}[`++setApprovalForAll(operator, approved)++`] +* {xref-IERC1155-isApprovedForAll-address-address-}[`++isApprovedForAll(account, operator)++`] +* {xref-IERC1155-safeTransferFrom-address-address-uint256-uint256-bytes-}[`++safeTransferFrom(from, to, id, value, data)++`] +* {xref-IERC1155-safeBatchTransferFrom-address-address-uint256---uint256---bytes-}[`++safeBatchTransferFrom(from, to, ids, values, data)++`] + +[.contract-subindex-inherited] +.IERC165 +* {xref-IERC165-supportsInterface-bytes4-}[`++supportsInterface(interfaceId)++`] + +-- + +[.contract-index] +.Events +-- + +[.contract-subindex-inherited] +.IERC1155 +* {xref-IERC1155-TransferSingle-address-address-address-uint256-uint256-}[`++TransferSingle(operator, from, to, id, value)++`] +* {xref-IERC1155-TransferBatch-address-address-address-uint256---uint256---}[`++TransferBatch(operator, from, to, ids, values)++`] +* {xref-IERC1155-ApprovalForAll-address-address-bool-}[`++ApprovalForAll(account, operator, approved)++`] +* {xref-IERC1155-URI-string-uint256-}[`++URI(value, id)++`] + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-item] +[[IERC1155MetadataURI-uri-uint256-]] +==== `[.contract-item-name]#++uri++#++(uint256 id) β†’ string++` [.item-kind]#external# + +Returns the URI for token type `id`. + +If the `\{id\}` substring is present in the URI, it must be replaced by +clients with the actual token type ID. + +:constructor: pass:normal[xref:#ERC1155-constructor-string-[`++constructor++`]] +:supportsInterface: pass:normal[xref:#ERC1155-supportsInterface-bytes4-[`++supportsInterface++`]] +:uri: pass:normal[xref:#ERC1155-uri-uint256-[`++uri++`]] +:balanceOf: pass:normal[xref:#ERC1155-balanceOf-address-uint256-[`++balanceOf++`]] +:balanceOfBatch: pass:normal[xref:#ERC1155-balanceOfBatch-address---uint256---[`++balanceOfBatch++`]] +:setApprovalForAll: pass:normal[xref:#ERC1155-setApprovalForAll-address-bool-[`++setApprovalForAll++`]] +:isApprovedForAll: pass:normal[xref:#ERC1155-isApprovedForAll-address-address-[`++isApprovedForAll++`]] +:safeTransferFrom: pass:normal[xref:#ERC1155-safeTransferFrom-address-address-uint256-uint256-bytes-[`++safeTransferFrom++`]] +:safeBatchTransferFrom: pass:normal[xref:#ERC1155-safeBatchTransferFrom-address-address-uint256---uint256---bytes-[`++safeBatchTransferFrom++`]] +:_update: pass:normal[xref:#ERC1155-_update-address-address-uint256---uint256---[`++_update++`]] +:_updateWithAcceptanceCheck: pass:normal[xref:#ERC1155-_updateWithAcceptanceCheck-address-address-uint256---uint256---bytes-[`++_updateWithAcceptanceCheck++`]] +:_safeTransferFrom: pass:normal[xref:#ERC1155-_safeTransferFrom-address-address-uint256-uint256-bytes-[`++_safeTransferFrom++`]] +:_safeBatchTransferFrom: pass:normal[xref:#ERC1155-_safeBatchTransferFrom-address-address-uint256---uint256---bytes-[`++_safeBatchTransferFrom++`]] +:_setURI: pass:normal[xref:#ERC1155-_setURI-string-[`++_setURI++`]] +:_mint: pass:normal[xref:#ERC1155-_mint-address-uint256-uint256-bytes-[`++_mint++`]] +:_mintBatch: pass:normal[xref:#ERC1155-_mintBatch-address-uint256---uint256---bytes-[`++_mintBatch++`]] +:_burn: pass:normal[xref:#ERC1155-_burn-address-uint256-uint256-[`++_burn++`]] +:_burnBatch: pass:normal[xref:#ERC1155-_burnBatch-address-uint256---uint256---[`++_burnBatch++`]] +:_setApprovalForAll: pass:normal[xref:#ERC1155-_setApprovalForAll-address-address-bool-[`++_setApprovalForAll++`]] + +[.contract] +[[ERC1155]] +=== `++ERC1155++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/token/ERC1155/ERC1155.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; +``` + +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 + +[.contract-index] +.Functions +-- +* {xref-ERC1155-constructor-string-}[`++constructor(uri_)++`] +* {xref-ERC1155-supportsInterface-bytes4-}[`++supportsInterface(interfaceId)++`] +* {xref-ERC1155-uri-uint256-}[`++uri()++`] +* {xref-ERC1155-balanceOf-address-uint256-}[`++balanceOf(account, id)++`] +* {xref-ERC1155-balanceOfBatch-address---uint256---}[`++balanceOfBatch(accounts, ids)++`] +* {xref-ERC1155-setApprovalForAll-address-bool-}[`++setApprovalForAll(operator, approved)++`] +* {xref-ERC1155-isApprovedForAll-address-address-}[`++isApprovedForAll(account, operator)++`] +* {xref-ERC1155-safeTransferFrom-address-address-uint256-uint256-bytes-}[`++safeTransferFrom(from, to, id, value, data)++`] +* {xref-ERC1155-safeBatchTransferFrom-address-address-uint256---uint256---bytes-}[`++safeBatchTransferFrom(from, to, ids, values, data)++`] +* {xref-ERC1155-_update-address-address-uint256---uint256---}[`++_update(from, to, ids, values)++`] +* {xref-ERC1155-_updateWithAcceptanceCheck-address-address-uint256---uint256---bytes-}[`++_updateWithAcceptanceCheck(from, to, ids, values, data)++`] +* {xref-ERC1155-_safeTransferFrom-address-address-uint256-uint256-bytes-}[`++_safeTransferFrom(from, to, id, value, data)++`] +* {xref-ERC1155-_safeBatchTransferFrom-address-address-uint256---uint256---bytes-}[`++_safeBatchTransferFrom(from, to, ids, values, data)++`] +* {xref-ERC1155-_setURI-string-}[`++_setURI(newuri)++`] +* {xref-ERC1155-_mint-address-uint256-uint256-bytes-}[`++_mint(to, id, value, data)++`] +* {xref-ERC1155-_mintBatch-address-uint256---uint256---bytes-}[`++_mintBatch(to, ids, values, data)++`] +* {xref-ERC1155-_burn-address-uint256-uint256-}[`++_burn(from, id, value)++`] +* {xref-ERC1155-_burnBatch-address-uint256---uint256---}[`++_burnBatch(from, ids, values)++`] +* {xref-ERC1155-_setApprovalForAll-address-address-bool-}[`++_setApprovalForAll(owner, operator, approved)++`] + +[.contract-subindex-inherited] +.IERC1155Errors + +[.contract-subindex-inherited] +.IERC1155MetadataURI + +[.contract-subindex-inherited] +.IERC1155 + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-index] +.Events +-- + +[.contract-subindex-inherited] +.IERC1155Errors + +[.contract-subindex-inherited] +.IERC1155MetadataURI + +[.contract-subindex-inherited] +.IERC1155 +* {xref-IERC1155-TransferSingle-address-address-address-uint256-uint256-}[`++TransferSingle(operator, from, to, id, value)++`] +* {xref-IERC1155-TransferBatch-address-address-address-uint256---uint256---}[`++TransferBatch(operator, from, to, ids, values)++`] +* {xref-IERC1155-ApprovalForAll-address-address-bool-}[`++ApprovalForAll(account, operator, approved)++`] +* {xref-IERC1155-URI-string-uint256-}[`++URI(value, id)++`] + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-index] +.Errors +-- + +[.contract-subindex-inherited] +.IERC1155Errors +* {xref-IERC1155Errors-ERC1155InsufficientBalance-address-uint256-uint256-uint256-}[`++ERC1155InsufficientBalance(sender, balance, needed, tokenId)++`] +* {xref-IERC1155Errors-ERC1155InvalidSender-address-}[`++ERC1155InvalidSender(sender)++`] +* {xref-IERC1155Errors-ERC1155InvalidReceiver-address-}[`++ERC1155InvalidReceiver(receiver)++`] +* {xref-IERC1155Errors-ERC1155MissingApprovalForAll-address-address-}[`++ERC1155MissingApprovalForAll(operator, owner)++`] +* {xref-IERC1155Errors-ERC1155InvalidApprover-address-}[`++ERC1155InvalidApprover(approver)++`] +* {xref-IERC1155Errors-ERC1155InvalidOperator-address-}[`++ERC1155InvalidOperator(operator)++`] +* {xref-IERC1155Errors-ERC1155InvalidArrayLength-uint256-uint256-}[`++ERC1155InvalidArrayLength(idsLength, valuesLength)++`] + +[.contract-subindex-inherited] +.IERC1155MetadataURI + +[.contract-subindex-inherited] +.IERC1155 + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-item] +[[ERC1155-constructor-string-]] +==== `[.contract-item-name]#++constructor++#++(string uri_)++` [.item-kind]#internal# + +See {_setURI}. + +[.contract-item] +[[ERC1155-supportsInterface-bytes4-]] +==== `[.contract-item-name]#++supportsInterface++#++(bytes4 interfaceId) β†’ bool++` [.item-kind]#public# + +See {IERC165-supportsInterface}. + +[.contract-item] +[[ERC1155-uri-uint256-]] +==== `[.contract-item-name]#++uri++#++(uint256) β†’ string++` [.item-kind]#public# + +See {IERC1155MetadataURI-uri}. + +This implementation returns the same URI for *all* token types. It relies +on the token type ID substitution mechanism +https://eips.ethereum.org/EIPS/eip-1155#metadata[defined in the EIP]. + +Clients calling this function must replace the `\{id\}` substring with the +actual token type ID. + +[.contract-item] +[[ERC1155-balanceOf-address-uint256-]] +==== `[.contract-item-name]#++balanceOf++#++(address account, uint256 id) β†’ uint256++` [.item-kind]#public# + +See {IERC1155-balanceOf}. + +[.contract-item] +[[ERC1155-balanceOfBatch-address---uint256---]] +==== `[.contract-item-name]#++balanceOfBatch++#++(address[] accounts, uint256[] ids) β†’ uint256[]++` [.item-kind]#public# + +See {IERC1155-balanceOfBatch}. + +Requirements: + +- `accounts` and `ids` must have the same length. + +[.contract-item] +[[ERC1155-setApprovalForAll-address-bool-]] +==== `[.contract-item-name]#++setApprovalForAll++#++(address operator, bool approved)++` [.item-kind]#public# + +See {IERC1155-setApprovalForAll}. + +[.contract-item] +[[ERC1155-isApprovedForAll-address-address-]] +==== `[.contract-item-name]#++isApprovedForAll++#++(address account, address operator) β†’ bool++` [.item-kind]#public# + +See {IERC1155-isApprovedForAll}. + +[.contract-item] +[[ERC1155-safeTransferFrom-address-address-uint256-uint256-bytes-]] +==== `[.contract-item-name]#++safeTransferFrom++#++(address from, address to, uint256 id, uint256 value, bytes data)++` [.item-kind]#public# + +See {IERC1155-safeTransferFrom}. + +[.contract-item] +[[ERC1155-safeBatchTransferFrom-address-address-uint256---uint256---bytes-]] +==== `[.contract-item-name]#++safeBatchTransferFrom++#++(address from, address to, uint256[] ids, uint256[] values, bytes data)++` [.item-kind]#public# + +See {IERC1155-safeBatchTransferFrom}. + +[.contract-item] +[[ERC1155-_update-address-address-uint256---uint256---]] +==== `[.contract-item-name]#++_update++#++(address from, address to, uint256[] ids, uint256[] values)++` [.item-kind]#internal# + +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. + +Requirements: + +- 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. + +[.contract-item] +[[ERC1155-_updateWithAcceptanceCheck-address-address-uint256---uint256---bytes-]] +==== `[.contract-item-name]#++_updateWithAcceptanceCheck++#++(address from, address to, uint256[] ids, uint256[] values, bytes data)++` [.item-kind]#internal# + +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. + +[.contract-item] +[[ERC1155-_safeTransferFrom-address-address-uint256-uint256-bytes-]] +==== `[.contract-item-name]#++_safeTransferFrom++#++(address from, address to, uint256 id, uint256 value, bytes data)++` [.item-kind]#internal# + +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 `value` amount. +- If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the +acceptance magic value. + +[.contract-item] +[[ERC1155-_safeBatchTransferFrom-address-address-uint256---uint256---bytes-]] +==== `[.contract-item-name]#++_safeBatchTransferFrom++#++(address from, address to, uint256[] ids, uint256[] values, bytes data)++` [.item-kind]#internal# + +xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {_safeTransferFrom}. + +Emits a {TransferBatch} event. + +Requirements: + +- 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. + +[.contract-item] +[[ERC1155-_setURI-string-]] +==== `[.contract-item-name]#++_setURI++#++(string newuri)++` [.item-kind]#internal# + +Sets a new URI for all token types, by relying on the token type ID +substitution mechanism +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 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 +interpreted by clients as +`https://token-cdn-domain/000000000000000000000000000000000000000000000000000000000004cce0.json` +for token type ID 0x4cce0. + +See {uri}. + +Because these URIs cannot be meaningfully represented by the {URI} event, +this function emits no events. + +[.contract-item] +[[ERC1155-_mint-address-uint256-uint256-bytes-]] +==== `[.contract-item-name]#++_mint++#++(address to, uint256 id, uint256 value, bytes data)++` [.item-kind]#internal# + +Creates a `value` amount of tokens of type `id`, and assigns them to `to`. + +Emits a {TransferSingle} event. + +Requirements: + +- `to` cannot be the zero address. +- If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the +acceptance magic value. + +[.contract-item] +[[ERC1155-_mintBatch-address-uint256---uint256---bytes-]] +==== `[.contract-item-name]#++_mintBatch++#++(address to, uint256[] ids, uint256[] values, bytes data)++` [.item-kind]#internal# + +xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {_mint}. + +Emits a {TransferBatch} event. + +Requirements: + +- `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. + +[.contract-item] +[[ERC1155-_burn-address-uint256-uint256-]] +==== `[.contract-item-name]#++_burn++#++(address from, uint256 id, uint256 value)++` [.item-kind]#internal# + +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 `value` amount of tokens of type `id`. + +[.contract-item] +[[ERC1155-_burnBatch-address-uint256---uint256---]] +==== `[.contract-item-name]#++_burnBatch++#++(address from, uint256[] ids, uint256[] values)++` [.item-kind]#internal# + +xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {_burn}. + +Emits a {TransferBatch} event. + +Requirements: + +- `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. + +[.contract-item] +[[ERC1155-_setApprovalForAll-address-address-bool-]] +==== `[.contract-item-name]#++_setApprovalForAll++#++(address owner, address operator, bool approved)++` [.item-kind]#internal# + +Approve `operator` to operate on all of `owner` tokens + +Emits an {ApprovalForAll} event. + +Requirements: + +- `operator` cannot be the zero address. + +:onERC1155Received: pass:normal[xref:#IERC1155Receiver-onERC1155Received-address-address-uint256-uint256-bytes-[`++onERC1155Received++`]] +:onERC1155BatchReceived: pass:normal[xref:#IERC1155Receiver-onERC1155BatchReceived-address-address-uint256---uint256---bytes-[`++onERC1155BatchReceived++`]] + +[.contract] +[[IERC1155Receiver]] +=== `++IERC1155Receiver++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/token/ERC1155/IERC1155Receiver.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol"; +``` + +Interface that must be implemented by smart contracts in order to receive +ERC-1155 token transfers. + +[.contract-index] +.Functions +-- +* {xref-IERC1155Receiver-onERC1155Received-address-address-uint256-uint256-bytes-}[`++onERC1155Received(operator, from, id, value, data)++`] +* {xref-IERC1155Receiver-onERC1155BatchReceived-address-address-uint256---uint256---bytes-}[`++onERC1155BatchReceived(operator, from, ids, values, data)++`] + +[.contract-subindex-inherited] +.IERC165 +* {xref-IERC165-supportsInterface-bytes4-}[`++supportsInterface(interfaceId)++`] + +-- + +[.contract-item] +[[IERC1155Receiver-onERC1155Received-address-address-uint256-uint256-bytes-]] +==== `[.contract-item-name]#++onERC1155Received++#++(address operator, address from, uint256 id, uint256 value, bytes data) β†’ bytes4++` [.item-kind]#external# + +Handles the receipt of a single ERC1155 token type. This function is +called at the end of a `safeTransferFrom` after the balance has been updated. + +NOTE: To accept the transfer, this must return +`bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` +(i.e. 0xf23a6e61, or its own function selector). + +[.contract-item] +[[IERC1155Receiver-onERC1155BatchReceived-address-address-uint256---uint256---bytes-]] +==== `[.contract-item-name]#++onERC1155BatchReceived++#++(address operator, address from, uint256[] ids, uint256[] values, bytes data) β†’ bytes4++` [.item-kind]#external# + +Handles the receipt of a multiple ERC1155 token types. This function +is called at the end of a `safeBatchTransferFrom` after the balances have +been updated. + +NOTE: To accept the transfer(s), this must return +`bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` +(i.e. 0xbc197c81, or its own function selector). + +== Extensions + +:_update: pass:normal[xref:#ERC1155Pausable-_update-address-address-uint256---uint256---[`++_update++`]] + +[.contract] +[[ERC1155Pausable]] +=== `++ERC1155Pausable++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/token/ERC1155/extensions/ERC1155Pausable.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Pausable.sol"; +``` + +ERC1155 token with pausable token transfers, minting and burning. + +Useful for scenarios such as preventing trades until the end of an evaluation +period, or having an emergency switch for freezing all token transfers in the +event of a large bug. + +IMPORTANT: This contract does not include public pause and unpause functions. In +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 pause mechanism of the contract unreachable, and thus unusable. + +[.contract-index] +.Functions +-- +* {xref-ERC1155Pausable-_update-address-address-uint256---uint256---}[`++_update(from, to, ids, values)++`] + +[.contract-subindex-inherited] +.Pausable +* {xref-Pausable-paused--}[`++paused()++`] +* {xref-Pausable-_requireNotPaused--}[`++_requireNotPaused()++`] +* {xref-Pausable-_requirePaused--}[`++_requirePaused()++`] +* {xref-Pausable-_pause--}[`++_pause()++`] +* {xref-Pausable-_unpause--}[`++_unpause()++`] + +[.contract-subindex-inherited] +.ERC1155 +* {xref-ERC1155-supportsInterface-bytes4-}[`++supportsInterface(interfaceId)++`] +* {xref-ERC1155-uri-uint256-}[`++uri()++`] +* {xref-ERC1155-balanceOf-address-uint256-}[`++balanceOf(account, id)++`] +* {xref-ERC1155-balanceOfBatch-address---uint256---}[`++balanceOfBatch(accounts, ids)++`] +* {xref-ERC1155-setApprovalForAll-address-bool-}[`++setApprovalForAll(operator, approved)++`] +* {xref-ERC1155-isApprovedForAll-address-address-}[`++isApprovedForAll(account, operator)++`] +* {xref-ERC1155-safeTransferFrom-address-address-uint256-uint256-bytes-}[`++safeTransferFrom(from, to, id, value, data)++`] +* {xref-ERC1155-safeBatchTransferFrom-address-address-uint256---uint256---bytes-}[`++safeBatchTransferFrom(from, to, ids, values, data)++`] +* {xref-ERC1155-_updateWithAcceptanceCheck-address-address-uint256---uint256---bytes-}[`++_updateWithAcceptanceCheck(from, to, ids, values, data)++`] +* {xref-ERC1155-_safeTransferFrom-address-address-uint256-uint256-bytes-}[`++_safeTransferFrom(from, to, id, value, data)++`] +* {xref-ERC1155-_safeBatchTransferFrom-address-address-uint256---uint256---bytes-}[`++_safeBatchTransferFrom(from, to, ids, values, data)++`] +* {xref-ERC1155-_setURI-string-}[`++_setURI(newuri)++`] +* {xref-ERC1155-_mint-address-uint256-uint256-bytes-}[`++_mint(to, id, value, data)++`] +* {xref-ERC1155-_mintBatch-address-uint256---uint256---bytes-}[`++_mintBatch(to, ids, values, data)++`] +* {xref-ERC1155-_burn-address-uint256-uint256-}[`++_burn(from, id, value)++`] +* {xref-ERC1155-_burnBatch-address-uint256---uint256---}[`++_burnBatch(from, ids, values)++`] +* {xref-ERC1155-_setApprovalForAll-address-address-bool-}[`++_setApprovalForAll(owner, operator, approved)++`] + +[.contract-subindex-inherited] +.IERC1155Errors + +[.contract-subindex-inherited] +.IERC1155MetadataURI + +[.contract-subindex-inherited] +.IERC1155 + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-index] +.Events +-- + +[.contract-subindex-inherited] +.Pausable +* {xref-Pausable-Paused-address-}[`++Paused(account)++`] +* {xref-Pausable-Unpaused-address-}[`++Unpaused(account)++`] + +[.contract-subindex-inherited] +.ERC1155 + +[.contract-subindex-inherited] +.IERC1155Errors + +[.contract-subindex-inherited] +.IERC1155MetadataURI + +[.contract-subindex-inherited] +.IERC1155 +* {xref-IERC1155-TransferSingle-address-address-address-uint256-uint256-}[`++TransferSingle(operator, from, to, id, value)++`] +* {xref-IERC1155-TransferBatch-address-address-address-uint256---uint256---}[`++TransferBatch(operator, from, to, ids, values)++`] +* {xref-IERC1155-ApprovalForAll-address-address-bool-}[`++ApprovalForAll(account, operator, approved)++`] +* {xref-IERC1155-URI-string-uint256-}[`++URI(value, id)++`] + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-index] +.Errors +-- + +[.contract-subindex-inherited] +.Pausable +* {xref-Pausable-EnforcedPause--}[`++EnforcedPause()++`] +* {xref-Pausable-ExpectedPause--}[`++ExpectedPause()++`] + +[.contract-subindex-inherited] +.ERC1155 + +[.contract-subindex-inherited] +.IERC1155Errors +* {xref-IERC1155Errors-ERC1155InsufficientBalance-address-uint256-uint256-uint256-}[`++ERC1155InsufficientBalance(sender, balance, needed, tokenId)++`] +* {xref-IERC1155Errors-ERC1155InvalidSender-address-}[`++ERC1155InvalidSender(sender)++`] +* {xref-IERC1155Errors-ERC1155InvalidReceiver-address-}[`++ERC1155InvalidReceiver(receiver)++`] +* {xref-IERC1155Errors-ERC1155MissingApprovalForAll-address-address-}[`++ERC1155MissingApprovalForAll(operator, owner)++`] +* {xref-IERC1155Errors-ERC1155InvalidApprover-address-}[`++ERC1155InvalidApprover(approver)++`] +* {xref-IERC1155Errors-ERC1155InvalidOperator-address-}[`++ERC1155InvalidOperator(operator)++`] +* {xref-IERC1155Errors-ERC1155InvalidArrayLength-uint256-uint256-}[`++ERC1155InvalidArrayLength(idsLength, valuesLength)++`] + +[.contract-subindex-inherited] +.IERC1155MetadataURI + +[.contract-subindex-inherited] +.IERC1155 + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-item] +[[ERC1155Pausable-_update-address-address-uint256---uint256---]] +==== `[.contract-item-name]#++_update++#++(address from, address to, uint256[] ids, uint256[] values)++` [.item-kind]#internal# + +See {ERC1155-_update}. + +Requirements: + +- the contract must not be paused. + +:burn: pass:normal[xref:#ERC1155Burnable-burn-address-uint256-uint256-[`++burn++`]] +:burnBatch: pass:normal[xref:#ERC1155Burnable-burnBatch-address-uint256---uint256---[`++burnBatch++`]] + +[.contract] +[[ERC1155Burnable]] +=== `++ERC1155Burnable++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/token/ERC1155/extensions/ERC1155Burnable.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Burnable.sol"; +``` + +Extension of {ERC1155} that allows token holders to destroy both their +own tokens and those that they have been approved to use. + +[.contract-index] +.Functions +-- +* {xref-ERC1155Burnable-burn-address-uint256-uint256-}[`++burn(account, id, value)++`] +* {xref-ERC1155Burnable-burnBatch-address-uint256---uint256---}[`++burnBatch(account, ids, values)++`] + +[.contract-subindex-inherited] +.ERC1155 +* {xref-ERC1155-supportsInterface-bytes4-}[`++supportsInterface(interfaceId)++`] +* {xref-ERC1155-uri-uint256-}[`++uri()++`] +* {xref-ERC1155-balanceOf-address-uint256-}[`++balanceOf(account, id)++`] +* {xref-ERC1155-balanceOfBatch-address---uint256---}[`++balanceOfBatch(accounts, ids)++`] +* {xref-ERC1155-setApprovalForAll-address-bool-}[`++setApprovalForAll(operator, approved)++`] +* {xref-ERC1155-isApprovedForAll-address-address-}[`++isApprovedForAll(account, operator)++`] +* {xref-ERC1155-safeTransferFrom-address-address-uint256-uint256-bytes-}[`++safeTransferFrom(from, to, id, value, data)++`] +* {xref-ERC1155-safeBatchTransferFrom-address-address-uint256---uint256---bytes-}[`++safeBatchTransferFrom(from, to, ids, values, data)++`] +* {xref-ERC1155-_update-address-address-uint256---uint256---}[`++_update(from, to, ids, values)++`] +* {xref-ERC1155-_updateWithAcceptanceCheck-address-address-uint256---uint256---bytes-}[`++_updateWithAcceptanceCheck(from, to, ids, values, data)++`] +* {xref-ERC1155-_safeTransferFrom-address-address-uint256-uint256-bytes-}[`++_safeTransferFrom(from, to, id, value, data)++`] +* {xref-ERC1155-_safeBatchTransferFrom-address-address-uint256---uint256---bytes-}[`++_safeBatchTransferFrom(from, to, ids, values, data)++`] +* {xref-ERC1155-_setURI-string-}[`++_setURI(newuri)++`] +* {xref-ERC1155-_mint-address-uint256-uint256-bytes-}[`++_mint(to, id, value, data)++`] +* {xref-ERC1155-_mintBatch-address-uint256---uint256---bytes-}[`++_mintBatch(to, ids, values, data)++`] +* {xref-ERC1155-_burn-address-uint256-uint256-}[`++_burn(from, id, value)++`] +* {xref-ERC1155-_burnBatch-address-uint256---uint256---}[`++_burnBatch(from, ids, values)++`] +* {xref-ERC1155-_setApprovalForAll-address-address-bool-}[`++_setApprovalForAll(owner, operator, approved)++`] + +[.contract-subindex-inherited] +.IERC1155Errors + +[.contract-subindex-inherited] +.IERC1155MetadataURI + +[.contract-subindex-inherited] +.IERC1155 + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-index] +.Events +-- + +[.contract-subindex-inherited] +.ERC1155 + +[.contract-subindex-inherited] +.IERC1155Errors + +[.contract-subindex-inherited] +.IERC1155MetadataURI + +[.contract-subindex-inherited] +.IERC1155 +* {xref-IERC1155-TransferSingle-address-address-address-uint256-uint256-}[`++TransferSingle(operator, from, to, id, value)++`] +* {xref-IERC1155-TransferBatch-address-address-address-uint256---uint256---}[`++TransferBatch(operator, from, to, ids, values)++`] +* {xref-IERC1155-ApprovalForAll-address-address-bool-}[`++ApprovalForAll(account, operator, approved)++`] +* {xref-IERC1155-URI-string-uint256-}[`++URI(value, id)++`] + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-index] +.Errors +-- + +[.contract-subindex-inherited] +.ERC1155 + +[.contract-subindex-inherited] +.IERC1155Errors +* {xref-IERC1155Errors-ERC1155InsufficientBalance-address-uint256-uint256-uint256-}[`++ERC1155InsufficientBalance(sender, balance, needed, tokenId)++`] +* {xref-IERC1155Errors-ERC1155InvalidSender-address-}[`++ERC1155InvalidSender(sender)++`] +* {xref-IERC1155Errors-ERC1155InvalidReceiver-address-}[`++ERC1155InvalidReceiver(receiver)++`] +* {xref-IERC1155Errors-ERC1155MissingApprovalForAll-address-address-}[`++ERC1155MissingApprovalForAll(operator, owner)++`] +* {xref-IERC1155Errors-ERC1155InvalidApprover-address-}[`++ERC1155InvalidApprover(approver)++`] +* {xref-IERC1155Errors-ERC1155InvalidOperator-address-}[`++ERC1155InvalidOperator(operator)++`] +* {xref-IERC1155Errors-ERC1155InvalidArrayLength-uint256-uint256-}[`++ERC1155InvalidArrayLength(idsLength, valuesLength)++`] + +[.contract-subindex-inherited] +.IERC1155MetadataURI + +[.contract-subindex-inherited] +.IERC1155 + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-item] +[[ERC1155Burnable-burn-address-uint256-uint256-]] +==== `[.contract-item-name]#++burn++#++(address account, uint256 id, uint256 value)++` [.item-kind]#public# + +[.contract-item] +[[ERC1155Burnable-burnBatch-address-uint256---uint256---]] +==== `[.contract-item-name]#++burnBatch++#++(address account, uint256[] ids, uint256[] values)++` [.item-kind]#public# + +:totalSupply: pass:normal[xref:#ERC1155Supply-totalSupply-uint256-[`++totalSupply++`]] +:totalSupply: pass:normal[xref:#ERC1155Supply-totalSupply--[`++totalSupply++`]] +:exists: pass:normal[xref:#ERC1155Supply-exists-uint256-[`++exists++`]] +:_update: pass:normal[xref:#ERC1155Supply-_update-address-address-uint256---uint256---[`++_update++`]] + +[.contract] +[[ERC1155Supply]] +=== `++ERC1155Supply++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/token/ERC1155/extensions/ERC1155Supply.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Supply.sol"; +``` + +Extension of ERC1155 that adds tracking of total supply per id. + +Useful for scenarios where Fungible and Non-fungible tokens have to be +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. + +CAUTION: This extension should not be added in an upgrade to an already deployed contract. + +[.contract-index] +.Functions +-- +* {xref-ERC1155Supply-totalSupply-uint256-}[`++totalSupply(id)++`] +* {xref-ERC1155Supply-totalSupply--}[`++totalSupply()++`] +* {xref-ERC1155Supply-exists-uint256-}[`++exists(id)++`] +* {xref-ERC1155Supply-_update-address-address-uint256---uint256---}[`++_update(from, to, ids, values)++`] + +[.contract-subindex-inherited] +.ERC1155 +* {xref-ERC1155-supportsInterface-bytes4-}[`++supportsInterface(interfaceId)++`] +* {xref-ERC1155-uri-uint256-}[`++uri()++`] +* {xref-ERC1155-balanceOf-address-uint256-}[`++balanceOf(account, id)++`] +* {xref-ERC1155-balanceOfBatch-address---uint256---}[`++balanceOfBatch(accounts, ids)++`] +* {xref-ERC1155-setApprovalForAll-address-bool-}[`++setApprovalForAll(operator, approved)++`] +* {xref-ERC1155-isApprovedForAll-address-address-}[`++isApprovedForAll(account, operator)++`] +* {xref-ERC1155-safeTransferFrom-address-address-uint256-uint256-bytes-}[`++safeTransferFrom(from, to, id, value, data)++`] +* {xref-ERC1155-safeBatchTransferFrom-address-address-uint256---uint256---bytes-}[`++safeBatchTransferFrom(from, to, ids, values, data)++`] +* {xref-ERC1155-_updateWithAcceptanceCheck-address-address-uint256---uint256---bytes-}[`++_updateWithAcceptanceCheck(from, to, ids, values, data)++`] +* {xref-ERC1155-_safeTransferFrom-address-address-uint256-uint256-bytes-}[`++_safeTransferFrom(from, to, id, value, data)++`] +* {xref-ERC1155-_safeBatchTransferFrom-address-address-uint256---uint256---bytes-}[`++_safeBatchTransferFrom(from, to, ids, values, data)++`] +* {xref-ERC1155-_setURI-string-}[`++_setURI(newuri)++`] +* {xref-ERC1155-_mint-address-uint256-uint256-bytes-}[`++_mint(to, id, value, data)++`] +* {xref-ERC1155-_mintBatch-address-uint256---uint256---bytes-}[`++_mintBatch(to, ids, values, data)++`] +* {xref-ERC1155-_burn-address-uint256-uint256-}[`++_burn(from, id, value)++`] +* {xref-ERC1155-_burnBatch-address-uint256---uint256---}[`++_burnBatch(from, ids, values)++`] +* {xref-ERC1155-_setApprovalForAll-address-address-bool-}[`++_setApprovalForAll(owner, operator, approved)++`] + +[.contract-subindex-inherited] +.IERC1155Errors + +[.contract-subindex-inherited] +.IERC1155MetadataURI + +[.contract-subindex-inherited] +.IERC1155 + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-index] +.Events +-- + +[.contract-subindex-inherited] +.ERC1155 + +[.contract-subindex-inherited] +.IERC1155Errors + +[.contract-subindex-inherited] +.IERC1155MetadataURI + +[.contract-subindex-inherited] +.IERC1155 +* {xref-IERC1155-TransferSingle-address-address-address-uint256-uint256-}[`++TransferSingle(operator, from, to, id, value)++`] +* {xref-IERC1155-TransferBatch-address-address-address-uint256---uint256---}[`++TransferBatch(operator, from, to, ids, values)++`] +* {xref-IERC1155-ApprovalForAll-address-address-bool-}[`++ApprovalForAll(account, operator, approved)++`] +* {xref-IERC1155-URI-string-uint256-}[`++URI(value, id)++`] + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-index] +.Errors +-- + +[.contract-subindex-inherited] +.ERC1155 + +[.contract-subindex-inherited] +.IERC1155Errors +* {xref-IERC1155Errors-ERC1155InsufficientBalance-address-uint256-uint256-uint256-}[`++ERC1155InsufficientBalance(sender, balance, needed, tokenId)++`] +* {xref-IERC1155Errors-ERC1155InvalidSender-address-}[`++ERC1155InvalidSender(sender)++`] +* {xref-IERC1155Errors-ERC1155InvalidReceiver-address-}[`++ERC1155InvalidReceiver(receiver)++`] +* {xref-IERC1155Errors-ERC1155MissingApprovalForAll-address-address-}[`++ERC1155MissingApprovalForAll(operator, owner)++`] +* {xref-IERC1155Errors-ERC1155InvalidApprover-address-}[`++ERC1155InvalidApprover(approver)++`] +* {xref-IERC1155Errors-ERC1155InvalidOperator-address-}[`++ERC1155InvalidOperator(operator)++`] +* {xref-IERC1155Errors-ERC1155InvalidArrayLength-uint256-uint256-}[`++ERC1155InvalidArrayLength(idsLength, valuesLength)++`] + +[.contract-subindex-inherited] +.IERC1155MetadataURI + +[.contract-subindex-inherited] +.IERC1155 + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-item] +[[ERC1155Supply-totalSupply-uint256-]] +==== `[.contract-item-name]#++totalSupply++#++(uint256 id) β†’ uint256++` [.item-kind]#public# + +Total value of tokens in with a given id. + +[.contract-item] +[[ERC1155Supply-totalSupply--]] +==== `[.contract-item-name]#++totalSupply++#++() β†’ uint256++` [.item-kind]#public# + +Total value of tokens. + +[.contract-item] +[[ERC1155Supply-exists-uint256-]] +==== `[.contract-item-name]#++exists++#++(uint256 id) β†’ bool++` [.item-kind]#public# + +Indicates whether any token exist with a given id, or not. + +[.contract-item] +[[ERC1155Supply-_update-address-address-uint256---uint256---]] +==== `[.contract-item-name]#++_update++#++(address from, address to, uint256[] ids, uint256[] values)++` [.item-kind]#internal# + +See {ERC1155-_update}. + +:uri: pass:normal[xref:#ERC1155URIStorage-uri-uint256-[`++uri++`]] +:_setURI: pass:normal[xref:#ERC1155URIStorage-_setURI-uint256-string-[`++_setURI++`]] +:_setBaseURI: pass:normal[xref:#ERC1155URIStorage-_setBaseURI-string-[`++_setBaseURI++`]] + +[.contract] +[[ERC1155URIStorage]] +=== `++ERC1155URIStorage++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/token/ERC1155/extensions/ERC1155URIStorage.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155URIStorage.sol"; +``` + +ERC1155 token with storage based token URI management. +Inspired by the ERC721URIStorage extension + +[.contract-index] +.Functions +-- +* {xref-ERC1155URIStorage-uri-uint256-}[`++uri(tokenId)++`] +* {xref-ERC1155URIStorage-_setURI-uint256-string-}[`++_setURI(tokenId, tokenURI)++`] +* {xref-ERC1155URIStorage-_setBaseURI-string-}[`++_setBaseURI(baseURI)++`] + +[.contract-subindex-inherited] +.ERC1155 +* {xref-ERC1155-supportsInterface-bytes4-}[`++supportsInterface(interfaceId)++`] +* {xref-ERC1155-balanceOf-address-uint256-}[`++balanceOf(account, id)++`] +* {xref-ERC1155-balanceOfBatch-address---uint256---}[`++balanceOfBatch(accounts, ids)++`] +* {xref-ERC1155-setApprovalForAll-address-bool-}[`++setApprovalForAll(operator, approved)++`] +* {xref-ERC1155-isApprovedForAll-address-address-}[`++isApprovedForAll(account, operator)++`] +* {xref-ERC1155-safeTransferFrom-address-address-uint256-uint256-bytes-}[`++safeTransferFrom(from, to, id, value, data)++`] +* {xref-ERC1155-safeBatchTransferFrom-address-address-uint256---uint256---bytes-}[`++safeBatchTransferFrom(from, to, ids, values, data)++`] +* {xref-ERC1155-_update-address-address-uint256---uint256---}[`++_update(from, to, ids, values)++`] +* {xref-ERC1155-_updateWithAcceptanceCheck-address-address-uint256---uint256---bytes-}[`++_updateWithAcceptanceCheck(from, to, ids, values, data)++`] +* {xref-ERC1155-_safeTransferFrom-address-address-uint256-uint256-bytes-}[`++_safeTransferFrom(from, to, id, value, data)++`] +* {xref-ERC1155-_safeBatchTransferFrom-address-address-uint256---uint256---bytes-}[`++_safeBatchTransferFrom(from, to, ids, values, data)++`] +* {xref-ERC1155-_setURI-string-}[`++_setURI(newuri)++`] +* {xref-ERC1155-_mint-address-uint256-uint256-bytes-}[`++_mint(to, id, value, data)++`] +* {xref-ERC1155-_mintBatch-address-uint256---uint256---bytes-}[`++_mintBatch(to, ids, values, data)++`] +* {xref-ERC1155-_burn-address-uint256-uint256-}[`++_burn(from, id, value)++`] +* {xref-ERC1155-_burnBatch-address-uint256---uint256---}[`++_burnBatch(from, ids, values)++`] +* {xref-ERC1155-_setApprovalForAll-address-address-bool-}[`++_setApprovalForAll(owner, operator, approved)++`] + +[.contract-subindex-inherited] +.IERC1155Errors + +[.contract-subindex-inherited] +.IERC1155MetadataURI + +[.contract-subindex-inherited] +.IERC1155 + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-index] +.Events +-- + +[.contract-subindex-inherited] +.ERC1155 + +[.contract-subindex-inherited] +.IERC1155Errors + +[.contract-subindex-inherited] +.IERC1155MetadataURI + +[.contract-subindex-inherited] +.IERC1155 +* {xref-IERC1155-TransferSingle-address-address-address-uint256-uint256-}[`++TransferSingle(operator, from, to, id, value)++`] +* {xref-IERC1155-TransferBatch-address-address-address-uint256---uint256---}[`++TransferBatch(operator, from, to, ids, values)++`] +* {xref-IERC1155-ApprovalForAll-address-address-bool-}[`++ApprovalForAll(account, operator, approved)++`] +* {xref-IERC1155-URI-string-uint256-}[`++URI(value, id)++`] + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-index] +.Errors +-- + +[.contract-subindex-inherited] +.ERC1155 + +[.contract-subindex-inherited] +.IERC1155Errors +* {xref-IERC1155Errors-ERC1155InsufficientBalance-address-uint256-uint256-uint256-}[`++ERC1155InsufficientBalance(sender, balance, needed, tokenId)++`] +* {xref-IERC1155Errors-ERC1155InvalidSender-address-}[`++ERC1155InvalidSender(sender)++`] +* {xref-IERC1155Errors-ERC1155InvalidReceiver-address-}[`++ERC1155InvalidReceiver(receiver)++`] +* {xref-IERC1155Errors-ERC1155MissingApprovalForAll-address-address-}[`++ERC1155MissingApprovalForAll(operator, owner)++`] +* {xref-IERC1155Errors-ERC1155InvalidApprover-address-}[`++ERC1155InvalidApprover(approver)++`] +* {xref-IERC1155Errors-ERC1155InvalidOperator-address-}[`++ERC1155InvalidOperator(operator)++`] +* {xref-IERC1155Errors-ERC1155InvalidArrayLength-uint256-uint256-}[`++ERC1155InvalidArrayLength(idsLength, valuesLength)++`] + +[.contract-subindex-inherited] +.IERC1155MetadataURI + +[.contract-subindex-inherited] +.IERC1155 + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-item] +[[ERC1155URIStorage-uri-uint256-]] +==== `[.contract-item-name]#++uri++#++(uint256 tokenId) β†’ string++` [.item-kind]#public# + +See {IERC1155MetadataURI-uri}. + +This implementation returns the concatenation of the `_baseURI` +and the token-specific uri if the latter is set + +This enables the following behaviors: + +- if `_tokenURIs[tokenId]` is set, then the result is the concatenation + of `_baseURI` and `_tokenURIs[tokenId]` (keep in mind that `_baseURI` + is empty per default); + +- if `_tokenURIs[tokenId]` is NOT set then we fallback to `super.uri()` + which in most cases will contain `ERC1155._uri`; + +- if `_tokenURIs[tokenId]` is NOT set, and if the parents do not have a + uri value set, then the result is empty. + +[.contract-item] +[[ERC1155URIStorage-_setURI-uint256-string-]] +==== `[.contract-item-name]#++_setURI++#++(uint256 tokenId, string tokenURI)++` [.item-kind]#internal# + +Sets `tokenURI` as the tokenURI of `tokenId`. + +[.contract-item] +[[ERC1155URIStorage-_setBaseURI-string-]] +==== `[.contract-item-name]#++_setBaseURI++#++(string baseURI)++` [.item-kind]#internal# + +Sets `baseURI` as the `_baseURI` for all tokens + +== Utilities + +:supportsInterface: pass:normal[xref:#ERC1155Holder-supportsInterface-bytes4-[`++supportsInterface++`]] +:onERC1155Received: pass:normal[xref:#ERC1155Holder-onERC1155Received-address-address-uint256-uint256-bytes-[`++onERC1155Received++`]] +:onERC1155BatchReceived: pass:normal[xref:#ERC1155Holder-onERC1155BatchReceived-address-address-uint256---uint256---bytes-[`++onERC1155BatchReceived++`]] + +[.contract] +[[ERC1155Holder]] +=== `++ERC1155Holder++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/token/ERC1155/utils/ERC1155Holder.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol"; +``` + +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. + +[.contract-index] +.Functions +-- +* {xref-ERC1155Holder-supportsInterface-bytes4-}[`++supportsInterface(interfaceId)++`] +* {xref-ERC1155Holder-onERC1155Received-address-address-uint256-uint256-bytes-}[`++onERC1155Received(, , , , )++`] +* {xref-ERC1155Holder-onERC1155BatchReceived-address-address-uint256---uint256---bytes-}[`++onERC1155BatchReceived(, , , , )++`] + +[.contract-subindex-inherited] +.IERC1155Receiver + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-item] +[[ERC1155Holder-supportsInterface-bytes4-]] +==== `[.contract-item-name]#++supportsInterface++#++(bytes4 interfaceId) β†’ bool++` [.item-kind]#public# + +See {IERC165-supportsInterface}. + +[.contract-item] +[[ERC1155Holder-onERC1155Received-address-address-uint256-uint256-bytes-]] +==== `[.contract-item-name]#++onERC1155Received++#++(address, address, uint256, uint256, bytes) β†’ bytes4++` [.item-kind]#public# + +[.contract-item] +[[ERC1155Holder-onERC1155BatchReceived-address-address-uint256---uint256---bytes-]] +==== `[.contract-item-name]#++onERC1155BatchReceived++#++(address, address, uint256[], uint256[], bytes) β†’ bytes4++` [.item-kind]#public# + diff --git a/docs/modules/api/pages/token/ERC20.adoc b/docs/modules/api/pages/token/ERC20.adoc new file mode 100644 index 000000000..466992ee2 --- /dev/null +++ b/docs/modules/api/pages/token/ERC20.adoc @@ -0,0 +1,2670 @@ +:github-icon: pass:[] +:IERC20: pass:normal[xref:token/ERC20.adoc#IERC20[`IERC20`]] +:IERC20Metadata: pass:normal[xref:token/ERC20.adoc#IERC20Metadata[`IERC20Metadata`]] +:ERC20: pass:normal[xref:token/ERC20.adoc#ERC20[`ERC20`]] +:ERC20Permit: pass:normal[xref:token/ERC20.adoc#ERC20Permit[`ERC20Permit`]] +:ERC20Burnable: pass:normal[xref:token/ERC20.adoc#ERC20Burnable[`ERC20Burnable`]] +:ERC20Capped: pass:normal[xref:token/ERC20.adoc#ERC20Capped[`ERC20Capped`]] +:ERC20Pausable: pass:normal[xref:token/ERC20.adoc#ERC20Pausable[`ERC20Pausable`]] +:ERC20FlashMint: pass:normal[xref:token/ERC20.adoc#ERC20FlashMint[`ERC20FlashMint`]] +:ERC20Votes: pass:normal[xref:token/ERC20.adoc#ERC20Votes[`ERC20Votes`]] +:ERC20Wrapper: pass:normal[xref:token/ERC20.adoc#ERC20Wrapper[`ERC20Wrapper`]] +:ERC20Votes: pass:normal[xref:token/ERC20.adoc#ERC20Votes[`ERC20Votes`]] +:ERC4626: pass:normal[xref:token/ERC20.adoc#ERC4626[`ERC4626`]] +:SafeERC20: pass:normal[xref:token/ERC20.adoc#SafeERC20[`SafeERC20`]] +:VestingWallet: pass:normal[xref:finance.adoc#VestingWallet[`VestingWallet`]] +:xref-IERC20-totalSupply--: xref:token/ERC20.adoc#IERC20-totalSupply-- +:xref-IERC20-balanceOf-address-: xref:token/ERC20.adoc#IERC20-balanceOf-address- +:xref-IERC20-transfer-address-uint256-: xref:token/ERC20.adoc#IERC20-transfer-address-uint256- +:xref-IERC20-allowance-address-address-: xref:token/ERC20.adoc#IERC20-allowance-address-address- +:xref-IERC20-approve-address-uint256-: xref:token/ERC20.adoc#IERC20-approve-address-uint256- +:xref-IERC20-transferFrom-address-address-uint256-: xref:token/ERC20.adoc#IERC20-transferFrom-address-address-uint256- +:xref-IERC20-Transfer-address-address-uint256-: xref:token/ERC20.adoc#IERC20-Transfer-address-address-uint256- +:xref-IERC20-Approval-address-address-uint256-: xref:token/ERC20.adoc#IERC20-Approval-address-address-uint256- +:xref-IERC20Metadata-name--: xref:token/ERC20.adoc#IERC20Metadata-name-- +:xref-IERC20Metadata-symbol--: xref:token/ERC20.adoc#IERC20Metadata-symbol-- +:xref-IERC20Metadata-decimals--: xref:token/ERC20.adoc#IERC20Metadata-decimals-- +:xref-IERC20-totalSupply--: xref:token/ERC20.adoc#IERC20-totalSupply-- +:xref-IERC20-balanceOf-address-: xref:token/ERC20.adoc#IERC20-balanceOf-address- +:xref-IERC20-transfer-address-uint256-: xref:token/ERC20.adoc#IERC20-transfer-address-uint256- +:xref-IERC20-allowance-address-address-: xref:token/ERC20.adoc#IERC20-allowance-address-address- +:xref-IERC20-approve-address-uint256-: xref:token/ERC20.adoc#IERC20-approve-address-uint256- +:xref-IERC20-transferFrom-address-address-uint256-: xref:token/ERC20.adoc#IERC20-transferFrom-address-address-uint256- +:xref-IERC20-Transfer-address-address-uint256-: xref:token/ERC20.adoc#IERC20-Transfer-address-address-uint256- +:xref-IERC20-Approval-address-address-uint256-: xref:token/ERC20.adoc#IERC20-Approval-address-address-uint256- +:IERC20: pass:normal[xref:token/ERC20.adoc#IERC20[`IERC20`]] +:xref-ERC20-constructor-string-string-: xref:token/ERC20.adoc#ERC20-constructor-string-string- +:xref-ERC20-name--: xref:token/ERC20.adoc#ERC20-name-- +:xref-ERC20-symbol--: xref:token/ERC20.adoc#ERC20-symbol-- +:xref-ERC20-decimals--: xref:token/ERC20.adoc#ERC20-decimals-- +:xref-ERC20-totalSupply--: xref:token/ERC20.adoc#ERC20-totalSupply-- +:xref-ERC20-balanceOf-address-: xref:token/ERC20.adoc#ERC20-balanceOf-address- +:xref-ERC20-transfer-address-uint256-: xref:token/ERC20.adoc#ERC20-transfer-address-uint256- +:xref-ERC20-allowance-address-address-: xref:token/ERC20.adoc#ERC20-allowance-address-address- +:xref-ERC20-approve-address-uint256-: xref:token/ERC20.adoc#ERC20-approve-address-uint256- +:xref-ERC20-transferFrom-address-address-uint256-: xref:token/ERC20.adoc#ERC20-transferFrom-address-address-uint256- +:xref-ERC20-_transfer-address-address-uint256-: xref:token/ERC20.adoc#ERC20-_transfer-address-address-uint256- +:xref-ERC20-_update-address-address-uint256-: xref:token/ERC20.adoc#ERC20-_update-address-address-uint256- +:xref-ERC20-_mint-address-uint256-: xref:token/ERC20.adoc#ERC20-_mint-address-uint256- +:xref-ERC20-_burn-address-uint256-: xref:token/ERC20.adoc#ERC20-_burn-address-uint256- +:xref-ERC20-_approve-address-address-uint256-: xref:token/ERC20.adoc#ERC20-_approve-address-address-uint256- +:xref-ERC20-_approve-address-address-uint256-bool-: xref:token/ERC20.adoc#ERC20-_approve-address-address-uint256-bool- +:xref-ERC20-_spendAllowance-address-address-uint256-: xref:token/ERC20.adoc#ERC20-_spendAllowance-address-address-uint256- +:xref-IERC20-Transfer-address-address-uint256-: xref:token/ERC20.adoc#IERC20-Transfer-address-address-uint256- +:xref-IERC20-Approval-address-address-uint256-: xref:token/ERC20.adoc#IERC20-Approval-address-address-uint256- +:xref-IERC20Errors-ERC20InsufficientBalance-address-uint256-uint256-: xref:interfaces.adoc#IERC20Errors-ERC20InsufficientBalance-address-uint256-uint256- +:xref-IERC20Errors-ERC20InvalidSender-address-: xref:interfaces.adoc#IERC20Errors-ERC20InvalidSender-address- +:xref-IERC20Errors-ERC20InvalidReceiver-address-: xref:interfaces.adoc#IERC20Errors-ERC20InvalidReceiver-address- +:xref-IERC20Errors-ERC20InsufficientAllowance-address-uint256-uint256-: xref:interfaces.adoc#IERC20Errors-ERC20InsufficientAllowance-address-uint256-uint256- +:xref-IERC20Errors-ERC20InvalidApprover-address-: xref:interfaces.adoc#IERC20Errors-ERC20InvalidApprover-address- +:xref-IERC20Errors-ERC20InvalidSpender-address-: xref:interfaces.adoc#IERC20Errors-ERC20InvalidSpender-address- +:IERC20-balanceOf: pass:normal[xref:token/ERC20.adoc#IERC20-balanceOf-address-[`IERC20.balanceOf`]] +:IERC20-transfer: pass:normal[xref:token/ERC20.adoc#IERC20-transfer-address-uint256-[`IERC20.transfer`]] +:IERC20-totalSupply: pass:normal[xref:token/ERC20.adoc#IERC20-totalSupply--[`IERC20.totalSupply`]] +:IERC20-balanceOf: pass:normal[xref:token/ERC20.adoc#IERC20-balanceOf-address-[`IERC20.balanceOf`]] +:IERC20-transfer: pass:normal[xref:token/ERC20.adoc#IERC20-transfer-address-uint256-[`IERC20.transfer`]] +:IERC20-allowance: pass:normal[xref:token/ERC20.adoc#IERC20-allowance-address-address-[`IERC20.allowance`]] +:IERC20-approve: pass:normal[xref:token/ERC20.adoc#IERC20-approve-address-uint256-[`IERC20.approve`]] +:IERC20-transferFrom: pass:normal[xref:token/ERC20.adoc#IERC20-transferFrom-address-address-uint256-[`IERC20.transferFrom`]] +:ERC20: pass:normal[xref:token/ERC20.adoc#ERC20[`ERC20`]] +:IERC20-allowance: pass:normal[xref:token/ERC20.adoc#IERC20-allowance-address-address-[`IERC20.allowance`]] +:IERC20-approve: pass:normal[xref:token/ERC20.adoc#IERC20-approve-address-uint256-[`IERC20.approve`]] +:SafeERC20-safeTransferFrom: pass:normal[xref:token/ERC20.adoc#SafeERC20-safeTransferFrom-contract-IERC20-address-address-uint256-[`SafeERC20.safeTransferFrom`]] +:xref-IERC20Permit-permit-address-address-uint256-uint256-uint8-bytes32-bytes32-: xref:token/ERC20.adoc#IERC20Permit-permit-address-address-uint256-uint256-uint8-bytes32-bytes32- +:xref-IERC20Permit-nonces-address-: xref:token/ERC20.adoc#IERC20Permit-nonces-address- +:xref-IERC20Permit-DOMAIN_SEPARATOR--: xref:token/ERC20.adoc#IERC20Permit-DOMAIN_SEPARATOR-- +:IERC20-approve: pass:normal[xref:token/ERC20.adoc#IERC20-approve-address-uint256-[`IERC20.approve`]] +:EIP712: pass:normal[xref:utils.adoc#EIP712[`EIP712`]] +:IERC20-allowance: pass:normal[xref:token/ERC20.adoc#IERC20-allowance-address-address-[`IERC20.allowance`]] +:IERC20-approve: pass:normal[xref:token/ERC20.adoc#IERC20-approve-address-uint256-[`IERC20.approve`]] +:xref-ERC20Permit-constructor-string-: xref:token/ERC20.adoc#ERC20Permit-constructor-string- +:xref-ERC20Permit-permit-address-address-uint256-uint256-uint8-bytes32-bytes32-: xref:token/ERC20.adoc#ERC20Permit-permit-address-address-uint256-uint256-uint8-bytes32-bytes32- +:xref-ERC20Permit-nonces-address-: xref:token/ERC20.adoc#ERC20Permit-nonces-address- +:xref-ERC20Permit-DOMAIN_SEPARATOR--: xref:token/ERC20.adoc#ERC20Permit-DOMAIN_SEPARATOR-- +:xref-Nonces-_useNonce-address-: xref:utils.adoc#Nonces-_useNonce-address- +:xref-Nonces-_useCheckedNonce-address-uint256-: xref:utils.adoc#Nonces-_useCheckedNonce-address-uint256- +:xref-EIP712-_domainSeparatorV4--: xref:utils.adoc#EIP712-_domainSeparatorV4-- +:xref-EIP712-_hashTypedDataV4-bytes32-: xref:utils.adoc#EIP712-_hashTypedDataV4-bytes32- +:xref-EIP712-eip712Domain--: xref:utils.adoc#EIP712-eip712Domain-- +:xref-EIP712-_EIP712Name--: xref:utils.adoc#EIP712-_EIP712Name-- +:xref-EIP712-_EIP712Version--: xref:utils.adoc#EIP712-_EIP712Version-- +:xref-ERC20-name--: xref:token/ERC20.adoc#ERC20-name-- +:xref-ERC20-symbol--: xref:token/ERC20.adoc#ERC20-symbol-- +:xref-ERC20-decimals--: xref:token/ERC20.adoc#ERC20-decimals-- +:xref-ERC20-totalSupply--: xref:token/ERC20.adoc#ERC20-totalSupply-- +:xref-ERC20-balanceOf-address-: xref:token/ERC20.adoc#ERC20-balanceOf-address- +:xref-ERC20-transfer-address-uint256-: xref:token/ERC20.adoc#ERC20-transfer-address-uint256- +:xref-ERC20-allowance-address-address-: xref:token/ERC20.adoc#ERC20-allowance-address-address- +:xref-ERC20-approve-address-uint256-: xref:token/ERC20.adoc#ERC20-approve-address-uint256- +:xref-ERC20-transferFrom-address-address-uint256-: xref:token/ERC20.adoc#ERC20-transferFrom-address-address-uint256- +:xref-ERC20-_transfer-address-address-uint256-: xref:token/ERC20.adoc#ERC20-_transfer-address-address-uint256- +:xref-ERC20-_update-address-address-uint256-: xref:token/ERC20.adoc#ERC20-_update-address-address-uint256- +:xref-ERC20-_mint-address-uint256-: xref:token/ERC20.adoc#ERC20-_mint-address-uint256- +:xref-ERC20-_burn-address-uint256-: xref:token/ERC20.adoc#ERC20-_burn-address-uint256- +:xref-ERC20-_approve-address-address-uint256-: xref:token/ERC20.adoc#ERC20-_approve-address-address-uint256- +:xref-ERC20-_approve-address-address-uint256-bool-: xref:token/ERC20.adoc#ERC20-_approve-address-address-uint256-bool- +:xref-ERC20-_spendAllowance-address-address-uint256-: xref:token/ERC20.adoc#ERC20-_spendAllowance-address-address-uint256- +:xref-IERC5267-EIP712DomainChanged--: xref:interfaces.adoc#IERC5267-EIP712DomainChanged-- +:xref-IERC20-Transfer-address-address-uint256-: xref:token/ERC20.adoc#IERC20-Transfer-address-address-uint256- +:xref-IERC20-Approval-address-address-uint256-: xref:token/ERC20.adoc#IERC20-Approval-address-address-uint256- +:xref-ERC20Permit-ERC2612ExpiredSignature-uint256-: xref:token/ERC20.adoc#ERC20Permit-ERC2612ExpiredSignature-uint256- +:xref-ERC20Permit-ERC2612InvalidSigner-address-address-: xref:token/ERC20.adoc#ERC20Permit-ERC2612InvalidSigner-address-address- +:xref-Nonces-InvalidAccountNonce-address-uint256-: xref:utils.adoc#Nonces-InvalidAccountNonce-address-uint256- +:xref-IERC20Errors-ERC20InsufficientBalance-address-uint256-uint256-: xref:interfaces.adoc#IERC20Errors-ERC20InsufficientBalance-address-uint256-uint256- +:xref-IERC20Errors-ERC20InvalidSender-address-: xref:interfaces.adoc#IERC20Errors-ERC20InvalidSender-address- +:xref-IERC20Errors-ERC20InvalidReceiver-address-: xref:interfaces.adoc#IERC20Errors-ERC20InvalidReceiver-address- +:xref-IERC20Errors-ERC20InsufficientAllowance-address-uint256-uint256-: xref:interfaces.adoc#IERC20Errors-ERC20InsufficientAllowance-address-uint256-uint256- +:xref-IERC20Errors-ERC20InvalidApprover-address-: xref:interfaces.adoc#IERC20Errors-ERC20InvalidApprover-address- +:xref-IERC20Errors-ERC20InvalidSpender-address-: xref:interfaces.adoc#IERC20Errors-ERC20InvalidSpender-address- +:EIP712: pass:normal[xref:utils.adoc#EIP712[`EIP712`]] +:IERC20-approve: pass:normal[xref:token/ERC20.adoc#IERC20-approve-address-uint256-[`IERC20.approve`]] +:EIP712: pass:normal[xref:utils.adoc#EIP712[`EIP712`]] +:ERC20: pass:normal[xref:token/ERC20.adoc#ERC20[`ERC20`]] +:xref-ERC20Burnable-burn-uint256-: xref:token/ERC20.adoc#ERC20Burnable-burn-uint256- +:xref-ERC20Burnable-burnFrom-address-uint256-: xref:token/ERC20.adoc#ERC20Burnable-burnFrom-address-uint256- +:xref-ERC20-name--: xref:token/ERC20.adoc#ERC20-name-- +:xref-ERC20-symbol--: xref:token/ERC20.adoc#ERC20-symbol-- +:xref-ERC20-decimals--: xref:token/ERC20.adoc#ERC20-decimals-- +:xref-ERC20-totalSupply--: xref:token/ERC20.adoc#ERC20-totalSupply-- +:xref-ERC20-balanceOf-address-: xref:token/ERC20.adoc#ERC20-balanceOf-address- +:xref-ERC20-transfer-address-uint256-: xref:token/ERC20.adoc#ERC20-transfer-address-uint256- +:xref-ERC20-allowance-address-address-: xref:token/ERC20.adoc#ERC20-allowance-address-address- +:xref-ERC20-approve-address-uint256-: xref:token/ERC20.adoc#ERC20-approve-address-uint256- +:xref-ERC20-transferFrom-address-address-uint256-: xref:token/ERC20.adoc#ERC20-transferFrom-address-address-uint256- +:xref-ERC20-_transfer-address-address-uint256-: xref:token/ERC20.adoc#ERC20-_transfer-address-address-uint256- +:xref-ERC20-_update-address-address-uint256-: xref:token/ERC20.adoc#ERC20-_update-address-address-uint256- +:xref-ERC20-_mint-address-uint256-: xref:token/ERC20.adoc#ERC20-_mint-address-uint256- +:xref-ERC20-_burn-address-uint256-: xref:token/ERC20.adoc#ERC20-_burn-address-uint256- +:xref-ERC20-_approve-address-address-uint256-: xref:token/ERC20.adoc#ERC20-_approve-address-address-uint256- +:xref-ERC20-_approve-address-address-uint256-bool-: xref:token/ERC20.adoc#ERC20-_approve-address-address-uint256-bool- +:xref-ERC20-_spendAllowance-address-address-uint256-: xref:token/ERC20.adoc#ERC20-_spendAllowance-address-address-uint256- +:xref-IERC20-Transfer-address-address-uint256-: xref:token/ERC20.adoc#IERC20-Transfer-address-address-uint256- +:xref-IERC20-Approval-address-address-uint256-: xref:token/ERC20.adoc#IERC20-Approval-address-address-uint256- +:xref-IERC20Errors-ERC20InsufficientBalance-address-uint256-uint256-: xref:interfaces.adoc#IERC20Errors-ERC20InsufficientBalance-address-uint256-uint256- +:xref-IERC20Errors-ERC20InvalidSender-address-: xref:interfaces.adoc#IERC20Errors-ERC20InvalidSender-address- +:xref-IERC20Errors-ERC20InvalidReceiver-address-: xref:interfaces.adoc#IERC20Errors-ERC20InvalidReceiver-address- +:xref-IERC20Errors-ERC20InsufficientAllowance-address-uint256-uint256-: xref:interfaces.adoc#IERC20Errors-ERC20InsufficientAllowance-address-uint256-uint256- +:xref-IERC20Errors-ERC20InvalidApprover-address-: xref:interfaces.adoc#IERC20Errors-ERC20InvalidApprover-address- +:xref-IERC20Errors-ERC20InvalidSpender-address-: xref:interfaces.adoc#IERC20Errors-ERC20InvalidSpender-address- +:ERC20-_burn: pass:normal[xref:token/ERC20.adoc#ERC20-_burn-address-uint256-[`ERC20._burn`]] +:ERC20-_burn: pass:normal[xref:token/ERC20.adoc#ERC20-_burn-address-uint256-[`ERC20._burn`]] +:ERC20-allowance: pass:normal[xref:token/ERC20.adoc#ERC20-allowance-address-address-[`ERC20.allowance`]] +:ERC20: pass:normal[xref:token/ERC20.adoc#ERC20[`ERC20`]] +:xref-ERC20Capped-constructor-uint256-: xref:token/ERC20.adoc#ERC20Capped-constructor-uint256- +:xref-ERC20Capped-cap--: xref:token/ERC20.adoc#ERC20Capped-cap-- +:xref-ERC20Capped-_update-address-address-uint256-: xref:token/ERC20.adoc#ERC20Capped-_update-address-address-uint256- +:xref-ERC20-name--: xref:token/ERC20.adoc#ERC20-name-- +:xref-ERC20-symbol--: xref:token/ERC20.adoc#ERC20-symbol-- +:xref-ERC20-decimals--: xref:token/ERC20.adoc#ERC20-decimals-- +:xref-ERC20-totalSupply--: xref:token/ERC20.adoc#ERC20-totalSupply-- +:xref-ERC20-balanceOf-address-: xref:token/ERC20.adoc#ERC20-balanceOf-address- +:xref-ERC20-transfer-address-uint256-: xref:token/ERC20.adoc#ERC20-transfer-address-uint256- +:xref-ERC20-allowance-address-address-: xref:token/ERC20.adoc#ERC20-allowance-address-address- +:xref-ERC20-approve-address-uint256-: xref:token/ERC20.adoc#ERC20-approve-address-uint256- +:xref-ERC20-transferFrom-address-address-uint256-: xref:token/ERC20.adoc#ERC20-transferFrom-address-address-uint256- +:xref-ERC20-_transfer-address-address-uint256-: xref:token/ERC20.adoc#ERC20-_transfer-address-address-uint256- +:xref-ERC20-_mint-address-uint256-: xref:token/ERC20.adoc#ERC20-_mint-address-uint256- +:xref-ERC20-_burn-address-uint256-: xref:token/ERC20.adoc#ERC20-_burn-address-uint256- +:xref-ERC20-_approve-address-address-uint256-: xref:token/ERC20.adoc#ERC20-_approve-address-address-uint256- +:xref-ERC20-_approve-address-address-uint256-bool-: xref:token/ERC20.adoc#ERC20-_approve-address-address-uint256-bool- +:xref-ERC20-_spendAllowance-address-address-uint256-: xref:token/ERC20.adoc#ERC20-_spendAllowance-address-address-uint256- +:xref-IERC20-Transfer-address-address-uint256-: xref:token/ERC20.adoc#IERC20-Transfer-address-address-uint256- +:xref-IERC20-Approval-address-address-uint256-: xref:token/ERC20.adoc#IERC20-Approval-address-address-uint256- +:xref-ERC20Capped-ERC20ExceededCap-uint256-uint256-: xref:token/ERC20.adoc#ERC20Capped-ERC20ExceededCap-uint256-uint256- +:xref-ERC20Capped-ERC20InvalidCap-uint256-: xref:token/ERC20.adoc#ERC20Capped-ERC20InvalidCap-uint256- +:xref-IERC20Errors-ERC20InsufficientBalance-address-uint256-uint256-: xref:interfaces.adoc#IERC20Errors-ERC20InsufficientBalance-address-uint256-uint256- +:xref-IERC20Errors-ERC20InvalidSender-address-: xref:interfaces.adoc#IERC20Errors-ERC20InvalidSender-address- +:xref-IERC20Errors-ERC20InvalidReceiver-address-: xref:interfaces.adoc#IERC20Errors-ERC20InvalidReceiver-address- +:xref-IERC20Errors-ERC20InsufficientAllowance-address-uint256-uint256-: xref:interfaces.adoc#IERC20Errors-ERC20InsufficientAllowance-address-uint256-uint256- +:xref-IERC20Errors-ERC20InvalidApprover-address-: xref:interfaces.adoc#IERC20Errors-ERC20InvalidApprover-address- +:xref-IERC20Errors-ERC20InvalidSpender-address-: xref:interfaces.adoc#IERC20Errors-ERC20InvalidSpender-address- +:ERC20-_update: pass:normal[xref:token/ERC20.adoc#ERC20-_update-address-address-uint256-[`ERC20._update`]] +:Pausable-_pause: pass:normal[xref:utils.adoc#Pausable-_pause--[`Pausable._pause`]] +:Pausable-_unpause: pass:normal[xref:utils.adoc#Pausable-_unpause--[`Pausable._unpause`]] +:AccessControl: pass:normal[xref:access.adoc#AccessControl[`AccessControl`]] +:Ownable: pass:normal[xref:access.adoc#Ownable[`Ownable`]] +:xref-ERC20Pausable-_update-address-address-uint256-: xref:token/ERC20.adoc#ERC20Pausable-_update-address-address-uint256- +:xref-Pausable-paused--: xref:utils.adoc#Pausable-paused-- +:xref-Pausable-_requireNotPaused--: xref:utils.adoc#Pausable-_requireNotPaused-- +:xref-Pausable-_requirePaused--: xref:utils.adoc#Pausable-_requirePaused-- +:xref-Pausable-_pause--: xref:utils.adoc#Pausable-_pause-- +:xref-Pausable-_unpause--: xref:utils.adoc#Pausable-_unpause-- +:xref-ERC20-name--: xref:token/ERC20.adoc#ERC20-name-- +:xref-ERC20-symbol--: xref:token/ERC20.adoc#ERC20-symbol-- +:xref-ERC20-decimals--: xref:token/ERC20.adoc#ERC20-decimals-- +:xref-ERC20-totalSupply--: xref:token/ERC20.adoc#ERC20-totalSupply-- +:xref-ERC20-balanceOf-address-: xref:token/ERC20.adoc#ERC20-balanceOf-address- +:xref-ERC20-transfer-address-uint256-: xref:token/ERC20.adoc#ERC20-transfer-address-uint256- +:xref-ERC20-allowance-address-address-: xref:token/ERC20.adoc#ERC20-allowance-address-address- +:xref-ERC20-approve-address-uint256-: xref:token/ERC20.adoc#ERC20-approve-address-uint256- +:xref-ERC20-transferFrom-address-address-uint256-: xref:token/ERC20.adoc#ERC20-transferFrom-address-address-uint256- +:xref-ERC20-_transfer-address-address-uint256-: xref:token/ERC20.adoc#ERC20-_transfer-address-address-uint256- +:xref-ERC20-_mint-address-uint256-: xref:token/ERC20.adoc#ERC20-_mint-address-uint256- +:xref-ERC20-_burn-address-uint256-: xref:token/ERC20.adoc#ERC20-_burn-address-uint256- +:xref-ERC20-_approve-address-address-uint256-: xref:token/ERC20.adoc#ERC20-_approve-address-address-uint256- +:xref-ERC20-_approve-address-address-uint256-bool-: xref:token/ERC20.adoc#ERC20-_approve-address-address-uint256-bool- +:xref-ERC20-_spendAllowance-address-address-uint256-: xref:token/ERC20.adoc#ERC20-_spendAllowance-address-address-uint256- +:xref-Pausable-Paused-address-: xref:utils.adoc#Pausable-Paused-address- +:xref-Pausable-Unpaused-address-: xref:utils.adoc#Pausable-Unpaused-address- +:xref-IERC20-Transfer-address-address-uint256-: xref:token/ERC20.adoc#IERC20-Transfer-address-address-uint256- +:xref-IERC20-Approval-address-address-uint256-: xref:token/ERC20.adoc#IERC20-Approval-address-address-uint256- +:xref-Pausable-EnforcedPause--: xref:utils.adoc#Pausable-EnforcedPause-- +:xref-Pausable-ExpectedPause--: xref:utils.adoc#Pausable-ExpectedPause-- +:xref-IERC20Errors-ERC20InsufficientBalance-address-uint256-uint256-: xref:interfaces.adoc#IERC20Errors-ERC20InsufficientBalance-address-uint256-uint256- +:xref-IERC20Errors-ERC20InvalidSender-address-: xref:interfaces.adoc#IERC20Errors-ERC20InvalidSender-address- +:xref-IERC20Errors-ERC20InvalidReceiver-address-: xref:interfaces.adoc#IERC20Errors-ERC20InvalidReceiver-address- +:xref-IERC20Errors-ERC20InsufficientAllowance-address-uint256-uint256-: xref:interfaces.adoc#IERC20Errors-ERC20InsufficientAllowance-address-uint256-uint256- +:xref-IERC20Errors-ERC20InvalidApprover-address-: xref:interfaces.adoc#IERC20Errors-ERC20InvalidApprover-address- +:xref-IERC20Errors-ERC20InvalidSpender-address-: xref:interfaces.adoc#IERC20Errors-ERC20InvalidSpender-address- +:ERC20-_update: pass:normal[xref:token/ERC20.adoc#ERC20-_update-address-address-uint256-[`ERC20._update`]] +:xref-ERC20Votes-_maxSupply--: xref:token/ERC20.adoc#ERC20Votes-_maxSupply-- +:xref-ERC20Votes-_update-address-address-uint256-: xref:token/ERC20.adoc#ERC20Votes-_update-address-address-uint256- +:xref-ERC20Votes-_getVotingUnits-address-: xref:token/ERC20.adoc#ERC20Votes-_getVotingUnits-address- +:xref-ERC20Votes-numCheckpoints-address-: xref:token/ERC20.adoc#ERC20Votes-numCheckpoints-address- +:xref-ERC20Votes-checkpoints-address-uint32-: xref:token/ERC20.adoc#ERC20Votes-checkpoints-address-uint32- +:xref-Votes-clock--: xref:governance.adoc#Votes-clock-- +:xref-Votes-CLOCK_MODE--: xref:governance.adoc#Votes-CLOCK_MODE-- +:xref-Votes-getVotes-address-: xref:governance.adoc#Votes-getVotes-address- +:xref-Votes-getPastVotes-address-uint256-: xref:governance.adoc#Votes-getPastVotes-address-uint256- +:xref-Votes-getPastTotalSupply-uint256-: xref:governance.adoc#Votes-getPastTotalSupply-uint256- +:xref-Votes-_getTotalSupply--: xref:governance.adoc#Votes-_getTotalSupply-- +:xref-Votes-delegates-address-: xref:governance.adoc#Votes-delegates-address- +:xref-Votes-delegate-address-: xref:governance.adoc#Votes-delegate-address- +:xref-Votes-delegateBySig-address-uint256-uint256-uint8-bytes32-bytes32-: xref:governance.adoc#Votes-delegateBySig-address-uint256-uint256-uint8-bytes32-bytes32- +:xref-Votes-_delegate-address-address-: xref:governance.adoc#Votes-_delegate-address-address- +:xref-Votes-_transferVotingUnits-address-address-uint256-: xref:governance.adoc#Votes-_transferVotingUnits-address-address-uint256- +:xref-Votes-_numCheckpoints-address-: xref:governance.adoc#Votes-_numCheckpoints-address- +:xref-Votes-_checkpoints-address-uint32-: xref:governance.adoc#Votes-_checkpoints-address-uint32- +:xref-Nonces-nonces-address-: xref:utils.adoc#Nonces-nonces-address- +:xref-Nonces-_useNonce-address-: xref:utils.adoc#Nonces-_useNonce-address- +:xref-Nonces-_useCheckedNonce-address-uint256-: xref:utils.adoc#Nonces-_useCheckedNonce-address-uint256- +:xref-EIP712-_domainSeparatorV4--: xref:utils.adoc#EIP712-_domainSeparatorV4-- +:xref-EIP712-_hashTypedDataV4-bytes32-: xref:utils.adoc#EIP712-_hashTypedDataV4-bytes32- +:xref-EIP712-eip712Domain--: xref:utils.adoc#EIP712-eip712Domain-- +:xref-EIP712-_EIP712Name--: xref:utils.adoc#EIP712-_EIP712Name-- +:xref-EIP712-_EIP712Version--: xref:utils.adoc#EIP712-_EIP712Version-- +:xref-ERC20-name--: xref:token/ERC20.adoc#ERC20-name-- +:xref-ERC20-symbol--: xref:token/ERC20.adoc#ERC20-symbol-- +:xref-ERC20-decimals--: xref:token/ERC20.adoc#ERC20-decimals-- +:xref-ERC20-totalSupply--: xref:token/ERC20.adoc#ERC20-totalSupply-- +:xref-ERC20-balanceOf-address-: xref:token/ERC20.adoc#ERC20-balanceOf-address- +:xref-ERC20-transfer-address-uint256-: xref:token/ERC20.adoc#ERC20-transfer-address-uint256- +:xref-ERC20-allowance-address-address-: xref:token/ERC20.adoc#ERC20-allowance-address-address- +:xref-ERC20-approve-address-uint256-: xref:token/ERC20.adoc#ERC20-approve-address-uint256- +:xref-ERC20-transferFrom-address-address-uint256-: xref:token/ERC20.adoc#ERC20-transferFrom-address-address-uint256- +:xref-ERC20-_transfer-address-address-uint256-: xref:token/ERC20.adoc#ERC20-_transfer-address-address-uint256- +:xref-ERC20-_mint-address-uint256-: xref:token/ERC20.adoc#ERC20-_mint-address-uint256- +:xref-ERC20-_burn-address-uint256-: xref:token/ERC20.adoc#ERC20-_burn-address-uint256- +:xref-ERC20-_approve-address-address-uint256-: xref:token/ERC20.adoc#ERC20-_approve-address-address-uint256- +:xref-ERC20-_approve-address-address-uint256-bool-: xref:token/ERC20.adoc#ERC20-_approve-address-address-uint256-bool- +:xref-ERC20-_spendAllowance-address-address-uint256-: xref:token/ERC20.adoc#ERC20-_spendAllowance-address-address-uint256- +:xref-IVotes-DelegateChanged-address-address-address-: xref:governance.adoc#IVotes-DelegateChanged-address-address-address- +:xref-IVotes-DelegateVotesChanged-address-uint256-uint256-: xref:governance.adoc#IVotes-DelegateVotesChanged-address-uint256-uint256- +:xref-IERC5267-EIP712DomainChanged--: xref:interfaces.adoc#IERC5267-EIP712DomainChanged-- +:xref-IERC20-Transfer-address-address-uint256-: xref:token/ERC20.adoc#IERC20-Transfer-address-address-uint256- +:xref-IERC20-Approval-address-address-uint256-: xref:token/ERC20.adoc#IERC20-Approval-address-address-uint256- +:xref-ERC20Votes-ERC20ExceededSafeSupply-uint256-uint256-: xref:token/ERC20.adoc#ERC20Votes-ERC20ExceededSafeSupply-uint256-uint256- +:xref-Votes-ERC6372InconsistentClock--: xref:governance.adoc#Votes-ERC6372InconsistentClock-- +:xref-Votes-ERC5805FutureLookup-uint256-uint48-: xref:governance.adoc#Votes-ERC5805FutureLookup-uint256-uint48- +:xref-IVotes-VotesExpiredSignature-uint256-: xref:governance.adoc#IVotes-VotesExpiredSignature-uint256- +:xref-Nonces-InvalidAccountNonce-address-uint256-: xref:utils.adoc#Nonces-InvalidAccountNonce-address-uint256- +:xref-IERC20Errors-ERC20InsufficientBalance-address-uint256-uint256-: xref:interfaces.adoc#IERC20Errors-ERC20InsufficientBalance-address-uint256-uint256- +:xref-IERC20Errors-ERC20InvalidSender-address-: xref:interfaces.adoc#IERC20Errors-ERC20InvalidSender-address- +:xref-IERC20Errors-ERC20InvalidReceiver-address-: xref:interfaces.adoc#IERC20Errors-ERC20InvalidReceiver-address- +:xref-IERC20Errors-ERC20InsufficientAllowance-address-uint256-uint256-: xref:interfaces.adoc#IERC20Errors-ERC20InsufficientAllowance-address-uint256-uint256- +:xref-IERC20Errors-ERC20InvalidApprover-address-: xref:interfaces.adoc#IERC20Errors-ERC20InvalidApprover-address- +:xref-IERC20Errors-ERC20InvalidSpender-address-: xref:interfaces.adoc#IERC20Errors-ERC20InvalidSpender-address- +:Votes: pass:normal[xref:governance.adoc#Votes[`Votes`]] +:IVotes-DelegateVotesChanged: pass:normal[xref:governance.adoc#IVotes-DelegateVotesChanged-address-uint256-uint256-[`IVotes.DelegateVotesChanged`]] +:ERC20Votes: pass:normal[xref:token/ERC20.adoc#ERC20Votes[`ERC20Votes`]] +:xref-ERC20Wrapper-constructor-contract-IERC20-: xref:token/ERC20.adoc#ERC20Wrapper-constructor-contract-IERC20- +:xref-ERC20Wrapper-decimals--: xref:token/ERC20.adoc#ERC20Wrapper-decimals-- +:xref-ERC20Wrapper-underlying--: xref:token/ERC20.adoc#ERC20Wrapper-underlying-- +:xref-ERC20Wrapper-depositFor-address-uint256-: xref:token/ERC20.adoc#ERC20Wrapper-depositFor-address-uint256- +:xref-ERC20Wrapper-withdrawTo-address-uint256-: xref:token/ERC20.adoc#ERC20Wrapper-withdrawTo-address-uint256- +:xref-ERC20Wrapper-_recover-address-: xref:token/ERC20.adoc#ERC20Wrapper-_recover-address- +:xref-ERC20-name--: xref:token/ERC20.adoc#ERC20-name-- +:xref-ERC20-symbol--: xref:token/ERC20.adoc#ERC20-symbol-- +:xref-ERC20-totalSupply--: xref:token/ERC20.adoc#ERC20-totalSupply-- +:xref-ERC20-balanceOf-address-: xref:token/ERC20.adoc#ERC20-balanceOf-address- +:xref-ERC20-transfer-address-uint256-: xref:token/ERC20.adoc#ERC20-transfer-address-uint256- +:xref-ERC20-allowance-address-address-: xref:token/ERC20.adoc#ERC20-allowance-address-address- +:xref-ERC20-approve-address-uint256-: xref:token/ERC20.adoc#ERC20-approve-address-uint256- +:xref-ERC20-transferFrom-address-address-uint256-: xref:token/ERC20.adoc#ERC20-transferFrom-address-address-uint256- +:xref-ERC20-_transfer-address-address-uint256-: xref:token/ERC20.adoc#ERC20-_transfer-address-address-uint256- +:xref-ERC20-_update-address-address-uint256-: xref:token/ERC20.adoc#ERC20-_update-address-address-uint256- +:xref-ERC20-_mint-address-uint256-: xref:token/ERC20.adoc#ERC20-_mint-address-uint256- +:xref-ERC20-_burn-address-uint256-: xref:token/ERC20.adoc#ERC20-_burn-address-uint256- +:xref-ERC20-_approve-address-address-uint256-: xref:token/ERC20.adoc#ERC20-_approve-address-address-uint256- +:xref-ERC20-_approve-address-address-uint256-bool-: xref:token/ERC20.adoc#ERC20-_approve-address-address-uint256-bool- +:xref-ERC20-_spendAllowance-address-address-uint256-: xref:token/ERC20.adoc#ERC20-_spendAllowance-address-address-uint256- +:xref-IERC20-Transfer-address-address-uint256-: xref:token/ERC20.adoc#IERC20-Transfer-address-address-uint256- +:xref-IERC20-Approval-address-address-uint256-: xref:token/ERC20.adoc#IERC20-Approval-address-address-uint256- +:xref-ERC20Wrapper-ERC20InvalidUnderlying-address-: xref:token/ERC20.adoc#ERC20Wrapper-ERC20InvalidUnderlying-address- +:xref-IERC20Errors-ERC20InsufficientBalance-address-uint256-uint256-: xref:interfaces.adoc#IERC20Errors-ERC20InsufficientBalance-address-uint256-uint256- +:xref-IERC20Errors-ERC20InvalidSender-address-: xref:interfaces.adoc#IERC20Errors-ERC20InvalidSender-address- +:xref-IERC20Errors-ERC20InvalidReceiver-address-: xref:interfaces.adoc#IERC20Errors-ERC20InvalidReceiver-address- +:xref-IERC20Errors-ERC20InsufficientAllowance-address-uint256-uint256-: xref:interfaces.adoc#IERC20Errors-ERC20InsufficientAllowance-address-uint256-uint256- +:xref-IERC20Errors-ERC20InvalidApprover-address-: xref:interfaces.adoc#IERC20Errors-ERC20InvalidApprover-address- +:xref-IERC20Errors-ERC20InvalidSpender-address-: xref:interfaces.adoc#IERC20Errors-ERC20InvalidSpender-address- +:ERC20-decimals: pass:normal[xref:token/ERC20.adoc#ERC20-decimals--[`ERC20.decimals`]] +:ERC20Capped: pass:normal[xref:token/ERC20.adoc#ERC20Capped[`ERC20Capped`]] +:ERC20Votes: pass:normal[xref:token/ERC20.adoc#ERC20Votes[`ERC20Votes`]] +:xref-ERC20FlashMint-maxFlashLoan-address-: xref:token/ERC20.adoc#ERC20FlashMint-maxFlashLoan-address- +:xref-ERC20FlashMint-flashFee-address-uint256-: xref:token/ERC20.adoc#ERC20FlashMint-flashFee-address-uint256- +:xref-ERC20FlashMint-_flashFee-address-uint256-: xref:token/ERC20.adoc#ERC20FlashMint-_flashFee-address-uint256- +:xref-ERC20FlashMint-_flashFeeReceiver--: xref:token/ERC20.adoc#ERC20FlashMint-_flashFeeReceiver-- +:xref-ERC20FlashMint-flashLoan-contract-IERC3156FlashBorrower-address-uint256-bytes-: xref:token/ERC20.adoc#ERC20FlashMint-flashLoan-contract-IERC3156FlashBorrower-address-uint256-bytes- +:xref-ERC20-name--: xref:token/ERC20.adoc#ERC20-name-- +:xref-ERC20-symbol--: xref:token/ERC20.adoc#ERC20-symbol-- +:xref-ERC20-decimals--: xref:token/ERC20.adoc#ERC20-decimals-- +:xref-ERC20-totalSupply--: xref:token/ERC20.adoc#ERC20-totalSupply-- +:xref-ERC20-balanceOf-address-: xref:token/ERC20.adoc#ERC20-balanceOf-address- +:xref-ERC20-transfer-address-uint256-: xref:token/ERC20.adoc#ERC20-transfer-address-uint256- +:xref-ERC20-allowance-address-address-: xref:token/ERC20.adoc#ERC20-allowance-address-address- +:xref-ERC20-approve-address-uint256-: xref:token/ERC20.adoc#ERC20-approve-address-uint256- +:xref-ERC20-transferFrom-address-address-uint256-: xref:token/ERC20.adoc#ERC20-transferFrom-address-address-uint256- +:xref-ERC20-_transfer-address-address-uint256-: xref:token/ERC20.adoc#ERC20-_transfer-address-address-uint256- +:xref-ERC20-_update-address-address-uint256-: xref:token/ERC20.adoc#ERC20-_update-address-address-uint256- +:xref-ERC20-_mint-address-uint256-: xref:token/ERC20.adoc#ERC20-_mint-address-uint256- +:xref-ERC20-_burn-address-uint256-: xref:token/ERC20.adoc#ERC20-_burn-address-uint256- +:xref-ERC20-_approve-address-address-uint256-: xref:token/ERC20.adoc#ERC20-_approve-address-address-uint256- +:xref-ERC20-_approve-address-address-uint256-bool-: xref:token/ERC20.adoc#ERC20-_approve-address-address-uint256-bool- +:xref-ERC20-_spendAllowance-address-address-uint256-: xref:token/ERC20.adoc#ERC20-_spendAllowance-address-address-uint256- +:xref-IERC20-Transfer-address-address-uint256-: xref:token/ERC20.adoc#IERC20-Transfer-address-address-uint256- +:xref-IERC20-Approval-address-address-uint256-: xref:token/ERC20.adoc#IERC20-Approval-address-address-uint256- +:xref-ERC20FlashMint-ERC3156UnsupportedToken-address-: xref:token/ERC20.adoc#ERC20FlashMint-ERC3156UnsupportedToken-address- +:xref-ERC20FlashMint-ERC3156ExceededMaxLoan-uint256-: xref:token/ERC20.adoc#ERC20FlashMint-ERC3156ExceededMaxLoan-uint256- +:xref-ERC20FlashMint-ERC3156InvalidReceiver-address-: xref:token/ERC20.adoc#ERC20FlashMint-ERC3156InvalidReceiver-address- +:xref-IERC20Errors-ERC20InsufficientBalance-address-uint256-uint256-: xref:interfaces.adoc#IERC20Errors-ERC20InsufficientBalance-address-uint256-uint256- +:xref-IERC20Errors-ERC20InvalidSender-address-: xref:interfaces.adoc#IERC20Errors-ERC20InvalidSender-address- +:xref-IERC20Errors-ERC20InvalidReceiver-address-: xref:interfaces.adoc#IERC20Errors-ERC20InvalidReceiver-address- +:xref-IERC20Errors-ERC20InsufficientAllowance-address-uint256-uint256-: xref:interfaces.adoc#IERC20Errors-ERC20InsufficientAllowance-address-uint256-uint256- +:xref-IERC20Errors-ERC20InvalidApprover-address-: xref:interfaces.adoc#IERC20Errors-ERC20InvalidApprover-address- +:xref-IERC20Errors-ERC20InvalidSpender-address-: xref:interfaces.adoc#IERC20Errors-ERC20InvalidSpender-address- +:IERC3156FlashBorrower: pass:normal[xref:interfaces.adoc#IERC3156FlashBorrower[`IERC3156FlashBorrower`]] +:xref-ERC4626-constructor-contract-IERC20-: xref:token/ERC20.adoc#ERC4626-constructor-contract-IERC20- +:xref-ERC4626-decimals--: xref:token/ERC20.adoc#ERC4626-decimals-- +:xref-ERC4626-asset--: xref:token/ERC20.adoc#ERC4626-asset-- +:xref-ERC4626-totalAssets--: xref:token/ERC20.adoc#ERC4626-totalAssets-- +:xref-ERC4626-convertToShares-uint256-: xref:token/ERC20.adoc#ERC4626-convertToShares-uint256- +:xref-ERC4626-convertToAssets-uint256-: xref:token/ERC20.adoc#ERC4626-convertToAssets-uint256- +:xref-ERC4626-maxDeposit-address-: xref:token/ERC20.adoc#ERC4626-maxDeposit-address- +:xref-ERC4626-maxMint-address-: xref:token/ERC20.adoc#ERC4626-maxMint-address- +:xref-ERC4626-maxWithdraw-address-: xref:token/ERC20.adoc#ERC4626-maxWithdraw-address- +:xref-ERC4626-maxRedeem-address-: xref:token/ERC20.adoc#ERC4626-maxRedeem-address- +:xref-ERC4626-previewDeposit-uint256-: xref:token/ERC20.adoc#ERC4626-previewDeposit-uint256- +:xref-ERC4626-previewMint-uint256-: xref:token/ERC20.adoc#ERC4626-previewMint-uint256- +:xref-ERC4626-previewWithdraw-uint256-: xref:token/ERC20.adoc#ERC4626-previewWithdraw-uint256- +:xref-ERC4626-previewRedeem-uint256-: xref:token/ERC20.adoc#ERC4626-previewRedeem-uint256- +:xref-ERC4626-deposit-uint256-address-: xref:token/ERC20.adoc#ERC4626-deposit-uint256-address- +:xref-ERC4626-mint-uint256-address-: xref:token/ERC20.adoc#ERC4626-mint-uint256-address- +:xref-ERC4626-withdraw-uint256-address-address-: xref:token/ERC20.adoc#ERC4626-withdraw-uint256-address-address- +:xref-ERC4626-redeem-uint256-address-address-: xref:token/ERC20.adoc#ERC4626-redeem-uint256-address-address- +:xref-ERC4626-_convertToShares-uint256-enum-Math-Rounding-: xref:token/ERC20.adoc#ERC4626-_convertToShares-uint256-enum-Math-Rounding- +:xref-ERC4626-_convertToAssets-uint256-enum-Math-Rounding-: xref:token/ERC20.adoc#ERC4626-_convertToAssets-uint256-enum-Math-Rounding- +:xref-ERC4626-_deposit-address-address-uint256-uint256-: xref:token/ERC20.adoc#ERC4626-_deposit-address-address-uint256-uint256- +:xref-ERC4626-_withdraw-address-address-address-uint256-uint256-: xref:token/ERC20.adoc#ERC4626-_withdraw-address-address-address-uint256-uint256- +:xref-ERC4626-_decimalsOffset--: xref:token/ERC20.adoc#ERC4626-_decimalsOffset-- +:xref-ERC20-name--: xref:token/ERC20.adoc#ERC20-name-- +:xref-ERC20-symbol--: xref:token/ERC20.adoc#ERC20-symbol-- +:xref-ERC20-totalSupply--: xref:token/ERC20.adoc#ERC20-totalSupply-- +:xref-ERC20-balanceOf-address-: xref:token/ERC20.adoc#ERC20-balanceOf-address- +:xref-ERC20-transfer-address-uint256-: xref:token/ERC20.adoc#ERC20-transfer-address-uint256- +:xref-ERC20-allowance-address-address-: xref:token/ERC20.adoc#ERC20-allowance-address-address- +:xref-ERC20-approve-address-uint256-: xref:token/ERC20.adoc#ERC20-approve-address-uint256- +:xref-ERC20-transferFrom-address-address-uint256-: xref:token/ERC20.adoc#ERC20-transferFrom-address-address-uint256- +:xref-ERC20-_transfer-address-address-uint256-: xref:token/ERC20.adoc#ERC20-_transfer-address-address-uint256- +:xref-ERC20-_update-address-address-uint256-: xref:token/ERC20.adoc#ERC20-_update-address-address-uint256- +:xref-ERC20-_mint-address-uint256-: xref:token/ERC20.adoc#ERC20-_mint-address-uint256- +:xref-ERC20-_burn-address-uint256-: xref:token/ERC20.adoc#ERC20-_burn-address-uint256- +:xref-ERC20-_approve-address-address-uint256-: xref:token/ERC20.adoc#ERC20-_approve-address-address-uint256- +:xref-ERC20-_approve-address-address-uint256-bool-: xref:token/ERC20.adoc#ERC20-_approve-address-address-uint256-bool- +:xref-ERC20-_spendAllowance-address-address-uint256-: xref:token/ERC20.adoc#ERC20-_spendAllowance-address-address-uint256- +:xref-IERC4626-Deposit-address-address-uint256-uint256-: xref:interfaces.adoc#IERC4626-Deposit-address-address-uint256-uint256- +:xref-IERC4626-Withdraw-address-address-address-uint256-uint256-: xref:interfaces.adoc#IERC4626-Withdraw-address-address-address-uint256-uint256- +:xref-IERC20-Transfer-address-address-uint256-: xref:token/ERC20.adoc#IERC20-Transfer-address-address-uint256- +:xref-IERC20-Approval-address-address-uint256-: xref:token/ERC20.adoc#IERC20-Approval-address-address-uint256- +:xref-ERC4626-ERC4626ExceededMaxDeposit-address-uint256-uint256-: xref:token/ERC20.adoc#ERC4626-ERC4626ExceededMaxDeposit-address-uint256-uint256- +:xref-ERC4626-ERC4626ExceededMaxMint-address-uint256-uint256-: xref:token/ERC20.adoc#ERC4626-ERC4626ExceededMaxMint-address-uint256-uint256- +:xref-ERC4626-ERC4626ExceededMaxWithdraw-address-uint256-uint256-: xref:token/ERC20.adoc#ERC4626-ERC4626ExceededMaxWithdraw-address-uint256-uint256- +:xref-ERC4626-ERC4626ExceededMaxRedeem-address-uint256-uint256-: xref:token/ERC20.adoc#ERC4626-ERC4626ExceededMaxRedeem-address-uint256-uint256- +:xref-IERC20Errors-ERC20InsufficientBalance-address-uint256-uint256-: xref:interfaces.adoc#IERC20Errors-ERC20InsufficientBalance-address-uint256-uint256- +:xref-IERC20Errors-ERC20InvalidSender-address-: xref:interfaces.adoc#IERC20Errors-ERC20InvalidSender-address- +:xref-IERC20Errors-ERC20InvalidReceiver-address-: xref:interfaces.adoc#IERC20Errors-ERC20InvalidReceiver-address- +:xref-IERC20Errors-ERC20InsufficientAllowance-address-uint256-uint256-: xref:interfaces.adoc#IERC20Errors-ERC20InsufficientAllowance-address-uint256-uint256- +:xref-IERC20Errors-ERC20InvalidApprover-address-: xref:interfaces.adoc#IERC20Errors-ERC20InvalidApprover-address- +:xref-IERC20Errors-ERC20InvalidSpender-address-: xref:interfaces.adoc#IERC20Errors-ERC20InvalidSpender-address- +:IERC20Metadata-decimals: pass:normal[xref:token/ERC20.adoc#IERC20Metadata-decimals--[`IERC20Metadata.decimals`]] +:IERC4626-asset: pass:normal[xref:interfaces.adoc#IERC4626-asset--[`IERC4626.asset`]] +:IERC4626-totalAssets: pass:normal[xref:interfaces.adoc#IERC4626-totalAssets--[`IERC4626.totalAssets`]] +:IERC4626-convertToShares: pass:normal[xref:interfaces.adoc#IERC4626-convertToShares-uint256-[`IERC4626.convertToShares`]] +:IERC4626-convertToAssets: pass:normal[xref:interfaces.adoc#IERC4626-convertToAssets-uint256-[`IERC4626.convertToAssets`]] +:IERC4626-maxDeposit: pass:normal[xref:interfaces.adoc#IERC4626-maxDeposit-address-[`IERC4626.maxDeposit`]] +:IERC4626-maxMint: pass:normal[xref:interfaces.adoc#IERC4626-maxMint-address-[`IERC4626.maxMint`]] +:IERC4626-maxWithdraw: pass:normal[xref:interfaces.adoc#IERC4626-maxWithdraw-address-[`IERC4626.maxWithdraw`]] +:IERC4626-maxRedeem: pass:normal[xref:interfaces.adoc#IERC4626-maxRedeem-address-[`IERC4626.maxRedeem`]] +:IERC4626-previewDeposit: pass:normal[xref:interfaces.adoc#IERC4626-previewDeposit-uint256-[`IERC4626.previewDeposit`]] +:IERC4626-previewMint: pass:normal[xref:interfaces.adoc#IERC4626-previewMint-uint256-[`IERC4626.previewMint`]] +:IERC4626-previewWithdraw: pass:normal[xref:interfaces.adoc#IERC4626-previewWithdraw-uint256-[`IERC4626.previewWithdraw`]] +:IERC4626-previewRedeem: pass:normal[xref:interfaces.adoc#IERC4626-previewRedeem-uint256-[`IERC4626.previewRedeem`]] +:IERC4626-deposit: pass:normal[xref:interfaces.adoc#IERC4626-deposit-uint256-address-[`IERC4626.deposit`]] +:IERC4626-mint: pass:normal[xref:interfaces.adoc#IERC4626-mint-uint256-address-[`IERC4626.mint`]] +:IERC4626-withdraw: pass:normal[xref:interfaces.adoc#IERC4626-withdraw-uint256-address-address-[`IERC4626.withdraw`]] +:IERC4626-redeem: pass:normal[xref:interfaces.adoc#IERC4626-redeem-uint256-address-address-[`IERC4626.redeem`]] +:xref-SafeERC20-safeTransfer-contract-IERC20-address-uint256-: xref:token/ERC20.adoc#SafeERC20-safeTransfer-contract-IERC20-address-uint256- +:xref-SafeERC20-safeTransferFrom-contract-IERC20-address-address-uint256-: xref:token/ERC20.adoc#SafeERC20-safeTransferFrom-contract-IERC20-address-address-uint256- +:xref-SafeERC20-safeIncreaseAllowance-contract-IERC20-address-uint256-: xref:token/ERC20.adoc#SafeERC20-safeIncreaseAllowance-contract-IERC20-address-uint256- +:xref-SafeERC20-safeDecreaseAllowance-contract-IERC20-address-uint256-: xref:token/ERC20.adoc#SafeERC20-safeDecreaseAllowance-contract-IERC20-address-uint256- +:xref-SafeERC20-forceApprove-contract-IERC20-address-uint256-: xref:token/ERC20.adoc#SafeERC20-forceApprove-contract-IERC20-address-uint256- +:xref-SafeERC20-SafeERC20FailedOperation-address-: xref:token/ERC20.adoc#SafeERC20-SafeERC20FailedOperation-address- +:xref-SafeERC20-SafeERC20FailedDecreaseAllowance-address-uint256-uint256-: xref:token/ERC20.adoc#SafeERC20-SafeERC20FailedDecreaseAllowance-address-uint256-uint256- += ERC 20 + +[.readme-notice] +NOTE: This document is better viewed at https://docs.openzeppelin.com/contracts/api/token/erc20 + +This set of interfaces, contracts, and utilities are all related to the https://eips.ethereum.org/EIPS/eip-20[ERC20 Token Standard]. + +TIP: For an overview of ERC20 tokens and a walk through on how to create a token contract read our xref:ROOT:erc20.adoc[ERC20 guide]. + +There are a few core contracts that implement the behavior specified in the EIP: + +* {IERC20}: the interface all ERC20 implementations should conform to. +* {IERC20Metadata}: the extended ERC20 interface including the <>, <> and <> functions. +* {ERC20}: the implementation of the ERC20 interface, including the <>, <> and <> optional standard extension to the base interface. + +Additionally there are multiple custom extensions, including: + +* {ERC20Permit}: gasless approval of tokens (standardized as ERC2612). +* {ERC20Burnable}: destruction of own tokens. +* {ERC20Capped}: enforcement of a cap to the total supply when minting tokens. +* {ERC20Pausable}: ability to pause token transfers. +* {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. +* {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: + +* {SafeERC20}: a wrapper around the interface that eliminates the need to handle boolean return values. + +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. + +== Core + +:Transfer: pass:normal[xref:#IERC20-Transfer-address-address-uint256-[`++Transfer++`]] +:Approval: pass:normal[xref:#IERC20-Approval-address-address-uint256-[`++Approval++`]] +:totalSupply: pass:normal[xref:#IERC20-totalSupply--[`++totalSupply++`]] +:balanceOf: pass:normal[xref:#IERC20-balanceOf-address-[`++balanceOf++`]] +:transfer: pass:normal[xref:#IERC20-transfer-address-uint256-[`++transfer++`]] +:allowance: pass:normal[xref:#IERC20-allowance-address-address-[`++allowance++`]] +:approve: pass:normal[xref:#IERC20-approve-address-uint256-[`++approve++`]] +:transferFrom: pass:normal[xref:#IERC20-transferFrom-address-address-uint256-[`++transferFrom++`]] + +[.contract] +[[IERC20]] +=== `++IERC20++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/token/ERC20/IERC20.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +``` + +Interface of the ERC20 standard as defined in the EIP. + +[.contract-index] +.Functions +-- +* {xref-IERC20-totalSupply--}[`++totalSupply()++`] +* {xref-IERC20-balanceOf-address-}[`++balanceOf(account)++`] +* {xref-IERC20-transfer-address-uint256-}[`++transfer(to, value)++`] +* {xref-IERC20-allowance-address-address-}[`++allowance(owner, spender)++`] +* {xref-IERC20-approve-address-uint256-}[`++approve(spender, value)++`] +* {xref-IERC20-transferFrom-address-address-uint256-}[`++transferFrom(from, to, value)++`] + +-- + +[.contract-index] +.Events +-- +* {xref-IERC20-Transfer-address-address-uint256-}[`++Transfer(from, to, value)++`] +* {xref-IERC20-Approval-address-address-uint256-}[`++Approval(owner, spender, value)++`] + +-- + +[.contract-item] +[[IERC20-totalSupply--]] +==== `[.contract-item-name]#++totalSupply++#++() β†’ uint256++` [.item-kind]#external# + +Returns the value of tokens in existence. + +[.contract-item] +[[IERC20-balanceOf-address-]] +==== `[.contract-item-name]#++balanceOf++#++(address account) β†’ uint256++` [.item-kind]#external# + +Returns the value of tokens owned by `account`. + +[.contract-item] +[[IERC20-transfer-address-uint256-]] +==== `[.contract-item-name]#++transfer++#++(address to, uint256 value) β†’ bool++` [.item-kind]#external# + +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. + +[.contract-item] +[[IERC20-allowance-address-address-]] +==== `[.contract-item-name]#++allowance++#++(address owner, address spender) β†’ uint256++` [.item-kind]#external# + +Returns the remaining number of tokens that `spender` will be +allowed to spend on behalf of `owner` through {transferFrom}. This is +zero by default. + +This value changes when {approve} or {transferFrom} are called. + +[.contract-item] +[[IERC20-approve-address-uint256-]] +==== `[.contract-item-name]#++approve++#++(address spender, uint256 value) β†’ bool++` [.item-kind]#external# + +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. + +IMPORTANT: Beware that changing an allowance with this method brings the risk +that someone may use both the old and the new allowance by unfortunate +transaction ordering. One possible solution to mitigate this race +condition is to first reduce the spender's allowance to 0 and set the +desired value afterwards: +https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + +Emits an {Approval} event. + +[.contract-item] +[[IERC20-transferFrom-address-address-uint256-]] +==== `[.contract-item-name]#++transferFrom++#++(address from, address to, uint256 value) β†’ bool++` [.item-kind]#external# + +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. + +[.contract-item] +[[IERC20-Transfer-address-address-uint256-]] +==== `[.contract-item-name]#++Transfer++#++(address indexed from, address indexed to, uint256 value)++` [.item-kind]#event# + +Emitted when `value` tokens are moved from one account (`from`) to +another (`to`). + +Note that `value` may be zero. + +[.contract-item] +[[IERC20-Approval-address-address-uint256-]] +==== `[.contract-item-name]#++Approval++#++(address indexed owner, address indexed spender, uint256 value)++` [.item-kind]#event# + +Emitted when the allowance of a `spender` for an `owner` is set by +a call to {approve}. `value` is the new allowance. + +:name: pass:normal[xref:#IERC20Metadata-name--[`++name++`]] +:symbol: pass:normal[xref:#IERC20Metadata-symbol--[`++symbol++`]] +:decimals: pass:normal[xref:#IERC20Metadata-decimals--[`++decimals++`]] + +[.contract] +[[IERC20Metadata]] +=== `++IERC20Metadata++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/token/ERC20/extensions/IERC20Metadata.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +``` + +Interface for the optional metadata functions from the ERC20 standard. + +[.contract-index] +.Functions +-- +* {xref-IERC20Metadata-name--}[`++name()++`] +* {xref-IERC20Metadata-symbol--}[`++symbol()++`] +* {xref-IERC20Metadata-decimals--}[`++decimals()++`] + +[.contract-subindex-inherited] +.IERC20 +* {xref-IERC20-totalSupply--}[`++totalSupply()++`] +* {xref-IERC20-balanceOf-address-}[`++balanceOf(account)++`] +* {xref-IERC20-transfer-address-uint256-}[`++transfer(to, value)++`] +* {xref-IERC20-allowance-address-address-}[`++allowance(owner, spender)++`] +* {xref-IERC20-approve-address-uint256-}[`++approve(spender, value)++`] +* {xref-IERC20-transferFrom-address-address-uint256-}[`++transferFrom(from, to, value)++`] + +-- + +[.contract-index] +.Events +-- + +[.contract-subindex-inherited] +.IERC20 +* {xref-IERC20-Transfer-address-address-uint256-}[`++Transfer(from, to, value)++`] +* {xref-IERC20-Approval-address-address-uint256-}[`++Approval(owner, spender, value)++`] + +-- + +[.contract-item] +[[IERC20Metadata-name--]] +==== `[.contract-item-name]#++name++#++() β†’ string++` [.item-kind]#external# + +Returns the name of the token. + +[.contract-item] +[[IERC20Metadata-symbol--]] +==== `[.contract-item-name]#++symbol++#++() β†’ string++` [.item-kind]#external# + +Returns the symbol of the token. + +[.contract-item] +[[IERC20Metadata-decimals--]] +==== `[.contract-item-name]#++decimals++#++() β†’ uint8++` [.item-kind]#external# + +Returns the decimals places of the token. + +:constructor: pass:normal[xref:#ERC20-constructor-string-string-[`++constructor++`]] +:name: pass:normal[xref:#ERC20-name--[`++name++`]] +:symbol: pass:normal[xref:#ERC20-symbol--[`++symbol++`]] +:decimals: pass:normal[xref:#ERC20-decimals--[`++decimals++`]] +:totalSupply: pass:normal[xref:#ERC20-totalSupply--[`++totalSupply++`]] +:balanceOf: pass:normal[xref:#ERC20-balanceOf-address-[`++balanceOf++`]] +:transfer: pass:normal[xref:#ERC20-transfer-address-uint256-[`++transfer++`]] +:allowance: pass:normal[xref:#ERC20-allowance-address-address-[`++allowance++`]] +:approve: pass:normal[xref:#ERC20-approve-address-uint256-[`++approve++`]] +:transferFrom: pass:normal[xref:#ERC20-transferFrom-address-address-uint256-[`++transferFrom++`]] +:_transfer: pass:normal[xref:#ERC20-_transfer-address-address-uint256-[`++_transfer++`]] +:_update: pass:normal[xref:#ERC20-_update-address-address-uint256-[`++_update++`]] +:_mint: pass:normal[xref:#ERC20-_mint-address-uint256-[`++_mint++`]] +:_burn: pass:normal[xref:#ERC20-_burn-address-uint256-[`++_burn++`]] +:_approve: pass:normal[xref:#ERC20-_approve-address-address-uint256-[`++_approve++`]] +:_approve: pass:normal[xref:#ERC20-_approve-address-address-uint256-bool-[`++_approve++`]] +:_spendAllowance: pass:normal[xref:#ERC20-_spendAllowance-address-address-uint256-[`++_spendAllowance++`]] + +[.contract] +[[ERC20]] +=== `++ERC20++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/token/ERC20/ERC20.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +``` + +Implementation of the {IERC20} 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}. + +TIP: For a detailed writeup see our guide +https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How +to implement supply mechanisms]. + +The default value of {decimals} is 18. To change this, you should override +this function so it returns a different value. + +We have followed general OpenZeppelin Contracts guidelines: functions revert +instead returning `false` on failure. This behavior is nonetheless +conventional and does not conflict with the expectations of ERC20 +applications. + +Additionally, an {Approval} event is emitted on calls to {transferFrom}. +This allows applications to reconstruct the allowance for all accounts just +by listening to said events. Other implementations of the EIP may not emit +these events, as it isn't required by the specification. + +[.contract-index] +.Functions +-- +* {xref-ERC20-constructor-string-string-}[`++constructor(name_, symbol_)++`] +* {xref-ERC20-name--}[`++name()++`] +* {xref-ERC20-symbol--}[`++symbol()++`] +* {xref-ERC20-decimals--}[`++decimals()++`] +* {xref-ERC20-totalSupply--}[`++totalSupply()++`] +* {xref-ERC20-balanceOf-address-}[`++balanceOf(account)++`] +* {xref-ERC20-transfer-address-uint256-}[`++transfer(to, value)++`] +* {xref-ERC20-allowance-address-address-}[`++allowance(owner, spender)++`] +* {xref-ERC20-approve-address-uint256-}[`++approve(spender, value)++`] +* {xref-ERC20-transferFrom-address-address-uint256-}[`++transferFrom(from, to, value)++`] +* {xref-ERC20-_transfer-address-address-uint256-}[`++_transfer(from, to, value)++`] +* {xref-ERC20-_update-address-address-uint256-}[`++_update(from, to, value)++`] +* {xref-ERC20-_mint-address-uint256-}[`++_mint(account, value)++`] +* {xref-ERC20-_burn-address-uint256-}[`++_burn(account, value)++`] +* {xref-ERC20-_approve-address-address-uint256-}[`++_approve(owner, spender, value)++`] +* {xref-ERC20-_approve-address-address-uint256-bool-}[`++_approve(owner, spender, value, emitEvent)++`] +* {xref-ERC20-_spendAllowance-address-address-uint256-}[`++_spendAllowance(owner, spender, value)++`] + +[.contract-subindex-inherited] +.IERC20Errors + +[.contract-subindex-inherited] +.IERC20Metadata + +[.contract-subindex-inherited] +.IERC20 + +-- + +[.contract-index] +.Events +-- + +[.contract-subindex-inherited] +.IERC20Errors + +[.contract-subindex-inherited] +.IERC20Metadata + +[.contract-subindex-inherited] +.IERC20 +* {xref-IERC20-Transfer-address-address-uint256-}[`++Transfer(from, to, value)++`] +* {xref-IERC20-Approval-address-address-uint256-}[`++Approval(owner, spender, value)++`] + +-- + +[.contract-index] +.Errors +-- + +[.contract-subindex-inherited] +.IERC20Errors +* {xref-IERC20Errors-ERC20InsufficientBalance-address-uint256-uint256-}[`++ERC20InsufficientBalance(sender, balance, needed)++`] +* {xref-IERC20Errors-ERC20InvalidSender-address-}[`++ERC20InvalidSender(sender)++`] +* {xref-IERC20Errors-ERC20InvalidReceiver-address-}[`++ERC20InvalidReceiver(receiver)++`] +* {xref-IERC20Errors-ERC20InsufficientAllowance-address-uint256-uint256-}[`++ERC20InsufficientAllowance(spender, allowance, needed)++`] +* {xref-IERC20Errors-ERC20InvalidApprover-address-}[`++ERC20InvalidApprover(approver)++`] +* {xref-IERC20Errors-ERC20InvalidSpender-address-}[`++ERC20InvalidSpender(spender)++`] + +[.contract-subindex-inherited] +.IERC20Metadata + +[.contract-subindex-inherited] +.IERC20 + +-- + +[.contract-item] +[[ERC20-constructor-string-string-]] +==== `[.contract-item-name]#++constructor++#++(string name_, string symbol_)++` [.item-kind]#internal# + +Sets the values for {name} and {symbol}. + +All two of these values are immutable: they can only be set once during +construction. + +[.contract-item] +[[ERC20-name--]] +==== `[.contract-item-name]#++name++#++() β†’ string++` [.item-kind]#public# + +Returns the name of the token. + +[.contract-item] +[[ERC20-symbol--]] +==== `[.contract-item-name]#++symbol++#++() β†’ string++` [.item-kind]#public# + +Returns the symbol of the token, usually a shorter version of the +name. + +[.contract-item] +[[ERC20-decimals--]] +==== `[.contract-item-name]#++decimals++#++() β†’ uint8++` [.item-kind]#public# + +Returns the number of decimals used to get its user representation. +For example, if `decimals` equals `2`, a balance of `505` tokens should +be displayed to a user as `5.05` (`505 / 10 ** 2`). + +Tokens usually opt for a value of 18, imitating the relationship between +Ether and Wei. This is the default value returned by this function, unless +it's overridden. + +NOTE: This information is only used for _display_ purposes: it in +no way affects any of the arithmetic of the contract, including +{IERC20-balanceOf} and {IERC20-transfer}. + +[.contract-item] +[[ERC20-totalSupply--]] +==== `[.contract-item-name]#++totalSupply++#++() β†’ uint256++` [.item-kind]#public# + +See {IERC20-totalSupply}. + +[.contract-item] +[[ERC20-balanceOf-address-]] +==== `[.contract-item-name]#++balanceOf++#++(address account) β†’ uint256++` [.item-kind]#public# + +See {IERC20-balanceOf}. + +[.contract-item] +[[ERC20-transfer-address-uint256-]] +==== `[.contract-item-name]#++transfer++#++(address to, uint256 value) β†’ bool++` [.item-kind]#public# + +See {IERC20-transfer}. + +Requirements: + +- `to` cannot be the zero address. +- the caller must have a balance of at least `value`. + +[.contract-item] +[[ERC20-allowance-address-address-]] +==== `[.contract-item-name]#++allowance++#++(address owner, address spender) β†’ uint256++` [.item-kind]#public# + +See {IERC20-allowance}. + +[.contract-item] +[[ERC20-approve-address-uint256-]] +==== `[.contract-item-name]#++approve++#++(address spender, uint256 value) β†’ bool++` [.item-kind]#public# + +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. + +Requirements: + +- `spender` cannot be the zero address. + +[.contract-item] +[[ERC20-transferFrom-address-address-uint256-]] +==== `[.contract-item-name]#++transferFrom++#++(address from, address to, uint256 value) β†’ bool++` [.item-kind]#public# + +See {IERC20-transferFrom}. + +Emits an {Approval} event indicating the updated allowance. This is not +required by the EIP. See the note at the beginning of {ERC20}. + +NOTE: Does not update the allowance if the current allowance +is the maximum `uint256`. + +Requirements: + +- `from` and `to` cannot be the zero address. +- `from` must have a balance of at least `value`. +- the caller must have allowance for ``from``'s tokens of at least +`value`. + +[.contract-item] +[[ERC20-_transfer-address-address-uint256-]] +==== `[.contract-item-name]#++_transfer++#++(address from, address to, uint256 value)++` [.item-kind]#internal# + +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. + +Emits a {Transfer} event. + +NOTE: This function is not virtual, {_update} should be overridden instead. + +[.contract-item] +[[ERC20-_update-address-address-uint256-]] +==== `[.contract-item-name]#++_update++#++(address from, address to, uint256 value)++` [.item-kind]#internal# + +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. + +[.contract-item] +[[ERC20-_mint-address-uint256-]] +==== `[.contract-item-name]#++_mint++#++(address account, uint256 value)++` [.item-kind]#internal# + +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. + +[.contract-item] +[[ERC20-_burn-address-uint256-]] +==== `[.contract-item-name]#++_burn++#++(address account, uint256 value)++` [.item-kind]#internal# + +Destroys a `value` amount of tokens from `account`, lowering the total supply. +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 + +[.contract-item] +[[ERC20-_approve-address-address-uint256-]] +==== `[.contract-item-name]#++_approve++#++(address owner, address spender, uint256 value)++` [.item-kind]#internal# + +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. + +Emits an {Approval} event. + +Requirements: + +- `owner` cannot be the zero address. +- `spender` cannot be the zero address. + +Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument. + +[.contract-item] +[[ERC20-_approve-address-address-uint256-bool-]] +==== `[.contract-item-name]#++_approve++#++(address owner, address spender, uint256 value, bool emitEvent)++` [.item-kind]#internal# + +Variant of {_approve} with an optional flag to 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 value, bool) internal virtual override { + super._approve(owner, spender, value, true); +} +``` + +Requirements are the same as {_approve}. + +[.contract-item] +[[ERC20-_spendAllowance-address-address-uint256-]] +==== `[.contract-item-name]#++_spendAllowance++#++(address owner, address spender, uint256 value)++` [.item-kind]#internal# + +Updates `owner` s allowance for `spender` based on spent `value`. + +Does not update the allowance value in case of infinite allowance. +Revert if not enough allowance is available. + +Does not emit an {Approval} event. + +== Extensions + +:permit: pass:normal[xref:#IERC20Permit-permit-address-address-uint256-uint256-uint8-bytes32-bytes32-[`++permit++`]] +:nonces: pass:normal[xref:#IERC20Permit-nonces-address-[`++nonces++`]] +:DOMAIN_SEPARATOR: pass:normal[xref:#IERC20Permit-DOMAIN_SEPARATOR--[`++DOMAIN_SEPARATOR++`]] + +[.contract] +[[IERC20Permit]] +=== `++IERC20Permit++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/token/ERC20/extensions/IERC20Permit.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol"; +``` + +Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in +https://eips.ethereum.org/EIPS/eip-2612[EIP-2612]. + +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. + +==== Security Considerations + +There are two important considerations concerning the use of `permit`. The first is that a valid permit signature +expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be +considered as an intention to spend the allowance in any specific way. The second is that because permits have +built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should +take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be +generally recommended is: + +```solidity +function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public { + try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {} + doThing(..., value); +} + +function doThing(..., uint256 value) public { + token.safeTransferFrom(msg.sender, address(this), value); + ... +} +``` + +Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of +`try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also +{SafeERC20-safeTransferFrom}). + +Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so +contracts should have entry points that don't rely on permit. + +[.contract-index] +.Functions +-- +* {xref-IERC20Permit-permit-address-address-uint256-uint256-uint8-bytes32-bytes32-}[`++permit(owner, spender, value, deadline, v, r, s)++`] +* {xref-IERC20Permit-nonces-address-}[`++nonces(owner)++`] +* {xref-IERC20Permit-DOMAIN_SEPARATOR--}[`++DOMAIN_SEPARATOR()++`] + +-- + +[.contract-item] +[[IERC20Permit-permit-address-address-uint256-uint256-uint8-bytes32-bytes32-]] +==== `[.contract-item-name]#++permit++#++(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s)++` [.item-kind]#external# + +Sets `value` as the allowance of `spender` over ``owner``'s tokens, +given ``owner``'s signed approval. + +IMPORTANT: The same issues {IERC20-approve} has related to transaction +ordering also apply here. + +Emits an {Approval} event. + +Requirements: + +- `spender` cannot be the zero address. +- `deadline` must be a timestamp in the future. +- `v`, `r` and `s` must be a valid `secp256k1` signature from `owner` +over the EIP712-formatted function arguments. +- the signature must use ``owner``'s current nonce (see {nonces}). + +For more information on the signature format, see the +https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP +section]. + +CAUTION: See Security Considerations above. + +[.contract-item] +[[IERC20Permit-nonces-address-]] +==== `[.contract-item-name]#++nonces++#++(address owner) β†’ uint256++` [.item-kind]#external# + +Returns the current nonce for `owner`. This value must be +included whenever a signature is generated for {permit}. + +Every successful call to {permit} increases ``owner``'s nonce by one. This +prevents a signature from being used multiple times. + +[.contract-item] +[[IERC20Permit-DOMAIN_SEPARATOR--]] +==== `[.contract-item-name]#++DOMAIN_SEPARATOR++#++() β†’ bytes32++` [.item-kind]#external# + +Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}. + +:ERC2612ExpiredSignature: pass:normal[xref:#ERC20Permit-ERC2612ExpiredSignature-uint256-[`++ERC2612ExpiredSignature++`]] +:ERC2612InvalidSigner: pass:normal[xref:#ERC20Permit-ERC2612InvalidSigner-address-address-[`++ERC2612InvalidSigner++`]] +:constructor: pass:normal[xref:#ERC20Permit-constructor-string-[`++constructor++`]] +:permit: pass:normal[xref:#ERC20Permit-permit-address-address-uint256-uint256-uint8-bytes32-bytes32-[`++permit++`]] +:nonces: pass:normal[xref:#ERC20Permit-nonces-address-[`++nonces++`]] +:DOMAIN_SEPARATOR: pass:normal[xref:#ERC20Permit-DOMAIN_SEPARATOR--[`++DOMAIN_SEPARATOR++`]] + +[.contract] +[[ERC20Permit]] +=== `++ERC20Permit++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/token/ERC20/extensions/ERC20Permit.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol"; +``` + +Implementation of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in +https://eips.ethereum.org/EIPS/eip-2612[EIP-2612]. + +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. + +[.contract-index] +.Functions +-- +* {xref-ERC20Permit-constructor-string-}[`++constructor(name)++`] +* {xref-ERC20Permit-permit-address-address-uint256-uint256-uint8-bytes32-bytes32-}[`++permit(owner, spender, value, deadline, v, r, s)++`] +* {xref-ERC20Permit-nonces-address-}[`++nonces(owner)++`] +* {xref-ERC20Permit-DOMAIN_SEPARATOR--}[`++DOMAIN_SEPARATOR()++`] + +[.contract-subindex-inherited] +.Nonces +* {xref-Nonces-_useNonce-address-}[`++_useNonce(owner)++`] +* {xref-Nonces-_useCheckedNonce-address-uint256-}[`++_useCheckedNonce(owner, nonce)++`] + +[.contract-subindex-inherited] +.EIP712 +* {xref-EIP712-_domainSeparatorV4--}[`++_domainSeparatorV4()++`] +* {xref-EIP712-_hashTypedDataV4-bytes32-}[`++_hashTypedDataV4(structHash)++`] +* {xref-EIP712-eip712Domain--}[`++eip712Domain()++`] +* {xref-EIP712-_EIP712Name--}[`++_EIP712Name()++`] +* {xref-EIP712-_EIP712Version--}[`++_EIP712Version()++`] + +[.contract-subindex-inherited] +.IERC5267 + +[.contract-subindex-inherited] +.IERC20Permit + +[.contract-subindex-inherited] +.ERC20 +* {xref-ERC20-name--}[`++name()++`] +* {xref-ERC20-symbol--}[`++symbol()++`] +* {xref-ERC20-decimals--}[`++decimals()++`] +* {xref-ERC20-totalSupply--}[`++totalSupply()++`] +* {xref-ERC20-balanceOf-address-}[`++balanceOf(account)++`] +* {xref-ERC20-transfer-address-uint256-}[`++transfer(to, value)++`] +* {xref-ERC20-allowance-address-address-}[`++allowance(owner, spender)++`] +* {xref-ERC20-approve-address-uint256-}[`++approve(spender, value)++`] +* {xref-ERC20-transferFrom-address-address-uint256-}[`++transferFrom(from, to, value)++`] +* {xref-ERC20-_transfer-address-address-uint256-}[`++_transfer(from, to, value)++`] +* {xref-ERC20-_update-address-address-uint256-}[`++_update(from, to, value)++`] +* {xref-ERC20-_mint-address-uint256-}[`++_mint(account, value)++`] +* {xref-ERC20-_burn-address-uint256-}[`++_burn(account, value)++`] +* {xref-ERC20-_approve-address-address-uint256-}[`++_approve(owner, spender, value)++`] +* {xref-ERC20-_approve-address-address-uint256-bool-}[`++_approve(owner, spender, value, emitEvent)++`] +* {xref-ERC20-_spendAllowance-address-address-uint256-}[`++_spendAllowance(owner, spender, value)++`] + +[.contract-subindex-inherited] +.IERC20Errors + +[.contract-subindex-inherited] +.IERC20Metadata + +[.contract-subindex-inherited] +.IERC20 + +-- + +[.contract-index] +.Events +-- + +[.contract-subindex-inherited] +.Nonces + +[.contract-subindex-inherited] +.EIP712 + +[.contract-subindex-inherited] +.IERC5267 +* {xref-IERC5267-EIP712DomainChanged--}[`++EIP712DomainChanged()++`] + +[.contract-subindex-inherited] +.IERC20Permit + +[.contract-subindex-inherited] +.ERC20 + +[.contract-subindex-inherited] +.IERC20Errors + +[.contract-subindex-inherited] +.IERC20Metadata + +[.contract-subindex-inherited] +.IERC20 +* {xref-IERC20-Transfer-address-address-uint256-}[`++Transfer(from, to, value)++`] +* {xref-IERC20-Approval-address-address-uint256-}[`++Approval(owner, spender, value)++`] + +-- + +[.contract-index] +.Errors +-- +* {xref-ERC20Permit-ERC2612ExpiredSignature-uint256-}[`++ERC2612ExpiredSignature(deadline)++`] +* {xref-ERC20Permit-ERC2612InvalidSigner-address-address-}[`++ERC2612InvalidSigner(signer, owner)++`] + +[.contract-subindex-inherited] +.Nonces +* {xref-Nonces-InvalidAccountNonce-address-uint256-}[`++InvalidAccountNonce(account, currentNonce)++`] + +[.contract-subindex-inherited] +.EIP712 + +[.contract-subindex-inherited] +.IERC5267 + +[.contract-subindex-inherited] +.IERC20Permit + +[.contract-subindex-inherited] +.ERC20 + +[.contract-subindex-inherited] +.IERC20Errors +* {xref-IERC20Errors-ERC20InsufficientBalance-address-uint256-uint256-}[`++ERC20InsufficientBalance(sender, balance, needed)++`] +* {xref-IERC20Errors-ERC20InvalidSender-address-}[`++ERC20InvalidSender(sender)++`] +* {xref-IERC20Errors-ERC20InvalidReceiver-address-}[`++ERC20InvalidReceiver(receiver)++`] +* {xref-IERC20Errors-ERC20InsufficientAllowance-address-uint256-uint256-}[`++ERC20InsufficientAllowance(spender, allowance, needed)++`] +* {xref-IERC20Errors-ERC20InvalidApprover-address-}[`++ERC20InvalidApprover(approver)++`] +* {xref-IERC20Errors-ERC20InvalidSpender-address-}[`++ERC20InvalidSpender(spender)++`] + +[.contract-subindex-inherited] +.IERC20Metadata + +[.contract-subindex-inherited] +.IERC20 + +-- + +[.contract-item] +[[ERC20Permit-constructor-string-]] +==== `[.contract-item-name]#++constructor++#++(string name)++` [.item-kind]#internal# + +Initializes the {EIP712} domain separator using the `name` parameter, and setting `version` to `"1"`. + +It's a good idea to use the same `name` that is defined as the ERC20 token name. + +[.contract-item] +[[ERC20Permit-permit-address-address-uint256-uint256-uint8-bytes32-bytes32-]] +==== `[.contract-item-name]#++permit++#++(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s)++` [.item-kind]#public# + +Sets `value` as the allowance of `spender` over ``owner``'s tokens, +given ``owner``'s signed approval. + +IMPORTANT: The same issues {IERC20-approve} has related to transaction +ordering also apply here. + +Emits an {Approval} event. + +Requirements: + +- `spender` cannot be the zero address. +- `deadline` must be a timestamp in the future. +- `v`, `r` and `s` must be a valid `secp256k1` signature from `owner` +over the EIP712-formatted function arguments. +- the signature must use ``owner``'s current nonce (see {nonces}). + +For more information on the signature format, see the +https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP +section]. + +CAUTION: See Security Considerations above. + +[.contract-item] +[[ERC20Permit-nonces-address-]] +==== `[.contract-item-name]#++nonces++#++(address owner) β†’ uint256++` [.item-kind]#public# + +Returns the current nonce for `owner`. This value must be +included whenever a signature is generated for {permit}. + +Every successful call to {permit} increases ``owner``'s nonce by one. This +prevents a signature from being used multiple times. + +[.contract-item] +[[ERC20Permit-DOMAIN_SEPARATOR--]] +==== `[.contract-item-name]#++DOMAIN_SEPARATOR++#++() β†’ bytes32++` [.item-kind]#external# + +Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}. + +[.contract-item] +[[ERC20Permit-ERC2612ExpiredSignature-uint256-]] +==== `[.contract-item-name]#++ERC2612ExpiredSignature++#++(uint256 deadline)++` [.item-kind]#error# + +Permit deadline has expired. + +[.contract-item] +[[ERC20Permit-ERC2612InvalidSigner-address-address-]] +==== `[.contract-item-name]#++ERC2612InvalidSigner++#++(address signer, address owner)++` [.item-kind]#error# + +Mismatched signature. + +:burn: pass:normal[xref:#ERC20Burnable-burn-uint256-[`++burn++`]] +:burnFrom: pass:normal[xref:#ERC20Burnable-burnFrom-address-uint256-[`++burnFrom++`]] + +[.contract] +[[ERC20Burnable]] +=== `++ERC20Burnable++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/token/ERC20/extensions/ERC20Burnable.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol"; +``` + +Extension of {ERC20} that allows token holders to destroy both their own +tokens and those that they have an allowance for, in a way that can be +recognized off-chain (via event analysis). + +[.contract-index] +.Functions +-- +* {xref-ERC20Burnable-burn-uint256-}[`++burn(value)++`] +* {xref-ERC20Burnable-burnFrom-address-uint256-}[`++burnFrom(account, value)++`] + +[.contract-subindex-inherited] +.ERC20 +* {xref-ERC20-name--}[`++name()++`] +* {xref-ERC20-symbol--}[`++symbol()++`] +* {xref-ERC20-decimals--}[`++decimals()++`] +* {xref-ERC20-totalSupply--}[`++totalSupply()++`] +* {xref-ERC20-balanceOf-address-}[`++balanceOf(account)++`] +* {xref-ERC20-transfer-address-uint256-}[`++transfer(to, value)++`] +* {xref-ERC20-allowance-address-address-}[`++allowance(owner, spender)++`] +* {xref-ERC20-approve-address-uint256-}[`++approve(spender, value)++`] +* {xref-ERC20-transferFrom-address-address-uint256-}[`++transferFrom(from, to, value)++`] +* {xref-ERC20-_transfer-address-address-uint256-}[`++_transfer(from, to, value)++`] +* {xref-ERC20-_update-address-address-uint256-}[`++_update(from, to, value)++`] +* {xref-ERC20-_mint-address-uint256-}[`++_mint(account, value)++`] +* {xref-ERC20-_burn-address-uint256-}[`++_burn(account, value)++`] +* {xref-ERC20-_approve-address-address-uint256-}[`++_approve(owner, spender, value)++`] +* {xref-ERC20-_approve-address-address-uint256-bool-}[`++_approve(owner, spender, value, emitEvent)++`] +* {xref-ERC20-_spendAllowance-address-address-uint256-}[`++_spendAllowance(owner, spender, value)++`] + +[.contract-subindex-inherited] +.IERC20Errors + +[.contract-subindex-inherited] +.IERC20Metadata + +[.contract-subindex-inherited] +.IERC20 + +-- + +[.contract-index] +.Events +-- + +[.contract-subindex-inherited] +.ERC20 + +[.contract-subindex-inherited] +.IERC20Errors + +[.contract-subindex-inherited] +.IERC20Metadata + +[.contract-subindex-inherited] +.IERC20 +* {xref-IERC20-Transfer-address-address-uint256-}[`++Transfer(from, to, value)++`] +* {xref-IERC20-Approval-address-address-uint256-}[`++Approval(owner, spender, value)++`] + +-- + +[.contract-index] +.Errors +-- + +[.contract-subindex-inherited] +.ERC20 + +[.contract-subindex-inherited] +.IERC20Errors +* {xref-IERC20Errors-ERC20InsufficientBalance-address-uint256-uint256-}[`++ERC20InsufficientBalance(sender, balance, needed)++`] +* {xref-IERC20Errors-ERC20InvalidSender-address-}[`++ERC20InvalidSender(sender)++`] +* {xref-IERC20Errors-ERC20InvalidReceiver-address-}[`++ERC20InvalidReceiver(receiver)++`] +* {xref-IERC20Errors-ERC20InsufficientAllowance-address-uint256-uint256-}[`++ERC20InsufficientAllowance(spender, allowance, needed)++`] +* {xref-IERC20Errors-ERC20InvalidApprover-address-}[`++ERC20InvalidApprover(approver)++`] +* {xref-IERC20Errors-ERC20InvalidSpender-address-}[`++ERC20InvalidSpender(spender)++`] + +[.contract-subindex-inherited] +.IERC20Metadata + +[.contract-subindex-inherited] +.IERC20 + +-- + +[.contract-item] +[[ERC20Burnable-burn-uint256-]] +==== `[.contract-item-name]#++burn++#++(uint256 value)++` [.item-kind]#public# + +Destroys a `value` amount of tokens from the caller. + +See {ERC20-_burn}. + +[.contract-item] +[[ERC20Burnable-burnFrom-address-uint256-]] +==== `[.contract-item-name]#++burnFrom++#++(address account, uint256 value)++` [.item-kind]#public# + +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 +`value`. + +:ERC20ExceededCap: pass:normal[xref:#ERC20Capped-ERC20ExceededCap-uint256-uint256-[`++ERC20ExceededCap++`]] +:ERC20InvalidCap: pass:normal[xref:#ERC20Capped-ERC20InvalidCap-uint256-[`++ERC20InvalidCap++`]] +:constructor: pass:normal[xref:#ERC20Capped-constructor-uint256-[`++constructor++`]] +:cap: pass:normal[xref:#ERC20Capped-cap--[`++cap++`]] +:_update: pass:normal[xref:#ERC20Capped-_update-address-address-uint256-[`++_update++`]] + +[.contract] +[[ERC20Capped]] +=== `++ERC20Capped++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/token/ERC20/extensions/ERC20Capped.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Capped.sol"; +``` + +Extension of {ERC20} that adds a cap to the supply of tokens. + +[.contract-index] +.Functions +-- +* {xref-ERC20Capped-constructor-uint256-}[`++constructor(cap_)++`] +* {xref-ERC20Capped-cap--}[`++cap()++`] +* {xref-ERC20Capped-_update-address-address-uint256-}[`++_update(from, to, value)++`] + +[.contract-subindex-inherited] +.ERC20 +* {xref-ERC20-name--}[`++name()++`] +* {xref-ERC20-symbol--}[`++symbol()++`] +* {xref-ERC20-decimals--}[`++decimals()++`] +* {xref-ERC20-totalSupply--}[`++totalSupply()++`] +* {xref-ERC20-balanceOf-address-}[`++balanceOf(account)++`] +* {xref-ERC20-transfer-address-uint256-}[`++transfer(to, value)++`] +* {xref-ERC20-allowance-address-address-}[`++allowance(owner, spender)++`] +* {xref-ERC20-approve-address-uint256-}[`++approve(spender, value)++`] +* {xref-ERC20-transferFrom-address-address-uint256-}[`++transferFrom(from, to, value)++`] +* {xref-ERC20-_transfer-address-address-uint256-}[`++_transfer(from, to, value)++`] +* {xref-ERC20-_mint-address-uint256-}[`++_mint(account, value)++`] +* {xref-ERC20-_burn-address-uint256-}[`++_burn(account, value)++`] +* {xref-ERC20-_approve-address-address-uint256-}[`++_approve(owner, spender, value)++`] +* {xref-ERC20-_approve-address-address-uint256-bool-}[`++_approve(owner, spender, value, emitEvent)++`] +* {xref-ERC20-_spendAllowance-address-address-uint256-}[`++_spendAllowance(owner, spender, value)++`] + +[.contract-subindex-inherited] +.IERC20Errors + +[.contract-subindex-inherited] +.IERC20Metadata + +[.contract-subindex-inherited] +.IERC20 + +-- + +[.contract-index] +.Events +-- + +[.contract-subindex-inherited] +.ERC20 + +[.contract-subindex-inherited] +.IERC20Errors + +[.contract-subindex-inherited] +.IERC20Metadata + +[.contract-subindex-inherited] +.IERC20 +* {xref-IERC20-Transfer-address-address-uint256-}[`++Transfer(from, to, value)++`] +* {xref-IERC20-Approval-address-address-uint256-}[`++Approval(owner, spender, value)++`] + +-- + +[.contract-index] +.Errors +-- +* {xref-ERC20Capped-ERC20ExceededCap-uint256-uint256-}[`++ERC20ExceededCap(increasedSupply, cap)++`] +* {xref-ERC20Capped-ERC20InvalidCap-uint256-}[`++ERC20InvalidCap(cap)++`] + +[.contract-subindex-inherited] +.ERC20 + +[.contract-subindex-inherited] +.IERC20Errors +* {xref-IERC20Errors-ERC20InsufficientBalance-address-uint256-uint256-}[`++ERC20InsufficientBalance(sender, balance, needed)++`] +* {xref-IERC20Errors-ERC20InvalidSender-address-}[`++ERC20InvalidSender(sender)++`] +* {xref-IERC20Errors-ERC20InvalidReceiver-address-}[`++ERC20InvalidReceiver(receiver)++`] +* {xref-IERC20Errors-ERC20InsufficientAllowance-address-uint256-uint256-}[`++ERC20InsufficientAllowance(spender, allowance, needed)++`] +* {xref-IERC20Errors-ERC20InvalidApprover-address-}[`++ERC20InvalidApprover(approver)++`] +* {xref-IERC20Errors-ERC20InvalidSpender-address-}[`++ERC20InvalidSpender(spender)++`] + +[.contract-subindex-inherited] +.IERC20Metadata + +[.contract-subindex-inherited] +.IERC20 + +-- + +[.contract-item] +[[ERC20Capped-constructor-uint256-]] +==== `[.contract-item-name]#++constructor++#++(uint256 cap_)++` [.item-kind]#internal# + +Sets the value of the `cap`. This value is immutable, it can only be +set once during construction. + +[.contract-item] +[[ERC20Capped-cap--]] +==== `[.contract-item-name]#++cap++#++() β†’ uint256++` [.item-kind]#public# + +Returns the cap on the token's total supply. + +[.contract-item] +[[ERC20Capped-_update-address-address-uint256-]] +==== `[.contract-item-name]#++_update++#++(address from, address to, uint256 value)++` [.item-kind]#internal# + +See {ERC20-_update}. + +[.contract-item] +[[ERC20Capped-ERC20ExceededCap-uint256-uint256-]] +==== `[.contract-item-name]#++ERC20ExceededCap++#++(uint256 increasedSupply, uint256 cap)++` [.item-kind]#error# + +Total supply cap has been exceeded. + +[.contract-item] +[[ERC20Capped-ERC20InvalidCap-uint256-]] +==== `[.contract-item-name]#++ERC20InvalidCap++#++(uint256 cap)++` [.item-kind]#error# + +The supplied cap is not a valid cap. + +:_update: pass:normal[xref:#ERC20Pausable-_update-address-address-uint256-[`++_update++`]] + +[.contract] +[[ERC20Pausable]] +=== `++ERC20Pausable++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/token/ERC20/extensions/ERC20Pausable.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Pausable.sol"; +``` + +ERC20 token with pausable token transfers, minting and burning. + +Useful for scenarios such as preventing trades until the end of an evaluation +period, or having an emergency switch for freezing all token transfers in the +event of a large bug. + +IMPORTANT: This contract does not include public pause and unpause functions. In +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 pause mechanism of the contract unreachable, and thus unusable. + +[.contract-index] +.Functions +-- +* {xref-ERC20Pausable-_update-address-address-uint256-}[`++_update(from, to, value)++`] + +[.contract-subindex-inherited] +.Pausable +* {xref-Pausable-paused--}[`++paused()++`] +* {xref-Pausable-_requireNotPaused--}[`++_requireNotPaused()++`] +* {xref-Pausable-_requirePaused--}[`++_requirePaused()++`] +* {xref-Pausable-_pause--}[`++_pause()++`] +* {xref-Pausable-_unpause--}[`++_unpause()++`] + +[.contract-subindex-inherited] +.ERC20 +* {xref-ERC20-name--}[`++name()++`] +* {xref-ERC20-symbol--}[`++symbol()++`] +* {xref-ERC20-decimals--}[`++decimals()++`] +* {xref-ERC20-totalSupply--}[`++totalSupply()++`] +* {xref-ERC20-balanceOf-address-}[`++balanceOf(account)++`] +* {xref-ERC20-transfer-address-uint256-}[`++transfer(to, value)++`] +* {xref-ERC20-allowance-address-address-}[`++allowance(owner, spender)++`] +* {xref-ERC20-approve-address-uint256-}[`++approve(spender, value)++`] +* {xref-ERC20-transferFrom-address-address-uint256-}[`++transferFrom(from, to, value)++`] +* {xref-ERC20-_transfer-address-address-uint256-}[`++_transfer(from, to, value)++`] +* {xref-ERC20-_mint-address-uint256-}[`++_mint(account, value)++`] +* {xref-ERC20-_burn-address-uint256-}[`++_burn(account, value)++`] +* {xref-ERC20-_approve-address-address-uint256-}[`++_approve(owner, spender, value)++`] +* {xref-ERC20-_approve-address-address-uint256-bool-}[`++_approve(owner, spender, value, emitEvent)++`] +* {xref-ERC20-_spendAllowance-address-address-uint256-}[`++_spendAllowance(owner, spender, value)++`] + +[.contract-subindex-inherited] +.IERC20Errors + +[.contract-subindex-inherited] +.IERC20Metadata + +[.contract-subindex-inherited] +.IERC20 + +-- + +[.contract-index] +.Events +-- + +[.contract-subindex-inherited] +.Pausable +* {xref-Pausable-Paused-address-}[`++Paused(account)++`] +* {xref-Pausable-Unpaused-address-}[`++Unpaused(account)++`] + +[.contract-subindex-inherited] +.ERC20 + +[.contract-subindex-inherited] +.IERC20Errors + +[.contract-subindex-inherited] +.IERC20Metadata + +[.contract-subindex-inherited] +.IERC20 +* {xref-IERC20-Transfer-address-address-uint256-}[`++Transfer(from, to, value)++`] +* {xref-IERC20-Approval-address-address-uint256-}[`++Approval(owner, spender, value)++`] + +-- + +[.contract-index] +.Errors +-- + +[.contract-subindex-inherited] +.Pausable +* {xref-Pausable-EnforcedPause--}[`++EnforcedPause()++`] +* {xref-Pausable-ExpectedPause--}[`++ExpectedPause()++`] + +[.contract-subindex-inherited] +.ERC20 + +[.contract-subindex-inherited] +.IERC20Errors +* {xref-IERC20Errors-ERC20InsufficientBalance-address-uint256-uint256-}[`++ERC20InsufficientBalance(sender, balance, needed)++`] +* {xref-IERC20Errors-ERC20InvalidSender-address-}[`++ERC20InvalidSender(sender)++`] +* {xref-IERC20Errors-ERC20InvalidReceiver-address-}[`++ERC20InvalidReceiver(receiver)++`] +* {xref-IERC20Errors-ERC20InsufficientAllowance-address-uint256-uint256-}[`++ERC20InsufficientAllowance(spender, allowance, needed)++`] +* {xref-IERC20Errors-ERC20InvalidApprover-address-}[`++ERC20InvalidApprover(approver)++`] +* {xref-IERC20Errors-ERC20InvalidSpender-address-}[`++ERC20InvalidSpender(spender)++`] + +[.contract-subindex-inherited] +.IERC20Metadata + +[.contract-subindex-inherited] +.IERC20 + +-- + +[.contract-item] +[[ERC20Pausable-_update-address-address-uint256-]] +==== `[.contract-item-name]#++_update++#++(address from, address to, uint256 value)++` [.item-kind]#internal# + +See {ERC20-_update}. + +Requirements: + +- the contract must not be paused. + +:ERC20ExceededSafeSupply: pass:normal[xref:#ERC20Votes-ERC20ExceededSafeSupply-uint256-uint256-[`++ERC20ExceededSafeSupply++`]] +:_maxSupply: pass:normal[xref:#ERC20Votes-_maxSupply--[`++_maxSupply++`]] +:_update: pass:normal[xref:#ERC20Votes-_update-address-address-uint256-[`++_update++`]] +:_getVotingUnits: pass:normal[xref:#ERC20Votes-_getVotingUnits-address-[`++_getVotingUnits++`]] +:numCheckpoints: pass:normal[xref:#ERC20Votes-numCheckpoints-address-[`++numCheckpoints++`]] +:checkpoints: pass:normal[xref:#ERC20Votes-checkpoints-address-uint32-[`++checkpoints++`]] + +[.contract] +[[ERC20Votes]] +=== `++ERC20Votes++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/token/ERC20/extensions/ERC20Votes.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol"; +``` + +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^208^ - 1, while COMP is limited to 2^96^ - 1. + +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 +power can be queried through the public accessors {getVotes} and {getPastVotes}. + +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. + +[.contract-index] +.Functions +-- +* {xref-ERC20Votes-_maxSupply--}[`++_maxSupply()++`] +* {xref-ERC20Votes-_update-address-address-uint256-}[`++_update(from, to, value)++`] +* {xref-ERC20Votes-_getVotingUnits-address-}[`++_getVotingUnits(account)++`] +* {xref-ERC20Votes-numCheckpoints-address-}[`++numCheckpoints(account)++`] +* {xref-ERC20Votes-checkpoints-address-uint32-}[`++checkpoints(account, pos)++`] + +[.contract-subindex-inherited] +.Votes +* {xref-Votes-clock--}[`++clock()++`] +* {xref-Votes-CLOCK_MODE--}[`++CLOCK_MODE()++`] +* {xref-Votes-getVotes-address-}[`++getVotes(account)++`] +* {xref-Votes-getPastVotes-address-uint256-}[`++getPastVotes(account, timepoint)++`] +* {xref-Votes-getPastTotalSupply-uint256-}[`++getPastTotalSupply(timepoint)++`] +* {xref-Votes-_getTotalSupply--}[`++_getTotalSupply()++`] +* {xref-Votes-delegates-address-}[`++delegates(account)++`] +* {xref-Votes-delegate-address-}[`++delegate(delegatee)++`] +* {xref-Votes-delegateBySig-address-uint256-uint256-uint8-bytes32-bytes32-}[`++delegateBySig(delegatee, nonce, expiry, v, r, s)++`] +* {xref-Votes-_delegate-address-address-}[`++_delegate(account, delegatee)++`] +* {xref-Votes-_transferVotingUnits-address-address-uint256-}[`++_transferVotingUnits(from, to, amount)++`] +* {xref-Votes-_numCheckpoints-address-}[`++_numCheckpoints(account)++`] +* {xref-Votes-_checkpoints-address-uint32-}[`++_checkpoints(account, pos)++`] + +[.contract-subindex-inherited] +.IERC5805 + +[.contract-subindex-inherited] +.IVotes + +[.contract-subindex-inherited] +.IERC6372 + +[.contract-subindex-inherited] +.Nonces +* {xref-Nonces-nonces-address-}[`++nonces(owner)++`] +* {xref-Nonces-_useNonce-address-}[`++_useNonce(owner)++`] +* {xref-Nonces-_useCheckedNonce-address-uint256-}[`++_useCheckedNonce(owner, nonce)++`] + +[.contract-subindex-inherited] +.EIP712 +* {xref-EIP712-_domainSeparatorV4--}[`++_domainSeparatorV4()++`] +* {xref-EIP712-_hashTypedDataV4-bytes32-}[`++_hashTypedDataV4(structHash)++`] +* {xref-EIP712-eip712Domain--}[`++eip712Domain()++`] +* {xref-EIP712-_EIP712Name--}[`++_EIP712Name()++`] +* {xref-EIP712-_EIP712Version--}[`++_EIP712Version()++`] + +[.contract-subindex-inherited] +.IERC5267 + +[.contract-subindex-inherited] +.ERC20 +* {xref-ERC20-name--}[`++name()++`] +* {xref-ERC20-symbol--}[`++symbol()++`] +* {xref-ERC20-decimals--}[`++decimals()++`] +* {xref-ERC20-totalSupply--}[`++totalSupply()++`] +* {xref-ERC20-balanceOf-address-}[`++balanceOf(account)++`] +* {xref-ERC20-transfer-address-uint256-}[`++transfer(to, value)++`] +* {xref-ERC20-allowance-address-address-}[`++allowance(owner, spender)++`] +* {xref-ERC20-approve-address-uint256-}[`++approve(spender, value)++`] +* {xref-ERC20-transferFrom-address-address-uint256-}[`++transferFrom(from, to, value)++`] +* {xref-ERC20-_transfer-address-address-uint256-}[`++_transfer(from, to, value)++`] +* {xref-ERC20-_mint-address-uint256-}[`++_mint(account, value)++`] +* {xref-ERC20-_burn-address-uint256-}[`++_burn(account, value)++`] +* {xref-ERC20-_approve-address-address-uint256-}[`++_approve(owner, spender, value)++`] +* {xref-ERC20-_approve-address-address-uint256-bool-}[`++_approve(owner, spender, value, emitEvent)++`] +* {xref-ERC20-_spendAllowance-address-address-uint256-}[`++_spendAllowance(owner, spender, value)++`] + +[.contract-subindex-inherited] +.IERC20Errors + +[.contract-subindex-inherited] +.IERC20Metadata + +[.contract-subindex-inherited] +.IERC20 + +-- + +[.contract-index] +.Events +-- + +[.contract-subindex-inherited] +.Votes + +[.contract-subindex-inherited] +.IERC5805 + +[.contract-subindex-inherited] +.IVotes +* {xref-IVotes-DelegateChanged-address-address-address-}[`++DelegateChanged(delegator, fromDelegate, toDelegate)++`] +* {xref-IVotes-DelegateVotesChanged-address-uint256-uint256-}[`++DelegateVotesChanged(delegate, previousVotes, newVotes)++`] + +[.contract-subindex-inherited] +.IERC6372 + +[.contract-subindex-inherited] +.Nonces + +[.contract-subindex-inherited] +.EIP712 + +[.contract-subindex-inherited] +.IERC5267 +* {xref-IERC5267-EIP712DomainChanged--}[`++EIP712DomainChanged()++`] + +[.contract-subindex-inherited] +.ERC20 + +[.contract-subindex-inherited] +.IERC20Errors + +[.contract-subindex-inherited] +.IERC20Metadata + +[.contract-subindex-inherited] +.IERC20 +* {xref-IERC20-Transfer-address-address-uint256-}[`++Transfer(from, to, value)++`] +* {xref-IERC20-Approval-address-address-uint256-}[`++Approval(owner, spender, value)++`] + +-- + +[.contract-index] +.Errors +-- +* {xref-ERC20Votes-ERC20ExceededSafeSupply-uint256-uint256-}[`++ERC20ExceededSafeSupply(increasedSupply, cap)++`] + +[.contract-subindex-inherited] +.Votes +* {xref-Votes-ERC6372InconsistentClock--}[`++ERC6372InconsistentClock()++`] +* {xref-Votes-ERC5805FutureLookup-uint256-uint48-}[`++ERC5805FutureLookup(timepoint, clock)++`] + +[.contract-subindex-inherited] +.IERC5805 + +[.contract-subindex-inherited] +.IVotes +* {xref-IVotes-VotesExpiredSignature-uint256-}[`++VotesExpiredSignature(expiry)++`] + +[.contract-subindex-inherited] +.IERC6372 + +[.contract-subindex-inherited] +.Nonces +* {xref-Nonces-InvalidAccountNonce-address-uint256-}[`++InvalidAccountNonce(account, currentNonce)++`] + +[.contract-subindex-inherited] +.EIP712 + +[.contract-subindex-inherited] +.IERC5267 + +[.contract-subindex-inherited] +.ERC20 + +[.contract-subindex-inherited] +.IERC20Errors +* {xref-IERC20Errors-ERC20InsufficientBalance-address-uint256-uint256-}[`++ERC20InsufficientBalance(sender, balance, needed)++`] +* {xref-IERC20Errors-ERC20InvalidSender-address-}[`++ERC20InvalidSender(sender)++`] +* {xref-IERC20Errors-ERC20InvalidReceiver-address-}[`++ERC20InvalidReceiver(receiver)++`] +* {xref-IERC20Errors-ERC20InsufficientAllowance-address-uint256-uint256-}[`++ERC20InsufficientAllowance(spender, allowance, needed)++`] +* {xref-IERC20Errors-ERC20InvalidApprover-address-}[`++ERC20InvalidApprover(approver)++`] +* {xref-IERC20Errors-ERC20InvalidSpender-address-}[`++ERC20InvalidSpender(spender)++`] + +[.contract-subindex-inherited] +.IERC20Metadata + +[.contract-subindex-inherited] +.IERC20 + +-- + +[.contract-item] +[[ERC20Votes-_maxSupply--]] +==== `[.contract-item-name]#++_maxSupply++#++() β†’ uint256++` [.item-kind]#internal# + +Maximum token supply. Defaults to `type(uint208).max` (2^208^ - 1). + +This maximum is enforced in {_update}. It limits the total supply of the token, which is otherwise a uint256, +so that checkpoints can be stored in the Trace208 structure used by {{Votes}}. Increasing this value will not +remove the underlying limitation, and will cause {_update} to fail because of a math overflow in +{_transferVotingUnits}. An override could be used to further restrict the total supply (to a lower value) if +additional logic requires it. When resolving override conflicts on this function, the minimum should be +returned. + +[.contract-item] +[[ERC20Votes-_update-address-address-uint256-]] +==== `[.contract-item-name]#++_update++#++(address from, address to, uint256 value)++` [.item-kind]#internal# + +Move voting power when tokens are transferred. + +Emits a {IVotes-DelegateVotesChanged} event. + +[.contract-item] +[[ERC20Votes-_getVotingUnits-address-]] +==== `[.contract-item-name]#++_getVotingUnits++#++(address account) β†’ uint256++` [.item-kind]#internal# + +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. + +[.contract-item] +[[ERC20Votes-numCheckpoints-address-]] +==== `[.contract-item-name]#++numCheckpoints++#++(address account) β†’ uint32++` [.item-kind]#public# + +Get number of checkpoints for `account`. + +[.contract-item] +[[ERC20Votes-checkpoints-address-uint32-]] +==== `[.contract-item-name]#++checkpoints++#++(address account, uint32 pos) β†’ struct Checkpoints.Checkpoint208++` [.item-kind]#public# + +Get the `pos`-th checkpoint for `account`. + +[.contract-item] +[[ERC20Votes-ERC20ExceededSafeSupply-uint256-uint256-]] +==== `[.contract-item-name]#++ERC20ExceededSafeSupply++#++(uint256 increasedSupply, uint256 cap)++` [.item-kind]#error# + +Total supply cap has been exceeded, introducing a risk of votes overflowing. + +:ERC20InvalidUnderlying: pass:normal[xref:#ERC20Wrapper-ERC20InvalidUnderlying-address-[`++ERC20InvalidUnderlying++`]] +:constructor: pass:normal[xref:#ERC20Wrapper-constructor-contract-IERC20-[`++constructor++`]] +:decimals: pass:normal[xref:#ERC20Wrapper-decimals--[`++decimals++`]] +:underlying: pass:normal[xref:#ERC20Wrapper-underlying--[`++underlying++`]] +:depositFor: pass:normal[xref:#ERC20Wrapper-depositFor-address-uint256-[`++depositFor++`]] +:withdrawTo: pass:normal[xref:#ERC20Wrapper-withdrawTo-address-uint256-[`++withdrawTo++`]] +:_recover: pass:normal[xref:#ERC20Wrapper-_recover-address-[`++_recover++`]] + +[.contract] +[[ERC20Wrapper]] +=== `++ERC20Wrapper++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/token/ERC20/extensions/ERC20Wrapper.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Wrapper.sol"; +``` + +Extension of the ERC20 token contract to support token wrapping. + +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. + +[.contract-index] +.Functions +-- +* {xref-ERC20Wrapper-constructor-contract-IERC20-}[`++constructor(underlyingToken)++`] +* {xref-ERC20Wrapper-decimals--}[`++decimals()++`] +* {xref-ERC20Wrapper-underlying--}[`++underlying()++`] +* {xref-ERC20Wrapper-depositFor-address-uint256-}[`++depositFor(account, value)++`] +* {xref-ERC20Wrapper-withdrawTo-address-uint256-}[`++withdrawTo(account, value)++`] +* {xref-ERC20Wrapper-_recover-address-}[`++_recover(account)++`] + +[.contract-subindex-inherited] +.ERC20 +* {xref-ERC20-name--}[`++name()++`] +* {xref-ERC20-symbol--}[`++symbol()++`] +* {xref-ERC20-totalSupply--}[`++totalSupply()++`] +* {xref-ERC20-balanceOf-address-}[`++balanceOf(account)++`] +* {xref-ERC20-transfer-address-uint256-}[`++transfer(to, value)++`] +* {xref-ERC20-allowance-address-address-}[`++allowance(owner, spender)++`] +* {xref-ERC20-approve-address-uint256-}[`++approve(spender, value)++`] +* {xref-ERC20-transferFrom-address-address-uint256-}[`++transferFrom(from, to, value)++`] +* {xref-ERC20-_transfer-address-address-uint256-}[`++_transfer(from, to, value)++`] +* {xref-ERC20-_update-address-address-uint256-}[`++_update(from, to, value)++`] +* {xref-ERC20-_mint-address-uint256-}[`++_mint(account, value)++`] +* {xref-ERC20-_burn-address-uint256-}[`++_burn(account, value)++`] +* {xref-ERC20-_approve-address-address-uint256-}[`++_approve(owner, spender, value)++`] +* {xref-ERC20-_approve-address-address-uint256-bool-}[`++_approve(owner, spender, value, emitEvent)++`] +* {xref-ERC20-_spendAllowance-address-address-uint256-}[`++_spendAllowance(owner, spender, value)++`] + +[.contract-subindex-inherited] +.IERC20Errors + +[.contract-subindex-inherited] +.IERC20Metadata + +[.contract-subindex-inherited] +.IERC20 + +-- + +[.contract-index] +.Events +-- + +[.contract-subindex-inherited] +.ERC20 + +[.contract-subindex-inherited] +.IERC20Errors + +[.contract-subindex-inherited] +.IERC20Metadata + +[.contract-subindex-inherited] +.IERC20 +* {xref-IERC20-Transfer-address-address-uint256-}[`++Transfer(from, to, value)++`] +* {xref-IERC20-Approval-address-address-uint256-}[`++Approval(owner, spender, value)++`] + +-- + +[.contract-index] +.Errors +-- +* {xref-ERC20Wrapper-ERC20InvalidUnderlying-address-}[`++ERC20InvalidUnderlying(token)++`] + +[.contract-subindex-inherited] +.ERC20 + +[.contract-subindex-inherited] +.IERC20Errors +* {xref-IERC20Errors-ERC20InsufficientBalance-address-uint256-uint256-}[`++ERC20InsufficientBalance(sender, balance, needed)++`] +* {xref-IERC20Errors-ERC20InvalidSender-address-}[`++ERC20InvalidSender(sender)++`] +* {xref-IERC20Errors-ERC20InvalidReceiver-address-}[`++ERC20InvalidReceiver(receiver)++`] +* {xref-IERC20Errors-ERC20InsufficientAllowance-address-uint256-uint256-}[`++ERC20InsufficientAllowance(spender, allowance, needed)++`] +* {xref-IERC20Errors-ERC20InvalidApprover-address-}[`++ERC20InvalidApprover(approver)++`] +* {xref-IERC20Errors-ERC20InvalidSpender-address-}[`++ERC20InvalidSpender(spender)++`] + +[.contract-subindex-inherited] +.IERC20Metadata + +[.contract-subindex-inherited] +.IERC20 + +-- + +[.contract-item] +[[ERC20Wrapper-constructor-contract-IERC20-]] +==== `[.contract-item-name]#++constructor++#++(contract IERC20 underlyingToken)++` [.item-kind]#internal# + +[.contract-item] +[[ERC20Wrapper-decimals--]] +==== `[.contract-item-name]#++decimals++#++() β†’ uint8++` [.item-kind]#public# + +See {ERC20-decimals}. + +[.contract-item] +[[ERC20Wrapper-underlying--]] +==== `[.contract-item-name]#++underlying++#++() β†’ contract IERC20++` [.item-kind]#public# + +Returns the address of the underlying ERC-20 token that is being wrapped. + +[.contract-item] +[[ERC20Wrapper-depositFor-address-uint256-]] +==== `[.contract-item-name]#++depositFor++#++(address account, uint256 value) β†’ bool++` [.item-kind]#public# + +Allow a user to deposit underlying tokens and mint the corresponding number of wrapped tokens. + +[.contract-item] +[[ERC20Wrapper-withdrawTo-address-uint256-]] +==== `[.contract-item-name]#++withdrawTo++#++(address account, uint256 value) β†’ bool++` [.item-kind]#public# + +Allow a user to burn a number of wrapped tokens and withdraw the corresponding number of underlying tokens. + +[.contract-item] +[[ERC20Wrapper-_recover-address-]] +==== `[.contract-item-name]#++_recover++#++(address account) β†’ uint256++` [.item-kind]#internal# + +Mint wrapped token to cover any underlyingTokens that would have been transferred by mistake. Internal +function that can be exposed with access control if desired. + +[.contract-item] +[[ERC20Wrapper-ERC20InvalidUnderlying-address-]] +==== `[.contract-item-name]#++ERC20InvalidUnderlying++#++(address token)++` [.item-kind]#error# + +The underlying token couldn't be wrapped. + +:ERC3156UnsupportedToken: pass:normal[xref:#ERC20FlashMint-ERC3156UnsupportedToken-address-[`++ERC3156UnsupportedToken++`]] +:ERC3156ExceededMaxLoan: pass:normal[xref:#ERC20FlashMint-ERC3156ExceededMaxLoan-uint256-[`++ERC3156ExceededMaxLoan++`]] +:ERC3156InvalidReceiver: pass:normal[xref:#ERC20FlashMint-ERC3156InvalidReceiver-address-[`++ERC3156InvalidReceiver++`]] +:maxFlashLoan: pass:normal[xref:#ERC20FlashMint-maxFlashLoan-address-[`++maxFlashLoan++`]] +:flashFee: pass:normal[xref:#ERC20FlashMint-flashFee-address-uint256-[`++flashFee++`]] +:_flashFee: pass:normal[xref:#ERC20FlashMint-_flashFee-address-uint256-[`++_flashFee++`]] +:_flashFeeReceiver: pass:normal[xref:#ERC20FlashMint-_flashFeeReceiver--[`++_flashFeeReceiver++`]] +:flashLoan: pass:normal[xref:#ERC20FlashMint-flashLoan-contract-IERC3156FlashBorrower-address-uint256-bytes-[`++flashLoan++`]] + +[.contract] +[[ERC20FlashMint]] +=== `++ERC20FlashMint++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/token/ERC20/extensions/ERC20FlashMint.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/token/ERC20/extensions/ERC20FlashMint.sol"; +``` + +Implementation of the ERC3156 Flash loans extension, as defined in +https://eips.ethereum.org/EIPS/eip-3156[ERC-3156]. + +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. + +[.contract-index] +.Functions +-- +* {xref-ERC20FlashMint-maxFlashLoan-address-}[`++maxFlashLoan(token)++`] +* {xref-ERC20FlashMint-flashFee-address-uint256-}[`++flashFee(token, value)++`] +* {xref-ERC20FlashMint-_flashFee-address-uint256-}[`++_flashFee(token, value)++`] +* {xref-ERC20FlashMint-_flashFeeReceiver--}[`++_flashFeeReceiver()++`] +* {xref-ERC20FlashMint-flashLoan-contract-IERC3156FlashBorrower-address-uint256-bytes-}[`++flashLoan(receiver, token, value, data)++`] + +[.contract-subindex-inherited] +.IERC3156FlashLender + +[.contract-subindex-inherited] +.ERC20 +* {xref-ERC20-name--}[`++name()++`] +* {xref-ERC20-symbol--}[`++symbol()++`] +* {xref-ERC20-decimals--}[`++decimals()++`] +* {xref-ERC20-totalSupply--}[`++totalSupply()++`] +* {xref-ERC20-balanceOf-address-}[`++balanceOf(account)++`] +* {xref-ERC20-transfer-address-uint256-}[`++transfer(to, value)++`] +* {xref-ERC20-allowance-address-address-}[`++allowance(owner, spender)++`] +* {xref-ERC20-approve-address-uint256-}[`++approve(spender, value)++`] +* {xref-ERC20-transferFrom-address-address-uint256-}[`++transferFrom(from, to, value)++`] +* {xref-ERC20-_transfer-address-address-uint256-}[`++_transfer(from, to, value)++`] +* {xref-ERC20-_update-address-address-uint256-}[`++_update(from, to, value)++`] +* {xref-ERC20-_mint-address-uint256-}[`++_mint(account, value)++`] +* {xref-ERC20-_burn-address-uint256-}[`++_burn(account, value)++`] +* {xref-ERC20-_approve-address-address-uint256-}[`++_approve(owner, spender, value)++`] +* {xref-ERC20-_approve-address-address-uint256-bool-}[`++_approve(owner, spender, value, emitEvent)++`] +* {xref-ERC20-_spendAllowance-address-address-uint256-}[`++_spendAllowance(owner, spender, value)++`] + +[.contract-subindex-inherited] +.IERC20Errors + +[.contract-subindex-inherited] +.IERC20Metadata + +[.contract-subindex-inherited] +.IERC20 + +-- + +[.contract-index] +.Events +-- + +[.contract-subindex-inherited] +.IERC3156FlashLender + +[.contract-subindex-inherited] +.ERC20 + +[.contract-subindex-inherited] +.IERC20Errors + +[.contract-subindex-inherited] +.IERC20Metadata + +[.contract-subindex-inherited] +.IERC20 +* {xref-IERC20-Transfer-address-address-uint256-}[`++Transfer(from, to, value)++`] +* {xref-IERC20-Approval-address-address-uint256-}[`++Approval(owner, spender, value)++`] + +-- + +[.contract-index] +.Errors +-- +* {xref-ERC20FlashMint-ERC3156UnsupportedToken-address-}[`++ERC3156UnsupportedToken(token)++`] +* {xref-ERC20FlashMint-ERC3156ExceededMaxLoan-uint256-}[`++ERC3156ExceededMaxLoan(maxLoan)++`] +* {xref-ERC20FlashMint-ERC3156InvalidReceiver-address-}[`++ERC3156InvalidReceiver(receiver)++`] + +[.contract-subindex-inherited] +.IERC3156FlashLender + +[.contract-subindex-inherited] +.ERC20 + +[.contract-subindex-inherited] +.IERC20Errors +* {xref-IERC20Errors-ERC20InsufficientBalance-address-uint256-uint256-}[`++ERC20InsufficientBalance(sender, balance, needed)++`] +* {xref-IERC20Errors-ERC20InvalidSender-address-}[`++ERC20InvalidSender(sender)++`] +* {xref-IERC20Errors-ERC20InvalidReceiver-address-}[`++ERC20InvalidReceiver(receiver)++`] +* {xref-IERC20Errors-ERC20InsufficientAllowance-address-uint256-uint256-}[`++ERC20InsufficientAllowance(spender, allowance, needed)++`] +* {xref-IERC20Errors-ERC20InvalidApprover-address-}[`++ERC20InvalidApprover(approver)++`] +* {xref-IERC20Errors-ERC20InvalidSpender-address-}[`++ERC20InvalidSpender(spender)++`] + +[.contract-subindex-inherited] +.IERC20Metadata + +[.contract-subindex-inherited] +.IERC20 + +-- + +[.contract-item] +[[ERC20FlashMint-maxFlashLoan-address-]] +==== `[.contract-item-name]#++maxFlashLoan++#++(address token) β†’ uint256++` [.item-kind]#public# + +Returns the maximum amount of tokens available for loan. + +[.contract-item] +[[ERC20FlashMint-flashFee-address-uint256-]] +==== `[.contract-item-name]#++flashFee++#++(address token, uint256 value) β†’ uint256++` [.item-kind]#public# + +Returns the fee applied when doing flash loans. This function calls +the {_flashFee} function which returns the fee applied when doing flash +loans. + +[.contract-item] +[[ERC20FlashMint-_flashFee-address-uint256-]] +==== `[.contract-item-name]#++_flashFee++#++(address token, uint256 value) β†’ uint256++` [.item-kind]#internal# + +Returns the fee applied when doing flash loans. By default this +implementation has 0 fees. This function can be overloaded to make +the flash loan mechanism deflationary. + +[.contract-item] +[[ERC20FlashMint-_flashFeeReceiver--]] +==== `[.contract-item-name]#++_flashFeeReceiver++#++() β†’ address++` [.item-kind]#internal# + +Returns the receiver address of the flash fee. By default this +implementation returns the address(0) which means the fee amount will be burnt. +This function can be overloaded to change the fee receiver. + +[.contract-item] +[[ERC20FlashMint-flashLoan-contract-IERC3156FlashBorrower-address-uint256-bytes-]] +==== `[.contract-item-name]#++flashLoan++#++(contract IERC3156FlashBorrower receiver, address token, uint256 value, bytes data) β†’ bool++` [.item-kind]#public# + +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 +value + fee tokens and have them approved back to the token contract itself so +they can be burned. + +[.contract-item] +[[ERC20FlashMint-ERC3156UnsupportedToken-address-]] +==== `[.contract-item-name]#++ERC3156UnsupportedToken++#++(address token)++` [.item-kind]#error# + +The loan token is not valid. + +[.contract-item] +[[ERC20FlashMint-ERC3156ExceededMaxLoan-uint256-]] +==== `[.contract-item-name]#++ERC3156ExceededMaxLoan++#++(uint256 maxLoan)++` [.item-kind]#error# + +The requested loan exceeds the max loan value for `token`. + +[.contract-item] +[[ERC20FlashMint-ERC3156InvalidReceiver-address-]] +==== `[.contract-item-name]#++ERC3156InvalidReceiver++#++(address receiver)++` [.item-kind]#error# + +The receiver of a flashloan is not a valid {onFlashLoan} implementer. + +:ERC4626ExceededMaxDeposit: pass:normal[xref:#ERC4626-ERC4626ExceededMaxDeposit-address-uint256-uint256-[`++ERC4626ExceededMaxDeposit++`]] +:ERC4626ExceededMaxMint: pass:normal[xref:#ERC4626-ERC4626ExceededMaxMint-address-uint256-uint256-[`++ERC4626ExceededMaxMint++`]] +:ERC4626ExceededMaxWithdraw: pass:normal[xref:#ERC4626-ERC4626ExceededMaxWithdraw-address-uint256-uint256-[`++ERC4626ExceededMaxWithdraw++`]] +:ERC4626ExceededMaxRedeem: pass:normal[xref:#ERC4626-ERC4626ExceededMaxRedeem-address-uint256-uint256-[`++ERC4626ExceededMaxRedeem++`]] +:constructor: pass:normal[xref:#ERC4626-constructor-contract-IERC20-[`++constructor++`]] +:decimals: pass:normal[xref:#ERC4626-decimals--[`++decimals++`]] +:asset: pass:normal[xref:#ERC4626-asset--[`++asset++`]] +:totalAssets: pass:normal[xref:#ERC4626-totalAssets--[`++totalAssets++`]] +:convertToShares: pass:normal[xref:#ERC4626-convertToShares-uint256-[`++convertToShares++`]] +:convertToAssets: pass:normal[xref:#ERC4626-convertToAssets-uint256-[`++convertToAssets++`]] +:maxDeposit: pass:normal[xref:#ERC4626-maxDeposit-address-[`++maxDeposit++`]] +:maxMint: pass:normal[xref:#ERC4626-maxMint-address-[`++maxMint++`]] +:maxWithdraw: pass:normal[xref:#ERC4626-maxWithdraw-address-[`++maxWithdraw++`]] +:maxRedeem: pass:normal[xref:#ERC4626-maxRedeem-address-[`++maxRedeem++`]] +:previewDeposit: pass:normal[xref:#ERC4626-previewDeposit-uint256-[`++previewDeposit++`]] +:previewMint: pass:normal[xref:#ERC4626-previewMint-uint256-[`++previewMint++`]] +:previewWithdraw: pass:normal[xref:#ERC4626-previewWithdraw-uint256-[`++previewWithdraw++`]] +:previewRedeem: pass:normal[xref:#ERC4626-previewRedeem-uint256-[`++previewRedeem++`]] +:deposit: pass:normal[xref:#ERC4626-deposit-uint256-address-[`++deposit++`]] +:mint: pass:normal[xref:#ERC4626-mint-uint256-address-[`++mint++`]] +:withdraw: pass:normal[xref:#ERC4626-withdraw-uint256-address-address-[`++withdraw++`]] +:redeem: pass:normal[xref:#ERC4626-redeem-uint256-address-address-[`++redeem++`]] +:_convertToShares: pass:normal[xref:#ERC4626-_convertToShares-uint256-enum-Math-Rounding-[`++_convertToShares++`]] +:_convertToAssets: pass:normal[xref:#ERC4626-_convertToAssets-uint256-enum-Math-Rounding-[`++_convertToAssets++`]] +:_deposit: pass:normal[xref:#ERC4626-_deposit-address-address-uint256-uint256-[`++_deposit++`]] +:_withdraw: pass:normal[xref:#ERC4626-_withdraw-address-address-address-uint256-uint256-[`++_withdraw++`]] +:_decimalsOffset: pass:normal[xref:#ERC4626-_decimalsOffset--[`++_decimalsOffset++`]] + +[.contract] +[[ERC4626]] +=== `++ERC4626++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/token/ERC20/extensions/ERC4626.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol"; +``` + +Implementation of the ERC4626 "Tokenized Vault Standard" as defined in +https://eips.ethereum.org/EIPS/eip-4626[EIP-4626]. + +This extension allows the minting and burning of "shares" (represented using the ERC20 inheritance) in exchange for +underlying "assets" through standardized {deposit}, {mint}, {redeem} and {burn} workflows. This contract extends +the ERC20 standard. Any additional extensions included along it would affect the "shares" token represented by this +contract and not the "assets" token which is an independent contract. + +[CAUTION] +==== +In empty (or nearly empty) ERC-4626 vaults, deposits are at high risk of being stolen through frontrunning +with a "donation" to the vault that inflates the price of a share. This is variously known as a donation or inflation +attack and is essentially a problem of slippage. Vault deployers can protect against this attack by making an initial +deposit of a non-trivial amount of the asset, such that price manipulation becomes infeasible. Withdrawals may +similarly be affected by slippage. Users can protect against this attack as well as unexpected slippage in general by +verifying the amount received is as expected, using a wrapper that performs these checks such as +https://github.com/fei-protocol/ERC4626#erc4626router-and-base[ERC4626Router]. + +Since v4.9, this implementation uses virtual assets and shares to mitigate that risk. The `_decimalsOffset()` +corresponds to an offset in the decimal representation between the underlying asset's decimals and the vault +decimals. This offset also determines the rate of virtual shares to virtual assets in the vault, which itself +determines the initial exchange rate. While not fully preventing the attack, analysis shows that the default offset +(0) makes it non-profitable, as a result of the value being captured by the virtual shares (out of the attacker's +donation) matching the attacker's expected gains. With a larger offset, the attack becomes orders of magnitude more +expensive than it is profitable. More details about the underlying math can be found +xref:erc4626.adoc#inflation-attack[here]. + +The drawback of this approach is that the virtual shares do capture (a very small) part of the value being accrued +to the vault. Also, if the vault experiences losses, the users try to exit the vault, the virtual shares and assets +will cause the first user to exit to experience reduced losses in detriment to the last users that will experience +bigger losses. Developers willing to revert back to the pre-v4.9 behavior just need to override the +`_convertToShares` and `_convertToAssets` functions. + +To learn more, check out our xref:ROOT:erc4626.adoc[ERC-4626 guide]. +==== + +[.contract-index] +.Functions +-- +* {xref-ERC4626-constructor-contract-IERC20-}[`++constructor(asset_)++`] +* {xref-ERC4626-decimals--}[`++decimals()++`] +* {xref-ERC4626-asset--}[`++asset()++`] +* {xref-ERC4626-totalAssets--}[`++totalAssets()++`] +* {xref-ERC4626-convertToShares-uint256-}[`++convertToShares(assets)++`] +* {xref-ERC4626-convertToAssets-uint256-}[`++convertToAssets(shares)++`] +* {xref-ERC4626-maxDeposit-address-}[`++maxDeposit()++`] +* {xref-ERC4626-maxMint-address-}[`++maxMint()++`] +* {xref-ERC4626-maxWithdraw-address-}[`++maxWithdraw(owner)++`] +* {xref-ERC4626-maxRedeem-address-}[`++maxRedeem(owner)++`] +* {xref-ERC4626-previewDeposit-uint256-}[`++previewDeposit(assets)++`] +* {xref-ERC4626-previewMint-uint256-}[`++previewMint(shares)++`] +* {xref-ERC4626-previewWithdraw-uint256-}[`++previewWithdraw(assets)++`] +* {xref-ERC4626-previewRedeem-uint256-}[`++previewRedeem(shares)++`] +* {xref-ERC4626-deposit-uint256-address-}[`++deposit(assets, receiver)++`] +* {xref-ERC4626-mint-uint256-address-}[`++mint(shares, receiver)++`] +* {xref-ERC4626-withdraw-uint256-address-address-}[`++withdraw(assets, receiver, owner)++`] +* {xref-ERC4626-redeem-uint256-address-address-}[`++redeem(shares, receiver, owner)++`] +* {xref-ERC4626-_convertToShares-uint256-enum-Math-Rounding-}[`++_convertToShares(assets, rounding)++`] +* {xref-ERC4626-_convertToAssets-uint256-enum-Math-Rounding-}[`++_convertToAssets(shares, rounding)++`] +* {xref-ERC4626-_deposit-address-address-uint256-uint256-}[`++_deposit(caller, receiver, assets, shares)++`] +* {xref-ERC4626-_withdraw-address-address-address-uint256-uint256-}[`++_withdraw(caller, receiver, owner, assets, shares)++`] +* {xref-ERC4626-_decimalsOffset--}[`++_decimalsOffset()++`] + +[.contract-subindex-inherited] +.IERC4626 + +[.contract-subindex-inherited] +.ERC20 +* {xref-ERC20-name--}[`++name()++`] +* {xref-ERC20-symbol--}[`++symbol()++`] +* {xref-ERC20-totalSupply--}[`++totalSupply()++`] +* {xref-ERC20-balanceOf-address-}[`++balanceOf(account)++`] +* {xref-ERC20-transfer-address-uint256-}[`++transfer(to, value)++`] +* {xref-ERC20-allowance-address-address-}[`++allowance(owner, spender)++`] +* {xref-ERC20-approve-address-uint256-}[`++approve(spender, value)++`] +* {xref-ERC20-transferFrom-address-address-uint256-}[`++transferFrom(from, to, value)++`] +* {xref-ERC20-_transfer-address-address-uint256-}[`++_transfer(from, to, value)++`] +* {xref-ERC20-_update-address-address-uint256-}[`++_update(from, to, value)++`] +* {xref-ERC20-_mint-address-uint256-}[`++_mint(account, value)++`] +* {xref-ERC20-_burn-address-uint256-}[`++_burn(account, value)++`] +* {xref-ERC20-_approve-address-address-uint256-}[`++_approve(owner, spender, value)++`] +* {xref-ERC20-_approve-address-address-uint256-bool-}[`++_approve(owner, spender, value, emitEvent)++`] +* {xref-ERC20-_spendAllowance-address-address-uint256-}[`++_spendAllowance(owner, spender, value)++`] + +[.contract-subindex-inherited] +.IERC20Errors + +[.contract-subindex-inherited] +.IERC20Metadata + +[.contract-subindex-inherited] +.IERC20 + +-- + +[.contract-index] +.Events +-- + +[.contract-subindex-inherited] +.IERC4626 +* {xref-IERC4626-Deposit-address-address-uint256-uint256-}[`++Deposit(sender, owner, assets, shares)++`] +* {xref-IERC4626-Withdraw-address-address-address-uint256-uint256-}[`++Withdraw(sender, receiver, owner, assets, shares)++`] + +[.contract-subindex-inherited] +.ERC20 + +[.contract-subindex-inherited] +.IERC20Errors + +[.contract-subindex-inherited] +.IERC20Metadata + +[.contract-subindex-inherited] +.IERC20 +* {xref-IERC20-Transfer-address-address-uint256-}[`++Transfer(from, to, value)++`] +* {xref-IERC20-Approval-address-address-uint256-}[`++Approval(owner, spender, value)++`] + +-- + +[.contract-index] +.Errors +-- +* {xref-ERC4626-ERC4626ExceededMaxDeposit-address-uint256-uint256-}[`++ERC4626ExceededMaxDeposit(receiver, assets, max)++`] +* {xref-ERC4626-ERC4626ExceededMaxMint-address-uint256-uint256-}[`++ERC4626ExceededMaxMint(receiver, shares, max)++`] +* {xref-ERC4626-ERC4626ExceededMaxWithdraw-address-uint256-uint256-}[`++ERC4626ExceededMaxWithdraw(owner, assets, max)++`] +* {xref-ERC4626-ERC4626ExceededMaxRedeem-address-uint256-uint256-}[`++ERC4626ExceededMaxRedeem(owner, shares, max)++`] + +[.contract-subindex-inherited] +.IERC4626 + +[.contract-subindex-inherited] +.ERC20 + +[.contract-subindex-inherited] +.IERC20Errors +* {xref-IERC20Errors-ERC20InsufficientBalance-address-uint256-uint256-}[`++ERC20InsufficientBalance(sender, balance, needed)++`] +* {xref-IERC20Errors-ERC20InvalidSender-address-}[`++ERC20InvalidSender(sender)++`] +* {xref-IERC20Errors-ERC20InvalidReceiver-address-}[`++ERC20InvalidReceiver(receiver)++`] +* {xref-IERC20Errors-ERC20InsufficientAllowance-address-uint256-uint256-}[`++ERC20InsufficientAllowance(spender, allowance, needed)++`] +* {xref-IERC20Errors-ERC20InvalidApprover-address-}[`++ERC20InvalidApprover(approver)++`] +* {xref-IERC20Errors-ERC20InvalidSpender-address-}[`++ERC20InvalidSpender(spender)++`] + +[.contract-subindex-inherited] +.IERC20Metadata + +[.contract-subindex-inherited] +.IERC20 + +-- + +[.contract-item] +[[ERC4626-constructor-contract-IERC20-]] +==== `[.contract-item-name]#++constructor++#++(contract IERC20 asset_)++` [.item-kind]#internal# + +Set the underlying asset contract. This must be an ERC20-compatible contract (ERC20 or ERC777). + +[.contract-item] +[[ERC4626-decimals--]] +==== `[.contract-item-name]#++decimals++#++() β†’ uint8++` [.item-kind]#public# + +Decimals are computed by adding the decimal offset on top of the underlying asset's decimals. This +"original" value is cached during construction of the vault contract. If this read operation fails (e.g., the +asset has not been created yet), a default of 18 is used to represent the underlying asset's decimals. + +See {IERC20Metadata-decimals}. + +[.contract-item] +[[ERC4626-asset--]] +==== `[.contract-item-name]#++asset++#++() β†’ address++` [.item-kind]#public# + +See {IERC4626-asset}. + +[.contract-item] +[[ERC4626-totalAssets--]] +==== `[.contract-item-name]#++totalAssets++#++() β†’ uint256++` [.item-kind]#public# + +See {IERC4626-totalAssets}. + +[.contract-item] +[[ERC4626-convertToShares-uint256-]] +==== `[.contract-item-name]#++convertToShares++#++(uint256 assets) β†’ uint256++` [.item-kind]#public# + +See {IERC4626-convertToShares}. + +[.contract-item] +[[ERC4626-convertToAssets-uint256-]] +==== `[.contract-item-name]#++convertToAssets++#++(uint256 shares) β†’ uint256++` [.item-kind]#public# + +See {IERC4626-convertToAssets}. + +[.contract-item] +[[ERC4626-maxDeposit-address-]] +==== `[.contract-item-name]#++maxDeposit++#++(address) β†’ uint256++` [.item-kind]#public# + +See {IERC4626-maxDeposit}. + +[.contract-item] +[[ERC4626-maxMint-address-]] +==== `[.contract-item-name]#++maxMint++#++(address) β†’ uint256++` [.item-kind]#public# + +See {IERC4626-maxMint}. + +[.contract-item] +[[ERC4626-maxWithdraw-address-]] +==== `[.contract-item-name]#++maxWithdraw++#++(address owner) β†’ uint256++` [.item-kind]#public# + +See {IERC4626-maxWithdraw}. + +[.contract-item] +[[ERC4626-maxRedeem-address-]] +==== `[.contract-item-name]#++maxRedeem++#++(address owner) β†’ uint256++` [.item-kind]#public# + +See {IERC4626-maxRedeem}. + +[.contract-item] +[[ERC4626-previewDeposit-uint256-]] +==== `[.contract-item-name]#++previewDeposit++#++(uint256 assets) β†’ uint256++` [.item-kind]#public# + +See {IERC4626-previewDeposit}. + +[.contract-item] +[[ERC4626-previewMint-uint256-]] +==== `[.contract-item-name]#++previewMint++#++(uint256 shares) β†’ uint256++` [.item-kind]#public# + +See {IERC4626-previewMint}. + +[.contract-item] +[[ERC4626-previewWithdraw-uint256-]] +==== `[.contract-item-name]#++previewWithdraw++#++(uint256 assets) β†’ uint256++` [.item-kind]#public# + +See {IERC4626-previewWithdraw}. + +[.contract-item] +[[ERC4626-previewRedeem-uint256-]] +==== `[.contract-item-name]#++previewRedeem++#++(uint256 shares) β†’ uint256++` [.item-kind]#public# + +See {IERC4626-previewRedeem}. + +[.contract-item] +[[ERC4626-deposit-uint256-address-]] +==== `[.contract-item-name]#++deposit++#++(uint256 assets, address receiver) β†’ uint256++` [.item-kind]#public# + +See {IERC4626-deposit}. + +[.contract-item] +[[ERC4626-mint-uint256-address-]] +==== `[.contract-item-name]#++mint++#++(uint256 shares, address receiver) β†’ uint256++` [.item-kind]#public# + +See {IERC4626-mint}. + +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. + +[.contract-item] +[[ERC4626-withdraw-uint256-address-address-]] +==== `[.contract-item-name]#++withdraw++#++(uint256 assets, address receiver, address owner) β†’ uint256++` [.item-kind]#public# + +See {IERC4626-withdraw}. + +[.contract-item] +[[ERC4626-redeem-uint256-address-address-]] +==== `[.contract-item-name]#++redeem++#++(uint256 shares, address receiver, address owner) β†’ uint256++` [.item-kind]#public# + +See {IERC4626-redeem}. + +[.contract-item] +[[ERC4626-_convertToShares-uint256-enum-Math-Rounding-]] +==== `[.contract-item-name]#++_convertToShares++#++(uint256 assets, enum Math.Rounding rounding) β†’ uint256++` [.item-kind]#internal# + +Internal conversion function (from assets to shares) with support for rounding direction. + +[.contract-item] +[[ERC4626-_convertToAssets-uint256-enum-Math-Rounding-]] +==== `[.contract-item-name]#++_convertToAssets++#++(uint256 shares, enum Math.Rounding rounding) β†’ uint256++` [.item-kind]#internal# + +Internal conversion function (from shares to assets) with support for rounding direction. + +[.contract-item] +[[ERC4626-_deposit-address-address-uint256-uint256-]] +==== `[.contract-item-name]#++_deposit++#++(address caller, address receiver, uint256 assets, uint256 shares)++` [.item-kind]#internal# + +Deposit/mint common workflow. + +[.contract-item] +[[ERC4626-_withdraw-address-address-address-uint256-uint256-]] +==== `[.contract-item-name]#++_withdraw++#++(address caller, address receiver, address owner, uint256 assets, uint256 shares)++` [.item-kind]#internal# + +Withdraw/redeem common workflow. + +[.contract-item] +[[ERC4626-_decimalsOffset--]] +==== `[.contract-item-name]#++_decimalsOffset++#++() β†’ uint8++` [.item-kind]#internal# + +[.contract-item] +[[ERC4626-ERC4626ExceededMaxDeposit-address-uint256-uint256-]] +==== `[.contract-item-name]#++ERC4626ExceededMaxDeposit++#++(address receiver, uint256 assets, uint256 max)++` [.item-kind]#error# + +Attempted to deposit more assets than the max amount for `receiver`. + +[.contract-item] +[[ERC4626-ERC4626ExceededMaxMint-address-uint256-uint256-]] +==== `[.contract-item-name]#++ERC4626ExceededMaxMint++#++(address receiver, uint256 shares, uint256 max)++` [.item-kind]#error# + +Attempted to mint more shares than the max amount for `receiver`. + +[.contract-item] +[[ERC4626-ERC4626ExceededMaxWithdraw-address-uint256-uint256-]] +==== `[.contract-item-name]#++ERC4626ExceededMaxWithdraw++#++(address owner, uint256 assets, uint256 max)++` [.item-kind]#error# + +Attempted to withdraw more assets than the max amount for `receiver`. + +[.contract-item] +[[ERC4626-ERC4626ExceededMaxRedeem-address-uint256-uint256-]] +==== `[.contract-item-name]#++ERC4626ExceededMaxRedeem++#++(address owner, uint256 shares, uint256 max)++` [.item-kind]#error# + +Attempted to redeem more shares than the max amount for `receiver`. + +== Utilities + +:SafeERC20FailedOperation: pass:normal[xref:#SafeERC20-SafeERC20FailedOperation-address-[`++SafeERC20FailedOperation++`]] +:SafeERC20FailedDecreaseAllowance: pass:normal[xref:#SafeERC20-SafeERC20FailedDecreaseAllowance-address-uint256-uint256-[`++SafeERC20FailedDecreaseAllowance++`]] +:safeTransfer: pass:normal[xref:#SafeERC20-safeTransfer-contract-IERC20-address-uint256-[`++safeTransfer++`]] +:safeTransferFrom: pass:normal[xref:#SafeERC20-safeTransferFrom-contract-IERC20-address-address-uint256-[`++safeTransferFrom++`]] +:safeIncreaseAllowance: pass:normal[xref:#SafeERC20-safeIncreaseAllowance-contract-IERC20-address-uint256-[`++safeIncreaseAllowance++`]] +:safeDecreaseAllowance: pass:normal[xref:#SafeERC20-safeDecreaseAllowance-contract-IERC20-address-uint256-[`++safeDecreaseAllowance++`]] +:forceApprove: pass:normal[xref:#SafeERC20-forceApprove-contract-IERC20-address-uint256-[`++forceApprove++`]] + +[.contract] +[[SafeERC20]] +=== `++SafeERC20++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/token/ERC20/utils/SafeERC20.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +``` + +Wrappers around ERC20 operations that throw on failure (when the token +contract returns false). Tokens that return no value (and instead revert or +throw on failure) are also supported, non-reverting calls are assumed to be +successful. +To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract, +which allows you to call the safe operations as `token.safeTransfer(...)`, etc. + +[.contract-index] +.Functions +-- +* {xref-SafeERC20-safeTransfer-contract-IERC20-address-uint256-}[`++safeTransfer(token, to, value)++`] +* {xref-SafeERC20-safeTransferFrom-contract-IERC20-address-address-uint256-}[`++safeTransferFrom(token, from, to, value)++`] +* {xref-SafeERC20-safeIncreaseAllowance-contract-IERC20-address-uint256-}[`++safeIncreaseAllowance(token, spender, value)++`] +* {xref-SafeERC20-safeDecreaseAllowance-contract-IERC20-address-uint256-}[`++safeDecreaseAllowance(token, spender, requestedDecrease)++`] +* {xref-SafeERC20-forceApprove-contract-IERC20-address-uint256-}[`++forceApprove(token, spender, value)++`] + +-- + +[.contract-index] +.Errors +-- +* {xref-SafeERC20-SafeERC20FailedOperation-address-}[`++SafeERC20FailedOperation(token)++`] +* {xref-SafeERC20-SafeERC20FailedDecreaseAllowance-address-uint256-uint256-}[`++SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease)++`] + +-- + +[.contract-item] +[[SafeERC20-safeTransfer-contract-IERC20-address-uint256-]] +==== `[.contract-item-name]#++safeTransfer++#++(contract IERC20 token, address to, uint256 value)++` [.item-kind]#internal# + +Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value, +non-reverting calls are assumed to be successful. + +[.contract-item] +[[SafeERC20-safeTransferFrom-contract-IERC20-address-address-uint256-]] +==== `[.contract-item-name]#++safeTransferFrom++#++(contract IERC20 token, address from, address to, uint256 value)++` [.item-kind]#internal# + +Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the +calling contract. If `token` returns no value, non-reverting calls are assumed to be successful. + +[.contract-item] +[[SafeERC20-safeIncreaseAllowance-contract-IERC20-address-uint256-]] +==== `[.contract-item-name]#++safeIncreaseAllowance++#++(contract IERC20 token, address spender, uint256 value)++` [.item-kind]#internal# + +Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value, +non-reverting calls are assumed to be successful. + +[.contract-item] +[[SafeERC20-safeDecreaseAllowance-contract-IERC20-address-uint256-]] +==== `[.contract-item-name]#++safeDecreaseAllowance++#++(contract IERC20 token, address spender, uint256 requestedDecrease)++` [.item-kind]#internal# + +Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no +value, non-reverting calls are assumed to be successful. + +[.contract-item] +[[SafeERC20-forceApprove-contract-IERC20-address-uint256-]] +==== `[.contract-item-name]#++forceApprove++#++(contract IERC20 token, address spender, uint256 value)++` [.item-kind]#internal# + +Set the calling contract's allowance toward `spender` to `value`. If `token` returns no 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. + +[.contract-item] +[[SafeERC20-SafeERC20FailedOperation-address-]] +==== `[.contract-item-name]#++SafeERC20FailedOperation++#++(address token)++` [.item-kind]#error# + +An operation with an ERC20 token failed. + +[.contract-item] +[[SafeERC20-SafeERC20FailedDecreaseAllowance-address-uint256-uint256-]] +==== `[.contract-item-name]#++SafeERC20FailedDecreaseAllowance++#++(address spender, uint256 currentAllowance, uint256 requestedDecrease)++` [.item-kind]#error# + +Indicates a failed `decreaseAllowance` request. + diff --git a/docs/modules/api/pages/token/ERC721.adoc b/docs/modules/api/pages/token/ERC721.adoc new file mode 100644 index 000000000..a270b2b20 --- /dev/null +++ b/docs/modules/api/pages/token/ERC721.adoc @@ -0,0 +1,2991 @@ +:github-icon: pass:[] +:IERC721: pass:normal[xref:token/ERC721.adoc#IERC721[`IERC721`]] +:IERC721Metadata: pass:normal[xref:token/ERC721.adoc#IERC721Metadata[`IERC721Metadata`]] +:IERC721Enumerable: pass:normal[xref:token/ERC721.adoc#IERC721Enumerable[`IERC721Enumerable`]] +:IERC721Receiver: pass:normal[xref:token/ERC721.adoc#IERC721Receiver[`IERC721Receiver`]] +:ERC721: pass:normal[xref:token/ERC721.adoc#ERC721[`ERC721`]] +:ERC721Enumerable: pass:normal[xref:token/ERC721.adoc#ERC721Enumerable[`ERC721Enumerable`]] +:ERC721Holder: pass:normal[xref:token/ERC721.adoc#ERC721Holder[`ERC721Holder`]] +:ERC721Consecutive: pass:normal[xref:token/ERC721.adoc#ERC721Consecutive[`ERC721Consecutive`]] +:ERC721URIStorage: pass:normal[xref:token/ERC721.adoc#ERC721URIStorage[`ERC721URIStorage`]] +:ERC721Votes: pass:normal[xref:token/ERC721.adoc#ERC721Votes[`ERC721Votes`]] +:ERC721Royalty: pass:normal[xref:token/ERC721.adoc#ERC721Royalty[`ERC721Royalty`]] +:ERC721Pausable: pass:normal[xref:token/ERC721.adoc#ERC721Pausable[`ERC721Pausable`]] +:ERC721Burnable: pass:normal[xref:token/ERC721.adoc#ERC721Burnable[`ERC721Burnable`]] +:ERC721Wrapper: pass:normal[xref:token/ERC721.adoc#ERC721Wrapper[`ERC721Wrapper`]] +:ERC721Votes: pass:normal[xref:token/ERC721.adoc#ERC721Votes[`ERC721Votes`]] +:xref-IERC721-balanceOf-address-: xref:token/ERC721.adoc#IERC721-balanceOf-address- +:xref-IERC721-ownerOf-uint256-: xref:token/ERC721.adoc#IERC721-ownerOf-uint256- +:xref-IERC721-safeTransferFrom-address-address-uint256-bytes-: xref:token/ERC721.adoc#IERC721-safeTransferFrom-address-address-uint256-bytes- +:xref-IERC721-safeTransferFrom-address-address-uint256-: xref:token/ERC721.adoc#IERC721-safeTransferFrom-address-address-uint256- +:xref-IERC721-transferFrom-address-address-uint256-: xref:token/ERC721.adoc#IERC721-transferFrom-address-address-uint256- +:xref-IERC721-approve-address-uint256-: xref:token/ERC721.adoc#IERC721-approve-address-uint256- +:xref-IERC721-setApprovalForAll-address-bool-: xref:token/ERC721.adoc#IERC721-setApprovalForAll-address-bool- +:xref-IERC721-getApproved-uint256-: xref:token/ERC721.adoc#IERC721-getApproved-uint256- +:xref-IERC721-isApprovedForAll-address-address-: xref:token/ERC721.adoc#IERC721-isApprovedForAll-address-address- +:xref-IERC165-supportsInterface-bytes4-: xref:utils.adoc#IERC165-supportsInterface-bytes4- +:xref-IERC721-Transfer-address-address-uint256-: xref:token/ERC721.adoc#IERC721-Transfer-address-address-uint256- +:xref-IERC721-Approval-address-address-uint256-: xref:token/ERC721.adoc#IERC721-Approval-address-address-uint256- +:xref-IERC721-ApprovalForAll-address-address-bool-: xref:token/ERC721.adoc#IERC721-ApprovalForAll-address-address-bool- +:IERC721Receiver-onERC721Received: pass:normal[xref:token/ERC721.adoc#IERC721Receiver-onERC721Received-address-address-uint256-bytes-[`IERC721Receiver.onERC721Received`]] +:IERC721Receiver-onERC721Received: pass:normal[xref:token/ERC721.adoc#IERC721Receiver-onERC721Received-address-address-uint256-bytes-[`IERC721Receiver.onERC721Received`]] +:xref-IERC721Metadata-name--: xref:token/ERC721.adoc#IERC721Metadata-name-- +:xref-IERC721Metadata-symbol--: xref:token/ERC721.adoc#IERC721Metadata-symbol-- +:xref-IERC721Metadata-tokenURI-uint256-: xref:token/ERC721.adoc#IERC721Metadata-tokenURI-uint256- +:xref-IERC721-balanceOf-address-: xref:token/ERC721.adoc#IERC721-balanceOf-address- +:xref-IERC721-ownerOf-uint256-: xref:token/ERC721.adoc#IERC721-ownerOf-uint256- +:xref-IERC721-safeTransferFrom-address-address-uint256-bytes-: xref:token/ERC721.adoc#IERC721-safeTransferFrom-address-address-uint256-bytes- +:xref-IERC721-safeTransferFrom-address-address-uint256-: xref:token/ERC721.adoc#IERC721-safeTransferFrom-address-address-uint256- +:xref-IERC721-transferFrom-address-address-uint256-: xref:token/ERC721.adoc#IERC721-transferFrom-address-address-uint256- +:xref-IERC721-approve-address-uint256-: xref:token/ERC721.adoc#IERC721-approve-address-uint256- +:xref-IERC721-setApprovalForAll-address-bool-: xref:token/ERC721.adoc#IERC721-setApprovalForAll-address-bool- +:xref-IERC721-getApproved-uint256-: xref:token/ERC721.adoc#IERC721-getApproved-uint256- +:xref-IERC721-isApprovedForAll-address-address-: xref:token/ERC721.adoc#IERC721-isApprovedForAll-address-address- +:xref-IERC165-supportsInterface-bytes4-: xref:utils.adoc#IERC165-supportsInterface-bytes4- +:xref-IERC721-Transfer-address-address-uint256-: xref:token/ERC721.adoc#IERC721-Transfer-address-address-uint256- +:xref-IERC721-Approval-address-address-uint256-: xref:token/ERC721.adoc#IERC721-Approval-address-address-uint256- +:xref-IERC721-ApprovalForAll-address-address-bool-: xref:token/ERC721.adoc#IERC721-ApprovalForAll-address-address-bool- +:xref-IERC721Enumerable-totalSupply--: xref:token/ERC721.adoc#IERC721Enumerable-totalSupply-- +:xref-IERC721Enumerable-tokenOfOwnerByIndex-address-uint256-: xref:token/ERC721.adoc#IERC721Enumerable-tokenOfOwnerByIndex-address-uint256- +:xref-IERC721Enumerable-tokenByIndex-uint256-: xref:token/ERC721.adoc#IERC721Enumerable-tokenByIndex-uint256- +:xref-IERC721-balanceOf-address-: xref:token/ERC721.adoc#IERC721-balanceOf-address- +:xref-IERC721-ownerOf-uint256-: xref:token/ERC721.adoc#IERC721-ownerOf-uint256- +:xref-IERC721-safeTransferFrom-address-address-uint256-bytes-: xref:token/ERC721.adoc#IERC721-safeTransferFrom-address-address-uint256-bytes- +:xref-IERC721-safeTransferFrom-address-address-uint256-: xref:token/ERC721.adoc#IERC721-safeTransferFrom-address-address-uint256- +:xref-IERC721-transferFrom-address-address-uint256-: xref:token/ERC721.adoc#IERC721-transferFrom-address-address-uint256- +:xref-IERC721-approve-address-uint256-: xref:token/ERC721.adoc#IERC721-approve-address-uint256- +:xref-IERC721-setApprovalForAll-address-bool-: xref:token/ERC721.adoc#IERC721-setApprovalForAll-address-bool- +:xref-IERC721-getApproved-uint256-: xref:token/ERC721.adoc#IERC721-getApproved-uint256- +:xref-IERC721-isApprovedForAll-address-address-: xref:token/ERC721.adoc#IERC721-isApprovedForAll-address-address- +:xref-IERC165-supportsInterface-bytes4-: xref:utils.adoc#IERC165-supportsInterface-bytes4- +:xref-IERC721-Transfer-address-address-uint256-: xref:token/ERC721.adoc#IERC721-Transfer-address-address-uint256- +:xref-IERC721-Approval-address-address-uint256-: xref:token/ERC721.adoc#IERC721-Approval-address-address-uint256- +:xref-IERC721-ApprovalForAll-address-address-bool-: xref:token/ERC721.adoc#IERC721-ApprovalForAll-address-address-bool- +:ERC721Enumerable: pass:normal[xref:token/ERC721.adoc#ERC721Enumerable[`ERC721Enumerable`]] +:xref-ERC721-constructor-string-string-: xref:token/ERC721.adoc#ERC721-constructor-string-string- +:xref-ERC721-supportsInterface-bytes4-: xref:token/ERC721.adoc#ERC721-supportsInterface-bytes4- +:xref-ERC721-balanceOf-address-: xref:token/ERC721.adoc#ERC721-balanceOf-address- +:xref-ERC721-ownerOf-uint256-: xref:token/ERC721.adoc#ERC721-ownerOf-uint256- +:xref-ERC721-name--: xref:token/ERC721.adoc#ERC721-name-- +:xref-ERC721-symbol--: xref:token/ERC721.adoc#ERC721-symbol-- +:xref-ERC721-tokenURI-uint256-: xref:token/ERC721.adoc#ERC721-tokenURI-uint256- +:xref-ERC721-_baseURI--: xref:token/ERC721.adoc#ERC721-_baseURI-- +:xref-ERC721-approve-address-uint256-: xref:token/ERC721.adoc#ERC721-approve-address-uint256- +:xref-ERC721-getApproved-uint256-: xref:token/ERC721.adoc#ERC721-getApproved-uint256- +:xref-ERC721-setApprovalForAll-address-bool-: xref:token/ERC721.adoc#ERC721-setApprovalForAll-address-bool- +:xref-ERC721-isApprovedForAll-address-address-: xref:token/ERC721.adoc#ERC721-isApprovedForAll-address-address- +:xref-ERC721-transferFrom-address-address-uint256-: xref:token/ERC721.adoc#ERC721-transferFrom-address-address-uint256- +:xref-ERC721-safeTransferFrom-address-address-uint256-: xref:token/ERC721.adoc#ERC721-safeTransferFrom-address-address-uint256- +:xref-ERC721-safeTransferFrom-address-address-uint256-bytes-: xref:token/ERC721.adoc#ERC721-safeTransferFrom-address-address-uint256-bytes- +:xref-ERC721-_ownerOf-uint256-: xref:token/ERC721.adoc#ERC721-_ownerOf-uint256- +:xref-ERC721-_getApproved-uint256-: xref:token/ERC721.adoc#ERC721-_getApproved-uint256- +:xref-ERC721-_isAuthorized-address-address-uint256-: xref:token/ERC721.adoc#ERC721-_isAuthorized-address-address-uint256- +:xref-ERC721-_checkAuthorized-address-address-uint256-: xref:token/ERC721.adoc#ERC721-_checkAuthorized-address-address-uint256- +:xref-ERC721-_increaseBalance-address-uint128-: xref:token/ERC721.adoc#ERC721-_increaseBalance-address-uint128- +:xref-ERC721-_update-address-uint256-address-: xref:token/ERC721.adoc#ERC721-_update-address-uint256-address- +:xref-ERC721-_mint-address-uint256-: xref:token/ERC721.adoc#ERC721-_mint-address-uint256- +:xref-ERC721-_safeMint-address-uint256-: xref:token/ERC721.adoc#ERC721-_safeMint-address-uint256- +:xref-ERC721-_safeMint-address-uint256-bytes-: xref:token/ERC721.adoc#ERC721-_safeMint-address-uint256-bytes- +:xref-ERC721-_burn-uint256-: xref:token/ERC721.adoc#ERC721-_burn-uint256- +:xref-ERC721-_transfer-address-address-uint256-: xref:token/ERC721.adoc#ERC721-_transfer-address-address-uint256- +:xref-ERC721-_safeTransfer-address-address-uint256-: xref:token/ERC721.adoc#ERC721-_safeTransfer-address-address-uint256- +:xref-ERC721-_safeTransfer-address-address-uint256-bytes-: xref:token/ERC721.adoc#ERC721-_safeTransfer-address-address-uint256-bytes- +:xref-ERC721-_approve-address-uint256-address-: xref:token/ERC721.adoc#ERC721-_approve-address-uint256-address- +:xref-ERC721-_approve-address-uint256-address-bool-: xref:token/ERC721.adoc#ERC721-_approve-address-uint256-address-bool- +:xref-ERC721-_setApprovalForAll-address-address-bool-: xref:token/ERC721.adoc#ERC721-_setApprovalForAll-address-address-bool- +:xref-ERC721-_requireOwned-uint256-: xref:token/ERC721.adoc#ERC721-_requireOwned-uint256- +:xref-IERC721-Transfer-address-address-uint256-: xref:token/ERC721.adoc#IERC721-Transfer-address-address-uint256- +:xref-IERC721-Approval-address-address-uint256-: xref:token/ERC721.adoc#IERC721-Approval-address-address-uint256- +:xref-IERC721-ApprovalForAll-address-address-bool-: xref:token/ERC721.adoc#IERC721-ApprovalForAll-address-address-bool- +:xref-IERC721Errors-ERC721InvalidOwner-address-: xref:interfaces.adoc#IERC721Errors-ERC721InvalidOwner-address- +:xref-IERC721Errors-ERC721NonexistentToken-uint256-: xref:interfaces.adoc#IERC721Errors-ERC721NonexistentToken-uint256- +:xref-IERC721Errors-ERC721IncorrectOwner-address-uint256-address-: xref:interfaces.adoc#IERC721Errors-ERC721IncorrectOwner-address-uint256-address- +:xref-IERC721Errors-ERC721InvalidSender-address-: xref:interfaces.adoc#IERC721Errors-ERC721InvalidSender-address- +:xref-IERC721Errors-ERC721InvalidReceiver-address-: xref:interfaces.adoc#IERC721Errors-ERC721InvalidReceiver-address- +:xref-IERC721Errors-ERC721InsufficientApproval-address-uint256-: xref:interfaces.adoc#IERC721Errors-ERC721InsufficientApproval-address-uint256- +:xref-IERC721Errors-ERC721InvalidApprover-address-: xref:interfaces.adoc#IERC721Errors-ERC721InvalidApprover-address- +:xref-IERC721Errors-ERC721InvalidOperator-address-: xref:interfaces.adoc#IERC721Errors-ERC721InvalidOperator-address- +:IERC165-supportsInterface: pass:normal[xref:utils.adoc#IERC165-supportsInterface-bytes4-[`IERC165.supportsInterface`]] +:IERC721-balanceOf: pass:normal[xref:token/ERC721.adoc#IERC721-balanceOf-address-[`IERC721.balanceOf`]] +:IERC721-ownerOf: pass:normal[xref:token/ERC721.adoc#IERC721-ownerOf-uint256-[`IERC721.ownerOf`]] +:IERC721Metadata-name: pass:normal[xref:token/ERC721.adoc#IERC721Metadata-name--[`IERC721Metadata.name`]] +:IERC721Metadata-symbol: pass:normal[xref:token/ERC721.adoc#IERC721Metadata-symbol--[`IERC721Metadata.symbol`]] +:IERC721Metadata-tokenURI: pass:normal[xref:token/ERC721.adoc#IERC721Metadata-tokenURI-uint256-[`IERC721Metadata.tokenURI`]] +:IERC721-approve: pass:normal[xref:token/ERC721.adoc#IERC721-approve-address-uint256-[`IERC721.approve`]] +:IERC721-getApproved: pass:normal[xref:token/ERC721.adoc#IERC721-getApproved-uint256-[`IERC721.getApproved`]] +:IERC721-setApprovalForAll: pass:normal[xref:token/ERC721.adoc#IERC721-setApprovalForAll-address-bool-[`IERC721.setApprovalForAll`]] +:IERC721-isApprovedForAll: pass:normal[xref:token/ERC721.adoc#IERC721-isApprovedForAll-address-address-[`IERC721.isApprovedForAll`]] +:IERC721-transferFrom: pass:normal[xref:token/ERC721.adoc#IERC721-transferFrom-address-address-uint256-[`IERC721.transferFrom`]] +:IERC721-safeTransferFrom: pass:normal[xref:token/ERC721.adoc#IERC721-safeTransferFrom-address-address-uint256-[`IERC721.safeTransferFrom`]] +:IERC721-safeTransferFrom: pass:normal[xref:token/ERC721.adoc#IERC721-safeTransferFrom-address-address-uint256-[`IERC721.safeTransferFrom`]] +:IERC721Receiver-onERC721Received: pass:normal[xref:token/ERC721.adoc#IERC721Receiver-onERC721Received-address-address-uint256-bytes-[`IERC721Receiver.onERC721Received`]] +:xref-ERC721-_safeMint-address-uint256-: xref:token/ERC721.adoc#ERC721-_safeMint-address-uint256- +:IERC721Receiver-onERC721Received: pass:normal[xref:token/ERC721.adoc#IERC721Receiver-onERC721Received-address-address-uint256-bytes-[`IERC721Receiver.onERC721Received`]] +:IERC721Receiver-onERC721Received: pass:normal[xref:token/ERC721.adoc#IERC721Receiver-onERC721Received-address-address-uint256-bytes-[`IERC721Receiver.onERC721Received`]] +:IERC721Receiver-onERC721Received: pass:normal[xref:token/ERC721.adoc#IERC721Receiver-onERC721Received-address-address-uint256-bytes-[`IERC721Receiver.onERC721Received`]] +:xref-ERC721-_safeTransfer-address-address-uint256-: xref:token/ERC721.adoc#ERC721-_safeTransfer-address-address-uint256- +:IERC721Receiver-onERC721Received: pass:normal[xref:token/ERC721.adoc#IERC721Receiver-onERC721Received-address-address-uint256-bytes-[`IERC721Receiver.onERC721Received`]] +:ERC721: pass:normal[xref:token/ERC721.adoc#ERC721[`ERC721`]] +:xref-ERC721Enumerable-supportsInterface-bytes4-: xref:token/ERC721.adoc#ERC721Enumerable-supportsInterface-bytes4- +:xref-ERC721Enumerable-tokenOfOwnerByIndex-address-uint256-: xref:token/ERC721.adoc#ERC721Enumerable-tokenOfOwnerByIndex-address-uint256- +:xref-ERC721Enumerable-totalSupply--: xref:token/ERC721.adoc#ERC721Enumerable-totalSupply-- +:xref-ERC721Enumerable-tokenByIndex-uint256-: xref:token/ERC721.adoc#ERC721Enumerable-tokenByIndex-uint256- +:xref-ERC721Enumerable-_update-address-uint256-address-: xref:token/ERC721.adoc#ERC721Enumerable-_update-address-uint256-address- +:xref-ERC721Enumerable-_increaseBalance-address-uint128-: xref:token/ERC721.adoc#ERC721Enumerable-_increaseBalance-address-uint128- +:xref-ERC721-balanceOf-address-: xref:token/ERC721.adoc#ERC721-balanceOf-address- +:xref-ERC721-ownerOf-uint256-: xref:token/ERC721.adoc#ERC721-ownerOf-uint256- +:xref-ERC721-name--: xref:token/ERC721.adoc#ERC721-name-- +:xref-ERC721-symbol--: xref:token/ERC721.adoc#ERC721-symbol-- +:xref-ERC721-tokenURI-uint256-: xref:token/ERC721.adoc#ERC721-tokenURI-uint256- +:xref-ERC721-_baseURI--: xref:token/ERC721.adoc#ERC721-_baseURI-- +:xref-ERC721-approve-address-uint256-: xref:token/ERC721.adoc#ERC721-approve-address-uint256- +:xref-ERC721-getApproved-uint256-: xref:token/ERC721.adoc#ERC721-getApproved-uint256- +:xref-ERC721-setApprovalForAll-address-bool-: xref:token/ERC721.adoc#ERC721-setApprovalForAll-address-bool- +:xref-ERC721-isApprovedForAll-address-address-: xref:token/ERC721.adoc#ERC721-isApprovedForAll-address-address- +:xref-ERC721-transferFrom-address-address-uint256-: xref:token/ERC721.adoc#ERC721-transferFrom-address-address-uint256- +:xref-ERC721-safeTransferFrom-address-address-uint256-: xref:token/ERC721.adoc#ERC721-safeTransferFrom-address-address-uint256- +:xref-ERC721-safeTransferFrom-address-address-uint256-bytes-: xref:token/ERC721.adoc#ERC721-safeTransferFrom-address-address-uint256-bytes- +:xref-ERC721-_ownerOf-uint256-: xref:token/ERC721.adoc#ERC721-_ownerOf-uint256- +:xref-ERC721-_getApproved-uint256-: xref:token/ERC721.adoc#ERC721-_getApproved-uint256- +:xref-ERC721-_isAuthorized-address-address-uint256-: xref:token/ERC721.adoc#ERC721-_isAuthorized-address-address-uint256- +:xref-ERC721-_checkAuthorized-address-address-uint256-: xref:token/ERC721.adoc#ERC721-_checkAuthorized-address-address-uint256- +:xref-ERC721-_mint-address-uint256-: xref:token/ERC721.adoc#ERC721-_mint-address-uint256- +:xref-ERC721-_safeMint-address-uint256-: xref:token/ERC721.adoc#ERC721-_safeMint-address-uint256- +:xref-ERC721-_safeMint-address-uint256-bytes-: xref:token/ERC721.adoc#ERC721-_safeMint-address-uint256-bytes- +:xref-ERC721-_burn-uint256-: xref:token/ERC721.adoc#ERC721-_burn-uint256- +:xref-ERC721-_transfer-address-address-uint256-: xref:token/ERC721.adoc#ERC721-_transfer-address-address-uint256- +:xref-ERC721-_safeTransfer-address-address-uint256-: xref:token/ERC721.adoc#ERC721-_safeTransfer-address-address-uint256- +:xref-ERC721-_safeTransfer-address-address-uint256-bytes-: xref:token/ERC721.adoc#ERC721-_safeTransfer-address-address-uint256-bytes- +:xref-ERC721-_approve-address-uint256-address-: xref:token/ERC721.adoc#ERC721-_approve-address-uint256-address- +:xref-ERC721-_approve-address-uint256-address-bool-: xref:token/ERC721.adoc#ERC721-_approve-address-uint256-address-bool- +:xref-ERC721-_setApprovalForAll-address-address-bool-: xref:token/ERC721.adoc#ERC721-_setApprovalForAll-address-address-bool- +:xref-ERC721-_requireOwned-uint256-: xref:token/ERC721.adoc#ERC721-_requireOwned-uint256- +:xref-IERC721-Transfer-address-address-uint256-: xref:token/ERC721.adoc#IERC721-Transfer-address-address-uint256- +:xref-IERC721-Approval-address-address-uint256-: xref:token/ERC721.adoc#IERC721-Approval-address-address-uint256- +:xref-IERC721-ApprovalForAll-address-address-bool-: xref:token/ERC721.adoc#IERC721-ApprovalForAll-address-address-bool- +:xref-ERC721Enumerable-ERC721OutOfBoundsIndex-address-uint256-: xref:token/ERC721.adoc#ERC721Enumerable-ERC721OutOfBoundsIndex-address-uint256- +:xref-ERC721Enumerable-ERC721EnumerableForbiddenBatchMint--: xref:token/ERC721.adoc#ERC721Enumerable-ERC721EnumerableForbiddenBatchMint-- +:xref-IERC721Errors-ERC721InvalidOwner-address-: xref:interfaces.adoc#IERC721Errors-ERC721InvalidOwner-address- +:xref-IERC721Errors-ERC721NonexistentToken-uint256-: xref:interfaces.adoc#IERC721Errors-ERC721NonexistentToken-uint256- +:xref-IERC721Errors-ERC721IncorrectOwner-address-uint256-address-: xref:interfaces.adoc#IERC721Errors-ERC721IncorrectOwner-address-uint256-address- +:xref-IERC721Errors-ERC721InvalidSender-address-: xref:interfaces.adoc#IERC721Errors-ERC721InvalidSender-address- +:xref-IERC721Errors-ERC721InvalidReceiver-address-: xref:interfaces.adoc#IERC721Errors-ERC721InvalidReceiver-address- +:xref-IERC721Errors-ERC721InsufficientApproval-address-uint256-: xref:interfaces.adoc#IERC721Errors-ERC721InsufficientApproval-address-uint256- +:xref-IERC721Errors-ERC721InvalidApprover-address-: xref:interfaces.adoc#IERC721Errors-ERC721InvalidApprover-address- +:xref-IERC721Errors-ERC721InvalidOperator-address-: xref:interfaces.adoc#IERC721Errors-ERC721InvalidOperator-address- +:IERC165-supportsInterface: pass:normal[xref:utils.adoc#IERC165-supportsInterface-bytes4-[`IERC165.supportsInterface`]] +:IERC721Enumerable-tokenOfOwnerByIndex: pass:normal[xref:token/ERC721.adoc#IERC721Enumerable-tokenOfOwnerByIndex-address-uint256-[`IERC721Enumerable.tokenOfOwnerByIndex`]] +:IERC721Enumerable-totalSupply: pass:normal[xref:token/ERC721.adoc#IERC721Enumerable-totalSupply--[`IERC721Enumerable.totalSupply`]] +:IERC721Enumerable-tokenByIndex: pass:normal[xref:token/ERC721.adoc#IERC721Enumerable-tokenByIndex-uint256-[`IERC721Enumerable.tokenByIndex`]] +:ERC721-_update: pass:normal[xref:token/ERC721.adoc#ERC721-_update-address-uint256-address-[`ERC721._update`]] +:xref-IERC721Receiver-onERC721Received-address-address-uint256-bytes-: xref:token/ERC721.adoc#IERC721Receiver-onERC721Received-address-address-uint256-bytes- +:IERC721: pass:normal[xref:token/ERC721.adoc#IERC721[`IERC721`]] +:IERC721-safeTransferFrom: pass:normal[xref:token/ERC721.adoc#IERC721-safeTransferFrom-address-address-uint256-[`IERC721.safeTransferFrom`]] +:Pausable-_pause: pass:normal[xref:utils.adoc#Pausable-_pause--[`Pausable._pause`]] +:Pausable-_unpause: pass:normal[xref:utils.adoc#Pausable-_unpause--[`Pausable._unpause`]] +:AccessControl: pass:normal[xref:access.adoc#AccessControl[`AccessControl`]] +:Ownable: pass:normal[xref:access.adoc#Ownable[`Ownable`]] +:xref-ERC721Pausable-_update-address-uint256-address-: xref:token/ERC721.adoc#ERC721Pausable-_update-address-uint256-address- +:xref-Pausable-paused--: xref:utils.adoc#Pausable-paused-- +:xref-Pausable-_requireNotPaused--: xref:utils.adoc#Pausable-_requireNotPaused-- +:xref-Pausable-_requirePaused--: xref:utils.adoc#Pausable-_requirePaused-- +:xref-Pausable-_pause--: xref:utils.adoc#Pausable-_pause-- +:xref-Pausable-_unpause--: xref:utils.adoc#Pausable-_unpause-- +:xref-ERC721-supportsInterface-bytes4-: xref:token/ERC721.adoc#ERC721-supportsInterface-bytes4- +:xref-ERC721-balanceOf-address-: xref:token/ERC721.adoc#ERC721-balanceOf-address- +:xref-ERC721-ownerOf-uint256-: xref:token/ERC721.adoc#ERC721-ownerOf-uint256- +:xref-ERC721-name--: xref:token/ERC721.adoc#ERC721-name-- +:xref-ERC721-symbol--: xref:token/ERC721.adoc#ERC721-symbol-- +:xref-ERC721-tokenURI-uint256-: xref:token/ERC721.adoc#ERC721-tokenURI-uint256- +:xref-ERC721-_baseURI--: xref:token/ERC721.adoc#ERC721-_baseURI-- +:xref-ERC721-approve-address-uint256-: xref:token/ERC721.adoc#ERC721-approve-address-uint256- +:xref-ERC721-getApproved-uint256-: xref:token/ERC721.adoc#ERC721-getApproved-uint256- +:xref-ERC721-setApprovalForAll-address-bool-: xref:token/ERC721.adoc#ERC721-setApprovalForAll-address-bool- +:xref-ERC721-isApprovedForAll-address-address-: xref:token/ERC721.adoc#ERC721-isApprovedForAll-address-address- +:xref-ERC721-transferFrom-address-address-uint256-: xref:token/ERC721.adoc#ERC721-transferFrom-address-address-uint256- +:xref-ERC721-safeTransferFrom-address-address-uint256-: xref:token/ERC721.adoc#ERC721-safeTransferFrom-address-address-uint256- +:xref-ERC721-safeTransferFrom-address-address-uint256-bytes-: xref:token/ERC721.adoc#ERC721-safeTransferFrom-address-address-uint256-bytes- +:xref-ERC721-_ownerOf-uint256-: xref:token/ERC721.adoc#ERC721-_ownerOf-uint256- +:xref-ERC721-_getApproved-uint256-: xref:token/ERC721.adoc#ERC721-_getApproved-uint256- +:xref-ERC721-_isAuthorized-address-address-uint256-: xref:token/ERC721.adoc#ERC721-_isAuthorized-address-address-uint256- +:xref-ERC721-_checkAuthorized-address-address-uint256-: xref:token/ERC721.adoc#ERC721-_checkAuthorized-address-address-uint256- +:xref-ERC721-_increaseBalance-address-uint128-: xref:token/ERC721.adoc#ERC721-_increaseBalance-address-uint128- +:xref-ERC721-_mint-address-uint256-: xref:token/ERC721.adoc#ERC721-_mint-address-uint256- +:xref-ERC721-_safeMint-address-uint256-: xref:token/ERC721.adoc#ERC721-_safeMint-address-uint256- +:xref-ERC721-_safeMint-address-uint256-bytes-: xref:token/ERC721.adoc#ERC721-_safeMint-address-uint256-bytes- +:xref-ERC721-_burn-uint256-: xref:token/ERC721.adoc#ERC721-_burn-uint256- +:xref-ERC721-_transfer-address-address-uint256-: xref:token/ERC721.adoc#ERC721-_transfer-address-address-uint256- +:xref-ERC721-_safeTransfer-address-address-uint256-: xref:token/ERC721.adoc#ERC721-_safeTransfer-address-address-uint256- +:xref-ERC721-_safeTransfer-address-address-uint256-bytes-: xref:token/ERC721.adoc#ERC721-_safeTransfer-address-address-uint256-bytes- +:xref-ERC721-_approve-address-uint256-address-: xref:token/ERC721.adoc#ERC721-_approve-address-uint256-address- +:xref-ERC721-_approve-address-uint256-address-bool-: xref:token/ERC721.adoc#ERC721-_approve-address-uint256-address-bool- +:xref-ERC721-_setApprovalForAll-address-address-bool-: xref:token/ERC721.adoc#ERC721-_setApprovalForAll-address-address-bool- +:xref-ERC721-_requireOwned-uint256-: xref:token/ERC721.adoc#ERC721-_requireOwned-uint256- +:xref-Pausable-Paused-address-: xref:utils.adoc#Pausable-Paused-address- +:xref-Pausable-Unpaused-address-: xref:utils.adoc#Pausable-Unpaused-address- +:xref-IERC721-Transfer-address-address-uint256-: xref:token/ERC721.adoc#IERC721-Transfer-address-address-uint256- +:xref-IERC721-Approval-address-address-uint256-: xref:token/ERC721.adoc#IERC721-Approval-address-address-uint256- +:xref-IERC721-ApprovalForAll-address-address-bool-: xref:token/ERC721.adoc#IERC721-ApprovalForAll-address-address-bool- +:xref-Pausable-EnforcedPause--: xref:utils.adoc#Pausable-EnforcedPause-- +:xref-Pausable-ExpectedPause--: xref:utils.adoc#Pausable-ExpectedPause-- +:xref-IERC721Errors-ERC721InvalidOwner-address-: xref:interfaces.adoc#IERC721Errors-ERC721InvalidOwner-address- +:xref-IERC721Errors-ERC721NonexistentToken-uint256-: xref:interfaces.adoc#IERC721Errors-ERC721NonexistentToken-uint256- +:xref-IERC721Errors-ERC721IncorrectOwner-address-uint256-address-: xref:interfaces.adoc#IERC721Errors-ERC721IncorrectOwner-address-uint256-address- +:xref-IERC721Errors-ERC721InvalidSender-address-: xref:interfaces.adoc#IERC721Errors-ERC721InvalidSender-address- +:xref-IERC721Errors-ERC721InvalidReceiver-address-: xref:interfaces.adoc#IERC721Errors-ERC721InvalidReceiver-address- +:xref-IERC721Errors-ERC721InsufficientApproval-address-uint256-: xref:interfaces.adoc#IERC721Errors-ERC721InsufficientApproval-address-uint256- +:xref-IERC721Errors-ERC721InvalidApprover-address-: xref:interfaces.adoc#IERC721Errors-ERC721InvalidApprover-address- +:xref-IERC721Errors-ERC721InvalidOperator-address-: xref:interfaces.adoc#IERC721Errors-ERC721InvalidOperator-address- +:ERC721-_update: pass:normal[xref:token/ERC721.adoc#ERC721-_update-address-uint256-address-[`ERC721._update`]] +:xref-ERC721Burnable-burn-uint256-: xref:token/ERC721.adoc#ERC721Burnable-burn-uint256- +:xref-ERC721-supportsInterface-bytes4-: xref:token/ERC721.adoc#ERC721-supportsInterface-bytes4- +:xref-ERC721-balanceOf-address-: xref:token/ERC721.adoc#ERC721-balanceOf-address- +:xref-ERC721-ownerOf-uint256-: xref:token/ERC721.adoc#ERC721-ownerOf-uint256- +:xref-ERC721-name--: xref:token/ERC721.adoc#ERC721-name-- +:xref-ERC721-symbol--: xref:token/ERC721.adoc#ERC721-symbol-- +:xref-ERC721-tokenURI-uint256-: xref:token/ERC721.adoc#ERC721-tokenURI-uint256- +:xref-ERC721-_baseURI--: xref:token/ERC721.adoc#ERC721-_baseURI-- +:xref-ERC721-approve-address-uint256-: xref:token/ERC721.adoc#ERC721-approve-address-uint256- +:xref-ERC721-getApproved-uint256-: xref:token/ERC721.adoc#ERC721-getApproved-uint256- +:xref-ERC721-setApprovalForAll-address-bool-: xref:token/ERC721.adoc#ERC721-setApprovalForAll-address-bool- +:xref-ERC721-isApprovedForAll-address-address-: xref:token/ERC721.adoc#ERC721-isApprovedForAll-address-address- +:xref-ERC721-transferFrom-address-address-uint256-: xref:token/ERC721.adoc#ERC721-transferFrom-address-address-uint256- +:xref-ERC721-safeTransferFrom-address-address-uint256-: xref:token/ERC721.adoc#ERC721-safeTransferFrom-address-address-uint256- +:xref-ERC721-safeTransferFrom-address-address-uint256-bytes-: xref:token/ERC721.adoc#ERC721-safeTransferFrom-address-address-uint256-bytes- +:xref-ERC721-_ownerOf-uint256-: xref:token/ERC721.adoc#ERC721-_ownerOf-uint256- +:xref-ERC721-_getApproved-uint256-: xref:token/ERC721.adoc#ERC721-_getApproved-uint256- +:xref-ERC721-_isAuthorized-address-address-uint256-: xref:token/ERC721.adoc#ERC721-_isAuthorized-address-address-uint256- +:xref-ERC721-_checkAuthorized-address-address-uint256-: xref:token/ERC721.adoc#ERC721-_checkAuthorized-address-address-uint256- +:xref-ERC721-_increaseBalance-address-uint128-: xref:token/ERC721.adoc#ERC721-_increaseBalance-address-uint128- +:xref-ERC721-_update-address-uint256-address-: xref:token/ERC721.adoc#ERC721-_update-address-uint256-address- +:xref-ERC721-_mint-address-uint256-: xref:token/ERC721.adoc#ERC721-_mint-address-uint256- +:xref-ERC721-_safeMint-address-uint256-: xref:token/ERC721.adoc#ERC721-_safeMint-address-uint256- +:xref-ERC721-_safeMint-address-uint256-bytes-: xref:token/ERC721.adoc#ERC721-_safeMint-address-uint256-bytes- +:xref-ERC721-_burn-uint256-: xref:token/ERC721.adoc#ERC721-_burn-uint256- +:xref-ERC721-_transfer-address-address-uint256-: xref:token/ERC721.adoc#ERC721-_transfer-address-address-uint256- +:xref-ERC721-_safeTransfer-address-address-uint256-: xref:token/ERC721.adoc#ERC721-_safeTransfer-address-address-uint256- +:xref-ERC721-_safeTransfer-address-address-uint256-bytes-: xref:token/ERC721.adoc#ERC721-_safeTransfer-address-address-uint256-bytes- +:xref-ERC721-_approve-address-uint256-address-: xref:token/ERC721.adoc#ERC721-_approve-address-uint256-address- +:xref-ERC721-_approve-address-uint256-address-bool-: xref:token/ERC721.adoc#ERC721-_approve-address-uint256-address-bool- +:xref-ERC721-_setApprovalForAll-address-address-bool-: xref:token/ERC721.adoc#ERC721-_setApprovalForAll-address-address-bool- +:xref-ERC721-_requireOwned-uint256-: xref:token/ERC721.adoc#ERC721-_requireOwned-uint256- +:xref-IERC721-Transfer-address-address-uint256-: xref:token/ERC721.adoc#IERC721-Transfer-address-address-uint256- +:xref-IERC721-Approval-address-address-uint256-: xref:token/ERC721.adoc#IERC721-Approval-address-address-uint256- +:xref-IERC721-ApprovalForAll-address-address-bool-: xref:token/ERC721.adoc#IERC721-ApprovalForAll-address-address-bool- +:xref-IERC721Errors-ERC721InvalidOwner-address-: xref:interfaces.adoc#IERC721Errors-ERC721InvalidOwner-address- +:xref-IERC721Errors-ERC721NonexistentToken-uint256-: xref:interfaces.adoc#IERC721Errors-ERC721NonexistentToken-uint256- +:xref-IERC721Errors-ERC721IncorrectOwner-address-uint256-address-: xref:interfaces.adoc#IERC721Errors-ERC721IncorrectOwner-address-uint256-address- +:xref-IERC721Errors-ERC721InvalidSender-address-: xref:interfaces.adoc#IERC721Errors-ERC721InvalidSender-address- +:xref-IERC721Errors-ERC721InvalidReceiver-address-: xref:interfaces.adoc#IERC721Errors-ERC721InvalidReceiver-address- +:xref-IERC721Errors-ERC721InsufficientApproval-address-uint256-: xref:interfaces.adoc#IERC721Errors-ERC721InsufficientApproval-address-uint256- +:xref-IERC721Errors-ERC721InvalidApprover-address-: xref:interfaces.adoc#IERC721Errors-ERC721InvalidApprover-address- +:xref-IERC721Errors-ERC721InvalidOperator-address-: xref:interfaces.adoc#IERC721Errors-ERC721InvalidOperator-address- +:ERC721-_burn: pass:normal[xref:token/ERC721.adoc#ERC721-_burn-uint256-[`ERC721._burn`]] +:xref-ERC721Consecutive-_maxBatchSize--: xref:token/ERC721.adoc#ERC721Consecutive-_maxBatchSize-- +:xref-ERC721Consecutive-_ownerOf-uint256-: xref:token/ERC721.adoc#ERC721Consecutive-_ownerOf-uint256- +:xref-ERC721Consecutive-_mintConsecutive-address-uint96-: xref:token/ERC721.adoc#ERC721Consecutive-_mintConsecutive-address-uint96- +:xref-ERC721Consecutive-_update-address-uint256-address-: xref:token/ERC721.adoc#ERC721Consecutive-_update-address-uint256-address- +:xref-ERC721Consecutive-_firstConsecutiveId--: xref:token/ERC721.adoc#ERC721Consecutive-_firstConsecutiveId-- +:xref-ERC721-supportsInterface-bytes4-: xref:token/ERC721.adoc#ERC721-supportsInterface-bytes4- +:xref-ERC721-balanceOf-address-: xref:token/ERC721.adoc#ERC721-balanceOf-address- +:xref-ERC721-ownerOf-uint256-: xref:token/ERC721.adoc#ERC721-ownerOf-uint256- +:xref-ERC721-name--: xref:token/ERC721.adoc#ERC721-name-- +:xref-ERC721-symbol--: xref:token/ERC721.adoc#ERC721-symbol-- +:xref-ERC721-tokenURI-uint256-: xref:token/ERC721.adoc#ERC721-tokenURI-uint256- +:xref-ERC721-_baseURI--: xref:token/ERC721.adoc#ERC721-_baseURI-- +:xref-ERC721-approve-address-uint256-: xref:token/ERC721.adoc#ERC721-approve-address-uint256- +:xref-ERC721-getApproved-uint256-: xref:token/ERC721.adoc#ERC721-getApproved-uint256- +:xref-ERC721-setApprovalForAll-address-bool-: xref:token/ERC721.adoc#ERC721-setApprovalForAll-address-bool- +:xref-ERC721-isApprovedForAll-address-address-: xref:token/ERC721.adoc#ERC721-isApprovedForAll-address-address- +:xref-ERC721-transferFrom-address-address-uint256-: xref:token/ERC721.adoc#ERC721-transferFrom-address-address-uint256- +:xref-ERC721-safeTransferFrom-address-address-uint256-: xref:token/ERC721.adoc#ERC721-safeTransferFrom-address-address-uint256- +:xref-ERC721-safeTransferFrom-address-address-uint256-bytes-: xref:token/ERC721.adoc#ERC721-safeTransferFrom-address-address-uint256-bytes- +:xref-ERC721-_getApproved-uint256-: xref:token/ERC721.adoc#ERC721-_getApproved-uint256- +:xref-ERC721-_isAuthorized-address-address-uint256-: xref:token/ERC721.adoc#ERC721-_isAuthorized-address-address-uint256- +:xref-ERC721-_checkAuthorized-address-address-uint256-: xref:token/ERC721.adoc#ERC721-_checkAuthorized-address-address-uint256- +:xref-ERC721-_increaseBalance-address-uint128-: xref:token/ERC721.adoc#ERC721-_increaseBalance-address-uint128- +:xref-ERC721-_mint-address-uint256-: xref:token/ERC721.adoc#ERC721-_mint-address-uint256- +:xref-ERC721-_safeMint-address-uint256-: xref:token/ERC721.adoc#ERC721-_safeMint-address-uint256- +:xref-ERC721-_safeMint-address-uint256-bytes-: xref:token/ERC721.adoc#ERC721-_safeMint-address-uint256-bytes- +:xref-ERC721-_burn-uint256-: xref:token/ERC721.adoc#ERC721-_burn-uint256- +:xref-ERC721-_transfer-address-address-uint256-: xref:token/ERC721.adoc#ERC721-_transfer-address-address-uint256- +:xref-ERC721-_safeTransfer-address-address-uint256-: xref:token/ERC721.adoc#ERC721-_safeTransfer-address-address-uint256- +:xref-ERC721-_safeTransfer-address-address-uint256-bytes-: xref:token/ERC721.adoc#ERC721-_safeTransfer-address-address-uint256-bytes- +:xref-ERC721-_approve-address-uint256-address-: xref:token/ERC721.adoc#ERC721-_approve-address-uint256-address- +:xref-ERC721-_approve-address-uint256-address-bool-: xref:token/ERC721.adoc#ERC721-_approve-address-uint256-address-bool- +:xref-ERC721-_setApprovalForAll-address-address-bool-: xref:token/ERC721.adoc#ERC721-_setApprovalForAll-address-address-bool- +:xref-ERC721-_requireOwned-uint256-: xref:token/ERC721.adoc#ERC721-_requireOwned-uint256- +:xref-IERC721-Transfer-address-address-uint256-: xref:token/ERC721.adoc#IERC721-Transfer-address-address-uint256- +:xref-IERC721-Approval-address-address-uint256-: xref:token/ERC721.adoc#IERC721-Approval-address-address-uint256- +:xref-IERC721-ApprovalForAll-address-address-bool-: xref:token/ERC721.adoc#IERC721-ApprovalForAll-address-address-bool- +:xref-IERC2309-ConsecutiveTransfer-uint256-uint256-address-address-: xref:interfaces.adoc#IERC2309-ConsecutiveTransfer-uint256-uint256-address-address- +:xref-ERC721Consecutive-ERC721ForbiddenBatchMint--: xref:token/ERC721.adoc#ERC721Consecutive-ERC721ForbiddenBatchMint-- +:xref-ERC721Consecutive-ERC721ExceededMaxBatchMint-uint256-uint256-: xref:token/ERC721.adoc#ERC721Consecutive-ERC721ExceededMaxBatchMint-uint256-uint256- +:xref-ERC721Consecutive-ERC721ForbiddenMint--: xref:token/ERC721.adoc#ERC721Consecutive-ERC721ForbiddenMint-- +:xref-ERC721Consecutive-ERC721ForbiddenBatchBurn--: xref:token/ERC721.adoc#ERC721Consecutive-ERC721ForbiddenBatchBurn-- +:xref-IERC721Errors-ERC721InvalidOwner-address-: xref:interfaces.adoc#IERC721Errors-ERC721InvalidOwner-address- +:xref-IERC721Errors-ERC721NonexistentToken-uint256-: xref:interfaces.adoc#IERC721Errors-ERC721NonexistentToken-uint256- +:xref-IERC721Errors-ERC721IncorrectOwner-address-uint256-address-: xref:interfaces.adoc#IERC721Errors-ERC721IncorrectOwner-address-uint256-address- +:xref-IERC721Errors-ERC721InvalidSender-address-: xref:interfaces.adoc#IERC721Errors-ERC721InvalidSender-address- +:xref-IERC721Errors-ERC721InvalidReceiver-address-: xref:interfaces.adoc#IERC721Errors-ERC721InvalidReceiver-address- +:xref-IERC721Errors-ERC721InsufficientApproval-address-uint256-: xref:interfaces.adoc#IERC721Errors-ERC721InsufficientApproval-address-uint256- +:xref-IERC721Errors-ERC721InvalidApprover-address-: xref:interfaces.adoc#IERC721Errors-ERC721InvalidApprover-address- +:xref-IERC721Errors-ERC721InvalidOperator-address-: xref:interfaces.adoc#IERC721Errors-ERC721InvalidOperator-address- +:ERC721-_ownerOf: pass:normal[xref:token/ERC721.adoc#ERC721-_ownerOf-uint256-[`ERC721._ownerOf`]] +:IERC2309-ConsecutiveTransfer: pass:normal[xref:interfaces.adoc#IERC2309-ConsecutiveTransfer-uint256-uint256-address-address-[`IERC2309.ConsecutiveTransfer`]] +:ERC721-_update: pass:normal[xref:token/ERC721.adoc#ERC721-_update-address-uint256-address-[`ERC721._update`]] +:ERC721Consecutive: pass:normal[xref:token/ERC721.adoc#ERC721Consecutive[`ERC721Consecutive`]] +:IERC721-Transfer: pass:normal[xref:token/ERC721.adoc#IERC721-Transfer-address-address-uint256-[`IERC721.Transfer`]] +:xref-ERC721URIStorage-supportsInterface-bytes4-: xref:token/ERC721.adoc#ERC721URIStorage-supportsInterface-bytes4- +:xref-ERC721URIStorage-tokenURI-uint256-: xref:token/ERC721.adoc#ERC721URIStorage-tokenURI-uint256- +:xref-ERC721URIStorage-_setTokenURI-uint256-string-: xref:token/ERC721.adoc#ERC721URIStorage-_setTokenURI-uint256-string- +:xref-ERC721-balanceOf-address-: xref:token/ERC721.adoc#ERC721-balanceOf-address- +:xref-ERC721-ownerOf-uint256-: xref:token/ERC721.adoc#ERC721-ownerOf-uint256- +:xref-ERC721-name--: xref:token/ERC721.adoc#ERC721-name-- +:xref-ERC721-symbol--: xref:token/ERC721.adoc#ERC721-symbol-- +:xref-ERC721-_baseURI--: xref:token/ERC721.adoc#ERC721-_baseURI-- +:xref-ERC721-approve-address-uint256-: xref:token/ERC721.adoc#ERC721-approve-address-uint256- +:xref-ERC721-getApproved-uint256-: xref:token/ERC721.adoc#ERC721-getApproved-uint256- +:xref-ERC721-setApprovalForAll-address-bool-: xref:token/ERC721.adoc#ERC721-setApprovalForAll-address-bool- +:xref-ERC721-isApprovedForAll-address-address-: xref:token/ERC721.adoc#ERC721-isApprovedForAll-address-address- +:xref-ERC721-transferFrom-address-address-uint256-: xref:token/ERC721.adoc#ERC721-transferFrom-address-address-uint256- +:xref-ERC721-safeTransferFrom-address-address-uint256-: xref:token/ERC721.adoc#ERC721-safeTransferFrom-address-address-uint256- +:xref-ERC721-safeTransferFrom-address-address-uint256-bytes-: xref:token/ERC721.adoc#ERC721-safeTransferFrom-address-address-uint256-bytes- +:xref-ERC721-_ownerOf-uint256-: xref:token/ERC721.adoc#ERC721-_ownerOf-uint256- +:xref-ERC721-_getApproved-uint256-: xref:token/ERC721.adoc#ERC721-_getApproved-uint256- +:xref-ERC721-_isAuthorized-address-address-uint256-: xref:token/ERC721.adoc#ERC721-_isAuthorized-address-address-uint256- +:xref-ERC721-_checkAuthorized-address-address-uint256-: xref:token/ERC721.adoc#ERC721-_checkAuthorized-address-address-uint256- +:xref-ERC721-_increaseBalance-address-uint128-: xref:token/ERC721.adoc#ERC721-_increaseBalance-address-uint128- +:xref-ERC721-_update-address-uint256-address-: xref:token/ERC721.adoc#ERC721-_update-address-uint256-address- +:xref-ERC721-_mint-address-uint256-: xref:token/ERC721.adoc#ERC721-_mint-address-uint256- +:xref-ERC721-_safeMint-address-uint256-: xref:token/ERC721.adoc#ERC721-_safeMint-address-uint256- +:xref-ERC721-_safeMint-address-uint256-bytes-: xref:token/ERC721.adoc#ERC721-_safeMint-address-uint256-bytes- +:xref-ERC721-_burn-uint256-: xref:token/ERC721.adoc#ERC721-_burn-uint256- +:xref-ERC721-_transfer-address-address-uint256-: xref:token/ERC721.adoc#ERC721-_transfer-address-address-uint256- +:xref-ERC721-_safeTransfer-address-address-uint256-: xref:token/ERC721.adoc#ERC721-_safeTransfer-address-address-uint256- +:xref-ERC721-_safeTransfer-address-address-uint256-bytes-: xref:token/ERC721.adoc#ERC721-_safeTransfer-address-address-uint256-bytes- +:xref-ERC721-_approve-address-uint256-address-: xref:token/ERC721.adoc#ERC721-_approve-address-uint256-address- +:xref-ERC721-_approve-address-uint256-address-bool-: xref:token/ERC721.adoc#ERC721-_approve-address-uint256-address-bool- +:xref-ERC721-_setApprovalForAll-address-address-bool-: xref:token/ERC721.adoc#ERC721-_setApprovalForAll-address-address-bool- +:xref-ERC721-_requireOwned-uint256-: xref:token/ERC721.adoc#ERC721-_requireOwned-uint256- +:xref-IERC4906-MetadataUpdate-uint256-: xref:interfaces.adoc#IERC4906-MetadataUpdate-uint256- +:xref-IERC4906-BatchMetadataUpdate-uint256-uint256-: xref:interfaces.adoc#IERC4906-BatchMetadataUpdate-uint256-uint256- +:xref-IERC721-Transfer-address-address-uint256-: xref:token/ERC721.adoc#IERC721-Transfer-address-address-uint256- +:xref-IERC721-Approval-address-address-uint256-: xref:token/ERC721.adoc#IERC721-Approval-address-address-uint256- +:xref-IERC721-ApprovalForAll-address-address-bool-: xref:token/ERC721.adoc#IERC721-ApprovalForAll-address-address-bool- +:xref-IERC721Errors-ERC721InvalidOwner-address-: xref:interfaces.adoc#IERC721Errors-ERC721InvalidOwner-address- +:xref-IERC721Errors-ERC721NonexistentToken-uint256-: xref:interfaces.adoc#IERC721Errors-ERC721NonexistentToken-uint256- +:xref-IERC721Errors-ERC721IncorrectOwner-address-uint256-address-: xref:interfaces.adoc#IERC721Errors-ERC721IncorrectOwner-address-uint256-address- +:xref-IERC721Errors-ERC721InvalidSender-address-: xref:interfaces.adoc#IERC721Errors-ERC721InvalidSender-address- +:xref-IERC721Errors-ERC721InvalidReceiver-address-: xref:interfaces.adoc#IERC721Errors-ERC721InvalidReceiver-address- +:xref-IERC721Errors-ERC721InsufficientApproval-address-uint256-: xref:interfaces.adoc#IERC721Errors-ERC721InsufficientApproval-address-uint256- +:xref-IERC721Errors-ERC721InvalidApprover-address-: xref:interfaces.adoc#IERC721Errors-ERC721InvalidApprover-address- +:xref-IERC721Errors-ERC721InvalidOperator-address-: xref:interfaces.adoc#IERC721Errors-ERC721InvalidOperator-address- +:IERC165-supportsInterface: pass:normal[xref:utils.adoc#IERC165-supportsInterface-bytes4-[`IERC165.supportsInterface`]] +:IERC721Metadata-tokenURI: pass:normal[xref:token/ERC721.adoc#IERC721Metadata-tokenURI-uint256-[`IERC721Metadata.tokenURI`]] +:Votes: pass:normal[xref:governance.adoc#Votes[`Votes`]] +:xref-ERC721Votes-_update-address-uint256-address-: xref:token/ERC721.adoc#ERC721Votes-_update-address-uint256-address- +:xref-ERC721Votes-_getVotingUnits-address-: xref:token/ERC721.adoc#ERC721Votes-_getVotingUnits-address- +:xref-ERC721Votes-_increaseBalance-address-uint128-: xref:token/ERC721.adoc#ERC721Votes-_increaseBalance-address-uint128- +:xref-Votes-clock--: xref:governance.adoc#Votes-clock-- +:xref-Votes-CLOCK_MODE--: xref:governance.adoc#Votes-CLOCK_MODE-- +:xref-Votes-getVotes-address-: xref:governance.adoc#Votes-getVotes-address- +:xref-Votes-getPastVotes-address-uint256-: xref:governance.adoc#Votes-getPastVotes-address-uint256- +:xref-Votes-getPastTotalSupply-uint256-: xref:governance.adoc#Votes-getPastTotalSupply-uint256- +:xref-Votes-_getTotalSupply--: xref:governance.adoc#Votes-_getTotalSupply-- +:xref-Votes-delegates-address-: xref:governance.adoc#Votes-delegates-address- +:xref-Votes-delegate-address-: xref:governance.adoc#Votes-delegate-address- +:xref-Votes-delegateBySig-address-uint256-uint256-uint8-bytes32-bytes32-: xref:governance.adoc#Votes-delegateBySig-address-uint256-uint256-uint8-bytes32-bytes32- +:xref-Votes-_delegate-address-address-: xref:governance.adoc#Votes-_delegate-address-address- +:xref-Votes-_transferVotingUnits-address-address-uint256-: xref:governance.adoc#Votes-_transferVotingUnits-address-address-uint256- +:xref-Votes-_numCheckpoints-address-: xref:governance.adoc#Votes-_numCheckpoints-address- +:xref-Votes-_checkpoints-address-uint32-: xref:governance.adoc#Votes-_checkpoints-address-uint32- +:xref-Nonces-nonces-address-: xref:utils.adoc#Nonces-nonces-address- +:xref-Nonces-_useNonce-address-: xref:utils.adoc#Nonces-_useNonce-address- +:xref-Nonces-_useCheckedNonce-address-uint256-: xref:utils.adoc#Nonces-_useCheckedNonce-address-uint256- +:xref-EIP712-_domainSeparatorV4--: xref:utils.adoc#EIP712-_domainSeparatorV4-- +:xref-EIP712-_hashTypedDataV4-bytes32-: xref:utils.adoc#EIP712-_hashTypedDataV4-bytes32- +:xref-EIP712-eip712Domain--: xref:utils.adoc#EIP712-eip712Domain-- +:xref-EIP712-_EIP712Name--: xref:utils.adoc#EIP712-_EIP712Name-- +:xref-EIP712-_EIP712Version--: xref:utils.adoc#EIP712-_EIP712Version-- +:xref-ERC721-supportsInterface-bytes4-: xref:token/ERC721.adoc#ERC721-supportsInterface-bytes4- +:xref-ERC721-balanceOf-address-: xref:token/ERC721.adoc#ERC721-balanceOf-address- +:xref-ERC721-ownerOf-uint256-: xref:token/ERC721.adoc#ERC721-ownerOf-uint256- +:xref-ERC721-name--: xref:token/ERC721.adoc#ERC721-name-- +:xref-ERC721-symbol--: xref:token/ERC721.adoc#ERC721-symbol-- +:xref-ERC721-tokenURI-uint256-: xref:token/ERC721.adoc#ERC721-tokenURI-uint256- +:xref-ERC721-_baseURI--: xref:token/ERC721.adoc#ERC721-_baseURI-- +:xref-ERC721-approve-address-uint256-: xref:token/ERC721.adoc#ERC721-approve-address-uint256- +:xref-ERC721-getApproved-uint256-: xref:token/ERC721.adoc#ERC721-getApproved-uint256- +:xref-ERC721-setApprovalForAll-address-bool-: xref:token/ERC721.adoc#ERC721-setApprovalForAll-address-bool- +:xref-ERC721-isApprovedForAll-address-address-: xref:token/ERC721.adoc#ERC721-isApprovedForAll-address-address- +:xref-ERC721-transferFrom-address-address-uint256-: xref:token/ERC721.adoc#ERC721-transferFrom-address-address-uint256- +:xref-ERC721-safeTransferFrom-address-address-uint256-: xref:token/ERC721.adoc#ERC721-safeTransferFrom-address-address-uint256- +:xref-ERC721-safeTransferFrom-address-address-uint256-bytes-: xref:token/ERC721.adoc#ERC721-safeTransferFrom-address-address-uint256-bytes- +:xref-ERC721-_ownerOf-uint256-: xref:token/ERC721.adoc#ERC721-_ownerOf-uint256- +:xref-ERC721-_getApproved-uint256-: xref:token/ERC721.adoc#ERC721-_getApproved-uint256- +:xref-ERC721-_isAuthorized-address-address-uint256-: xref:token/ERC721.adoc#ERC721-_isAuthorized-address-address-uint256- +:xref-ERC721-_checkAuthorized-address-address-uint256-: xref:token/ERC721.adoc#ERC721-_checkAuthorized-address-address-uint256- +:xref-ERC721-_mint-address-uint256-: xref:token/ERC721.adoc#ERC721-_mint-address-uint256- +:xref-ERC721-_safeMint-address-uint256-: xref:token/ERC721.adoc#ERC721-_safeMint-address-uint256- +:xref-ERC721-_safeMint-address-uint256-bytes-: xref:token/ERC721.adoc#ERC721-_safeMint-address-uint256-bytes- +:xref-ERC721-_burn-uint256-: xref:token/ERC721.adoc#ERC721-_burn-uint256- +:xref-ERC721-_transfer-address-address-uint256-: xref:token/ERC721.adoc#ERC721-_transfer-address-address-uint256- +:xref-ERC721-_safeTransfer-address-address-uint256-: xref:token/ERC721.adoc#ERC721-_safeTransfer-address-address-uint256- +:xref-ERC721-_safeTransfer-address-address-uint256-bytes-: xref:token/ERC721.adoc#ERC721-_safeTransfer-address-address-uint256-bytes- +:xref-ERC721-_approve-address-uint256-address-: xref:token/ERC721.adoc#ERC721-_approve-address-uint256-address- +:xref-ERC721-_approve-address-uint256-address-bool-: xref:token/ERC721.adoc#ERC721-_approve-address-uint256-address-bool- +:xref-ERC721-_setApprovalForAll-address-address-bool-: xref:token/ERC721.adoc#ERC721-_setApprovalForAll-address-address-bool- +:xref-ERC721-_requireOwned-uint256-: xref:token/ERC721.adoc#ERC721-_requireOwned-uint256- +:xref-IVotes-DelegateChanged-address-address-address-: xref:governance.adoc#IVotes-DelegateChanged-address-address-address- +:xref-IVotes-DelegateVotesChanged-address-uint256-uint256-: xref:governance.adoc#IVotes-DelegateVotesChanged-address-uint256-uint256- +:xref-IERC5267-EIP712DomainChanged--: xref:interfaces.adoc#IERC5267-EIP712DomainChanged-- +:xref-IERC721-Transfer-address-address-uint256-: xref:token/ERC721.adoc#IERC721-Transfer-address-address-uint256- +:xref-IERC721-Approval-address-address-uint256-: xref:token/ERC721.adoc#IERC721-Approval-address-address-uint256- +:xref-IERC721-ApprovalForAll-address-address-bool-: xref:token/ERC721.adoc#IERC721-ApprovalForAll-address-address-bool- +:xref-Votes-ERC6372InconsistentClock--: xref:governance.adoc#Votes-ERC6372InconsistentClock-- +:xref-Votes-ERC5805FutureLookup-uint256-uint48-: xref:governance.adoc#Votes-ERC5805FutureLookup-uint256-uint48- +:xref-IVotes-VotesExpiredSignature-uint256-: xref:governance.adoc#IVotes-VotesExpiredSignature-uint256- +:xref-Nonces-InvalidAccountNonce-address-uint256-: xref:utils.adoc#Nonces-InvalidAccountNonce-address-uint256- +:xref-IERC721Errors-ERC721InvalidOwner-address-: xref:interfaces.adoc#IERC721Errors-ERC721InvalidOwner-address- +:xref-IERC721Errors-ERC721NonexistentToken-uint256-: xref:interfaces.adoc#IERC721Errors-ERC721NonexistentToken-uint256- +:xref-IERC721Errors-ERC721IncorrectOwner-address-uint256-address-: xref:interfaces.adoc#IERC721Errors-ERC721IncorrectOwner-address-uint256-address- +:xref-IERC721Errors-ERC721InvalidSender-address-: xref:interfaces.adoc#IERC721Errors-ERC721InvalidSender-address- +:xref-IERC721Errors-ERC721InvalidReceiver-address-: xref:interfaces.adoc#IERC721Errors-ERC721InvalidReceiver-address- +:xref-IERC721Errors-ERC721InsufficientApproval-address-uint256-: xref:interfaces.adoc#IERC721Errors-ERC721InsufficientApproval-address-uint256- +:xref-IERC721Errors-ERC721InvalidApprover-address-: xref:interfaces.adoc#IERC721Errors-ERC721InvalidApprover-address- +:xref-IERC721Errors-ERC721InvalidOperator-address-: xref:interfaces.adoc#IERC721Errors-ERC721InvalidOperator-address- +:ERC721-_update: pass:normal[xref:token/ERC721.adoc#ERC721-_update-address-uint256-address-[`ERC721._update`]] +:IVotes-DelegateVotesChanged: pass:normal[xref:governance.adoc#IVotes-DelegateVotesChanged-address-uint256-uint256-[`IVotes.DelegateVotesChanged`]] +:ERC721-_increaseBalance: pass:normal[xref:token/ERC721.adoc#ERC721-_increaseBalance-address-uint128-[`ERC721._increaseBalance`]] +:ERC2981-_setDefaultRoyalty: pass:normal[xref:token/common.adoc#ERC2981-_setDefaultRoyalty-address-uint96-[`ERC2981._setDefaultRoyalty`]] +:ERC2981-_setTokenRoyalty: pass:normal[xref:token/common.adoc#ERC2981-_setTokenRoyalty-uint256-address-uint96-[`ERC2981._setTokenRoyalty`]] +:xref-ERC721Royalty-supportsInterface-bytes4-: xref:token/ERC721.adoc#ERC721Royalty-supportsInterface-bytes4- +:xref-ERC721-balanceOf-address-: xref:token/ERC721.adoc#ERC721-balanceOf-address- +:xref-ERC721-ownerOf-uint256-: xref:token/ERC721.adoc#ERC721-ownerOf-uint256- +:xref-ERC721-name--: xref:token/ERC721.adoc#ERC721-name-- +:xref-ERC721-symbol--: xref:token/ERC721.adoc#ERC721-symbol-- +:xref-ERC721-tokenURI-uint256-: xref:token/ERC721.adoc#ERC721-tokenURI-uint256- +:xref-ERC721-_baseURI--: xref:token/ERC721.adoc#ERC721-_baseURI-- +:xref-ERC721-approve-address-uint256-: xref:token/ERC721.adoc#ERC721-approve-address-uint256- +:xref-ERC721-getApproved-uint256-: xref:token/ERC721.adoc#ERC721-getApproved-uint256- +:xref-ERC721-setApprovalForAll-address-bool-: xref:token/ERC721.adoc#ERC721-setApprovalForAll-address-bool- +:xref-ERC721-isApprovedForAll-address-address-: xref:token/ERC721.adoc#ERC721-isApprovedForAll-address-address- +:xref-ERC721-transferFrom-address-address-uint256-: xref:token/ERC721.adoc#ERC721-transferFrom-address-address-uint256- +:xref-ERC721-safeTransferFrom-address-address-uint256-: xref:token/ERC721.adoc#ERC721-safeTransferFrom-address-address-uint256- +:xref-ERC721-safeTransferFrom-address-address-uint256-bytes-: xref:token/ERC721.adoc#ERC721-safeTransferFrom-address-address-uint256-bytes- +:xref-ERC721-_ownerOf-uint256-: xref:token/ERC721.adoc#ERC721-_ownerOf-uint256- +:xref-ERC721-_getApproved-uint256-: xref:token/ERC721.adoc#ERC721-_getApproved-uint256- +:xref-ERC721-_isAuthorized-address-address-uint256-: xref:token/ERC721.adoc#ERC721-_isAuthorized-address-address-uint256- +:xref-ERC721-_checkAuthorized-address-address-uint256-: xref:token/ERC721.adoc#ERC721-_checkAuthorized-address-address-uint256- +:xref-ERC721-_increaseBalance-address-uint128-: xref:token/ERC721.adoc#ERC721-_increaseBalance-address-uint128- +:xref-ERC721-_update-address-uint256-address-: xref:token/ERC721.adoc#ERC721-_update-address-uint256-address- +:xref-ERC721-_mint-address-uint256-: xref:token/ERC721.adoc#ERC721-_mint-address-uint256- +:xref-ERC721-_safeMint-address-uint256-: xref:token/ERC721.adoc#ERC721-_safeMint-address-uint256- +:xref-ERC721-_safeMint-address-uint256-bytes-: xref:token/ERC721.adoc#ERC721-_safeMint-address-uint256-bytes- +:xref-ERC721-_burn-uint256-: xref:token/ERC721.adoc#ERC721-_burn-uint256- +:xref-ERC721-_transfer-address-address-uint256-: xref:token/ERC721.adoc#ERC721-_transfer-address-address-uint256- +:xref-ERC721-_safeTransfer-address-address-uint256-: xref:token/ERC721.adoc#ERC721-_safeTransfer-address-address-uint256- +:xref-ERC721-_safeTransfer-address-address-uint256-bytes-: xref:token/ERC721.adoc#ERC721-_safeTransfer-address-address-uint256-bytes- +:xref-ERC721-_approve-address-uint256-address-: xref:token/ERC721.adoc#ERC721-_approve-address-uint256-address- +:xref-ERC721-_approve-address-uint256-address-bool-: xref:token/ERC721.adoc#ERC721-_approve-address-uint256-address-bool- +:xref-ERC721-_setApprovalForAll-address-address-bool-: xref:token/ERC721.adoc#ERC721-_setApprovalForAll-address-address-bool- +:xref-ERC721-_requireOwned-uint256-: xref:token/ERC721.adoc#ERC721-_requireOwned-uint256- +:xref-ERC2981-royaltyInfo-uint256-uint256-: xref:token/common.adoc#ERC2981-royaltyInfo-uint256-uint256- +:xref-ERC2981-_feeDenominator--: xref:token/common.adoc#ERC2981-_feeDenominator-- +:xref-ERC2981-_setDefaultRoyalty-address-uint96-: xref:token/common.adoc#ERC2981-_setDefaultRoyalty-address-uint96- +:xref-ERC2981-_deleteDefaultRoyalty--: xref:token/common.adoc#ERC2981-_deleteDefaultRoyalty-- +:xref-ERC2981-_setTokenRoyalty-uint256-address-uint96-: xref:token/common.adoc#ERC2981-_setTokenRoyalty-uint256-address-uint96- +:xref-ERC2981-_resetTokenRoyalty-uint256-: xref:token/common.adoc#ERC2981-_resetTokenRoyalty-uint256- +:xref-IERC721-Transfer-address-address-uint256-: xref:token/ERC721.adoc#IERC721-Transfer-address-address-uint256- +:xref-IERC721-Approval-address-address-uint256-: xref:token/ERC721.adoc#IERC721-Approval-address-address-uint256- +:xref-IERC721-ApprovalForAll-address-address-bool-: xref:token/ERC721.adoc#IERC721-ApprovalForAll-address-address-bool- +:xref-IERC721Errors-ERC721InvalidOwner-address-: xref:interfaces.adoc#IERC721Errors-ERC721InvalidOwner-address- +:xref-IERC721Errors-ERC721NonexistentToken-uint256-: xref:interfaces.adoc#IERC721Errors-ERC721NonexistentToken-uint256- +:xref-IERC721Errors-ERC721IncorrectOwner-address-uint256-address-: xref:interfaces.adoc#IERC721Errors-ERC721IncorrectOwner-address-uint256-address- +:xref-IERC721Errors-ERC721InvalidSender-address-: xref:interfaces.adoc#IERC721Errors-ERC721InvalidSender-address- +:xref-IERC721Errors-ERC721InvalidReceiver-address-: xref:interfaces.adoc#IERC721Errors-ERC721InvalidReceiver-address- +:xref-IERC721Errors-ERC721InsufficientApproval-address-uint256-: xref:interfaces.adoc#IERC721Errors-ERC721InsufficientApproval-address-uint256- +:xref-IERC721Errors-ERC721InvalidApprover-address-: xref:interfaces.adoc#IERC721Errors-ERC721InvalidApprover-address- +:xref-IERC721Errors-ERC721InvalidOperator-address-: xref:interfaces.adoc#IERC721Errors-ERC721InvalidOperator-address- +:xref-ERC2981-ERC2981InvalidDefaultRoyalty-uint256-uint256-: xref:token/common.adoc#ERC2981-ERC2981InvalidDefaultRoyalty-uint256-uint256- +:xref-ERC2981-ERC2981InvalidDefaultRoyaltyReceiver-address-: xref:token/common.adoc#ERC2981-ERC2981InvalidDefaultRoyaltyReceiver-address- +:xref-ERC2981-ERC2981InvalidTokenRoyalty-uint256-uint256-uint256-: xref:token/common.adoc#ERC2981-ERC2981InvalidTokenRoyalty-uint256-uint256-uint256- +:xref-ERC2981-ERC2981InvalidTokenRoyaltyReceiver-uint256-address-: xref:token/common.adoc#ERC2981-ERC2981InvalidTokenRoyaltyReceiver-uint256-address- +:IERC165-supportsInterface: pass:normal[xref:utils.adoc#IERC165-supportsInterface-bytes4-[`IERC165.supportsInterface`]] +:ERC721Votes: pass:normal[xref:token/ERC721.adoc#ERC721Votes[`ERC721Votes`]] +:xref-ERC721Wrapper-constructor-contract-IERC721-: xref:token/ERC721.adoc#ERC721Wrapper-constructor-contract-IERC721- +:xref-ERC721Wrapper-depositFor-address-uint256---: xref:token/ERC721.adoc#ERC721Wrapper-depositFor-address-uint256--- +:xref-ERC721Wrapper-withdrawTo-address-uint256---: xref:token/ERC721.adoc#ERC721Wrapper-withdrawTo-address-uint256--- +:xref-ERC721Wrapper-onERC721Received-address-address-uint256-bytes-: xref:token/ERC721.adoc#ERC721Wrapper-onERC721Received-address-address-uint256-bytes- +:xref-ERC721Wrapper-_recover-address-uint256-: xref:token/ERC721.adoc#ERC721Wrapper-_recover-address-uint256- +:xref-ERC721Wrapper-underlying--: xref:token/ERC721.adoc#ERC721Wrapper-underlying-- +:xref-ERC721-supportsInterface-bytes4-: xref:token/ERC721.adoc#ERC721-supportsInterface-bytes4- +:xref-ERC721-balanceOf-address-: xref:token/ERC721.adoc#ERC721-balanceOf-address- +:xref-ERC721-ownerOf-uint256-: xref:token/ERC721.adoc#ERC721-ownerOf-uint256- +:xref-ERC721-name--: xref:token/ERC721.adoc#ERC721-name-- +:xref-ERC721-symbol--: xref:token/ERC721.adoc#ERC721-symbol-- +:xref-ERC721-tokenURI-uint256-: xref:token/ERC721.adoc#ERC721-tokenURI-uint256- +:xref-ERC721-_baseURI--: xref:token/ERC721.adoc#ERC721-_baseURI-- +:xref-ERC721-approve-address-uint256-: xref:token/ERC721.adoc#ERC721-approve-address-uint256- +:xref-ERC721-getApproved-uint256-: xref:token/ERC721.adoc#ERC721-getApproved-uint256- +:xref-ERC721-setApprovalForAll-address-bool-: xref:token/ERC721.adoc#ERC721-setApprovalForAll-address-bool- +:xref-ERC721-isApprovedForAll-address-address-: xref:token/ERC721.adoc#ERC721-isApprovedForAll-address-address- +:xref-ERC721-transferFrom-address-address-uint256-: xref:token/ERC721.adoc#ERC721-transferFrom-address-address-uint256- +:xref-ERC721-safeTransferFrom-address-address-uint256-: xref:token/ERC721.adoc#ERC721-safeTransferFrom-address-address-uint256- +:xref-ERC721-safeTransferFrom-address-address-uint256-bytes-: xref:token/ERC721.adoc#ERC721-safeTransferFrom-address-address-uint256-bytes- +:xref-ERC721-_ownerOf-uint256-: xref:token/ERC721.adoc#ERC721-_ownerOf-uint256- +:xref-ERC721-_getApproved-uint256-: xref:token/ERC721.adoc#ERC721-_getApproved-uint256- +:xref-ERC721-_isAuthorized-address-address-uint256-: xref:token/ERC721.adoc#ERC721-_isAuthorized-address-address-uint256- +:xref-ERC721-_checkAuthorized-address-address-uint256-: xref:token/ERC721.adoc#ERC721-_checkAuthorized-address-address-uint256- +:xref-ERC721-_increaseBalance-address-uint128-: xref:token/ERC721.adoc#ERC721-_increaseBalance-address-uint128- +:xref-ERC721-_update-address-uint256-address-: xref:token/ERC721.adoc#ERC721-_update-address-uint256-address- +:xref-ERC721-_mint-address-uint256-: xref:token/ERC721.adoc#ERC721-_mint-address-uint256- +:xref-ERC721-_safeMint-address-uint256-: xref:token/ERC721.adoc#ERC721-_safeMint-address-uint256- +:xref-ERC721-_safeMint-address-uint256-bytes-: xref:token/ERC721.adoc#ERC721-_safeMint-address-uint256-bytes- +:xref-ERC721-_burn-uint256-: xref:token/ERC721.adoc#ERC721-_burn-uint256- +:xref-ERC721-_transfer-address-address-uint256-: xref:token/ERC721.adoc#ERC721-_transfer-address-address-uint256- +:xref-ERC721-_safeTransfer-address-address-uint256-: xref:token/ERC721.adoc#ERC721-_safeTransfer-address-address-uint256- +:xref-ERC721-_safeTransfer-address-address-uint256-bytes-: xref:token/ERC721.adoc#ERC721-_safeTransfer-address-address-uint256-bytes- +:xref-ERC721-_approve-address-uint256-address-: xref:token/ERC721.adoc#ERC721-_approve-address-uint256-address- +:xref-ERC721-_approve-address-uint256-address-bool-: xref:token/ERC721.adoc#ERC721-_approve-address-uint256-address-bool- +:xref-ERC721-_setApprovalForAll-address-address-bool-: xref:token/ERC721.adoc#ERC721-_setApprovalForAll-address-address-bool- +:xref-ERC721-_requireOwned-uint256-: xref:token/ERC721.adoc#ERC721-_requireOwned-uint256- +:xref-IERC721-Transfer-address-address-uint256-: xref:token/ERC721.adoc#IERC721-Transfer-address-address-uint256- +:xref-IERC721-Approval-address-address-uint256-: xref:token/ERC721.adoc#IERC721-Approval-address-address-uint256- +:xref-IERC721-ApprovalForAll-address-address-bool-: xref:token/ERC721.adoc#IERC721-ApprovalForAll-address-address-bool- +:xref-ERC721Wrapper-ERC721UnsupportedToken-address-: xref:token/ERC721.adoc#ERC721Wrapper-ERC721UnsupportedToken-address- +:xref-IERC721Errors-ERC721InvalidOwner-address-: xref:interfaces.adoc#IERC721Errors-ERC721InvalidOwner-address- +:xref-IERC721Errors-ERC721NonexistentToken-uint256-: xref:interfaces.adoc#IERC721Errors-ERC721NonexistentToken-uint256- +:xref-IERC721Errors-ERC721IncorrectOwner-address-uint256-address-: xref:interfaces.adoc#IERC721Errors-ERC721IncorrectOwner-address-uint256-address- +:xref-IERC721Errors-ERC721InvalidSender-address-: xref:interfaces.adoc#IERC721Errors-ERC721InvalidSender-address- +:xref-IERC721Errors-ERC721InvalidReceiver-address-: xref:interfaces.adoc#IERC721Errors-ERC721InvalidReceiver-address- +:xref-IERC721Errors-ERC721InsufficientApproval-address-uint256-: xref:interfaces.adoc#IERC721Errors-ERC721InsufficientApproval-address-uint256- +:xref-IERC721Errors-ERC721InvalidApprover-address-: xref:interfaces.adoc#IERC721Errors-ERC721InvalidApprover-address- +:xref-IERC721Errors-ERC721InvalidOperator-address-: xref:interfaces.adoc#IERC721Errors-ERC721InvalidOperator-address- +:IERC721Receiver-onERC721Received: pass:normal[xref:token/ERC721.adoc#IERC721Receiver-onERC721Received-address-address-uint256-bytes-[`IERC721Receiver.onERC721Received`]] +:IERC721-transferFrom: pass:normal[xref:token/ERC721.adoc#IERC721-transferFrom-address-address-uint256-[`IERC721.transferFrom`]] +:ERC721Wrapper-_recover: pass:normal[xref:token/ERC721.adoc#ERC721Wrapper-_recover-address-uint256-[`ERC721Wrapper._recover`]] +:IERC721Receiver: pass:normal[xref:token/ERC721.adoc#IERC721Receiver[`IERC721Receiver`]] +:IERC721-safeTransferFrom: pass:normal[xref:token/ERC721.adoc#IERC721-safeTransferFrom-address-address-uint256-[`IERC721.safeTransferFrom`]] +:IERC721-approve: pass:normal[xref:token/ERC721.adoc#IERC721-approve-address-uint256-[`IERC721.approve`]] +:IERC721-setApprovalForAll: pass:normal[xref:token/ERC721.adoc#IERC721-setApprovalForAll-address-bool-[`IERC721.setApprovalForAll`]] +:xref-ERC721Holder-onERC721Received-address-address-uint256-bytes-: xref:token/ERC721.adoc#ERC721Holder-onERC721Received-address-address-uint256-bytes- +:IERC721Receiver-onERC721Received: pass:normal[xref:token/ERC721.adoc#IERC721Receiver-onERC721Received-address-address-uint256-bytes-[`IERC721Receiver.onERC721Received`]] += ERC 721 + +[.readme-notice] +NOTE: This document is better viewed at https://docs.openzeppelin.com/contracts/api/token/erc721 + +This set of interfaces, contracts, and utilities are all related to the https://eips.ethereum.org/EIPS/eip-721[ERC721 Non-Fungible Token Standard]. + +TIP: For a walk through on how to create an ERC721 token read our xref:ROOT:erc721.adoc[ERC721 guide]. + +The EIP specifies four interfaces: + +* {IERC721}: Core functionality required in all compliant implementation. +* {IERC721Metadata}: Optional extension that adds name, symbol, and token URI, almost always included. +* {IERC721Enumerable}: Optional extension that allows enumerating the tokens on chain, often not included since it requires large gas overhead. +* {IERC721Receiver}: An interface that must be implemented by contracts if they want to accept tokens through `safeTransferFrom`. + +OpenZeppelin Contracts provides implementations of all four interfaces: + +* {ERC721}: The core and metadata extensions, with a base URI mechanism. +* {ERC721Enumerable}: The enumerable extension. +* {ERC721Holder}: A bare bones implementation of the receiver interface. + +Additionally there are a few of other extensions: + +* {ERC721Consecutive}: An implementation of https://eips.ethereum.org/EIPS/eip-2309[ERC2309] for minting batchs of tokens during construction, in accordance with ERC721. +* {ERC721URIStorage}: A more flexible but more expensive way of storing metadata. +* {ERC721Votes}: Support for voting and vote delegation. +* {ERC721Royalty}: A way to signal royalty information following ERC2981. +* {ERC721Pausable}: A primitive to pause contract operation. +* {ERC721Burnable}: A way for token holders to burn their own tokens. +* {ERC721Wrapper}: Wrapper to create an ERC721 backed by another ERC721, with deposit and withdraw methods. Useful in conjunction with {ERC721Votes}. + +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 + +:Transfer: pass:normal[xref:#IERC721-Transfer-address-address-uint256-[`++Transfer++`]] +:Approval: pass:normal[xref:#IERC721-Approval-address-address-uint256-[`++Approval++`]] +:ApprovalForAll: pass:normal[xref:#IERC721-ApprovalForAll-address-address-bool-[`++ApprovalForAll++`]] +:balanceOf: pass:normal[xref:#IERC721-balanceOf-address-[`++balanceOf++`]] +:ownerOf: pass:normal[xref:#IERC721-ownerOf-uint256-[`++ownerOf++`]] +:safeTransferFrom: pass:normal[xref:#IERC721-safeTransferFrom-address-address-uint256-bytes-[`++safeTransferFrom++`]] +:safeTransferFrom: pass:normal[xref:#IERC721-safeTransferFrom-address-address-uint256-[`++safeTransferFrom++`]] +:transferFrom: pass:normal[xref:#IERC721-transferFrom-address-address-uint256-[`++transferFrom++`]] +:approve: pass:normal[xref:#IERC721-approve-address-uint256-[`++approve++`]] +:setApprovalForAll: pass:normal[xref:#IERC721-setApprovalForAll-address-bool-[`++setApprovalForAll++`]] +:getApproved: pass:normal[xref:#IERC721-getApproved-uint256-[`++getApproved++`]] +:isApprovedForAll: pass:normal[xref:#IERC721-isApprovedForAll-address-address-[`++isApprovedForAll++`]] + +[.contract] +[[IERC721]] +=== `++IERC721++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/token/ERC721/IERC721.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; +``` + +Required interface of an ERC721 compliant contract. + +[.contract-index] +.Functions +-- +* {xref-IERC721-balanceOf-address-}[`++balanceOf(owner)++`] +* {xref-IERC721-ownerOf-uint256-}[`++ownerOf(tokenId)++`] +* {xref-IERC721-safeTransferFrom-address-address-uint256-bytes-}[`++safeTransferFrom(from, to, tokenId, data)++`] +* {xref-IERC721-safeTransferFrom-address-address-uint256-}[`++safeTransferFrom(from, to, tokenId)++`] +* {xref-IERC721-transferFrom-address-address-uint256-}[`++transferFrom(from, to, tokenId)++`] +* {xref-IERC721-approve-address-uint256-}[`++approve(to, tokenId)++`] +* {xref-IERC721-setApprovalForAll-address-bool-}[`++setApprovalForAll(operator, approved)++`] +* {xref-IERC721-getApproved-uint256-}[`++getApproved(tokenId)++`] +* {xref-IERC721-isApprovedForAll-address-address-}[`++isApprovedForAll(owner, operator)++`] + +[.contract-subindex-inherited] +.IERC165 +* {xref-IERC165-supportsInterface-bytes4-}[`++supportsInterface(interfaceId)++`] + +-- + +[.contract-index] +.Events +-- +* {xref-IERC721-Transfer-address-address-uint256-}[`++Transfer(from, to, tokenId)++`] +* {xref-IERC721-Approval-address-address-uint256-}[`++Approval(owner, approved, tokenId)++`] +* {xref-IERC721-ApprovalForAll-address-address-bool-}[`++ApprovalForAll(owner, operator, approved)++`] + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-item] +[[IERC721-balanceOf-address-]] +==== `[.contract-item-name]#++balanceOf++#++(address owner) β†’ uint256 balance++` [.item-kind]#external# + +Returns the number of tokens in ``owner``'s account. + +[.contract-item] +[[IERC721-ownerOf-uint256-]] +==== `[.contract-item-name]#++ownerOf++#++(uint256 tokenId) β†’ address owner++` [.item-kind]#external# + +Returns the owner of the `tokenId` token. + +Requirements: + +- `tokenId` must exist. + +[.contract-item] +[[IERC721-safeTransferFrom-address-address-uint256-bytes-]] +==== `[.contract-item-name]#++safeTransferFrom++#++(address from, address to, uint256 tokenId, bytes data)++` [.item-kind]#external# + +Safely transfers `tokenId` token from `from` to `to`. + +Requirements: + +- `from` cannot be the zero address. +- `to` cannot be the zero address. +- `tokenId` token must exist and be owned by `from`. +- If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}. +- If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon + a safe transfer. + +Emits a {Transfer} event. + +[.contract-item] +[[IERC721-safeTransferFrom-address-address-uint256-]] +==== `[.contract-item-name]#++safeTransferFrom++#++(address from, address to, uint256 tokenId)++` [.item-kind]#external# + +Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients +are aware of the ERC721 protocol to prevent tokens from being forever locked. + +Requirements: + +- `from` cannot be the zero address. +- `to` cannot be the zero address. +- `tokenId` token must exist and be owned by `from`. +- If the caller is not `from`, it must have been allowed to move this token by either {approve} or + {setApprovalForAll}. +- If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon + a safe transfer. + +Emits a {Transfer} event. + +[.contract-item] +[[IERC721-transferFrom-address-address-uint256-]] +==== `[.contract-item-name]#++transferFrom++#++(address from, address to, uint256 tokenId)++` [.item-kind]#external# + +Transfers `tokenId` token from `from` to `to`. + +WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC721 +or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must +understand this adds an external call which potentially creates a reentrancy vulnerability. + +Requirements: + +- `from` cannot be the zero address. +- `to` cannot be the zero address. +- `tokenId` token must be owned by `from`. +- If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}. + +Emits a {Transfer} event. + +[.contract-item] +[[IERC721-approve-address-uint256-]] +==== `[.contract-item-name]#++approve++#++(address to, uint256 tokenId)++` [.item-kind]#external# + +Gives permission to `to` to transfer `tokenId` token to another account. +The approval is cleared when the token is transferred. + +Only a single account can be approved at a time, so approving the zero address clears previous approvals. + +Requirements: + +- The caller must own the token or be an approved operator. +- `tokenId` must exist. + +Emits an {Approval} event. + +[.contract-item] +[[IERC721-setApprovalForAll-address-bool-]] +==== `[.contract-item-name]#++setApprovalForAll++#++(address operator, bool approved)++` [.item-kind]#external# + +Approve or remove `operator` as an operator for the caller. +Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller. + +Requirements: + +- The `operator` cannot be the address zero. + +Emits an {ApprovalForAll} event. + +[.contract-item] +[[IERC721-getApproved-uint256-]] +==== `[.contract-item-name]#++getApproved++#++(uint256 tokenId) β†’ address operator++` [.item-kind]#external# + +Returns the account approved for `tokenId` token. + +Requirements: + +- `tokenId` must exist. + +[.contract-item] +[[IERC721-isApprovedForAll-address-address-]] +==== `[.contract-item-name]#++isApprovedForAll++#++(address owner, address operator) β†’ bool++` [.item-kind]#external# + +Returns if the `operator` is allowed to manage all of the assets of `owner`. + +See {setApprovalForAll} + +[.contract-item] +[[IERC721-Transfer-address-address-uint256-]] +==== `[.contract-item-name]#++Transfer++#++(address indexed from, address indexed to, uint256 indexed tokenId)++` [.item-kind]#event# + +Emitted when `tokenId` token is transferred from `from` to `to`. + +[.contract-item] +[[IERC721-Approval-address-address-uint256-]] +==== `[.contract-item-name]#++Approval++#++(address indexed owner, address indexed approved, uint256 indexed tokenId)++` [.item-kind]#event# + +Emitted when `owner` enables `approved` to manage the `tokenId` token. + +[.contract-item] +[[IERC721-ApprovalForAll-address-address-bool-]] +==== `[.contract-item-name]#++ApprovalForAll++#++(address indexed owner, address indexed operator, bool approved)++` [.item-kind]#event# + +Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets. + +:name: pass:normal[xref:#IERC721Metadata-name--[`++name++`]] +:symbol: pass:normal[xref:#IERC721Metadata-symbol--[`++symbol++`]] +:tokenURI: pass:normal[xref:#IERC721Metadata-tokenURI-uint256-[`++tokenURI++`]] + +[.contract] +[[IERC721Metadata]] +=== `++IERC721Metadata++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/token/ERC721/extensions/IERC721Metadata.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol"; +``` + +See https://eips.ethereum.org/EIPS/eip-721 + +[.contract-index] +.Functions +-- +* {xref-IERC721Metadata-name--}[`++name()++`] +* {xref-IERC721Metadata-symbol--}[`++symbol()++`] +* {xref-IERC721Metadata-tokenURI-uint256-}[`++tokenURI(tokenId)++`] + +[.contract-subindex-inherited] +.IERC721 +* {xref-IERC721-balanceOf-address-}[`++balanceOf(owner)++`] +* {xref-IERC721-ownerOf-uint256-}[`++ownerOf(tokenId)++`] +* {xref-IERC721-safeTransferFrom-address-address-uint256-bytes-}[`++safeTransferFrom(from, to, tokenId, data)++`] +* {xref-IERC721-safeTransferFrom-address-address-uint256-}[`++safeTransferFrom(from, to, tokenId)++`] +* {xref-IERC721-transferFrom-address-address-uint256-}[`++transferFrom(from, to, tokenId)++`] +* {xref-IERC721-approve-address-uint256-}[`++approve(to, tokenId)++`] +* {xref-IERC721-setApprovalForAll-address-bool-}[`++setApprovalForAll(operator, approved)++`] +* {xref-IERC721-getApproved-uint256-}[`++getApproved(tokenId)++`] +* {xref-IERC721-isApprovedForAll-address-address-}[`++isApprovedForAll(owner, operator)++`] + +[.contract-subindex-inherited] +.IERC165 +* {xref-IERC165-supportsInterface-bytes4-}[`++supportsInterface(interfaceId)++`] + +-- + +[.contract-index] +.Events +-- + +[.contract-subindex-inherited] +.IERC721 +* {xref-IERC721-Transfer-address-address-uint256-}[`++Transfer(from, to, tokenId)++`] +* {xref-IERC721-Approval-address-address-uint256-}[`++Approval(owner, approved, tokenId)++`] +* {xref-IERC721-ApprovalForAll-address-address-bool-}[`++ApprovalForAll(owner, operator, approved)++`] + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-item] +[[IERC721Metadata-name--]] +==== `[.contract-item-name]#++name++#++() β†’ string++` [.item-kind]#external# + +Returns the token collection name. + +[.contract-item] +[[IERC721Metadata-symbol--]] +==== `[.contract-item-name]#++symbol++#++() β†’ string++` [.item-kind]#external# + +Returns the token collection symbol. + +[.contract-item] +[[IERC721Metadata-tokenURI-uint256-]] +==== `[.contract-item-name]#++tokenURI++#++(uint256 tokenId) β†’ string++` [.item-kind]#external# + +Returns the Uniform Resource Identifier (URI) for `tokenId` token. + +:totalSupply: pass:normal[xref:#IERC721Enumerable-totalSupply--[`++totalSupply++`]] +:tokenOfOwnerByIndex: pass:normal[xref:#IERC721Enumerable-tokenOfOwnerByIndex-address-uint256-[`++tokenOfOwnerByIndex++`]] +:tokenByIndex: pass:normal[xref:#IERC721Enumerable-tokenByIndex-uint256-[`++tokenByIndex++`]] + +[.contract] +[[IERC721Enumerable]] +=== `++IERC721Enumerable++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/token/ERC721/extensions/IERC721Enumerable.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/token/ERC721/extensions/IERC721Enumerable.sol"; +``` + +See https://eips.ethereum.org/EIPS/eip-721 + +[.contract-index] +.Functions +-- +* {xref-IERC721Enumerable-totalSupply--}[`++totalSupply()++`] +* {xref-IERC721Enumerable-tokenOfOwnerByIndex-address-uint256-}[`++tokenOfOwnerByIndex(owner, index)++`] +* {xref-IERC721Enumerable-tokenByIndex-uint256-}[`++tokenByIndex(index)++`] + +[.contract-subindex-inherited] +.IERC721 +* {xref-IERC721-balanceOf-address-}[`++balanceOf(owner)++`] +* {xref-IERC721-ownerOf-uint256-}[`++ownerOf(tokenId)++`] +* {xref-IERC721-safeTransferFrom-address-address-uint256-bytes-}[`++safeTransferFrom(from, to, tokenId, data)++`] +* {xref-IERC721-safeTransferFrom-address-address-uint256-}[`++safeTransferFrom(from, to, tokenId)++`] +* {xref-IERC721-transferFrom-address-address-uint256-}[`++transferFrom(from, to, tokenId)++`] +* {xref-IERC721-approve-address-uint256-}[`++approve(to, tokenId)++`] +* {xref-IERC721-setApprovalForAll-address-bool-}[`++setApprovalForAll(operator, approved)++`] +* {xref-IERC721-getApproved-uint256-}[`++getApproved(tokenId)++`] +* {xref-IERC721-isApprovedForAll-address-address-}[`++isApprovedForAll(owner, operator)++`] + +[.contract-subindex-inherited] +.IERC165 +* {xref-IERC165-supportsInterface-bytes4-}[`++supportsInterface(interfaceId)++`] + +-- + +[.contract-index] +.Events +-- + +[.contract-subindex-inherited] +.IERC721 +* {xref-IERC721-Transfer-address-address-uint256-}[`++Transfer(from, to, tokenId)++`] +* {xref-IERC721-Approval-address-address-uint256-}[`++Approval(owner, approved, tokenId)++`] +* {xref-IERC721-ApprovalForAll-address-address-bool-}[`++ApprovalForAll(owner, operator, approved)++`] + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-item] +[[IERC721Enumerable-totalSupply--]] +==== `[.contract-item-name]#++totalSupply++#++() β†’ uint256++` [.item-kind]#external# + +Returns the total amount of tokens stored by the contract. + +[.contract-item] +[[IERC721Enumerable-tokenOfOwnerByIndex-address-uint256-]] +==== `[.contract-item-name]#++tokenOfOwnerByIndex++#++(address owner, uint256 index) β†’ uint256++` [.item-kind]#external# + +Returns a token ID owned by `owner` at a given `index` of its token list. +Use along with {balanceOf} to enumerate all of ``owner``'s tokens. + +[.contract-item] +[[IERC721Enumerable-tokenByIndex-uint256-]] +==== `[.contract-item-name]#++tokenByIndex++#++(uint256 index) β†’ uint256++` [.item-kind]#external# + +Returns a token ID at a given `index` of all the tokens stored by the contract. +Use along with {totalSupply} to enumerate all tokens. + +:constructor: pass:normal[xref:#ERC721-constructor-string-string-[`++constructor++`]] +:supportsInterface: pass:normal[xref:#ERC721-supportsInterface-bytes4-[`++supportsInterface++`]] +:balanceOf: pass:normal[xref:#ERC721-balanceOf-address-[`++balanceOf++`]] +:ownerOf: pass:normal[xref:#ERC721-ownerOf-uint256-[`++ownerOf++`]] +:name: pass:normal[xref:#ERC721-name--[`++name++`]] +:symbol: pass:normal[xref:#ERC721-symbol--[`++symbol++`]] +:tokenURI: pass:normal[xref:#ERC721-tokenURI-uint256-[`++tokenURI++`]] +:_baseURI: pass:normal[xref:#ERC721-_baseURI--[`++_baseURI++`]] +:approve: pass:normal[xref:#ERC721-approve-address-uint256-[`++approve++`]] +:getApproved: pass:normal[xref:#ERC721-getApproved-uint256-[`++getApproved++`]] +:setApprovalForAll: pass:normal[xref:#ERC721-setApprovalForAll-address-bool-[`++setApprovalForAll++`]] +:isApprovedForAll: pass:normal[xref:#ERC721-isApprovedForAll-address-address-[`++isApprovedForAll++`]] +:transferFrom: pass:normal[xref:#ERC721-transferFrom-address-address-uint256-[`++transferFrom++`]] +:safeTransferFrom: pass:normal[xref:#ERC721-safeTransferFrom-address-address-uint256-[`++safeTransferFrom++`]] +:safeTransferFrom: pass:normal[xref:#ERC721-safeTransferFrom-address-address-uint256-bytes-[`++safeTransferFrom++`]] +:_ownerOf: pass:normal[xref:#ERC721-_ownerOf-uint256-[`++_ownerOf++`]] +:_getApproved: pass:normal[xref:#ERC721-_getApproved-uint256-[`++_getApproved++`]] +:_isAuthorized: pass:normal[xref:#ERC721-_isAuthorized-address-address-uint256-[`++_isAuthorized++`]] +:_checkAuthorized: pass:normal[xref:#ERC721-_checkAuthorized-address-address-uint256-[`++_checkAuthorized++`]] +:_increaseBalance: pass:normal[xref:#ERC721-_increaseBalance-address-uint128-[`++_increaseBalance++`]] +:_update: pass:normal[xref:#ERC721-_update-address-uint256-address-[`++_update++`]] +:_mint: pass:normal[xref:#ERC721-_mint-address-uint256-[`++_mint++`]] +:_safeMint: pass:normal[xref:#ERC721-_safeMint-address-uint256-[`++_safeMint++`]] +:_safeMint: pass:normal[xref:#ERC721-_safeMint-address-uint256-bytes-[`++_safeMint++`]] +:_burn: pass:normal[xref:#ERC721-_burn-uint256-[`++_burn++`]] +:_transfer: pass:normal[xref:#ERC721-_transfer-address-address-uint256-[`++_transfer++`]] +:_safeTransfer: pass:normal[xref:#ERC721-_safeTransfer-address-address-uint256-[`++_safeTransfer++`]] +:_safeTransfer: pass:normal[xref:#ERC721-_safeTransfer-address-address-uint256-bytes-[`++_safeTransfer++`]] +:_approve: pass:normal[xref:#ERC721-_approve-address-uint256-address-[`++_approve++`]] +:_approve: pass:normal[xref:#ERC721-_approve-address-uint256-address-bool-[`++_approve++`]] +:_setApprovalForAll: pass:normal[xref:#ERC721-_setApprovalForAll-address-address-bool-[`++_setApprovalForAll++`]] +:_requireOwned: pass:normal[xref:#ERC721-_requireOwned-uint256-[`++_requireOwned++`]] + +[.contract] +[[ERC721]] +=== `++ERC721++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/token/ERC721/ERC721.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; +``` + +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-index] +.Functions +-- +* {xref-ERC721-constructor-string-string-}[`++constructor(name_, symbol_)++`] +* {xref-ERC721-supportsInterface-bytes4-}[`++supportsInterface(interfaceId)++`] +* {xref-ERC721-balanceOf-address-}[`++balanceOf(owner)++`] +* {xref-ERC721-ownerOf-uint256-}[`++ownerOf(tokenId)++`] +* {xref-ERC721-name--}[`++name()++`] +* {xref-ERC721-symbol--}[`++symbol()++`] +* {xref-ERC721-tokenURI-uint256-}[`++tokenURI(tokenId)++`] +* {xref-ERC721-_baseURI--}[`++_baseURI()++`] +* {xref-ERC721-approve-address-uint256-}[`++approve(to, tokenId)++`] +* {xref-ERC721-getApproved-uint256-}[`++getApproved(tokenId)++`] +* {xref-ERC721-setApprovalForAll-address-bool-}[`++setApprovalForAll(operator, approved)++`] +* {xref-ERC721-isApprovedForAll-address-address-}[`++isApprovedForAll(owner, operator)++`] +* {xref-ERC721-transferFrom-address-address-uint256-}[`++transferFrom(from, to, tokenId)++`] +* {xref-ERC721-safeTransferFrom-address-address-uint256-}[`++safeTransferFrom(from, to, tokenId)++`] +* {xref-ERC721-safeTransferFrom-address-address-uint256-bytes-}[`++safeTransferFrom(from, to, tokenId, data)++`] +* {xref-ERC721-_ownerOf-uint256-}[`++_ownerOf(tokenId)++`] +* {xref-ERC721-_getApproved-uint256-}[`++_getApproved(tokenId)++`] +* {xref-ERC721-_isAuthorized-address-address-uint256-}[`++_isAuthorized(owner, spender, tokenId)++`] +* {xref-ERC721-_checkAuthorized-address-address-uint256-}[`++_checkAuthorized(owner, spender, tokenId)++`] +* {xref-ERC721-_increaseBalance-address-uint128-}[`++_increaseBalance(account, value)++`] +* {xref-ERC721-_update-address-uint256-address-}[`++_update(to, tokenId, auth)++`] +* {xref-ERC721-_mint-address-uint256-}[`++_mint(to, tokenId)++`] +* {xref-ERC721-_safeMint-address-uint256-}[`++_safeMint(to, tokenId)++`] +* {xref-ERC721-_safeMint-address-uint256-bytes-}[`++_safeMint(to, tokenId, data)++`] +* {xref-ERC721-_burn-uint256-}[`++_burn(tokenId)++`] +* {xref-ERC721-_transfer-address-address-uint256-}[`++_transfer(from, to, tokenId)++`] +* {xref-ERC721-_safeTransfer-address-address-uint256-}[`++_safeTransfer(from, to, tokenId)++`] +* {xref-ERC721-_safeTransfer-address-address-uint256-bytes-}[`++_safeTransfer(from, to, tokenId, data)++`] +* {xref-ERC721-_approve-address-uint256-address-}[`++_approve(to, tokenId, auth)++`] +* {xref-ERC721-_approve-address-uint256-address-bool-}[`++_approve(to, tokenId, auth, emitEvent)++`] +* {xref-ERC721-_setApprovalForAll-address-address-bool-}[`++_setApprovalForAll(owner, operator, approved)++`] +* {xref-ERC721-_requireOwned-uint256-}[`++_requireOwned(tokenId)++`] + +[.contract-subindex-inherited] +.IERC721Errors + +[.contract-subindex-inherited] +.IERC721Metadata + +[.contract-subindex-inherited] +.IERC721 + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-index] +.Events +-- + +[.contract-subindex-inherited] +.IERC721Errors + +[.contract-subindex-inherited] +.IERC721Metadata + +[.contract-subindex-inherited] +.IERC721 +* {xref-IERC721-Transfer-address-address-uint256-}[`++Transfer(from, to, tokenId)++`] +* {xref-IERC721-Approval-address-address-uint256-}[`++Approval(owner, approved, tokenId)++`] +* {xref-IERC721-ApprovalForAll-address-address-bool-}[`++ApprovalForAll(owner, operator, approved)++`] + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-index] +.Errors +-- + +[.contract-subindex-inherited] +.IERC721Errors +* {xref-IERC721Errors-ERC721InvalidOwner-address-}[`++ERC721InvalidOwner(owner)++`] +* {xref-IERC721Errors-ERC721NonexistentToken-uint256-}[`++ERC721NonexistentToken(tokenId)++`] +* {xref-IERC721Errors-ERC721IncorrectOwner-address-uint256-address-}[`++ERC721IncorrectOwner(sender, tokenId, owner)++`] +* {xref-IERC721Errors-ERC721InvalidSender-address-}[`++ERC721InvalidSender(sender)++`] +* {xref-IERC721Errors-ERC721InvalidReceiver-address-}[`++ERC721InvalidReceiver(receiver)++`] +* {xref-IERC721Errors-ERC721InsufficientApproval-address-uint256-}[`++ERC721InsufficientApproval(operator, tokenId)++`] +* {xref-IERC721Errors-ERC721InvalidApprover-address-}[`++ERC721InvalidApprover(approver)++`] +* {xref-IERC721Errors-ERC721InvalidOperator-address-}[`++ERC721InvalidOperator(operator)++`] + +[.contract-subindex-inherited] +.IERC721Metadata + +[.contract-subindex-inherited] +.IERC721 + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-item] +[[ERC721-constructor-string-string-]] +==== `[.contract-item-name]#++constructor++#++(string name_, string symbol_)++` [.item-kind]#internal# + +Initializes the contract by setting a `name` and a `symbol` to the token collection. + +[.contract-item] +[[ERC721-supportsInterface-bytes4-]] +==== `[.contract-item-name]#++supportsInterface++#++(bytes4 interfaceId) β†’ bool++` [.item-kind]#public# + +See {IERC165-supportsInterface}. + +[.contract-item] +[[ERC721-balanceOf-address-]] +==== `[.contract-item-name]#++balanceOf++#++(address owner) β†’ uint256++` [.item-kind]#public# + +See {IERC721-balanceOf}. + +[.contract-item] +[[ERC721-ownerOf-uint256-]] +==== `[.contract-item-name]#++ownerOf++#++(uint256 tokenId) β†’ address++` [.item-kind]#public# + +See {IERC721-ownerOf}. + +[.contract-item] +[[ERC721-name--]] +==== `[.contract-item-name]#++name++#++() β†’ string++` [.item-kind]#public# + +See {IERC721Metadata-name}. + +[.contract-item] +[[ERC721-symbol--]] +==== `[.contract-item-name]#++symbol++#++() β†’ string++` [.item-kind]#public# + +See {IERC721Metadata-symbol}. + +[.contract-item] +[[ERC721-tokenURI-uint256-]] +==== `[.contract-item-name]#++tokenURI++#++(uint256 tokenId) β†’ string++` [.item-kind]#public# + +See {IERC721Metadata-tokenURI}. + +[.contract-item] +[[ERC721-_baseURI--]] +==== `[.contract-item-name]#++_baseURI++#++() β†’ string++` [.item-kind]#internal# + +Base URI for computing {tokenURI}. If set, the resulting URI for each +token will be the concatenation of the `baseURI` and the `tokenId`. Empty +by default, can be overridden in child contracts. + +[.contract-item] +[[ERC721-approve-address-uint256-]] +==== `[.contract-item-name]#++approve++#++(address to, uint256 tokenId)++` [.item-kind]#public# + +See {IERC721-approve}. + +[.contract-item] +[[ERC721-getApproved-uint256-]] +==== `[.contract-item-name]#++getApproved++#++(uint256 tokenId) β†’ address++` [.item-kind]#public# + +See {IERC721-getApproved}. + +[.contract-item] +[[ERC721-setApprovalForAll-address-bool-]] +==== `[.contract-item-name]#++setApprovalForAll++#++(address operator, bool approved)++` [.item-kind]#public# + +See {IERC721-setApprovalForAll}. + +[.contract-item] +[[ERC721-isApprovedForAll-address-address-]] +==== `[.contract-item-name]#++isApprovedForAll++#++(address owner, address operator) β†’ bool++` [.item-kind]#public# + +See {IERC721-isApprovedForAll}. + +[.contract-item] +[[ERC721-transferFrom-address-address-uint256-]] +==== `[.contract-item-name]#++transferFrom++#++(address from, address to, uint256 tokenId)++` [.item-kind]#public# + +See {IERC721-transferFrom}. + +[.contract-item] +[[ERC721-safeTransferFrom-address-address-uint256-]] +==== `[.contract-item-name]#++safeTransferFrom++#++(address from, address to, uint256 tokenId)++` [.item-kind]#public# + +See {IERC721-safeTransferFrom}. + +[.contract-item] +[[ERC721-safeTransferFrom-address-address-uint256-bytes-]] +==== `[.contract-item-name]#++safeTransferFrom++#++(address from, address to, uint256 tokenId, bytes data)++` [.item-kind]#public# + +See {IERC721-safeTransferFrom}. + +[.contract-item] +[[ERC721-_ownerOf-uint256-]] +==== `[.contract-item-name]#++_ownerOf++#++(uint256 tokenId) β†’ address++` [.item-kind]#internal# + +Returns the owner of the `tokenId`. Does NOT revert if token doesn't exist + +IMPORTANT: Any overrides to this function that add ownership of tokens not tracked by the +core ERC721 logic MUST be matched with the use of {_increaseBalance} to keep balances +consistent with ownership. The invariant to preserve is that for any address `a` the value returned by +`balanceOf(a)` must be equal to the number of tokens such that `_ownerOf(tokenId)` is `a`. + +[.contract-item] +[[ERC721-_getApproved-uint256-]] +==== `[.contract-item-name]#++_getApproved++#++(uint256 tokenId) β†’ address++` [.item-kind]#internal# + +Returns the approved address for `tokenId`. Returns 0 if `tokenId` is not minted. + +[.contract-item] +[[ERC721-_isAuthorized-address-address-uint256-]] +==== `[.contract-item-name]#++_isAuthorized++#++(address owner, address spender, uint256 tokenId) β†’ bool++` [.item-kind]#internal# + +Returns whether `spender` is allowed to manage `owner`'s tokens, or `tokenId` in +particular (ignoring whether it is owned by `owner`). + +WARNING: This function assumes that `owner` is the actual owner of `tokenId` and does not verify this +assumption. + +[.contract-item] +[[ERC721-_checkAuthorized-address-address-uint256-]] +==== `[.contract-item-name]#++_checkAuthorized++#++(address owner, address spender, uint256 tokenId)++` [.item-kind]#internal# + +Checks if `spender` can operate on `tokenId`, assuming the provided `owner` is the actual owner. +Reverts if `spender` does not have approval from the provided `owner` for the given token or for all its assets +the `spender` for the specific `tokenId`. + +WARNING: This function assumes that `owner` is the actual owner of `tokenId` and does not verify this +assumption. + +[.contract-item] +[[ERC721-_increaseBalance-address-uint128-]] +==== `[.contract-item-name]#++_increaseBalance++#++(address account, uint128 value)++` [.item-kind]#internal# + +Unsafe write access to the balances, used by extensions that "mint" tokens using an {ownerOf} override. + +NOTE: the value is limited to type(uint128).max. This protect against _balance overflow. It is unrealistic that +a uint256 would ever overflow from increments when these increments are bounded to uint128 values. + +WARNING: Increasing an account's balance using this function tends to be paired with an override of the +{_ownerOf} function to resolve the ownership of the corresponding tokens so that balances and ownership +remain consistent with one another. + +[.contract-item] +[[ERC721-_update-address-uint256-address-]] +==== `[.contract-item-name]#++_update++#++(address to, uint256 tokenId, address auth) β†’ address++` [.item-kind]#internal# + +Transfers `tokenId` from its current owner to `to`, or alternatively mints (or burns) if the current owner +(or `to`) is the zero address. Returns the owner of the `tokenId` before the update. + +The `auth` argument is optional. If the value passed is non 0, then this function will check that +`auth` is either the owner of the token, or approved to operate on the token (by the owner). + +Emits a {Transfer} event. + +NOTE: If overriding this function in a way that tracks balances, see also {_increaseBalance}. + +[.contract-item] +[[ERC721-_mint-address-uint256-]] +==== `[.contract-item-name]#++_mint++#++(address to, uint256 tokenId)++` [.item-kind]#internal# + +Mints `tokenId` and transfers it to `to`. + +WARNING: Usage of this method is discouraged, use {_safeMint} whenever possible + +Requirements: + +- `tokenId` must not exist. +- `to` cannot be the zero address. + +Emits a {Transfer} event. + +[.contract-item] +[[ERC721-_safeMint-address-uint256-]] +==== `[.contract-item-name]#++_safeMint++#++(address to, uint256 tokenId)++` [.item-kind]#internal# + +Mints `tokenId`, transfers it to `to` and checks for `to` acceptance. + +Requirements: + +- `tokenId` must not exist. +- If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. + +Emits a {Transfer} event. + +[.contract-item] +[[ERC721-_safeMint-address-uint256-bytes-]] +==== `[.contract-item-name]#++_safeMint++#++(address to, uint256 tokenId, bytes data)++` [.item-kind]#internal# + +Same as {xref-ERC721-_safeMint-address-uint256-}[`_safeMint`], with an additional `data` parameter which is +forwarded in {IERC721Receiver-onERC721Received} to contract recipients. + +[.contract-item] +[[ERC721-_burn-uint256-]] +==== `[.contract-item-name]#++_burn++#++(uint256 tokenId)++` [.item-kind]#internal# + +Destroys `tokenId`. +The approval is cleared when the token is burned. +This is an internal function that does not check if the sender is authorized to operate on the token. + +Requirements: + +- `tokenId` must exist. + +Emits a {Transfer} event. + +[.contract-item] +[[ERC721-_transfer-address-address-uint256-]] +==== `[.contract-item-name]#++_transfer++#++(address from, address to, uint256 tokenId)++` [.item-kind]#internal# + +Transfers `tokenId` from `from` to `to`. + As opposed to {transferFrom}, this imposes no restrictions on msg.sender. + +Requirements: + +- `to` cannot be the zero address. +- `tokenId` token must be owned by `from`. + +Emits a {Transfer} event. + +[.contract-item] +[[ERC721-_safeTransfer-address-address-uint256-]] +==== `[.contract-item-name]#++_safeTransfer++#++(address from, address to, uint256 tokenId)++` [.item-kind]#internal# + +Safely transfers `tokenId` token from `from` to `to`, checking that contract recipients +are aware of the ERC721 standard to prevent tokens from being forever locked. + +`data` is additional data, it has no specified format and it is sent in call to `to`. + +This internal function is like {safeTransferFrom} in the sense that it invokes +{IERC721Receiver-onERC721Received} on the receiver, and can be used to e.g. +implement alternative mechanisms to perform token transfer, such as signature-based. + +Requirements: + +- `tokenId` token must exist and be owned by `from`. +- `to` cannot be the zero address. +- `from` cannot be the zero address. +- If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. + +Emits a {Transfer} event. + +[.contract-item] +[[ERC721-_safeTransfer-address-address-uint256-bytes-]] +==== `[.contract-item-name]#++_safeTransfer++#++(address from, address to, uint256 tokenId, bytes data)++` [.item-kind]#internal# + +Same as {xref-ERC721-_safeTransfer-address-address-uint256-}[`_safeTransfer`], with an additional `data` parameter which is +forwarded in {IERC721Receiver-onERC721Received} to contract recipients. + +[.contract-item] +[[ERC721-_approve-address-uint256-address-]] +==== `[.contract-item-name]#++_approve++#++(address to, uint256 tokenId, address auth)++` [.item-kind]#internal# + +Approve `to` to operate on `tokenId` + +The `auth` argument is optional. If the value passed is non 0, then this function will check that `auth` is +either the owner of the token, or approved to operate on all tokens held by this owner. + +Emits an {Approval} event. + +Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument. + +[.contract-item] +[[ERC721-_approve-address-uint256-address-bool-]] +==== `[.contract-item-name]#++_approve++#++(address to, uint256 tokenId, address auth, bool emitEvent)++` [.item-kind]#internal# + +Variant of `_approve` with an optional flag to enable or disable the {Approval} event. The event is not +emitted in the context of transfers. + +[.contract-item] +[[ERC721-_setApprovalForAll-address-address-bool-]] +==== `[.contract-item-name]#++_setApprovalForAll++#++(address owner, address operator, bool approved)++` [.item-kind]#internal# + +Approve `operator` to operate on all of `owner` tokens + +Requirements: +- operator can't be the address zero. + +Emits an {ApprovalForAll} event. + +[.contract-item] +[[ERC721-_requireOwned-uint256-]] +==== `[.contract-item-name]#++_requireOwned++#++(uint256 tokenId) β†’ address++` [.item-kind]#internal# + +Reverts if the `tokenId` doesn't have a current owner (it hasn't been minted, or it has been burned). +Returns the owner. + +Overrides to ownership logic should be done to {_ownerOf}. + +:ERC721OutOfBoundsIndex: pass:normal[xref:#ERC721Enumerable-ERC721OutOfBoundsIndex-address-uint256-[`++ERC721OutOfBoundsIndex++`]] +:ERC721EnumerableForbiddenBatchMint: pass:normal[xref:#ERC721Enumerable-ERC721EnumerableForbiddenBatchMint--[`++ERC721EnumerableForbiddenBatchMint++`]] +:supportsInterface: pass:normal[xref:#ERC721Enumerable-supportsInterface-bytes4-[`++supportsInterface++`]] +:tokenOfOwnerByIndex: pass:normal[xref:#ERC721Enumerable-tokenOfOwnerByIndex-address-uint256-[`++tokenOfOwnerByIndex++`]] +:totalSupply: pass:normal[xref:#ERC721Enumerable-totalSupply--[`++totalSupply++`]] +:tokenByIndex: pass:normal[xref:#ERC721Enumerable-tokenByIndex-uint256-[`++tokenByIndex++`]] +:_update: pass:normal[xref:#ERC721Enumerable-_update-address-uint256-address-[`++_update++`]] +:_increaseBalance: pass:normal[xref:#ERC721Enumerable-_increaseBalance-address-uint128-[`++_increaseBalance++`]] + +[.contract] +[[ERC721Enumerable]] +=== `++ERC721Enumerable++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/token/ERC721/extensions/ERC721Enumerable.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; +``` + +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`. + +[.contract-index] +.Functions +-- +* {xref-ERC721Enumerable-supportsInterface-bytes4-}[`++supportsInterface(interfaceId)++`] +* {xref-ERC721Enumerable-tokenOfOwnerByIndex-address-uint256-}[`++tokenOfOwnerByIndex(owner, index)++`] +* {xref-ERC721Enumerable-totalSupply--}[`++totalSupply()++`] +* {xref-ERC721Enumerable-tokenByIndex-uint256-}[`++tokenByIndex(index)++`] +* {xref-ERC721Enumerable-_update-address-uint256-address-}[`++_update(to, tokenId, auth)++`] +* {xref-ERC721Enumerable-_increaseBalance-address-uint128-}[`++_increaseBalance(account, amount)++`] + +[.contract-subindex-inherited] +.IERC721Enumerable + +[.contract-subindex-inherited] +.ERC721 +* {xref-ERC721-balanceOf-address-}[`++balanceOf(owner)++`] +* {xref-ERC721-ownerOf-uint256-}[`++ownerOf(tokenId)++`] +* {xref-ERC721-name--}[`++name()++`] +* {xref-ERC721-symbol--}[`++symbol()++`] +* {xref-ERC721-tokenURI-uint256-}[`++tokenURI(tokenId)++`] +* {xref-ERC721-_baseURI--}[`++_baseURI()++`] +* {xref-ERC721-approve-address-uint256-}[`++approve(to, tokenId)++`] +* {xref-ERC721-getApproved-uint256-}[`++getApproved(tokenId)++`] +* {xref-ERC721-setApprovalForAll-address-bool-}[`++setApprovalForAll(operator, approved)++`] +* {xref-ERC721-isApprovedForAll-address-address-}[`++isApprovedForAll(owner, operator)++`] +* {xref-ERC721-transferFrom-address-address-uint256-}[`++transferFrom(from, to, tokenId)++`] +* {xref-ERC721-safeTransferFrom-address-address-uint256-}[`++safeTransferFrom(from, to, tokenId)++`] +* {xref-ERC721-safeTransferFrom-address-address-uint256-bytes-}[`++safeTransferFrom(from, to, tokenId, data)++`] +* {xref-ERC721-_ownerOf-uint256-}[`++_ownerOf(tokenId)++`] +* {xref-ERC721-_getApproved-uint256-}[`++_getApproved(tokenId)++`] +* {xref-ERC721-_isAuthorized-address-address-uint256-}[`++_isAuthorized(owner, spender, tokenId)++`] +* {xref-ERC721-_checkAuthorized-address-address-uint256-}[`++_checkAuthorized(owner, spender, tokenId)++`] +* {xref-ERC721-_mint-address-uint256-}[`++_mint(to, tokenId)++`] +* {xref-ERC721-_safeMint-address-uint256-}[`++_safeMint(to, tokenId)++`] +* {xref-ERC721-_safeMint-address-uint256-bytes-}[`++_safeMint(to, tokenId, data)++`] +* {xref-ERC721-_burn-uint256-}[`++_burn(tokenId)++`] +* {xref-ERC721-_transfer-address-address-uint256-}[`++_transfer(from, to, tokenId)++`] +* {xref-ERC721-_safeTransfer-address-address-uint256-}[`++_safeTransfer(from, to, tokenId)++`] +* {xref-ERC721-_safeTransfer-address-address-uint256-bytes-}[`++_safeTransfer(from, to, tokenId, data)++`] +* {xref-ERC721-_approve-address-uint256-address-}[`++_approve(to, tokenId, auth)++`] +* {xref-ERC721-_approve-address-uint256-address-bool-}[`++_approve(to, tokenId, auth, emitEvent)++`] +* {xref-ERC721-_setApprovalForAll-address-address-bool-}[`++_setApprovalForAll(owner, operator, approved)++`] +* {xref-ERC721-_requireOwned-uint256-}[`++_requireOwned(tokenId)++`] + +[.contract-subindex-inherited] +.IERC721Errors + +[.contract-subindex-inherited] +.IERC721Metadata + +[.contract-subindex-inherited] +.IERC721 + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-index] +.Events +-- + +[.contract-subindex-inherited] +.IERC721Enumerable + +[.contract-subindex-inherited] +.ERC721 + +[.contract-subindex-inherited] +.IERC721Errors + +[.contract-subindex-inherited] +.IERC721Metadata + +[.contract-subindex-inherited] +.IERC721 +* {xref-IERC721-Transfer-address-address-uint256-}[`++Transfer(from, to, tokenId)++`] +* {xref-IERC721-Approval-address-address-uint256-}[`++Approval(owner, approved, tokenId)++`] +* {xref-IERC721-ApprovalForAll-address-address-bool-}[`++ApprovalForAll(owner, operator, approved)++`] + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-index] +.Errors +-- +* {xref-ERC721Enumerable-ERC721OutOfBoundsIndex-address-uint256-}[`++ERC721OutOfBoundsIndex(owner, index)++`] +* {xref-ERC721Enumerable-ERC721EnumerableForbiddenBatchMint--}[`++ERC721EnumerableForbiddenBatchMint()++`] + +[.contract-subindex-inherited] +.IERC721Enumerable + +[.contract-subindex-inherited] +.ERC721 + +[.contract-subindex-inherited] +.IERC721Errors +* {xref-IERC721Errors-ERC721InvalidOwner-address-}[`++ERC721InvalidOwner(owner)++`] +* {xref-IERC721Errors-ERC721NonexistentToken-uint256-}[`++ERC721NonexistentToken(tokenId)++`] +* {xref-IERC721Errors-ERC721IncorrectOwner-address-uint256-address-}[`++ERC721IncorrectOwner(sender, tokenId, owner)++`] +* {xref-IERC721Errors-ERC721InvalidSender-address-}[`++ERC721InvalidSender(sender)++`] +* {xref-IERC721Errors-ERC721InvalidReceiver-address-}[`++ERC721InvalidReceiver(receiver)++`] +* {xref-IERC721Errors-ERC721InsufficientApproval-address-uint256-}[`++ERC721InsufficientApproval(operator, tokenId)++`] +* {xref-IERC721Errors-ERC721InvalidApprover-address-}[`++ERC721InvalidApprover(approver)++`] +* {xref-IERC721Errors-ERC721InvalidOperator-address-}[`++ERC721InvalidOperator(operator)++`] + +[.contract-subindex-inherited] +.IERC721Metadata + +[.contract-subindex-inherited] +.IERC721 + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-item] +[[ERC721Enumerable-supportsInterface-bytes4-]] +==== `[.contract-item-name]#++supportsInterface++#++(bytes4 interfaceId) β†’ bool++` [.item-kind]#public# + +See {IERC165-supportsInterface}. + +[.contract-item] +[[ERC721Enumerable-tokenOfOwnerByIndex-address-uint256-]] +==== `[.contract-item-name]#++tokenOfOwnerByIndex++#++(address owner, uint256 index) β†’ uint256++` [.item-kind]#public# + +See {IERC721Enumerable-tokenOfOwnerByIndex}. + +[.contract-item] +[[ERC721Enumerable-totalSupply--]] +==== `[.contract-item-name]#++totalSupply++#++() β†’ uint256++` [.item-kind]#public# + +See {IERC721Enumerable-totalSupply}. + +[.contract-item] +[[ERC721Enumerable-tokenByIndex-uint256-]] +==== `[.contract-item-name]#++tokenByIndex++#++(uint256 index) β†’ uint256++` [.item-kind]#public# + +See {IERC721Enumerable-tokenByIndex}. + +[.contract-item] +[[ERC721Enumerable-_update-address-uint256-address-]] +==== `[.contract-item-name]#++_update++#++(address to, uint256 tokenId, address auth) β†’ address++` [.item-kind]#internal# + +See {ERC721-_update}. + +[.contract-item] +[[ERC721Enumerable-_increaseBalance-address-uint128-]] +==== `[.contract-item-name]#++_increaseBalance++#++(address account, uint128 amount)++` [.item-kind]#internal# + +[.contract-item] +[[ERC721Enumerable-ERC721OutOfBoundsIndex-address-uint256-]] +==== `[.contract-item-name]#++ERC721OutOfBoundsIndex++#++(address owner, uint256 index)++` [.item-kind]#error# + +An `owner`'s token query was out of bounds for `index`. + +NOTE: The owner being `address(0)` indicates a global out of bounds index. + +[.contract-item] +[[ERC721Enumerable-ERC721EnumerableForbiddenBatchMint--]] +==== `[.contract-item-name]#++ERC721EnumerableForbiddenBatchMint++#++()++` [.item-kind]#error# + +Batch mint is not allowed. + +:onERC721Received: pass:normal[xref:#IERC721Receiver-onERC721Received-address-address-uint256-bytes-[`++onERC721Received++`]] + +[.contract] +[[IERC721Receiver]] +=== `++IERC721Receiver++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/token/ERC721/IERC721Receiver.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; +``` + +Interface for any contract that wants to support safeTransfers +from ERC721 asset contracts. + +[.contract-index] +.Functions +-- +* {xref-IERC721Receiver-onERC721Received-address-address-uint256-bytes-}[`++onERC721Received(operator, from, tokenId, data)++`] + +-- + +[.contract-item] +[[IERC721Receiver-onERC721Received-address-address-uint256-bytes-]] +==== `[.contract-item-name]#++onERC721Received++#++(address operator, address from, uint256 tokenId, bytes data) β†’ bytes4++` [.item-kind]#external# + +Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom} +by `operator` from `from`, this function is called. + +It must return its Solidity selector to confirm the token transfer. +If any other value is returned or the interface is not implemented by the recipient, the transfer will be +reverted. + +The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`. + +== Extensions + +:_update: pass:normal[xref:#ERC721Pausable-_update-address-uint256-address-[`++_update++`]] + +[.contract] +[[ERC721Pausable]] +=== `++ERC721Pausable++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/token/ERC721/extensions/ERC721Pausable.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Pausable.sol"; +``` + +ERC721 token with pausable token transfers, minting and burning. + +Useful for scenarios such as preventing trades until the end of an evaluation +period, or having an emergency switch for freezing all token transfers in the +event of a large bug. + +IMPORTANT: This contract does not include public pause and unpause functions. In +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 pause mechanism of the contract unreachable, and thus unusable. + +[.contract-index] +.Functions +-- +* {xref-ERC721Pausable-_update-address-uint256-address-}[`++_update(to, tokenId, auth)++`] + +[.contract-subindex-inherited] +.Pausable +* {xref-Pausable-paused--}[`++paused()++`] +* {xref-Pausable-_requireNotPaused--}[`++_requireNotPaused()++`] +* {xref-Pausable-_requirePaused--}[`++_requirePaused()++`] +* {xref-Pausable-_pause--}[`++_pause()++`] +* {xref-Pausable-_unpause--}[`++_unpause()++`] + +[.contract-subindex-inherited] +.ERC721 +* {xref-ERC721-supportsInterface-bytes4-}[`++supportsInterface(interfaceId)++`] +* {xref-ERC721-balanceOf-address-}[`++balanceOf(owner)++`] +* {xref-ERC721-ownerOf-uint256-}[`++ownerOf(tokenId)++`] +* {xref-ERC721-name--}[`++name()++`] +* {xref-ERC721-symbol--}[`++symbol()++`] +* {xref-ERC721-tokenURI-uint256-}[`++tokenURI(tokenId)++`] +* {xref-ERC721-_baseURI--}[`++_baseURI()++`] +* {xref-ERC721-approve-address-uint256-}[`++approve(to, tokenId)++`] +* {xref-ERC721-getApproved-uint256-}[`++getApproved(tokenId)++`] +* {xref-ERC721-setApprovalForAll-address-bool-}[`++setApprovalForAll(operator, approved)++`] +* {xref-ERC721-isApprovedForAll-address-address-}[`++isApprovedForAll(owner, operator)++`] +* {xref-ERC721-transferFrom-address-address-uint256-}[`++transferFrom(from, to, tokenId)++`] +* {xref-ERC721-safeTransferFrom-address-address-uint256-}[`++safeTransferFrom(from, to, tokenId)++`] +* {xref-ERC721-safeTransferFrom-address-address-uint256-bytes-}[`++safeTransferFrom(from, to, tokenId, data)++`] +* {xref-ERC721-_ownerOf-uint256-}[`++_ownerOf(tokenId)++`] +* {xref-ERC721-_getApproved-uint256-}[`++_getApproved(tokenId)++`] +* {xref-ERC721-_isAuthorized-address-address-uint256-}[`++_isAuthorized(owner, spender, tokenId)++`] +* {xref-ERC721-_checkAuthorized-address-address-uint256-}[`++_checkAuthorized(owner, spender, tokenId)++`] +* {xref-ERC721-_increaseBalance-address-uint128-}[`++_increaseBalance(account, value)++`] +* {xref-ERC721-_mint-address-uint256-}[`++_mint(to, tokenId)++`] +* {xref-ERC721-_safeMint-address-uint256-}[`++_safeMint(to, tokenId)++`] +* {xref-ERC721-_safeMint-address-uint256-bytes-}[`++_safeMint(to, tokenId, data)++`] +* {xref-ERC721-_burn-uint256-}[`++_burn(tokenId)++`] +* {xref-ERC721-_transfer-address-address-uint256-}[`++_transfer(from, to, tokenId)++`] +* {xref-ERC721-_safeTransfer-address-address-uint256-}[`++_safeTransfer(from, to, tokenId)++`] +* {xref-ERC721-_safeTransfer-address-address-uint256-bytes-}[`++_safeTransfer(from, to, tokenId, data)++`] +* {xref-ERC721-_approve-address-uint256-address-}[`++_approve(to, tokenId, auth)++`] +* {xref-ERC721-_approve-address-uint256-address-bool-}[`++_approve(to, tokenId, auth, emitEvent)++`] +* {xref-ERC721-_setApprovalForAll-address-address-bool-}[`++_setApprovalForAll(owner, operator, approved)++`] +* {xref-ERC721-_requireOwned-uint256-}[`++_requireOwned(tokenId)++`] + +[.contract-subindex-inherited] +.IERC721Errors + +[.contract-subindex-inherited] +.IERC721Metadata + +[.contract-subindex-inherited] +.IERC721 + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-index] +.Events +-- + +[.contract-subindex-inherited] +.Pausable +* {xref-Pausable-Paused-address-}[`++Paused(account)++`] +* {xref-Pausable-Unpaused-address-}[`++Unpaused(account)++`] + +[.contract-subindex-inherited] +.ERC721 + +[.contract-subindex-inherited] +.IERC721Errors + +[.contract-subindex-inherited] +.IERC721Metadata + +[.contract-subindex-inherited] +.IERC721 +* {xref-IERC721-Transfer-address-address-uint256-}[`++Transfer(from, to, tokenId)++`] +* {xref-IERC721-Approval-address-address-uint256-}[`++Approval(owner, approved, tokenId)++`] +* {xref-IERC721-ApprovalForAll-address-address-bool-}[`++ApprovalForAll(owner, operator, approved)++`] + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-index] +.Errors +-- + +[.contract-subindex-inherited] +.Pausable +* {xref-Pausable-EnforcedPause--}[`++EnforcedPause()++`] +* {xref-Pausable-ExpectedPause--}[`++ExpectedPause()++`] + +[.contract-subindex-inherited] +.ERC721 + +[.contract-subindex-inherited] +.IERC721Errors +* {xref-IERC721Errors-ERC721InvalidOwner-address-}[`++ERC721InvalidOwner(owner)++`] +* {xref-IERC721Errors-ERC721NonexistentToken-uint256-}[`++ERC721NonexistentToken(tokenId)++`] +* {xref-IERC721Errors-ERC721IncorrectOwner-address-uint256-address-}[`++ERC721IncorrectOwner(sender, tokenId, owner)++`] +* {xref-IERC721Errors-ERC721InvalidSender-address-}[`++ERC721InvalidSender(sender)++`] +* {xref-IERC721Errors-ERC721InvalidReceiver-address-}[`++ERC721InvalidReceiver(receiver)++`] +* {xref-IERC721Errors-ERC721InsufficientApproval-address-uint256-}[`++ERC721InsufficientApproval(operator, tokenId)++`] +* {xref-IERC721Errors-ERC721InvalidApprover-address-}[`++ERC721InvalidApprover(approver)++`] +* {xref-IERC721Errors-ERC721InvalidOperator-address-}[`++ERC721InvalidOperator(operator)++`] + +[.contract-subindex-inherited] +.IERC721Metadata + +[.contract-subindex-inherited] +.IERC721 + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-item] +[[ERC721Pausable-_update-address-uint256-address-]] +==== `[.contract-item-name]#++_update++#++(address to, uint256 tokenId, address auth) β†’ address++` [.item-kind]#internal# + +See {ERC721-_update}. + +Requirements: + +- the contract must not be paused. + +:burn: pass:normal[xref:#ERC721Burnable-burn-uint256-[`++burn++`]] + +[.contract] +[[ERC721Burnable]] +=== `++ERC721Burnable++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/token/ERC721/extensions/ERC721Burnable.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Burnable.sol"; +``` + +ERC721 Token that can be burned (destroyed). + +[.contract-index] +.Functions +-- +* {xref-ERC721Burnable-burn-uint256-}[`++burn(tokenId)++`] + +[.contract-subindex-inherited] +.ERC721 +* {xref-ERC721-supportsInterface-bytes4-}[`++supportsInterface(interfaceId)++`] +* {xref-ERC721-balanceOf-address-}[`++balanceOf(owner)++`] +* {xref-ERC721-ownerOf-uint256-}[`++ownerOf(tokenId)++`] +* {xref-ERC721-name--}[`++name()++`] +* {xref-ERC721-symbol--}[`++symbol()++`] +* {xref-ERC721-tokenURI-uint256-}[`++tokenURI(tokenId)++`] +* {xref-ERC721-_baseURI--}[`++_baseURI()++`] +* {xref-ERC721-approve-address-uint256-}[`++approve(to, tokenId)++`] +* {xref-ERC721-getApproved-uint256-}[`++getApproved(tokenId)++`] +* {xref-ERC721-setApprovalForAll-address-bool-}[`++setApprovalForAll(operator, approved)++`] +* {xref-ERC721-isApprovedForAll-address-address-}[`++isApprovedForAll(owner, operator)++`] +* {xref-ERC721-transferFrom-address-address-uint256-}[`++transferFrom(from, to, tokenId)++`] +* {xref-ERC721-safeTransferFrom-address-address-uint256-}[`++safeTransferFrom(from, to, tokenId)++`] +* {xref-ERC721-safeTransferFrom-address-address-uint256-bytes-}[`++safeTransferFrom(from, to, tokenId, data)++`] +* {xref-ERC721-_ownerOf-uint256-}[`++_ownerOf(tokenId)++`] +* {xref-ERC721-_getApproved-uint256-}[`++_getApproved(tokenId)++`] +* {xref-ERC721-_isAuthorized-address-address-uint256-}[`++_isAuthorized(owner, spender, tokenId)++`] +* {xref-ERC721-_checkAuthorized-address-address-uint256-}[`++_checkAuthorized(owner, spender, tokenId)++`] +* {xref-ERC721-_increaseBalance-address-uint128-}[`++_increaseBalance(account, value)++`] +* {xref-ERC721-_update-address-uint256-address-}[`++_update(to, tokenId, auth)++`] +* {xref-ERC721-_mint-address-uint256-}[`++_mint(to, tokenId)++`] +* {xref-ERC721-_safeMint-address-uint256-}[`++_safeMint(to, tokenId)++`] +* {xref-ERC721-_safeMint-address-uint256-bytes-}[`++_safeMint(to, tokenId, data)++`] +* {xref-ERC721-_burn-uint256-}[`++_burn(tokenId)++`] +* {xref-ERC721-_transfer-address-address-uint256-}[`++_transfer(from, to, tokenId)++`] +* {xref-ERC721-_safeTransfer-address-address-uint256-}[`++_safeTransfer(from, to, tokenId)++`] +* {xref-ERC721-_safeTransfer-address-address-uint256-bytes-}[`++_safeTransfer(from, to, tokenId, data)++`] +* {xref-ERC721-_approve-address-uint256-address-}[`++_approve(to, tokenId, auth)++`] +* {xref-ERC721-_approve-address-uint256-address-bool-}[`++_approve(to, tokenId, auth, emitEvent)++`] +* {xref-ERC721-_setApprovalForAll-address-address-bool-}[`++_setApprovalForAll(owner, operator, approved)++`] +* {xref-ERC721-_requireOwned-uint256-}[`++_requireOwned(tokenId)++`] + +[.contract-subindex-inherited] +.IERC721Errors + +[.contract-subindex-inherited] +.IERC721Metadata + +[.contract-subindex-inherited] +.IERC721 + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-index] +.Events +-- + +[.contract-subindex-inherited] +.ERC721 + +[.contract-subindex-inherited] +.IERC721Errors + +[.contract-subindex-inherited] +.IERC721Metadata + +[.contract-subindex-inherited] +.IERC721 +* {xref-IERC721-Transfer-address-address-uint256-}[`++Transfer(from, to, tokenId)++`] +* {xref-IERC721-Approval-address-address-uint256-}[`++Approval(owner, approved, tokenId)++`] +* {xref-IERC721-ApprovalForAll-address-address-bool-}[`++ApprovalForAll(owner, operator, approved)++`] + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-index] +.Errors +-- + +[.contract-subindex-inherited] +.ERC721 + +[.contract-subindex-inherited] +.IERC721Errors +* {xref-IERC721Errors-ERC721InvalidOwner-address-}[`++ERC721InvalidOwner(owner)++`] +* {xref-IERC721Errors-ERC721NonexistentToken-uint256-}[`++ERC721NonexistentToken(tokenId)++`] +* {xref-IERC721Errors-ERC721IncorrectOwner-address-uint256-address-}[`++ERC721IncorrectOwner(sender, tokenId, owner)++`] +* {xref-IERC721Errors-ERC721InvalidSender-address-}[`++ERC721InvalidSender(sender)++`] +* {xref-IERC721Errors-ERC721InvalidReceiver-address-}[`++ERC721InvalidReceiver(receiver)++`] +* {xref-IERC721Errors-ERC721InsufficientApproval-address-uint256-}[`++ERC721InsufficientApproval(operator, tokenId)++`] +* {xref-IERC721Errors-ERC721InvalidApprover-address-}[`++ERC721InvalidApprover(approver)++`] +* {xref-IERC721Errors-ERC721InvalidOperator-address-}[`++ERC721InvalidOperator(operator)++`] + +[.contract-subindex-inherited] +.IERC721Metadata + +[.contract-subindex-inherited] +.IERC721 + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-item] +[[ERC721Burnable-burn-uint256-]] +==== `[.contract-item-name]#++burn++#++(uint256 tokenId)++` [.item-kind]#public# + +Burns `tokenId`. See {ERC721-_burn}. + +Requirements: + +- The caller must own `tokenId` or be an approved operator. + +:ERC721ForbiddenBatchMint: pass:normal[xref:#ERC721Consecutive-ERC721ForbiddenBatchMint--[`++ERC721ForbiddenBatchMint++`]] +:ERC721ExceededMaxBatchMint: pass:normal[xref:#ERC721Consecutive-ERC721ExceededMaxBatchMint-uint256-uint256-[`++ERC721ExceededMaxBatchMint++`]] +:ERC721ForbiddenMint: pass:normal[xref:#ERC721Consecutive-ERC721ForbiddenMint--[`++ERC721ForbiddenMint++`]] +:ERC721ForbiddenBatchBurn: pass:normal[xref:#ERC721Consecutive-ERC721ForbiddenBatchBurn--[`++ERC721ForbiddenBatchBurn++`]] +:_maxBatchSize: pass:normal[xref:#ERC721Consecutive-_maxBatchSize--[`++_maxBatchSize++`]] +:_ownerOf: pass:normal[xref:#ERC721Consecutive-_ownerOf-uint256-[`++_ownerOf++`]] +:_mintConsecutive: pass:normal[xref:#ERC721Consecutive-_mintConsecutive-address-uint96-[`++_mintConsecutive++`]] +:_update: pass:normal[xref:#ERC721Consecutive-_update-address-uint256-address-[`++_update++`]] +:_firstConsecutiveId: pass:normal[xref:#ERC721Consecutive-_firstConsecutiveId--[`++_firstConsecutiveId++`]] + +[.contract] +[[ERC721Consecutive]] +=== `++ERC721Consecutive++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/token/ERC721/extensions/ERC721Consecutive.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Consecutive.sol"; +``` + +Implementation of the ERC2309 "Consecutive Transfer Extension" as defined in +https://eips.ethereum.org/EIPS/eip-2309[EIP-2309]. + +This extension allows the minting of large batches of tokens, during contract construction only. For upgradeable +contracts this implies that batch minting is only available during proxy deployment, and not in subsequent upgrades. +These batches are limited to 5000 tokens at a time by default to accommodate off-chain indexers. + +Using this extension removes the ability to mint single tokens during contract construction. This ability is +regained after construction. During construction, only batch minting is allowed. + +IMPORTANT: This extension does not call the {_update} function for tokens minted in batch. Any logic added to this +function through overrides will not be triggered when token are minted in batch. You may want to also override +{_increaseBalance} or {_mintConsecutive} to account for these mints. + +IMPORTANT: When overriding {_mintConsecutive}, be careful about call ordering. {ownerOf} may return invalid +values during the {_mintConsecutive} execution if the super call is not called first. To be safe, execute the +super call before your custom logic. + +[.contract-index] +.Functions +-- +* {xref-ERC721Consecutive-_maxBatchSize--}[`++_maxBatchSize()++`] +* {xref-ERC721Consecutive-_ownerOf-uint256-}[`++_ownerOf(tokenId)++`] +* {xref-ERC721Consecutive-_mintConsecutive-address-uint96-}[`++_mintConsecutive(to, batchSize)++`] +* {xref-ERC721Consecutive-_update-address-uint256-address-}[`++_update(to, tokenId, auth)++`] +* {xref-ERC721Consecutive-_firstConsecutiveId--}[`++_firstConsecutiveId()++`] + +[.contract-subindex-inherited] +.ERC721 +* {xref-ERC721-supportsInterface-bytes4-}[`++supportsInterface(interfaceId)++`] +* {xref-ERC721-balanceOf-address-}[`++balanceOf(owner)++`] +* {xref-ERC721-ownerOf-uint256-}[`++ownerOf(tokenId)++`] +* {xref-ERC721-name--}[`++name()++`] +* {xref-ERC721-symbol--}[`++symbol()++`] +* {xref-ERC721-tokenURI-uint256-}[`++tokenURI(tokenId)++`] +* {xref-ERC721-_baseURI--}[`++_baseURI()++`] +* {xref-ERC721-approve-address-uint256-}[`++approve(to, tokenId)++`] +* {xref-ERC721-getApproved-uint256-}[`++getApproved(tokenId)++`] +* {xref-ERC721-setApprovalForAll-address-bool-}[`++setApprovalForAll(operator, approved)++`] +* {xref-ERC721-isApprovedForAll-address-address-}[`++isApprovedForAll(owner, operator)++`] +* {xref-ERC721-transferFrom-address-address-uint256-}[`++transferFrom(from, to, tokenId)++`] +* {xref-ERC721-safeTransferFrom-address-address-uint256-}[`++safeTransferFrom(from, to, tokenId)++`] +* {xref-ERC721-safeTransferFrom-address-address-uint256-bytes-}[`++safeTransferFrom(from, to, tokenId, data)++`] +* {xref-ERC721-_getApproved-uint256-}[`++_getApproved(tokenId)++`] +* {xref-ERC721-_isAuthorized-address-address-uint256-}[`++_isAuthorized(owner, spender, tokenId)++`] +* {xref-ERC721-_checkAuthorized-address-address-uint256-}[`++_checkAuthorized(owner, spender, tokenId)++`] +* {xref-ERC721-_increaseBalance-address-uint128-}[`++_increaseBalance(account, value)++`] +* {xref-ERC721-_mint-address-uint256-}[`++_mint(to, tokenId)++`] +* {xref-ERC721-_safeMint-address-uint256-}[`++_safeMint(to, tokenId)++`] +* {xref-ERC721-_safeMint-address-uint256-bytes-}[`++_safeMint(to, tokenId, data)++`] +* {xref-ERC721-_burn-uint256-}[`++_burn(tokenId)++`] +* {xref-ERC721-_transfer-address-address-uint256-}[`++_transfer(from, to, tokenId)++`] +* {xref-ERC721-_safeTransfer-address-address-uint256-}[`++_safeTransfer(from, to, tokenId)++`] +* {xref-ERC721-_safeTransfer-address-address-uint256-bytes-}[`++_safeTransfer(from, to, tokenId, data)++`] +* {xref-ERC721-_approve-address-uint256-address-}[`++_approve(to, tokenId, auth)++`] +* {xref-ERC721-_approve-address-uint256-address-bool-}[`++_approve(to, tokenId, auth, emitEvent)++`] +* {xref-ERC721-_setApprovalForAll-address-address-bool-}[`++_setApprovalForAll(owner, operator, approved)++`] +* {xref-ERC721-_requireOwned-uint256-}[`++_requireOwned(tokenId)++`] + +[.contract-subindex-inherited] +.IERC721Errors + +[.contract-subindex-inherited] +.IERC721Metadata + +[.contract-subindex-inherited] +.IERC721 + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +[.contract-subindex-inherited] +.IERC2309 + +-- + +[.contract-index] +.Events +-- + +[.contract-subindex-inherited] +.ERC721 + +[.contract-subindex-inherited] +.IERC721Errors + +[.contract-subindex-inherited] +.IERC721Metadata + +[.contract-subindex-inherited] +.IERC721 +* {xref-IERC721-Transfer-address-address-uint256-}[`++Transfer(from, to, tokenId)++`] +* {xref-IERC721-Approval-address-address-uint256-}[`++Approval(owner, approved, tokenId)++`] +* {xref-IERC721-ApprovalForAll-address-address-bool-}[`++ApprovalForAll(owner, operator, approved)++`] + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +[.contract-subindex-inherited] +.IERC2309 +* {xref-IERC2309-ConsecutiveTransfer-uint256-uint256-address-address-}[`++ConsecutiveTransfer(fromTokenId, toTokenId, fromAddress, toAddress)++`] + +-- + +[.contract-index] +.Errors +-- +* {xref-ERC721Consecutive-ERC721ForbiddenBatchMint--}[`++ERC721ForbiddenBatchMint()++`] +* {xref-ERC721Consecutive-ERC721ExceededMaxBatchMint-uint256-uint256-}[`++ERC721ExceededMaxBatchMint(batchSize, maxBatch)++`] +* {xref-ERC721Consecutive-ERC721ForbiddenMint--}[`++ERC721ForbiddenMint()++`] +* {xref-ERC721Consecutive-ERC721ForbiddenBatchBurn--}[`++ERC721ForbiddenBatchBurn()++`] + +[.contract-subindex-inherited] +.ERC721 + +[.contract-subindex-inherited] +.IERC721Errors +* {xref-IERC721Errors-ERC721InvalidOwner-address-}[`++ERC721InvalidOwner(owner)++`] +* {xref-IERC721Errors-ERC721NonexistentToken-uint256-}[`++ERC721NonexistentToken(tokenId)++`] +* {xref-IERC721Errors-ERC721IncorrectOwner-address-uint256-address-}[`++ERC721IncorrectOwner(sender, tokenId, owner)++`] +* {xref-IERC721Errors-ERC721InvalidSender-address-}[`++ERC721InvalidSender(sender)++`] +* {xref-IERC721Errors-ERC721InvalidReceiver-address-}[`++ERC721InvalidReceiver(receiver)++`] +* {xref-IERC721Errors-ERC721InsufficientApproval-address-uint256-}[`++ERC721InsufficientApproval(operator, tokenId)++`] +* {xref-IERC721Errors-ERC721InvalidApprover-address-}[`++ERC721InvalidApprover(approver)++`] +* {xref-IERC721Errors-ERC721InvalidOperator-address-}[`++ERC721InvalidOperator(operator)++`] + +[.contract-subindex-inherited] +.IERC721Metadata + +[.contract-subindex-inherited] +.IERC721 + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +[.contract-subindex-inherited] +.IERC2309 + +-- + +[.contract-item] +[[ERC721Consecutive-_maxBatchSize--]] +==== `[.contract-item-name]#++_maxBatchSize++#++() β†’ uint96++` [.item-kind]#internal# + +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 +tokens. + +NOTE: Overriding the default value of 5000 will not cause on-chain issues, but may result in the asset not being +correctly supported by off-chain indexing services (including marketplaces). + +[.contract-item] +[[ERC721Consecutive-_ownerOf-uint256-]] +==== `[.contract-item-name]#++_ownerOf++#++(uint256 tokenId) β†’ address++` [.item-kind]#internal# + +See {ERC721-_ownerOf}. Override that checks the sequential ownership structure for tokens that have +been minted as part of a batch, and not yet transferred. + +[.contract-item] +[[ERC721Consecutive-_mintConsecutive-address-uint96-]] +==== `[.contract-item-name]#++_mintConsecutive++#++(address to, uint96 batchSize) β†’ uint96++` [.item-kind]#internal# + +Mint a batch of tokens of length `batchSize` for `to`. Returns the token id of the first token minted in the +batch; if `batchSize` is 0, returns the number of consecutive ids minted so far. + +Requirements: + +- `batchSize` must not be greater than {_maxBatchSize}. +- The function is called in the constructor of the contract (directly or indirectly). + +CAUTION: Does not emit a `Transfer` event. This is ERC721 compliant as long as it is done inside of the +constructor, which is enforced by this function. + +CAUTION: Does not invoke `onERC721Received` on the receiver. + +Emits a {IERC2309-ConsecutiveTransfer} event. + +[.contract-item] +[[ERC721Consecutive-_update-address-uint256-address-]] +==== `[.contract-item-name]#++_update++#++(address to, uint256 tokenId, address auth) β†’ address++` [.item-kind]#internal# + +See {ERC721-_update}. Override version that restricts normal minting to after construction. + +WARNING: Using {ERC721Consecutive} prevents minting during construction in favor of {_mintConsecutive}. +After construction, {_mintConsecutive} is no longer available and minting through {_update} becomes available. + +[.contract-item] +[[ERC721Consecutive-_firstConsecutiveId--]] +==== `[.contract-item-name]#++_firstConsecutiveId++#++() β†’ uint96++` [.item-kind]#internal# + +Used to offset the first token id in {_nextConsecutiveId} + +[.contract-item] +[[ERC721Consecutive-ERC721ForbiddenBatchMint--]] +==== `[.contract-item-name]#++ERC721ForbiddenBatchMint++#++()++` [.item-kind]#error# + +Batch mint is restricted to the constructor. +Any batch mint not emitting the {IERC721-Transfer} event outside of the constructor +is non-ERC721 compliant. + +[.contract-item] +[[ERC721Consecutive-ERC721ExceededMaxBatchMint-uint256-uint256-]] +==== `[.contract-item-name]#++ERC721ExceededMaxBatchMint++#++(uint256 batchSize, uint256 maxBatch)++` [.item-kind]#error# + +Exceeds the max amount of mints per batch. + +[.contract-item] +[[ERC721Consecutive-ERC721ForbiddenMint--]] +==== `[.contract-item-name]#++ERC721ForbiddenMint++#++()++` [.item-kind]#error# + +Individual minting is not allowed. + +[.contract-item] +[[ERC721Consecutive-ERC721ForbiddenBatchBurn--]] +==== `[.contract-item-name]#++ERC721ForbiddenBatchBurn++#++()++` [.item-kind]#error# + +Batch burn is not supported. + +:supportsInterface: pass:normal[xref:#ERC721URIStorage-supportsInterface-bytes4-[`++supportsInterface++`]] +:tokenURI: pass:normal[xref:#ERC721URIStorage-tokenURI-uint256-[`++tokenURI++`]] +:_setTokenURI: pass:normal[xref:#ERC721URIStorage-_setTokenURI-uint256-string-[`++_setTokenURI++`]] + +[.contract] +[[ERC721URIStorage]] +=== `++ERC721URIStorage++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/token/ERC721/extensions/ERC721URIStorage.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; +``` + +ERC721 token with storage based token URI management. + +[.contract-index] +.Functions +-- +* {xref-ERC721URIStorage-supportsInterface-bytes4-}[`++supportsInterface(interfaceId)++`] +* {xref-ERC721URIStorage-tokenURI-uint256-}[`++tokenURI(tokenId)++`] +* {xref-ERC721URIStorage-_setTokenURI-uint256-string-}[`++_setTokenURI(tokenId, _tokenURI)++`] + +[.contract-subindex-inherited] +.ERC721 +* {xref-ERC721-balanceOf-address-}[`++balanceOf(owner)++`] +* {xref-ERC721-ownerOf-uint256-}[`++ownerOf(tokenId)++`] +* {xref-ERC721-name--}[`++name()++`] +* {xref-ERC721-symbol--}[`++symbol()++`] +* {xref-ERC721-_baseURI--}[`++_baseURI()++`] +* {xref-ERC721-approve-address-uint256-}[`++approve(to, tokenId)++`] +* {xref-ERC721-getApproved-uint256-}[`++getApproved(tokenId)++`] +* {xref-ERC721-setApprovalForAll-address-bool-}[`++setApprovalForAll(operator, approved)++`] +* {xref-ERC721-isApprovedForAll-address-address-}[`++isApprovedForAll(owner, operator)++`] +* {xref-ERC721-transferFrom-address-address-uint256-}[`++transferFrom(from, to, tokenId)++`] +* {xref-ERC721-safeTransferFrom-address-address-uint256-}[`++safeTransferFrom(from, to, tokenId)++`] +* {xref-ERC721-safeTransferFrom-address-address-uint256-bytes-}[`++safeTransferFrom(from, to, tokenId, data)++`] +* {xref-ERC721-_ownerOf-uint256-}[`++_ownerOf(tokenId)++`] +* {xref-ERC721-_getApproved-uint256-}[`++_getApproved(tokenId)++`] +* {xref-ERC721-_isAuthorized-address-address-uint256-}[`++_isAuthorized(owner, spender, tokenId)++`] +* {xref-ERC721-_checkAuthorized-address-address-uint256-}[`++_checkAuthorized(owner, spender, tokenId)++`] +* {xref-ERC721-_increaseBalance-address-uint128-}[`++_increaseBalance(account, value)++`] +* {xref-ERC721-_update-address-uint256-address-}[`++_update(to, tokenId, auth)++`] +* {xref-ERC721-_mint-address-uint256-}[`++_mint(to, tokenId)++`] +* {xref-ERC721-_safeMint-address-uint256-}[`++_safeMint(to, tokenId)++`] +* {xref-ERC721-_safeMint-address-uint256-bytes-}[`++_safeMint(to, tokenId, data)++`] +* {xref-ERC721-_burn-uint256-}[`++_burn(tokenId)++`] +* {xref-ERC721-_transfer-address-address-uint256-}[`++_transfer(from, to, tokenId)++`] +* {xref-ERC721-_safeTransfer-address-address-uint256-}[`++_safeTransfer(from, to, tokenId)++`] +* {xref-ERC721-_safeTransfer-address-address-uint256-bytes-}[`++_safeTransfer(from, to, tokenId, data)++`] +* {xref-ERC721-_approve-address-uint256-address-}[`++_approve(to, tokenId, auth)++`] +* {xref-ERC721-_approve-address-uint256-address-bool-}[`++_approve(to, tokenId, auth, emitEvent)++`] +* {xref-ERC721-_setApprovalForAll-address-address-bool-}[`++_setApprovalForAll(owner, operator, approved)++`] +* {xref-ERC721-_requireOwned-uint256-}[`++_requireOwned(tokenId)++`] + +[.contract-subindex-inherited] +.IERC721Errors + +[.contract-subindex-inherited] +.IERC721Metadata + +[.contract-subindex-inherited] +.IERC4906 + +[.contract-subindex-inherited] +.IERC721 + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-index] +.Events +-- + +[.contract-subindex-inherited] +.ERC721 + +[.contract-subindex-inherited] +.IERC721Errors + +[.contract-subindex-inherited] +.IERC721Metadata + +[.contract-subindex-inherited] +.IERC4906 +* {xref-IERC4906-MetadataUpdate-uint256-}[`++MetadataUpdate(_tokenId)++`] +* {xref-IERC4906-BatchMetadataUpdate-uint256-uint256-}[`++BatchMetadataUpdate(_fromTokenId, _toTokenId)++`] + +[.contract-subindex-inherited] +.IERC721 +* {xref-IERC721-Transfer-address-address-uint256-}[`++Transfer(from, to, tokenId)++`] +* {xref-IERC721-Approval-address-address-uint256-}[`++Approval(owner, approved, tokenId)++`] +* {xref-IERC721-ApprovalForAll-address-address-bool-}[`++ApprovalForAll(owner, operator, approved)++`] + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-index] +.Errors +-- + +[.contract-subindex-inherited] +.ERC721 + +[.contract-subindex-inherited] +.IERC721Errors +* {xref-IERC721Errors-ERC721InvalidOwner-address-}[`++ERC721InvalidOwner(owner)++`] +* {xref-IERC721Errors-ERC721NonexistentToken-uint256-}[`++ERC721NonexistentToken(tokenId)++`] +* {xref-IERC721Errors-ERC721IncorrectOwner-address-uint256-address-}[`++ERC721IncorrectOwner(sender, tokenId, owner)++`] +* {xref-IERC721Errors-ERC721InvalidSender-address-}[`++ERC721InvalidSender(sender)++`] +* {xref-IERC721Errors-ERC721InvalidReceiver-address-}[`++ERC721InvalidReceiver(receiver)++`] +* {xref-IERC721Errors-ERC721InsufficientApproval-address-uint256-}[`++ERC721InsufficientApproval(operator, tokenId)++`] +* {xref-IERC721Errors-ERC721InvalidApprover-address-}[`++ERC721InvalidApprover(approver)++`] +* {xref-IERC721Errors-ERC721InvalidOperator-address-}[`++ERC721InvalidOperator(operator)++`] + +[.contract-subindex-inherited] +.IERC721Metadata + +[.contract-subindex-inherited] +.IERC4906 + +[.contract-subindex-inherited] +.IERC721 + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-item] +[[ERC721URIStorage-supportsInterface-bytes4-]] +==== `[.contract-item-name]#++supportsInterface++#++(bytes4 interfaceId) β†’ bool++` [.item-kind]#public# + +See {IERC165-supportsInterface} + +[.contract-item] +[[ERC721URIStorage-tokenURI-uint256-]] +==== `[.contract-item-name]#++tokenURI++#++(uint256 tokenId) β†’ string++` [.item-kind]#public# + +See {IERC721Metadata-tokenURI}. + +[.contract-item] +[[ERC721URIStorage-_setTokenURI-uint256-string-]] +==== `[.contract-item-name]#++_setTokenURI++#++(uint256 tokenId, string _tokenURI)++` [.item-kind]#internal# + +Sets `_tokenURI` as the tokenURI of `tokenId`. + +Emits {MetadataUpdate}. + +:_update: pass:normal[xref:#ERC721Votes-_update-address-uint256-address-[`++_update++`]] +:_getVotingUnits: pass:normal[xref:#ERC721Votes-_getVotingUnits-address-[`++_getVotingUnits++`]] +:_increaseBalance: pass:normal[xref:#ERC721Votes-_increaseBalance-address-uint128-[`++_increaseBalance++`]] + +[.contract] +[[ERC721Votes]] +=== `++ERC721Votes++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/token/ERC721/extensions/ERC721Votes.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Votes.sol"; +``` + +Extension of ERC721 to support voting and delegation as implemented by {Votes}, where each individual NFT counts +as 1 vote unit. + +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. + +[.contract-index] +.Functions +-- +* {xref-ERC721Votes-_update-address-uint256-address-}[`++_update(to, tokenId, auth)++`] +* {xref-ERC721Votes-_getVotingUnits-address-}[`++_getVotingUnits(account)++`] +* {xref-ERC721Votes-_increaseBalance-address-uint128-}[`++_increaseBalance(account, amount)++`] + +[.contract-subindex-inherited] +.Votes +* {xref-Votes-clock--}[`++clock()++`] +* {xref-Votes-CLOCK_MODE--}[`++CLOCK_MODE()++`] +* {xref-Votes-getVotes-address-}[`++getVotes(account)++`] +* {xref-Votes-getPastVotes-address-uint256-}[`++getPastVotes(account, timepoint)++`] +* {xref-Votes-getPastTotalSupply-uint256-}[`++getPastTotalSupply(timepoint)++`] +* {xref-Votes-_getTotalSupply--}[`++_getTotalSupply()++`] +* {xref-Votes-delegates-address-}[`++delegates(account)++`] +* {xref-Votes-delegate-address-}[`++delegate(delegatee)++`] +* {xref-Votes-delegateBySig-address-uint256-uint256-uint8-bytes32-bytes32-}[`++delegateBySig(delegatee, nonce, expiry, v, r, s)++`] +* {xref-Votes-_delegate-address-address-}[`++_delegate(account, delegatee)++`] +* {xref-Votes-_transferVotingUnits-address-address-uint256-}[`++_transferVotingUnits(from, to, amount)++`] +* {xref-Votes-_numCheckpoints-address-}[`++_numCheckpoints(account)++`] +* {xref-Votes-_checkpoints-address-uint32-}[`++_checkpoints(account, pos)++`] + +[.contract-subindex-inherited] +.IERC5805 + +[.contract-subindex-inherited] +.IVotes + +[.contract-subindex-inherited] +.IERC6372 + +[.contract-subindex-inherited] +.Nonces +* {xref-Nonces-nonces-address-}[`++nonces(owner)++`] +* {xref-Nonces-_useNonce-address-}[`++_useNonce(owner)++`] +* {xref-Nonces-_useCheckedNonce-address-uint256-}[`++_useCheckedNonce(owner, nonce)++`] + +[.contract-subindex-inherited] +.EIP712 +* {xref-EIP712-_domainSeparatorV4--}[`++_domainSeparatorV4()++`] +* {xref-EIP712-_hashTypedDataV4-bytes32-}[`++_hashTypedDataV4(structHash)++`] +* {xref-EIP712-eip712Domain--}[`++eip712Domain()++`] +* {xref-EIP712-_EIP712Name--}[`++_EIP712Name()++`] +* {xref-EIP712-_EIP712Version--}[`++_EIP712Version()++`] + +[.contract-subindex-inherited] +.IERC5267 + +[.contract-subindex-inherited] +.ERC721 +* {xref-ERC721-supportsInterface-bytes4-}[`++supportsInterface(interfaceId)++`] +* {xref-ERC721-balanceOf-address-}[`++balanceOf(owner)++`] +* {xref-ERC721-ownerOf-uint256-}[`++ownerOf(tokenId)++`] +* {xref-ERC721-name--}[`++name()++`] +* {xref-ERC721-symbol--}[`++symbol()++`] +* {xref-ERC721-tokenURI-uint256-}[`++tokenURI(tokenId)++`] +* {xref-ERC721-_baseURI--}[`++_baseURI()++`] +* {xref-ERC721-approve-address-uint256-}[`++approve(to, tokenId)++`] +* {xref-ERC721-getApproved-uint256-}[`++getApproved(tokenId)++`] +* {xref-ERC721-setApprovalForAll-address-bool-}[`++setApprovalForAll(operator, approved)++`] +* {xref-ERC721-isApprovedForAll-address-address-}[`++isApprovedForAll(owner, operator)++`] +* {xref-ERC721-transferFrom-address-address-uint256-}[`++transferFrom(from, to, tokenId)++`] +* {xref-ERC721-safeTransferFrom-address-address-uint256-}[`++safeTransferFrom(from, to, tokenId)++`] +* {xref-ERC721-safeTransferFrom-address-address-uint256-bytes-}[`++safeTransferFrom(from, to, tokenId, data)++`] +* {xref-ERC721-_ownerOf-uint256-}[`++_ownerOf(tokenId)++`] +* {xref-ERC721-_getApproved-uint256-}[`++_getApproved(tokenId)++`] +* {xref-ERC721-_isAuthorized-address-address-uint256-}[`++_isAuthorized(owner, spender, tokenId)++`] +* {xref-ERC721-_checkAuthorized-address-address-uint256-}[`++_checkAuthorized(owner, spender, tokenId)++`] +* {xref-ERC721-_mint-address-uint256-}[`++_mint(to, tokenId)++`] +* {xref-ERC721-_safeMint-address-uint256-}[`++_safeMint(to, tokenId)++`] +* {xref-ERC721-_safeMint-address-uint256-bytes-}[`++_safeMint(to, tokenId, data)++`] +* {xref-ERC721-_burn-uint256-}[`++_burn(tokenId)++`] +* {xref-ERC721-_transfer-address-address-uint256-}[`++_transfer(from, to, tokenId)++`] +* {xref-ERC721-_safeTransfer-address-address-uint256-}[`++_safeTransfer(from, to, tokenId)++`] +* {xref-ERC721-_safeTransfer-address-address-uint256-bytes-}[`++_safeTransfer(from, to, tokenId, data)++`] +* {xref-ERC721-_approve-address-uint256-address-}[`++_approve(to, tokenId, auth)++`] +* {xref-ERC721-_approve-address-uint256-address-bool-}[`++_approve(to, tokenId, auth, emitEvent)++`] +* {xref-ERC721-_setApprovalForAll-address-address-bool-}[`++_setApprovalForAll(owner, operator, approved)++`] +* {xref-ERC721-_requireOwned-uint256-}[`++_requireOwned(tokenId)++`] + +[.contract-subindex-inherited] +.IERC721Errors + +[.contract-subindex-inherited] +.IERC721Metadata + +[.contract-subindex-inherited] +.IERC721 + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-index] +.Events +-- + +[.contract-subindex-inherited] +.Votes + +[.contract-subindex-inherited] +.IERC5805 + +[.contract-subindex-inherited] +.IVotes +* {xref-IVotes-DelegateChanged-address-address-address-}[`++DelegateChanged(delegator, fromDelegate, toDelegate)++`] +* {xref-IVotes-DelegateVotesChanged-address-uint256-uint256-}[`++DelegateVotesChanged(delegate, previousVotes, newVotes)++`] + +[.contract-subindex-inherited] +.IERC6372 + +[.contract-subindex-inherited] +.Nonces + +[.contract-subindex-inherited] +.EIP712 + +[.contract-subindex-inherited] +.IERC5267 +* {xref-IERC5267-EIP712DomainChanged--}[`++EIP712DomainChanged()++`] + +[.contract-subindex-inherited] +.ERC721 + +[.contract-subindex-inherited] +.IERC721Errors + +[.contract-subindex-inherited] +.IERC721Metadata + +[.contract-subindex-inherited] +.IERC721 +* {xref-IERC721-Transfer-address-address-uint256-}[`++Transfer(from, to, tokenId)++`] +* {xref-IERC721-Approval-address-address-uint256-}[`++Approval(owner, approved, tokenId)++`] +* {xref-IERC721-ApprovalForAll-address-address-bool-}[`++ApprovalForAll(owner, operator, approved)++`] + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-index] +.Errors +-- + +[.contract-subindex-inherited] +.Votes +* {xref-Votes-ERC6372InconsistentClock--}[`++ERC6372InconsistentClock()++`] +* {xref-Votes-ERC5805FutureLookup-uint256-uint48-}[`++ERC5805FutureLookup(timepoint, clock)++`] + +[.contract-subindex-inherited] +.IERC5805 + +[.contract-subindex-inherited] +.IVotes +* {xref-IVotes-VotesExpiredSignature-uint256-}[`++VotesExpiredSignature(expiry)++`] + +[.contract-subindex-inherited] +.IERC6372 + +[.contract-subindex-inherited] +.Nonces +* {xref-Nonces-InvalidAccountNonce-address-uint256-}[`++InvalidAccountNonce(account, currentNonce)++`] + +[.contract-subindex-inherited] +.EIP712 + +[.contract-subindex-inherited] +.IERC5267 + +[.contract-subindex-inherited] +.ERC721 + +[.contract-subindex-inherited] +.IERC721Errors +* {xref-IERC721Errors-ERC721InvalidOwner-address-}[`++ERC721InvalidOwner(owner)++`] +* {xref-IERC721Errors-ERC721NonexistentToken-uint256-}[`++ERC721NonexistentToken(tokenId)++`] +* {xref-IERC721Errors-ERC721IncorrectOwner-address-uint256-address-}[`++ERC721IncorrectOwner(sender, tokenId, owner)++`] +* {xref-IERC721Errors-ERC721InvalidSender-address-}[`++ERC721InvalidSender(sender)++`] +* {xref-IERC721Errors-ERC721InvalidReceiver-address-}[`++ERC721InvalidReceiver(receiver)++`] +* {xref-IERC721Errors-ERC721InsufficientApproval-address-uint256-}[`++ERC721InsufficientApproval(operator, tokenId)++`] +* {xref-IERC721Errors-ERC721InvalidApprover-address-}[`++ERC721InvalidApprover(approver)++`] +* {xref-IERC721Errors-ERC721InvalidOperator-address-}[`++ERC721InvalidOperator(operator)++`] + +[.contract-subindex-inherited] +.IERC721Metadata + +[.contract-subindex-inherited] +.IERC721 + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-item] +[[ERC721Votes-_update-address-uint256-address-]] +==== `[.contract-item-name]#++_update++#++(address to, uint256 tokenId, address auth) β†’ address++` [.item-kind]#internal# + +See {ERC721-_update}. Adjusts votes when tokens are transferred. + +Emits a {IVotes-DelegateVotesChanged} event. + +[.contract-item] +[[ERC721Votes-_getVotingUnits-address-]] +==== `[.contract-item-name]#++_getVotingUnits++#++(address account) β†’ uint256++` [.item-kind]#internal# + +Returns the balance of `account`. + +WARNING: Overriding this function will likely result in incorrect vote tracking. + +[.contract-item] +[[ERC721Votes-_increaseBalance-address-uint128-]] +==== `[.contract-item-name]#++_increaseBalance++#++(address account, uint128 amount)++` [.item-kind]#internal# + +See {ERC721-_increaseBalance}. We need that to account tokens that were minted in batch. + +:supportsInterface: pass:normal[xref:#ERC721Royalty-supportsInterface-bytes4-[`++supportsInterface++`]] + +[.contract] +[[ERC721Royalty]] +=== `++ERC721Royalty++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/token/ERC721/extensions/ERC721Royalty.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Royalty.sol"; +``` + +Extension of ERC721 with the ERC2981 NFT Royalty Standard, a standardized way to retrieve royalty payment +information. + +Royalty information can be specified globally for all token ids via {ERC2981-_setDefaultRoyalty}, and/or individually +for specific token ids via {ERC2981-_setTokenRoyalty}. The latter takes precedence over the first. + +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. + +[.contract-index] +.Functions +-- +* {xref-ERC721Royalty-supportsInterface-bytes4-}[`++supportsInterface(interfaceId)++`] + +[.contract-subindex-inherited] +.ERC721 +* {xref-ERC721-balanceOf-address-}[`++balanceOf(owner)++`] +* {xref-ERC721-ownerOf-uint256-}[`++ownerOf(tokenId)++`] +* {xref-ERC721-name--}[`++name()++`] +* {xref-ERC721-symbol--}[`++symbol()++`] +* {xref-ERC721-tokenURI-uint256-}[`++tokenURI(tokenId)++`] +* {xref-ERC721-_baseURI--}[`++_baseURI()++`] +* {xref-ERC721-approve-address-uint256-}[`++approve(to, tokenId)++`] +* {xref-ERC721-getApproved-uint256-}[`++getApproved(tokenId)++`] +* {xref-ERC721-setApprovalForAll-address-bool-}[`++setApprovalForAll(operator, approved)++`] +* {xref-ERC721-isApprovedForAll-address-address-}[`++isApprovedForAll(owner, operator)++`] +* {xref-ERC721-transferFrom-address-address-uint256-}[`++transferFrom(from, to, tokenId)++`] +* {xref-ERC721-safeTransferFrom-address-address-uint256-}[`++safeTransferFrom(from, to, tokenId)++`] +* {xref-ERC721-safeTransferFrom-address-address-uint256-bytes-}[`++safeTransferFrom(from, to, tokenId, data)++`] +* {xref-ERC721-_ownerOf-uint256-}[`++_ownerOf(tokenId)++`] +* {xref-ERC721-_getApproved-uint256-}[`++_getApproved(tokenId)++`] +* {xref-ERC721-_isAuthorized-address-address-uint256-}[`++_isAuthorized(owner, spender, tokenId)++`] +* {xref-ERC721-_checkAuthorized-address-address-uint256-}[`++_checkAuthorized(owner, spender, tokenId)++`] +* {xref-ERC721-_increaseBalance-address-uint128-}[`++_increaseBalance(account, value)++`] +* {xref-ERC721-_update-address-uint256-address-}[`++_update(to, tokenId, auth)++`] +* {xref-ERC721-_mint-address-uint256-}[`++_mint(to, tokenId)++`] +* {xref-ERC721-_safeMint-address-uint256-}[`++_safeMint(to, tokenId)++`] +* {xref-ERC721-_safeMint-address-uint256-bytes-}[`++_safeMint(to, tokenId, data)++`] +* {xref-ERC721-_burn-uint256-}[`++_burn(tokenId)++`] +* {xref-ERC721-_transfer-address-address-uint256-}[`++_transfer(from, to, tokenId)++`] +* {xref-ERC721-_safeTransfer-address-address-uint256-}[`++_safeTransfer(from, to, tokenId)++`] +* {xref-ERC721-_safeTransfer-address-address-uint256-bytes-}[`++_safeTransfer(from, to, tokenId, data)++`] +* {xref-ERC721-_approve-address-uint256-address-}[`++_approve(to, tokenId, auth)++`] +* {xref-ERC721-_approve-address-uint256-address-bool-}[`++_approve(to, tokenId, auth, emitEvent)++`] +* {xref-ERC721-_setApprovalForAll-address-address-bool-}[`++_setApprovalForAll(owner, operator, approved)++`] +* {xref-ERC721-_requireOwned-uint256-}[`++_requireOwned(tokenId)++`] + +[.contract-subindex-inherited] +.IERC721Errors + +[.contract-subindex-inherited] +.IERC721Metadata + +[.contract-subindex-inherited] +.IERC721 + +[.contract-subindex-inherited] +.ERC2981 +* {xref-ERC2981-royaltyInfo-uint256-uint256-}[`++royaltyInfo(tokenId, salePrice)++`] +* {xref-ERC2981-_feeDenominator--}[`++_feeDenominator()++`] +* {xref-ERC2981-_setDefaultRoyalty-address-uint96-}[`++_setDefaultRoyalty(receiver, feeNumerator)++`] +* {xref-ERC2981-_deleteDefaultRoyalty--}[`++_deleteDefaultRoyalty()++`] +* {xref-ERC2981-_setTokenRoyalty-uint256-address-uint96-}[`++_setTokenRoyalty(tokenId, receiver, feeNumerator)++`] +* {xref-ERC2981-_resetTokenRoyalty-uint256-}[`++_resetTokenRoyalty(tokenId)++`] + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC2981 + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-index] +.Events +-- + +[.contract-subindex-inherited] +.ERC721 + +[.contract-subindex-inherited] +.IERC721Errors + +[.contract-subindex-inherited] +.IERC721Metadata + +[.contract-subindex-inherited] +.IERC721 +* {xref-IERC721-Transfer-address-address-uint256-}[`++Transfer(from, to, tokenId)++`] +* {xref-IERC721-Approval-address-address-uint256-}[`++Approval(owner, approved, tokenId)++`] +* {xref-IERC721-ApprovalForAll-address-address-bool-}[`++ApprovalForAll(owner, operator, approved)++`] + +[.contract-subindex-inherited] +.ERC2981 + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC2981 + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-index] +.Errors +-- + +[.contract-subindex-inherited] +.ERC721 + +[.contract-subindex-inherited] +.IERC721Errors +* {xref-IERC721Errors-ERC721InvalidOwner-address-}[`++ERC721InvalidOwner(owner)++`] +* {xref-IERC721Errors-ERC721NonexistentToken-uint256-}[`++ERC721NonexistentToken(tokenId)++`] +* {xref-IERC721Errors-ERC721IncorrectOwner-address-uint256-address-}[`++ERC721IncorrectOwner(sender, tokenId, owner)++`] +* {xref-IERC721Errors-ERC721InvalidSender-address-}[`++ERC721InvalidSender(sender)++`] +* {xref-IERC721Errors-ERC721InvalidReceiver-address-}[`++ERC721InvalidReceiver(receiver)++`] +* {xref-IERC721Errors-ERC721InsufficientApproval-address-uint256-}[`++ERC721InsufficientApproval(operator, tokenId)++`] +* {xref-IERC721Errors-ERC721InvalidApprover-address-}[`++ERC721InvalidApprover(approver)++`] +* {xref-IERC721Errors-ERC721InvalidOperator-address-}[`++ERC721InvalidOperator(operator)++`] + +[.contract-subindex-inherited] +.IERC721Metadata + +[.contract-subindex-inherited] +.IERC721 + +[.contract-subindex-inherited] +.ERC2981 +* {xref-ERC2981-ERC2981InvalidDefaultRoyalty-uint256-uint256-}[`++ERC2981InvalidDefaultRoyalty(numerator, denominator)++`] +* {xref-ERC2981-ERC2981InvalidDefaultRoyaltyReceiver-address-}[`++ERC2981InvalidDefaultRoyaltyReceiver(receiver)++`] +* {xref-ERC2981-ERC2981InvalidTokenRoyalty-uint256-uint256-uint256-}[`++ERC2981InvalidTokenRoyalty(tokenId, numerator, denominator)++`] +* {xref-ERC2981-ERC2981InvalidTokenRoyaltyReceiver-uint256-address-}[`++ERC2981InvalidTokenRoyaltyReceiver(tokenId, receiver)++`] + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC2981 + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-item] +[[ERC721Royalty-supportsInterface-bytes4-]] +==== `[.contract-item-name]#++supportsInterface++#++(bytes4 interfaceId) β†’ bool++` [.item-kind]#public# + +See {IERC165-supportsInterface}. + +:ERC721UnsupportedToken: pass:normal[xref:#ERC721Wrapper-ERC721UnsupportedToken-address-[`++ERC721UnsupportedToken++`]] +:constructor: pass:normal[xref:#ERC721Wrapper-constructor-contract-IERC721-[`++constructor++`]] +:depositFor: pass:normal[xref:#ERC721Wrapper-depositFor-address-uint256---[`++depositFor++`]] +:withdrawTo: pass:normal[xref:#ERC721Wrapper-withdrawTo-address-uint256---[`++withdrawTo++`]] +:onERC721Received: pass:normal[xref:#ERC721Wrapper-onERC721Received-address-address-uint256-bytes-[`++onERC721Received++`]] +:_recover: pass:normal[xref:#ERC721Wrapper-_recover-address-uint256-[`++_recover++`]] +:underlying: pass:normal[xref:#ERC721Wrapper-underlying--[`++underlying++`]] + +[.contract] +[[ERC721Wrapper]] +=== `++ERC721Wrapper++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/token/ERC721/extensions/ERC721Wrapper.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Wrapper.sol"; +``` + +Extension of the ERC721 token contract to support token wrapping. + +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. + +[.contract-index] +.Functions +-- +* {xref-ERC721Wrapper-constructor-contract-IERC721-}[`++constructor(underlyingToken)++`] +* {xref-ERC721Wrapper-depositFor-address-uint256---}[`++depositFor(account, tokenIds)++`] +* {xref-ERC721Wrapper-withdrawTo-address-uint256---}[`++withdrawTo(account, tokenIds)++`] +* {xref-ERC721Wrapper-onERC721Received-address-address-uint256-bytes-}[`++onERC721Received(, from, tokenId, )++`] +* {xref-ERC721Wrapper-_recover-address-uint256-}[`++_recover(account, tokenId)++`] +* {xref-ERC721Wrapper-underlying--}[`++underlying()++`] + +[.contract-subindex-inherited] +.IERC721Receiver + +[.contract-subindex-inherited] +.ERC721 +* {xref-ERC721-supportsInterface-bytes4-}[`++supportsInterface(interfaceId)++`] +* {xref-ERC721-balanceOf-address-}[`++balanceOf(owner)++`] +* {xref-ERC721-ownerOf-uint256-}[`++ownerOf(tokenId)++`] +* {xref-ERC721-name--}[`++name()++`] +* {xref-ERC721-symbol--}[`++symbol()++`] +* {xref-ERC721-tokenURI-uint256-}[`++tokenURI(tokenId)++`] +* {xref-ERC721-_baseURI--}[`++_baseURI()++`] +* {xref-ERC721-approve-address-uint256-}[`++approve(to, tokenId)++`] +* {xref-ERC721-getApproved-uint256-}[`++getApproved(tokenId)++`] +* {xref-ERC721-setApprovalForAll-address-bool-}[`++setApprovalForAll(operator, approved)++`] +* {xref-ERC721-isApprovedForAll-address-address-}[`++isApprovedForAll(owner, operator)++`] +* {xref-ERC721-transferFrom-address-address-uint256-}[`++transferFrom(from, to, tokenId)++`] +* {xref-ERC721-safeTransferFrom-address-address-uint256-}[`++safeTransferFrom(from, to, tokenId)++`] +* {xref-ERC721-safeTransferFrom-address-address-uint256-bytes-}[`++safeTransferFrom(from, to, tokenId, data)++`] +* {xref-ERC721-_ownerOf-uint256-}[`++_ownerOf(tokenId)++`] +* {xref-ERC721-_getApproved-uint256-}[`++_getApproved(tokenId)++`] +* {xref-ERC721-_isAuthorized-address-address-uint256-}[`++_isAuthorized(owner, spender, tokenId)++`] +* {xref-ERC721-_checkAuthorized-address-address-uint256-}[`++_checkAuthorized(owner, spender, tokenId)++`] +* {xref-ERC721-_increaseBalance-address-uint128-}[`++_increaseBalance(account, value)++`] +* {xref-ERC721-_update-address-uint256-address-}[`++_update(to, tokenId, auth)++`] +* {xref-ERC721-_mint-address-uint256-}[`++_mint(to, tokenId)++`] +* {xref-ERC721-_safeMint-address-uint256-}[`++_safeMint(to, tokenId)++`] +* {xref-ERC721-_safeMint-address-uint256-bytes-}[`++_safeMint(to, tokenId, data)++`] +* {xref-ERC721-_burn-uint256-}[`++_burn(tokenId)++`] +* {xref-ERC721-_transfer-address-address-uint256-}[`++_transfer(from, to, tokenId)++`] +* {xref-ERC721-_safeTransfer-address-address-uint256-}[`++_safeTransfer(from, to, tokenId)++`] +* {xref-ERC721-_safeTransfer-address-address-uint256-bytes-}[`++_safeTransfer(from, to, tokenId, data)++`] +* {xref-ERC721-_approve-address-uint256-address-}[`++_approve(to, tokenId, auth)++`] +* {xref-ERC721-_approve-address-uint256-address-bool-}[`++_approve(to, tokenId, auth, emitEvent)++`] +* {xref-ERC721-_setApprovalForAll-address-address-bool-}[`++_setApprovalForAll(owner, operator, approved)++`] +* {xref-ERC721-_requireOwned-uint256-}[`++_requireOwned(tokenId)++`] + +[.contract-subindex-inherited] +.IERC721Errors + +[.contract-subindex-inherited] +.IERC721Metadata + +[.contract-subindex-inherited] +.IERC721 + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-index] +.Events +-- + +[.contract-subindex-inherited] +.IERC721Receiver + +[.contract-subindex-inherited] +.ERC721 + +[.contract-subindex-inherited] +.IERC721Errors + +[.contract-subindex-inherited] +.IERC721Metadata + +[.contract-subindex-inherited] +.IERC721 +* {xref-IERC721-Transfer-address-address-uint256-}[`++Transfer(from, to, tokenId)++`] +* {xref-IERC721-Approval-address-address-uint256-}[`++Approval(owner, approved, tokenId)++`] +* {xref-IERC721-ApprovalForAll-address-address-bool-}[`++ApprovalForAll(owner, operator, approved)++`] + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-index] +.Errors +-- +* {xref-ERC721Wrapper-ERC721UnsupportedToken-address-}[`++ERC721UnsupportedToken(token)++`] + +[.contract-subindex-inherited] +.IERC721Receiver + +[.contract-subindex-inherited] +.ERC721 + +[.contract-subindex-inherited] +.IERC721Errors +* {xref-IERC721Errors-ERC721InvalidOwner-address-}[`++ERC721InvalidOwner(owner)++`] +* {xref-IERC721Errors-ERC721NonexistentToken-uint256-}[`++ERC721NonexistentToken(tokenId)++`] +* {xref-IERC721Errors-ERC721IncorrectOwner-address-uint256-address-}[`++ERC721IncorrectOwner(sender, tokenId, owner)++`] +* {xref-IERC721Errors-ERC721InvalidSender-address-}[`++ERC721InvalidSender(sender)++`] +* {xref-IERC721Errors-ERC721InvalidReceiver-address-}[`++ERC721InvalidReceiver(receiver)++`] +* {xref-IERC721Errors-ERC721InsufficientApproval-address-uint256-}[`++ERC721InsufficientApproval(operator, tokenId)++`] +* {xref-IERC721Errors-ERC721InvalidApprover-address-}[`++ERC721InvalidApprover(approver)++`] +* {xref-IERC721Errors-ERC721InvalidOperator-address-}[`++ERC721InvalidOperator(operator)++`] + +[.contract-subindex-inherited] +.IERC721Metadata + +[.contract-subindex-inherited] +.IERC721 + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-item] +[[ERC721Wrapper-constructor-contract-IERC721-]] +==== `[.contract-item-name]#++constructor++#++(contract IERC721 underlyingToken)++` [.item-kind]#internal# + +[.contract-item] +[[ERC721Wrapper-depositFor-address-uint256---]] +==== `[.contract-item-name]#++depositFor++#++(address account, uint256[] tokenIds) β†’ bool++` [.item-kind]#public# + +Allow a user to deposit underlying tokens and mint the corresponding tokenIds. + +[.contract-item] +[[ERC721Wrapper-withdrawTo-address-uint256---]] +==== `[.contract-item-name]#++withdrawTo++#++(address account, uint256[] tokenIds) β†’ bool++` [.item-kind]#public# + +Allow a user to burn wrapped tokens and withdraw the corresponding tokenIds of the underlying tokens. + +[.contract-item] +[[ERC721Wrapper-onERC721Received-address-address-uint256-bytes-]] +==== `[.contract-item-name]#++onERC721Received++#++(address, address from, uint256 tokenId, bytes) β†’ bytes4++` [.item-kind]#public# + +Overrides {IERC721Receiver-onERC721Received} to allow minting on direct ERC721 transfers to +this contract. + +In case there's data attached, it validates that the operator is this contract, so only trusted data +is accepted from {depositFor}. + +WARNING: Doesn't work with unsafe transfers (eg. {IERC721-transferFrom}). Use {ERC721Wrapper-_recover} +for recovering in that scenario. + +[.contract-item] +[[ERC721Wrapper-_recover-address-uint256-]] +==== `[.contract-item-name]#++_recover++#++(address account, uint256 tokenId) β†’ uint256++` [.item-kind]#internal# + +Mint a wrapped token to cover any underlyingToken that would have been transferred by mistake. Internal +function that can be exposed with access control if desired. + +[.contract-item] +[[ERC721Wrapper-underlying--]] +==== `[.contract-item-name]#++underlying++#++() β†’ contract IERC721++` [.item-kind]#public# + +Returns the underlying token. + +[.contract-item] +[[ERC721Wrapper-ERC721UnsupportedToken-address-]] +==== `[.contract-item-name]#++ERC721UnsupportedToken++#++(address token)++` [.item-kind]#error# + +The received ERC721 token couldn't be wrapped. + +== Utilities + +:onERC721Received: pass:normal[xref:#ERC721Holder-onERC721Received-address-address-uint256-bytes-[`++onERC721Received++`]] + +[.contract] +[[ERC721Holder]] +=== `++ERC721Holder++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/token/ERC721/utils/ERC721Holder.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol"; +``` + +Implementation of the {IERC721Receiver} interface. + +Accepts all token transfers. +Make sure the contract is able to use its token with {IERC721-safeTransferFrom}, {IERC721-approve} or +{IERC721-setApprovalForAll}. + +[.contract-index] +.Functions +-- +* {xref-ERC721Holder-onERC721Received-address-address-uint256-bytes-}[`++onERC721Received(, , , )++`] + +[.contract-subindex-inherited] +.IERC721Receiver + +-- + +[.contract-item] +[[ERC721Holder-onERC721Received-address-address-uint256-bytes-]] +==== `[.contract-item-name]#++onERC721Received++#++(address, address, uint256, bytes) β†’ bytes4++` [.item-kind]#public# + +See {IERC721Receiver-onERC721Received}. + +Always returns `IERC721Receiver.onERC721Received.selector`. + diff --git a/docs/modules/api/pages/token/common.adoc b/docs/modules/api/pages/token/common.adoc new file mode 100644 index 000000000..719c3869f --- /dev/null +++ b/docs/modules/api/pages/token/common.adoc @@ -0,0 +1,178 @@ +:github-icon: pass:[] +:ERC2981: pass:normal[xref:token/common.adoc#ERC2981[`ERC2981`]] +:ERC721Royalty: pass:normal[xref:token/ERC721.adoc#ERC721Royalty[`ERC721Royalty`]] +:xref-ERC2981-supportsInterface-bytes4-: xref:token/common.adoc#ERC2981-supportsInterface-bytes4- +:xref-ERC2981-royaltyInfo-uint256-uint256-: xref:token/common.adoc#ERC2981-royaltyInfo-uint256-uint256- +:xref-ERC2981-_feeDenominator--: xref:token/common.adoc#ERC2981-_feeDenominator-- +:xref-ERC2981-_setDefaultRoyalty-address-uint96-: xref:token/common.adoc#ERC2981-_setDefaultRoyalty-address-uint96- +:xref-ERC2981-_deleteDefaultRoyalty--: xref:token/common.adoc#ERC2981-_deleteDefaultRoyalty-- +:xref-ERC2981-_setTokenRoyalty-uint256-address-uint96-: xref:token/common.adoc#ERC2981-_setTokenRoyalty-uint256-address-uint96- +:xref-ERC2981-_resetTokenRoyalty-uint256-: xref:token/common.adoc#ERC2981-_resetTokenRoyalty-uint256- +:xref-ERC2981-ERC2981InvalidDefaultRoyalty-uint256-uint256-: xref:token/common.adoc#ERC2981-ERC2981InvalidDefaultRoyalty-uint256-uint256- +:xref-ERC2981-ERC2981InvalidDefaultRoyaltyReceiver-address-: xref:token/common.adoc#ERC2981-ERC2981InvalidDefaultRoyaltyReceiver-address- +:xref-ERC2981-ERC2981InvalidTokenRoyalty-uint256-uint256-uint256-: xref:token/common.adoc#ERC2981-ERC2981InvalidTokenRoyalty-uint256-uint256-uint256- +:xref-ERC2981-ERC2981InvalidTokenRoyaltyReceiver-uint256-address-: xref:token/common.adoc#ERC2981-ERC2981InvalidTokenRoyaltyReceiver-uint256-address- +:IERC165-supportsInterface: pass:normal[xref:utils.adoc#IERC165-supportsInterface-bytes4-[`IERC165.supportsInterface`]] += Common (Tokens) + +Functionality that is common to multiple token standards. + +* {ERC2981}: NFT Royalties compatible with both ERC721 and ERC1155. +** For ERC721 consider {ERC721Royalty} which clears the royalty information from storage on burn. + +== Contracts + +:RoyaltyInfo: pass:normal[xref:#ERC2981-RoyaltyInfo[`++RoyaltyInfo++`]] +:ERC2981InvalidDefaultRoyalty: pass:normal[xref:#ERC2981-ERC2981InvalidDefaultRoyalty-uint256-uint256-[`++ERC2981InvalidDefaultRoyalty++`]] +:ERC2981InvalidDefaultRoyaltyReceiver: pass:normal[xref:#ERC2981-ERC2981InvalidDefaultRoyaltyReceiver-address-[`++ERC2981InvalidDefaultRoyaltyReceiver++`]] +:ERC2981InvalidTokenRoyalty: pass:normal[xref:#ERC2981-ERC2981InvalidTokenRoyalty-uint256-uint256-uint256-[`++ERC2981InvalidTokenRoyalty++`]] +:ERC2981InvalidTokenRoyaltyReceiver: pass:normal[xref:#ERC2981-ERC2981InvalidTokenRoyaltyReceiver-uint256-address-[`++ERC2981InvalidTokenRoyaltyReceiver++`]] +:supportsInterface: pass:normal[xref:#ERC2981-supportsInterface-bytes4-[`++supportsInterface++`]] +:royaltyInfo: pass:normal[xref:#ERC2981-royaltyInfo-uint256-uint256-[`++royaltyInfo++`]] +:_feeDenominator: pass:normal[xref:#ERC2981-_feeDenominator--[`++_feeDenominator++`]] +:_setDefaultRoyalty: pass:normal[xref:#ERC2981-_setDefaultRoyalty-address-uint96-[`++_setDefaultRoyalty++`]] +:_deleteDefaultRoyalty: pass:normal[xref:#ERC2981-_deleteDefaultRoyalty--[`++_deleteDefaultRoyalty++`]] +:_setTokenRoyalty: pass:normal[xref:#ERC2981-_setTokenRoyalty-uint256-address-uint96-[`++_setTokenRoyalty++`]] +:_resetTokenRoyalty: pass:normal[xref:#ERC2981-_resetTokenRoyalty-uint256-[`++_resetTokenRoyalty++`]] + +[.contract] +[[ERC2981]] +=== `++ERC2981++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/token/common/ERC2981.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/token/common/ERC2981.sol"; +``` + +Implementation of the NFT Royalty Standard, a standardized way to retrieve royalty payment information. + +Royalty information can be specified globally for all token ids via {_setDefaultRoyalty}, and/or individually for +specific token ids via {_setTokenRoyalty}. The latter takes precedence over the first. + +Royalty is specified as a fraction of sale price. {_feeDenominator} is overridable but defaults to 10000, meaning the +fee is specified in basis points by default. + +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. + +[.contract-index] +.Functions +-- +* {xref-ERC2981-supportsInterface-bytes4-}[`++supportsInterface(interfaceId)++`] +* {xref-ERC2981-royaltyInfo-uint256-uint256-}[`++royaltyInfo(tokenId, salePrice)++`] +* {xref-ERC2981-_feeDenominator--}[`++_feeDenominator()++`] +* {xref-ERC2981-_setDefaultRoyalty-address-uint96-}[`++_setDefaultRoyalty(receiver, feeNumerator)++`] +* {xref-ERC2981-_deleteDefaultRoyalty--}[`++_deleteDefaultRoyalty()++`] +* {xref-ERC2981-_setTokenRoyalty-uint256-address-uint96-}[`++_setTokenRoyalty(tokenId, receiver, feeNumerator)++`] +* {xref-ERC2981-_resetTokenRoyalty-uint256-}[`++_resetTokenRoyalty(tokenId)++`] + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC2981 + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-index] +.Errors +-- +* {xref-ERC2981-ERC2981InvalidDefaultRoyalty-uint256-uint256-}[`++ERC2981InvalidDefaultRoyalty(numerator, denominator)++`] +* {xref-ERC2981-ERC2981InvalidDefaultRoyaltyReceiver-address-}[`++ERC2981InvalidDefaultRoyaltyReceiver(receiver)++`] +* {xref-ERC2981-ERC2981InvalidTokenRoyalty-uint256-uint256-uint256-}[`++ERC2981InvalidTokenRoyalty(tokenId, numerator, denominator)++`] +* {xref-ERC2981-ERC2981InvalidTokenRoyaltyReceiver-uint256-address-}[`++ERC2981InvalidTokenRoyaltyReceiver(tokenId, receiver)++`] + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC2981 + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-item] +[[ERC2981-supportsInterface-bytes4-]] +==== `[.contract-item-name]#++supportsInterface++#++(bytes4 interfaceId) β†’ bool++` [.item-kind]#public# + +See {IERC165-supportsInterface}. + +[.contract-item] +[[ERC2981-royaltyInfo-uint256-uint256-]] +==== `[.contract-item-name]#++royaltyInfo++#++(uint256 tokenId, uint256 salePrice) β†’ address, uint256++` [.item-kind]#public# + +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. + +[.contract-item] +[[ERC2981-_feeDenominator--]] +==== `[.contract-item-name]#++_feeDenominator++#++() β†’ uint96++` [.item-kind]#internal# + +The denominator with which to interpret the fee set in {_setTokenRoyalty} and {_setDefaultRoyalty} as a +fraction of the sale price. Defaults to 10000 so fees are expressed in basis points, but may be customized by an +override. + +[.contract-item] +[[ERC2981-_setDefaultRoyalty-address-uint96-]] +==== `[.contract-item-name]#++_setDefaultRoyalty++#++(address receiver, uint96 feeNumerator)++` [.item-kind]#internal# + +Sets the royalty information that all ids in this contract will default to. + +Requirements: + +- `receiver` cannot be the zero address. +- `feeNumerator` cannot be greater than the fee denominator. + +[.contract-item] +[[ERC2981-_deleteDefaultRoyalty--]] +==== `[.contract-item-name]#++_deleteDefaultRoyalty++#++()++` [.item-kind]#internal# + +Removes default royalty information. + +[.contract-item] +[[ERC2981-_setTokenRoyalty-uint256-address-uint96-]] +==== `[.contract-item-name]#++_setTokenRoyalty++#++(uint256 tokenId, address receiver, uint96 feeNumerator)++` [.item-kind]#internal# + +Sets the royalty information for a specific token id, overriding the global default. + +Requirements: + +- `receiver` cannot be the zero address. +- `feeNumerator` cannot be greater than the fee denominator. + +[.contract-item] +[[ERC2981-_resetTokenRoyalty-uint256-]] +==== `[.contract-item-name]#++_resetTokenRoyalty++#++(uint256 tokenId)++` [.item-kind]#internal# + +Resets royalty information for the token id back to the global default. + +[.contract-item] +[[ERC2981-ERC2981InvalidDefaultRoyalty-uint256-uint256-]] +==== `[.contract-item-name]#++ERC2981InvalidDefaultRoyalty++#++(uint256 numerator, uint256 denominator)++` [.item-kind]#error# + +The default royalty set is invalid (eg. (numerator / denominator) >= 1). + +[.contract-item] +[[ERC2981-ERC2981InvalidDefaultRoyaltyReceiver-address-]] +==== `[.contract-item-name]#++ERC2981InvalidDefaultRoyaltyReceiver++#++(address receiver)++` [.item-kind]#error# + +The default royalty receiver is invalid. + +[.contract-item] +[[ERC2981-ERC2981InvalidTokenRoyalty-uint256-uint256-uint256-]] +==== `[.contract-item-name]#++ERC2981InvalidTokenRoyalty++#++(uint256 tokenId, uint256 numerator, uint256 denominator)++` [.item-kind]#error# + +The royalty set for an specific `tokenId` is invalid (eg. (numerator / denominator) >= 1). + +[.contract-item] +[[ERC2981-ERC2981InvalidTokenRoyaltyReceiver-uint256-address-]] +==== `[.contract-item-name]#++ERC2981InvalidTokenRoyaltyReceiver++#++(uint256 tokenId, address receiver)++` [.item-kind]#error# + +The royalty receiver for `tokenId` is invalid. + diff --git a/docs/modules/api/pages/utils.adoc b/docs/modules/api/pages/utils.adoc new file mode 100644 index 000000000..f2b08f944 --- /dev/null +++ b/docs/modules/api/pages/utils.adoc @@ -0,0 +1,4497 @@ +:github-icon: pass:[] +:ReentrancyGuard: pass:normal[xref:utils.adoc#ReentrancyGuard[`ReentrancyGuard`]] +:Pausable: pass:normal[xref:utils.adoc#Pausable[`Pausable`]] +:SafeCast: pass:normal[xref:utils.adoc#SafeCast[`SafeCast`]] +:Math: pass:normal[xref:utils.adoc#Math[`Math`]] +:SignedMath: pass:normal[xref:utils.adoc#SignedMath[`SignedMath`]] +:Multicall: pass:normal[xref:utils.adoc#Multicall[`Multicall`]] +:Create2: pass:normal[xref:utils.adoc#Create2[`Create2`]] +:EnumerableMap: pass:normal[xref:utils.adoc#EnumerableMap[`EnumerableMap`]] +:EnumerableSet: pass:normal[xref:utils.adoc#EnumerableSet[`EnumerableSet`]] +:EnumerableMap: pass:normal[xref:utils.adoc#EnumerableMap[`EnumerableMap`]] +:EnumerableMap: pass:normal[xref:utils.adoc#EnumerableMap[`EnumerableMap`]] +:EnumerableSet: pass:normal[xref:utils.adoc#EnumerableSet[`EnumerableSet`]] +:xref-Math-tryAdd-uint256-uint256-: xref:utils.adoc#Math-tryAdd-uint256-uint256- +:xref-Math-trySub-uint256-uint256-: xref:utils.adoc#Math-trySub-uint256-uint256- +:xref-Math-tryMul-uint256-uint256-: xref:utils.adoc#Math-tryMul-uint256-uint256- +:xref-Math-tryDiv-uint256-uint256-: xref:utils.adoc#Math-tryDiv-uint256-uint256- +:xref-Math-tryMod-uint256-uint256-: xref:utils.adoc#Math-tryMod-uint256-uint256- +:xref-Math-max-uint256-uint256-: xref:utils.adoc#Math-max-uint256-uint256- +:xref-Math-min-uint256-uint256-: xref:utils.adoc#Math-min-uint256-uint256- +:xref-Math-average-uint256-uint256-: xref:utils.adoc#Math-average-uint256-uint256- +:xref-Math-ceilDiv-uint256-uint256-: xref:utils.adoc#Math-ceilDiv-uint256-uint256- +:xref-Math-mulDiv-uint256-uint256-uint256-: xref:utils.adoc#Math-mulDiv-uint256-uint256-uint256- +:xref-Math-mulDiv-uint256-uint256-uint256-enum-Math-Rounding-: xref:utils.adoc#Math-mulDiv-uint256-uint256-uint256-enum-Math-Rounding- +:xref-Math-sqrt-uint256-: xref:utils.adoc#Math-sqrt-uint256- +:xref-Math-sqrt-uint256-enum-Math-Rounding-: xref:utils.adoc#Math-sqrt-uint256-enum-Math-Rounding- +:xref-Math-log2-uint256-: xref:utils.adoc#Math-log2-uint256- +:xref-Math-log2-uint256-enum-Math-Rounding-: xref:utils.adoc#Math-log2-uint256-enum-Math-Rounding- +:xref-Math-log10-uint256-: xref:utils.adoc#Math-log10-uint256- +:xref-Math-log10-uint256-enum-Math-Rounding-: xref:utils.adoc#Math-log10-uint256-enum-Math-Rounding- +:xref-Math-log256-uint256-: xref:utils.adoc#Math-log256-uint256- +:xref-Math-log256-uint256-enum-Math-Rounding-: xref:utils.adoc#Math-log256-uint256-enum-Math-Rounding- +:xref-Math-unsignedRoundsUp-enum-Math-Rounding-: xref:utils.adoc#Math-unsignedRoundsUp-enum-Math-Rounding- +:xref-Math-MathOverflowedMulDiv--: xref:utils.adoc#Math-MathOverflowedMulDiv-- +:xref-SignedMath-max-int256-int256-: xref:utils.adoc#SignedMath-max-int256-int256- +:xref-SignedMath-min-int256-int256-: xref:utils.adoc#SignedMath-min-int256-int256- +:xref-SignedMath-average-int256-int256-: xref:utils.adoc#SignedMath-average-int256-int256- +:xref-SignedMath-abs-int256-: xref:utils.adoc#SignedMath-abs-int256- +:xref-SafeCast-toUint248-uint256-: xref:utils.adoc#SafeCast-toUint248-uint256- +:xref-SafeCast-toUint240-uint256-: xref:utils.adoc#SafeCast-toUint240-uint256- +:xref-SafeCast-toUint232-uint256-: xref:utils.adoc#SafeCast-toUint232-uint256- +:xref-SafeCast-toUint224-uint256-: xref:utils.adoc#SafeCast-toUint224-uint256- +:xref-SafeCast-toUint216-uint256-: xref:utils.adoc#SafeCast-toUint216-uint256- +:xref-SafeCast-toUint208-uint256-: xref:utils.adoc#SafeCast-toUint208-uint256- +:xref-SafeCast-toUint200-uint256-: xref:utils.adoc#SafeCast-toUint200-uint256- +:xref-SafeCast-toUint192-uint256-: xref:utils.adoc#SafeCast-toUint192-uint256- +:xref-SafeCast-toUint184-uint256-: xref:utils.adoc#SafeCast-toUint184-uint256- +:xref-SafeCast-toUint176-uint256-: xref:utils.adoc#SafeCast-toUint176-uint256- +:xref-SafeCast-toUint168-uint256-: xref:utils.adoc#SafeCast-toUint168-uint256- +:xref-SafeCast-toUint160-uint256-: xref:utils.adoc#SafeCast-toUint160-uint256- +:xref-SafeCast-toUint152-uint256-: xref:utils.adoc#SafeCast-toUint152-uint256- +:xref-SafeCast-toUint144-uint256-: xref:utils.adoc#SafeCast-toUint144-uint256- +:xref-SafeCast-toUint136-uint256-: xref:utils.adoc#SafeCast-toUint136-uint256- +:xref-SafeCast-toUint128-uint256-: xref:utils.adoc#SafeCast-toUint128-uint256- +:xref-SafeCast-toUint120-uint256-: xref:utils.adoc#SafeCast-toUint120-uint256- +:xref-SafeCast-toUint112-uint256-: xref:utils.adoc#SafeCast-toUint112-uint256- +:xref-SafeCast-toUint104-uint256-: xref:utils.adoc#SafeCast-toUint104-uint256- +:xref-SafeCast-toUint96-uint256-: xref:utils.adoc#SafeCast-toUint96-uint256- +:xref-SafeCast-toUint88-uint256-: xref:utils.adoc#SafeCast-toUint88-uint256- +:xref-SafeCast-toUint80-uint256-: xref:utils.adoc#SafeCast-toUint80-uint256- +:xref-SafeCast-toUint72-uint256-: xref:utils.adoc#SafeCast-toUint72-uint256- +:xref-SafeCast-toUint64-uint256-: xref:utils.adoc#SafeCast-toUint64-uint256- +:xref-SafeCast-toUint56-uint256-: xref:utils.adoc#SafeCast-toUint56-uint256- +:xref-SafeCast-toUint48-uint256-: xref:utils.adoc#SafeCast-toUint48-uint256- +:xref-SafeCast-toUint40-uint256-: xref:utils.adoc#SafeCast-toUint40-uint256- +:xref-SafeCast-toUint32-uint256-: xref:utils.adoc#SafeCast-toUint32-uint256- +:xref-SafeCast-toUint24-uint256-: xref:utils.adoc#SafeCast-toUint24-uint256- +:xref-SafeCast-toUint16-uint256-: xref:utils.adoc#SafeCast-toUint16-uint256- +:xref-SafeCast-toUint8-uint256-: xref:utils.adoc#SafeCast-toUint8-uint256- +:xref-SafeCast-toUint256-int256-: xref:utils.adoc#SafeCast-toUint256-int256- +:xref-SafeCast-toInt248-int256-: xref:utils.adoc#SafeCast-toInt248-int256- +:xref-SafeCast-toInt240-int256-: xref:utils.adoc#SafeCast-toInt240-int256- +:xref-SafeCast-toInt232-int256-: xref:utils.adoc#SafeCast-toInt232-int256- +:xref-SafeCast-toInt224-int256-: xref:utils.adoc#SafeCast-toInt224-int256- +:xref-SafeCast-toInt216-int256-: xref:utils.adoc#SafeCast-toInt216-int256- +:xref-SafeCast-toInt208-int256-: xref:utils.adoc#SafeCast-toInt208-int256- +:xref-SafeCast-toInt200-int256-: xref:utils.adoc#SafeCast-toInt200-int256- +:xref-SafeCast-toInt192-int256-: xref:utils.adoc#SafeCast-toInt192-int256- +:xref-SafeCast-toInt184-int256-: xref:utils.adoc#SafeCast-toInt184-int256- +:xref-SafeCast-toInt176-int256-: xref:utils.adoc#SafeCast-toInt176-int256- +:xref-SafeCast-toInt168-int256-: xref:utils.adoc#SafeCast-toInt168-int256- +:xref-SafeCast-toInt160-int256-: xref:utils.adoc#SafeCast-toInt160-int256- +:xref-SafeCast-toInt152-int256-: xref:utils.adoc#SafeCast-toInt152-int256- +:xref-SafeCast-toInt144-int256-: xref:utils.adoc#SafeCast-toInt144-int256- +:xref-SafeCast-toInt136-int256-: xref:utils.adoc#SafeCast-toInt136-int256- +:xref-SafeCast-toInt128-int256-: xref:utils.adoc#SafeCast-toInt128-int256- +:xref-SafeCast-toInt120-int256-: xref:utils.adoc#SafeCast-toInt120-int256- +:xref-SafeCast-toInt112-int256-: xref:utils.adoc#SafeCast-toInt112-int256- +:xref-SafeCast-toInt104-int256-: xref:utils.adoc#SafeCast-toInt104-int256- +:xref-SafeCast-toInt96-int256-: xref:utils.adoc#SafeCast-toInt96-int256- +:xref-SafeCast-toInt88-int256-: xref:utils.adoc#SafeCast-toInt88-int256- +:xref-SafeCast-toInt80-int256-: xref:utils.adoc#SafeCast-toInt80-int256- +:xref-SafeCast-toInt72-int256-: xref:utils.adoc#SafeCast-toInt72-int256- +:xref-SafeCast-toInt64-int256-: xref:utils.adoc#SafeCast-toInt64-int256- +:xref-SafeCast-toInt56-int256-: xref:utils.adoc#SafeCast-toInt56-int256- +:xref-SafeCast-toInt48-int256-: xref:utils.adoc#SafeCast-toInt48-int256- +:xref-SafeCast-toInt40-int256-: xref:utils.adoc#SafeCast-toInt40-int256- +:xref-SafeCast-toInt32-int256-: xref:utils.adoc#SafeCast-toInt32-int256- +:xref-SafeCast-toInt24-int256-: xref:utils.adoc#SafeCast-toInt24-int256- +:xref-SafeCast-toInt16-int256-: xref:utils.adoc#SafeCast-toInt16-int256- +:xref-SafeCast-toInt8-int256-: xref:utils.adoc#SafeCast-toInt8-int256- +:xref-SafeCast-toInt256-uint256-: xref:utils.adoc#SafeCast-toInt256-uint256- +:xref-SafeCast-SafeCastOverflowedUintDowncast-uint8-uint256-: xref:utils.adoc#SafeCast-SafeCastOverflowedUintDowncast-uint8-uint256- +:xref-SafeCast-SafeCastOverflowedIntToUint-int256-: xref:utils.adoc#SafeCast-SafeCastOverflowedIntToUint-int256- +:xref-SafeCast-SafeCastOverflowedIntDowncast-uint8-int256-: xref:utils.adoc#SafeCast-SafeCastOverflowedIntDowncast-uint8-int256- +:xref-SafeCast-SafeCastOverflowedUintToInt-uint256-: xref:utils.adoc#SafeCast-SafeCastOverflowedUintToInt-uint256- +:xref-ECDSA-tryRecover-bytes32-bytes-: xref:utils.adoc#ECDSA-tryRecover-bytes32-bytes- +:xref-ECDSA-recover-bytes32-bytes-: xref:utils.adoc#ECDSA-recover-bytes32-bytes- +:xref-ECDSA-tryRecover-bytes32-bytes32-bytes32-: xref:utils.adoc#ECDSA-tryRecover-bytes32-bytes32-bytes32- +:xref-ECDSA-recover-bytes32-bytes32-bytes32-: xref:utils.adoc#ECDSA-recover-bytes32-bytes32-bytes32- +:xref-ECDSA-tryRecover-bytes32-uint8-bytes32-bytes32-: xref:utils.adoc#ECDSA-tryRecover-bytes32-uint8-bytes32-bytes32- +:xref-ECDSA-recover-bytes32-uint8-bytes32-bytes32-: xref:utils.adoc#ECDSA-recover-bytes32-uint8-bytes32-bytes32- +:xref-ECDSA-ECDSAInvalidSignature--: xref:utils.adoc#ECDSA-ECDSAInvalidSignature-- +:xref-ECDSA-ECDSAInvalidSignatureLength-uint256-: xref:utils.adoc#ECDSA-ECDSAInvalidSignatureLength-uint256- +:xref-ECDSA-ECDSAInvalidSignatureS-bytes32-: xref:utils.adoc#ECDSA-ECDSAInvalidSignatureS-bytes32- +:MessageHashUtils-toEthSignedMessageHash: pass:normal[xref:utils.adoc#MessageHashUtils-toEthSignedMessageHash-bytes-[`MessageHashUtils.toEthSignedMessageHash`]] +:MessageHashUtils-toEthSignedMessageHash: pass:normal[xref:utils.adoc#MessageHashUtils-toEthSignedMessageHash-bytes-[`MessageHashUtils.toEthSignedMessageHash`]] +:ECDSA-tryRecover: pass:normal[xref:utils.adoc#ECDSA-tryRecover-bytes32-uint8-bytes32-bytes32-[`ECDSA.tryRecover`]] +:ECDSA-recover: pass:normal[xref:utils.adoc#ECDSA-recover-bytes32-uint8-bytes32-bytes32-[`ECDSA.recover`]] +:ECDSA-tryRecover: pass:normal[xref:utils.adoc#ECDSA-tryRecover-bytes32-uint8-bytes32-bytes32-[`ECDSA.tryRecover`]] +:ECDSA-recover: pass:normal[xref:utils.adoc#ECDSA-recover-bytes32-uint8-bytes32-bytes32-[`ECDSA.recover`]] +:ECDSA: pass:normal[xref:utils.adoc#ECDSA[`ECDSA`]] +:xref-MessageHashUtils-toEthSignedMessageHash-bytes32-: xref:utils.adoc#MessageHashUtils-toEthSignedMessageHash-bytes32- +:xref-MessageHashUtils-toEthSignedMessageHash-bytes-: xref:utils.adoc#MessageHashUtils-toEthSignedMessageHash-bytes- +:xref-MessageHashUtils-toDataWithIntendedValidatorHash-address-bytes-: xref:utils.adoc#MessageHashUtils-toDataWithIntendedValidatorHash-address-bytes- +:xref-MessageHashUtils-toTypedDataHash-bytes32-bytes32-: xref:utils.adoc#MessageHashUtils-toTypedDataHash-bytes32-bytes32- +:ECDSA-recover: pass:normal[xref:utils.adoc#ECDSA-recover-bytes32-uint8-bytes32-bytes32-[`ECDSA.recover`]] +:ECDSA-recover: pass:normal[xref:utils.adoc#ECDSA-recover-bytes32-uint8-bytes32-bytes32-[`ECDSA.recover`]] +:ECDSA-recover: pass:normal[xref:utils.adoc#ECDSA-recover-bytes32-uint8-bytes32-bytes32-[`ECDSA.recover`]] +:ECDSA-recover: pass:normal[xref:utils.adoc#ECDSA-recover-bytes32-uint8-bytes32-bytes32-[`ECDSA.recover`]] +:xref-SignatureChecker-isValidSignatureNow-address-bytes32-bytes-: xref:utils.adoc#SignatureChecker-isValidSignatureNow-address-bytes32-bytes- +:xref-SignatureChecker-isValidERC1271SignatureNow-address-bytes32-bytes-: xref:utils.adoc#SignatureChecker-isValidERC1271SignatureNow-address-bytes32-bytes- +:xref-MerkleProof-verify-bytes32---bytes32-bytes32-: xref:utils.adoc#MerkleProof-verify-bytes32---bytes32-bytes32- +:xref-MerkleProof-verifyCalldata-bytes32---bytes32-bytes32-: xref:utils.adoc#MerkleProof-verifyCalldata-bytes32---bytes32-bytes32- +:xref-MerkleProof-processProof-bytes32---bytes32-: xref:utils.adoc#MerkleProof-processProof-bytes32---bytes32- +:xref-MerkleProof-processProofCalldata-bytes32---bytes32-: xref:utils.adoc#MerkleProof-processProofCalldata-bytes32---bytes32- +:xref-MerkleProof-multiProofVerify-bytes32---bool---bytes32-bytes32---: xref:utils.adoc#MerkleProof-multiProofVerify-bytes32---bool---bytes32-bytes32--- +:xref-MerkleProof-multiProofVerifyCalldata-bytes32---bool---bytes32-bytes32---: xref:utils.adoc#MerkleProof-multiProofVerifyCalldata-bytes32---bool---bytes32-bytes32--- +:xref-MerkleProof-processMultiProof-bytes32---bool---bytes32---: xref:utils.adoc#MerkleProof-processMultiProof-bytes32---bool---bytes32--- +:xref-MerkleProof-processMultiProofCalldata-bytes32---bool---bytes32---: xref:utils.adoc#MerkleProof-processMultiProofCalldata-bytes32---bool---bytes32--- +:xref-MerkleProof-MerkleProofInvalidMultiproof--: xref:utils.adoc#MerkleProof-MerkleProofInvalidMultiproof-- +:xref-EIP712-constructor-string-string-: xref:utils.adoc#EIP712-constructor-string-string- +:xref-EIP712-_domainSeparatorV4--: xref:utils.adoc#EIP712-_domainSeparatorV4-- +:xref-EIP712-_hashTypedDataV4-bytes32-: xref:utils.adoc#EIP712-_hashTypedDataV4-bytes32- +:xref-EIP712-eip712Domain--: xref:utils.adoc#EIP712-eip712Domain-- +:xref-EIP712-_EIP712Name--: xref:utils.adoc#EIP712-_EIP712Name-- +:xref-EIP712-_EIP712Version--: xref:utils.adoc#EIP712-_EIP712Version-- +:xref-IERC5267-EIP712DomainChanged--: xref:interfaces.adoc#IERC5267-EIP712DomainChanged-- +:ECDSA-recover: pass:normal[xref:utils.adoc#ECDSA-recover-bytes32-uint8-bytes32-bytes32-[`ECDSA.recover`]] +:xref-ReentrancyGuard-nonReentrant--: xref:utils.adoc#ReentrancyGuard-nonReentrant-- +:xref-ReentrancyGuard-constructor--: xref:utils.adoc#ReentrancyGuard-constructor-- +:xref-ReentrancyGuard-_reentrancyGuardEntered--: xref:utils.adoc#ReentrancyGuard-_reentrancyGuardEntered-- +:xref-ReentrancyGuard-ReentrancyGuardReentrantCall--: xref:utils.adoc#ReentrancyGuard-ReentrancyGuardReentrantCall-- +:xref-Pausable-whenNotPaused--: xref:utils.adoc#Pausable-whenNotPaused-- +:xref-Pausable-whenPaused--: xref:utils.adoc#Pausable-whenPaused-- +:xref-Pausable-constructor--: xref:utils.adoc#Pausable-constructor-- +:xref-Pausable-paused--: xref:utils.adoc#Pausable-paused-- +:xref-Pausable-_requireNotPaused--: xref:utils.adoc#Pausable-_requireNotPaused-- +:xref-Pausable-_requirePaused--: xref:utils.adoc#Pausable-_requirePaused-- +:xref-Pausable-_pause--: xref:utils.adoc#Pausable-_pause-- +:xref-Pausable-_unpause--: xref:utils.adoc#Pausable-_unpause-- +:xref-Pausable-Paused-address-: xref:utils.adoc#Pausable-Paused-address- +:xref-Pausable-Unpaused-address-: xref:utils.adoc#Pausable-Unpaused-address- +:xref-Pausable-EnforcedPause--: xref:utils.adoc#Pausable-EnforcedPause-- +:xref-Pausable-ExpectedPause--: xref:utils.adoc#Pausable-ExpectedPause-- +:ERC165Checker: pass:normal[xref:utils.adoc#ERC165Checker[`ERC165Checker`]] +:ERC165: pass:normal[xref:utils.adoc#ERC165[`ERC165`]] +:xref-IERC165-supportsInterface-bytes4-: xref:utils.adoc#IERC165-supportsInterface-bytes4- +:IERC165: pass:normal[xref:utils.adoc#IERC165[`IERC165`]] +:xref-ERC165-supportsInterface-bytes4-: xref:utils.adoc#ERC165-supportsInterface-bytes4- +:IERC165-supportsInterface: pass:normal[xref:utils.adoc#IERC165-supportsInterface-bytes4-[`IERC165.supportsInterface`]] +:IERC165: pass:normal[xref:utils.adoc#IERC165[`IERC165`]] +:xref-ERC165Checker-supportsERC165-address-: xref:utils.adoc#ERC165Checker-supportsERC165-address- +:xref-ERC165Checker-supportsInterface-address-bytes4-: xref:utils.adoc#ERC165Checker-supportsInterface-address-bytes4- +:xref-ERC165Checker-getSupportedInterfaces-address-bytes4---: xref:utils.adoc#ERC165Checker-getSupportedInterfaces-address-bytes4--- +:xref-ERC165Checker-supportsAllInterfaces-address-bytes4---: xref:utils.adoc#ERC165Checker-supportsAllInterfaces-address-bytes4--- +:xref-ERC165Checker-supportsERC165InterfaceUnchecked-address-bytes4-: xref:utils.adoc#ERC165Checker-supportsERC165InterfaceUnchecked-address-bytes4- +:IERC165: pass:normal[xref:utils.adoc#IERC165[`IERC165`]] +:IERC165: pass:normal[xref:utils.adoc#IERC165[`IERC165`]] +:IERC165-supportsInterface: pass:normal[xref:utils.adoc#IERC165-supportsInterface-bytes4-[`IERC165.supportsInterface`]] +:IERC165-supportsInterface: pass:normal[xref:utils.adoc#IERC165-supportsInterface-bytes4-[`IERC165.supportsInterface`]] +:IERC165: pass:normal[xref:utils.adoc#IERC165[`IERC165`]] +:IERC165: pass:normal[xref:utils.adoc#IERC165[`IERC165`]] +:IERC165-supportsInterface: pass:normal[xref:utils.adoc#IERC165-supportsInterface-bytes4-[`IERC165.supportsInterface`]] +:xref-BitMaps-get-struct-BitMaps-BitMap-uint256-: xref:utils.adoc#BitMaps-get-struct-BitMaps-BitMap-uint256- +:xref-BitMaps-setTo-struct-BitMaps-BitMap-uint256-bool-: xref:utils.adoc#BitMaps-setTo-struct-BitMaps-BitMap-uint256-bool- +:xref-BitMaps-set-struct-BitMaps-BitMap-uint256-: xref:utils.adoc#BitMaps-set-struct-BitMaps-BitMap-uint256- +:xref-BitMaps-unset-struct-BitMaps-BitMap-uint256-: xref:utils.adoc#BitMaps-unset-struct-BitMaps-BitMap-uint256- +:xref-EnumerableMap-set-struct-EnumerableMap-Bytes32ToBytes32Map-bytes32-bytes32-: xref:utils.adoc#EnumerableMap-set-struct-EnumerableMap-Bytes32ToBytes32Map-bytes32-bytes32- +:xref-EnumerableMap-remove-struct-EnumerableMap-Bytes32ToBytes32Map-bytes32-: xref:utils.adoc#EnumerableMap-remove-struct-EnumerableMap-Bytes32ToBytes32Map-bytes32- +:xref-EnumerableMap-contains-struct-EnumerableMap-Bytes32ToBytes32Map-bytes32-: xref:utils.adoc#EnumerableMap-contains-struct-EnumerableMap-Bytes32ToBytes32Map-bytes32- +:xref-EnumerableMap-length-struct-EnumerableMap-Bytes32ToBytes32Map-: xref:utils.adoc#EnumerableMap-length-struct-EnumerableMap-Bytes32ToBytes32Map- +:xref-EnumerableMap-at-struct-EnumerableMap-Bytes32ToBytes32Map-uint256-: xref:utils.adoc#EnumerableMap-at-struct-EnumerableMap-Bytes32ToBytes32Map-uint256- +:xref-EnumerableMap-tryGet-struct-EnumerableMap-Bytes32ToBytes32Map-bytes32-: xref:utils.adoc#EnumerableMap-tryGet-struct-EnumerableMap-Bytes32ToBytes32Map-bytes32- +:xref-EnumerableMap-get-struct-EnumerableMap-Bytes32ToBytes32Map-bytes32-: xref:utils.adoc#EnumerableMap-get-struct-EnumerableMap-Bytes32ToBytes32Map-bytes32- +:xref-EnumerableMap-keys-struct-EnumerableMap-Bytes32ToBytes32Map-: xref:utils.adoc#EnumerableMap-keys-struct-EnumerableMap-Bytes32ToBytes32Map- +:xref-EnumerableMap-set-struct-EnumerableMap-UintToUintMap-uint256-uint256-: xref:utils.adoc#EnumerableMap-set-struct-EnumerableMap-UintToUintMap-uint256-uint256- +:xref-EnumerableMap-remove-struct-EnumerableMap-UintToUintMap-uint256-: xref:utils.adoc#EnumerableMap-remove-struct-EnumerableMap-UintToUintMap-uint256- +:xref-EnumerableMap-contains-struct-EnumerableMap-UintToUintMap-uint256-: xref:utils.adoc#EnumerableMap-contains-struct-EnumerableMap-UintToUintMap-uint256- +:xref-EnumerableMap-length-struct-EnumerableMap-UintToUintMap-: xref:utils.adoc#EnumerableMap-length-struct-EnumerableMap-UintToUintMap- +:xref-EnumerableMap-at-struct-EnumerableMap-UintToUintMap-uint256-: xref:utils.adoc#EnumerableMap-at-struct-EnumerableMap-UintToUintMap-uint256- +:xref-EnumerableMap-tryGet-struct-EnumerableMap-UintToUintMap-uint256-: xref:utils.adoc#EnumerableMap-tryGet-struct-EnumerableMap-UintToUintMap-uint256- +:xref-EnumerableMap-get-struct-EnumerableMap-UintToUintMap-uint256-: xref:utils.adoc#EnumerableMap-get-struct-EnumerableMap-UintToUintMap-uint256- +:xref-EnumerableMap-keys-struct-EnumerableMap-UintToUintMap-: xref:utils.adoc#EnumerableMap-keys-struct-EnumerableMap-UintToUintMap- +:xref-EnumerableMap-set-struct-EnumerableMap-UintToAddressMap-uint256-address-: xref:utils.adoc#EnumerableMap-set-struct-EnumerableMap-UintToAddressMap-uint256-address- +:xref-EnumerableMap-remove-struct-EnumerableMap-UintToAddressMap-uint256-: xref:utils.adoc#EnumerableMap-remove-struct-EnumerableMap-UintToAddressMap-uint256- +:xref-EnumerableMap-contains-struct-EnumerableMap-UintToAddressMap-uint256-: xref:utils.adoc#EnumerableMap-contains-struct-EnumerableMap-UintToAddressMap-uint256- +:xref-EnumerableMap-length-struct-EnumerableMap-UintToAddressMap-: xref:utils.adoc#EnumerableMap-length-struct-EnumerableMap-UintToAddressMap- +:xref-EnumerableMap-at-struct-EnumerableMap-UintToAddressMap-uint256-: xref:utils.adoc#EnumerableMap-at-struct-EnumerableMap-UintToAddressMap-uint256- +:xref-EnumerableMap-tryGet-struct-EnumerableMap-UintToAddressMap-uint256-: xref:utils.adoc#EnumerableMap-tryGet-struct-EnumerableMap-UintToAddressMap-uint256- +:xref-EnumerableMap-get-struct-EnumerableMap-UintToAddressMap-uint256-: xref:utils.adoc#EnumerableMap-get-struct-EnumerableMap-UintToAddressMap-uint256- +:xref-EnumerableMap-keys-struct-EnumerableMap-UintToAddressMap-: xref:utils.adoc#EnumerableMap-keys-struct-EnumerableMap-UintToAddressMap- +:xref-EnumerableMap-set-struct-EnumerableMap-AddressToUintMap-address-uint256-: xref:utils.adoc#EnumerableMap-set-struct-EnumerableMap-AddressToUintMap-address-uint256- +:xref-EnumerableMap-remove-struct-EnumerableMap-AddressToUintMap-address-: xref:utils.adoc#EnumerableMap-remove-struct-EnumerableMap-AddressToUintMap-address- +:xref-EnumerableMap-contains-struct-EnumerableMap-AddressToUintMap-address-: xref:utils.adoc#EnumerableMap-contains-struct-EnumerableMap-AddressToUintMap-address- +:xref-EnumerableMap-length-struct-EnumerableMap-AddressToUintMap-: xref:utils.adoc#EnumerableMap-length-struct-EnumerableMap-AddressToUintMap- +:xref-EnumerableMap-at-struct-EnumerableMap-AddressToUintMap-uint256-: xref:utils.adoc#EnumerableMap-at-struct-EnumerableMap-AddressToUintMap-uint256- +:xref-EnumerableMap-tryGet-struct-EnumerableMap-AddressToUintMap-address-: xref:utils.adoc#EnumerableMap-tryGet-struct-EnumerableMap-AddressToUintMap-address- +:xref-EnumerableMap-get-struct-EnumerableMap-AddressToUintMap-address-: xref:utils.adoc#EnumerableMap-get-struct-EnumerableMap-AddressToUintMap-address- +:xref-EnumerableMap-keys-struct-EnumerableMap-AddressToUintMap-: xref:utils.adoc#EnumerableMap-keys-struct-EnumerableMap-AddressToUintMap- +:xref-EnumerableMap-set-struct-EnumerableMap-Bytes32ToUintMap-bytes32-uint256-: xref:utils.adoc#EnumerableMap-set-struct-EnumerableMap-Bytes32ToUintMap-bytes32-uint256- +:xref-EnumerableMap-remove-struct-EnumerableMap-Bytes32ToUintMap-bytes32-: xref:utils.adoc#EnumerableMap-remove-struct-EnumerableMap-Bytes32ToUintMap-bytes32- +:xref-EnumerableMap-contains-struct-EnumerableMap-Bytes32ToUintMap-bytes32-: xref:utils.adoc#EnumerableMap-contains-struct-EnumerableMap-Bytes32ToUintMap-bytes32- +:xref-EnumerableMap-length-struct-EnumerableMap-Bytes32ToUintMap-: xref:utils.adoc#EnumerableMap-length-struct-EnumerableMap-Bytes32ToUintMap- +:xref-EnumerableMap-at-struct-EnumerableMap-Bytes32ToUintMap-uint256-: xref:utils.adoc#EnumerableMap-at-struct-EnumerableMap-Bytes32ToUintMap-uint256- +:xref-EnumerableMap-tryGet-struct-EnumerableMap-Bytes32ToUintMap-bytes32-: xref:utils.adoc#EnumerableMap-tryGet-struct-EnumerableMap-Bytes32ToUintMap-bytes32- +:xref-EnumerableMap-get-struct-EnumerableMap-Bytes32ToUintMap-bytes32-: xref:utils.adoc#EnumerableMap-get-struct-EnumerableMap-Bytes32ToUintMap-bytes32- +:xref-EnumerableMap-keys-struct-EnumerableMap-Bytes32ToUintMap-: xref:utils.adoc#EnumerableMap-keys-struct-EnumerableMap-Bytes32ToUintMap- +:xref-EnumerableMap-EnumerableMapNonexistentKey-bytes32-: xref:utils.adoc#EnumerableMap-EnumerableMapNonexistentKey-bytes32- +:xref-EnumerableSet-add-struct-EnumerableSet-Bytes32Set-bytes32-: xref:utils.adoc#EnumerableSet-add-struct-EnumerableSet-Bytes32Set-bytes32- +:xref-EnumerableSet-remove-struct-EnumerableSet-Bytes32Set-bytes32-: xref:utils.adoc#EnumerableSet-remove-struct-EnumerableSet-Bytes32Set-bytes32- +:xref-EnumerableSet-contains-struct-EnumerableSet-Bytes32Set-bytes32-: xref:utils.adoc#EnumerableSet-contains-struct-EnumerableSet-Bytes32Set-bytes32- +:xref-EnumerableSet-length-struct-EnumerableSet-Bytes32Set-: xref:utils.adoc#EnumerableSet-length-struct-EnumerableSet-Bytes32Set- +:xref-EnumerableSet-at-struct-EnumerableSet-Bytes32Set-uint256-: xref:utils.adoc#EnumerableSet-at-struct-EnumerableSet-Bytes32Set-uint256- +:xref-EnumerableSet-values-struct-EnumerableSet-Bytes32Set-: xref:utils.adoc#EnumerableSet-values-struct-EnumerableSet-Bytes32Set- +:xref-EnumerableSet-add-struct-EnumerableSet-AddressSet-address-: xref:utils.adoc#EnumerableSet-add-struct-EnumerableSet-AddressSet-address- +:xref-EnumerableSet-remove-struct-EnumerableSet-AddressSet-address-: xref:utils.adoc#EnumerableSet-remove-struct-EnumerableSet-AddressSet-address- +:xref-EnumerableSet-contains-struct-EnumerableSet-AddressSet-address-: xref:utils.adoc#EnumerableSet-contains-struct-EnumerableSet-AddressSet-address- +:xref-EnumerableSet-length-struct-EnumerableSet-AddressSet-: xref:utils.adoc#EnumerableSet-length-struct-EnumerableSet-AddressSet- +:xref-EnumerableSet-at-struct-EnumerableSet-AddressSet-uint256-: xref:utils.adoc#EnumerableSet-at-struct-EnumerableSet-AddressSet-uint256- +:xref-EnumerableSet-values-struct-EnumerableSet-AddressSet-: xref:utils.adoc#EnumerableSet-values-struct-EnumerableSet-AddressSet- +:xref-EnumerableSet-add-struct-EnumerableSet-UintSet-uint256-: xref:utils.adoc#EnumerableSet-add-struct-EnumerableSet-UintSet-uint256- +:xref-EnumerableSet-remove-struct-EnumerableSet-UintSet-uint256-: xref:utils.adoc#EnumerableSet-remove-struct-EnumerableSet-UintSet-uint256- +:xref-EnumerableSet-contains-struct-EnumerableSet-UintSet-uint256-: xref:utils.adoc#EnumerableSet-contains-struct-EnumerableSet-UintSet-uint256- +:xref-EnumerableSet-length-struct-EnumerableSet-UintSet-: xref:utils.adoc#EnumerableSet-length-struct-EnumerableSet-UintSet- +:xref-EnumerableSet-at-struct-EnumerableSet-UintSet-uint256-: xref:utils.adoc#EnumerableSet-at-struct-EnumerableSet-UintSet-uint256- +:xref-EnumerableSet-values-struct-EnumerableSet-UintSet-: xref:utils.adoc#EnumerableSet-values-struct-EnumerableSet-UintSet- +:xref-DoubleEndedQueue-pushBack-struct-DoubleEndedQueue-Bytes32Deque-bytes32-: xref:utils.adoc#DoubleEndedQueue-pushBack-struct-DoubleEndedQueue-Bytes32Deque-bytes32- +:xref-DoubleEndedQueue-popBack-struct-DoubleEndedQueue-Bytes32Deque-: xref:utils.adoc#DoubleEndedQueue-popBack-struct-DoubleEndedQueue-Bytes32Deque- +:xref-DoubleEndedQueue-pushFront-struct-DoubleEndedQueue-Bytes32Deque-bytes32-: xref:utils.adoc#DoubleEndedQueue-pushFront-struct-DoubleEndedQueue-Bytes32Deque-bytes32- +:xref-DoubleEndedQueue-popFront-struct-DoubleEndedQueue-Bytes32Deque-: xref:utils.adoc#DoubleEndedQueue-popFront-struct-DoubleEndedQueue-Bytes32Deque- +:xref-DoubleEndedQueue-front-struct-DoubleEndedQueue-Bytes32Deque-: xref:utils.adoc#DoubleEndedQueue-front-struct-DoubleEndedQueue-Bytes32Deque- +:xref-DoubleEndedQueue-back-struct-DoubleEndedQueue-Bytes32Deque-: xref:utils.adoc#DoubleEndedQueue-back-struct-DoubleEndedQueue-Bytes32Deque- +:xref-DoubleEndedQueue-at-struct-DoubleEndedQueue-Bytes32Deque-uint256-: xref:utils.adoc#DoubleEndedQueue-at-struct-DoubleEndedQueue-Bytes32Deque-uint256- +:xref-DoubleEndedQueue-clear-struct-DoubleEndedQueue-Bytes32Deque-: xref:utils.adoc#DoubleEndedQueue-clear-struct-DoubleEndedQueue-Bytes32Deque- +:xref-DoubleEndedQueue-length-struct-DoubleEndedQueue-Bytes32Deque-: xref:utils.adoc#DoubleEndedQueue-length-struct-DoubleEndedQueue-Bytes32Deque- +:xref-DoubleEndedQueue-empty-struct-DoubleEndedQueue-Bytes32Deque-: xref:utils.adoc#DoubleEndedQueue-empty-struct-DoubleEndedQueue-Bytes32Deque- +:xref-DoubleEndedQueue-QueueEmpty--: xref:utils.adoc#DoubleEndedQueue-QueueEmpty-- +:xref-DoubleEndedQueue-QueueFull--: xref:utils.adoc#DoubleEndedQueue-QueueFull-- +:xref-DoubleEndedQueue-QueueOutOfBounds--: xref:utils.adoc#DoubleEndedQueue-QueueOutOfBounds-- +:Votes: pass:normal[xref:governance.adoc#Votes[`Votes`]] +:xref-Checkpoints-push-struct-Checkpoints-Trace224-uint32-uint224-: xref:utils.adoc#Checkpoints-push-struct-Checkpoints-Trace224-uint32-uint224- +:xref-Checkpoints-lowerLookup-struct-Checkpoints-Trace224-uint32-: xref:utils.adoc#Checkpoints-lowerLookup-struct-Checkpoints-Trace224-uint32- +:xref-Checkpoints-upperLookup-struct-Checkpoints-Trace224-uint32-: xref:utils.adoc#Checkpoints-upperLookup-struct-Checkpoints-Trace224-uint32- +:xref-Checkpoints-upperLookupRecent-struct-Checkpoints-Trace224-uint32-: xref:utils.adoc#Checkpoints-upperLookupRecent-struct-Checkpoints-Trace224-uint32- +:xref-Checkpoints-latest-struct-Checkpoints-Trace224-: xref:utils.adoc#Checkpoints-latest-struct-Checkpoints-Trace224- +:xref-Checkpoints-latestCheckpoint-struct-Checkpoints-Trace224-: xref:utils.adoc#Checkpoints-latestCheckpoint-struct-Checkpoints-Trace224- +:xref-Checkpoints-length-struct-Checkpoints-Trace224-: xref:utils.adoc#Checkpoints-length-struct-Checkpoints-Trace224- +:xref-Checkpoints-at-struct-Checkpoints-Trace224-uint32-: xref:utils.adoc#Checkpoints-at-struct-Checkpoints-Trace224-uint32- +:xref-Checkpoints-push-struct-Checkpoints-Trace208-uint48-uint208-: xref:utils.adoc#Checkpoints-push-struct-Checkpoints-Trace208-uint48-uint208- +:xref-Checkpoints-lowerLookup-struct-Checkpoints-Trace208-uint48-: xref:utils.adoc#Checkpoints-lowerLookup-struct-Checkpoints-Trace208-uint48- +:xref-Checkpoints-upperLookup-struct-Checkpoints-Trace208-uint48-: xref:utils.adoc#Checkpoints-upperLookup-struct-Checkpoints-Trace208-uint48- +:xref-Checkpoints-upperLookupRecent-struct-Checkpoints-Trace208-uint48-: xref:utils.adoc#Checkpoints-upperLookupRecent-struct-Checkpoints-Trace208-uint48- +:xref-Checkpoints-latest-struct-Checkpoints-Trace208-: xref:utils.adoc#Checkpoints-latest-struct-Checkpoints-Trace208- +:xref-Checkpoints-latestCheckpoint-struct-Checkpoints-Trace208-: xref:utils.adoc#Checkpoints-latestCheckpoint-struct-Checkpoints-Trace208- +:xref-Checkpoints-length-struct-Checkpoints-Trace208-: xref:utils.adoc#Checkpoints-length-struct-Checkpoints-Trace208- +:xref-Checkpoints-at-struct-Checkpoints-Trace208-uint32-: xref:utils.adoc#Checkpoints-at-struct-Checkpoints-Trace208-uint32- +:xref-Checkpoints-push-struct-Checkpoints-Trace160-uint96-uint160-: xref:utils.adoc#Checkpoints-push-struct-Checkpoints-Trace160-uint96-uint160- +:xref-Checkpoints-lowerLookup-struct-Checkpoints-Trace160-uint96-: xref:utils.adoc#Checkpoints-lowerLookup-struct-Checkpoints-Trace160-uint96- +:xref-Checkpoints-upperLookup-struct-Checkpoints-Trace160-uint96-: xref:utils.adoc#Checkpoints-upperLookup-struct-Checkpoints-Trace160-uint96- +:xref-Checkpoints-upperLookupRecent-struct-Checkpoints-Trace160-uint96-: xref:utils.adoc#Checkpoints-upperLookupRecent-struct-Checkpoints-Trace160-uint96- +:xref-Checkpoints-latest-struct-Checkpoints-Trace160-: xref:utils.adoc#Checkpoints-latest-struct-Checkpoints-Trace160- +:xref-Checkpoints-latestCheckpoint-struct-Checkpoints-Trace160-: xref:utils.adoc#Checkpoints-latestCheckpoint-struct-Checkpoints-Trace160- +:xref-Checkpoints-length-struct-Checkpoints-Trace160-: xref:utils.adoc#Checkpoints-length-struct-Checkpoints-Trace160- +:xref-Checkpoints-at-struct-Checkpoints-Trace160-uint32-: xref:utils.adoc#Checkpoints-at-struct-Checkpoints-Trace160-uint32- +:xref-Checkpoints-CheckpointUnorderedInsertion--: xref:utils.adoc#Checkpoints-CheckpointUnorderedInsertion-- +:xref-Create2-deploy-uint256-bytes32-bytes-: xref:utils.adoc#Create2-deploy-uint256-bytes32-bytes- +:xref-Create2-computeAddress-bytes32-bytes32-: xref:utils.adoc#Create2-computeAddress-bytes32-bytes32- +:xref-Create2-computeAddress-bytes32-bytes32-address-: xref:utils.adoc#Create2-computeAddress-bytes32-bytes32-address- +:xref-Create2-Create2InsufficientBalance-uint256-uint256-: xref:utils.adoc#Create2-Create2InsufficientBalance-uint256-uint256- +:xref-Create2-Create2EmptyBytecode--: xref:utils.adoc#Create2-Create2EmptyBytecode-- +:xref-Create2-Create2FailedDeployment--: xref:utils.adoc#Create2-Create2FailedDeployment-- +:xref-Address-sendValue-address-payable-uint256-: xref:utils.adoc#Address-sendValue-address-payable-uint256- +:xref-Address-functionCall-address-bytes-: xref:utils.adoc#Address-functionCall-address-bytes- +:xref-Address-functionCallWithValue-address-bytes-uint256-: xref:utils.adoc#Address-functionCallWithValue-address-bytes-uint256- +:xref-Address-functionStaticCall-address-bytes-: xref:utils.adoc#Address-functionStaticCall-address-bytes- +:xref-Address-functionDelegateCall-address-bytes-: xref:utils.adoc#Address-functionDelegateCall-address-bytes- +:xref-Address-verifyCallResultFromTarget-address-bool-bytes-: xref:utils.adoc#Address-verifyCallResultFromTarget-address-bool-bytes- +:xref-Address-verifyCallResult-bool-bytes-: xref:utils.adoc#Address-verifyCallResult-bool-bytes- +:xref-Address-AddressInsufficientBalance-address-: xref:utils.adoc#Address-AddressInsufficientBalance-address- +:xref-Address-AddressEmptyCode-address-: xref:utils.adoc#Address-AddressEmptyCode-address- +:xref-Address-FailedInnerCall--: xref:utils.adoc#Address-FailedInnerCall-- +:ReentrancyGuard: pass:normal[xref:utils.adoc#ReentrancyGuard[`ReentrancyGuard`]] +:xref-Address-functionCall-address-bytes-: xref:utils.adoc#Address-functionCall-address-bytes- +:xref-Address-functionCall-address-bytes-: xref:utils.adoc#Address-functionCall-address-bytes- +:xref-Address-functionCall-address-bytes-: xref:utils.adoc#Address-functionCall-address-bytes- +:xref-Arrays-findUpperBound-uint256---uint256-: xref:utils.adoc#Arrays-findUpperBound-uint256---uint256- +:xref-Arrays-unsafeAccess-address---uint256-: xref:utils.adoc#Arrays-unsafeAccess-address---uint256- +:xref-Arrays-unsafeAccess-bytes32---uint256-: xref:utils.adoc#Arrays-unsafeAccess-bytes32---uint256- +:xref-Arrays-unsafeAccess-uint256---uint256-: xref:utils.adoc#Arrays-unsafeAccess-uint256---uint256- +:xref-Arrays-unsafeMemoryAccess-uint256---uint256-: xref:utils.adoc#Arrays-unsafeMemoryAccess-uint256---uint256- +:xref-Arrays-unsafeMemoryAccess-address---uint256-: xref:utils.adoc#Arrays-unsafeMemoryAccess-address---uint256- +:xref-Base64-encode-bytes-: xref:utils.adoc#Base64-encode-bytes- +:xref-Strings-toString-uint256-: xref:utils.adoc#Strings-toString-uint256- +:xref-Strings-toStringSigned-int256-: xref:utils.adoc#Strings-toStringSigned-int256- +:xref-Strings-toHexString-uint256-: xref:utils.adoc#Strings-toHexString-uint256- +:xref-Strings-toHexString-uint256-uint256-: xref:utils.adoc#Strings-toHexString-uint256-uint256- +:xref-Strings-toHexString-address-: xref:utils.adoc#Strings-toHexString-address- +:xref-Strings-equal-string-string-: xref:utils.adoc#Strings-equal-string-string- +:xref-Strings-StringsInsufficientHexLength-uint256-uint256-: xref:utils.adoc#Strings-StringsInsufficientHexLength-uint256-uint256- +:xref-ShortStrings-toShortString-string-: xref:utils.adoc#ShortStrings-toShortString-string- +:xref-ShortStrings-toString-ShortString-: xref:utils.adoc#ShortStrings-toString-ShortString- +:xref-ShortStrings-byteLength-ShortString-: xref:utils.adoc#ShortStrings-byteLength-ShortString- +:xref-ShortStrings-toShortStringWithFallback-string-string-: xref:utils.adoc#ShortStrings-toShortStringWithFallback-string-string- +:xref-ShortStrings-toStringWithFallback-ShortString-string-: xref:utils.adoc#ShortStrings-toStringWithFallback-ShortString-string- +:xref-ShortStrings-byteLengthWithFallback-ShortString-string-: xref:utils.adoc#ShortStrings-byteLengthWithFallback-ShortString-string- +:xref-ShortStrings-StringTooLong-string-: xref:utils.adoc#ShortStrings-StringTooLong-string- +:xref-ShortStrings-InvalidShortString--: xref:utils.adoc#ShortStrings-InvalidShortString-- +:xref-StorageSlot-getAddressSlot-bytes32-: xref:utils.adoc#StorageSlot-getAddressSlot-bytes32- +:xref-StorageSlot-getBooleanSlot-bytes32-: xref:utils.adoc#StorageSlot-getBooleanSlot-bytes32- +:xref-StorageSlot-getBytes32Slot-bytes32-: xref:utils.adoc#StorageSlot-getBytes32Slot-bytes32- +:xref-StorageSlot-getUint256Slot-bytes32-: xref:utils.adoc#StorageSlot-getUint256Slot-bytes32- +:xref-StorageSlot-getStringSlot-bytes32-: xref:utils.adoc#StorageSlot-getStringSlot-bytes32- +:xref-StorageSlot-getStringSlot-string-: xref:utils.adoc#StorageSlot-getStringSlot-string- +:xref-StorageSlot-getBytesSlot-bytes32-: xref:utils.adoc#StorageSlot-getBytesSlot-bytes32- +:xref-StorageSlot-getBytesSlot-bytes-: xref:utils.adoc#StorageSlot-getBytesSlot-bytes- +:xref-Multicall-multicall-bytes---: xref:utils.adoc#Multicall-multicall-bytes--- += Utilities + +[.readme-notice] +NOTE: This document is better viewed at https://docs.openzeppelin.com/contracts/api/utils + +Miscellaneous contracts and libraries containing utility functions you can use to improve security, work with new data types, or safely use low-level primitives. + + * {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. + * {SafeCast}: Checked downcasting functions to avoid silent truncation. + * {Math}, {SignedMath}: Implementation of various arithmetic functions. + * {Multicall}: Simple way to batch together multiple calls in a single external call. + * {Create2}: Wrapper around the https://blog.openzeppelin.com/getting-the-most-out-of-create2/[`CREATE2` EVM opcode] for safe use without having to deal with low-level assembly. + * {EnumerableMap}: A type like Solidity's https://solidity.readthedocs.io/en/latest/types.html#mapping-types[`mapping`], 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. + +[NOTE] +==== +Because Solidity does not support generic types, {EnumerableMap} and {EnumerableSet} are specialized to a limited number of key-value types. +==== + +== Math + +:MathOverflowedMulDiv: pass:normal[xref:#Math-MathOverflowedMulDiv--[`++MathOverflowedMulDiv++`]] +:Rounding: pass:normal[xref:#Math-Rounding[`++Rounding++`]] +:tryAdd: pass:normal[xref:#Math-tryAdd-uint256-uint256-[`++tryAdd++`]] +:trySub: pass:normal[xref:#Math-trySub-uint256-uint256-[`++trySub++`]] +:tryMul: pass:normal[xref:#Math-tryMul-uint256-uint256-[`++tryMul++`]] +:tryDiv: pass:normal[xref:#Math-tryDiv-uint256-uint256-[`++tryDiv++`]] +:tryMod: pass:normal[xref:#Math-tryMod-uint256-uint256-[`++tryMod++`]] +:max: pass:normal[xref:#Math-max-uint256-uint256-[`++max++`]] +:min: pass:normal[xref:#Math-min-uint256-uint256-[`++min++`]] +:average: pass:normal[xref:#Math-average-uint256-uint256-[`++average++`]] +:ceilDiv: pass:normal[xref:#Math-ceilDiv-uint256-uint256-[`++ceilDiv++`]] +:mulDiv: pass:normal[xref:#Math-mulDiv-uint256-uint256-uint256-[`++mulDiv++`]] +:mulDiv: pass:normal[xref:#Math-mulDiv-uint256-uint256-uint256-enum-Math-Rounding-[`++mulDiv++`]] +:sqrt: pass:normal[xref:#Math-sqrt-uint256-[`++sqrt++`]] +:sqrt: pass:normal[xref:#Math-sqrt-uint256-enum-Math-Rounding-[`++sqrt++`]] +:log2: pass:normal[xref:#Math-log2-uint256-[`++log2++`]] +:log2: pass:normal[xref:#Math-log2-uint256-enum-Math-Rounding-[`++log2++`]] +:log10: pass:normal[xref:#Math-log10-uint256-[`++log10++`]] +:log10: pass:normal[xref:#Math-log10-uint256-enum-Math-Rounding-[`++log10++`]] +:log256: pass:normal[xref:#Math-log256-uint256-[`++log256++`]] +:log256: pass:normal[xref:#Math-log256-uint256-enum-Math-Rounding-[`++log256++`]] +:unsignedRoundsUp: pass:normal[xref:#Math-unsignedRoundsUp-enum-Math-Rounding-[`++unsignedRoundsUp++`]] + +[.contract] +[[Math]] +=== `++Math++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/utils/math/Math.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/utils/math/Math.sol"; +``` + +Standard math utilities missing in the Solidity language. + +[.contract-index] +.Functions +-- +* {xref-Math-tryAdd-uint256-uint256-}[`++tryAdd(a, b)++`] +* {xref-Math-trySub-uint256-uint256-}[`++trySub(a, b)++`] +* {xref-Math-tryMul-uint256-uint256-}[`++tryMul(a, b)++`] +* {xref-Math-tryDiv-uint256-uint256-}[`++tryDiv(a, b)++`] +* {xref-Math-tryMod-uint256-uint256-}[`++tryMod(a, b)++`] +* {xref-Math-max-uint256-uint256-}[`++max(a, b)++`] +* {xref-Math-min-uint256-uint256-}[`++min(a, b)++`] +* {xref-Math-average-uint256-uint256-}[`++average(a, b)++`] +* {xref-Math-ceilDiv-uint256-uint256-}[`++ceilDiv(a, b)++`] +* {xref-Math-mulDiv-uint256-uint256-uint256-}[`++mulDiv(x, y, denominator)++`] +* {xref-Math-mulDiv-uint256-uint256-uint256-enum-Math-Rounding-}[`++mulDiv(x, y, denominator, rounding)++`] +* {xref-Math-sqrt-uint256-}[`++sqrt(a)++`] +* {xref-Math-sqrt-uint256-enum-Math-Rounding-}[`++sqrt(a, rounding)++`] +* {xref-Math-log2-uint256-}[`++log2(value)++`] +* {xref-Math-log2-uint256-enum-Math-Rounding-}[`++log2(value, rounding)++`] +* {xref-Math-log10-uint256-}[`++log10(value)++`] +* {xref-Math-log10-uint256-enum-Math-Rounding-}[`++log10(value, rounding)++`] +* {xref-Math-log256-uint256-}[`++log256(value)++`] +* {xref-Math-log256-uint256-enum-Math-Rounding-}[`++log256(value, rounding)++`] +* {xref-Math-unsignedRoundsUp-enum-Math-Rounding-}[`++unsignedRoundsUp(rounding)++`] + +-- + +[.contract-index] +.Errors +-- +* {xref-Math-MathOverflowedMulDiv--}[`++MathOverflowedMulDiv()++`] + +-- + +[.contract-item] +[[Math-tryAdd-uint256-uint256-]] +==== `[.contract-item-name]#++tryAdd++#++(uint256 a, uint256 b) β†’ bool, uint256++` [.item-kind]#internal# + +Returns the addition of two unsigned integers, with an overflow flag. + +[.contract-item] +[[Math-trySub-uint256-uint256-]] +==== `[.contract-item-name]#++trySub++#++(uint256 a, uint256 b) β†’ bool, uint256++` [.item-kind]#internal# + +Returns the subtraction of two unsigned integers, with an overflow flag. + +[.contract-item] +[[Math-tryMul-uint256-uint256-]] +==== `[.contract-item-name]#++tryMul++#++(uint256 a, uint256 b) β†’ bool, uint256++` [.item-kind]#internal# + +Returns the multiplication of two unsigned integers, with an overflow flag. + +[.contract-item] +[[Math-tryDiv-uint256-uint256-]] +==== `[.contract-item-name]#++tryDiv++#++(uint256 a, uint256 b) β†’ bool, uint256++` [.item-kind]#internal# + +Returns the division of two unsigned integers, with a division by zero flag. + +[.contract-item] +[[Math-tryMod-uint256-uint256-]] +==== `[.contract-item-name]#++tryMod++#++(uint256 a, uint256 b) β†’ bool, uint256++` [.item-kind]#internal# + +Returns the remainder of dividing two unsigned integers, with a division by zero flag. + +[.contract-item] +[[Math-max-uint256-uint256-]] +==== `[.contract-item-name]#++max++#++(uint256 a, uint256 b) β†’ uint256++` [.item-kind]#internal# + +Returns the largest of two numbers. + +[.contract-item] +[[Math-min-uint256-uint256-]] +==== `[.contract-item-name]#++min++#++(uint256 a, uint256 b) β†’ uint256++` [.item-kind]#internal# + +Returns the smallest of two numbers. + +[.contract-item] +[[Math-average-uint256-uint256-]] +==== `[.contract-item-name]#++average++#++(uint256 a, uint256 b) β†’ uint256++` [.item-kind]#internal# + +Returns the average of two numbers. The result is rounded towards +zero. + +[.contract-item] +[[Math-ceilDiv-uint256-uint256-]] +==== `[.contract-item-name]#++ceilDiv++#++(uint256 a, uint256 b) β†’ uint256++` [.item-kind]#internal# + +Returns the ceiling of the division of two numbers. + +This differs from standard division with `/` in that it rounds towards infinity instead +of rounding towards zero. + +[.contract-item] +[[Math-mulDiv-uint256-uint256-uint256-]] +==== `[.contract-item-name]#++mulDiv++#++(uint256 x, uint256 y, uint256 denominator) β†’ uint256 result++` [.item-kind]#internal# + +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. + +[.contract-item] +[[Math-mulDiv-uint256-uint256-uint256-enum-Math-Rounding-]] +==== `[.contract-item-name]#++mulDiv++#++(uint256 x, uint256 y, uint256 denominator, enum Math.Rounding rounding) β†’ uint256++` [.item-kind]#internal# + +[.contract-item] +[[Math-sqrt-uint256-]] +==== `[.contract-item-name]#++sqrt++#++(uint256 a) β†’ uint256++` [.item-kind]#internal# + +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). + +[.contract-item] +[[Math-sqrt-uint256-enum-Math-Rounding-]] +==== `[.contract-item-name]#++sqrt++#++(uint256 a, enum Math.Rounding rounding) β†’ uint256++` [.item-kind]#internal# + +[.contract-item] +[[Math-log2-uint256-]] +==== `[.contract-item-name]#++log2++#++(uint256 value) β†’ uint256++` [.item-kind]#internal# + +Return the log in base 2 of a positive value rounded towards zero. +Returns 0 if given 0. + +[.contract-item] +[[Math-log2-uint256-enum-Math-Rounding-]] +==== `[.contract-item-name]#++log2++#++(uint256 value, enum Math.Rounding rounding) β†’ uint256++` [.item-kind]#internal# + +Return the log in base 2, following the selected rounding direction, of a positive value. +Returns 0 if given 0. + +[.contract-item] +[[Math-log10-uint256-]] +==== `[.contract-item-name]#++log10++#++(uint256 value) β†’ uint256++` [.item-kind]#internal# + +Return the log in base 10 of a positive value rounded towards zero. +Returns 0 if given 0. + +[.contract-item] +[[Math-log10-uint256-enum-Math-Rounding-]] +==== `[.contract-item-name]#++log10++#++(uint256 value, enum Math.Rounding rounding) β†’ uint256++` [.item-kind]#internal# + +Return the log in base 10, following the selected rounding direction, of a positive value. +Returns 0 if given 0. + +[.contract-item] +[[Math-log256-uint256-]] +==== `[.contract-item-name]#++log256++#++(uint256 value) β†’ uint256++` [.item-kind]#internal# + +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. + +[.contract-item] +[[Math-log256-uint256-enum-Math-Rounding-]] +==== `[.contract-item-name]#++log256++#++(uint256 value, enum Math.Rounding rounding) β†’ uint256++` [.item-kind]#internal# + +Return the log in base 256, following the selected rounding direction, of a positive value. +Returns 0 if given 0. + +[.contract-item] +[[Math-unsignedRoundsUp-enum-Math-Rounding-]] +==== `[.contract-item-name]#++unsignedRoundsUp++#++(enum Math.Rounding rounding) β†’ bool++` [.item-kind]#internal# + +Returns whether a provided rounding mode is considered rounding up for unsigned integers. + +[.contract-item] +[[Math-MathOverflowedMulDiv--]] +==== `[.contract-item-name]#++MathOverflowedMulDiv++#++()++` [.item-kind]#error# + +Muldiv operation overflow. + +:max: pass:normal[xref:#SignedMath-max-int256-int256-[`++max++`]] +:min: pass:normal[xref:#SignedMath-min-int256-int256-[`++min++`]] +:average: pass:normal[xref:#SignedMath-average-int256-int256-[`++average++`]] +:abs: pass:normal[xref:#SignedMath-abs-int256-[`++abs++`]] + +[.contract] +[[SignedMath]] +=== `++SignedMath++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/utils/math/SignedMath.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/utils/math/SignedMath.sol"; +``` + +Standard signed math utilities missing in the Solidity language. + +[.contract-index] +.Functions +-- +* {xref-SignedMath-max-int256-int256-}[`++max(a, b)++`] +* {xref-SignedMath-min-int256-int256-}[`++min(a, b)++`] +* {xref-SignedMath-average-int256-int256-}[`++average(a, b)++`] +* {xref-SignedMath-abs-int256-}[`++abs(n)++`] + +-- + +[.contract-item] +[[SignedMath-max-int256-int256-]] +==== `[.contract-item-name]#++max++#++(int256 a, int256 b) β†’ int256++` [.item-kind]#internal# + +Returns the largest of two signed numbers. + +[.contract-item] +[[SignedMath-min-int256-int256-]] +==== `[.contract-item-name]#++min++#++(int256 a, int256 b) β†’ int256++` [.item-kind]#internal# + +Returns the smallest of two signed numbers. + +[.contract-item] +[[SignedMath-average-int256-int256-]] +==== `[.contract-item-name]#++average++#++(int256 a, int256 b) β†’ int256++` [.item-kind]#internal# + +Returns the average of two signed numbers without overflow. +The result is rounded towards zero. + +[.contract-item] +[[SignedMath-abs-int256-]] +==== `[.contract-item-name]#++abs++#++(int256 n) β†’ uint256++` [.item-kind]#internal# + +Returns the absolute unsigned value of a signed value. + +:SafeCastOverflowedUintDowncast: pass:normal[xref:#SafeCast-SafeCastOverflowedUintDowncast-uint8-uint256-[`++SafeCastOverflowedUintDowncast++`]] +:SafeCastOverflowedIntToUint: pass:normal[xref:#SafeCast-SafeCastOverflowedIntToUint-int256-[`++SafeCastOverflowedIntToUint++`]] +:SafeCastOverflowedIntDowncast: pass:normal[xref:#SafeCast-SafeCastOverflowedIntDowncast-uint8-int256-[`++SafeCastOverflowedIntDowncast++`]] +:SafeCastOverflowedUintToInt: pass:normal[xref:#SafeCast-SafeCastOverflowedUintToInt-uint256-[`++SafeCastOverflowedUintToInt++`]] +:toUint248: pass:normal[xref:#SafeCast-toUint248-uint256-[`++toUint248++`]] +:toUint240: pass:normal[xref:#SafeCast-toUint240-uint256-[`++toUint240++`]] +:toUint232: pass:normal[xref:#SafeCast-toUint232-uint256-[`++toUint232++`]] +:toUint224: pass:normal[xref:#SafeCast-toUint224-uint256-[`++toUint224++`]] +:toUint216: pass:normal[xref:#SafeCast-toUint216-uint256-[`++toUint216++`]] +:toUint208: pass:normal[xref:#SafeCast-toUint208-uint256-[`++toUint208++`]] +:toUint200: pass:normal[xref:#SafeCast-toUint200-uint256-[`++toUint200++`]] +:toUint192: pass:normal[xref:#SafeCast-toUint192-uint256-[`++toUint192++`]] +:toUint184: pass:normal[xref:#SafeCast-toUint184-uint256-[`++toUint184++`]] +:toUint176: pass:normal[xref:#SafeCast-toUint176-uint256-[`++toUint176++`]] +:toUint168: pass:normal[xref:#SafeCast-toUint168-uint256-[`++toUint168++`]] +:toUint160: pass:normal[xref:#SafeCast-toUint160-uint256-[`++toUint160++`]] +:toUint152: pass:normal[xref:#SafeCast-toUint152-uint256-[`++toUint152++`]] +:toUint144: pass:normal[xref:#SafeCast-toUint144-uint256-[`++toUint144++`]] +:toUint136: pass:normal[xref:#SafeCast-toUint136-uint256-[`++toUint136++`]] +:toUint128: pass:normal[xref:#SafeCast-toUint128-uint256-[`++toUint128++`]] +:toUint120: pass:normal[xref:#SafeCast-toUint120-uint256-[`++toUint120++`]] +:toUint112: pass:normal[xref:#SafeCast-toUint112-uint256-[`++toUint112++`]] +:toUint104: pass:normal[xref:#SafeCast-toUint104-uint256-[`++toUint104++`]] +:toUint96: pass:normal[xref:#SafeCast-toUint96-uint256-[`++toUint96++`]] +:toUint88: pass:normal[xref:#SafeCast-toUint88-uint256-[`++toUint88++`]] +:toUint80: pass:normal[xref:#SafeCast-toUint80-uint256-[`++toUint80++`]] +:toUint72: pass:normal[xref:#SafeCast-toUint72-uint256-[`++toUint72++`]] +:toUint64: pass:normal[xref:#SafeCast-toUint64-uint256-[`++toUint64++`]] +:toUint56: pass:normal[xref:#SafeCast-toUint56-uint256-[`++toUint56++`]] +:toUint48: pass:normal[xref:#SafeCast-toUint48-uint256-[`++toUint48++`]] +:toUint40: pass:normal[xref:#SafeCast-toUint40-uint256-[`++toUint40++`]] +:toUint32: pass:normal[xref:#SafeCast-toUint32-uint256-[`++toUint32++`]] +:toUint24: pass:normal[xref:#SafeCast-toUint24-uint256-[`++toUint24++`]] +:toUint16: pass:normal[xref:#SafeCast-toUint16-uint256-[`++toUint16++`]] +:toUint8: pass:normal[xref:#SafeCast-toUint8-uint256-[`++toUint8++`]] +:toUint256: pass:normal[xref:#SafeCast-toUint256-int256-[`++toUint256++`]] +:toInt248: pass:normal[xref:#SafeCast-toInt248-int256-[`++toInt248++`]] +:toInt240: pass:normal[xref:#SafeCast-toInt240-int256-[`++toInt240++`]] +:toInt232: pass:normal[xref:#SafeCast-toInt232-int256-[`++toInt232++`]] +:toInt224: pass:normal[xref:#SafeCast-toInt224-int256-[`++toInt224++`]] +:toInt216: pass:normal[xref:#SafeCast-toInt216-int256-[`++toInt216++`]] +:toInt208: pass:normal[xref:#SafeCast-toInt208-int256-[`++toInt208++`]] +:toInt200: pass:normal[xref:#SafeCast-toInt200-int256-[`++toInt200++`]] +:toInt192: pass:normal[xref:#SafeCast-toInt192-int256-[`++toInt192++`]] +:toInt184: pass:normal[xref:#SafeCast-toInt184-int256-[`++toInt184++`]] +:toInt176: pass:normal[xref:#SafeCast-toInt176-int256-[`++toInt176++`]] +:toInt168: pass:normal[xref:#SafeCast-toInt168-int256-[`++toInt168++`]] +:toInt160: pass:normal[xref:#SafeCast-toInt160-int256-[`++toInt160++`]] +:toInt152: pass:normal[xref:#SafeCast-toInt152-int256-[`++toInt152++`]] +:toInt144: pass:normal[xref:#SafeCast-toInt144-int256-[`++toInt144++`]] +:toInt136: pass:normal[xref:#SafeCast-toInt136-int256-[`++toInt136++`]] +:toInt128: pass:normal[xref:#SafeCast-toInt128-int256-[`++toInt128++`]] +:toInt120: pass:normal[xref:#SafeCast-toInt120-int256-[`++toInt120++`]] +:toInt112: pass:normal[xref:#SafeCast-toInt112-int256-[`++toInt112++`]] +:toInt104: pass:normal[xref:#SafeCast-toInt104-int256-[`++toInt104++`]] +:toInt96: pass:normal[xref:#SafeCast-toInt96-int256-[`++toInt96++`]] +:toInt88: pass:normal[xref:#SafeCast-toInt88-int256-[`++toInt88++`]] +:toInt80: pass:normal[xref:#SafeCast-toInt80-int256-[`++toInt80++`]] +:toInt72: pass:normal[xref:#SafeCast-toInt72-int256-[`++toInt72++`]] +:toInt64: pass:normal[xref:#SafeCast-toInt64-int256-[`++toInt64++`]] +:toInt56: pass:normal[xref:#SafeCast-toInt56-int256-[`++toInt56++`]] +:toInt48: pass:normal[xref:#SafeCast-toInt48-int256-[`++toInt48++`]] +:toInt40: pass:normal[xref:#SafeCast-toInt40-int256-[`++toInt40++`]] +:toInt32: pass:normal[xref:#SafeCast-toInt32-int256-[`++toInt32++`]] +:toInt24: pass:normal[xref:#SafeCast-toInt24-int256-[`++toInt24++`]] +:toInt16: pass:normal[xref:#SafeCast-toInt16-int256-[`++toInt16++`]] +:toInt8: pass:normal[xref:#SafeCast-toInt8-int256-[`++toInt8++`]] +:toInt256: pass:normal[xref:#SafeCast-toInt256-uint256-[`++toInt256++`]] + +[.contract] +[[SafeCast]] +=== `++SafeCast++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/utils/math/SafeCast.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/utils/math/SafeCast.sol"; +``` + +Wrappers over Solidity's uintXX/intXX casting operators with added overflow +checks. + +Downcasting from uint256/int256 in Solidity does not revert on overflow. This can +easily result in undesired exploitation or bugs, since developers usually +assume that overflows raise errors. `SafeCast` restores this intuition by +reverting the transaction when such an operation overflows. + +Using this library instead of the unchecked operations eliminates an entire +class of bugs, so it's recommended to use it always. + +[.contract-index] +.Functions +-- +* {xref-SafeCast-toUint248-uint256-}[`++toUint248(value)++`] +* {xref-SafeCast-toUint240-uint256-}[`++toUint240(value)++`] +* {xref-SafeCast-toUint232-uint256-}[`++toUint232(value)++`] +* {xref-SafeCast-toUint224-uint256-}[`++toUint224(value)++`] +* {xref-SafeCast-toUint216-uint256-}[`++toUint216(value)++`] +* {xref-SafeCast-toUint208-uint256-}[`++toUint208(value)++`] +* {xref-SafeCast-toUint200-uint256-}[`++toUint200(value)++`] +* {xref-SafeCast-toUint192-uint256-}[`++toUint192(value)++`] +* {xref-SafeCast-toUint184-uint256-}[`++toUint184(value)++`] +* {xref-SafeCast-toUint176-uint256-}[`++toUint176(value)++`] +* {xref-SafeCast-toUint168-uint256-}[`++toUint168(value)++`] +* {xref-SafeCast-toUint160-uint256-}[`++toUint160(value)++`] +* {xref-SafeCast-toUint152-uint256-}[`++toUint152(value)++`] +* {xref-SafeCast-toUint144-uint256-}[`++toUint144(value)++`] +* {xref-SafeCast-toUint136-uint256-}[`++toUint136(value)++`] +* {xref-SafeCast-toUint128-uint256-}[`++toUint128(value)++`] +* {xref-SafeCast-toUint120-uint256-}[`++toUint120(value)++`] +* {xref-SafeCast-toUint112-uint256-}[`++toUint112(value)++`] +* {xref-SafeCast-toUint104-uint256-}[`++toUint104(value)++`] +* {xref-SafeCast-toUint96-uint256-}[`++toUint96(value)++`] +* {xref-SafeCast-toUint88-uint256-}[`++toUint88(value)++`] +* {xref-SafeCast-toUint80-uint256-}[`++toUint80(value)++`] +* {xref-SafeCast-toUint72-uint256-}[`++toUint72(value)++`] +* {xref-SafeCast-toUint64-uint256-}[`++toUint64(value)++`] +* {xref-SafeCast-toUint56-uint256-}[`++toUint56(value)++`] +* {xref-SafeCast-toUint48-uint256-}[`++toUint48(value)++`] +* {xref-SafeCast-toUint40-uint256-}[`++toUint40(value)++`] +* {xref-SafeCast-toUint32-uint256-}[`++toUint32(value)++`] +* {xref-SafeCast-toUint24-uint256-}[`++toUint24(value)++`] +* {xref-SafeCast-toUint16-uint256-}[`++toUint16(value)++`] +* {xref-SafeCast-toUint8-uint256-}[`++toUint8(value)++`] +* {xref-SafeCast-toUint256-int256-}[`++toUint256(value)++`] +* {xref-SafeCast-toInt248-int256-}[`++toInt248(value)++`] +* {xref-SafeCast-toInt240-int256-}[`++toInt240(value)++`] +* {xref-SafeCast-toInt232-int256-}[`++toInt232(value)++`] +* {xref-SafeCast-toInt224-int256-}[`++toInt224(value)++`] +* {xref-SafeCast-toInt216-int256-}[`++toInt216(value)++`] +* {xref-SafeCast-toInt208-int256-}[`++toInt208(value)++`] +* {xref-SafeCast-toInt200-int256-}[`++toInt200(value)++`] +* {xref-SafeCast-toInt192-int256-}[`++toInt192(value)++`] +* {xref-SafeCast-toInt184-int256-}[`++toInt184(value)++`] +* {xref-SafeCast-toInt176-int256-}[`++toInt176(value)++`] +* {xref-SafeCast-toInt168-int256-}[`++toInt168(value)++`] +* {xref-SafeCast-toInt160-int256-}[`++toInt160(value)++`] +* {xref-SafeCast-toInt152-int256-}[`++toInt152(value)++`] +* {xref-SafeCast-toInt144-int256-}[`++toInt144(value)++`] +* {xref-SafeCast-toInt136-int256-}[`++toInt136(value)++`] +* {xref-SafeCast-toInt128-int256-}[`++toInt128(value)++`] +* {xref-SafeCast-toInt120-int256-}[`++toInt120(value)++`] +* {xref-SafeCast-toInt112-int256-}[`++toInt112(value)++`] +* {xref-SafeCast-toInt104-int256-}[`++toInt104(value)++`] +* {xref-SafeCast-toInt96-int256-}[`++toInt96(value)++`] +* {xref-SafeCast-toInt88-int256-}[`++toInt88(value)++`] +* {xref-SafeCast-toInt80-int256-}[`++toInt80(value)++`] +* {xref-SafeCast-toInt72-int256-}[`++toInt72(value)++`] +* {xref-SafeCast-toInt64-int256-}[`++toInt64(value)++`] +* {xref-SafeCast-toInt56-int256-}[`++toInt56(value)++`] +* {xref-SafeCast-toInt48-int256-}[`++toInt48(value)++`] +* {xref-SafeCast-toInt40-int256-}[`++toInt40(value)++`] +* {xref-SafeCast-toInt32-int256-}[`++toInt32(value)++`] +* {xref-SafeCast-toInt24-int256-}[`++toInt24(value)++`] +* {xref-SafeCast-toInt16-int256-}[`++toInt16(value)++`] +* {xref-SafeCast-toInt8-int256-}[`++toInt8(value)++`] +* {xref-SafeCast-toInt256-uint256-}[`++toInt256(value)++`] + +-- + +[.contract-index] +.Errors +-- +* {xref-SafeCast-SafeCastOverflowedUintDowncast-uint8-uint256-}[`++SafeCastOverflowedUintDowncast(bits, value)++`] +* {xref-SafeCast-SafeCastOverflowedIntToUint-int256-}[`++SafeCastOverflowedIntToUint(value)++`] +* {xref-SafeCast-SafeCastOverflowedIntDowncast-uint8-int256-}[`++SafeCastOverflowedIntDowncast(bits, value)++`] +* {xref-SafeCast-SafeCastOverflowedUintToInt-uint256-}[`++SafeCastOverflowedUintToInt(value)++`] + +-- + +[.contract-item] +[[SafeCast-toUint248-uint256-]] +==== `[.contract-item-name]#++toUint248++#++(uint256 value) β†’ uint248++` [.item-kind]#internal# + +Returns the downcasted uint248 from uint256, reverting on +overflow (when the input is greater than largest uint248). + +Counterpart to Solidity's `uint248` operator. + +Requirements: + +- input must fit into 248 bits + +[.contract-item] +[[SafeCast-toUint240-uint256-]] +==== `[.contract-item-name]#++toUint240++#++(uint256 value) β†’ uint240++` [.item-kind]#internal# + +Returns the downcasted uint240 from uint256, reverting on +overflow (when the input is greater than largest uint240). + +Counterpart to Solidity's `uint240` operator. + +Requirements: + +- input must fit into 240 bits + +[.contract-item] +[[SafeCast-toUint232-uint256-]] +==== `[.contract-item-name]#++toUint232++#++(uint256 value) β†’ uint232++` [.item-kind]#internal# + +Returns the downcasted uint232 from uint256, reverting on +overflow (when the input is greater than largest uint232). + +Counterpart to Solidity's `uint232` operator. + +Requirements: + +- input must fit into 232 bits + +[.contract-item] +[[SafeCast-toUint224-uint256-]] +==== `[.contract-item-name]#++toUint224++#++(uint256 value) β†’ uint224++` [.item-kind]#internal# + +Returns the downcasted uint224 from uint256, reverting on +overflow (when the input is greater than largest uint224). + +Counterpart to Solidity's `uint224` operator. + +Requirements: + +- input must fit into 224 bits + +[.contract-item] +[[SafeCast-toUint216-uint256-]] +==== `[.contract-item-name]#++toUint216++#++(uint256 value) β†’ uint216++` [.item-kind]#internal# + +Returns the downcasted uint216 from uint256, reverting on +overflow (when the input is greater than largest uint216). + +Counterpart to Solidity's `uint216` operator. + +Requirements: + +- input must fit into 216 bits + +[.contract-item] +[[SafeCast-toUint208-uint256-]] +==== `[.contract-item-name]#++toUint208++#++(uint256 value) β†’ uint208++` [.item-kind]#internal# + +Returns the downcasted uint208 from uint256, reverting on +overflow (when the input is greater than largest uint208). + +Counterpart to Solidity's `uint208` operator. + +Requirements: + +- input must fit into 208 bits + +[.contract-item] +[[SafeCast-toUint200-uint256-]] +==== `[.contract-item-name]#++toUint200++#++(uint256 value) β†’ uint200++` [.item-kind]#internal# + +Returns the downcasted uint200 from uint256, reverting on +overflow (when the input is greater than largest uint200). + +Counterpart to Solidity's `uint200` operator. + +Requirements: + +- input must fit into 200 bits + +[.contract-item] +[[SafeCast-toUint192-uint256-]] +==== `[.contract-item-name]#++toUint192++#++(uint256 value) β†’ uint192++` [.item-kind]#internal# + +Returns the downcasted uint192 from uint256, reverting on +overflow (when the input is greater than largest uint192). + +Counterpart to Solidity's `uint192` operator. + +Requirements: + +- input must fit into 192 bits + +[.contract-item] +[[SafeCast-toUint184-uint256-]] +==== `[.contract-item-name]#++toUint184++#++(uint256 value) β†’ uint184++` [.item-kind]#internal# + +Returns the downcasted uint184 from uint256, reverting on +overflow (when the input is greater than largest uint184). + +Counterpart to Solidity's `uint184` operator. + +Requirements: + +- input must fit into 184 bits + +[.contract-item] +[[SafeCast-toUint176-uint256-]] +==== `[.contract-item-name]#++toUint176++#++(uint256 value) β†’ uint176++` [.item-kind]#internal# + +Returns the downcasted uint176 from uint256, reverting on +overflow (when the input is greater than largest uint176). + +Counterpart to Solidity's `uint176` operator. + +Requirements: + +- input must fit into 176 bits + +[.contract-item] +[[SafeCast-toUint168-uint256-]] +==== `[.contract-item-name]#++toUint168++#++(uint256 value) β†’ uint168++` [.item-kind]#internal# + +Returns the downcasted uint168 from uint256, reverting on +overflow (when the input is greater than largest uint168). + +Counterpart to Solidity's `uint168` operator. + +Requirements: + +- input must fit into 168 bits + +[.contract-item] +[[SafeCast-toUint160-uint256-]] +==== `[.contract-item-name]#++toUint160++#++(uint256 value) β†’ uint160++` [.item-kind]#internal# + +Returns the downcasted uint160 from uint256, reverting on +overflow (when the input is greater than largest uint160). + +Counterpart to Solidity's `uint160` operator. + +Requirements: + +- input must fit into 160 bits + +[.contract-item] +[[SafeCast-toUint152-uint256-]] +==== `[.contract-item-name]#++toUint152++#++(uint256 value) β†’ uint152++` [.item-kind]#internal# + +Returns the downcasted uint152 from uint256, reverting on +overflow (when the input is greater than largest uint152). + +Counterpart to Solidity's `uint152` operator. + +Requirements: + +- input must fit into 152 bits + +[.contract-item] +[[SafeCast-toUint144-uint256-]] +==== `[.contract-item-name]#++toUint144++#++(uint256 value) β†’ uint144++` [.item-kind]#internal# + +Returns the downcasted uint144 from uint256, reverting on +overflow (when the input is greater than largest uint144). + +Counterpart to Solidity's `uint144` operator. + +Requirements: + +- input must fit into 144 bits + +[.contract-item] +[[SafeCast-toUint136-uint256-]] +==== `[.contract-item-name]#++toUint136++#++(uint256 value) β†’ uint136++` [.item-kind]#internal# + +Returns the downcasted uint136 from uint256, reverting on +overflow (when the input is greater than largest uint136). + +Counterpart to Solidity's `uint136` operator. + +Requirements: + +- input must fit into 136 bits + +[.contract-item] +[[SafeCast-toUint128-uint256-]] +==== `[.contract-item-name]#++toUint128++#++(uint256 value) β†’ uint128++` [.item-kind]#internal# + +Returns the downcasted uint128 from uint256, reverting on +overflow (when the input is greater than largest uint128). + +Counterpart to Solidity's `uint128` operator. + +Requirements: + +- input must fit into 128 bits + +[.contract-item] +[[SafeCast-toUint120-uint256-]] +==== `[.contract-item-name]#++toUint120++#++(uint256 value) β†’ uint120++` [.item-kind]#internal# + +Returns the downcasted uint120 from uint256, reverting on +overflow (when the input is greater than largest uint120). + +Counterpart to Solidity's `uint120` operator. + +Requirements: + +- input must fit into 120 bits + +[.contract-item] +[[SafeCast-toUint112-uint256-]] +==== `[.contract-item-name]#++toUint112++#++(uint256 value) β†’ uint112++` [.item-kind]#internal# + +Returns the downcasted uint112 from uint256, reverting on +overflow (when the input is greater than largest uint112). + +Counterpart to Solidity's `uint112` operator. + +Requirements: + +- input must fit into 112 bits + +[.contract-item] +[[SafeCast-toUint104-uint256-]] +==== `[.contract-item-name]#++toUint104++#++(uint256 value) β†’ uint104++` [.item-kind]#internal# + +Returns the downcasted uint104 from uint256, reverting on +overflow (when the input is greater than largest uint104). + +Counterpart to Solidity's `uint104` operator. + +Requirements: + +- input must fit into 104 bits + +[.contract-item] +[[SafeCast-toUint96-uint256-]] +==== `[.contract-item-name]#++toUint96++#++(uint256 value) β†’ uint96++` [.item-kind]#internal# + +Returns the downcasted uint96 from uint256, reverting on +overflow (when the input is greater than largest uint96). + +Counterpart to Solidity's `uint96` operator. + +Requirements: + +- input must fit into 96 bits + +[.contract-item] +[[SafeCast-toUint88-uint256-]] +==== `[.contract-item-name]#++toUint88++#++(uint256 value) β†’ uint88++` [.item-kind]#internal# + +Returns the downcasted uint88 from uint256, reverting on +overflow (when the input is greater than largest uint88). + +Counterpart to Solidity's `uint88` operator. + +Requirements: + +- input must fit into 88 bits + +[.contract-item] +[[SafeCast-toUint80-uint256-]] +==== `[.contract-item-name]#++toUint80++#++(uint256 value) β†’ uint80++` [.item-kind]#internal# + +Returns the downcasted uint80 from uint256, reverting on +overflow (when the input is greater than largest uint80). + +Counterpart to Solidity's `uint80` operator. + +Requirements: + +- input must fit into 80 bits + +[.contract-item] +[[SafeCast-toUint72-uint256-]] +==== `[.contract-item-name]#++toUint72++#++(uint256 value) β†’ uint72++` [.item-kind]#internal# + +Returns the downcasted uint72 from uint256, reverting on +overflow (when the input is greater than largest uint72). + +Counterpart to Solidity's `uint72` operator. + +Requirements: + +- input must fit into 72 bits + +[.contract-item] +[[SafeCast-toUint64-uint256-]] +==== `[.contract-item-name]#++toUint64++#++(uint256 value) β†’ uint64++` [.item-kind]#internal# + +Returns the downcasted uint64 from uint256, reverting on +overflow (when the input is greater than largest uint64). + +Counterpart to Solidity's `uint64` operator. + +Requirements: + +- input must fit into 64 bits + +[.contract-item] +[[SafeCast-toUint56-uint256-]] +==== `[.contract-item-name]#++toUint56++#++(uint256 value) β†’ uint56++` [.item-kind]#internal# + +Returns the downcasted uint56 from uint256, reverting on +overflow (when the input is greater than largest uint56). + +Counterpart to Solidity's `uint56` operator. + +Requirements: + +- input must fit into 56 bits + +[.contract-item] +[[SafeCast-toUint48-uint256-]] +==== `[.contract-item-name]#++toUint48++#++(uint256 value) β†’ uint48++` [.item-kind]#internal# + +Returns the downcasted uint48 from uint256, reverting on +overflow (when the input is greater than largest uint48). + +Counterpart to Solidity's `uint48` operator. + +Requirements: + +- input must fit into 48 bits + +[.contract-item] +[[SafeCast-toUint40-uint256-]] +==== `[.contract-item-name]#++toUint40++#++(uint256 value) β†’ uint40++` [.item-kind]#internal# + +Returns the downcasted uint40 from uint256, reverting on +overflow (when the input is greater than largest uint40). + +Counterpart to Solidity's `uint40` operator. + +Requirements: + +- input must fit into 40 bits + +[.contract-item] +[[SafeCast-toUint32-uint256-]] +==== `[.contract-item-name]#++toUint32++#++(uint256 value) β†’ uint32++` [.item-kind]#internal# + +Returns the downcasted uint32 from uint256, reverting on +overflow (when the input is greater than largest uint32). + +Counterpart to Solidity's `uint32` operator. + +Requirements: + +- input must fit into 32 bits + +[.contract-item] +[[SafeCast-toUint24-uint256-]] +==== `[.contract-item-name]#++toUint24++#++(uint256 value) β†’ uint24++` [.item-kind]#internal# + +Returns the downcasted uint24 from uint256, reverting on +overflow (when the input is greater than largest uint24). + +Counterpart to Solidity's `uint24` operator. + +Requirements: + +- input must fit into 24 bits + +[.contract-item] +[[SafeCast-toUint16-uint256-]] +==== `[.contract-item-name]#++toUint16++#++(uint256 value) β†’ uint16++` [.item-kind]#internal# + +Returns the downcasted uint16 from uint256, reverting on +overflow (when the input is greater than largest uint16). + +Counterpart to Solidity's `uint16` operator. + +Requirements: + +- input must fit into 16 bits + +[.contract-item] +[[SafeCast-toUint8-uint256-]] +==== `[.contract-item-name]#++toUint8++#++(uint256 value) β†’ uint8++` [.item-kind]#internal# + +Returns the downcasted uint8 from uint256, reverting on +overflow (when the input is greater than largest uint8). + +Counterpart to Solidity's `uint8` operator. + +Requirements: + +- input must fit into 8 bits + +[.contract-item] +[[SafeCast-toUint256-int256-]] +==== `[.contract-item-name]#++toUint256++#++(int256 value) β†’ uint256++` [.item-kind]#internal# + +Converts a signed int256 into an unsigned uint256. + +Requirements: + +- input must be greater than or equal to 0. + +[.contract-item] +[[SafeCast-toInt248-int256-]] +==== `[.contract-item-name]#++toInt248++#++(int256 value) β†’ int248 downcasted++` [.item-kind]#internal# + +Returns the downcasted int248 from int256, reverting on +overflow (when the input is less than smallest int248 or +greater than largest int248). + +Counterpart to Solidity's `int248` operator. + +Requirements: + +- input must fit into 248 bits + +[.contract-item] +[[SafeCast-toInt240-int256-]] +==== `[.contract-item-name]#++toInt240++#++(int256 value) β†’ int240 downcasted++` [.item-kind]#internal# + +Returns the downcasted int240 from int256, reverting on +overflow (when the input is less than smallest int240 or +greater than largest int240). + +Counterpart to Solidity's `int240` operator. + +Requirements: + +- input must fit into 240 bits + +[.contract-item] +[[SafeCast-toInt232-int256-]] +==== `[.contract-item-name]#++toInt232++#++(int256 value) β†’ int232 downcasted++` [.item-kind]#internal# + +Returns the downcasted int232 from int256, reverting on +overflow (when the input is less than smallest int232 or +greater than largest int232). + +Counterpart to Solidity's `int232` operator. + +Requirements: + +- input must fit into 232 bits + +[.contract-item] +[[SafeCast-toInt224-int256-]] +==== `[.contract-item-name]#++toInt224++#++(int256 value) β†’ int224 downcasted++` [.item-kind]#internal# + +Returns the downcasted int224 from int256, reverting on +overflow (when the input is less than smallest int224 or +greater than largest int224). + +Counterpart to Solidity's `int224` operator. + +Requirements: + +- input must fit into 224 bits + +[.contract-item] +[[SafeCast-toInt216-int256-]] +==== `[.contract-item-name]#++toInt216++#++(int256 value) β†’ int216 downcasted++` [.item-kind]#internal# + +Returns the downcasted int216 from int256, reverting on +overflow (when the input is less than smallest int216 or +greater than largest int216). + +Counterpart to Solidity's `int216` operator. + +Requirements: + +- input must fit into 216 bits + +[.contract-item] +[[SafeCast-toInt208-int256-]] +==== `[.contract-item-name]#++toInt208++#++(int256 value) β†’ int208 downcasted++` [.item-kind]#internal# + +Returns the downcasted int208 from int256, reverting on +overflow (when the input is less than smallest int208 or +greater than largest int208). + +Counterpart to Solidity's `int208` operator. + +Requirements: + +- input must fit into 208 bits + +[.contract-item] +[[SafeCast-toInt200-int256-]] +==== `[.contract-item-name]#++toInt200++#++(int256 value) β†’ int200 downcasted++` [.item-kind]#internal# + +Returns the downcasted int200 from int256, reverting on +overflow (when the input is less than smallest int200 or +greater than largest int200). + +Counterpart to Solidity's `int200` operator. + +Requirements: + +- input must fit into 200 bits + +[.contract-item] +[[SafeCast-toInt192-int256-]] +==== `[.contract-item-name]#++toInt192++#++(int256 value) β†’ int192 downcasted++` [.item-kind]#internal# + +Returns the downcasted int192 from int256, reverting on +overflow (when the input is less than smallest int192 or +greater than largest int192). + +Counterpart to Solidity's `int192` operator. + +Requirements: + +- input must fit into 192 bits + +[.contract-item] +[[SafeCast-toInt184-int256-]] +==== `[.contract-item-name]#++toInt184++#++(int256 value) β†’ int184 downcasted++` [.item-kind]#internal# + +Returns the downcasted int184 from int256, reverting on +overflow (when the input is less than smallest int184 or +greater than largest int184). + +Counterpart to Solidity's `int184` operator. + +Requirements: + +- input must fit into 184 bits + +[.contract-item] +[[SafeCast-toInt176-int256-]] +==== `[.contract-item-name]#++toInt176++#++(int256 value) β†’ int176 downcasted++` [.item-kind]#internal# + +Returns the downcasted int176 from int256, reverting on +overflow (when the input is less than smallest int176 or +greater than largest int176). + +Counterpart to Solidity's `int176` operator. + +Requirements: + +- input must fit into 176 bits + +[.contract-item] +[[SafeCast-toInt168-int256-]] +==== `[.contract-item-name]#++toInt168++#++(int256 value) β†’ int168 downcasted++` [.item-kind]#internal# + +Returns the downcasted int168 from int256, reverting on +overflow (when the input is less than smallest int168 or +greater than largest int168). + +Counterpart to Solidity's `int168` operator. + +Requirements: + +- input must fit into 168 bits + +[.contract-item] +[[SafeCast-toInt160-int256-]] +==== `[.contract-item-name]#++toInt160++#++(int256 value) β†’ int160 downcasted++` [.item-kind]#internal# + +Returns the downcasted int160 from int256, reverting on +overflow (when the input is less than smallest int160 or +greater than largest int160). + +Counterpart to Solidity's `int160` operator. + +Requirements: + +- input must fit into 160 bits + +[.contract-item] +[[SafeCast-toInt152-int256-]] +==== `[.contract-item-name]#++toInt152++#++(int256 value) β†’ int152 downcasted++` [.item-kind]#internal# + +Returns the downcasted int152 from int256, reverting on +overflow (when the input is less than smallest int152 or +greater than largest int152). + +Counterpart to Solidity's `int152` operator. + +Requirements: + +- input must fit into 152 bits + +[.contract-item] +[[SafeCast-toInt144-int256-]] +==== `[.contract-item-name]#++toInt144++#++(int256 value) β†’ int144 downcasted++` [.item-kind]#internal# + +Returns the downcasted int144 from int256, reverting on +overflow (when the input is less than smallest int144 or +greater than largest int144). + +Counterpart to Solidity's `int144` operator. + +Requirements: + +- input must fit into 144 bits + +[.contract-item] +[[SafeCast-toInt136-int256-]] +==== `[.contract-item-name]#++toInt136++#++(int256 value) β†’ int136 downcasted++` [.item-kind]#internal# + +Returns the downcasted int136 from int256, reverting on +overflow (when the input is less than smallest int136 or +greater than largest int136). + +Counterpart to Solidity's `int136` operator. + +Requirements: + +- input must fit into 136 bits + +[.contract-item] +[[SafeCast-toInt128-int256-]] +==== `[.contract-item-name]#++toInt128++#++(int256 value) β†’ int128 downcasted++` [.item-kind]#internal# + +Returns the downcasted int128 from int256, reverting on +overflow (when the input is less than smallest int128 or +greater than largest int128). + +Counterpart to Solidity's `int128` operator. + +Requirements: + +- input must fit into 128 bits + +[.contract-item] +[[SafeCast-toInt120-int256-]] +==== `[.contract-item-name]#++toInt120++#++(int256 value) β†’ int120 downcasted++` [.item-kind]#internal# + +Returns the downcasted int120 from int256, reverting on +overflow (when the input is less than smallest int120 or +greater than largest int120). + +Counterpart to Solidity's `int120` operator. + +Requirements: + +- input must fit into 120 bits + +[.contract-item] +[[SafeCast-toInt112-int256-]] +==== `[.contract-item-name]#++toInt112++#++(int256 value) β†’ int112 downcasted++` [.item-kind]#internal# + +Returns the downcasted int112 from int256, reverting on +overflow (when the input is less than smallest int112 or +greater than largest int112). + +Counterpart to Solidity's `int112` operator. + +Requirements: + +- input must fit into 112 bits + +[.contract-item] +[[SafeCast-toInt104-int256-]] +==== `[.contract-item-name]#++toInt104++#++(int256 value) β†’ int104 downcasted++` [.item-kind]#internal# + +Returns the downcasted int104 from int256, reverting on +overflow (when the input is less than smallest int104 or +greater than largest int104). + +Counterpart to Solidity's `int104` operator. + +Requirements: + +- input must fit into 104 bits + +[.contract-item] +[[SafeCast-toInt96-int256-]] +==== `[.contract-item-name]#++toInt96++#++(int256 value) β†’ int96 downcasted++` [.item-kind]#internal# + +Returns the downcasted int96 from int256, reverting on +overflow (when the input is less than smallest int96 or +greater than largest int96). + +Counterpart to Solidity's `int96` operator. + +Requirements: + +- input must fit into 96 bits + +[.contract-item] +[[SafeCast-toInt88-int256-]] +==== `[.contract-item-name]#++toInt88++#++(int256 value) β†’ int88 downcasted++` [.item-kind]#internal# + +Returns the downcasted int88 from int256, reverting on +overflow (when the input is less than smallest int88 or +greater than largest int88). + +Counterpart to Solidity's `int88` operator. + +Requirements: + +- input must fit into 88 bits + +[.contract-item] +[[SafeCast-toInt80-int256-]] +==== `[.contract-item-name]#++toInt80++#++(int256 value) β†’ int80 downcasted++` [.item-kind]#internal# + +Returns the downcasted int80 from int256, reverting on +overflow (when the input is less than smallest int80 or +greater than largest int80). + +Counterpart to Solidity's `int80` operator. + +Requirements: + +- input must fit into 80 bits + +[.contract-item] +[[SafeCast-toInt72-int256-]] +==== `[.contract-item-name]#++toInt72++#++(int256 value) β†’ int72 downcasted++` [.item-kind]#internal# + +Returns the downcasted int72 from int256, reverting on +overflow (when the input is less than smallest int72 or +greater than largest int72). + +Counterpart to Solidity's `int72` operator. + +Requirements: + +- input must fit into 72 bits + +[.contract-item] +[[SafeCast-toInt64-int256-]] +==== `[.contract-item-name]#++toInt64++#++(int256 value) β†’ int64 downcasted++` [.item-kind]#internal# + +Returns the downcasted int64 from int256, reverting on +overflow (when the input is less than smallest int64 or +greater than largest int64). + +Counterpart to Solidity's `int64` operator. + +Requirements: + +- input must fit into 64 bits + +[.contract-item] +[[SafeCast-toInt56-int256-]] +==== `[.contract-item-name]#++toInt56++#++(int256 value) β†’ int56 downcasted++` [.item-kind]#internal# + +Returns the downcasted int56 from int256, reverting on +overflow (when the input is less than smallest int56 or +greater than largest int56). + +Counterpart to Solidity's `int56` operator. + +Requirements: + +- input must fit into 56 bits + +[.contract-item] +[[SafeCast-toInt48-int256-]] +==== `[.contract-item-name]#++toInt48++#++(int256 value) β†’ int48 downcasted++` [.item-kind]#internal# + +Returns the downcasted int48 from int256, reverting on +overflow (when the input is less than smallest int48 or +greater than largest int48). + +Counterpart to Solidity's `int48` operator. + +Requirements: + +- input must fit into 48 bits + +[.contract-item] +[[SafeCast-toInt40-int256-]] +==== `[.contract-item-name]#++toInt40++#++(int256 value) β†’ int40 downcasted++` [.item-kind]#internal# + +Returns the downcasted int40 from int256, reverting on +overflow (when the input is less than smallest int40 or +greater than largest int40). + +Counterpart to Solidity's `int40` operator. + +Requirements: + +- input must fit into 40 bits + +[.contract-item] +[[SafeCast-toInt32-int256-]] +==== `[.contract-item-name]#++toInt32++#++(int256 value) β†’ int32 downcasted++` [.item-kind]#internal# + +Returns the downcasted int32 from int256, reverting on +overflow (when the input is less than smallest int32 or +greater than largest int32). + +Counterpart to Solidity's `int32` operator. + +Requirements: + +- input must fit into 32 bits + +[.contract-item] +[[SafeCast-toInt24-int256-]] +==== `[.contract-item-name]#++toInt24++#++(int256 value) β†’ int24 downcasted++` [.item-kind]#internal# + +Returns the downcasted int24 from int256, reverting on +overflow (when the input is less than smallest int24 or +greater than largest int24). + +Counterpart to Solidity's `int24` operator. + +Requirements: + +- input must fit into 24 bits + +[.contract-item] +[[SafeCast-toInt16-int256-]] +==== `[.contract-item-name]#++toInt16++#++(int256 value) β†’ int16 downcasted++` [.item-kind]#internal# + +Returns the downcasted int16 from int256, reverting on +overflow (when the input is less than smallest int16 or +greater than largest int16). + +Counterpart to Solidity's `int16` operator. + +Requirements: + +- input must fit into 16 bits + +[.contract-item] +[[SafeCast-toInt8-int256-]] +==== `[.contract-item-name]#++toInt8++#++(int256 value) β†’ int8 downcasted++` [.item-kind]#internal# + +Returns the downcasted int8 from int256, reverting on +overflow (when the input is less than smallest int8 or +greater than largest int8). + +Counterpart to Solidity's `int8` operator. + +Requirements: + +- input must fit into 8 bits + +[.contract-item] +[[SafeCast-toInt256-uint256-]] +==== `[.contract-item-name]#++toInt256++#++(uint256 value) β†’ int256++` [.item-kind]#internal# + +Converts an unsigned uint256 into a signed int256. + +Requirements: + +- input must be less than or equal to maxInt256. + +[.contract-item] +[[SafeCast-SafeCastOverflowedUintDowncast-uint8-uint256-]] +==== `[.contract-item-name]#++SafeCastOverflowedUintDowncast++#++(uint8 bits, uint256 value)++` [.item-kind]#error# + +Value doesn't fit in an uint of `bits` size. + +[.contract-item] +[[SafeCast-SafeCastOverflowedIntToUint-int256-]] +==== `[.contract-item-name]#++SafeCastOverflowedIntToUint++#++(int256 value)++` [.item-kind]#error# + +An int value doesn't fit in an uint of `bits` size. + +[.contract-item] +[[SafeCast-SafeCastOverflowedIntDowncast-uint8-int256-]] +==== `[.contract-item-name]#++SafeCastOverflowedIntDowncast++#++(uint8 bits, int256 value)++` [.item-kind]#error# + +Value doesn't fit in an int of `bits` size. + +[.contract-item] +[[SafeCast-SafeCastOverflowedUintToInt-uint256-]] +==== `[.contract-item-name]#++SafeCastOverflowedUintToInt++#++(uint256 value)++` [.item-kind]#error# + +An uint value doesn't fit in an int of `bits` size. + +== Cryptography + +:RecoverError: pass:normal[xref:#ECDSA-RecoverError[`++RecoverError++`]] +:ECDSAInvalidSignature: pass:normal[xref:#ECDSA-ECDSAInvalidSignature--[`++ECDSAInvalidSignature++`]] +:ECDSAInvalidSignatureLength: pass:normal[xref:#ECDSA-ECDSAInvalidSignatureLength-uint256-[`++ECDSAInvalidSignatureLength++`]] +:ECDSAInvalidSignatureS: pass:normal[xref:#ECDSA-ECDSAInvalidSignatureS-bytes32-[`++ECDSAInvalidSignatureS++`]] +:tryRecover: pass:normal[xref:#ECDSA-tryRecover-bytes32-bytes-[`++tryRecover++`]] +:recover: pass:normal[xref:#ECDSA-recover-bytes32-bytes-[`++recover++`]] +:tryRecover: pass:normal[xref:#ECDSA-tryRecover-bytes32-bytes32-bytes32-[`++tryRecover++`]] +:recover: pass:normal[xref:#ECDSA-recover-bytes32-bytes32-bytes32-[`++recover++`]] +:tryRecover: pass:normal[xref:#ECDSA-tryRecover-bytes32-uint8-bytes32-bytes32-[`++tryRecover++`]] +:recover: pass:normal[xref:#ECDSA-recover-bytes32-uint8-bytes32-bytes32-[`++recover++`]] + +[.contract] +[[ECDSA]] +=== `++ECDSA++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/utils/cryptography/ECDSA.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +``` + +Elliptic Curve Digital Signature Algorithm (ECDSA) operations. + +These functions can be used to verify that a message was signed by the holder +of the private keys of a given address. + +[.contract-index] +.Functions +-- +* {xref-ECDSA-tryRecover-bytes32-bytes-}[`++tryRecover(hash, signature)++`] +* {xref-ECDSA-recover-bytes32-bytes-}[`++recover(hash, signature)++`] +* {xref-ECDSA-tryRecover-bytes32-bytes32-bytes32-}[`++tryRecover(hash, r, vs)++`] +* {xref-ECDSA-recover-bytes32-bytes32-bytes32-}[`++recover(hash, r, vs)++`] +* {xref-ECDSA-tryRecover-bytes32-uint8-bytes32-bytes32-}[`++tryRecover(hash, v, r, s)++`] +* {xref-ECDSA-recover-bytes32-uint8-bytes32-bytes32-}[`++recover(hash, v, r, s)++`] + +-- + +[.contract-index] +.Errors +-- +* {xref-ECDSA-ECDSAInvalidSignature--}[`++ECDSAInvalidSignature()++`] +* {xref-ECDSA-ECDSAInvalidSignatureLength-uint256-}[`++ECDSAInvalidSignatureLength(length)++`] +* {xref-ECDSA-ECDSAInvalidSignatureS-bytes32-}[`++ECDSAInvalidSignatureS(s)++`] + +-- + +[.contract-item] +[[ECDSA-tryRecover-bytes32-bytes-]] +==== `[.contract-item-name]#++tryRecover++#++(bytes32 hash, bytes signature) β†’ address, enum ECDSA.RecoverError, bytes32++` [.item-kind]#internal# + +Returns the address that signed a hashed message (`hash`) with `signature` or an error. This will not +return address(0) without also returning an error description. Errors are documented using an enum (error type) +and a bytes32 providing additional information about the error. + +If no error is returned, then the address can be used for verification purposes. + +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. + +IMPORTANT: `hash` _must_ be the result of a hash operation for the +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 {MessageHashUtils-toEthSignedMessageHash} on it. + +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] + +[.contract-item] +[[ECDSA-recover-bytes32-bytes-]] +==== `[.contract-item-name]#++recover++#++(bytes32 hash, bytes signature) β†’ address++` [.item-kind]#internal# + +Returns the address that signed a hashed message (`hash`) with +`signature`. This address can then be used for verification purposes. + +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. + +IMPORTANT: `hash` _must_ be the result of a hash operation for the +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 {MessageHashUtils-toEthSignedMessageHash} on it. + +[.contract-item] +[[ECDSA-tryRecover-bytes32-bytes32-bytes32-]] +==== `[.contract-item-name]#++tryRecover++#++(bytes32 hash, bytes32 r, bytes32 vs) β†’ address, enum ECDSA.RecoverError, bytes32++` [.item-kind]#internal# + +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] + +[.contract-item] +[[ECDSA-recover-bytes32-bytes32-bytes32-]] +==== `[.contract-item-name]#++recover++#++(bytes32 hash, bytes32 r, bytes32 vs) β†’ address++` [.item-kind]#internal# + +Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately. + +[.contract-item] +[[ECDSA-tryRecover-bytes32-uint8-bytes32-bytes32-]] +==== `[.contract-item-name]#++tryRecover++#++(bytes32 hash, uint8 v, bytes32 r, bytes32 s) β†’ address, enum ECDSA.RecoverError, bytes32++` [.item-kind]#internal# + +Overload of {ECDSA-tryRecover} that receives the `v`, +`r` and `s` signature fields separately. + +[.contract-item] +[[ECDSA-recover-bytes32-uint8-bytes32-bytes32-]] +==== `[.contract-item-name]#++recover++#++(bytes32 hash, uint8 v, bytes32 r, bytes32 s) β†’ address++` [.item-kind]#internal# + +Overload of {ECDSA-recover} that receives the `v`, +`r` and `s` signature fields separately. + +[.contract-item] +[[ECDSA-ECDSAInvalidSignature--]] +==== `[.contract-item-name]#++ECDSAInvalidSignature++#++()++` [.item-kind]#error# + +The signature derives the `address(0)`. + +[.contract-item] +[[ECDSA-ECDSAInvalidSignatureLength-uint256-]] +==== `[.contract-item-name]#++ECDSAInvalidSignatureLength++#++(uint256 length)++` [.item-kind]#error# + +The signature has an invalid length. + +[.contract-item] +[[ECDSA-ECDSAInvalidSignatureS-bytes32-]] +==== `[.contract-item-name]#++ECDSAInvalidSignatureS++#++(bytes32 s)++` [.item-kind]#error# + +The signature has an S value that is in the upper half order. + +:toEthSignedMessageHash: pass:normal[xref:#MessageHashUtils-toEthSignedMessageHash-bytes32-[`++toEthSignedMessageHash++`]] +:toEthSignedMessageHash: pass:normal[xref:#MessageHashUtils-toEthSignedMessageHash-bytes-[`++toEthSignedMessageHash++`]] +:toDataWithIntendedValidatorHash: pass:normal[xref:#MessageHashUtils-toDataWithIntendedValidatorHash-address-bytes-[`++toDataWithIntendedValidatorHash++`]] +:toTypedDataHash: pass:normal[xref:#MessageHashUtils-toTypedDataHash-bytes32-bytes32-[`++toTypedDataHash++`]] + +[.contract] +[[MessageHashUtils]] +=== `++MessageHashUtils++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/utils/cryptography/MessageHashUtils.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; +``` + +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. + +[.contract-index] +.Functions +-- +* {xref-MessageHashUtils-toEthSignedMessageHash-bytes32-}[`++toEthSignedMessageHash(messageHash)++`] +* {xref-MessageHashUtils-toEthSignedMessageHash-bytes-}[`++toEthSignedMessageHash(message)++`] +* {xref-MessageHashUtils-toDataWithIntendedValidatorHash-address-bytes-}[`++toDataWithIntendedValidatorHash(validator, data)++`] +* {xref-MessageHashUtils-toTypedDataHash-bytes32-bytes32-}[`++toTypedDataHash(domainSeparator, structHash)++`] + +-- + +[.contract-item] +[[MessageHashUtils-toEthSignedMessageHash-bytes32-]] +==== `[.contract-item-name]#++toEthSignedMessageHash++#++(bytes32 messageHash) β†’ bytes32 digest++` [.item-kind]#internal# + +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 `messageHash` parameter is intended to be the result of hashing a raw message with +keccak256, although any bytes32 value can be safely used because the final digest will +be re-hashed. + +See {ECDSA-recover}. + +[.contract-item] +[[MessageHashUtils-toEthSignedMessageHash-bytes-]] +==== `[.contract-item-name]#++toEthSignedMessageHash++#++(bytes message) β†’ bytes32++` [.item-kind]#internal# + +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}. + +[.contract-item] +[[MessageHashUtils-toDataWithIntendedValidatorHash-address-bytes-]] +==== `[.contract-item-name]#++toDataWithIntendedValidatorHash++#++(address validator, bytes data) β†’ bytes32++` [.item-kind]#internal# + +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}. + +[.contract-item] +[[MessageHashUtils-toTypedDataHash-bytes32-bytes32-]] +==== `[.contract-item-name]#++toTypedDataHash++#++(bytes32 domainSeparator, bytes32 structHash) β†’ bytes32 digest++` [.item-kind]#internal# + +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}. + +:isValidSignatureNow: pass:normal[xref:#SignatureChecker-isValidSignatureNow-address-bytes32-bytes-[`++isValidSignatureNow++`]] +:isValidERC1271SignatureNow: pass:normal[xref:#SignatureChecker-isValidERC1271SignatureNow-address-bytes32-bytes-[`++isValidERC1271SignatureNow++`]] + +[.contract] +[[SignatureChecker]] +=== `++SignatureChecker++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/utils/cryptography/SignatureChecker.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; +``` + +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). + +[.contract-index] +.Functions +-- +* {xref-SignatureChecker-isValidSignatureNow-address-bytes32-bytes-}[`++isValidSignatureNow(signer, hash, signature)++`] +* {xref-SignatureChecker-isValidERC1271SignatureNow-address-bytes32-bytes-}[`++isValidERC1271SignatureNow(signer, hash, signature)++`] + +-- + +[.contract-item] +[[SignatureChecker-isValidSignatureNow-address-bytes32-bytes-]] +==== `[.contract-item-name]#++isValidSignatureNow++#++(address signer, bytes32 hash, bytes signature) β†’ bool++` [.item-kind]#internal# + +Checks if a signature is valid for a given signer and data hash. If the signer is a smart contract, the +signature is validated against that smart contract using ERC1271, otherwise it's validated using `ECDSA.recover`. + +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). + +[.contract-item] +[[SignatureChecker-isValidERC1271SignatureNow-address-bytes32-bytes-]] +==== `[.contract-item-name]#++isValidERC1271SignatureNow++#++(address signer, bytes32 hash, bytes signature) β†’ bool++` [.item-kind]#internal# + +Checks if a signature is valid for a given signer and data hash. The signature is validated +against the signer smart contract using ERC1271. + +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). + +:MerkleProofInvalidMultiproof: pass:normal[xref:#MerkleProof-MerkleProofInvalidMultiproof--[`++MerkleProofInvalidMultiproof++`]] +:verify: pass:normal[xref:#MerkleProof-verify-bytes32---bytes32-bytes32-[`++verify++`]] +:verifyCalldata: pass:normal[xref:#MerkleProof-verifyCalldata-bytes32---bytes32-bytes32-[`++verifyCalldata++`]] +:processProof: pass:normal[xref:#MerkleProof-processProof-bytes32---bytes32-[`++processProof++`]] +:processProofCalldata: pass:normal[xref:#MerkleProof-processProofCalldata-bytes32---bytes32-[`++processProofCalldata++`]] +:multiProofVerify: pass:normal[xref:#MerkleProof-multiProofVerify-bytes32---bool---bytes32-bytes32---[`++multiProofVerify++`]] +:multiProofVerifyCalldata: pass:normal[xref:#MerkleProof-multiProofVerifyCalldata-bytes32---bool---bytes32-bytes32---[`++multiProofVerifyCalldata++`]] +:processMultiProof: pass:normal[xref:#MerkleProof-processMultiProof-bytes32---bool---bytes32---[`++processMultiProof++`]] +:processMultiProofCalldata: pass:normal[xref:#MerkleProof-processMultiProofCalldata-bytes32---bool---bytes32---[`++processMultiProofCalldata++`]] + +[.contract] +[[MerkleProof]] +=== `++MerkleProof++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/utils/cryptography/MerkleProof.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; +``` + +These functions deal with verification of Merkle Tree proofs. + +The tree and the proofs can be generated using our +https://github.com/OpenZeppelin/merkle-tree[JavaScript library]. +You will find a quickstart guide in the readme. + +WARNING: You should avoid using leaf values that are 64 bytes long prior to +hashing, or use a hash function other than keccak256 for hashing leaves. +This is because the concatenation of a sorted pair of internal nodes in +the Merkle tree could be reinterpreted as a leaf value. +OpenZeppelin's JavaScript library generates Merkle trees that are safe +against this attack out of the box. + +[.contract-index] +.Functions +-- +* {xref-MerkleProof-verify-bytes32---bytes32-bytes32-}[`++verify(proof, root, leaf)++`] +* {xref-MerkleProof-verifyCalldata-bytes32---bytes32-bytes32-}[`++verifyCalldata(proof, root, leaf)++`] +* {xref-MerkleProof-processProof-bytes32---bytes32-}[`++processProof(proof, leaf)++`] +* {xref-MerkleProof-processProofCalldata-bytes32---bytes32-}[`++processProofCalldata(proof, leaf)++`] +* {xref-MerkleProof-multiProofVerify-bytes32---bool---bytes32-bytes32---}[`++multiProofVerify(proof, proofFlags, root, leaves)++`] +* {xref-MerkleProof-multiProofVerifyCalldata-bytes32---bool---bytes32-bytes32---}[`++multiProofVerifyCalldata(proof, proofFlags, root, leaves)++`] +* {xref-MerkleProof-processMultiProof-bytes32---bool---bytes32---}[`++processMultiProof(proof, proofFlags, leaves)++`] +* {xref-MerkleProof-processMultiProofCalldata-bytes32---bool---bytes32---}[`++processMultiProofCalldata(proof, proofFlags, leaves)++`] + +-- + +[.contract-index] +.Errors +-- +* {xref-MerkleProof-MerkleProofInvalidMultiproof--}[`++MerkleProofInvalidMultiproof()++`] + +-- + +[.contract-item] +[[MerkleProof-verify-bytes32---bytes32-bytes32-]] +==== `[.contract-item-name]#++verify++#++(bytes32[] proof, bytes32 root, bytes32 leaf) β†’ bool++` [.item-kind]#internal# + +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 +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. + +[.contract-item] +[[MerkleProof-verifyCalldata-bytes32---bytes32-bytes32-]] +==== `[.contract-item-name]#++verifyCalldata++#++(bytes32[] proof, bytes32 root, bytes32 leaf) β†’ bool++` [.item-kind]#internal# + +Calldata version of {verify} + +[.contract-item] +[[MerkleProof-processProof-bytes32---bytes32-]] +==== `[.contract-item-name]#++processProof++#++(bytes32[] proof, bytes32 leaf) β†’ bytes32++` [.item-kind]#internal# + +Returns the rebuilt hash obtained by traversing a Merkle tree up +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. + +[.contract-item] +[[MerkleProof-processProofCalldata-bytes32---bytes32-]] +==== `[.contract-item-name]#++processProofCalldata++#++(bytes32[] proof, bytes32 leaf) β†’ bytes32++` [.item-kind]#internal# + +Calldata version of {processProof} + +[.contract-item] +[[MerkleProof-multiProofVerify-bytes32---bool---bytes32-bytes32---]] +==== `[.contract-item-name]#++multiProofVerify++#++(bytes32[] proof, bool[] proofFlags, bytes32 root, bytes32[] leaves) β†’ bool++` [.item-kind]#internal# + +Returns true if the `leaves` can be simultaneously proven to be a part of a Merkle tree defined by +`root`, according to `proof` and `proofFlags` as described in {processMultiProof}. + +CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details. + +[.contract-item] +[[MerkleProof-multiProofVerifyCalldata-bytes32---bool---bytes32-bytes32---]] +==== `[.contract-item-name]#++multiProofVerifyCalldata++#++(bytes32[] proof, bool[] proofFlags, bytes32 root, bytes32[] leaves) β†’ bool++` [.item-kind]#internal# + +Calldata version of {multiProofVerify} + +CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details. + +[.contract-item] +[[MerkleProof-processMultiProof-bytes32---bool---bytes32---]] +==== `[.contract-item-name]#++processMultiProof++#++(bytes32[] proof, bool[] proofFlags, bytes32[] leaves) β†’ bytes32 merkleRoot++` [.item-kind]#internal# + +Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction +proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another +leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false +respectively. + +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). + +[.contract-item] +[[MerkleProof-processMultiProofCalldata-bytes32---bool---bytes32---]] +==== `[.contract-item-name]#++processMultiProofCalldata++#++(bytes32[] proof, bool[] proofFlags, bytes32[] leaves) β†’ bytes32 merkleRoot++` [.item-kind]#internal# + +Calldata version of {processMultiProof}. + +CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details. + +[.contract-item] +[[MerkleProof-MerkleProofInvalidMultiproof--]] +==== `[.contract-item-name]#++MerkleProofInvalidMultiproof++#++()++` [.item-kind]#error# + +The multiproof provided is not valid. + +:constructor: pass:normal[xref:#EIP712-constructor-string-string-[`++constructor++`]] +:_domainSeparatorV4: pass:normal[xref:#EIP712-_domainSeparatorV4--[`++_domainSeparatorV4++`]] +:_hashTypedDataV4: pass:normal[xref:#EIP712-_hashTypedDataV4-bytes32-[`++_hashTypedDataV4++`]] +:eip712Domain: pass:normal[xref:#EIP712-eip712Domain--[`++eip712Domain++`]] +:_EIP712Name: pass:normal[xref:#EIP712-_EIP712Name--[`++_EIP712Name++`]] +:_EIP712Version: pass:normal[xref:#EIP712-_EIP712Version--[`++_EIP712Version++`]] + +[.contract] +[[EIP712]] +=== `++EIP712++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/utils/cryptography/EIP712.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; +``` + +https://eips.ethereum.org/EIPS/eip-712[EIP 712] is a standard for hashing and signing of typed structured data. + +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 +({_hashTypedDataV4}). + +The implementation of the domain separator was designed to be as efficient as possible while still properly updating +the chain id to protect against replay attacks on an eventual fork of the chain. + +NOTE: This contract implements the version of the encoding known as "v4", as implemented by the JSON RPC method +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 from the immutable values, which is cheaper than accessing a cached version in cold storage. + +[.contract-index] +.Functions +-- +* {xref-EIP712-constructor-string-string-}[`++constructor(name, version)++`] +* {xref-EIP712-_domainSeparatorV4--}[`++_domainSeparatorV4()++`] +* {xref-EIP712-_hashTypedDataV4-bytes32-}[`++_hashTypedDataV4(structHash)++`] +* {xref-EIP712-eip712Domain--}[`++eip712Domain()++`] +* {xref-EIP712-_EIP712Name--}[`++_EIP712Name()++`] +* {xref-EIP712-_EIP712Version--}[`++_EIP712Version()++`] + +[.contract-subindex-inherited] +.IERC5267 + +-- + +[.contract-index] +.Events +-- + +[.contract-subindex-inherited] +.IERC5267 +* {xref-IERC5267-EIP712DomainChanged--}[`++EIP712DomainChanged()++`] + +-- + +[.contract-item] +[[EIP712-constructor-string-string-]] +==== `[.contract-item-name]#++constructor++#++(string name, string version)++` [.item-kind]#internal# + +Initializes the domain separator and parameter caches. + +The meaning of `name` and `version` is specified in +https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator[EIP 712]: + +- `name`: the user readable name of the signing domain, i.e. the name of the DApp or the protocol. +- `version`: the current major version of the signing domain. + +NOTE: These parameters cannot be changed except through a xref:learn::upgrading-smart-contracts.adoc[smart +contract upgrade]. + +[.contract-item] +[[EIP712-_domainSeparatorV4--]] +==== `[.contract-item-name]#++_domainSeparatorV4++#++() β†’ bytes32++` [.item-kind]#internal# + +Returns the domain separator for the current chain. + +[.contract-item] +[[EIP712-_hashTypedDataV4-bytes32-]] +==== `[.contract-item-name]#++_hashTypedDataV4++#++(bytes32 structHash) β†’ bytes32++` [.item-kind]#internal# + +Given an already https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct[hashed struct], this +function returns the hash of the fully encoded EIP712 message for this domain. + +This hash can be used together with {ECDSA-recover} to obtain the signer of a message. For example: + +```solidity +bytes32 digest = _hashTypedDataV4(keccak256(abi.encode( + keccak256("Mail(address to,string contents)"), + mailTo, + keccak256(bytes(mailContents)) +))); +address signer = ECDSA.recover(digest, signature); +``` + +[.contract-item] +[[EIP712-eip712Domain--]] +==== `[.contract-item-name]#++eip712Domain++#++() β†’ bytes1 fields, string name, string version, uint256 chainId, address verifyingContract, bytes32 salt, uint256[] extensions++` [.item-kind]#public# + +See {IERC-5267}. + +[.contract-item] +[[EIP712-_EIP712Name--]] +==== `[.contract-item-name]#++_EIP712Name++#++() β†’ string++` [.item-kind]#internal# + +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). + +[.contract-item] +[[EIP712-_EIP712Version--]] +==== `[.contract-item-name]#++_EIP712Version++#++() β†’ string++` [.item-kind]#internal# + +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). + +== Security + +:ReentrancyGuardReentrantCall: pass:normal[xref:#ReentrancyGuard-ReentrancyGuardReentrantCall--[`++ReentrancyGuardReentrantCall++`]] +:constructor: pass:normal[xref:#ReentrancyGuard-constructor--[`++constructor++`]] +:nonReentrant: pass:normal[xref:#ReentrancyGuard-nonReentrant--[`++nonReentrant++`]] +:_reentrancyGuardEntered: pass:normal[xref:#ReentrancyGuard-_reentrancyGuardEntered--[`++_reentrancyGuardEntered++`]] + +[.contract] +[[ReentrancyGuard]] +=== `++ReentrancyGuard++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/utils/ReentrancyGuard.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; +``` + +Contract module that helps prevent reentrant calls to a function. + +Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier +available, which can be applied to functions to make sure there are no nested +(reentrant) calls to them. + +Note that because there is a single `nonReentrant` guard, functions marked as +`nonReentrant` may not call one another. This can be worked around by making +those functions `private`, and then adding `external` `nonReentrant` entry +points to them. + +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]. + +[.contract-index] +.Modifiers +-- +* {xref-ReentrancyGuard-nonReentrant--}[`++nonReentrant()++`] +-- + +[.contract-index] +.Functions +-- +* {xref-ReentrancyGuard-constructor--}[`++constructor()++`] +* {xref-ReentrancyGuard-_reentrancyGuardEntered--}[`++_reentrancyGuardEntered()++`] + +-- + +[.contract-index] +.Errors +-- +* {xref-ReentrancyGuard-ReentrancyGuardReentrantCall--}[`++ReentrancyGuardReentrantCall()++`] + +-- + +[.contract-item] +[[ReentrancyGuard-nonReentrant--]] +==== `[.contract-item-name]#++nonReentrant++#++()++` [.item-kind]#modifier# + +Prevents a contract from calling itself, directly or indirectly. +Calling a `nonReentrant` function from another `nonReentrant` +function is not supported. It is possible to prevent this from happening +by making the `nonReentrant` function external, and making it call a +`private` function that does the actual work. + +[.contract-item] +[[ReentrancyGuard-constructor--]] +==== `[.contract-item-name]#++constructor++#++()++` [.item-kind]#internal# + +[.contract-item] +[[ReentrancyGuard-_reentrancyGuardEntered--]] +==== `[.contract-item-name]#++_reentrancyGuardEntered++#++() β†’ bool++` [.item-kind]#internal# + +Returns true if the reentrancy guard is currently set to "entered", which indicates there is a +`nonReentrant` function in the call stack. + +[.contract-item] +[[ReentrancyGuard-ReentrancyGuardReentrantCall--]] +==== `[.contract-item-name]#++ReentrancyGuardReentrantCall++#++()++` [.item-kind]#error# + +Unauthorized reentrant call. + +:Paused: pass:normal[xref:#Pausable-Paused-address-[`++Paused++`]] +:Unpaused: pass:normal[xref:#Pausable-Unpaused-address-[`++Unpaused++`]] +:EnforcedPause: pass:normal[xref:#Pausable-EnforcedPause--[`++EnforcedPause++`]] +:ExpectedPause: pass:normal[xref:#Pausable-ExpectedPause--[`++ExpectedPause++`]] +:constructor: pass:normal[xref:#Pausable-constructor--[`++constructor++`]] +:whenNotPaused: pass:normal[xref:#Pausable-whenNotPaused--[`++whenNotPaused++`]] +:whenPaused: pass:normal[xref:#Pausable-whenPaused--[`++whenPaused++`]] +:paused: pass:normal[xref:#Pausable-paused--[`++paused++`]] +:_requireNotPaused: pass:normal[xref:#Pausable-_requireNotPaused--[`++_requireNotPaused++`]] +:_requirePaused: pass:normal[xref:#Pausable-_requirePaused--[`++_requirePaused++`]] +:_pause: pass:normal[xref:#Pausable-_pause--[`++_pause++`]] +:_unpause: pass:normal[xref:#Pausable-_unpause--[`++_unpause++`]] + +[.contract] +[[Pausable]] +=== `++Pausable++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/utils/Pausable.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/utils/Pausable.sol"; +``` + +Contract module which allows children to implement an emergency stop +mechanism that can be triggered by an authorized account. + +This module is used through inheritance. It will make available the +modifiers `whenNotPaused` and `whenPaused`, which can be applied to +the functions of your contract. Note that they will not be pausable by +simply including this module, only once the modifiers are put in place. + +[.contract-index] +.Modifiers +-- +* {xref-Pausable-whenNotPaused--}[`++whenNotPaused()++`] +* {xref-Pausable-whenPaused--}[`++whenPaused()++`] +-- + +[.contract-index] +.Functions +-- +* {xref-Pausable-constructor--}[`++constructor()++`] +* {xref-Pausable-paused--}[`++paused()++`] +* {xref-Pausable-_requireNotPaused--}[`++_requireNotPaused()++`] +* {xref-Pausable-_requirePaused--}[`++_requirePaused()++`] +* {xref-Pausable-_pause--}[`++_pause()++`] +* {xref-Pausable-_unpause--}[`++_unpause()++`] + +-- + +[.contract-index] +.Events +-- +* {xref-Pausable-Paused-address-}[`++Paused(account)++`] +* {xref-Pausable-Unpaused-address-}[`++Unpaused(account)++`] + +-- + +[.contract-index] +.Errors +-- +* {xref-Pausable-EnforcedPause--}[`++EnforcedPause()++`] +* {xref-Pausable-ExpectedPause--}[`++ExpectedPause()++`] + +-- + +[.contract-item] +[[Pausable-whenNotPaused--]] +==== `[.contract-item-name]#++whenNotPaused++#++()++` [.item-kind]#modifier# + +Modifier to make a function callable only when the contract is not paused. + +Requirements: + +- The contract must not be paused. + +[.contract-item] +[[Pausable-whenPaused--]] +==== `[.contract-item-name]#++whenPaused++#++()++` [.item-kind]#modifier# + +Modifier to make a function callable only when the contract is paused. + +Requirements: + +- The contract must be paused. + +[.contract-item] +[[Pausable-constructor--]] +==== `[.contract-item-name]#++constructor++#++()++` [.item-kind]#internal# + +Initializes the contract in unpaused state. + +[.contract-item] +[[Pausable-paused--]] +==== `[.contract-item-name]#++paused++#++() β†’ bool++` [.item-kind]#public# + +Returns true if the contract is paused, and false otherwise. + +[.contract-item] +[[Pausable-_requireNotPaused--]] +==== `[.contract-item-name]#++_requireNotPaused++#++()++` [.item-kind]#internal# + +Throws if the contract is paused. + +[.contract-item] +[[Pausable-_requirePaused--]] +==== `[.contract-item-name]#++_requirePaused++#++()++` [.item-kind]#internal# + +Throws if the contract is not paused. + +[.contract-item] +[[Pausable-_pause--]] +==== `[.contract-item-name]#++_pause++#++()++` [.item-kind]#internal# + +Triggers stopped state. + +Requirements: + +- The contract must not be paused. + +[.contract-item] +[[Pausable-_unpause--]] +==== `[.contract-item-name]#++_unpause++#++()++` [.item-kind]#internal# + +Returns to normal state. + +Requirements: + +- The contract must be paused. + +[.contract-item] +[[Pausable-Paused-address-]] +==== `[.contract-item-name]#++Paused++#++(address account)++` [.item-kind]#event# + +Emitted when the pause is triggered by `account`. + +[.contract-item] +[[Pausable-Unpaused-address-]] +==== `[.contract-item-name]#++Unpaused++#++(address account)++` [.item-kind]#event# + +Emitted when the pause is lifted by `account`. + +[.contract-item] +[[Pausable-EnforcedPause--]] +==== `[.contract-item-name]#++EnforcedPause++#++()++` [.item-kind]#error# + +The operation failed because the contract is paused. + +[.contract-item] +[[Pausable-ExpectedPause--]] +==== `[.contract-item-name]#++ExpectedPause++#++()++` [.item-kind]#error# + +The operation failed because the contract is not paused. + +== 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. + +:supportsInterface: pass:normal[xref:#IERC165-supportsInterface-bytes4-[`++supportsInterface++`]] + +[.contract] +[[IERC165]] +=== `++IERC165++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/utils/introspection/IERC165.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/utils/introspection/IERC165.sol"; +``` + +Interface of the ERC165 standard, as defined in the +https://eips.ethereum.org/EIPS/eip-165[EIP]. + +Implementers can declare support of contract interfaces, which can then be +queried by others ({ERC165Checker}). + +For an implementation, see {ERC165}. + +[.contract-index] +.Functions +-- +* {xref-IERC165-supportsInterface-bytes4-}[`++supportsInterface(interfaceId)++`] + +-- + +[.contract-item] +[[IERC165-supportsInterface-bytes4-]] +==== `[.contract-item-name]#++supportsInterface++#++(bytes4 interfaceId) β†’ bool++` [.item-kind]#external# + +Returns true if this contract implements the interface defined by +`interfaceId`. See the corresponding +https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section] +to learn more about how these ids are created. + +This function call must use less than 30 000 gas. + +:supportsInterface: pass:normal[xref:#ERC165-supportsInterface-bytes4-[`++supportsInterface++`]] + +[.contract] +[[ERC165]] +=== `++ERC165++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/utils/introspection/ERC165.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; +``` + +Implementation of the {IERC165} interface. + +Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check +for the additional interface id that will be supported. For example: + +```solidity +function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { + return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId); +} +``` + +[.contract-index] +.Functions +-- +* {xref-ERC165-supportsInterface-bytes4-}[`++supportsInterface(interfaceId)++`] + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-item] +[[ERC165-supportsInterface-bytes4-]] +==== `[.contract-item-name]#++supportsInterface++#++(bytes4 interfaceId) β†’ bool++` [.item-kind]#public# + +See {IERC165-supportsInterface}. + +:supportsERC165: pass:normal[xref:#ERC165Checker-supportsERC165-address-[`++supportsERC165++`]] +:supportsInterface: pass:normal[xref:#ERC165Checker-supportsInterface-address-bytes4-[`++supportsInterface++`]] +:getSupportedInterfaces: pass:normal[xref:#ERC165Checker-getSupportedInterfaces-address-bytes4---[`++getSupportedInterfaces++`]] +:supportsAllInterfaces: pass:normal[xref:#ERC165Checker-supportsAllInterfaces-address-bytes4---[`++supportsAllInterfaces++`]] +:supportsERC165InterfaceUnchecked: pass:normal[xref:#ERC165Checker-supportsERC165InterfaceUnchecked-address-bytes4-[`++supportsERC165InterfaceUnchecked++`]] + +[.contract] +[[ERC165Checker]] +=== `++ERC165Checker++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/utils/introspection/ERC165Checker.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; +``` + +Library used to query support of an interface declared via {IERC165}. + +Note that these functions return the actual result of the query: they do not +`revert` if an interface is not supported. It is up to the caller to decide +what to do in these cases. + +[.contract-index] +.Functions +-- +* {xref-ERC165Checker-supportsERC165-address-}[`++supportsERC165(account)++`] +* {xref-ERC165Checker-supportsInterface-address-bytes4-}[`++supportsInterface(account, interfaceId)++`] +* {xref-ERC165Checker-getSupportedInterfaces-address-bytes4---}[`++getSupportedInterfaces(account, interfaceIds)++`] +* {xref-ERC165Checker-supportsAllInterfaces-address-bytes4---}[`++supportsAllInterfaces(account, interfaceIds)++`] +* {xref-ERC165Checker-supportsERC165InterfaceUnchecked-address-bytes4-}[`++supportsERC165InterfaceUnchecked(account, interfaceId)++`] + +-- + +[.contract-item] +[[ERC165Checker-supportsERC165-address-]] +==== `[.contract-item-name]#++supportsERC165++#++(address account) β†’ bool++` [.item-kind]#internal# + +Returns true if `account` supports the {IERC165} interface. + +[.contract-item] +[[ERC165Checker-supportsInterface-address-bytes4-]] +==== `[.contract-item-name]#++supportsInterface++#++(address account, bytes4 interfaceId) β†’ bool++` [.item-kind]#internal# + +Returns true if `account` supports the interface defined by +`interfaceId`. Support for {IERC165} itself is queried automatically. + +See {IERC165-supportsInterface}. + +[.contract-item] +[[ERC165Checker-getSupportedInterfaces-address-bytes4---]] +==== `[.contract-item-name]#++getSupportedInterfaces++#++(address account, bytes4[] interfaceIds) β†’ bool[]++` [.item-kind]#internal# + +Returns a boolean array where each value corresponds to the +interfaces passed in and whether they're supported or not. This allows +you to batch check interfaces for a contract where your expectation +is that some interfaces may not be supported. + +See {IERC165-supportsInterface}. + +[.contract-item] +[[ERC165Checker-supportsAllInterfaces-address-bytes4---]] +==== `[.contract-item-name]#++supportsAllInterfaces++#++(address account, bytes4[] interfaceIds) β†’ bool++` [.item-kind]#internal# + +Returns true if `account` supports all the interfaces defined in +`interfaceIds`. Support for {IERC165} itself is queried automatically. + +Batch-querying can lead to gas savings by skipping repeated checks for +{IERC165} support. + +See {IERC165-supportsInterface}. + +[.contract-item] +[[ERC165Checker-supportsERC165InterfaceUnchecked-address-bytes4-]] +==== `[.contract-item-name]#++supportsERC165InterfaceUnchecked++#++(address account, bytes4 interfaceId) β†’ bool++` [.item-kind]#internal# + +Assumes that account contains a contract that supports ERC165, otherwise +the behavior of this method is undefined. This precondition can be checked +with {supportsERC165}. + +Some precompiled contracts will falsely indicate support for a given interface, so caution +should be exercised when using this function. + +Interface identification is specified in ERC-165. + +== Data Structures + +:BitMap: pass:normal[xref:#BitMaps-BitMap[`++BitMap++`]] +:get: pass:normal[xref:#BitMaps-get-struct-BitMaps-BitMap-uint256-[`++get++`]] +:setTo: pass:normal[xref:#BitMaps-setTo-struct-BitMaps-BitMap-uint256-bool-[`++setTo++`]] +:set: pass:normal[xref:#BitMaps-set-struct-BitMaps-BitMap-uint256-[`++set++`]] +:unset: pass:normal[xref:#BitMaps-unset-struct-BitMaps-BitMap-uint256-[`++unset++`]] + +[.contract] +[[BitMaps]] +=== `++BitMaps++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/utils/structs/BitMaps.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/utils/structs/BitMaps.sol"; +``` + +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 + +[.contract-index] +.Functions +-- +* {xref-BitMaps-get-struct-BitMaps-BitMap-uint256-}[`++get(bitmap, index)++`] +* {xref-BitMaps-setTo-struct-BitMaps-BitMap-uint256-bool-}[`++setTo(bitmap, index, value)++`] +* {xref-BitMaps-set-struct-BitMaps-BitMap-uint256-}[`++set(bitmap, index)++`] +* {xref-BitMaps-unset-struct-BitMaps-BitMap-uint256-}[`++unset(bitmap, index)++`] + +-- + +[.contract-item] +[[BitMaps-get-struct-BitMaps-BitMap-uint256-]] +==== `[.contract-item-name]#++get++#++(struct BitMaps.BitMap bitmap, uint256 index) β†’ bool++` [.item-kind]#internal# + +Returns whether the bit at `index` is set. + +[.contract-item] +[[BitMaps-setTo-struct-BitMaps-BitMap-uint256-bool-]] +==== `[.contract-item-name]#++setTo++#++(struct BitMaps.BitMap bitmap, uint256 index, bool value)++` [.item-kind]#internal# + +Sets the bit at `index` to the boolean `value`. + +[.contract-item] +[[BitMaps-set-struct-BitMaps-BitMap-uint256-]] +==== `[.contract-item-name]#++set++#++(struct BitMaps.BitMap bitmap, uint256 index)++` [.item-kind]#internal# + +Sets the bit at `index`. + +[.contract-item] +[[BitMaps-unset-struct-BitMaps-BitMap-uint256-]] +==== `[.contract-item-name]#++unset++#++(struct BitMaps.BitMap bitmap, uint256 index)++` [.item-kind]#internal# + +Unsets the bit at `index`. + +:EnumerableMapNonexistentKey: pass:normal[xref:#EnumerableMap-EnumerableMapNonexistentKey-bytes32-[`++EnumerableMapNonexistentKey++`]] +:Bytes32ToBytes32Map: pass:normal[xref:#EnumerableMap-Bytes32ToBytes32Map[`++Bytes32ToBytes32Map++`]] +:set: pass:normal[xref:#EnumerableMap-set-struct-EnumerableMap-Bytes32ToBytes32Map-bytes32-bytes32-[`++set++`]] +:remove: pass:normal[xref:#EnumerableMap-remove-struct-EnumerableMap-Bytes32ToBytes32Map-bytes32-[`++remove++`]] +:contains: pass:normal[xref:#EnumerableMap-contains-struct-EnumerableMap-Bytes32ToBytes32Map-bytes32-[`++contains++`]] +:length: pass:normal[xref:#EnumerableMap-length-struct-EnumerableMap-Bytes32ToBytes32Map-[`++length++`]] +:at: pass:normal[xref:#EnumerableMap-at-struct-EnumerableMap-Bytes32ToBytes32Map-uint256-[`++at++`]] +:tryGet: pass:normal[xref:#EnumerableMap-tryGet-struct-EnumerableMap-Bytes32ToBytes32Map-bytes32-[`++tryGet++`]] +:get: pass:normal[xref:#EnumerableMap-get-struct-EnumerableMap-Bytes32ToBytes32Map-bytes32-[`++get++`]] +:keys: pass:normal[xref:#EnumerableMap-keys-struct-EnumerableMap-Bytes32ToBytes32Map-[`++keys++`]] +:UintToUintMap: pass:normal[xref:#EnumerableMap-UintToUintMap[`++UintToUintMap++`]] +:set: pass:normal[xref:#EnumerableMap-set-struct-EnumerableMap-UintToUintMap-uint256-uint256-[`++set++`]] +:remove: pass:normal[xref:#EnumerableMap-remove-struct-EnumerableMap-UintToUintMap-uint256-[`++remove++`]] +:contains: pass:normal[xref:#EnumerableMap-contains-struct-EnumerableMap-UintToUintMap-uint256-[`++contains++`]] +:length: pass:normal[xref:#EnumerableMap-length-struct-EnumerableMap-UintToUintMap-[`++length++`]] +:at: pass:normal[xref:#EnumerableMap-at-struct-EnumerableMap-UintToUintMap-uint256-[`++at++`]] +:tryGet: pass:normal[xref:#EnumerableMap-tryGet-struct-EnumerableMap-UintToUintMap-uint256-[`++tryGet++`]] +:get: pass:normal[xref:#EnumerableMap-get-struct-EnumerableMap-UintToUintMap-uint256-[`++get++`]] +:keys: pass:normal[xref:#EnumerableMap-keys-struct-EnumerableMap-UintToUintMap-[`++keys++`]] +:UintToAddressMap: pass:normal[xref:#EnumerableMap-UintToAddressMap[`++UintToAddressMap++`]] +:set: pass:normal[xref:#EnumerableMap-set-struct-EnumerableMap-UintToAddressMap-uint256-address-[`++set++`]] +:remove: pass:normal[xref:#EnumerableMap-remove-struct-EnumerableMap-UintToAddressMap-uint256-[`++remove++`]] +:contains: pass:normal[xref:#EnumerableMap-contains-struct-EnumerableMap-UintToAddressMap-uint256-[`++contains++`]] +:length: pass:normal[xref:#EnumerableMap-length-struct-EnumerableMap-UintToAddressMap-[`++length++`]] +:at: pass:normal[xref:#EnumerableMap-at-struct-EnumerableMap-UintToAddressMap-uint256-[`++at++`]] +:tryGet: pass:normal[xref:#EnumerableMap-tryGet-struct-EnumerableMap-UintToAddressMap-uint256-[`++tryGet++`]] +:get: pass:normal[xref:#EnumerableMap-get-struct-EnumerableMap-UintToAddressMap-uint256-[`++get++`]] +:keys: pass:normal[xref:#EnumerableMap-keys-struct-EnumerableMap-UintToAddressMap-[`++keys++`]] +:AddressToUintMap: pass:normal[xref:#EnumerableMap-AddressToUintMap[`++AddressToUintMap++`]] +:set: pass:normal[xref:#EnumerableMap-set-struct-EnumerableMap-AddressToUintMap-address-uint256-[`++set++`]] +:remove: pass:normal[xref:#EnumerableMap-remove-struct-EnumerableMap-AddressToUintMap-address-[`++remove++`]] +:contains: pass:normal[xref:#EnumerableMap-contains-struct-EnumerableMap-AddressToUintMap-address-[`++contains++`]] +:length: pass:normal[xref:#EnumerableMap-length-struct-EnumerableMap-AddressToUintMap-[`++length++`]] +:at: pass:normal[xref:#EnumerableMap-at-struct-EnumerableMap-AddressToUintMap-uint256-[`++at++`]] +:tryGet: pass:normal[xref:#EnumerableMap-tryGet-struct-EnumerableMap-AddressToUintMap-address-[`++tryGet++`]] +:get: pass:normal[xref:#EnumerableMap-get-struct-EnumerableMap-AddressToUintMap-address-[`++get++`]] +:keys: pass:normal[xref:#EnumerableMap-keys-struct-EnumerableMap-AddressToUintMap-[`++keys++`]] +:Bytes32ToUintMap: pass:normal[xref:#EnumerableMap-Bytes32ToUintMap[`++Bytes32ToUintMap++`]] +:set: pass:normal[xref:#EnumerableMap-set-struct-EnumerableMap-Bytes32ToUintMap-bytes32-uint256-[`++set++`]] +:remove: pass:normal[xref:#EnumerableMap-remove-struct-EnumerableMap-Bytes32ToUintMap-bytes32-[`++remove++`]] +:contains: pass:normal[xref:#EnumerableMap-contains-struct-EnumerableMap-Bytes32ToUintMap-bytes32-[`++contains++`]] +:length: pass:normal[xref:#EnumerableMap-length-struct-EnumerableMap-Bytes32ToUintMap-[`++length++`]] +:at: pass:normal[xref:#EnumerableMap-at-struct-EnumerableMap-Bytes32ToUintMap-uint256-[`++at++`]] +:tryGet: pass:normal[xref:#EnumerableMap-tryGet-struct-EnumerableMap-Bytes32ToUintMap-bytes32-[`++tryGet++`]] +:get: pass:normal[xref:#EnumerableMap-get-struct-EnumerableMap-Bytes32ToUintMap-bytes32-[`++get++`]] +:keys: pass:normal[xref:#EnumerableMap-keys-struct-EnumerableMap-Bytes32ToUintMap-[`++keys++`]] + +[.contract] +[[EnumerableMap]] +=== `++EnumerableMap++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/utils/structs/EnumerableMap.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; +``` + +Library for managing an enumerable variant of Solidity's +https://solidity.readthedocs.io/en/latest/types.html#mapping-types[`mapping`] +type. + +Maps have the following properties: + +- Entries are added, removed, and checked for existence in constant time +(O(1)). +- Entries are enumerated in O(n). No guarantees are made on the ordering. + +```solidity +contract Example { + // Add the library methods + using EnumerableMap for EnumerableMap.UintToAddressMap; + + // Declare a set state variable + EnumerableMap.UintToAddressMap private myMap; +} +``` + +The following map types are supported: + +- `uint256 -> address` (`UintToAddressMap`) since v3.0.0 +- `address -> uint256` (`AddressToUintMap`) since v4.6.0 +- `bytes32 -> bytes32` (`Bytes32ToBytes32Map`) since v4.6.0 +- `uint256 -> uint256` (`UintToUintMap`) since v4.7.0 +- `bytes32 -> uint256` (`Bytes32ToUintMap`) since v4.7.0 + +[WARNING] +==== +Trying to delete such a structure from storage will likely result in data corruption, rendering the structure +unusable. +See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info. + +In order to clean an EnumerableMap, you can either remove all elements one by one or create a fresh instance using an +array of EnumerableMap. +==== + +[.contract-index] +.Functions +-- +* {xref-EnumerableMap-set-struct-EnumerableMap-Bytes32ToBytes32Map-bytes32-bytes32-}[`++set(map, key, value)++`] +* {xref-EnumerableMap-remove-struct-EnumerableMap-Bytes32ToBytes32Map-bytes32-}[`++remove(map, key)++`] +* {xref-EnumerableMap-contains-struct-EnumerableMap-Bytes32ToBytes32Map-bytes32-}[`++contains(map, key)++`] +* {xref-EnumerableMap-length-struct-EnumerableMap-Bytes32ToBytes32Map-}[`++length(map)++`] +* {xref-EnumerableMap-at-struct-EnumerableMap-Bytes32ToBytes32Map-uint256-}[`++at(map, index)++`] +* {xref-EnumerableMap-tryGet-struct-EnumerableMap-Bytes32ToBytes32Map-bytes32-}[`++tryGet(map, key)++`] +* {xref-EnumerableMap-get-struct-EnumerableMap-Bytes32ToBytes32Map-bytes32-}[`++get(map, key)++`] +* {xref-EnumerableMap-keys-struct-EnumerableMap-Bytes32ToBytes32Map-}[`++keys(map)++`] +* {xref-EnumerableMap-set-struct-EnumerableMap-UintToUintMap-uint256-uint256-}[`++set(map, key, value)++`] +* {xref-EnumerableMap-remove-struct-EnumerableMap-UintToUintMap-uint256-}[`++remove(map, key)++`] +* {xref-EnumerableMap-contains-struct-EnumerableMap-UintToUintMap-uint256-}[`++contains(map, key)++`] +* {xref-EnumerableMap-length-struct-EnumerableMap-UintToUintMap-}[`++length(map)++`] +* {xref-EnumerableMap-at-struct-EnumerableMap-UintToUintMap-uint256-}[`++at(map, index)++`] +* {xref-EnumerableMap-tryGet-struct-EnumerableMap-UintToUintMap-uint256-}[`++tryGet(map, key)++`] +* {xref-EnumerableMap-get-struct-EnumerableMap-UintToUintMap-uint256-}[`++get(map, key)++`] +* {xref-EnumerableMap-keys-struct-EnumerableMap-UintToUintMap-}[`++keys(map)++`] +* {xref-EnumerableMap-set-struct-EnumerableMap-UintToAddressMap-uint256-address-}[`++set(map, key, value)++`] +* {xref-EnumerableMap-remove-struct-EnumerableMap-UintToAddressMap-uint256-}[`++remove(map, key)++`] +* {xref-EnumerableMap-contains-struct-EnumerableMap-UintToAddressMap-uint256-}[`++contains(map, key)++`] +* {xref-EnumerableMap-length-struct-EnumerableMap-UintToAddressMap-}[`++length(map)++`] +* {xref-EnumerableMap-at-struct-EnumerableMap-UintToAddressMap-uint256-}[`++at(map, index)++`] +* {xref-EnumerableMap-tryGet-struct-EnumerableMap-UintToAddressMap-uint256-}[`++tryGet(map, key)++`] +* {xref-EnumerableMap-get-struct-EnumerableMap-UintToAddressMap-uint256-}[`++get(map, key)++`] +* {xref-EnumerableMap-keys-struct-EnumerableMap-UintToAddressMap-}[`++keys(map)++`] +* {xref-EnumerableMap-set-struct-EnumerableMap-AddressToUintMap-address-uint256-}[`++set(map, key, value)++`] +* {xref-EnumerableMap-remove-struct-EnumerableMap-AddressToUintMap-address-}[`++remove(map, key)++`] +* {xref-EnumerableMap-contains-struct-EnumerableMap-AddressToUintMap-address-}[`++contains(map, key)++`] +* {xref-EnumerableMap-length-struct-EnumerableMap-AddressToUintMap-}[`++length(map)++`] +* {xref-EnumerableMap-at-struct-EnumerableMap-AddressToUintMap-uint256-}[`++at(map, index)++`] +* {xref-EnumerableMap-tryGet-struct-EnumerableMap-AddressToUintMap-address-}[`++tryGet(map, key)++`] +* {xref-EnumerableMap-get-struct-EnumerableMap-AddressToUintMap-address-}[`++get(map, key)++`] +* {xref-EnumerableMap-keys-struct-EnumerableMap-AddressToUintMap-}[`++keys(map)++`] +* {xref-EnumerableMap-set-struct-EnumerableMap-Bytes32ToUintMap-bytes32-uint256-}[`++set(map, key, value)++`] +* {xref-EnumerableMap-remove-struct-EnumerableMap-Bytes32ToUintMap-bytes32-}[`++remove(map, key)++`] +* {xref-EnumerableMap-contains-struct-EnumerableMap-Bytes32ToUintMap-bytes32-}[`++contains(map, key)++`] +* {xref-EnumerableMap-length-struct-EnumerableMap-Bytes32ToUintMap-}[`++length(map)++`] +* {xref-EnumerableMap-at-struct-EnumerableMap-Bytes32ToUintMap-uint256-}[`++at(map, index)++`] +* {xref-EnumerableMap-tryGet-struct-EnumerableMap-Bytes32ToUintMap-bytes32-}[`++tryGet(map, key)++`] +* {xref-EnumerableMap-get-struct-EnumerableMap-Bytes32ToUintMap-bytes32-}[`++get(map, key)++`] +* {xref-EnumerableMap-keys-struct-EnumerableMap-Bytes32ToUintMap-}[`++keys(map)++`] + +-- + +[.contract-index] +.Errors +-- +* {xref-EnumerableMap-EnumerableMapNonexistentKey-bytes32-}[`++EnumerableMapNonexistentKey(key)++`] + +-- + +[.contract-item] +[[EnumerableMap-set-struct-EnumerableMap-Bytes32ToBytes32Map-bytes32-bytes32-]] +==== `[.contract-item-name]#++set++#++(struct EnumerableMap.Bytes32ToBytes32Map map, bytes32 key, bytes32 value) β†’ bool++` [.item-kind]#internal# + +Adds a key-value pair to a map, or updates the value for an existing +key. O(1). + +Returns true if the key was added to the map, that is if it was not +already present. + +[.contract-item] +[[EnumerableMap-remove-struct-EnumerableMap-Bytes32ToBytes32Map-bytes32-]] +==== `[.contract-item-name]#++remove++#++(struct EnumerableMap.Bytes32ToBytes32Map map, bytes32 key) β†’ bool++` [.item-kind]#internal# + +Removes a key-value pair from a map. O(1). + +Returns true if the key was removed from the map, that is if it was present. + +[.contract-item] +[[EnumerableMap-contains-struct-EnumerableMap-Bytes32ToBytes32Map-bytes32-]] +==== `[.contract-item-name]#++contains++#++(struct EnumerableMap.Bytes32ToBytes32Map map, bytes32 key) β†’ bool++` [.item-kind]#internal# + +Returns true if the key is in the map. O(1). + +[.contract-item] +[[EnumerableMap-length-struct-EnumerableMap-Bytes32ToBytes32Map-]] +==== `[.contract-item-name]#++length++#++(struct EnumerableMap.Bytes32ToBytes32Map map) β†’ uint256++` [.item-kind]#internal# + +Returns the number of key-value pairs in the map. O(1). + +[.contract-item] +[[EnumerableMap-at-struct-EnumerableMap-Bytes32ToBytes32Map-uint256-]] +==== `[.contract-item-name]#++at++#++(struct EnumerableMap.Bytes32ToBytes32Map map, uint256 index) β†’ bytes32, bytes32++` [.item-kind]#internal# + +Returns the key-value pair stored at position `index` in the map. O(1). + +Note that there are no guarantees on the ordering of entries inside the +array, and it may change when more entries are added or removed. + +Requirements: + +- `index` must be strictly less than {length}. + +[.contract-item] +[[EnumerableMap-tryGet-struct-EnumerableMap-Bytes32ToBytes32Map-bytes32-]] +==== `[.contract-item-name]#++tryGet++#++(struct EnumerableMap.Bytes32ToBytes32Map map, bytes32 key) β†’ bool, bytes32++` [.item-kind]#internal# + +Tries to returns the value associated with `key`. O(1). +Does not revert if `key` is not in the map. + +[.contract-item] +[[EnumerableMap-get-struct-EnumerableMap-Bytes32ToBytes32Map-bytes32-]] +==== `[.contract-item-name]#++get++#++(struct EnumerableMap.Bytes32ToBytes32Map map, bytes32 key) β†’ bytes32++` [.item-kind]#internal# + +Returns the value associated with `key`. O(1). + +Requirements: + +- `key` must be in the map. + +[.contract-item] +[[EnumerableMap-keys-struct-EnumerableMap-Bytes32ToBytes32Map-]] +==== `[.contract-item-name]#++keys++#++(struct EnumerableMap.Bytes32ToBytes32Map map) β†’ bytes32[]++` [.item-kind]#internal# + +Return the an array containing all the keys + +WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed +to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that +this function has an unbounded cost, and using it as part of a state-changing function may render the function +uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. + +[.contract-item] +[[EnumerableMap-set-struct-EnumerableMap-UintToUintMap-uint256-uint256-]] +==== `[.contract-item-name]#++set++#++(struct EnumerableMap.UintToUintMap map, uint256 key, uint256 value) β†’ bool++` [.item-kind]#internal# + +Adds a key-value pair to a map, or updates the value for an existing +key. O(1). + +Returns true if the key was added to the map, that is if it was not +already present. + +[.contract-item] +[[EnumerableMap-remove-struct-EnumerableMap-UintToUintMap-uint256-]] +==== `[.contract-item-name]#++remove++#++(struct EnumerableMap.UintToUintMap map, uint256 key) β†’ bool++` [.item-kind]#internal# + +Removes a value from a map. O(1). + +Returns true if the key was removed from the map, that is if it was present. + +[.contract-item] +[[EnumerableMap-contains-struct-EnumerableMap-UintToUintMap-uint256-]] +==== `[.contract-item-name]#++contains++#++(struct EnumerableMap.UintToUintMap map, uint256 key) β†’ bool++` [.item-kind]#internal# + +Returns true if the key is in the map. O(1). + +[.contract-item] +[[EnumerableMap-length-struct-EnumerableMap-UintToUintMap-]] +==== `[.contract-item-name]#++length++#++(struct EnumerableMap.UintToUintMap map) β†’ uint256++` [.item-kind]#internal# + +Returns the number of elements in the map. O(1). + +[.contract-item] +[[EnumerableMap-at-struct-EnumerableMap-UintToUintMap-uint256-]] +==== `[.contract-item-name]#++at++#++(struct EnumerableMap.UintToUintMap map, uint256 index) β†’ uint256, uint256++` [.item-kind]#internal# + +Returns the element stored at position `index` in the map. O(1). +Note that there are no guarantees on the ordering of values inside the +array, and it may change when more values are added or removed. + +Requirements: + +- `index` must be strictly less than {length}. + +[.contract-item] +[[EnumerableMap-tryGet-struct-EnumerableMap-UintToUintMap-uint256-]] +==== `[.contract-item-name]#++tryGet++#++(struct EnumerableMap.UintToUintMap map, uint256 key) β†’ bool, uint256++` [.item-kind]#internal# + +Tries to returns the value associated with `key`. O(1). +Does not revert if `key` is not in the map. + +[.contract-item] +[[EnumerableMap-get-struct-EnumerableMap-UintToUintMap-uint256-]] +==== `[.contract-item-name]#++get++#++(struct EnumerableMap.UintToUintMap map, uint256 key) β†’ uint256++` [.item-kind]#internal# + +Returns the value associated with `key`. O(1). + +Requirements: + +- `key` must be in the map. + +[.contract-item] +[[EnumerableMap-keys-struct-EnumerableMap-UintToUintMap-]] +==== `[.contract-item-name]#++keys++#++(struct EnumerableMap.UintToUintMap map) β†’ uint256[]++` [.item-kind]#internal# + +Return the an array containing all the keys + +WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed +to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that +this function has an unbounded cost, and using it as part of a state-changing function may render the function +uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. + +[.contract-item] +[[EnumerableMap-set-struct-EnumerableMap-UintToAddressMap-uint256-address-]] +==== `[.contract-item-name]#++set++#++(struct EnumerableMap.UintToAddressMap map, uint256 key, address value) β†’ bool++` [.item-kind]#internal# + +Adds a key-value pair to a map, or updates the value for an existing +key. O(1). + +Returns true if the key was added to the map, that is if it was not +already present. + +[.contract-item] +[[EnumerableMap-remove-struct-EnumerableMap-UintToAddressMap-uint256-]] +==== `[.contract-item-name]#++remove++#++(struct EnumerableMap.UintToAddressMap map, uint256 key) β†’ bool++` [.item-kind]#internal# + +Removes a value from a map. O(1). + +Returns true if the key was removed from the map, that is if it was present. + +[.contract-item] +[[EnumerableMap-contains-struct-EnumerableMap-UintToAddressMap-uint256-]] +==== `[.contract-item-name]#++contains++#++(struct EnumerableMap.UintToAddressMap map, uint256 key) β†’ bool++` [.item-kind]#internal# + +Returns true if the key is in the map. O(1). + +[.contract-item] +[[EnumerableMap-length-struct-EnumerableMap-UintToAddressMap-]] +==== `[.contract-item-name]#++length++#++(struct EnumerableMap.UintToAddressMap map) β†’ uint256++` [.item-kind]#internal# + +Returns the number of elements in the map. O(1). + +[.contract-item] +[[EnumerableMap-at-struct-EnumerableMap-UintToAddressMap-uint256-]] +==== `[.contract-item-name]#++at++#++(struct EnumerableMap.UintToAddressMap map, uint256 index) β†’ uint256, address++` [.item-kind]#internal# + +Returns the element stored at position `index` in the map. O(1). +Note that there are no guarantees on the ordering of values inside the +array, and it may change when more values are added or removed. + +Requirements: + +- `index` must be strictly less than {length}. + +[.contract-item] +[[EnumerableMap-tryGet-struct-EnumerableMap-UintToAddressMap-uint256-]] +==== `[.contract-item-name]#++tryGet++#++(struct EnumerableMap.UintToAddressMap map, uint256 key) β†’ bool, address++` [.item-kind]#internal# + +Tries to returns the value associated with `key`. O(1). +Does not revert if `key` is not in the map. + +[.contract-item] +[[EnumerableMap-get-struct-EnumerableMap-UintToAddressMap-uint256-]] +==== `[.contract-item-name]#++get++#++(struct EnumerableMap.UintToAddressMap map, uint256 key) β†’ address++` [.item-kind]#internal# + +Returns the value associated with `key`. O(1). + +Requirements: + +- `key` must be in the map. + +[.contract-item] +[[EnumerableMap-keys-struct-EnumerableMap-UintToAddressMap-]] +==== `[.contract-item-name]#++keys++#++(struct EnumerableMap.UintToAddressMap map) β†’ uint256[]++` [.item-kind]#internal# + +Return the an array containing all the keys + +WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed +to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that +this function has an unbounded cost, and using it as part of a state-changing function may render the function +uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. + +[.contract-item] +[[EnumerableMap-set-struct-EnumerableMap-AddressToUintMap-address-uint256-]] +==== `[.contract-item-name]#++set++#++(struct EnumerableMap.AddressToUintMap map, address key, uint256 value) β†’ bool++` [.item-kind]#internal# + +Adds a key-value pair to a map, or updates the value for an existing +key. O(1). + +Returns true if the key was added to the map, that is if it was not +already present. + +[.contract-item] +[[EnumerableMap-remove-struct-EnumerableMap-AddressToUintMap-address-]] +==== `[.contract-item-name]#++remove++#++(struct EnumerableMap.AddressToUintMap map, address key) β†’ bool++` [.item-kind]#internal# + +Removes a value from a map. O(1). + +Returns true if the key was removed from the map, that is if it was present. + +[.contract-item] +[[EnumerableMap-contains-struct-EnumerableMap-AddressToUintMap-address-]] +==== `[.contract-item-name]#++contains++#++(struct EnumerableMap.AddressToUintMap map, address key) β†’ bool++` [.item-kind]#internal# + +Returns true if the key is in the map. O(1). + +[.contract-item] +[[EnumerableMap-length-struct-EnumerableMap-AddressToUintMap-]] +==== `[.contract-item-name]#++length++#++(struct EnumerableMap.AddressToUintMap map) β†’ uint256++` [.item-kind]#internal# + +Returns the number of elements in the map. O(1). + +[.contract-item] +[[EnumerableMap-at-struct-EnumerableMap-AddressToUintMap-uint256-]] +==== `[.contract-item-name]#++at++#++(struct EnumerableMap.AddressToUintMap map, uint256 index) β†’ address, uint256++` [.item-kind]#internal# + +Returns the element stored at position `index` in the map. O(1). +Note that there are no guarantees on the ordering of values inside the +array, and it may change when more values are added or removed. + +Requirements: + +- `index` must be strictly less than {length}. + +[.contract-item] +[[EnumerableMap-tryGet-struct-EnumerableMap-AddressToUintMap-address-]] +==== `[.contract-item-name]#++tryGet++#++(struct EnumerableMap.AddressToUintMap map, address key) β†’ bool, uint256++` [.item-kind]#internal# + +Tries to returns the value associated with `key`. O(1). +Does not revert if `key` is not in the map. + +[.contract-item] +[[EnumerableMap-get-struct-EnumerableMap-AddressToUintMap-address-]] +==== `[.contract-item-name]#++get++#++(struct EnumerableMap.AddressToUintMap map, address key) β†’ uint256++` [.item-kind]#internal# + +Returns the value associated with `key`. O(1). + +Requirements: + +- `key` must be in the map. + +[.contract-item] +[[EnumerableMap-keys-struct-EnumerableMap-AddressToUintMap-]] +==== `[.contract-item-name]#++keys++#++(struct EnumerableMap.AddressToUintMap map) β†’ address[]++` [.item-kind]#internal# + +Return the an array containing all the keys + +WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed +to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that +this function has an unbounded cost, and using it as part of a state-changing function may render the function +uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. + +[.contract-item] +[[EnumerableMap-set-struct-EnumerableMap-Bytes32ToUintMap-bytes32-uint256-]] +==== `[.contract-item-name]#++set++#++(struct EnumerableMap.Bytes32ToUintMap map, bytes32 key, uint256 value) β†’ bool++` [.item-kind]#internal# + +Adds a key-value pair to a map, or updates the value for an existing +key. O(1). + +Returns true if the key was added to the map, that is if it was not +already present. + +[.contract-item] +[[EnumerableMap-remove-struct-EnumerableMap-Bytes32ToUintMap-bytes32-]] +==== `[.contract-item-name]#++remove++#++(struct EnumerableMap.Bytes32ToUintMap map, bytes32 key) β†’ bool++` [.item-kind]#internal# + +Removes a value from a map. O(1). + +Returns true if the key was removed from the map, that is if it was present. + +[.contract-item] +[[EnumerableMap-contains-struct-EnumerableMap-Bytes32ToUintMap-bytes32-]] +==== `[.contract-item-name]#++contains++#++(struct EnumerableMap.Bytes32ToUintMap map, bytes32 key) β†’ bool++` [.item-kind]#internal# + +Returns true if the key is in the map. O(1). + +[.contract-item] +[[EnumerableMap-length-struct-EnumerableMap-Bytes32ToUintMap-]] +==== `[.contract-item-name]#++length++#++(struct EnumerableMap.Bytes32ToUintMap map) β†’ uint256++` [.item-kind]#internal# + +Returns the number of elements in the map. O(1). + +[.contract-item] +[[EnumerableMap-at-struct-EnumerableMap-Bytes32ToUintMap-uint256-]] +==== `[.contract-item-name]#++at++#++(struct EnumerableMap.Bytes32ToUintMap map, uint256 index) β†’ bytes32, uint256++` [.item-kind]#internal# + +Returns the element stored at position `index` in the map. O(1). +Note that there are no guarantees on the ordering of values inside the +array, and it may change when more values are added or removed. + +Requirements: + +- `index` must be strictly less than {length}. + +[.contract-item] +[[EnumerableMap-tryGet-struct-EnumerableMap-Bytes32ToUintMap-bytes32-]] +==== `[.contract-item-name]#++tryGet++#++(struct EnumerableMap.Bytes32ToUintMap map, bytes32 key) β†’ bool, uint256++` [.item-kind]#internal# + +Tries to returns the value associated with `key`. O(1). +Does not revert if `key` is not in the map. + +[.contract-item] +[[EnumerableMap-get-struct-EnumerableMap-Bytes32ToUintMap-bytes32-]] +==== `[.contract-item-name]#++get++#++(struct EnumerableMap.Bytes32ToUintMap map, bytes32 key) β†’ uint256++` [.item-kind]#internal# + +Returns the value associated with `key`. O(1). + +Requirements: + +- `key` must be in the map. + +[.contract-item] +[[EnumerableMap-keys-struct-EnumerableMap-Bytes32ToUintMap-]] +==== `[.contract-item-name]#++keys++#++(struct EnumerableMap.Bytes32ToUintMap map) β†’ bytes32[]++` [.item-kind]#internal# + +Return the an array containing all the keys + +WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed +to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that +this function has an unbounded cost, and using it as part of a state-changing function may render the function +uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. + +[.contract-item] +[[EnumerableMap-EnumerableMapNonexistentKey-bytes32-]] +==== `[.contract-item-name]#++EnumerableMapNonexistentKey++#++(bytes32 key)++` [.item-kind]#error# + +Query for a nonexistent map key. + +:Set: pass:normal[xref:#EnumerableSet-Set[`++Set++`]] +:Bytes32Set: pass:normal[xref:#EnumerableSet-Bytes32Set[`++Bytes32Set++`]] +:add: pass:normal[xref:#EnumerableSet-add-struct-EnumerableSet-Bytes32Set-bytes32-[`++add++`]] +:remove: pass:normal[xref:#EnumerableSet-remove-struct-EnumerableSet-Bytes32Set-bytes32-[`++remove++`]] +:contains: pass:normal[xref:#EnumerableSet-contains-struct-EnumerableSet-Bytes32Set-bytes32-[`++contains++`]] +:length: pass:normal[xref:#EnumerableSet-length-struct-EnumerableSet-Bytes32Set-[`++length++`]] +:at: pass:normal[xref:#EnumerableSet-at-struct-EnumerableSet-Bytes32Set-uint256-[`++at++`]] +:values: pass:normal[xref:#EnumerableSet-values-struct-EnumerableSet-Bytes32Set-[`++values++`]] +:AddressSet: pass:normal[xref:#EnumerableSet-AddressSet[`++AddressSet++`]] +:add: pass:normal[xref:#EnumerableSet-add-struct-EnumerableSet-AddressSet-address-[`++add++`]] +:remove: pass:normal[xref:#EnumerableSet-remove-struct-EnumerableSet-AddressSet-address-[`++remove++`]] +:contains: pass:normal[xref:#EnumerableSet-contains-struct-EnumerableSet-AddressSet-address-[`++contains++`]] +:length: pass:normal[xref:#EnumerableSet-length-struct-EnumerableSet-AddressSet-[`++length++`]] +:at: pass:normal[xref:#EnumerableSet-at-struct-EnumerableSet-AddressSet-uint256-[`++at++`]] +:values: pass:normal[xref:#EnumerableSet-values-struct-EnumerableSet-AddressSet-[`++values++`]] +:UintSet: pass:normal[xref:#EnumerableSet-UintSet[`++UintSet++`]] +:add: pass:normal[xref:#EnumerableSet-add-struct-EnumerableSet-UintSet-uint256-[`++add++`]] +:remove: pass:normal[xref:#EnumerableSet-remove-struct-EnumerableSet-UintSet-uint256-[`++remove++`]] +:contains: pass:normal[xref:#EnumerableSet-contains-struct-EnumerableSet-UintSet-uint256-[`++contains++`]] +:length: pass:normal[xref:#EnumerableSet-length-struct-EnumerableSet-UintSet-[`++length++`]] +:at: pass:normal[xref:#EnumerableSet-at-struct-EnumerableSet-UintSet-uint256-[`++at++`]] +:values: pass:normal[xref:#EnumerableSet-values-struct-EnumerableSet-UintSet-[`++values++`]] + +[.contract] +[[EnumerableSet]] +=== `++EnumerableSet++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/utils/structs/EnumerableSet.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; +``` + +Library for managing +https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive +types. + +Sets have the following properties: + +- Elements are added, removed, and checked for existence in constant time +(O(1)). +- Elements are enumerated in O(n). No guarantees are made on the ordering. + +```solidity +contract Example { + // Add the library methods + using EnumerableSet for EnumerableSet.AddressSet; + + // Declare a set state variable + EnumerableSet.AddressSet private mySet; +} +``` + +As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`) +and `uint256` (`UintSet`) are supported. + +[WARNING] +==== +Trying to delete such a structure from storage will likely result in data corruption, rendering the structure +unusable. +See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info. + +In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an +array of EnumerableSet. +==== + +[.contract-index] +.Functions +-- +* {xref-EnumerableSet-add-struct-EnumerableSet-Bytes32Set-bytes32-}[`++add(set, value)++`] +* {xref-EnumerableSet-remove-struct-EnumerableSet-Bytes32Set-bytes32-}[`++remove(set, value)++`] +* {xref-EnumerableSet-contains-struct-EnumerableSet-Bytes32Set-bytes32-}[`++contains(set, value)++`] +* {xref-EnumerableSet-length-struct-EnumerableSet-Bytes32Set-}[`++length(set)++`] +* {xref-EnumerableSet-at-struct-EnumerableSet-Bytes32Set-uint256-}[`++at(set, index)++`] +* {xref-EnumerableSet-values-struct-EnumerableSet-Bytes32Set-}[`++values(set)++`] +* {xref-EnumerableSet-add-struct-EnumerableSet-AddressSet-address-}[`++add(set, value)++`] +* {xref-EnumerableSet-remove-struct-EnumerableSet-AddressSet-address-}[`++remove(set, value)++`] +* {xref-EnumerableSet-contains-struct-EnumerableSet-AddressSet-address-}[`++contains(set, value)++`] +* {xref-EnumerableSet-length-struct-EnumerableSet-AddressSet-}[`++length(set)++`] +* {xref-EnumerableSet-at-struct-EnumerableSet-AddressSet-uint256-}[`++at(set, index)++`] +* {xref-EnumerableSet-values-struct-EnumerableSet-AddressSet-}[`++values(set)++`] +* {xref-EnumerableSet-add-struct-EnumerableSet-UintSet-uint256-}[`++add(set, value)++`] +* {xref-EnumerableSet-remove-struct-EnumerableSet-UintSet-uint256-}[`++remove(set, value)++`] +* {xref-EnumerableSet-contains-struct-EnumerableSet-UintSet-uint256-}[`++contains(set, value)++`] +* {xref-EnumerableSet-length-struct-EnumerableSet-UintSet-}[`++length(set)++`] +* {xref-EnumerableSet-at-struct-EnumerableSet-UintSet-uint256-}[`++at(set, index)++`] +* {xref-EnumerableSet-values-struct-EnumerableSet-UintSet-}[`++values(set)++`] + +-- + +[.contract-item] +[[EnumerableSet-add-struct-EnumerableSet-Bytes32Set-bytes32-]] +==== `[.contract-item-name]#++add++#++(struct EnumerableSet.Bytes32Set set, bytes32 value) β†’ bool++` [.item-kind]#internal# + +Add a value to a set. O(1). + +Returns true if the value was added to the set, that is if it was not +already present. + +[.contract-item] +[[EnumerableSet-remove-struct-EnumerableSet-Bytes32Set-bytes32-]] +==== `[.contract-item-name]#++remove++#++(struct EnumerableSet.Bytes32Set set, bytes32 value) β†’ bool++` [.item-kind]#internal# + +Removes a value from a set. O(1). + +Returns true if the value was removed from the set, that is if it was +present. + +[.contract-item] +[[EnumerableSet-contains-struct-EnumerableSet-Bytes32Set-bytes32-]] +==== `[.contract-item-name]#++contains++#++(struct EnumerableSet.Bytes32Set set, bytes32 value) β†’ bool++` [.item-kind]#internal# + +Returns true if the value is in the set. O(1). + +[.contract-item] +[[EnumerableSet-length-struct-EnumerableSet-Bytes32Set-]] +==== `[.contract-item-name]#++length++#++(struct EnumerableSet.Bytes32Set set) β†’ uint256++` [.item-kind]#internal# + +Returns the number of values in the set. O(1). + +[.contract-item] +[[EnumerableSet-at-struct-EnumerableSet-Bytes32Set-uint256-]] +==== `[.contract-item-name]#++at++#++(struct EnumerableSet.Bytes32Set set, uint256 index) β†’ bytes32++` [.item-kind]#internal# + +Returns the value stored at position `index` in the set. O(1). + +Note that there are no guarantees on the ordering of values inside the +array, and it may change when more values are added or removed. + +Requirements: + +- `index` must be strictly less than {length}. + +[.contract-item] +[[EnumerableSet-values-struct-EnumerableSet-Bytes32Set-]] +==== `[.contract-item-name]#++values++#++(struct EnumerableSet.Bytes32Set set) β†’ bytes32[]++` [.item-kind]#internal# + +Return the entire set in an array + +WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed +to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that +this function has an unbounded cost, and using it as part of a state-changing function may render the function +uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. + +[.contract-item] +[[EnumerableSet-add-struct-EnumerableSet-AddressSet-address-]] +==== `[.contract-item-name]#++add++#++(struct EnumerableSet.AddressSet set, address value) β†’ bool++` [.item-kind]#internal# + +Add a value to a set. O(1). + +Returns true if the value was added to the set, that is if it was not +already present. + +[.contract-item] +[[EnumerableSet-remove-struct-EnumerableSet-AddressSet-address-]] +==== `[.contract-item-name]#++remove++#++(struct EnumerableSet.AddressSet set, address value) β†’ bool++` [.item-kind]#internal# + +Removes a value from a set. O(1). + +Returns true if the value was removed from the set, that is if it was +present. + +[.contract-item] +[[EnumerableSet-contains-struct-EnumerableSet-AddressSet-address-]] +==== `[.contract-item-name]#++contains++#++(struct EnumerableSet.AddressSet set, address value) β†’ bool++` [.item-kind]#internal# + +Returns true if the value is in the set. O(1). + +[.contract-item] +[[EnumerableSet-length-struct-EnumerableSet-AddressSet-]] +==== `[.contract-item-name]#++length++#++(struct EnumerableSet.AddressSet set) β†’ uint256++` [.item-kind]#internal# + +Returns the number of values in the set. O(1). + +[.contract-item] +[[EnumerableSet-at-struct-EnumerableSet-AddressSet-uint256-]] +==== `[.contract-item-name]#++at++#++(struct EnumerableSet.AddressSet set, uint256 index) β†’ address++` [.item-kind]#internal# + +Returns the value stored at position `index` in the set. O(1). + +Note that there are no guarantees on the ordering of values inside the +array, and it may change when more values are added or removed. + +Requirements: + +- `index` must be strictly less than {length}. + +[.contract-item] +[[EnumerableSet-values-struct-EnumerableSet-AddressSet-]] +==== `[.contract-item-name]#++values++#++(struct EnumerableSet.AddressSet set) β†’ address[]++` [.item-kind]#internal# + +Return the entire set in an array + +WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed +to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that +this function has an unbounded cost, and using it as part of a state-changing function may render the function +uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. + +[.contract-item] +[[EnumerableSet-add-struct-EnumerableSet-UintSet-uint256-]] +==== `[.contract-item-name]#++add++#++(struct EnumerableSet.UintSet set, uint256 value) β†’ bool++` [.item-kind]#internal# + +Add a value to a set. O(1). + +Returns true if the value was added to the set, that is if it was not +already present. + +[.contract-item] +[[EnumerableSet-remove-struct-EnumerableSet-UintSet-uint256-]] +==== `[.contract-item-name]#++remove++#++(struct EnumerableSet.UintSet set, uint256 value) β†’ bool++` [.item-kind]#internal# + +Removes a value from a set. O(1). + +Returns true if the value was removed from the set, that is if it was +present. + +[.contract-item] +[[EnumerableSet-contains-struct-EnumerableSet-UintSet-uint256-]] +==== `[.contract-item-name]#++contains++#++(struct EnumerableSet.UintSet set, uint256 value) β†’ bool++` [.item-kind]#internal# + +Returns true if the value is in the set. O(1). + +[.contract-item] +[[EnumerableSet-length-struct-EnumerableSet-UintSet-]] +==== `[.contract-item-name]#++length++#++(struct EnumerableSet.UintSet set) β†’ uint256++` [.item-kind]#internal# + +Returns the number of values in the set. O(1). + +[.contract-item] +[[EnumerableSet-at-struct-EnumerableSet-UintSet-uint256-]] +==== `[.contract-item-name]#++at++#++(struct EnumerableSet.UintSet set, uint256 index) β†’ uint256++` [.item-kind]#internal# + +Returns the value stored at position `index` in the set. O(1). + +Note that there are no guarantees on the ordering of values inside the +array, and it may change when more values are added or removed. + +Requirements: + +- `index` must be strictly less than {length}. + +[.contract-item] +[[EnumerableSet-values-struct-EnumerableSet-UintSet-]] +==== `[.contract-item-name]#++values++#++(struct EnumerableSet.UintSet set) β†’ uint256[]++` [.item-kind]#internal# + +Return the entire set in an array + +WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed +to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that +this function has an unbounded cost, and using it as part of a state-changing function may render the function +uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. + +:QueueEmpty: pass:normal[xref:#DoubleEndedQueue-QueueEmpty--[`++QueueEmpty++`]] +:QueueFull: pass:normal[xref:#DoubleEndedQueue-QueueFull--[`++QueueFull++`]] +:QueueOutOfBounds: pass:normal[xref:#DoubleEndedQueue-QueueOutOfBounds--[`++QueueOutOfBounds++`]] +:Bytes32Deque: pass:normal[xref:#DoubleEndedQueue-Bytes32Deque[`++Bytes32Deque++`]] +:pushBack: pass:normal[xref:#DoubleEndedQueue-pushBack-struct-DoubleEndedQueue-Bytes32Deque-bytes32-[`++pushBack++`]] +:popBack: pass:normal[xref:#DoubleEndedQueue-popBack-struct-DoubleEndedQueue-Bytes32Deque-[`++popBack++`]] +:pushFront: pass:normal[xref:#DoubleEndedQueue-pushFront-struct-DoubleEndedQueue-Bytes32Deque-bytes32-[`++pushFront++`]] +:popFront: pass:normal[xref:#DoubleEndedQueue-popFront-struct-DoubleEndedQueue-Bytes32Deque-[`++popFront++`]] +:front: pass:normal[xref:#DoubleEndedQueue-front-struct-DoubleEndedQueue-Bytes32Deque-[`++front++`]] +:back: pass:normal[xref:#DoubleEndedQueue-back-struct-DoubleEndedQueue-Bytes32Deque-[`++back++`]] +:at: pass:normal[xref:#DoubleEndedQueue-at-struct-DoubleEndedQueue-Bytes32Deque-uint256-[`++at++`]] +:clear: pass:normal[xref:#DoubleEndedQueue-clear-struct-DoubleEndedQueue-Bytes32Deque-[`++clear++`]] +:length: pass:normal[xref:#DoubleEndedQueue-length-struct-DoubleEndedQueue-Bytes32Deque-[`++length++`]] +:empty: pass:normal[xref:#DoubleEndedQueue-empty-struct-DoubleEndedQueue-Bytes32Deque-[`++empty++`]] + +[.contract] +[[DoubleEndedQueue]] +=== `++DoubleEndedQueue++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/utils/structs/DoubleEndedQueue.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/utils/structs/DoubleEndedQueue.sol"; +``` + +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 +FIFO queues. Storage use is optimized, and all operations are O(1) constant time. This includes {clear}, given that +the existing queue contents are left in storage. + +The struct is called `Bytes32Deque`. Other types can be cast to and from `bytes32`. This data structure can only be +used in storage, and not in memory. +```solidity +DoubleEndedQueue.Bytes32Deque queue; +``` + +[.contract-index] +.Functions +-- +* {xref-DoubleEndedQueue-pushBack-struct-DoubleEndedQueue-Bytes32Deque-bytes32-}[`++pushBack(deque, value)++`] +* {xref-DoubleEndedQueue-popBack-struct-DoubleEndedQueue-Bytes32Deque-}[`++popBack(deque)++`] +* {xref-DoubleEndedQueue-pushFront-struct-DoubleEndedQueue-Bytes32Deque-bytes32-}[`++pushFront(deque, value)++`] +* {xref-DoubleEndedQueue-popFront-struct-DoubleEndedQueue-Bytes32Deque-}[`++popFront(deque)++`] +* {xref-DoubleEndedQueue-front-struct-DoubleEndedQueue-Bytes32Deque-}[`++front(deque)++`] +* {xref-DoubleEndedQueue-back-struct-DoubleEndedQueue-Bytes32Deque-}[`++back(deque)++`] +* {xref-DoubleEndedQueue-at-struct-DoubleEndedQueue-Bytes32Deque-uint256-}[`++at(deque, index)++`] +* {xref-DoubleEndedQueue-clear-struct-DoubleEndedQueue-Bytes32Deque-}[`++clear(deque)++`] +* {xref-DoubleEndedQueue-length-struct-DoubleEndedQueue-Bytes32Deque-}[`++length(deque)++`] +* {xref-DoubleEndedQueue-empty-struct-DoubleEndedQueue-Bytes32Deque-}[`++empty(deque)++`] + +-- + +[.contract-index] +.Errors +-- +* {xref-DoubleEndedQueue-QueueEmpty--}[`++QueueEmpty()++`] +* {xref-DoubleEndedQueue-QueueFull--}[`++QueueFull()++`] +* {xref-DoubleEndedQueue-QueueOutOfBounds--}[`++QueueOutOfBounds()++`] + +-- + +[.contract-item] +[[DoubleEndedQueue-pushBack-struct-DoubleEndedQueue-Bytes32Deque-bytes32-]] +==== `[.contract-item-name]#++pushBack++#++(struct DoubleEndedQueue.Bytes32Deque deque, bytes32 value)++` [.item-kind]#internal# + +Inserts an item at the end of the queue. + +Reverts with {QueueFull} if the queue is full. + +[.contract-item] +[[DoubleEndedQueue-popBack-struct-DoubleEndedQueue-Bytes32Deque-]] +==== `[.contract-item-name]#++popBack++#++(struct DoubleEndedQueue.Bytes32Deque deque) β†’ bytes32 value++` [.item-kind]#internal# + +Removes the item at the end of the queue and returns it. + +Reverts with {QueueEmpty} if the queue is empty. + +[.contract-item] +[[DoubleEndedQueue-pushFront-struct-DoubleEndedQueue-Bytes32Deque-bytes32-]] +==== `[.contract-item-name]#++pushFront++#++(struct DoubleEndedQueue.Bytes32Deque deque, bytes32 value)++` [.item-kind]#internal# + +Inserts an item at the beginning of the queue. + +Reverts with {QueueFull} if the queue is full. + +[.contract-item] +[[DoubleEndedQueue-popFront-struct-DoubleEndedQueue-Bytes32Deque-]] +==== `[.contract-item-name]#++popFront++#++(struct DoubleEndedQueue.Bytes32Deque deque) β†’ bytes32 value++` [.item-kind]#internal# + +Removes the item at the beginning of the queue and returns it. + +Reverts with `QueueEmpty` if the queue is empty. + +[.contract-item] +[[DoubleEndedQueue-front-struct-DoubleEndedQueue-Bytes32Deque-]] +==== `[.contract-item-name]#++front++#++(struct DoubleEndedQueue.Bytes32Deque deque) β†’ bytes32 value++` [.item-kind]#internal# + +Returns the item at the beginning of the queue. + +Reverts with `QueueEmpty` if the queue is empty. + +[.contract-item] +[[DoubleEndedQueue-back-struct-DoubleEndedQueue-Bytes32Deque-]] +==== `[.contract-item-name]#++back++#++(struct DoubleEndedQueue.Bytes32Deque deque) β†’ bytes32 value++` [.item-kind]#internal# + +Returns the item at the end of the queue. + +Reverts with `QueueEmpty` if the queue is empty. + +[.contract-item] +[[DoubleEndedQueue-at-struct-DoubleEndedQueue-Bytes32Deque-uint256-]] +==== `[.contract-item-name]#++at++#++(struct DoubleEndedQueue.Bytes32Deque deque, uint256 index) β†’ bytes32 value++` [.item-kind]#internal# + +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 `QueueOutOfBounds` if the index is out of bounds. + +[.contract-item] +[[DoubleEndedQueue-clear-struct-DoubleEndedQueue-Bytes32Deque-]] +==== `[.contract-item-name]#++clear++#++(struct DoubleEndedQueue.Bytes32Deque deque)++` [.item-kind]#internal# + +Resets the queue back to being empty. + +NOTE: The current items are left behind in storage. This does not affect the functioning of the queue, but misses +out on potential gas refunds. + +[.contract-item] +[[DoubleEndedQueue-length-struct-DoubleEndedQueue-Bytes32Deque-]] +==== `[.contract-item-name]#++length++#++(struct DoubleEndedQueue.Bytes32Deque deque) β†’ uint256++` [.item-kind]#internal# + +Returns the number of items in the queue. + +[.contract-item] +[[DoubleEndedQueue-empty-struct-DoubleEndedQueue-Bytes32Deque-]] +==== `[.contract-item-name]#++empty++#++(struct DoubleEndedQueue.Bytes32Deque deque) β†’ bool++` [.item-kind]#internal# + +Returns true if the queue is empty. + +[.contract-item] +[[DoubleEndedQueue-QueueEmpty--]] +==== `[.contract-item-name]#++QueueEmpty++#++()++` [.item-kind]#error# + +An operation (e.g. {front}) couldn't be completed due to the queue being empty. + +[.contract-item] +[[DoubleEndedQueue-QueueFull--]] +==== `[.contract-item-name]#++QueueFull++#++()++` [.item-kind]#error# + +A push operation couldn't be completed due to the queue being full. + +[.contract-item] +[[DoubleEndedQueue-QueueOutOfBounds--]] +==== `[.contract-item-name]#++QueueOutOfBounds++#++()++` [.item-kind]#error# + +An operation (e.g. {at}) couldn't be completed due to an index being out of bounds. + +:CheckpointUnorderedInsertion: pass:normal[xref:#Checkpoints-CheckpointUnorderedInsertion--[`++CheckpointUnorderedInsertion++`]] +:Trace224: pass:normal[xref:#Checkpoints-Trace224[`++Trace224++`]] +:Checkpoint224: pass:normal[xref:#Checkpoints-Checkpoint224[`++Checkpoint224++`]] +:push: pass:normal[xref:#Checkpoints-push-struct-Checkpoints-Trace224-uint32-uint224-[`++push++`]] +:lowerLookup: pass:normal[xref:#Checkpoints-lowerLookup-struct-Checkpoints-Trace224-uint32-[`++lowerLookup++`]] +:upperLookup: pass:normal[xref:#Checkpoints-upperLookup-struct-Checkpoints-Trace224-uint32-[`++upperLookup++`]] +:upperLookupRecent: pass:normal[xref:#Checkpoints-upperLookupRecent-struct-Checkpoints-Trace224-uint32-[`++upperLookupRecent++`]] +:latest: pass:normal[xref:#Checkpoints-latest-struct-Checkpoints-Trace224-[`++latest++`]] +:latestCheckpoint: pass:normal[xref:#Checkpoints-latestCheckpoint-struct-Checkpoints-Trace224-[`++latestCheckpoint++`]] +:length: pass:normal[xref:#Checkpoints-length-struct-Checkpoints-Trace224-[`++length++`]] +:at: pass:normal[xref:#Checkpoints-at-struct-Checkpoints-Trace224-uint32-[`++at++`]] +:Trace208: pass:normal[xref:#Checkpoints-Trace208[`++Trace208++`]] +:Checkpoint208: pass:normal[xref:#Checkpoints-Checkpoint208[`++Checkpoint208++`]] +:push: pass:normal[xref:#Checkpoints-push-struct-Checkpoints-Trace208-uint48-uint208-[`++push++`]] +:lowerLookup: pass:normal[xref:#Checkpoints-lowerLookup-struct-Checkpoints-Trace208-uint48-[`++lowerLookup++`]] +:upperLookup: pass:normal[xref:#Checkpoints-upperLookup-struct-Checkpoints-Trace208-uint48-[`++upperLookup++`]] +:upperLookupRecent: pass:normal[xref:#Checkpoints-upperLookupRecent-struct-Checkpoints-Trace208-uint48-[`++upperLookupRecent++`]] +:latest: pass:normal[xref:#Checkpoints-latest-struct-Checkpoints-Trace208-[`++latest++`]] +:latestCheckpoint: pass:normal[xref:#Checkpoints-latestCheckpoint-struct-Checkpoints-Trace208-[`++latestCheckpoint++`]] +:length: pass:normal[xref:#Checkpoints-length-struct-Checkpoints-Trace208-[`++length++`]] +:at: pass:normal[xref:#Checkpoints-at-struct-Checkpoints-Trace208-uint32-[`++at++`]] +:Trace160: pass:normal[xref:#Checkpoints-Trace160[`++Trace160++`]] +:Checkpoint160: pass:normal[xref:#Checkpoints-Checkpoint160[`++Checkpoint160++`]] +:push: pass:normal[xref:#Checkpoints-push-struct-Checkpoints-Trace160-uint96-uint160-[`++push++`]] +:lowerLookup: pass:normal[xref:#Checkpoints-lowerLookup-struct-Checkpoints-Trace160-uint96-[`++lowerLookup++`]] +:upperLookup: pass:normal[xref:#Checkpoints-upperLookup-struct-Checkpoints-Trace160-uint96-[`++upperLookup++`]] +:upperLookupRecent: pass:normal[xref:#Checkpoints-upperLookupRecent-struct-Checkpoints-Trace160-uint96-[`++upperLookupRecent++`]] +:latest: pass:normal[xref:#Checkpoints-latest-struct-Checkpoints-Trace160-[`++latest++`]] +:latestCheckpoint: pass:normal[xref:#Checkpoints-latestCheckpoint-struct-Checkpoints-Trace160-[`++latestCheckpoint++`]] +:length: pass:normal[xref:#Checkpoints-length-struct-Checkpoints-Trace160-[`++length++`]] +:at: pass:normal[xref:#Checkpoints-at-struct-Checkpoints-Trace160-uint32-[`++at++`]] + +[.contract] +[[Checkpoints]] +=== `++Checkpoints++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/utils/structs/Checkpoints.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/utils/structs/Checkpoints.sol"; +``` + +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.Trace*` in your contract, and store a new +checkpoint for the current transaction block using the {push} function. + +[.contract-index] +.Functions +-- +* {xref-Checkpoints-push-struct-Checkpoints-Trace224-uint32-uint224-}[`++push(self, key, value)++`] +* {xref-Checkpoints-lowerLookup-struct-Checkpoints-Trace224-uint32-}[`++lowerLookup(self, key)++`] +* {xref-Checkpoints-upperLookup-struct-Checkpoints-Trace224-uint32-}[`++upperLookup(self, key)++`] +* {xref-Checkpoints-upperLookupRecent-struct-Checkpoints-Trace224-uint32-}[`++upperLookupRecent(self, key)++`] +* {xref-Checkpoints-latest-struct-Checkpoints-Trace224-}[`++latest(self)++`] +* {xref-Checkpoints-latestCheckpoint-struct-Checkpoints-Trace224-}[`++latestCheckpoint(self)++`] +* {xref-Checkpoints-length-struct-Checkpoints-Trace224-}[`++length(self)++`] +* {xref-Checkpoints-at-struct-Checkpoints-Trace224-uint32-}[`++at(self, pos)++`] +* {xref-Checkpoints-push-struct-Checkpoints-Trace208-uint48-uint208-}[`++push(self, key, value)++`] +* {xref-Checkpoints-lowerLookup-struct-Checkpoints-Trace208-uint48-}[`++lowerLookup(self, key)++`] +* {xref-Checkpoints-upperLookup-struct-Checkpoints-Trace208-uint48-}[`++upperLookup(self, key)++`] +* {xref-Checkpoints-upperLookupRecent-struct-Checkpoints-Trace208-uint48-}[`++upperLookupRecent(self, key)++`] +* {xref-Checkpoints-latest-struct-Checkpoints-Trace208-}[`++latest(self)++`] +* {xref-Checkpoints-latestCheckpoint-struct-Checkpoints-Trace208-}[`++latestCheckpoint(self)++`] +* {xref-Checkpoints-length-struct-Checkpoints-Trace208-}[`++length(self)++`] +* {xref-Checkpoints-at-struct-Checkpoints-Trace208-uint32-}[`++at(self, pos)++`] +* {xref-Checkpoints-push-struct-Checkpoints-Trace160-uint96-uint160-}[`++push(self, key, value)++`] +* {xref-Checkpoints-lowerLookup-struct-Checkpoints-Trace160-uint96-}[`++lowerLookup(self, key)++`] +* {xref-Checkpoints-upperLookup-struct-Checkpoints-Trace160-uint96-}[`++upperLookup(self, key)++`] +* {xref-Checkpoints-upperLookupRecent-struct-Checkpoints-Trace160-uint96-}[`++upperLookupRecent(self, key)++`] +* {xref-Checkpoints-latest-struct-Checkpoints-Trace160-}[`++latest(self)++`] +* {xref-Checkpoints-latestCheckpoint-struct-Checkpoints-Trace160-}[`++latestCheckpoint(self)++`] +* {xref-Checkpoints-length-struct-Checkpoints-Trace160-}[`++length(self)++`] +* {xref-Checkpoints-at-struct-Checkpoints-Trace160-uint32-}[`++at(self, pos)++`] + +-- + +[.contract-index] +.Errors +-- +* {xref-Checkpoints-CheckpointUnorderedInsertion--}[`++CheckpointUnorderedInsertion()++`] + +-- + +[.contract-item] +[[Checkpoints-push-struct-Checkpoints-Trace224-uint32-uint224-]] +==== `[.contract-item-name]#++push++#++(struct Checkpoints.Trace224 self, uint32 key, uint224 value) β†’ uint224, uint224++` [.item-kind]#internal# + +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. + +[.contract-item] +[[Checkpoints-lowerLookup-struct-Checkpoints-Trace224-uint32-]] +==== `[.contract-item-name]#++lowerLookup++#++(struct Checkpoints.Trace224 self, uint32 key) β†’ uint224++` [.item-kind]#internal# + +Returns the value in the first (oldest) checkpoint with key greater or equal than the search key, or zero if +there is none. + +[.contract-item] +[[Checkpoints-upperLookup-struct-Checkpoints-Trace224-uint32-]] +==== `[.contract-item-name]#++upperLookup++#++(struct Checkpoints.Trace224 self, uint32 key) β†’ uint224++` [.item-kind]#internal# + +Returns the value in the last (most recent) checkpoint with key lower or equal than the search key, or zero +if there is none. + +[.contract-item] +[[Checkpoints-upperLookupRecent-struct-Checkpoints-Trace224-uint32-]] +==== `[.contract-item-name]#++upperLookupRecent++#++(struct Checkpoints.Trace224 self, uint32 key) β†’ uint224++` [.item-kind]#internal# + +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). + +[.contract-item] +[[Checkpoints-latest-struct-Checkpoints-Trace224-]] +==== `[.contract-item-name]#++latest++#++(struct Checkpoints.Trace224 self) β†’ uint224++` [.item-kind]#internal# + +Returns the value in the most recent checkpoint, or zero if there are no checkpoints. + +[.contract-item] +[[Checkpoints-latestCheckpoint-struct-Checkpoints-Trace224-]] +==== `[.contract-item-name]#++latestCheckpoint++#++(struct Checkpoints.Trace224 self) β†’ bool exists, uint32 _key, uint224 _value++` [.item-kind]#internal# + +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. + +[.contract-item] +[[Checkpoints-length-struct-Checkpoints-Trace224-]] +==== `[.contract-item-name]#++length++#++(struct Checkpoints.Trace224 self) β†’ uint256++` [.item-kind]#internal# + +Returns the number of checkpoint. + +[.contract-item] +[[Checkpoints-at-struct-Checkpoints-Trace224-uint32-]] +==== `[.contract-item-name]#++at++#++(struct Checkpoints.Trace224 self, uint32 pos) β†’ struct Checkpoints.Checkpoint224++` [.item-kind]#internal# + +Returns checkpoint at given position. + +[.contract-item] +[[Checkpoints-push-struct-Checkpoints-Trace208-uint48-uint208-]] +==== `[.contract-item-name]#++push++#++(struct Checkpoints.Trace208 self, uint48 key, uint208 value) β†’ uint208, uint208++` [.item-kind]#internal# + +Pushes a (`key`, `value`) pair into a Trace208 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(uint48).max` key set will disable the +library. + +[.contract-item] +[[Checkpoints-lowerLookup-struct-Checkpoints-Trace208-uint48-]] +==== `[.contract-item-name]#++lowerLookup++#++(struct Checkpoints.Trace208 self, uint48 key) β†’ uint208++` [.item-kind]#internal# + +Returns the value in the first (oldest) checkpoint with key greater or equal than the search key, or zero if +there is none. + +[.contract-item] +[[Checkpoints-upperLookup-struct-Checkpoints-Trace208-uint48-]] +==== `[.contract-item-name]#++upperLookup++#++(struct Checkpoints.Trace208 self, uint48 key) β†’ uint208++` [.item-kind]#internal# + +Returns the value in the last (most recent) checkpoint with key lower or equal than the search key, or zero +if there is none. + +[.contract-item] +[[Checkpoints-upperLookupRecent-struct-Checkpoints-Trace208-uint48-]] +==== `[.contract-item-name]#++upperLookupRecent++#++(struct Checkpoints.Trace208 self, uint48 key) β†’ uint208++` [.item-kind]#internal# + +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). + +[.contract-item] +[[Checkpoints-latest-struct-Checkpoints-Trace208-]] +==== `[.contract-item-name]#++latest++#++(struct Checkpoints.Trace208 self) β†’ uint208++` [.item-kind]#internal# + +Returns the value in the most recent checkpoint, or zero if there are no checkpoints. + +[.contract-item] +[[Checkpoints-latestCheckpoint-struct-Checkpoints-Trace208-]] +==== `[.contract-item-name]#++latestCheckpoint++#++(struct Checkpoints.Trace208 self) β†’ bool exists, uint48 _key, uint208 _value++` [.item-kind]#internal# + +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. + +[.contract-item] +[[Checkpoints-length-struct-Checkpoints-Trace208-]] +==== `[.contract-item-name]#++length++#++(struct Checkpoints.Trace208 self) β†’ uint256++` [.item-kind]#internal# + +Returns the number of checkpoint. + +[.contract-item] +[[Checkpoints-at-struct-Checkpoints-Trace208-uint32-]] +==== `[.contract-item-name]#++at++#++(struct Checkpoints.Trace208 self, uint32 pos) β†’ struct Checkpoints.Checkpoint208++` [.item-kind]#internal# + +Returns checkpoint at given position. + +[.contract-item] +[[Checkpoints-push-struct-Checkpoints-Trace160-uint96-uint160-]] +==== `[.contract-item-name]#++push++#++(struct Checkpoints.Trace160 self, uint96 key, uint160 value) β†’ uint160, uint160++` [.item-kind]#internal# + +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. + +[.contract-item] +[[Checkpoints-lowerLookup-struct-Checkpoints-Trace160-uint96-]] +==== `[.contract-item-name]#++lowerLookup++#++(struct Checkpoints.Trace160 self, uint96 key) β†’ uint160++` [.item-kind]#internal# + +Returns the value in the first (oldest) checkpoint with key greater or equal than the search key, or zero if +there is none. + +[.contract-item] +[[Checkpoints-upperLookup-struct-Checkpoints-Trace160-uint96-]] +==== `[.contract-item-name]#++upperLookup++#++(struct Checkpoints.Trace160 self, uint96 key) β†’ uint160++` [.item-kind]#internal# + +Returns the value in the last (most recent) checkpoint with key lower or equal than the search key, or zero +if there is none. + +[.contract-item] +[[Checkpoints-upperLookupRecent-struct-Checkpoints-Trace160-uint96-]] +==== `[.contract-item-name]#++upperLookupRecent++#++(struct Checkpoints.Trace160 self, uint96 key) β†’ uint160++` [.item-kind]#internal# + +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). + +[.contract-item] +[[Checkpoints-latest-struct-Checkpoints-Trace160-]] +==== `[.contract-item-name]#++latest++#++(struct Checkpoints.Trace160 self) β†’ uint160++` [.item-kind]#internal# + +Returns the value in the most recent checkpoint, or zero if there are no checkpoints. + +[.contract-item] +[[Checkpoints-latestCheckpoint-struct-Checkpoints-Trace160-]] +==== `[.contract-item-name]#++latestCheckpoint++#++(struct Checkpoints.Trace160 self) β†’ bool exists, uint96 _key, uint160 _value++` [.item-kind]#internal# + +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. + +[.contract-item] +[[Checkpoints-length-struct-Checkpoints-Trace160-]] +==== `[.contract-item-name]#++length++#++(struct Checkpoints.Trace160 self) β†’ uint256++` [.item-kind]#internal# + +Returns the number of checkpoint. + +[.contract-item] +[[Checkpoints-at-struct-Checkpoints-Trace160-uint32-]] +==== `[.contract-item-name]#++at++#++(struct Checkpoints.Trace160 self, uint32 pos) β†’ struct Checkpoints.Checkpoint160++` [.item-kind]#internal# + +Returns checkpoint at given position. + +[.contract-item] +[[Checkpoints-CheckpointUnorderedInsertion--]] +==== `[.contract-item-name]#++CheckpointUnorderedInsertion++#++()++` [.item-kind]#error# + +A value was attempted to be inserted on a past checkpoint. + +== Libraries + +:Create2InsufficientBalance: pass:normal[xref:#Create2-Create2InsufficientBalance-uint256-uint256-[`++Create2InsufficientBalance++`]] +:Create2EmptyBytecode: pass:normal[xref:#Create2-Create2EmptyBytecode--[`++Create2EmptyBytecode++`]] +:Create2FailedDeployment: pass:normal[xref:#Create2-Create2FailedDeployment--[`++Create2FailedDeployment++`]] +:deploy: pass:normal[xref:#Create2-deploy-uint256-bytes32-bytes-[`++deploy++`]] +:computeAddress: pass:normal[xref:#Create2-computeAddress-bytes32-bytes32-[`++computeAddress++`]] +:computeAddress: pass:normal[xref:#Create2-computeAddress-bytes32-bytes32-address-[`++computeAddress++`]] + +[.contract] +[[Create2]] +=== `++Create2++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/utils/Create2.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/utils/Create2.sol"; +``` + +Helper to make usage of the `CREATE2` EVM opcode easier and safer. +`CREATE2` can be used to compute in advance the address where a smart +contract will be deployed, which allows for interesting new mechanisms known +as 'counterfactual interactions'. + +See the https://eips.ethereum.org/EIPS/eip-1014#motivation[EIP] for more +information. + +[.contract-index] +.Functions +-- +* {xref-Create2-deploy-uint256-bytes32-bytes-}[`++deploy(amount, salt, bytecode)++`] +* {xref-Create2-computeAddress-bytes32-bytes32-}[`++computeAddress(salt, bytecodeHash)++`] +* {xref-Create2-computeAddress-bytes32-bytes32-address-}[`++computeAddress(salt, bytecodeHash, deployer)++`] + +-- + +[.contract-index] +.Errors +-- +* {xref-Create2-Create2InsufficientBalance-uint256-uint256-}[`++Create2InsufficientBalance(balance, needed)++`] +* {xref-Create2-Create2EmptyBytecode--}[`++Create2EmptyBytecode()++`] +* {xref-Create2-Create2FailedDeployment--}[`++Create2FailedDeployment()++`] + +-- + +[.contract-item] +[[Create2-deploy-uint256-bytes32-bytes-]] +==== `[.contract-item-name]#++deploy++#++(uint256 amount, bytes32 salt, bytes bytecode) β†’ address addr++` [.item-kind]#internal# + +Deploys a contract using `CREATE2`. The address where the contract +will be deployed can be known in advance via {computeAddress}. + +The bytecode for a contract can be obtained from Solidity with +`type(contractName).creationCode`. + +Requirements: + +- `bytecode` must not be empty. +- `salt` must have not been used for `bytecode` already. +- the factory must have a balance of at least `amount`. +- if `amount` is non-zero, `bytecode` must have a `payable` constructor. + +[.contract-item] +[[Create2-computeAddress-bytes32-bytes32-]] +==== `[.contract-item-name]#++computeAddress++#++(bytes32 salt, bytes32 bytecodeHash) β†’ address++` [.item-kind]#internal# + +Returns the address where a contract will be stored if deployed via {deploy}. Any change in the +`bytecodeHash` or `salt` will result in a new destination address. + +[.contract-item] +[[Create2-computeAddress-bytes32-bytes32-address-]] +==== `[.contract-item-name]#++computeAddress++#++(bytes32 salt, bytes32 bytecodeHash, address deployer) β†’ address addr++` [.item-kind]#internal# + +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}. + +[.contract-item] +[[Create2-Create2InsufficientBalance-uint256-uint256-]] +==== `[.contract-item-name]#++Create2InsufficientBalance++#++(uint256 balance, uint256 needed)++` [.item-kind]#error# + +Not enough balance for performing a CREATE2 deploy. + +[.contract-item] +[[Create2-Create2EmptyBytecode--]] +==== `[.contract-item-name]#++Create2EmptyBytecode++#++()++` [.item-kind]#error# + +There's no code to deploy. + +[.contract-item] +[[Create2-Create2FailedDeployment--]] +==== `[.contract-item-name]#++Create2FailedDeployment++#++()++` [.item-kind]#error# + +The deployment failed. + +:AddressInsufficientBalance: pass:normal[xref:#Address-AddressInsufficientBalance-address-[`++AddressInsufficientBalance++`]] +:AddressEmptyCode: pass:normal[xref:#Address-AddressEmptyCode-address-[`++AddressEmptyCode++`]] +:FailedInnerCall: pass:normal[xref:#Address-FailedInnerCall--[`++FailedInnerCall++`]] +:sendValue: pass:normal[xref:#Address-sendValue-address-payable-uint256-[`++sendValue++`]] +:functionCall: pass:normal[xref:#Address-functionCall-address-bytes-[`++functionCall++`]] +:functionCallWithValue: pass:normal[xref:#Address-functionCallWithValue-address-bytes-uint256-[`++functionCallWithValue++`]] +:functionStaticCall: pass:normal[xref:#Address-functionStaticCall-address-bytes-[`++functionStaticCall++`]] +:functionDelegateCall: pass:normal[xref:#Address-functionDelegateCall-address-bytes-[`++functionDelegateCall++`]] +:verifyCallResultFromTarget: pass:normal[xref:#Address-verifyCallResultFromTarget-address-bool-bytes-[`++verifyCallResultFromTarget++`]] +:verifyCallResult: pass:normal[xref:#Address-verifyCallResult-bool-bytes-[`++verifyCallResult++`]] + +[.contract] +[[Address]] +=== `++Address++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/utils/Address.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/utils/Address.sol"; +``` + +Collection of functions related to the address type + +[.contract-index] +.Functions +-- +* {xref-Address-sendValue-address-payable-uint256-}[`++sendValue(recipient, amount)++`] +* {xref-Address-functionCall-address-bytes-}[`++functionCall(target, data)++`] +* {xref-Address-functionCallWithValue-address-bytes-uint256-}[`++functionCallWithValue(target, data, value)++`] +* {xref-Address-functionStaticCall-address-bytes-}[`++functionStaticCall(target, data)++`] +* {xref-Address-functionDelegateCall-address-bytes-}[`++functionDelegateCall(target, data)++`] +* {xref-Address-verifyCallResultFromTarget-address-bool-bytes-}[`++verifyCallResultFromTarget(target, success, returndata)++`] +* {xref-Address-verifyCallResult-bool-bytes-}[`++verifyCallResult(success, returndata)++`] + +-- + +[.contract-index] +.Errors +-- +* {xref-Address-AddressInsufficientBalance-address-}[`++AddressInsufficientBalance(account)++`] +* {xref-Address-AddressEmptyCode-address-}[`++AddressEmptyCode(target)++`] +* {xref-Address-FailedInnerCall--}[`++FailedInnerCall()++`] + +-- + +[.contract-item] +[[Address-sendValue-address-payable-uint256-]] +==== `[.contract-item-name]#++sendValue++#++(address payable recipient, uint256 amount)++` [.item-kind]#internal# + +Replacement for Solidity's `transfer`: sends `amount` wei to +`recipient`, forwarding all available gas and reverting on errors. + +https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost +of certain opcodes, possibly making contracts go over the 2300 gas limit +imposed by `transfer`, making them unable to receive funds via +`transfer`. {sendValue} removes this limitation. + +https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more]. + +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.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. + +[.contract-item] +[[Address-functionCall-address-bytes-]] +==== `[.contract-item-name]#++functionCall++#++(address target, bytes data) β†’ bytes++` [.item-kind]#internal# + +Performs a Solidity function call using a low level `call`. A +plain `call` is an unsafe replacement for a function call: use this +function instead. + +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`]. + +Requirements: + +- `target` must be a contract. +- calling `target` with `data` must not revert. + +[.contract-item] +[[Address-functionCallWithValue-address-bytes-uint256-]] +==== `[.contract-item-name]#++functionCallWithValue++#++(address target, bytes data, uint256 value) β†’ bytes++` [.item-kind]#internal# + +Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], +but also transferring `value` wei to `target`. + +Requirements: + +- the calling contract must have an ETH balance of at least `value`. +- the called Solidity function must be `payable`. + +[.contract-item] +[[Address-functionStaticCall-address-bytes-]] +==== `[.contract-item-name]#++functionStaticCall++#++(address target, bytes data) β†’ bytes++` [.item-kind]#internal# + +Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], +but performing a static call. + +[.contract-item] +[[Address-functionDelegateCall-address-bytes-]] +==== `[.contract-item-name]#++functionDelegateCall++#++(address target, bytes data) β†’ bytes++` [.item-kind]#internal# + +Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], +but performing a delegate call. + +[.contract-item] +[[Address-verifyCallResultFromTarget-address-bool-bytes-]] +==== `[.contract-item-name]#++verifyCallResultFromTarget++#++(address target, bool success, bytes returndata) β†’ bytes++` [.item-kind]#internal# + +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. + +[.contract-item] +[[Address-verifyCallResult-bool-bytes-]] +==== `[.contract-item-name]#++verifyCallResult++#++(bool success, bytes returndata) β†’ bytes++` [.item-kind]#internal# + +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. + +[.contract-item] +[[Address-AddressInsufficientBalance-address-]] +==== `[.contract-item-name]#++AddressInsufficientBalance++#++(address account)++` [.item-kind]#error# + +The ETH balance of the account is not enough to perform the operation. + +[.contract-item] +[[Address-AddressEmptyCode-address-]] +==== `[.contract-item-name]#++AddressEmptyCode++#++(address target)++` [.item-kind]#error# + +There's no code at `target` (it is not a contract). + +[.contract-item] +[[Address-FailedInnerCall--]] +==== `[.contract-item-name]#++FailedInnerCall++#++()++` [.item-kind]#error# + +A call to an address target failed. The target may have reverted. + +:findUpperBound: pass:normal[xref:#Arrays-findUpperBound-uint256---uint256-[`++findUpperBound++`]] +:unsafeAccess: pass:normal[xref:#Arrays-unsafeAccess-address---uint256-[`++unsafeAccess++`]] +:unsafeAccess: pass:normal[xref:#Arrays-unsafeAccess-bytes32---uint256-[`++unsafeAccess++`]] +:unsafeAccess: pass:normal[xref:#Arrays-unsafeAccess-uint256---uint256-[`++unsafeAccess++`]] +:unsafeMemoryAccess: pass:normal[xref:#Arrays-unsafeMemoryAccess-uint256---uint256-[`++unsafeMemoryAccess++`]] +:unsafeMemoryAccess: pass:normal[xref:#Arrays-unsafeMemoryAccess-address---uint256-[`++unsafeMemoryAccess++`]] + +[.contract] +[[Arrays]] +=== `++Arrays++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/utils/Arrays.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/utils/Arrays.sol"; +``` + +Collection of functions related to array types. + +[.contract-index] +.Functions +-- +* {xref-Arrays-findUpperBound-uint256---uint256-}[`++findUpperBound(array, element)++`] +* {xref-Arrays-unsafeAccess-address---uint256-}[`++unsafeAccess(arr, pos)++`] +* {xref-Arrays-unsafeAccess-bytes32---uint256-}[`++unsafeAccess(arr, pos)++`] +* {xref-Arrays-unsafeAccess-uint256---uint256-}[`++unsafeAccess(arr, pos)++`] +* {xref-Arrays-unsafeMemoryAccess-uint256---uint256-}[`++unsafeMemoryAccess(arr, pos)++`] +* {xref-Arrays-unsafeMemoryAccess-address---uint256-}[`++unsafeMemoryAccess(arr, pos)++`] + +-- + +[.contract-item] +[[Arrays-findUpperBound-uint256---uint256-]] +==== `[.contract-item-name]#++findUpperBound++#++(uint256[] array, uint256 element) β†’ uint256++` [.item-kind]#internal# + +Searches a sorted `array` and returns the first index that contains +a value greater or equal to `element`. If no such index exists (i.e. all +values in the array are strictly less than `element`), the array length is +returned. Time complexity O(log n). + +`array` is expected to be sorted in ascending order, and to contain no +repeated elements. + +[.contract-item] +[[Arrays-unsafeAccess-address---uint256-]] +==== `[.contract-item-name]#++unsafeAccess++#++(address[] arr, uint256 pos) β†’ struct StorageSlot.AddressSlot++` [.item-kind]#internal# + +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. + +[.contract-item] +[[Arrays-unsafeAccess-bytes32---uint256-]] +==== `[.contract-item-name]#++unsafeAccess++#++(bytes32[] arr, uint256 pos) β†’ struct StorageSlot.Bytes32Slot++` [.item-kind]#internal# + +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. + +[.contract-item] +[[Arrays-unsafeAccess-uint256---uint256-]] +==== `[.contract-item-name]#++unsafeAccess++#++(uint256[] arr, uint256 pos) β†’ struct StorageSlot.Uint256Slot++` [.item-kind]#internal# + +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. + +[.contract-item] +[[Arrays-unsafeMemoryAccess-uint256---uint256-]] +==== `[.contract-item-name]#++unsafeMemoryAccess++#++(uint256[] arr, uint256 pos) β†’ uint256 res++` [.item-kind]#internal# + +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. + +[.contract-item] +[[Arrays-unsafeMemoryAccess-address---uint256-]] +==== `[.contract-item-name]#++unsafeMemoryAccess++#++(address[] arr, uint256 pos) β†’ address res++` [.item-kind]#internal# + +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. + +:_TABLE: pass:normal[xref:#Base64-_TABLE-string[`++_TABLE++`]] +:encode: pass:normal[xref:#Base64-encode-bytes-[`++encode++`]] + +[.contract] +[[Base64]] +=== `++Base64++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/utils/Base64.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/utils/Base64.sol"; +``` + +Provides a set of functions to operate with Base64 strings. + +[.contract-index] +.Functions +-- +* {xref-Base64-encode-bytes-}[`++encode(data)++`] + +-- + +[.contract-item] +[[Base64-encode-bytes-]] +==== `[.contract-item-name]#++encode++#++(bytes data) β†’ string++` [.item-kind]#internal# + +Converts a `bytes` to its Bytes64 `string` representation. + +:StringsInsufficientHexLength: pass:normal[xref:#Strings-StringsInsufficientHexLength-uint256-uint256-[`++StringsInsufficientHexLength++`]] +:toString: pass:normal[xref:#Strings-toString-uint256-[`++toString++`]] +:toStringSigned: pass:normal[xref:#Strings-toStringSigned-int256-[`++toStringSigned++`]] +:toHexString: pass:normal[xref:#Strings-toHexString-uint256-[`++toHexString++`]] +:toHexString: pass:normal[xref:#Strings-toHexString-uint256-uint256-[`++toHexString++`]] +:toHexString: pass:normal[xref:#Strings-toHexString-address-[`++toHexString++`]] +:equal: pass:normal[xref:#Strings-equal-string-string-[`++equal++`]] + +[.contract] +[[Strings]] +=== `++Strings++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/utils/Strings.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/utils/Strings.sol"; +``` + +String operations. + +[.contract-index] +.Functions +-- +* {xref-Strings-toString-uint256-}[`++toString(value)++`] +* {xref-Strings-toStringSigned-int256-}[`++toStringSigned(value)++`] +* {xref-Strings-toHexString-uint256-}[`++toHexString(value)++`] +* {xref-Strings-toHexString-uint256-uint256-}[`++toHexString(value, length)++`] +* {xref-Strings-toHexString-address-}[`++toHexString(addr)++`] +* {xref-Strings-equal-string-string-}[`++equal(a, b)++`] + +-- + +[.contract-index] +.Errors +-- +* {xref-Strings-StringsInsufficientHexLength-uint256-uint256-}[`++StringsInsufficientHexLength(value, length)++`] + +-- + +[.contract-item] +[[Strings-toString-uint256-]] +==== `[.contract-item-name]#++toString++#++(uint256 value) β†’ string++` [.item-kind]#internal# + +Converts a `uint256` to its ASCII `string` decimal representation. + +[.contract-item] +[[Strings-toStringSigned-int256-]] +==== `[.contract-item-name]#++toStringSigned++#++(int256 value) β†’ string++` [.item-kind]#internal# + +Converts a `int256` to its ASCII `string` decimal representation. + +[.contract-item] +[[Strings-toHexString-uint256-]] +==== `[.contract-item-name]#++toHexString++#++(uint256 value) β†’ string++` [.item-kind]#internal# + +Converts a `uint256` to its ASCII `string` hexadecimal representation. + +[.contract-item] +[[Strings-toHexString-uint256-uint256-]] +==== `[.contract-item-name]#++toHexString++#++(uint256 value, uint256 length) β†’ string++` [.item-kind]#internal# + +Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length. + +[.contract-item] +[[Strings-toHexString-address-]] +==== `[.contract-item-name]#++toHexString++#++(address addr) β†’ string++` [.item-kind]#internal# + +Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal +representation. + +[.contract-item] +[[Strings-equal-string-string-]] +==== `[.contract-item-name]#++equal++#++(string a, string b) β†’ bool++` [.item-kind]#internal# + +Returns true if the two strings are equal. + +[.contract-item] +[[Strings-StringsInsufficientHexLength-uint256-uint256-]] +==== `[.contract-item-name]#++StringsInsufficientHexLength++#++(uint256 value, uint256 length)++` [.item-kind]#error# + +The `value` string doesn't fit in the specified `length`. + +:StringTooLong: pass:normal[xref:#ShortStrings-StringTooLong-string-[`++StringTooLong++`]] +:InvalidShortString: pass:normal[xref:#ShortStrings-InvalidShortString--[`++InvalidShortString++`]] +:toShortString: pass:normal[xref:#ShortStrings-toShortString-string-[`++toShortString++`]] +:toString: pass:normal[xref:#ShortStrings-toString-ShortString-[`++toString++`]] +:byteLength: pass:normal[xref:#ShortStrings-byteLength-ShortString-[`++byteLength++`]] +:toShortStringWithFallback: pass:normal[xref:#ShortStrings-toShortStringWithFallback-string-string-[`++toShortStringWithFallback++`]] +:toStringWithFallback: pass:normal[xref:#ShortStrings-toStringWithFallback-ShortString-string-[`++toStringWithFallback++`]] +:byteLengthWithFallback: pass:normal[xref:#ShortStrings-byteLengthWithFallback-ShortString-string-[`++byteLengthWithFallback++`]] + +[.contract] +[[ShortStrings]] +=== `++ShortStrings++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/utils/ShortStrings.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/utils/ShortStrings.sol"; +``` + +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 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: + +```solidity +contract Named { + using ShortStrings for *; + + ShortString private immutable _name; + string private _nameFallback; + + constructor(string memory contractName) { + _name = contractName.toShortStringWithFallback(_nameFallback); + } + + function name() external view returns (string memory) { + return _name.toStringWithFallback(_nameFallback); + } +} +``` + +[.contract-index] +.Functions +-- +* {xref-ShortStrings-toShortString-string-}[`++toShortString(str)++`] +* {xref-ShortStrings-toString-ShortString-}[`++toString(sstr)++`] +* {xref-ShortStrings-byteLength-ShortString-}[`++byteLength(sstr)++`] +* {xref-ShortStrings-toShortStringWithFallback-string-string-}[`++toShortStringWithFallback(value, store)++`] +* {xref-ShortStrings-toStringWithFallback-ShortString-string-}[`++toStringWithFallback(value, store)++`] +* {xref-ShortStrings-byteLengthWithFallback-ShortString-string-}[`++byteLengthWithFallback(value, store)++`] + +-- + +[.contract-index] +.Errors +-- +* {xref-ShortStrings-StringTooLong-string-}[`++StringTooLong(str)++`] +* {xref-ShortStrings-InvalidShortString--}[`++InvalidShortString()++`] + +-- + +[.contract-item] +[[ShortStrings-toShortString-string-]] +==== `[.contract-item-name]#++toShortString++#++(string str) β†’ ShortString++` [.item-kind]#internal# + +Encode a string of at most 31 chars into a `ShortString`. + +This will trigger a `StringTooLong` error is the input string is too long. + +[.contract-item] +[[ShortStrings-toString-ShortString-]] +==== `[.contract-item-name]#++toString++#++(ShortString sstr) β†’ string++` [.item-kind]#internal# + +Decode a `ShortString` back to a "normal" string. + +[.contract-item] +[[ShortStrings-byteLength-ShortString-]] +==== `[.contract-item-name]#++byteLength++#++(ShortString sstr) β†’ uint256++` [.item-kind]#internal# + +Return the length of a `ShortString`. + +[.contract-item] +[[ShortStrings-toShortStringWithFallback-string-string-]] +==== `[.contract-item-name]#++toShortStringWithFallback++#++(string value, string store) β†’ ShortString++` [.item-kind]#internal# + +Encode a string into a `ShortString`, or write it to storage if it is too long. + +[.contract-item] +[[ShortStrings-toStringWithFallback-ShortString-string-]] +==== `[.contract-item-name]#++toStringWithFallback++#++(ShortString value, string store) β†’ string++` [.item-kind]#internal# + +Decode a string that was encoded to `ShortString` or written to storage using {setWithFallback}. + +[.contract-item] +[[ShortStrings-byteLengthWithFallback-ShortString-string-]] +==== `[.contract-item-name]#++byteLengthWithFallback++#++(ShortString value, string store) β†’ uint256++` [.item-kind]#internal# + +Return the length of a string that was encoded to `ShortString` or written to storage using +{setWithFallback}. + +WARNING: This will return the "byte length" of the string. This may not reflect the actual length in terms of +actual characters as the UTF-8 encoding of a single character can span over multiple bytes. + +[.contract-item] +[[ShortStrings-StringTooLong-string-]] +==== `[.contract-item-name]#++StringTooLong++#++(string str)++` [.item-kind]#error# + +[.contract-item] +[[ShortStrings-InvalidShortString--]] +==== `[.contract-item-name]#++InvalidShortString++#++()++` [.item-kind]#error# + +:AddressSlot: pass:normal[xref:#StorageSlot-AddressSlot[`++AddressSlot++`]] +:BooleanSlot: pass:normal[xref:#StorageSlot-BooleanSlot[`++BooleanSlot++`]] +:Bytes32Slot: pass:normal[xref:#StorageSlot-Bytes32Slot[`++Bytes32Slot++`]] +:Uint256Slot: pass:normal[xref:#StorageSlot-Uint256Slot[`++Uint256Slot++`]] +:StringSlot: pass:normal[xref:#StorageSlot-StringSlot[`++StringSlot++`]] +:BytesSlot: pass:normal[xref:#StorageSlot-BytesSlot[`++BytesSlot++`]] +:getAddressSlot: pass:normal[xref:#StorageSlot-getAddressSlot-bytes32-[`++getAddressSlot++`]] +:getBooleanSlot: pass:normal[xref:#StorageSlot-getBooleanSlot-bytes32-[`++getBooleanSlot++`]] +:getBytes32Slot: pass:normal[xref:#StorageSlot-getBytes32Slot-bytes32-[`++getBytes32Slot++`]] +:getUint256Slot: pass:normal[xref:#StorageSlot-getUint256Slot-bytes32-[`++getUint256Slot++`]] +:getStringSlot: pass:normal[xref:#StorageSlot-getStringSlot-bytes32-[`++getStringSlot++`]] +:getStringSlot: pass:normal[xref:#StorageSlot-getStringSlot-string-[`++getStringSlot++`]] +:getBytesSlot: pass:normal[xref:#StorageSlot-getBytesSlot-bytes32-[`++getBytesSlot++`]] +:getBytesSlot: pass:normal[xref:#StorageSlot-getBytesSlot-bytes-[`++getBytesSlot++`]] + +[.contract] +[[StorageSlot]] +=== `++StorageSlot++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/utils/StorageSlot.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/utils/StorageSlot.sol"; +``` + +Library for reading and writing primitive types to specific storage slots. + +Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts. +This library helps with reading and writing to such slots without the need for inline assembly. + +The functions in this library return Slot structs that contain a `value` member that can be used to read or write. + +Example usage to set ERC1967 implementation slot: +```solidity +contract ERC1967 { + bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; + + function _getImplementation() internal view returns (address) { + return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value; + } + + function _setImplementation(address newImplementation) internal { + require(newImplementation.code.length > 0); + StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation; + } +} +``` + +[.contract-index] +.Functions +-- +* {xref-StorageSlot-getAddressSlot-bytes32-}[`++getAddressSlot(slot)++`] +* {xref-StorageSlot-getBooleanSlot-bytes32-}[`++getBooleanSlot(slot)++`] +* {xref-StorageSlot-getBytes32Slot-bytes32-}[`++getBytes32Slot(slot)++`] +* {xref-StorageSlot-getUint256Slot-bytes32-}[`++getUint256Slot(slot)++`] +* {xref-StorageSlot-getStringSlot-bytes32-}[`++getStringSlot(slot)++`] +* {xref-StorageSlot-getStringSlot-string-}[`++getStringSlot(store)++`] +* {xref-StorageSlot-getBytesSlot-bytes32-}[`++getBytesSlot(slot)++`] +* {xref-StorageSlot-getBytesSlot-bytes-}[`++getBytesSlot(store)++`] + +-- + +[.contract-item] +[[StorageSlot-getAddressSlot-bytes32-]] +==== `[.contract-item-name]#++getAddressSlot++#++(bytes32 slot) β†’ struct StorageSlot.AddressSlot r++` [.item-kind]#internal# + +Returns an `AddressSlot` with member `value` located at `slot`. + +[.contract-item] +[[StorageSlot-getBooleanSlot-bytes32-]] +==== `[.contract-item-name]#++getBooleanSlot++#++(bytes32 slot) β†’ struct StorageSlot.BooleanSlot r++` [.item-kind]#internal# + +Returns an `BooleanSlot` with member `value` located at `slot`. + +[.contract-item] +[[StorageSlot-getBytes32Slot-bytes32-]] +==== `[.contract-item-name]#++getBytes32Slot++#++(bytes32 slot) β†’ struct StorageSlot.Bytes32Slot r++` [.item-kind]#internal# + +Returns an `Bytes32Slot` with member `value` located at `slot`. + +[.contract-item] +[[StorageSlot-getUint256Slot-bytes32-]] +==== `[.contract-item-name]#++getUint256Slot++#++(bytes32 slot) β†’ struct StorageSlot.Uint256Slot r++` [.item-kind]#internal# + +Returns an `Uint256Slot` with member `value` located at `slot`. + +[.contract-item] +[[StorageSlot-getStringSlot-bytes32-]] +==== `[.contract-item-name]#++getStringSlot++#++(bytes32 slot) β†’ struct StorageSlot.StringSlot r++` [.item-kind]#internal# + +Returns an `StringSlot` with member `value` located at `slot`. + +[.contract-item] +[[StorageSlot-getStringSlot-string-]] +==== `[.contract-item-name]#++getStringSlot++#++(string store) β†’ struct StorageSlot.StringSlot r++` [.item-kind]#internal# + +Returns an `StringSlot` representation of the string storage pointer `store`. + +[.contract-item] +[[StorageSlot-getBytesSlot-bytes32-]] +==== `[.contract-item-name]#++getBytesSlot++#++(bytes32 slot) β†’ struct StorageSlot.BytesSlot r++` [.item-kind]#internal# + +Returns an `BytesSlot` with member `value` located at `slot`. + +[.contract-item] +[[StorageSlot-getBytesSlot-bytes-]] +==== `[.contract-item-name]#++getBytesSlot++#++(bytes store) β†’ struct StorageSlot.BytesSlot r++` [.item-kind]#internal# + +Returns an `BytesSlot` representation of the bytes storage pointer `store`. + +:multicall: pass:normal[xref:#Multicall-multicall-bytes---[`++multicall++`]] + +[.contract] +[[Multicall]] +=== `++Multicall++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.0-rc.0/contracts/utils/Multicall.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/utils/Multicall.sol"; +``` + +Provides a function to batch together multiple calls in a single external call. + +[.contract-index] +.Functions +-- +* {xref-Multicall-multicall-bytes---}[`++multicall(data)++`] + +-- + +[.contract-item] +[[Multicall-multicall-bytes---]] +==== `[.contract-item-name]#++multicall++#++(bytes[] data) β†’ bytes[] results++` [.item-kind]#external# + +Receives and executes a batch of function calls on this contract. + diff --git a/docs/templates/contract.hbs b/docs/templates/contract.hbs new file mode 100644 index 000000000..a4716825a --- /dev/null +++ b/docs/templates/contract.hbs @@ -0,0 +1,111 @@ +{{#each items}} +:{{name}}: pass:normal[xref:#{{anchor}}[`++{{name}}++`]] +{{/each}} + +[.contract] +[[{{anchor}}]] +=== `++{{name}}++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v{{oz-version}}/{{__item_context.file.absolutePath}}[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/{{__item_context.file.absolutePath}}"; +``` + +{{{natspec.dev}}} + +{{#if modifiers}} +[.contract-index] +.Modifiers +-- +{{#each modifiers}} +* {xref-{{anchor~}} }[`++{{name}}({{names params}})++`] +{{/each}} +-- +{{/if}} + +{{#if has-functions}} +[.contract-index] +.Functions +-- +{{#each inherited-functions}} +{{#unless @first}} +[.contract-subindex-inherited] +.{{contract.name}} +{{/unless}} +{{#each functions}} +* {xref-{{anchor~}} }[`++{{name}}({{names params}})++`] +{{/each}} + +{{/each}} +-- +{{/if}} + +{{#if has-events}} +[.contract-index] +.Events +-- +{{#each inheritance}} +{{#unless @first}} +[.contract-subindex-inherited] +.{{name}} +{{/unless}} +{{#each events}} +* {xref-{{anchor~}} }[`++{{name}}({{names params}})++`] +{{/each}} + +{{/each}} +-- +{{/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}}]] +==== `[.contract-item-name]#++{{name}}++#++({{typed-params params}})++` [.item-kind]#modifier# + +{{{natspec.dev}}} + +{{/each}} + +{{#each functions}} +[.contract-item] +[[{{anchor}}]] +==== `[.contract-item-name]#++{{name}}++#++({{typed-params params}}){{#if returns}} β†’ {{typed-params returns}}{{/if}}++` [.item-kind]#{{visibility}}# + +{{{natspec.dev}}} + +{{/each}} + +{{#each events}} +[.contract-item] +[[{{anchor}}]] +==== `[.contract-item-name]#++{{name}}++#++({{typed-params params}})++` [.item-kind]#event# + +{{{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/helpers.js b/docs/templates/helpers.js new file mode 100644 index 000000000..65f168eba --- /dev/null +++ b/docs/templates/helpers.js @@ -0,0 +1,46 @@ +const { version } = require('../../package.json'); + +module.exports['oz-version'] = () => version; + +module.exports['readme-path'] = opts => { + return 'contracts/' + opts.data.root.id.replace(/\.adoc$/, '') + '/README.adoc'; +}; + +module.exports.names = params => params.map(p => p.name).join(', '); + +module.exports['typed-params'] = params => { + return params?.map(p => `${p.type}${p.indexed ? ' indexed' : ''}${p.name ? ' ' + p.name : ''}`).join(', '); +}; + +const slug = (module.exports.slug = str => { + if (str === undefined) { + throw new Error('Missing argument'); + } + return str.replace(/\W/g, '-'); +}); + +const linksCache = new WeakMap(); + +function getAllLinks(items) { + if (linksCache.has(items)) { + return linksCache.get(items); + } + const res = {}; + linksCache.set(items, res); + for (const item of items) { + res[`xref-${item.anchor}`] = `xref:${item.__item_context.page}#${item.anchor}`; + res[slug(item.fullName)] = `pass:normal[xref:${item.__item_context.page}#${item.anchor}[\`${item.fullName}\`]]`; + } + return res; +} + +module.exports['with-prelude'] = opts => { + const links = getAllLinks(opts.data.site.items); + const contents = opts.fn(); + const neededLinks = contents + .match(/\{[-._a-z0-9]+\}/gi) + .map(m => m.replace(/^\{(.+)\}$/, '$1')) + .filter(k => k in links); + const prelude = neededLinks.map(k => `:${k}: ${links[k]}`).join('\n'); + return prelude + '\n' + contents; +}; diff --git a/docs/templates/page.hbs b/docs/templates/page.hbs new file mode 100644 index 000000000..cab050acb --- /dev/null +++ b/docs/templates/page.hbs @@ -0,0 +1,4 @@ +:github-icon: pass:[] +{{#with-prelude}} +{{readme (readme-path)}} +{{/with-prelude}} diff --git a/docs/templates/properties.js b/docs/templates/properties.js new file mode 100644 index 000000000..584c0abca --- /dev/null +++ b/docs/templates/properties.js @@ -0,0 +1,49 @@ +const { isNodeType } = require('solidity-ast/utils'); +const { slug } = require('./helpers'); + +module.exports.anchor = function anchor({ item, contract }) { + let res = ''; + if (contract) { + res += contract.name + '-'; + } + res += item.name; + if ('parameters' in item) { + const signature = item.parameters.parameters.map(v => v.typeName.typeDescriptions.typeString).join(','); + res += slug('(' + signature + ')'); + } + if (isNodeType('VariableDeclaration', item)) { + res += '-' + slug(item.typeName.typeDescriptions.typeString); + } + return res; +}; + +module.exports.inheritance = function ({ item, build }) { + if (!isNodeType('ContractDefinition', item)) { + throw new Error('used inherited-items on non-contract'); + } + + return item.linearizedBaseContracts + .map(id => build.deref('ContractDefinition', id)) + .filter((c, i) => c.name !== 'Context' || i === 0); +}; + +module.exports['has-functions'] = function ({ item }) { + return item.inheritance.some(c => c.functions.length > 0); +}; + +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 ?? []))); + return inheritance.map((contract, i) => ({ + contract, + functions: contract.functions.filter(f => !baseFunctions.has(f.id) && (f.name !== 'constructor' || i === 0)), + })); +}; diff --git a/foundry.toml b/foundry.toml new file mode 100644 index 000000000..d0aa4ea39 --- /dev/null +++ b/foundry.toml @@ -0,0 +1,3 @@ +[fuzz] +runs = 10000 +max_test_rejects = 150000 diff --git a/hardhat.config.js b/hardhat.config.js new file mode 100644 index 000000000..1c87aac94 --- /dev/null +++ b/hardhat.config.js @@ -0,0 +1,115 @@ +/// ENVVAR +// - CI: output gas report to file instead of stdout +// - 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.20) +// - COINMARKETCAP: coinmarkercat api key for USD value in gas report + +const fs = require('fs'); +const path = require('path'); +const argv = require('yargs/yargs')() + .env('') + .options({ + coverage: { + type: 'boolean', + default: false, + }, + gas: { + alias: 'enableGasReport', + type: 'boolean', + default: false, + }, + gasReport: { + alias: 'enableGasReportPath', + type: 'string', + implies: 'gas', + default: undefined, + }, + mode: { + alias: 'compileMode', + type: 'string', + choices: ['production', 'development'], + default: 'development', + }, + ir: { + alias: 'enableIR', + type: 'boolean', + default: false, + }, + compiler: { + alias: 'compileVersion', + type: 'string', + default: '0.8.20', + }, + coinmarketcap: { + alias: 'coinmarketcapApiKey', + type: 'string', + }, + }).argv; + +require('@nomiclabs/hardhat-truffle5'); +require('hardhat-ignore-warnings'); +require('hardhat-exposed'); + +require('solidity-docgen'); + +for (const f of fs.readdirSync(path.join(__dirname, 'hardhat'))) { + require(path.join(__dirname, 'hardhat', f)); +} + +const withOptimizations = argv.gas || argv.compileMode === 'production'; + +/** + * @type import('hardhat/config').HardhatUserConfig + */ +module.exports = { + solidity: { + version: argv.compiler, + settings: { + optimizer: { + enabled: withOptimizations, + runs: 200, + }, + viaIR: withOptimizations && argv.ir, + outputSelection: { '*': { '*': ['storageLayout'] } }, + }, + }, + warnings: { + 'contracts-exposed/**/*': { + 'code-size': 'off', + 'initcode-size': 'off', + }, + '*': { + 'code-size': withOptimizations, + 'unused-param': !argv.coverage, // coverage causes unused-param warnings + default: 'error', + }, + }, + networks: { + hardhat: { + blockGasLimit: 10000000, + allowUnlimitedContractSize: !withOptimizations, + }, + }, + exposed: { + initializers: true, + exclude: ['vendor/**/*'], + }, + docgen: require('./docs/config'), +}; + +if (argv.gas) { + require('hardhat-gas-reporter'); + module.exports.gasReporter = { + showMethodSig: true, + currency: 'USD', + outputFile: argv.gasReport, + coinmarketcap: argv.coinmarketcap, + }; +} + +if (argv.coverage) { + require('solidity-coverage'); + module.exports.networks.hardhat.initialBaseFeePerGas = 0; +} 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/env-contract.js b/hardhat/env-contract.js new file mode 100644 index 000000000..74d54cfbb --- /dev/null +++ b/hardhat/env-contract.js @@ -0,0 +1,10 @@ +extendEnvironment(env => { + const { contract } = env; + + env.contract = function (name, body) { + // remove the default account from the accounts list used in tests, in order + // to protect tests against accidentally passing due to the contract + // deployer being used subsequently as function caller + contract(name, accounts => body(accounts.slice(1))); + }; +}); diff --git a/hardhat/ignore-unreachable-warnings.js b/hardhat/ignore-unreachable-warnings.js new file mode 100644 index 000000000..8e3e34340 --- /dev/null +++ b/hardhat/ignore-unreachable-warnings.js @@ -0,0 +1,45 @@ +// Warnings about unreachable code are emitted with a source location that corresponds to the unreachable code. +// We have some testing contracts that purposely cause unreachable code, but said code is in the library contracts, and +// with hardhat-ignore-warnings we are not able to selectively ignore them without potentially ignoring relevant +// warnings that we don't want to miss. +// Thus, we need to handle these warnings separately. We force Hardhat to compile them in a separate compilation job and +// then ignore the warnings about unreachable code that come from that compilation job. + +const { task } = require('hardhat/config'); +const { + TASK_COMPILE_SOLIDITY_GET_COMPILATION_JOB_FOR_FILE, + TASK_COMPILE_SOLIDITY_COMPILE, +} = require('hardhat/builtin-tasks/task-names'); + +const marker = Symbol('unreachable'); +const markedCache = new WeakMap(); + +task(TASK_COMPILE_SOLIDITY_GET_COMPILATION_JOB_FOR_FILE, async (params, _, runSuper) => { + const job = await runSuper(params); + // If the file is in the unreachable directory, we make a copy of the config and mark it, which will cause it to get + // compiled separately (along with the other marked files). + if (params.file.sourceName.startsWith('contracts/mocks/') && /\bunreachable\b/.test(params.file.sourceName)) { + const originalConfig = job.solidityConfig; + let markedConfig = markedCache.get(originalConfig); + if (markedConfig === undefined) { + markedConfig = { ...originalConfig, [marker]: true }; + markedCache.set(originalConfig, markedConfig); + } + job.solidityConfig = markedConfig; + } + return job; +}); + +const W_UNREACHABLE_CODE = '5740'; + +task(TASK_COMPILE_SOLIDITY_COMPILE, async (params, _, runSuper) => { + const marked = params.compilationJob.solidityConfig[marker]; + const result = await runSuper(params); + if (marked) { + result.output = { + ...result.output, + errors: result.output.errors?.filter(e => e.severity !== 'warning' || e.errorCode !== W_UNREACHABLE_CODE), + }; + } + return result; +}); diff --git a/hardhat/skip-foundry-tests.js b/hardhat/skip-foundry-tests.js new file mode 100644 index 000000000..965ba37c3 --- /dev/null +++ b/hardhat/skip-foundry-tests.js @@ -0,0 +1,6 @@ +const { subtask } = require('hardhat/config'); +const { TASK_COMPILE_SOLIDITY_GET_SOURCE_PATHS } = require('hardhat/builtin-tasks/task-names'); + +subtask(TASK_COMPILE_SOLIDITY_GET_SOURCE_PATHS).setAction(async (_, __, runSuper) => + (await runSuper()).filter(path => !path.endsWith('.t.sol')), +); diff --git a/hardhat/task-test-get-files.js b/hardhat/task-test-get-files.js new file mode 100644 index 000000000..108f40a42 --- /dev/null +++ b/hardhat/task-test-get-files.js @@ -0,0 +1,25 @@ +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 (args, hre, runSuper) => { + const path = require('path'); + const { promises: fs } = require('fs'); + + const hasProxies = await fs + .access(path.join(hre.config.paths.sources, 'proxy/Proxy.sol')) + .then(() => true) + .catch(() => false); + + 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)); +}); diff --git a/lib/erc4626-tests b/lib/erc4626-tests new file mode 160000 index 000000000..8b1d7c2ac --- /dev/null +++ b/lib/erc4626-tests @@ -0,0 +1 @@ +Subproject commit 8b1d7c2ac248c33c3506b1bff8321758943c5e11 diff --git a/lib/forge-std b/lib/forge-std new file mode 160000 index 000000000..eb980e1d4 --- /dev/null +++ b/lib/forge-std @@ -0,0 +1 @@ +Subproject commit eb980e1d4f0e8173ec27da77297ae411840c8ccb diff --git a/logo.svg b/logo.svg new file mode 100644 index 000000000..f1e14c2bb --- /dev/null +++ b/logo.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/netlify.toml b/netlify.toml new file mode 100644 index 000000000..0447f41ad --- /dev/null +++ b/netlify.toml @@ -0,0 +1,3 @@ +[build] +command = "npm run docs" +publish = "build/site" diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 000000000..ea4c6db0d --- /dev/null +++ b/package-lock.json @@ -0,0 +1,27465 @@ +{ + "name": "openzeppelin-solidity", + "version": "4.9.2", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "openzeppelin-solidity", + "version": "4.9.2", + "license": "MIT", + "devDependencies": { + "@changesets/changelog-github": "^0.4.8", + "@changesets/cli": "^2.26.0", + "@changesets/pre": "^1.0.14", + "@changesets/read": "^0.5.9", + "@nomicfoundation/hardhat-network-helpers": "^1.0.3", + "@nomiclabs/hardhat-truffle5": "^2.0.5", + "@nomiclabs/hardhat-web3": "^2.0.0", + "@openzeppelin/docs-utils": "^0.1.4", + "@openzeppelin/test-helpers": "^0.5.13", + "@openzeppelin/upgrades-core": "^1.20.6", + "array.prototype.at": "^1.1.1", + "chai": "^4.2.0", + "eslint": "^8.30.0", + "eslint-config-prettier": "^9.0.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-exposed": "^0.3.11", + "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", + "p-limit": "^3.1.0", + "prettier": "^3.0.0", + "prettier-plugin-solidity": "^1.1.0", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "solhint": "^3.3.6", + "solhint-plugin-openzeppelin": "file:scripts/solhint-custom", + "solidity-ast": "^0.4.50", + "solidity-coverage": "^0.8.0", + "solidity-docgen": "^0.6.0-beta.29", + "undici": "^5.22.1", + "web3": "^1.3.0", + "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.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.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "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.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.22.5", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/runtime": { + "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" + }, + "engines": { + "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.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.1", + "@changesets/get-version-range-type": "^0.3.2", + "@changesets/git": "^2.0.0", + "@changesets/types": "^5.2.1", + "@manypkg/get-packages": "^1.1.3", + "detect-indent": "^6.0.0", + "fs-extra": "^7.0.1", + "lodash.startcase": "^4.4.0", + "outdent": "^0.5.0", + "prettier": "^2.7.1", + "resolve-from": "^5.0.0", + "semver": "^7.5.3" + } + }, + "node_modules/@changesets/apply-release-plan/node_modules/prettier": { + "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" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/@changesets/assemble-release-plan": { + "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.6", + "@changesets/types": "^5.2.1", + "@manypkg/get-packages": "^1.1.3", + "semver": "^7.5.3" + } + }, + "node_modules/@changesets/changelog-git": { + "version": "0.1.14", + "resolved": "https://registry.npmjs.org/@changesets/changelog-git/-/changelog-git-0.1.14.tgz", + "integrity": "sha512-+vRfnKtXVWsDDxGctOfzJsPhaCdXRYoe+KyWYoq5X/GqoISREiat0l3L8B0a453B2B4dfHGcZaGyowHbp9BSaA==", + "dev": true, + "dependencies": { + "@changesets/types": "^5.2.1" + } + }, + "node_modules/@changesets/changelog-github": { + "version": "0.4.8", + "resolved": "https://registry.npmjs.org/@changesets/changelog-github/-/changelog-github-0.4.8.tgz", + "integrity": "sha512-jR1DHibkMAb5v/8ym77E4AMNWZKB5NPzw5a5Wtqm1JepAuIF+hrKp2u04NKM14oBZhHglkCfrla9uq8ORnK/dw==", + "dev": true, + "dependencies": { + "@changesets/get-github-info": "^0.5.2", + "@changesets/types": "^5.2.1", + "dotenv": "^8.1.0" + } + }, + "node_modules/@changesets/cli": { + "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.4", + "@changesets/assemble-release-plan": "^5.2.4", + "@changesets/changelog-git": "^0.1.14", + "@changesets/config": "^2.3.1", + "@changesets/errors": "^0.1.4", + "@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", + "@changesets/read": "^0.5.9", + "@changesets/types": "^5.2.1", + "@changesets/write": "^0.2.3", + "@manypkg/get-packages": "^1.1.3", + "@types/is-ci": "^3.0.0", + "@types/semver": "^7.5.0", + "ansi-colors": "^4.1.3", + "chalk": "^2.1.0", + "enquirer": "^2.3.0", + "external-editor": "^3.1.0", + "fs-extra": "^7.0.1", + "human-id": "^1.0.2", + "is-ci": "^3.0.1", + "meow": "^6.0.0", + "outdent": "^0.5.0", + "p-limit": "^2.2.0", + "preferred-pm": "^3.0.0", + "resolve-from": "^5.0.0", + "semver": "^7.5.3", + "spawndamnit": "^2.0.0", + "term-size": "^2.1.0", + "tty-table": "^4.1.5" + }, + "bin": { + "changeset": "bin.js" + } + }, + "node_modules/@changesets/cli/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/@changesets/config": { + "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.6", + "@changesets/logger": "^0.0.5", + "@changesets/types": "^5.2.1", + "@manypkg/get-packages": "^1.1.3", + "fs-extra": "^7.0.1", + "micromatch": "^4.0.2" + } + }, + "node_modules/@changesets/errors": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@changesets/errors/-/errors-0.1.4.tgz", + "integrity": "sha512-HAcqPF7snsUJ/QzkWoKfRfXushHTu+K5KZLJWPb34s4eCZShIf8BFO3fwq6KU8+G7L5KdtN2BzQAXOSXEyiY9Q==", + "dev": true, + "dependencies": { + "extendable-error": "^0.1.5" + } + }, + "node_modules/@changesets/get-dependents-graph": { + "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": "^7.5.3" + } + }, + "node_modules/@changesets/get-github-info": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@changesets/get-github-info/-/get-github-info-0.5.2.tgz", + "integrity": "sha512-JppheLu7S114aEs157fOZDjFqUDpm7eHdq5E8SSR0gUBTEK0cNSHsrSR5a66xs0z3RWuo46QvA3vawp8BxDHvg==", + "dev": true, + "dependencies": { + "dataloader": "^1.4.0", + "node-fetch": "^2.5.0" + } + }, + "node_modules/@changesets/get-release-plan": { + "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.4", + "@changesets/config": "^2.3.1", + "@changesets/pre": "^1.0.14", + "@changesets/read": "^0.5.9", + "@changesets/types": "^5.2.1", + "@manypkg/get-packages": "^1.1.3" + } + }, + "node_modules/@changesets/get-version-range-type": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@changesets/get-version-range-type/-/get-version-range-type-0.3.2.tgz", + "integrity": "sha512-SVqwYs5pULYjYT4op21F2pVbcrca4qA/bAA3FmFXKMN7Y+HcO8sbZUTx3TAy2VXulP2FACd1aC7f2nTuqSPbqg==", + "dev": true + }, + "node_modules/@changesets/git": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@changesets/git/-/git-2.0.0.tgz", + "integrity": "sha512-enUVEWbiqUTxqSnmesyJGWfzd51PY4H7mH9yUw0hPVpZBJ6tQZFMU3F3mT/t9OJ/GjyiM4770i+sehAn6ymx6A==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.20.1", + "@changesets/errors": "^0.1.4", + "@changesets/types": "^5.2.1", + "@manypkg/get-packages": "^1.1.3", + "is-subdir": "^1.1.1", + "micromatch": "^4.0.2", + "spawndamnit": "^2.0.0" + } + }, + "node_modules/@changesets/logger": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/@changesets/logger/-/logger-0.0.5.tgz", + "integrity": "sha512-gJyZHomu8nASHpaANzc6bkQMO9gU/ib20lqew1rVx753FOxffnCrJlGIeQVxNWCqM+o6OOleCo/ivL8UAO5iFw==", + "dev": true, + "dependencies": { + "chalk": "^2.1.0" + } + }, + "node_modules/@changesets/parse": { + "version": "0.3.16", + "resolved": "https://registry.npmjs.org/@changesets/parse/-/parse-0.3.16.tgz", + "integrity": "sha512-127JKNd167ayAuBjUggZBkmDS5fIKsthnr9jr6bdnuUljroiERW7FBTDNnNVyJ4l69PzR57pk6mXQdtJyBCJKg==", + "dev": true, + "dependencies": { + "@changesets/types": "^5.2.1", + "js-yaml": "^3.13.1" + } + }, + "node_modules/@changesets/pre": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/@changesets/pre/-/pre-1.0.14.tgz", + "integrity": "sha512-dTsHmxQWEQekHYHbg+M1mDVYFvegDh9j/kySNuDKdylwfMEevTeDouR7IfHNyVodxZXu17sXoJuf2D0vi55FHQ==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.20.1", + "@changesets/errors": "^0.1.4", + "@changesets/types": "^5.2.1", + "@manypkg/get-packages": "^1.1.3", + "fs-extra": "^7.0.1" + } + }, + "node_modules/@changesets/read": { + "version": "0.5.9", + "resolved": "https://registry.npmjs.org/@changesets/read/-/read-0.5.9.tgz", + "integrity": "sha512-T8BJ6JS6j1gfO1HFq50kU3qawYxa4NTbI/ASNVVCBTsKquy2HYwM9r7ZnzkiMe8IEObAJtUVGSrePCOxAK2haQ==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.20.1", + "@changesets/git": "^2.0.0", + "@changesets/logger": "^0.0.5", + "@changesets/parse": "^0.3.16", + "@changesets/types": "^5.2.1", + "chalk": "^2.1.0", + "fs-extra": "^7.0.1", + "p-filter": "^2.1.0" + } + }, + "node_modules/@changesets/types": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@changesets/types/-/types-5.2.1.tgz", + "integrity": "sha512-myLfHbVOqaq9UtUKqR/nZA/OY7xFjQMdfgfqeZIBK4d0hA6pgxArvdv8M+6NUzzBsjWLOtvApv8YHr4qM+Kpfg==", + "dev": true + }, + "node_modules/@changesets/write": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@changesets/write/-/write-0.2.3.tgz", + "integrity": "sha512-Dbamr7AIMvslKnNYsLFafaVORx4H0pvCA2MHqgtNCySMe1blImEyAEOzDmcgKAkgz4+uwoLz7demIrX+JBr/Xw==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.20.1", + "@changesets/types": "^5.2.1", + "fs-extra": "^7.0.1", + "human-id": "^1.0.2", + "prettier": "^2.7.1" + } + }, + "node_modules/@changesets/write/node_modules/prettier": { + "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" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "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, + "dependencies": { + "bech32": "^1.1.3", + "blakejs": "^1.1.0", + "bn.js": "^4.11.8", + "bs58": "^4.0.1", + "crypto-addr-codec": "^0.1.7", + "nano-base32": "^1.0.1", + "ripemd160": "^2.0.2" + } + }, + "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, + "dependencies": { + "bluebird": "^3.5.2", + "eth-ens-namehash": "^2.0.8", + "solc": "^0.4.20", + "testrpc": "0.0.1", + "web3-utils": "^1.0.0-beta.31" + } + }, + "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, + "dependencies": { + "@babel/runtime": "^7.4.4", + "@ensdomains/address-encoder": "^0.1.7", + "@ensdomains/ens": "0.4.5", + "@ensdomains/resolver": "0.2.4", + "content-hash": "^2.5.2", + "eth-ens-namehash": "^2.0.8", + "ethers": "^5.0.13", + "js-sha3": "^0.8.0" + } + }, + "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 + }, + "node_modules/@eslint-community/eslint-utils": { + "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" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "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.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.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/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/@eslint/eslintrc/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/@eslint/js": { + "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" + } + }, + "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, + "dependencies": { + "crc-32": "^1.2.0", + "ethereumjs-util": "^7.1.1" + } + }, + "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, + "dependencies": { + "@ethereumjs/common": "^2.5.0", + "ethereumjs-util": "^7.1.2" + } + }, + "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, + "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", + "@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" + } + }, + "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, + "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", + "@ethersproject/networks": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/transactions": "^5.7.0", + "@ethersproject/web": "^5.7.0" + } + }, + "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, + "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", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0" + } + }, + "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, + "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", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/rlp": "^5.7.0" + } + }, + "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, + "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" + } + }, + "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, + "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" + } + }, + "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, + "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" + } + }, + "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, + "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" + } + }, + "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, + "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" + } + }, + "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, + "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/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" + } + }, + "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, + "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", + "@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" + } + }, + "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, + "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", + "@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" + } + }, + "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, + "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", + "@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" + } + }, + "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, + "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" + } + }, + "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, + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ] + }, + "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, + "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" + } + }, + "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, + "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" + } + }, + "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, + "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" + } + }, + "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, + "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", + "@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" + } + }, + "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" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "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, + "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" + } + }, + "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, + "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" + } + }, + "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, + "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" + } + }, + "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, + "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" + } + }, + "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, + "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", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/sha2": "^5.7.0", + "@ethersproject/strings": "^5.7.0" + } + }, + "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, + "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" + } + }, + "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, + "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", + "@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" + } + }, + "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, + "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" + } + }, + "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, + "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", + "@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" + } + }, + "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, + "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", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/strings": "^5.7.0" + } + }, + "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, + "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", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/strings": "^5.7.0" + } + }, + "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, + "engines": { + "node": ">=12.x", + "pnpm": "7.5.1" + } + }, + "node_modules/@humanwhocodes/config-array": { + "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", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "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 + }, + "node_modules/@manypkg/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@manypkg/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.5.5", + "@types/node": "^12.7.1", + "find-up": "^4.1.0", + "fs-extra": "^8.1.0" + } + }, + "node_modules/@manypkg/find-root/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/@manypkg/get-packages": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@manypkg/get-packages/-/get-packages-1.1.3.tgz", + "integrity": "sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.5.5", + "@changesets/types": "^4.0.1", + "@manypkg/find-root": "^1.1.0", + "fs-extra": "^8.1.0", + "globby": "^11.0.0", + "read-yaml-file": "^1.1.0" + } + }, + "node_modules/@manypkg/get-packages/node_modules/@changesets/types": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@changesets/types/-/types-4.1.0.tgz", + "integrity": "sha512-LDQvVDv5Kb50ny2s25Fhm3d9QSZimsoUGBsUioj6MC3qbMUCuC8GPIvk/M6IvXx3lYhAs0lwWUQLb+VIEUCECw==", + "dev": true + }, + "node_modules/@manypkg/get-packages/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/@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, + "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/node": "*" + } + }, + "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.2.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.2.0.tgz", + "integrity": "sha512-FZfhjEDbT5GRswV3C6uvLPHMiVD6lQBmpoX5+eSiPaMTXte/IKqI5dykDxzZB/WBeK/CDuQRBWarPdi3FNY2zQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ] + }, + "node_modules/@noble/secp256k1": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-1.7.1.tgz", + "integrity": "sha512-hOUk6AyBFmqVrv7k5WAw/LpszxVbj9gGN4JRkIX52fdFAj1UA61KXmZDvqVEm+pOyec3+fIeZB02LYa/pWOArw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ] + }, + "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, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "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, + "engines": { + "node": ">= 8" + } + }, + "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, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nomicfoundation/ethereumjs-block": { + "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": "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": "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": "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", + "level": "^8.0.0", + "lru-cache": "^5.1.1", + "memory-level": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@nomicfoundation/ethereumjs-common": { + "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": "9.0.1", + "crc-32": "^1.2.0" + } + }, + "node_modules/@nomicfoundation/ethereumjs-ethash": { + "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": "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" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@nomicfoundation/ethereumjs-evm": { + "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": { + "@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", + "rustbn.js": "~0.2.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@nomicfoundation/ethereumjs-rlp": { + "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" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@nomicfoundation/ethereumjs-statemanager": { + "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": "4.0.1", + "@nomicfoundation/ethereumjs-rlp": "5.0.1", + "debug": "^4.3.3", + "ethereum-cryptography": "0.1.3", + "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": "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": "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" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@nomicfoundation/ethereumjs-tx": { + "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": { + "@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": { + "node": ">=14" + } + }, + "node_modules/@nomicfoundation/ethereumjs-util": { + "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": { + "@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": "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", + "mcl-wasm": "^0.7.1", + "rustbn.js": "~0.2.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@nomicfoundation/hardhat-network-helpers": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-network-helpers/-/hardhat-network-helpers-1.0.8.tgz", + "integrity": "sha512-MNqQbzUJZnCMIYvlniC3U+kcavz/PhhQSsY90tbEtUyMj/IQqsLwIRZa4ctjABh3Bz0KCh9OXUZ7Yk/d9hr45Q==", + "dev": true, + "dependencies": { + "ethereumjs-util": "^7.1.4" + }, + "peerDependencies": { + "hardhat": "^2.9.5" + } + }, + "node_modules/@nomicfoundation/solidity-analyzer": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer/-/solidity-analyzer-0.1.1.tgz", + "integrity": "sha512-1LMtXj1puAxyFusBgUIy5pZk3073cNXYnXUpuNKFghHbIit/xZgbk0AokpUADbNm3gyD6bFWl3LRFh3dhVdREg==", + "dev": true, + "engines": { + "node": ">= 12" + }, + "optionalDependencies": { + "@nomicfoundation/solidity-analyzer-darwin-arm64": "0.1.1", + "@nomicfoundation/solidity-analyzer-darwin-x64": "0.1.1", + "@nomicfoundation/solidity-analyzer-freebsd-x64": "0.1.1", + "@nomicfoundation/solidity-analyzer-linux-arm64-gnu": "0.1.1", + "@nomicfoundation/solidity-analyzer-linux-arm64-musl": "0.1.1", + "@nomicfoundation/solidity-analyzer-linux-x64-gnu": "0.1.1", + "@nomicfoundation/solidity-analyzer-linux-x64-musl": "0.1.1", + "@nomicfoundation/solidity-analyzer-win32-arm64-msvc": "0.1.1", + "@nomicfoundation/solidity-analyzer-win32-ia32-msvc": "0.1.1", + "@nomicfoundation/solidity-analyzer-win32-x64-msvc": "0.1.1" + } + }, + "node_modules/@nomicfoundation/solidity-analyzer-darwin-arm64": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-darwin-arm64/-/solidity-analyzer-darwin-arm64-0.1.1.tgz", + "integrity": "sha512-KcTodaQw8ivDZyF+D76FokN/HdpgGpfjc/gFCImdLUyqB6eSWVaZPazMbeAjmfhx3R0zm/NYVzxwAokFKgrc0w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nomicfoundation/solidity-analyzer-darwin-x64": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-darwin-x64/-/solidity-analyzer-darwin-x64-0.1.1.tgz", + "integrity": "sha512-XhQG4BaJE6cIbjAVtzGOGbK3sn1BO9W29uhk9J8y8fZF1DYz0Doj8QDMfpMu+A6TjPDs61lbsmeYodIDnfveSA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nomicfoundation/solidity-analyzer-freebsd-x64": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-freebsd-x64/-/solidity-analyzer-freebsd-x64-0.1.1.tgz", + "integrity": "sha512-GHF1VKRdHW3G8CndkwdaeLkVBi5A9u2jwtlS7SLhBc8b5U/GcoL39Q+1CSO3hYqePNP+eV5YI7Zgm0ea6kMHoA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nomicfoundation/solidity-analyzer-linux-arm64-gnu": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-arm64-gnu/-/solidity-analyzer-linux-arm64-gnu-0.1.1.tgz", + "integrity": "sha512-g4Cv2fO37ZsUENQ2vwPnZc2zRenHyAxHcyBjKcjaSmmkKrFr64yvzeNO8S3GBFCo90rfochLs99wFVGT/0owpg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nomicfoundation/solidity-analyzer-linux-arm64-musl": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-arm64-musl/-/solidity-analyzer-linux-arm64-musl-0.1.1.tgz", + "integrity": "sha512-WJ3CE5Oek25OGE3WwzK7oaopY8xMw9Lhb0mlYuJl/maZVo+WtP36XoQTb7bW/i8aAdHW5Z+BqrHMux23pvxG3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nomicfoundation/solidity-analyzer-linux-x64-gnu": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-x64-gnu/-/solidity-analyzer-linux-x64-gnu-0.1.1.tgz", + "integrity": "sha512-5WN7leSr5fkUBBjE4f3wKENUy9HQStu7HmWqbtknfXkkil+eNWiBV275IOlpXku7v3uLsXTOKpnnGHJYI2qsdA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nomicfoundation/solidity-analyzer-linux-x64-musl": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-x64-musl/-/solidity-analyzer-linux-x64-musl-0.1.1.tgz", + "integrity": "sha512-KdYMkJOq0SYPQMmErv/63CwGwMm5XHenEna9X9aB8mQmhDBrYrlAOSsIPgFCUSL0hjxE3xHP65/EPXR/InD2+w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nomicfoundation/solidity-analyzer-win32-arm64-msvc": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-win32-arm64-msvc/-/solidity-analyzer-win32-arm64-msvc-0.1.1.tgz", + "integrity": "sha512-VFZASBfl4qiBYwW5xeY20exWhmv6ww9sWu/krWSesv3q5hA0o1JuzmPHR4LPN6SUZj5vcqci0O6JOL8BPw+APg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nomicfoundation/solidity-analyzer-win32-ia32-msvc": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-win32-ia32-msvc/-/solidity-analyzer-win32-ia32-msvc-0.1.1.tgz", + "integrity": "sha512-JnFkYuyCSA70j6Si6cS1A9Gh1aHTEb8kOTBApp/c7NRTFGNMH8eaInKlyuuiIbvYFhlXW4LicqyYuWNNq9hkpQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nomicfoundation/solidity-analyzer-win32-x64-msvc": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-win32-x64-msvc/-/solidity-analyzer-win32-x64-msvc-0.1.1.tgz", + "integrity": "sha512-HrVJr6+WjIXGnw3Q9u6KQcbZCtk0caVWhCdFADySvRyUxJ8PnzlaP+MhwNE8oyT8OZ6ejHBRrrgjSqDCFXGirw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "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, + "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" + } + }, + "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, + "dependencies": { + "@types/bignumber.js": "^5.0.0" + }, + "peerDependencies": { + "hardhat": "^2.0.0", + "web3": "^1.0.0-beta.36" + } + }, + "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, + "dependencies": { + "@ensdomains/ensjs": "^2.0.1", + "@truffle/blockchain-utils": "^0.1.3", + "@truffle/contract-schema": "^3.4.7", + "@truffle/debug-utils": "^6.0.22", + "@truffle/error": "^0.1.0", + "@truffle/interface-adapter": "^0.5.16", + "bignumber.js": "^7.2.1", + "ethereum-ens": "^0.8.0", + "ethers": "^4.0.0-beta.1", + "source-map-support": "^0.5.19" + }, + "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" + } + }, + "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, + "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": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/@openzeppelin/docs-utils": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@openzeppelin/docs-utils/-/docs-utils-0.1.4.tgz", + "integrity": "sha512-2I56U1GhnNlymz0gGmJbyZKhnErGIaJ+rqtKTGyNf7YX3jgeS9/Twv8H0T+OgLV4dimtCHPz/27w6CYhWQ/TGg==", + "dev": true, + "dependencies": { + "@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" + }, + "bin": { + "oz-docs": "oz-docs.js" + } + }, + "node_modules/@openzeppelin/docs-utils/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/@openzeppelin/docs-utils/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/@openzeppelin/docs-utils/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/@openzeppelin/docs-utils/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/@openzeppelin/docs-utils/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/@openzeppelin/docs-utils/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/@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, + "dependencies": { + "@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" + } + }, + "node_modules/@openzeppelin/test-helpers/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/@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/@openzeppelin/upgrades-core": { + "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", + "chalk": "^4.1.0", + "compare-versions": "^5.0.0", + "debug": "^4.1.1", + "ethereumjs-util": "^7.0.3", + "minimist": "^1.2.7", + "proper-lockfile": "^4.1.1", + "solidity-ast": "^0.4.15" + }, + "bin": { + "openzeppelin-upgrades-core": "dist/cli/cli.js" + } + }, + "node_modules/@openzeppelin/upgrades-core/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/@openzeppelin/upgrades-core/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/@openzeppelin/upgrades-core/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/@openzeppelin/upgrades-core/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/@openzeppelin/upgrades-core/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/@openzeppelin/upgrades-core/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/@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, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ] + }, + "node_modules/@scure/bip32": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.1.5.tgz", + "integrity": "sha512-XyNh1rB0SkEqd3tXcXMi+Xe1fvg+kUIcoRIEujP1Jgv7DqW2r9lg3Ah0NkFaCs9sTkQAQA8kw7xiRXzENi9Rtw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "@noble/hashes": "~1.2.0", + "@noble/secp256k1": "~1.7.0", + "@scure/base": "~1.1.0" + } + }, + "node_modules/@scure/bip39": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.1.1.tgz", + "integrity": "sha512-t+wDck2rVkh65Hmv280fYdVdY25J9YeEUIgn2LG1WM6gxFkGzcksoDiUkWVpVp3Oex9xGC68JU2dSbUfwZ2jPg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "@noble/hashes": "~1.2.0", + "@scure/base": "~1.1.0" + } + }, + "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, + "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" + } + }, + "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, + "dependencies": { + "@sentry/types": "5.30.0", + "@sentry/utils": "5.30.0", + "tslib": "^1.9.3" + }, + "engines": { + "node": ">=6" + } + }, + "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, + "dependencies": { + "@sentry/hub": "5.30.0", + "@sentry/types": "5.30.0", + "tslib": "^1.9.3" + }, + "engines": { + "node": ">=6" + } + }, + "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, + "dependencies": { + "@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" + }, + "engines": { + "node": ">=6" + } + }, + "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, + "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" + } + }, + "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, + "engines": { + "node": ">=6" + } + }, + "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, + "dependencies": { + "@sentry/types": "5.30.0", + "tslib": "^1.9.3" + }, + "engines": { + "node": ">=6" + } + }, + "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, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "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, + "dependencies": { + "antlr4ts": "^0.5.0-alpha.4" + } + }, + "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, + "dependencies": { + "defer-to-connect": "^2.0.1" + }, + "engines": { + "node": ">=14.16" + } + }, + "node_modules/@truffle/abi-utils": { + "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", + "fast-check": "3.1.1", + "web3-utils": "1.10.0" + } + }, + "node_modules/@truffle/blockchain-utils": { + "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.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.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.5.2", + "utf8": "^3.0.0", + "web3-utils": "1.10.0" + } + }, + "node_modules/@truffle/codec/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/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/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/@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/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/@truffle/codec/node_modules/semver": { + "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" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "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.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.1", + "colors": "1.4.0" + } + }, + "node_modules/@truffle/compile-common/node_modules/@truffle/error": { + "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.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.8", + "@truffle/contract-schema": "^3.4.14", + "@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", + "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.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", + "debug": "^4.3.1" + } + }, + "node_modules/@truffle/contract/node_modules/@truffle/error": { + "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.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.17.0", + "@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.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", + "ethers": "^4.0.32", + "web3": "1.10.0" + } + }, + "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/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/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.5", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.5.tgz", + "integrity": "sha512-mEo1sAde+UCE6b2hxn332f1g1E8WfYRu6p5SvTKr2ZKC1f7gFJXk4h5PyGP9Dt6gCaG8y8XhwnXWC6Iy2cmBng==", + "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/is-ci": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/is-ci/-/is-ci-3.0.0.tgz", + "integrity": "sha512-Q0Op0hdWbYd1iahB+IFNQcWXFq4O0Q5MwQP7uN0souuQ4rPg1vEYcnIOfr1gY+M+6rc8FGoRaBO1mOOvL29sEQ==", + "dev": true, + "dependencies": { + "ci-info": "^3.1.0" + } + }, + "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/minimist": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", + "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==", + "dev": true + }, + "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/@types/normalize-package-data": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", + "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==", + "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/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", + "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/@types/semver": { + "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": { + "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" + }, + { + "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/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": "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" + }, + "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": "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/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": "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/antlr4": { + "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" + } + }, + "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-buffer-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", + "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "is-array-buffer": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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-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.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==", + "dev": true, + "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.findlast": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.2.tgz", + "integrity": "sha512-p1YDNPNqA+P6cPX9ATsxg7DKir7gOmJ+jh5dEP3LlumMNYVC1F2Jgnyh6oI3n/qD9FeIkqR2jXfd73G68ImYUQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0", + "get-intrinsic": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "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", + "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/arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "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/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": "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", + "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.12.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", + "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==", + "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/better-path-resolve": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/better-path-resolve/-/better-path-resolve-1.0.0.tgz", + "integrity": "sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==", + "dev": true, + "dependencies": { + "is-windows": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "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.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" + } + }, + "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.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "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/body-parser/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/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/breakword": { + "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" + } + }, + "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/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.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", + "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/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/camelcase-keys": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", + "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "map-obj": "^4.0.0", + "quick-lru": "^4.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/camelcase-keys/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/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", + "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": "8.1.0", + "resolved": "https://registry.npmjs.org/cbor/-/cbor-8.1.0.tgz", + "integrity": "sha512-DwGjNW9omn6EwP70aXsn7FQJx5kO12tX0bZkaTjzdVFM6/7nhA4t0EENocKGx6D2Bch9PE2KzCUf5SceBdeijg==", + "dev": true, + "dependencies": { + "nofilter": "^3.1.0" + }, + "engines": { + "node": ">=12.19" + } + }, + "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": "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/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": "3.8.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", + "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "engines": { + "node": ">=8" + } + }, + "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.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.2.2", + "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-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/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/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": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "dev": true, + "engines": { + "node": ">=0.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": "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/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/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": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/compare-versions": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-5.0.3.tgz", + "integrity": "sha512-4UZlZP8Z99MGEY+Ovg/uJxJuvoXuN4M6B3hKaiackiHrgzQFEe3diJi1mf1PNHbFujM7FvLrK2bpgIaImbtZ1A==", + "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.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "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.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "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": "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", + "js-yaml": "^4.1.0", + "parse-json": "^5.0.0", + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + } + }, + "node_modules/cosmiconfig/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/cosmiconfig/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/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-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.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.12" + } + }, + "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-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/csv": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/csv/-/csv-5.5.3.tgz", + "integrity": "sha512-QTaY0XjjhTQOdguARF0lGKm5/mEq9PD9/VhZZegHDIBq2tQwgNpHc3dneD4mGo2iJs+fTKv5Bp0fZ+BRuY3Z0g==", + "dev": true, + "dependencies": { + "csv-generate": "^3.4.3", + "csv-parse": "^4.16.3", + "csv-stringify": "^5.6.5", + "stream-transform": "^2.1.3" + }, + "engines": { + "node": ">= 0.1.90" + } + }, + "node_modules/csv-generate": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/csv-generate/-/csv-generate-3.4.3.tgz", + "integrity": "sha512-w/T+rqR0vwvHqWs/1ZyMDWtHHSJaN06klRqJXBEpDJaM/+dZkso0OKh1VcuuYvK3XM53KysVNq8Ko/epCK8wOw==", + "dev": true + }, + "node_modules/csv-parse": { + "version": "4.16.3", + "resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-4.16.3.tgz", + "integrity": "sha512-cO1I/zmz4w2dcKHVvpCr7JVRu8/FymG5OEpmvsZYlccYolPBLoVGKUHgNoc4ZGkFeFlWGEDmMyBM+TTqRdW/wg==", + "dev": true + }, + "node_modules/csv-stringify": { + "version": "5.6.5", + "resolved": "https://registry.npmjs.org/csv-stringify/-/csv-stringify-5.6.5.tgz", + "integrity": "sha512-PjiQ659aQ+fUTQqSrd1XEDnOr52jh30RBurfzkscaE2tPaFsDH5wOAHJiw8XAHphRknCwMUE9KRayc4K/NbO8A==", + "dev": true + }, + "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/dataloader": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/dataloader/-/dataloader-1.4.0.tgz", + "integrity": "sha512-68s5jYdlvasItOJnCuI2Q9s4q98g0pCyL3HrcKJu8KNugUl8ahgmZYg38ysLTgQjjXX3H8CJLkAvWrclWfcalw==", + "dev": true + }, + "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": "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/decamelize-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz", + "integrity": "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==", + "dev": true, + "dependencies": { + "decamelize": "^1.1.0", + "map-obj": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decamelize-keys/node_modules/map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "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/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dev": true, + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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.2.0", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", + "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", + "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/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": "6.1.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz", + "integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "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/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.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.3" + }, + "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/dotenv": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz", + "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "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": "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/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/entities": { + "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" + }, + "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.21.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.2.tgz", + "integrity": "sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "es-set-tostringtag": "^2.0.1", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.5", + "get-intrinsic": "^1.2.0", + "get-symbol-description": "^1.0.0", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.5", + "is-array-buffer": "^3.0.2", + "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-typed-array": "^1.1.10", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.3", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.4.3", + "safe-regex-test": "^1.0.0", + "string.prototype.trim": "^1.2.7", + "string.prototype.trimend": "^1.0.6", + "string.prototype.trimstart": "^1.0.6", + "typed-array-length": "^1.0.4", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.9" + }, + "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-set-tostringtag": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", + "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3", + "has": "^1.0.3", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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": "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/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": "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.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", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.0", + "eslint-visitor-keys": "^3.4.1", + "espree": "^9.6.0", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "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-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-prettier": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.0.0.tgz", + "integrity": "sha512-IcJsTkJae2S35pRsRAwoCE+925rJJStOdkKnLVgtE+tEpqU0EVVM7OqrwxqgptKdX29NUwC82I5pXsGFIgSevw==", + "dev": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-scope": { + "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", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "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" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "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/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/eslint/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/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/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/eslint/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/eslint/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/eslint/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/eslint/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/eslint/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/eslint/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/eslint/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/eslint/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/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/eslint/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/espree": { + "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.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "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.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "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/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/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/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/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/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/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/ethereum-cryptography": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-1.2.0.tgz", + "integrity": "sha512-6yFQC9b5ug6/17CQpCyE3k9eKBMdhyVjzUy1WkiuY/E4vj/SXDBbCw8QEIaXqf0Mf2SnY6RmpDcwlUmBSS0EJw==", + "dev": true, + "dependencies": { + "@noble/hashes": "1.2.0", + "@noble/secp256k1": "1.7.1", + "@scure/bip32": "1.1.5", + "@scure/bip39": "1.1.1" + } + }, + "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/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-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/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/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/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/express/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/express/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/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/extendable-error": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/extendable-error/-/extendable-error-0.1.7.tgz", + "integrity": "sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==", + "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.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": { + "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", + "@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.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "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/find-yarn-workspace-root2": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/find-yarn-workspace-root2/-/find-yarn-workspace-root2-1.2.16.tgz", + "integrity": "sha512-hr6hb1w8ePMpPVUK39S4RlwJzi+xPLuVuG8XlwXU3KD5Yn3qgBWVfy3AzNlDhWvE1EORCE65/Qm26rFQt3VLVA==", + "dev": true, + "dependencies": { + "micromatch": "^4.0.2", + "pkg-dir": "^4.2.0" + } + }, + "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.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": { + "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/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "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.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "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.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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.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": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "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", + "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/hard-rejection": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", + "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/hardhat": { + "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", + "@metamask/eth-sig-util": "^4.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", + "@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", + "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.14.0", + "uuid": "^8.3.2", + "ws": "^7.4.6" + }, + "bin": { + "hardhat": "internal/cli/bootstrap.js" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "ts-node": "*", + "typescript": "*" + }, + "peerDependenciesMeta": { + "ts-node": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/hardhat-exposed": { + "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", + "solidity-ast": "^0.4.25" + }, + "peerDependencies": { + "hardhat": "^2.3.0" + } + }, + "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.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", + "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.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/hardhat/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/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/ethereum-cryptography": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-1.2.0.tgz", + "integrity": "sha512-6yFQC9b5ug6/17CQpCyE3k9eKBMdhyVjzUy1WkiuY/E4vj/SXDBbCw8QEIaXqf0Mf2SnY6RmpDcwlUmBSS0EJw==", + "dev": true, + "dependencies": { + "@noble/hashes": "1.2.0", + "@noble/secp256k1": "1.7.1", + "@scure/bip32": "1.1.5", + "@scure/bip39": "1.1.1" + } + }, + "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/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/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/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": "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/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-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "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.6", + "resolved": "https://registry.npmjs.org/highlightjs-solidity/-/highlightjs-solidity-2.0.6.tgz", + "integrity": "sha512-DySXWfQghjm2l6a/flF+cteroJqD4gI8GSdL4PtvxZSsAHie8m3yVe2JFoRg03ROKT6hp2Lc/BxXkqerNmtQYg==", + "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.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", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "entities": "^4.4.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.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", + "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/http2-wrapper/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/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/human-id": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/human-id/-/human-id-1.0.2.tgz", + "integrity": "sha512-UNopramDEhHJD+VR+ehk8rOslwSfByxPIZyJRfV739NDhN5LF1fa1MqnzKm2lGTQRjNrjK19Q5fhkgIfjlVUKw==", + "dev": true + }, + "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": "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/immutable": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.0.tgz", + "integrity": "sha512-0AOCmOip+xgJwEVTQj1EfiDDOkPmuyllDuTuEX+DDXUgapLAsBIfkg3sxCYyCEA8mQqZrrxPUGjcOQ2JS3WLkg==", + "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/import-fresh/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/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/internal-slot": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", + "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.0", + "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-array-buffer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", + "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.0", + "is-typed-array": "^1.1.10" + }, + "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-ci": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", + "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", + "dev": true, + "dependencies": { + "ci-info": "^3.2.0" + }, + "bin": { + "is-ci": "bin.js" + } + }, + "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-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-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "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-subdir": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-subdir/-/is-subdir-1.2.0.tgz", + "integrity": "sha512-2AT6j+gXe/1ueqbW6fLZJiIw3F8iXGJtt0yDrZaBhAZEG1raiTxKWU+IPqMCzQAXOUCKdA4UDMgacKH25XG2Cw==", + "dev": true, + "dependencies": { + "better-path-resolve": "1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "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/is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "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-sdsl": { + "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", + "url": "https://opencollective.com/js-sdsl" + } + }, + "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-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "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/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.3", + "resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.3.tgz", + "integrity": "sha512-JZrLIAJWuZxKbCilMpNz5Vj7Vtb4scDG3dMXLOsbzBmQGyjwE61BbW7bJkfKKCShXiQZt3T6sBgALRtmd+nZaQ==", + "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/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "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/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "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/load-yaml-file": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/load-yaml-file/-/load-yaml-file-0.2.0.tgz", + "integrity": "sha512-OfCBkGEw4nN6JLtgRidPX6QxjBQGQf72q3si2uvqyFEMbycSFFHwAZeXx6cJgFM9wmLrf9zBwCP3Ivqa+LLZPw==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.5", + "js-yaml": "^3.13.0", + "pify": "^4.0.1", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "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/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/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/log-symbols/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/log-symbols/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/log-symbols/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/log-symbols/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/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/map-obj": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", + "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/meow": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/meow/-/meow-6.1.1.tgz", + "integrity": "sha512-3YffViIt2QWgTy6Pale5QpopX/IvU3LPL03jOTqp6pGj3VjesdO/U8CuHMKpnQr4shCNCM5fd5XFFvIIl6JBHg==", + "dev": true, + "dependencies": { + "@types/minimist": "^1.2.0", + "camelcase-keys": "^6.2.2", + "decamelize-keys": "^1.1.0", + "hard-rejection": "^2.1.0", + "minimist-options": "^4.0.2", + "normalize-package-data": "^2.5.0", + "read-pkg-up": "^7.0.1", + "redent": "^3.0.0", + "trim-newlines": "^3.0.0", + "type-fest": "^0.13.1", + "yargs-parser": "^18.1.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/meow/node_modules/type-fest": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", + "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/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-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/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "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.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minimist-options": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", + "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", + "dev": true, + "dependencies": { + "arrify": "^1.0.1", + "is-plain-obj": "^1.1.0", + "kind-of": "^6.0.3" + }, + "engines": { + "node": ">= 6" + } + }, + "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/mixme": { + "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" + } + }, + "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/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/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/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/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-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/mocha/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/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/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.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": { + "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/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.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" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-gyp-build": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.0.tgz", + "integrity": "sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==", + "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": "3.1.0", + "resolved": "https://registry.npmjs.org/nofilter/-/nofilter-3.1.0.tgz", + "integrity": "sha512-l2NNj07e9afPnhAhvgVrCD/oy2Ai1yfLpuo3EpiO1jFTsB4sFz6oIfAfSZyQzVpkZQ9xS8ZS5g1jCBgq4Hwo0g==", + "dev": true, + "engines": { + "node": ">=12.19" + } + }, + "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.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "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.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.2.0", + "es-abstract": "^1.21.2", + "safe-array-concat": "^1.0.0" + }, + "engines": { + "node": ">= 0.8" + }, + "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/optionator": { + "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" + }, + "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/outdent": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/outdent/-/outdent-0.5.0.tgz", + "integrity": "sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==", + "dev": true + }, + "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-filter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-filter/-/p-filter-2.1.0.tgz", + "integrity": "sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==", + "dev": true, + "dependencies": { + "p-map": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-filter/node_modules/p-map": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", + "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "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/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-locate/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-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-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": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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-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/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pluralize": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", + "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/preferred-pm": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/preferred-pm/-/preferred-pm-3.0.3.tgz", + "integrity": "sha512-+wZgbxNES/KlJs9q40F/1sfOd/j7f1O9JaHcW5Dsn3aUUOZg3L2bjpVUcKV2jvtElYfoTuQiNeMfQJ4kwUAhCQ==", + "dev": true, + "dependencies": { + "find-up": "^5.0.0", + "find-yarn-workspace-root2": "1.2.16", + "path-exists": "^4.0.0", + "which-pm": "2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/preferred-pm/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/preferred-pm/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/preferred-pm/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/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": "3.0.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz", + "integrity": "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-plugin-solidity": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/prettier-plugin-solidity/-/prettier-plugin-solidity-1.1.3.tgz", + "integrity": "sha512-fQ9yucPi2sBbA2U2Xjh6m4isUTJ7S7QLc/XDDsktqqxYfTwdYKJ0EnnywXHwCGAaYbQNK+HIYPL1OemxuMsgeg==", + "dev": true, + "dependencies": { + "@solidity-parser/parser": "^0.16.0", + "semver": "^7.3.8", + "solidity-comments-extractor": "^0.0.7" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "prettier": ">=2.3.0 || >=3.0.0-alpha.0" + } + }, + "node_modules/prettier-plugin-solidity/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/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/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/proper-lockfile": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", + "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "retry": "^0.12.0", + "signal-exit": "^3.0.2" + } + }, + "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/pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==", + "dev": true + }, + "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/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.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/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": "4.0.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", + "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "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/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.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "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": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "dev": true, + "dependencies": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "dev": true, + "dependencies": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg-up/node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg/node_modules/type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-yaml-file": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-yaml-file/-/read-yaml-file-1.1.0.tgz", + "integrity": "sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.5", + "js-yaml": "^3.6.1", + "pify": "^4.0.1", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "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/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/redent/node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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.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.2.0", + "functions-have-names": "^1.2.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/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.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/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": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "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/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "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-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/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", + "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/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.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" + }, + "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/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/slice-ansi/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/slice-ansi/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/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/smartwrap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/smartwrap/-/smartwrap-2.0.2.tgz", + "integrity": "sha512-vCsKNQxb7PnCNd2wY1WClWifAc2lwqsG8OaswpJkVJsvMGcnEntdTCDajZCkk93Ay1U3t/9puJmb525Rg5MZBA==", + "dev": true, + "dependencies": { + "array.prototype.flat": "^1.2.3", + "breakword": "^1.0.5", + "grapheme-splitter": "^1.0.4", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1", + "yargs": "^15.1.0" + }, + "bin": { + "smartwrap": "src/terminal-adapter.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/smartwrap/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/smartwrap/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/smartwrap/node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/smartwrap/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/smartwrap/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/smartwrap/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/smartwrap/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/smartwrap/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/smartwrap/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/smartwrap/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/smartwrap/node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dev": true, + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.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": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "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/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/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/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/solc/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/solc/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/solc/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/solc/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/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.4.1", + "resolved": "https://registry.npmjs.org/solhint/-/solhint-3.4.1.tgz", + "integrity": "sha512-pzZn2RlZhws1XwvLPVSsxfHrwsteFf5eySOhpAytzXwKQYbTCJV6z8EevYDiSVKMpWrvbKpEtJ055CuEmzp4Xg==", + "dev": true, + "dependencies": { + "@solidity-parser/parser": "^0.16.0", + "ajv": "^6.12.6", + "antlr4": "^4.11.0", + "ast-parents": "^0.0.1", + "chalk": "^4.1.2", + "commander": "^10.0.0", + "cosmiconfig": "^8.0.0", + "fast-diff": "^1.2.0", + "glob": "^8.0.3", + "ignore": "^5.2.4", + "js-yaml": "^4.1.0", + "lodash": "^4.17.21", + "pluralize": "^8.0.0", + "semver": "^6.3.0", + "strip-ansi": "^6.0.1", + "table": "^6.8.1", + "text-table": "^0.2.0" + }, + "bin": { + "solhint": "solhint.js" + }, + "optionalDependencies": { + "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", + "integrity": "sha512-ESipEcHyRHg4Np4SqBCfcXwyxxna1DgFVz69bgpLV8vzl/NP1DtcKsJ4dJZXWQhY/Z4J2LeKBiOkOVZn9ct33Q==", + "dev": true, + "dependencies": { + "antlr4ts": "^0.5.0-alpha.4" + } + }, + "node_modules/solhint/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/solhint/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/solhint/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/solhint/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/solhint/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/solhint/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/solhint/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/solhint/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/solhint/node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "dev": true, + "optional": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "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/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/solhint/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/solidity-ast": { + "version": "0.4.50", + "resolved": "https://registry.npmjs.org/solidity-ast/-/solidity-ast-0.4.50.tgz", + "integrity": "sha512-WpIhaUibbjcBY4bg8TO2UXFWl8PQPhtH1QtMYJUqFUGxx0rRiEFsVLV+ow8XiWEnSPeu4xPp1/K43P4esxuK1Q==", + "dev": true, + "dependencies": { + "array.prototype.findlast": "^1.2.2" + } + }, + "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.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.16.0", + "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/@solidity-parser/parser": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.16.1.tgz", + "integrity": "sha512-PdhRFNhbTtu3x8Axm0uYpqOy/lODYQK+MlYSgqIsq2L8SFYEHJPHNUiOTAJbDGzNjjr1/n9AcIayxafR/fWmYw==", + "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", + "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/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/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/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/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/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.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/solidity-coverage/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/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/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/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/mocha/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/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-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/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": "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/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.35", + "resolved": "https://registry.npmjs.org/solidity-docgen/-/solidity-docgen-0.6.0-beta.35.tgz", + "integrity": "sha512-9QdwK1THk/MWIdq1PEW/6dvtND0pUqpFTsbKwwU9YQIMYuRhH1lek9SsgnsGGYtdJ0VTrXXcVT30q20a8Y610A==", + "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/spawndamnit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/spawndamnit/-/spawndamnit-2.0.0.tgz", + "integrity": "sha512-j4JKEcncSjFlqIwU5L/rp2N5SIPsdxaRsIv678+TZxZ0SRDJTm8JrxJMjE/XuiEZNEir3S8l0Fa3Ke339WI4qA==", + "dev": true, + "dependencies": { + "cross-spawn": "^5.1.0", + "signal-exit": "^3.0.2" + } + }, + "node_modules/spawndamnit/node_modules/cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==", + "dev": true, + "dependencies": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "node_modules/spawndamnit/node_modules/lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "dependencies": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "node_modules/spawndamnit/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/spawndamnit/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/spawndamnit/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/spawndamnit/node_modules/yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", + "dev": true + }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "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.13", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.13.tgz", + "integrity": "sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w==", + "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/stream-transform": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/stream-transform/-/stream-transform-2.1.3.tgz", + "integrity": "sha512-9GHUiM5hMiCi6Y03jD2ARC1ettBXkQBoQAe7nJsPknnI0ow10aXjTnew8QtYQmLjzn974BnmWEAJgCY6ZP1DeQ==", + "dev": true, + "dependencies": { + "mixme": "^0.5.1" + } + }, + "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.trim": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz", + "integrity": "sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==", + "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/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": "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/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/swarm-js/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/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.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "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/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/term-size": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz", + "integrity": "sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/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.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "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/trim-newlines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", + "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "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/tty-table": { + "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.3", + "kleur": "^4.1.5", + "smartwrap": "^2.0.2", + "strip-ansi": "^6.0.1", + "wcwidth": "^1.0.1", + "yargs": "^17.7.1" + }, + "bin": { + "tty-table": "adapters/terminal-adapter.js" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/tty-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/tty-table/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/tty-table/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/tty-table/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/tty-table/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/tty-table/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/tty-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/tty-table/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/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/typed-array-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", + "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "is-typed-array": "^1.1.9" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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.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" + }, + "engines": { + "node": ">=14.0" + } + }, + "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/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/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dev": true, + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/web3": { + "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.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.10.0", + "resolved": "https://registry.npmjs.org/web3-bzz/-/web3-bzz-1.10.0.tgz", + "integrity": "sha512-o9IR59io3pDUsXTsps5pO5hW1D5zBmg46iNc2t4j2DkaYHNdDLwk2IP9ukoM2wg47QILfPEJYzhTfkS/CcX0KA==", + "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-core": { + "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.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.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.10.0", + "web3-utils": "1.10.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/web3-core-method": { + "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.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.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" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/web3-core-requestmanager": { + "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.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.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.10.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "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.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.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.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.10.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/web3-eth-accounts": { + "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", + "@ethereumjs/tx": "3.3.2", + "eth-lib": "0.2.8", + "ethereumjs-util": "^7.1.5", + "scrypt-js": "^3.0.1", + "uuid": "^9.0.0", + "web3-core": "1.10.0", + "web3-core-helpers": "1.10.0", + "web3-core-method": "1.10.0", + "web3-utils": "1.10.0" + }, + "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.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.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.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.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.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.10.0" + }, + "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.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.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.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.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.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.10.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/web3-providers-ipc": { + "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.10.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/web3-providers-ws": { + "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.10.0", + "websocket": "^1.0.32" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/web3-shh": { + "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.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.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", + "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.1", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", + "dev": true + }, + "node_modules/which-pm": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-pm/-/which-pm-2.0.0.tgz", + "integrity": "sha512-Lhs9Pmyph0p5n5Z3mVnN0yWcbQYUAD7rbQUiMsQxOJ3T57k7RFe35SUwWMf7dsbDZks1uOmw4AecB/JMDj3v/w==", + "dev": true, + "dependencies": { + "load-yaml-file": "^0.2.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8.15" + } + }, + "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.4", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz", + "integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==", + "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/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/wrap-ansi/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/wrap-ansi/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/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/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.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", + "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": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/yargs-parser/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/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-unparser/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/yargs-unparser/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/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/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" + } + }, + "scripts/lints": { + "version": "1.0.2", + "extraneous": true, + "license": "MIT" + }, + "scripts/solhint-custom": { + "name": "solhint-plugin-openzeppelin", + "dev": true + } + }, + "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.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.22.5" + } + }, + "@babel/helper-validator-identifier": { + "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.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.22.5", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/runtime": { + "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" + } + }, + "@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.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.1", + "@changesets/get-version-range-type": "^0.3.2", + "@changesets/git": "^2.0.0", + "@changesets/types": "^5.2.1", + "@manypkg/get-packages": "^1.1.3", + "detect-indent": "^6.0.0", + "fs-extra": "^7.0.1", + "lodash.startcase": "^4.4.0", + "outdent": "^0.5.0", + "prettier": "^2.7.1", + "resolve-from": "^5.0.0", + "semver": "^7.5.3" + }, + "dependencies": { + "prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "dev": true + } + } + }, + "@changesets/assemble-release-plan": { + "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.6", + "@changesets/types": "^5.2.1", + "@manypkg/get-packages": "^1.1.3", + "semver": "^7.5.3" + } + }, + "@changesets/changelog-git": { + "version": "0.1.14", + "resolved": "https://registry.npmjs.org/@changesets/changelog-git/-/changelog-git-0.1.14.tgz", + "integrity": "sha512-+vRfnKtXVWsDDxGctOfzJsPhaCdXRYoe+KyWYoq5X/GqoISREiat0l3L8B0a453B2B4dfHGcZaGyowHbp9BSaA==", + "dev": true, + "requires": { + "@changesets/types": "^5.2.1" + } + }, + "@changesets/changelog-github": { + "version": "0.4.8", + "resolved": "https://registry.npmjs.org/@changesets/changelog-github/-/changelog-github-0.4.8.tgz", + "integrity": "sha512-jR1DHibkMAb5v/8ym77E4AMNWZKB5NPzw5a5Wtqm1JepAuIF+hrKp2u04NKM14oBZhHglkCfrla9uq8ORnK/dw==", + "dev": true, + "requires": { + "@changesets/get-github-info": "^0.5.2", + "@changesets/types": "^5.2.1", + "dotenv": "^8.1.0" + } + }, + "@changesets/cli": { + "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.4", + "@changesets/assemble-release-plan": "^5.2.4", + "@changesets/changelog-git": "^0.1.14", + "@changesets/config": "^2.3.1", + "@changesets/errors": "^0.1.4", + "@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", + "@changesets/read": "^0.5.9", + "@changesets/types": "^5.2.1", + "@changesets/write": "^0.2.3", + "@manypkg/get-packages": "^1.1.3", + "@types/is-ci": "^3.0.0", + "@types/semver": "^7.5.0", + "ansi-colors": "^4.1.3", + "chalk": "^2.1.0", + "enquirer": "^2.3.0", + "external-editor": "^3.1.0", + "fs-extra": "^7.0.1", + "human-id": "^1.0.2", + "is-ci": "^3.0.1", + "meow": "^6.0.0", + "outdent": "^0.5.0", + "p-limit": "^2.2.0", + "preferred-pm": "^3.0.0", + "resolve-from": "^5.0.0", + "semver": "^7.5.3", + "spawndamnit": "^2.0.0", + "term-size": "^2.1.0", + "tty-table": "^4.1.5" + }, + "dependencies": { + "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, + "requires": { + "p-try": "^2.0.0" + } + } + } + }, + "@changesets/config": { + "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.6", + "@changesets/logger": "^0.0.5", + "@changesets/types": "^5.2.1", + "@manypkg/get-packages": "^1.1.3", + "fs-extra": "^7.0.1", + "micromatch": "^4.0.2" + } + }, + "@changesets/errors": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@changesets/errors/-/errors-0.1.4.tgz", + "integrity": "sha512-HAcqPF7snsUJ/QzkWoKfRfXushHTu+K5KZLJWPb34s4eCZShIf8BFO3fwq6KU8+G7L5KdtN2BzQAXOSXEyiY9Q==", + "dev": true, + "requires": { + "extendable-error": "^0.1.5" + } + }, + "@changesets/get-dependents-graph": { + "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": "^7.5.3" + } + }, + "@changesets/get-github-info": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@changesets/get-github-info/-/get-github-info-0.5.2.tgz", + "integrity": "sha512-JppheLu7S114aEs157fOZDjFqUDpm7eHdq5E8SSR0gUBTEK0cNSHsrSR5a66xs0z3RWuo46QvA3vawp8BxDHvg==", + "dev": true, + "requires": { + "dataloader": "^1.4.0", + "node-fetch": "^2.5.0" + } + }, + "@changesets/get-release-plan": { + "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.4", + "@changesets/config": "^2.3.1", + "@changesets/pre": "^1.0.14", + "@changesets/read": "^0.5.9", + "@changesets/types": "^5.2.1", + "@manypkg/get-packages": "^1.1.3" + } + }, + "@changesets/get-version-range-type": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@changesets/get-version-range-type/-/get-version-range-type-0.3.2.tgz", + "integrity": "sha512-SVqwYs5pULYjYT4op21F2pVbcrca4qA/bAA3FmFXKMN7Y+HcO8sbZUTx3TAy2VXulP2FACd1aC7f2nTuqSPbqg==", + "dev": true + }, + "@changesets/git": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@changesets/git/-/git-2.0.0.tgz", + "integrity": "sha512-enUVEWbiqUTxqSnmesyJGWfzd51PY4H7mH9yUw0hPVpZBJ6tQZFMU3F3mT/t9OJ/GjyiM4770i+sehAn6ymx6A==", + "dev": true, + "requires": { + "@babel/runtime": "^7.20.1", + "@changesets/errors": "^0.1.4", + "@changesets/types": "^5.2.1", + "@manypkg/get-packages": "^1.1.3", + "is-subdir": "^1.1.1", + "micromatch": "^4.0.2", + "spawndamnit": "^2.0.0" + } + }, + "@changesets/logger": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/@changesets/logger/-/logger-0.0.5.tgz", + "integrity": "sha512-gJyZHomu8nASHpaANzc6bkQMO9gU/ib20lqew1rVx753FOxffnCrJlGIeQVxNWCqM+o6OOleCo/ivL8UAO5iFw==", + "dev": true, + "requires": { + "chalk": "^2.1.0" + } + }, + "@changesets/parse": { + "version": "0.3.16", + "resolved": "https://registry.npmjs.org/@changesets/parse/-/parse-0.3.16.tgz", + "integrity": "sha512-127JKNd167ayAuBjUggZBkmDS5fIKsthnr9jr6bdnuUljroiERW7FBTDNnNVyJ4l69PzR57pk6mXQdtJyBCJKg==", + "dev": true, + "requires": { + "@changesets/types": "^5.2.1", + "js-yaml": "^3.13.1" + } + }, + "@changesets/pre": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/@changesets/pre/-/pre-1.0.14.tgz", + "integrity": "sha512-dTsHmxQWEQekHYHbg+M1mDVYFvegDh9j/kySNuDKdylwfMEevTeDouR7IfHNyVodxZXu17sXoJuf2D0vi55FHQ==", + "dev": true, + "requires": { + "@babel/runtime": "^7.20.1", + "@changesets/errors": "^0.1.4", + "@changesets/types": "^5.2.1", + "@manypkg/get-packages": "^1.1.3", + "fs-extra": "^7.0.1" + } + }, + "@changesets/read": { + "version": "0.5.9", + "resolved": "https://registry.npmjs.org/@changesets/read/-/read-0.5.9.tgz", + "integrity": "sha512-T8BJ6JS6j1gfO1HFq50kU3qawYxa4NTbI/ASNVVCBTsKquy2HYwM9r7ZnzkiMe8IEObAJtUVGSrePCOxAK2haQ==", + "dev": true, + "requires": { + "@babel/runtime": "^7.20.1", + "@changesets/git": "^2.0.0", + "@changesets/logger": "^0.0.5", + "@changesets/parse": "^0.3.16", + "@changesets/types": "^5.2.1", + "chalk": "^2.1.0", + "fs-extra": "^7.0.1", + "p-filter": "^2.1.0" + } + }, + "@changesets/types": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@changesets/types/-/types-5.2.1.tgz", + "integrity": "sha512-myLfHbVOqaq9UtUKqR/nZA/OY7xFjQMdfgfqeZIBK4d0hA6pgxArvdv8M+6NUzzBsjWLOtvApv8YHr4qM+Kpfg==", + "dev": true + }, + "@changesets/write": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@changesets/write/-/write-0.2.3.tgz", + "integrity": "sha512-Dbamr7AIMvslKnNYsLFafaVORx4H0pvCA2MHqgtNCySMe1blImEyAEOzDmcgKAkgz4+uwoLz7demIrX+JBr/Xw==", + "dev": true, + "requires": { + "@babel/runtime": "^7.20.1", + "@changesets/types": "^5.2.1", + "fs-extra": "^7.0.1", + "human-id": "^1.0.2", + "prettier": "^2.7.1" + }, + "dependencies": { + "prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "dev": true + } + } + }, + "@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-community/eslint-utils": { + "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.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.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.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "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 + }, + "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, + "requires": { + "argparse": "^2.0.1" + } + } + } + }, + "@eslint/js": { + "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": { + "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": {} + } + } + }, + "@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.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", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + } + }, + "@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true + }, + "@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 + }, + "@manypkg/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@manypkg/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==", + "dev": true, + "requires": { + "@babel/runtime": "^7.5.5", + "@types/node": "^12.7.1", + "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": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + } + } + }, + "@manypkg/get-packages": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@manypkg/get-packages/-/get-packages-1.1.3.tgz", + "integrity": "sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==", + "dev": true, + "requires": { + "@babel/runtime": "^7.5.5", + "@changesets/types": "^4.0.1", + "@manypkg/find-root": "^1.1.0", + "fs-extra": "^8.1.0", + "globby": "^11.0.0", + "read-yaml-file": "^1.1.0" + }, + "dependencies": { + "@changesets/types": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@changesets/types/-/types-4.1.0.tgz", + "integrity": "sha512-LDQvVDv5Kb50ny2s25Fhm3d9QSZimsoUGBsUioj6MC3qbMUCuC8GPIvk/M6IvXx3lYhAs0lwWUQLb+VIEUCECw==", + "dev": true + }, + "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" + } + } + } + }, + "@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": { + "@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" + } + } + } + }, + "@noble/hashes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.2.0.tgz", + "integrity": "sha512-FZfhjEDbT5GRswV3C6uvLPHMiVD6lQBmpoX5+eSiPaMTXte/IKqI5dykDxzZB/WBeK/CDuQRBWarPdi3FNY2zQ==", + "dev": true + }, + "@noble/secp256k1": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-1.7.1.tgz", + "integrity": "sha512-hOUk6AyBFmqVrv7k5WAw/LpszxVbj9gGN4JRkIX52fdFAj1UA61KXmZDvqVEm+pOyec3+fIeZB02LYa/pWOArw==", + "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": "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": "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": "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": "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", + "level": "^8.0.0", + "lru-cache": "^5.1.1", + "memory-level": "^1.0.0" + } + }, + "@nomicfoundation/ethereumjs-common": { + "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": "9.0.1", + "crc-32": "^1.2.0" + } + }, + "@nomicfoundation/ethereumjs-ethash": { + "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": "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": "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": { + "@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", + "rustbn.js": "~0.2.0" + } + }, + "@nomicfoundation/ethereumjs-rlp": { + "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": "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": "4.0.1", + "@nomicfoundation/ethereumjs-rlp": "5.0.1", + "debug": "^4.3.3", + "ethereum-cryptography": "0.1.3", + "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": "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": "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": "5.0.1", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-tx/-/ethereumjs-tx-5.0.1.tgz", + "integrity": "sha512-0HwxUF2u2hrsIM1fsasjXvlbDOq1ZHFV2dd1yGq8CA+MEYhaxZr8OTScpVkkxqMwBcc5y83FyPl0J9MZn3kY0w==", + "dev": true, + "requires": { + "@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": "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": { + "@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": "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", + "mcl-wasm": "^0.7.1", + "rustbn.js": "~0.2.0" + } + }, + "@nomicfoundation/hardhat-network-helpers": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-network-helpers/-/hardhat-network-helpers-1.0.8.tgz", + "integrity": "sha512-MNqQbzUJZnCMIYvlniC3U+kcavz/PhhQSsY90tbEtUyMj/IQqsLwIRZa4ctjABh3Bz0KCh9OXUZ7Yk/d9hr45Q==", + "dev": true, + "requires": { + "ethereumjs-util": "^7.1.4" + } + }, + "@nomicfoundation/solidity-analyzer": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer/-/solidity-analyzer-0.1.1.tgz", + "integrity": "sha512-1LMtXj1puAxyFusBgUIy5pZk3073cNXYnXUpuNKFghHbIit/xZgbk0AokpUADbNm3gyD6bFWl3LRFh3dhVdREg==", + "dev": true, + "requires": { + "@nomicfoundation/solidity-analyzer-darwin-arm64": "0.1.1", + "@nomicfoundation/solidity-analyzer-darwin-x64": "0.1.1", + "@nomicfoundation/solidity-analyzer-freebsd-x64": "0.1.1", + "@nomicfoundation/solidity-analyzer-linux-arm64-gnu": "0.1.1", + "@nomicfoundation/solidity-analyzer-linux-arm64-musl": "0.1.1", + "@nomicfoundation/solidity-analyzer-linux-x64-gnu": "0.1.1", + "@nomicfoundation/solidity-analyzer-linux-x64-musl": "0.1.1", + "@nomicfoundation/solidity-analyzer-win32-arm64-msvc": "0.1.1", + "@nomicfoundation/solidity-analyzer-win32-ia32-msvc": "0.1.1", + "@nomicfoundation/solidity-analyzer-win32-x64-msvc": "0.1.1" + } + }, + "@nomicfoundation/solidity-analyzer-darwin-arm64": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-darwin-arm64/-/solidity-analyzer-darwin-arm64-0.1.1.tgz", + "integrity": "sha512-KcTodaQw8ivDZyF+D76FokN/HdpgGpfjc/gFCImdLUyqB6eSWVaZPazMbeAjmfhx3R0zm/NYVzxwAokFKgrc0w==", + "dev": true, + "optional": true + }, + "@nomicfoundation/solidity-analyzer-darwin-x64": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-darwin-x64/-/solidity-analyzer-darwin-x64-0.1.1.tgz", + "integrity": "sha512-XhQG4BaJE6cIbjAVtzGOGbK3sn1BO9W29uhk9J8y8fZF1DYz0Doj8QDMfpMu+A6TjPDs61lbsmeYodIDnfveSA==", + "dev": true, + "optional": true + }, + "@nomicfoundation/solidity-analyzer-freebsd-x64": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-freebsd-x64/-/solidity-analyzer-freebsd-x64-0.1.1.tgz", + "integrity": "sha512-GHF1VKRdHW3G8CndkwdaeLkVBi5A9u2jwtlS7SLhBc8b5U/GcoL39Q+1CSO3hYqePNP+eV5YI7Zgm0ea6kMHoA==", + "dev": true, + "optional": true + }, + "@nomicfoundation/solidity-analyzer-linux-arm64-gnu": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-arm64-gnu/-/solidity-analyzer-linux-arm64-gnu-0.1.1.tgz", + "integrity": "sha512-g4Cv2fO37ZsUENQ2vwPnZc2zRenHyAxHcyBjKcjaSmmkKrFr64yvzeNO8S3GBFCo90rfochLs99wFVGT/0owpg==", + "dev": true, + "optional": true + }, + "@nomicfoundation/solidity-analyzer-linux-arm64-musl": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-arm64-musl/-/solidity-analyzer-linux-arm64-musl-0.1.1.tgz", + "integrity": "sha512-WJ3CE5Oek25OGE3WwzK7oaopY8xMw9Lhb0mlYuJl/maZVo+WtP36XoQTb7bW/i8aAdHW5Z+BqrHMux23pvxG3w==", + "dev": true, + "optional": true + }, + "@nomicfoundation/solidity-analyzer-linux-x64-gnu": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-x64-gnu/-/solidity-analyzer-linux-x64-gnu-0.1.1.tgz", + "integrity": "sha512-5WN7leSr5fkUBBjE4f3wKENUy9HQStu7HmWqbtknfXkkil+eNWiBV275IOlpXku7v3uLsXTOKpnnGHJYI2qsdA==", + "dev": true, + "optional": true + }, + "@nomicfoundation/solidity-analyzer-linux-x64-musl": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-x64-musl/-/solidity-analyzer-linux-x64-musl-0.1.1.tgz", + "integrity": "sha512-KdYMkJOq0SYPQMmErv/63CwGwMm5XHenEna9X9aB8mQmhDBrYrlAOSsIPgFCUSL0hjxE3xHP65/EPXR/InD2+w==", + "dev": true, + "optional": true + }, + "@nomicfoundation/solidity-analyzer-win32-arm64-msvc": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-win32-arm64-msvc/-/solidity-analyzer-win32-arm64-msvc-0.1.1.tgz", + "integrity": "sha512-VFZASBfl4qiBYwW5xeY20exWhmv6ww9sWu/krWSesv3q5hA0o1JuzmPHR4LPN6SUZj5vcqci0O6JOL8BPw+APg==", + "dev": true, + "optional": true + }, + "@nomicfoundation/solidity-analyzer-win32-ia32-msvc": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-win32-ia32-msvc/-/solidity-analyzer-win32-ia32-msvc-0.1.1.tgz", + "integrity": "sha512-JnFkYuyCSA70j6Si6cS1A9Gh1aHTEb8kOTBApp/c7NRTFGNMH8eaInKlyuuiIbvYFhlXW4LicqyYuWNNq9hkpQ==", + "dev": true, + "optional": true + }, + "@nomicfoundation/solidity-analyzer-win32-x64-msvc": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-win32-x64-msvc/-/solidity-analyzer-win32-x64-msvc-0.1.1.tgz", + "integrity": "sha512-HrVJr6+WjIXGnw3Q9u6KQcbZCtk0caVWhCdFADySvRyUxJ8PnzlaP+MhwNE8oyT8OZ6ejHBRrrgjSqDCFXGirw==", + "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": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + } + } + }, + "@openzeppelin/docs-utils": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@openzeppelin/docs-utils/-/docs-utils-0.1.4.tgz", + "integrity": "sha512-2I56U1GhnNlymz0gGmJbyZKhnErGIaJ+rqtKTGyNf7YX3jgeS9/Twv8H0T+OgLV4dimtCHPz/27w6CYhWQ/TGg==", + "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" + }, + "dependencies": { + "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, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "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, + "requires": { + "color-name": "~1.1.4" + } + }, + "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 + }, + "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 + }, + "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, + "requires": { + "has-flag": "^4.0.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": { + "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 + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "@openzeppelin/upgrades-core": { + "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", + "chalk": "^4.1.0", + "compare-versions": "^5.0.0", + "debug": "^4.1.1", + "ethereumjs-util": "^7.0.3", + "minimist": "^1.2.7", + "proper-lockfile": "^4.1.1", + "solidity-ast": "^0.4.15" + }, + "dependencies": { + "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, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "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, + "requires": { + "color-name": "~1.1.4" + } + }, + "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 + }, + "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 + }, + "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, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@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.5", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.1.5.tgz", + "integrity": "sha512-XyNh1rB0SkEqd3tXcXMi+Xe1fvg+kUIcoRIEujP1Jgv7DqW2r9lg3Ah0NkFaCs9sTkQAQA8kw7xiRXzENi9Rtw==", + "dev": true, + "requires": { + "@noble/hashes": "~1.2.0", + "@noble/secp256k1": "~1.7.0", + "@scure/base": "~1.1.0" + } + }, + "@scure/bip39": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.1.1.tgz", + "integrity": "sha512-t+wDck2rVkh65Hmv280fYdVdY25J9YeEUIgn2LG1WM6gxFkGzcksoDiUkWVpVp3Oex9xGC68JU2dSbUfwZ2jPg==", + "dev": true, + "requires": { + "@noble/hashes": "~1.2.0", + "@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": "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", + "fast-check": "3.1.1", + "web3-utils": "1.10.0" + } + }, + "@truffle/blockchain-utils": { + "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.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.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.5.2", + "utf8": "^3.0.0", + "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.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" + } + }, + "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.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.1", + "colors": "1.4.0" + }, + "dependencies": { + "@truffle/error": { + "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.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.8", + "@truffle/contract-schema": "^3.4.14", + "@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", + "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.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-schema": { + "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", + "debug": "^4.3.1" + } + }, + "@truffle/debug-utils": { + "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.17.0", + "@trufflesuite/chromafi": "^3.0.0", + "bn.js": "^5.1.3", + "chalk": "^2.4.2", + "debug": "^4.3.1", + "highlightjs-solidity": "^2.0.6" + }, + "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 + } + } + }, + "@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.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", + "ethers": "^4.0.32", + "web3": "1.10.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 + } + } + }, + "@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, + "requires": { + "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" + }, + "dependencies": { + "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 + } + } + }, + "@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==", + "dev": true, + "requires": { + "bignumber.js": "*" + } + }, + "@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, + "requires": { + "@types/node": "*" + } + }, + "@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, + "requires": { + "@types/http-cache-semantics": "*", + "@types/keyv": "^3.1.4", + "@types/node": "*", + "@types/responselike": "^1.0.0" + } + }, + "@types/chai": { + "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": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@types/concat-stream/-/concat-stream-1.6.1.tgz", + "integrity": "sha512-eHE4cQPoj6ngxBZMvVf6Hw7Mh4jMW4U9lpGmS5GBPB9RYxlFg+CHaVN7ErNY4W9XfLIEn20b4VDYaIrbq0q4uA==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@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, + "requires": { + "@types/node": "*" + } + }, + "@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, + "requires": { + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "@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 + }, + "@types/is-ci": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/is-ci/-/is-ci-3.0.0.tgz", + "integrity": "sha512-Q0Op0hdWbYd1iahB+IFNQcWXFq4O0Q5MwQP7uN0souuQ4rPg1vEYcnIOfr1gY+M+6rc8FGoRaBO1mOOvL29sEQ==", + "dev": true, + "requires": { + "ci-info": "^3.1.0" + } + }, + "@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, + "requires": { + "@types/node": "*" + } + }, + "@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 + }, + "@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 + }, + "@types/minimist": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", + "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==", + "dev": true + }, + "@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 + }, + "@types/normalize-package-data": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", + "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==", + "dev": true + }, + "@types/pbkdf2": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/pbkdf2/-/pbkdf2-3.1.0.tgz", + "integrity": "sha512-Cf63Rv7jCQ0LaL8tNXmEyqTHuIJxRdlS5vMh1mj5voN4+QFhVZnlZruezqpWYDiJ8UTzhP0VmeLXCmBk66YrMQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@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 + }, + "@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", + "integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@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, + "requires": { + "@types/node": "*" + } + }, + "@types/semver": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==", + "dev": true + }, + "abbrev": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", + "integrity": "sha512-LEyx4aLEC3x6T0UguF6YILf+ntvmOaWsVfENmIW0E9H09vKlLDGelMjjSm0jkDHALj8A8quZ/HapKNigzwge+Q==", + "dev": true + }, + "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, + "requires": { + "event-target-shim": "^5.0.0" + } + }, + "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 + }, + "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, + "requires": { + "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" + }, + "dependencies": { + "buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + } + } + }, + "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, + "requires": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + } + }, + "acorn": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", + "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", + "dev": true + }, + "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, + "requires": {} + }, + "address": { + "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": { + "version": "0.4.16", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.16.tgz", + "integrity": "sha512-TFi4HBKSGfIKsK5YCkKaaFG2m4PEDyViZmEwof3MTIgzimHLto6muaHVpbrljdIvIrFZzEq/p4nafOeLcYegrg==", + "dev": true + }, + "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 + }, + "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, + "requires": { + "debug": "4" + } + }, + "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, + "requires": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + } + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "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 + }, + "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 + }, + "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, + "requires": { + "type-fest": "^0.21.3" + }, + "dependencies": { + "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 + } + } + }, + "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 + }, + "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" + } + }, + "antlr4": { + "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": { + "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 + }, + "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, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "array-buffer-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", + "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "is-array-buffer": "^3.0.1" + } + }, + "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 + }, + "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 + }, + "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 + }, + "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==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" + } + }, + "array.prototype.findlast": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.2.tgz", + "integrity": "sha512-p1YDNPNqA+P6cPX9ATsxg7DKir7gOmJ+jh5dEP3LlumMNYVC1F2Jgnyh6oI3n/qD9FeIkqR2jXfd73G68ImYUQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0", + "get-intrinsic": "^1.1.3" + } + }, + "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, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" + } + }, + "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, + "requires": { + "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" + } + }, + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", + "dev": true + }, + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "dev": true + }, + "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, + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "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 + }, + "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 + }, + "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 + }, + "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 + }, + "async": { + "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", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true + }, + "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 + }, + "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 + }, + "aws4": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", + "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==", + "dev": true + }, + "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 + }, + "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, + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "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 + }, + "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, + "requires": { + "tweetnacl": "^0.14.3" + }, + "dependencies": { + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", + "dev": true + } + } + }, + "bech32": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz", + "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==", + "dev": true + }, + "better-path-resolve": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/better-path-resolve/-/better-path-resolve-1.0.0.tgz", + "integrity": "sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==", + "dev": true, + "requires": { + "is-windows": "^1.0.0" + } + }, + "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 + }, + "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 + }, + "bigint-crypto-utils": { + "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": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-7.2.1.tgz", + "integrity": "sha512-S4XzBk5sMB+Rcb/LNcpzXr57VRTxgAvaAEDAl1AwRx27j00hT84O6OkteE7u8UB3NuaaygCRrEpqox4uDOrbdQ==", + "dev": true + }, + "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 + }, + "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 + }, + "bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true + }, + "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 + }, + "body-parser": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "dev": true, + "requires": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "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 + }, + "qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dev": true, + "requires": { + "side-channel": "^1.0.4" + } + } + } + }, + "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 + }, + "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" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "breakword": { + "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" + } + }, + "brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", + "dev": true + }, + "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, + "requires": { + "abstract-level": "^1.0.2", + "catering": "^2.1.1", + "module-error": "^1.0.2", + "run-parallel-limit": "^1.1.0" + } + }, + "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 + }, + "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, + "requires": { + "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" + } + }, + "bs58": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", + "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", + "dev": true, + "requires": { + "base-x": "^3.0.2" + } + }, + "bs58check": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz", + "integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==", + "dev": true, + "requires": { + "bs58": "^4.0.0", + "create-hash": "^1.1.0", + "safe-buffer": "^5.1.2" + } + }, + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "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 + }, + "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 + }, + "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 + }, + "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 + }, + "bufferutil": { + "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", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true + }, + "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 + }, + "cacheable-request": { + "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", + "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" + }, + "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 + } + } + }, + "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, + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "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, + "requires": { + "no-case": "^2.2.0", + "upper-case": "^1.1.1" + } + }, + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha512-FxAv7HpHrXbh3aPo4o2qxHay2lkLY3x5Mw3KeE4KQE8ysVfziWeRZDwcjauvwBSGEC/nXUPzZy8zeh4HokqOnw==", + "dev": true + }, + "camelcase-keys": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", + "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", + "dev": true, + "requires": { + "camelcase": "^5.3.1", + "map-obj": "^4.0.0", + "quick-lru": "^4.0.1" + }, + "dependencies": { + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + } + } + }, + "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", + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", + "dev": true + }, + "catering": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/catering/-/catering-2.1.1.tgz", + "integrity": "sha512-K7Qy8O9p76sL3/3m7/zLKbRkyOlSZAgzEaLhyj2mXS8PsCud2Eo4hAb8aLtZqHh0QGqLcb9dlJSu6lHRVENm1w==", + "dev": true + }, + "cbor": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/cbor/-/cbor-8.1.0.tgz", + "integrity": "sha512-DwGjNW9omn6EwP70aXsn7FQJx5kO12tX0bZkaTjzdVFM6/7nhA4t0EENocKGx6D2Bch9PE2KzCUf5SceBdeijg==", + "dev": true, + "requires": { + "nofilter": "^3.1.0" + } + }, + "chai": { + "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": "^4.1.2", + "get-func-name": "^2.0.0", + "loupe": "^2.3.1", + "pathval": "^1.1.1", + "type-detect": "^4.0.5" + } + }, + "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, + "requires": {} + }, + "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" + } + }, + "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, + "requires": { + "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" + } + }, + "chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, + "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 + }, + "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 + }, + "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, + "requires": { + "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" + } + }, + "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, + "requires": { + "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" + } + }, + "chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.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" + } + }, + "chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true + }, + "ci-info": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", + "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", + "dev": true + }, + "cids": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/cids/-/cids-0.7.5.tgz", + "integrity": "sha512-zT7mPeghoWAu+ppn8+BS1tQ5qGmbMfB4AregnQjA/qHY3GC1m1ptI9GkWNlgeu38r7CuRdXB47uY2XgAYt6QVA==", + "dev": true, + "requires": { + "buffer": "^5.5.0", + "class-is": "^1.1.0", + "multibase": "~0.6.0", + "multicodec": "^1.0.0", + "multihashes": "~0.4.15" + }, + "dependencies": { + "multicodec": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/multicodec/-/multicodec-1.0.4.tgz", + "integrity": "sha512-NDd7FeS3QamVtbgfvu5h7fd1IlbaC4EQ0/pgU4zqE2vdHCmBGsUa0TiM8/TdSeG6BMPC92OOCf8F1ocE/Wkrrg==", + "dev": true, + "requires": { + "buffer": "^5.6.0", + "varint": "^5.0.0" + } + } + } + }, + "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, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "class-is": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/class-is/-/class-is-1.1.0.tgz", + "integrity": "sha512-rhjH9AG1fvabIDoGRVH587413LPjTZgmDF9fOFCbFJQV4yuocX1mHxxvXI4g3cGwbVY9wAYIoKlg1N79frJKQw==", + "dev": true + }, + "classic-level": { + "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.2.2", + "node-gyp-build": "^4.3.0" + } + }, + "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 + }, + "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, + "requires": { + "colors": "^1.1.2", + "object-assign": "^4.1.0", + "string-width": "^2.1.1" + } + }, + "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, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "dependencies": { + "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 + }, + "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 + }, + "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, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "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, + "requires": { + "ansi-regex": "^5.0.1" + } + } + } + }, + "clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "dev": true + }, + "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, + "requires": { + "mimic-response": "^1.0.0" + } + }, + "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 + }, + "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 + }, + "colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "dev": true + }, + "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, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "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 + }, + "commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true + }, + "compare-versions": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-5.0.3.tgz", + "integrity": "sha512-4UZlZP8Z99MGEY+Ovg/uJxJuvoXuN4M6B3hKaiackiHrgzQFEe3diJi1mf1PNHbFujM7FvLrK2bpgIaImbtZ1A==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "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, + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "requires": { + "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" + } + }, + "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 + }, + "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, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "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, + "requires": { + "snake-case": "^2.1.0", + "upper-case": "^1.1.1" + } + }, + "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, + "requires": { + "safe-buffer": "5.2.1" + } + }, + "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, + "requires": { + "cids": "^0.7.1", + "multicodec": "^0.5.5", + "multihashes": "^0.4.15" + } + }, + "content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "dev": true + }, + "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 + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "dev": true + }, + "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 + }, + "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, + "requires": { + "object-assign": "^4", + "vary": "^1" + } + }, + "cosmiconfig": { + "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", + "js-yaml": "^4.1.0", + "parse-json": "^5.0.0", + "path-type": "^4.0.0" + }, + "dependencies": { + "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 + }, + "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, + "requires": { + "argparse": "^2.0.1" + } + } + } + }, + "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 + }, + "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, + "requires": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "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, + "requires": { + "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" + } + }, + "cross-fetch": { + "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.12" + } + }, + "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, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "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 + }, + "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, + "requires": { + "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" + } + }, + "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 + }, + "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, + "requires": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + } + }, + "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 + }, + "csv": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/csv/-/csv-5.5.3.tgz", + "integrity": "sha512-QTaY0XjjhTQOdguARF0lGKm5/mEq9PD9/VhZZegHDIBq2tQwgNpHc3dneD4mGo2iJs+fTKv5Bp0fZ+BRuY3Z0g==", + "dev": true, + "requires": { + "csv-generate": "^3.4.3", + "csv-parse": "^4.16.3", + "csv-stringify": "^5.6.5", + "stream-transform": "^2.1.3" + } + }, + "csv-generate": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/csv-generate/-/csv-generate-3.4.3.tgz", + "integrity": "sha512-w/T+rqR0vwvHqWs/1ZyMDWtHHSJaN06klRqJXBEpDJaM/+dZkso0OKh1VcuuYvK3XM53KysVNq8Ko/epCK8wOw==", + "dev": true + }, + "csv-parse": { + "version": "4.16.3", + "resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-4.16.3.tgz", + "integrity": "sha512-cO1I/zmz4w2dcKHVvpCr7JVRu8/FymG5OEpmvsZYlccYolPBLoVGKUHgNoc4ZGkFeFlWGEDmMyBM+TTqRdW/wg==", + "dev": true + }, + "csv-stringify": { + "version": "5.6.5", + "resolved": "https://registry.npmjs.org/csv-stringify/-/csv-stringify-5.6.5.tgz", + "integrity": "sha512-PjiQ659aQ+fUTQqSrd1XEDnOr52jh30RBurfzkscaE2tPaFsDH5wOAHJiw8XAHphRknCwMUE9KRayc4K/NbO8A==", + "dev": true + }, + "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, + "requires": { + "es5-ext": "^0.10.50", + "type": "^1.0.1" + } + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "dataloader": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/dataloader/-/dataloader-1.4.0.tgz", + "integrity": "sha512-68s5jYdlvasItOJnCuI2Q9s4q98g0pCyL3HrcKJu8KNugUl8ahgmZYg38ysLTgQjjXX3H8CJLkAvWrclWfcalw==", + "dev": true + }, + "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 + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "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 + }, + "decamelize-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz", + "integrity": "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==", + "dev": true, + "requires": { + "decamelize": "^1.1.0", + "map-obj": "^1.0.0" + }, + "dependencies": { + "map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==", + "dev": true + } + } + }, + "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 + }, + "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, + "requires": { + "mimic-response": "^3.1.0" + }, + "dependencies": { + "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 + } + } + }, + "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, + "requires": { + "type-detect": "^4.0.0" + } + }, + "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 + }, + "defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dev": true, + "requires": { + "clone": "^1.0.2" + } + }, + "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 + }, + "define-properties": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", + "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", + "dev": true, + "requires": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true + }, + "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 + }, + "destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true + }, + "detect-indent": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz", + "integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==", + "dev": true + }, + "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, + "requires": { + "address": "^1.0.1", + "debug": "4" + } + }, + "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 + }, + "difflib": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/difflib/-/difflib-0.2.4.tgz", + "integrity": "sha512-9YVwmMb0wQHQNr5J9m6BSj6fk4pfGITGQOOs+D9Fl+INODWFOfvhIU1hNv6GgR1RBoC/9NJcwu77zShxV0kT7w==", + "dev": true, + "requires": { + "heap": ">= 0.2.0" + } + }, + "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, + "requires": { + "path-type": "^4.0.0" + } + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "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, + "requires": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + } + }, + "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 + }, + "domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true + }, + "domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "requires": { + "domelementtype": "^2.3.0" + } + }, + "domutils": { + "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.3" + } + }, + "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, + "requires": { + "no-case": "^2.2.0" + } + }, + "dotenv": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz", + "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==", + "dev": true + }, + "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, + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "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 + }, + "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, + "requires": { + "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" + } + }, + "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 + }, + "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 + }, + "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, + "requires": { + "once": "^1.4.0" + } + }, + "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, + "requires": { + "ansi-colors": "^4.1.1" + } + }, + "entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true + }, + "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 + }, + "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, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es-abstract": { + "version": "1.21.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.2.tgz", + "integrity": "sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg==", + "dev": true, + "requires": { + "array-buffer-byte-length": "^1.0.0", + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "es-set-tostringtag": "^2.0.1", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.5", + "get-intrinsic": "^1.2.0", + "get-symbol-description": "^1.0.0", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.5", + "is-array-buffer": "^3.0.2", + "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-typed-array": "^1.1.10", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.3", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.4.3", + "safe-regex-test": "^1.0.0", + "string.prototype.trim": "^1.2.7", + "string.prototype.trimend": "^1.0.6", + "string.prototype.trimstart": "^1.0.6", + "typed-array-length": "^1.0.4", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.9" + } + }, + "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 + }, + "es-set-tostringtag": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", + "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.3", + "has": "^1.0.3", + "has-tostringtag": "^1.0.0" + } + }, + "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, + "requires": { + "has": "^1.0.3" + } + }, + "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, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "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, + "requires": { + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "next-tick": "^1.1.0" + } + }, + "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, + "requires": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "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 + }, + "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, + "requires": { + "d": "^1.0.1", + "ext": "^1.1.2" + } + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, + "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 + }, + "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 + }, + "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, + "requires": { + "esprima": "^2.7.1", + "estraverse": "^1.9.1", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.2.0" + }, + "dependencies": { + "esprima": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", + "integrity": "sha512-OarPfz0lFCiW4/AV2Oy1Rp9qu0iusTKqykwTspGCZtPxmF81JR4MmIebvF1F9+UOKth2ZubLQ4XGGaU+hSn99A==", + "dev": true + }, + "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 + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "requires": { + "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" + } + }, + "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 + }, + "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, + "requires": { + "prelude-ls": "~1.1.2" + } + } + } + }, + "eslint": { + "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.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", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.0", + "eslint-visitor-keys": "^3.4.1", + "espree": "^9.6.0", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "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-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0" + }, + "dependencies": { + "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 + }, + "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, + "requires": { + "color-convert": "^2.0.1" + } + }, + "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 + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "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, + "requires": { + "color-name": "~1.1.4" + } + }, + "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 + }, + "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 + }, + "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, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "requires": { + "is-glob": "^4.0.3" + } + }, + "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 + }, + "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, + "requires": { + "argparse": "^2.0.1" + } + }, + "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, + "requires": { + "p-locate": "^5.0.0" + } + }, + "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, + "requires": { + "p-limit": "^3.0.2" + } + }, + "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, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "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, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "eslint-config-prettier": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.0.0.tgz", + "integrity": "sha512-IcJsTkJae2S35pRsRAwoCE+925rJJStOdkKnLVgtE+tEpqU0EVVM7OqrwxqgptKdX29NUwC82I5pXsGFIgSevw==", + "dev": true, + "requires": {} + }, + "eslint-scope": { + "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", + "estraverse": "^5.2.0" + } + }, + "eslint-visitor-keys": { + "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.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.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + } + }, + "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 + }, + "esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + } + }, + "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, + "requires": { + "estraverse": "^5.2.0" + } + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true + }, + "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, + "requires": { + "idna-uts46-hx": "^2.3.1", + "js-sha3": "^0.5.7" + }, + "dependencies": { + "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 + } + } + }, + "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, + "requires": { + "@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" + }, + "dependencies": { + "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 + }, + "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 + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "chokidar": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.0.tgz", + "integrity": "sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A==", + "dev": true, + "requires": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.1.1", + "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" + } + }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + } + }, + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "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 + }, + "ethereum-cryptography": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-1.2.0.tgz", + "integrity": "sha512-6yFQC9b5ug6/17CQpCyE3k9eKBMdhyVjzUy1WkiuY/E4vj/SXDBbCw8QEIaXqf0Mf2SnY6RmpDcwlUmBSS0EJw==", + "dev": true, + "requires": { + "@noble/hashes": "1.2.0", + "@noble/secp256k1": "1.7.1", + "@scure/bip32": "1.1.5", + "@scure/bip39": "1.1.1" + } + }, + "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, + "requires": { + "locate-path": "^3.0.0" + } + }, + "flat": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.1.tgz", + "integrity": "sha512-FmTtBsHskrU6FJ2VxCnsDb84wu9zhmO3cUX2kGFb5tuwhfXxGciiT0oRY+cck35QmG+NmGh5eLz6lLCpWTqwpA==", + "dev": true, + "requires": { + "is-buffer": "~2.0.3" + } + }, + "fsevents": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", + "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "dev": true, + "optional": true + }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "requires": { + "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" + } + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "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, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "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, + "requires": { + "chalk": "^2.4.2" + } + }, + "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" + } + }, + "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, + "requires": { + "minimist": "^1.2.5" + } + }, + "mocha": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-7.2.0.tgz", + "integrity": "sha512-O9CIypScywTVpNaRrCAgoUnJgozpIofjKUYmJhiCIJMiuYnLI6otcb1/kpW9/n/tJODHGZ7i8aLQoDVsMtOKQQ==", + "dev": true, + "requires": { + "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" + } + }, + "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 + }, + "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, + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, + "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, + "requires": { + "p-try": "^2.0.0" + } + }, + "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, + "requires": { + "p-limit": "^2.0.0" + } + }, + "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 + }, + "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, + "requires": { + "picomatch": "^2.0.4" + } + }, + "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, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "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, + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "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 + }, + "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, + "requires": { + "has-flag": "^3.0.0" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "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, + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + } + }, + "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 + }, + "yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "dev": true, + "requires": { + "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" + } + }, + "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, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, + "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, + "requires": { + "flat": "^4.1.0", + "lodash": "^4.17.15", + "yargs": "^13.3.0" + } + } + } + }, + "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, + "requires": { + "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" + }, + "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 + }, + "ws": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", + "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", + "dev": true, + "requires": { + "async-limiter": "~1.0.0", + "safe-buffer": "~5.1.0", + "ultron": "~1.1.0" + } + } + } + }, + "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==", + "dev": true, + "requires": { + "ethereumjs-abi": "^0.6.8", + "ethereumjs-util": "^5.1.1", + "tweetnacl": "^1.0.3", + "tweetnacl-util": "^0.15.0" + }, + "dependencies": { + "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, + "requires": { + "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" + } + } + } + }, + "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, + "requires": { + "js-sha3": "^0.8.0" + } + }, + "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, + "requires": { + "@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" + } + }, + "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, + "requires": { + "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" + }, + "dependencies": { + "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 + } + } + }, + "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, + "requires": { + "bn.js": "^4.11.8", + "ethereumjs-util": "^6.0.0" + }, + "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" + } + } + } + }, + "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, + "requires": { + "@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" + }, + "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 + } + } + }, + "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, + "requires": { + "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" + } + }, + "ethers": { + "version": "4.0.49", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-4.0.49.tgz", + "integrity": "sha512-kPltTvWiyu+OktYy1IStSO16i2e7cS9D9OxZ81q2UUaiNPVrm/RTcbxamCXF9VUSKzJIdJV68EAIhTEVBalRWg==", + "dev": true, + "requires": { + "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" + }, + "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 + }, + "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, + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.0" + } + }, + "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 + }, + "scrypt-js": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-2.0.4.tgz", + "integrity": "sha512-4KsaGcPnuhtCZQCxFxN3GVYIhKFPTdLd8PLC552XwbMndtD0cjRFAhDuuydXQ0h08ZfPgzqe6EKHozpuH74iDw==", + "dev": true + }, + "setimmediate": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.4.tgz", + "integrity": "sha512-/TjEmXQVEzdod/FFskf3o7oOAsGhHf2j1dZqRFbDzq4F3mvvxflIIi4Hd3bLQE9y/CpwqfSQam5JakI/mi3Pog==", + "dev": true + }, + "uuid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.1.tgz", + "integrity": "sha512-nWg9+Oa3qD2CQzHIP4qKUqwNfzKn8P0LtFhotaCTFchsV7ZfDhAybeip/HZVeMIpZi9JgY1E3nUlwaCmZT1sEg==", + "dev": true + } + } + }, + "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, + "requires": { + "bn.js": "4.11.6", + "js-sha3": "0.5.5", + "number-to-bn": "1.7.0" + }, + "dependencies": { + "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 + }, + "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 + } + } + }, + "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, + "requires": { + "bn.js": "4.11.6", + "number-to-bn": "1.7.0" + }, + "dependencies": { + "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 + } + } + }, + "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, + "requires": { + "is-hex-prefixed": "1.0.0", + "strip-hex-prefix": "1.0.0" + } + }, + "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 + }, + "eventemitter3": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.4.tgz", + "integrity": "sha512-rlaVLnVxtxvoyLsQQFBx53YmXHDxRIzzTLbdfxqi4yocpSjAxXwkU0cScM5JgSKMqEhrZpnvQ2D9gjylR0AimQ==", + "dev": true + }, + "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, + "requires": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, + "express": { + "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.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" + }, + "dependencies": { + "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, + "requires": { + "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" + } + }, + "cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "dev": true + }, + "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 + }, + "qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dev": true, + "requires": { + "side-channel": "^1.0.4" + } + }, + "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, + "requires": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + } + } + }, + "ext": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", + "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", + "dev": true, + "requires": { + "type": "^2.7.2" + }, + "dependencies": { + "type": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", + "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==", + "dev": true + } + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "extendable-error": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/extendable-error/-/extendable-error-0.1.7.tgz", + "integrity": "sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==", + "dev": true + }, + "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, + "requires": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + } + }, + "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 + }, + "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, + "requires": { + "pure-rand": "^5.0.1" + } + }, + "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 + }, + "fast-diff": { + "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": { + "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", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + } + }, + "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 + }, + "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 + }, + "fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, + "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, + "requires": { + "flat-cache": "^3.0.4" + } + }, + "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, + "requires": { + "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", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "find-yarn-workspace-root2": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/find-yarn-workspace-root2/-/find-yarn-workspace-root2-1.2.16.tgz", + "integrity": "sha512-hr6hb1w8ePMpPVUK39S4RlwJzi+xPLuVuG8XlwXU3KD5Yn3qgBWVfy3AzNlDhWvE1EORCE65/Qm26rFQt3VLVA==", + "dev": true, + "requires": { + "micromatch": "^4.0.2", + "pkg-dir": "^4.2.0" + } + }, + "flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true + }, + "flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "requires": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + } + }, + "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 + }, + "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 + }, + "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, + "requires": { + "is-callable": "^1.1.3" + } + }, + "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 + }, + "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, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "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 + }, + "forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true + }, + "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 + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true + }, + "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, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "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, + "requires": { + "minipass": "^2.6.0" + } + }, + "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 + }, + "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 + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "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", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + } + }, + "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 + }, + "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 + }, + "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 + }, + "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 + }, + "get-intrinsic": { + "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" + } + }, + "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 + }, + "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 + }, + "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, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + } + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "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, + "requires": { + "chalk": "^2.4.2", + "node-emoji": "^1.10.0" + } + }, + "glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "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" + } + }, + "minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } + } + }, + "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, + "requires": { + "is-glob": "^4.0.1" + } + }, + "global": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz", + "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==", + "dev": true, + "requires": { + "min-document": "^2.19.0", + "process": "^0.11.10" + } + }, + "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, + "requires": { + "global-prefix": "^3.0.0" + } + }, + "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, + "requires": { + "ini": "^1.3.5", + "kind-of": "^6.0.2", + "which": "^1.3.1" + }, + "dependencies": { + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "globals": { + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "dev": true, + "requires": { + "define-properties": "^1.1.3" + } + }, + "globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + } + }, + "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", + "integrity": "sha512-hBv2ty9QN2RdbJJMK3hesmSkFTjVIHyIDDbssCKnSmq62edGgImJWD10Eb1k77TiV1bxloxqcFAVK8+9pkhOig==", + "dev": true, + "requires": { + "@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" + } + }, + "graceful-fs": { + "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": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "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", + "integrity": "sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==", + "dev": true, + "requires": { + "lodash": "^4.17.15" + } + }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, + "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, + "requires": { + "minimist": "^1.2.5", + "neo-async": "^2.6.0", + "source-map": "^0.6.1", + "uglify-js": "^3.1.4", + "wordwrap": "^1.0.0" + }, + "dependencies": { + "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 + } + } + }, + "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 + }, + "har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "dev": true, + "requires": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + } + }, + "hard-rejection": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", + "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", + "dev": true + }, + "hardhat": { + "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", + "@metamask/eth-sig-util": "^4.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", + "@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", + "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.14.0", + "uuid": "^8.3.2", + "ws": "^7.4.6" + }, + "dependencies": { + "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 + }, + "commander": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/commander/-/commander-3.0.2.tgz", + "integrity": "sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==", + "dev": true + }, + "ethereum-cryptography": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-1.2.0.tgz", + "integrity": "sha512-6yFQC9b5ug6/17CQpCyE3k9eKBMdhyVjzUy1WkiuY/E4vj/SXDBbCw8QEIaXqf0Mf2SnY6RmpDcwlUmBSS0EJw==", + "dev": true, + "requires": { + "@noble/hashes": "1.2.0", + "@noble/secp256k1": "1.7.1", + "@scure/bip32": "1.1.5", + "@scure/bip39": "1.1.1" + } + }, + "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, + "requires": { + "locate-path": "^2.0.0" + } + }, + "glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "requires": { + "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" + } + }, + "jsonfile": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", + "integrity": "sha512-PKllAqbgLgxHaj8TElYymKCAgrASebJrWpTnEkOaTowt23VKXXN0sUeriJ+eh7y6ufb/CC5ap11pz71/cM0hUw==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "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, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "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, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "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 + }, + "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 + }, + "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 + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "solc": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/solc/-/solc-0.7.3.tgz", + "integrity": "sha512-GAsWNAjGzIDg7VxzP6mPjdurby3IkGCjQcM8GFYZT6RyaoUZKmMU6Y7YwG+tFGhv7dwZ8rmR4iwFDrrD99JwqA==", + "dev": true, + "requires": { + "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" + }, + "dependencies": { + "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, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^2.1.0", + "klaw": "^1.0.0", + "path-is-absolute": "^1.0.0", + "rimraf": "^2.2.8" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + } + } + }, + "hardhat-exposed": { + "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", + "solidity-ast": "^0.4.25" + } + }, + "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, + "requires": { + "array-uniq": "1.0.3", + "eth-gas-reporter": "^0.2.25", + "sha1": "^1.1.1" + } + }, + "hardhat-ignore-warnings": { + "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", + "node-interval-tree": "^2.0.1", + "solidity-comments": "^0.0.2" + }, + "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" + } + }, + "minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } + } + }, + "has": { + "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" + } + }, + "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 + }, + "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 + }, + "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, + "requires": { + "get-intrinsic": "^1.1.1" + } + }, + "has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "dev": true + }, + "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 + }, + "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" + } + }, + "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, + "requires": { + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + } + }, + "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, + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "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 + }, + "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, + "requires": { + "no-case": "^2.2.0", + "upper-case": "^1.1.3" + } + }, + "heap": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/heap/-/heap-0.2.7.tgz", + "integrity": "sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg==", + "dev": true + }, + "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 + }, + "highlightjs-solidity": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/highlightjs-solidity/-/highlightjs-solidity-2.0.6.tgz", + "integrity": "sha512-DySXWfQghjm2l6a/flF+cteroJqD4gI8GSdL4PtvxZSsAHie8m3yVe2JFoRg03ROKT6hp2Lc/BxXkqerNmtQYg==", + "dev": true + }, + "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, + "requires": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "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 + }, + "htmlparser2": { + "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.3", + "domutils": "^3.0.1", + "entities": "^4.4.0" + } + }, + "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, + "requires": { + "caseless": "^0.12.0", + "concat-stream": "^1.6.2", + "http-response-object": "^3.0.1", + "parse-cache-control": "^1.0.1" + } + }, + "http-cache-semantics": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", + "dev": true + }, + "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, + "requires": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + } + }, + "http-https": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/http-https/-/http-https-1.0.0.tgz", + "integrity": "sha512-o0PWwVCSp3O0wS6FvNr6xfBCHgt0m1tvPLFOCc2iFDKTRAXhB7m8klDf7ErowFH8POa6dVdGatKU5I1YYwzUyg==", + "dev": true + }, + "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, + "requires": { + "@types/node": "^10.0.3" + }, + "dependencies": { + "@types/node": { + "version": "10.17.60", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz", + "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==", + "dev": true + } + } + }, + "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, + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "http2-wrapper": { + "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", + "resolve-alpn": "^1.2.0" + }, + "dependencies": { + "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 + } + } + }, + "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, + "requires": { + "agent-base": "6", + "debug": "4" + } + }, + "human-id": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/human-id/-/human-id-1.0.2.tgz", + "integrity": "sha512-UNopramDEhHJD+VR+ehk8rOslwSfByxPIZyJRfV739NDhN5LF1fa1MqnzKm2lGTQRjNrjK19Q5fhkgIfjlVUKw==", + "dev": true + }, + "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, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "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, + "requires": { + "punycode": "2.1.0" + } + }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true + }, + "ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true + }, + "immutable": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.0.tgz", + "integrity": "sha512-0AOCmOip+xgJwEVTQj1EfiDDOkPmuyllDuTuEX+DDXUgapLAsBIfkg3sxCYyCEA8mQqZrrxPUGjcOQ2JS3WLkg==", + "dev": true + }, + "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, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "dependencies": { + "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 + } + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true + }, + "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 + }, + "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, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, + "internal-slot": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", + "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", + "dev": true, + "requires": { + "get-intrinsic": "^1.2.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + } + }, + "interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "dev": true + }, + "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 + }, + "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, + "requires": { + "fp-ts": "^1.0.0" + } + }, + "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 + }, + "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, + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-array-buffer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", + "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.0", + "is-typed-array": "^1.1.10" + } + }, + "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 + }, + "is-bigint": { + "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" + } + }, + "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, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "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, + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "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 + }, + "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 + }, + "is-ci": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", + "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", + "dev": true, + "requires": { + "ci-info": "^3.2.0" + } + }, + "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, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true + }, + "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 + }, + "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 + }, + "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, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "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, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "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 + }, + "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, + "requires": { + "lower-case": "^1.1.0" + } + }, + "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 + }, + "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 + }, + "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, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true + }, + "is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "dev": true + }, + "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 + }, + "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, + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "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, + "requires": { + "call-bind": "^1.0.2" + } + }, + "is-string": { + "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" + } + }, + "is-subdir": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-subdir/-/is-subdir-1.2.0.tgz", + "integrity": "sha512-2AT6j+gXe/1ueqbW6fLZJiIw3F8iXGJtt0yDrZaBhAZEG1raiTxKWU+IPqMCzQAXOUCKdA4UDMgacKH25XG2Cw==", + "dev": true, + "requires": { + "better-path-resolve": "1.0.0" + } + }, + "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, + "requires": { + "has-symbols": "^1.0.2" + } + }, + "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, + "requires": { + "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-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "dev": true + }, + "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 + }, + "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, + "requires": { + "upper-case": "^1.1.0" + } + }, + "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 + }, + "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, + "requires": { + "call-bind": "^1.0.2" + } + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", + "dev": true + }, + "js-sdsl": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.1.tgz", + "integrity": "sha512-6Gsx8R0RucyePbWqPssR8DyfuXmLBooYN5cZFZKjHGnQuaf7pEzhtpceagJxVu4LqhYY5EYA7nko3FmeHZ1KbA==", + "dev": true + }, + "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 + }, + "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 + }, + "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, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", + "dev": true + }, + "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 + }, + "json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "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 + }, + "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 + }, + "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 + }, + "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 + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "jsonschema": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsonschema/-/jsonschema-1.4.1.tgz", + "integrity": "sha512-S6cATIPVv1z0IlxdN+zUk5EPjkGCdnhN4wVSBlvoUO1tOLJootbo9CquNJmbIh4yikWHiUedhRYrNPn1arpEmQ==", + "dev": true + }, + "jsprim": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", + "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + } + }, + "keccak": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.3.tgz", + "integrity": "sha512-JZrLIAJWuZxKbCilMpNz5Vj7Vtb4scDG3dMXLOsbzBmQGyjwE61BbW7bJkfKKCShXiQZt3T6sBgALRtmd+nZaQ==", + "dev": true, + "requires": { + "node-addon-api": "^2.0.0", + "node-gyp-build": "^4.2.0", + "readable-stream": "^3.6.0" + } + }, + "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, + "requires": { + "bn.js": "^5.2.0", + "buffer": "^6.0.3", + "keccak": "^3.0.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 + }, + "buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + } + } + }, + "keyv": { + "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" + } + }, + "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 + }, + "klaw": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz", + "integrity": "sha512-TED5xi9gGQjGpNnvRWknrwAB1eL5GciPfVFOt3Vk1OJCVDQbzuSfrF3hkUQKlsgKrG1F+0t5W0m+Fje1jIt8rw==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.9" + } + }, + "kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true + }, + "lcid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", + "integrity": "sha512-YiGkH6EnGrDGqLMITnGjXtGmNtjoXw9SVUzcaos8RBi7Ps0VBylkq+vOcY9QE5poLasPCR849ucFUkl0UzUyOw==", + "dev": true, + "requires": { + "invert-kv": "^1.0.0" + } + }, + "level": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/level/-/level-8.0.0.tgz", + "integrity": "sha512-ypf0jjAk2BWI33yzEaaotpq7fkOPALKAgDBxggO6Q9HGX2MRXn0wbP1Jn/tJv1gtL867+YOjOB49WaUF3UoJNQ==", + "dev": true, + "requires": { + "browser-level": "^1.0.1", + "classic-level": "^1.2.0" + } + }, + "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 + }, + "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, + "requires": { + "buffer": "^6.0.3", + "module-error": "^1.0.1" + }, + "dependencies": { + "buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + } + } + }, + "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, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, + "lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "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, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" + }, + "dependencies": { + "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, + "requires": { + "error-ex": "^1.2.0" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true + }, + "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, + "requires": { + "is-utf8": "^0.2.0" + } + } + } + }, + "load-yaml-file": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/load-yaml-file/-/load-yaml-file-0.2.0.tgz", + "integrity": "sha512-OfCBkGEw4nN6JLtgRidPX6QxjBQGQf72q3si2uvqyFEMbycSFFHwAZeXx6cJgFM9wmLrf9zBwCP3Ivqa+LLZPw==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.5", + "js-yaml": "^3.13.0", + "pify": "^4.0.1", + "strip-bom": "^3.0.0" + } + }, + "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, + "requires": { + "p-locate": "^4.1.0" + } + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "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 + }, + "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 + }, + "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 + }, + "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 + }, + "lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", + "dev": true + }, + "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 + }, + "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, + "requires": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "dependencies": { + "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, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "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, + "requires": { + "color-name": "~1.1.4" + } + }, + "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 + }, + "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 + }, + "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, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "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, + "requires": { + "get-func-name": "^2.0.0" + } + }, + "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 + }, + "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, + "requires": { + "lower-case": "^1.1.2" + } + }, + "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 + }, + "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", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "map-obj": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", + "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", + "dev": true + }, + "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 + }, + "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 + }, + "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, + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "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 + }, + "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, + "requires": { + "abstract-level": "^1.0.0", + "functional-red-black-tree": "^1.0.1", + "module-error": "^1.0.1" + } + }, + "memorystream": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", + "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==", + "dev": true + }, + "meow": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/meow/-/meow-6.1.1.tgz", + "integrity": "sha512-3YffViIt2QWgTy6Pale5QpopX/IvU3LPL03jOTqp6pGj3VjesdO/U8CuHMKpnQr4shCNCM5fd5XFFvIIl6JBHg==", + "dev": true, + "requires": { + "@types/minimist": "^1.2.0", + "camelcase-keys": "^6.2.2", + "decamelize-keys": "^1.1.0", + "hard-rejection": "^2.1.0", + "minimist-options": "^4.0.2", + "normalize-package-data": "^2.5.0", + "read-pkg-up": "^7.0.1", + "redent": "^3.0.0", + "trim-newlines": "^3.0.0", + "type-fest": "^0.13.1", + "yargs-parser": "^18.1.3" + }, + "dependencies": { + "type-fest": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", + "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", + "dev": true + } + } + }, + "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 + }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, + "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, + "requires": { + "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" + }, + "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 + } + } + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "dev": true + }, + "micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "requires": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + } + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true + }, + "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 + }, + "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, + "requires": { + "mime-db": "1.52.0" + } + }, + "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 + }, + "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, + "requires": { + "dom-walk": "^0.1.0" + } + }, + "min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true + }, + "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 + }, + "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 + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true + }, + "minimist-options": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", + "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", + "dev": true, + "requires": { + "arrify": "^1.0.1", + "is-plain-obj": "^1.1.0", + "kind-of": "^6.0.3" + } + }, + "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, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", + "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", + "dev": true, + "requires": { + "minipass": "^2.9.0" + } + }, + "mixme": { + "version": "0.5.9", + "resolved": "https://registry.npmjs.org/mixme/-/mixme-0.5.9.tgz", + "integrity": "sha512-VC5fg6ySUscaWUpI4gxCBTQMH2RdUpNrk+MsbpCYtIvf9SBJdiUey4qE7BXviJsJR4nDQxCZ+3yaYNW3guz/Pw==", + "dev": true + }, + "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, + "requires": { + "minimist": "^1.2.6" + } + }, + "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==", + "dev": true, + "requires": { + "mkdirp": "*" + } + }, + "mnemonist": { + "version": "0.38.5", + "resolved": "https://registry.npmjs.org/mnemonist/-/mnemonist-0.38.5.tgz", + "integrity": "sha512-bZTFT5rrPKtPJxj8KSV0WkPyNxl72vQepqqVUAW2ARUpUSF2qXMB6jZj7hW5/k7C1rtpzqbD/IIbJwLXUjCHeg==", + "dev": true, + "requires": { + "obliterator": "^2.0.0" + } + }, + "mocha": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", + "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==", + "dev": true, + "requires": { + "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" + }, + "dependencies": { + "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 + }, + "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 + }, + "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 + }, + "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": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "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 + }, + "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, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "requires": { + "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" + }, + "dependencies": { + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + } + } + }, + "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 + }, + "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 + }, + "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, + "requires": { + "argparse": "^2.0.1" + } + }, + "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, + "requires": { + "p-locate": "^5.0.0" + } + }, + "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, + "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": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "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, + "requires": { + "p-limit": "^3.0.2" + } + }, + "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, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "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, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "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, + "requires": { + "has-flag": "^4.0.0" + } + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "requires": { + "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" + } + }, + "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 + } + } + }, + "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 + }, + "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 + }, + "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 + }, + "multibase": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/multibase/-/multibase-0.6.1.tgz", + "integrity": "sha512-pFfAwyTjbbQgNc3G7D48JkJxWtoJoBMaR4xQUOuB8RnCgRqaYmWNFeJTTvrJ2w51bjLq2zTby6Rqj9TQ9elSUw==", + "dev": true, + "requires": { + "base-x": "^3.0.8", + "buffer": "^5.5.0" + } + }, + "multicodec": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/multicodec/-/multicodec-0.5.7.tgz", + "integrity": "sha512-PscoRxm3f+88fAtELwUnZxGDkduE2HD9Q6GHUOywQLjOGT/HAdhjLDYNZ1e7VR0s0TP0EwZ16LNUTFpoBGivOA==", + "dev": true, + "requires": { + "varint": "^5.0.0" + } + }, + "multihashes": { + "version": "0.4.21", + "resolved": "https://registry.npmjs.org/multihashes/-/multihashes-0.4.21.tgz", + "integrity": "sha512-uVSvmeCWf36pU2nB4/1kzYZjsXD9vofZKpgudqkceYY5g2aZZXJ5r9lxuzoRLl1OAp28XljXsEJ/X/85ZsKmKw==", + "dev": true, + "requires": { + "buffer": "^5.5.0", + "multibase": "^0.7.0", + "varint": "^5.0.0" + }, + "dependencies": { + "multibase": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/multibase/-/multibase-0.7.0.tgz", + "integrity": "sha512-TW8q03O0f6PNFTQDvh3xxH03c8CjGaaYrjkl9UQPG6rz53TQzzxJVCIWVjzcbN/Q5Y53Zd0IBQBMVktVgNx4Fg==", + "dev": true, + "requires": { + "base-x": "^3.0.8", + "buffer": "^5.5.0" + } + } + } + }, + "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 + }, + "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 + }, + "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 + }, + "napi-macros": { + "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": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "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 + }, + "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 + }, + "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 + }, + "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, + "requires": { + "lower-case": "^1.1.1" + } + }, + "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-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, + "requires": { + "lodash": "^4.17.21" + } + }, + "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, + "requires": { + "object.getownpropertydescriptors": "^2.0.3", + "semver": "^5.7.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "node-fetch": { + "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" + } + }, + "node-gyp-build": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.0.tgz", + "integrity": "sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==", + "dev": true + }, + "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, + "requires": { + "shallowequal": "^1.1.0" + } + }, + "nofilter": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/nofilter/-/nofilter-3.1.0.tgz", + "integrity": "sha512-l2NNj07e9afPnhAhvgVrCD/oy2Ai1yfLpuo3EpiO1jFTsB4sFz6oIfAfSZyQzVpkZQ9xS8ZS5g1jCBgq4Hwo0g==", + "dev": true + }, + "nopt": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", + "integrity": "sha512-4GUt3kSEYmk4ITxzB/b9vaIDfUVWN/Ml1Fwl11IlnIG2iaJ9O6WXZ9SrYM9NLI8OCBieN2Y8SWC2oJV0RQ7qYg==", + "dev": true, + "requires": { + "abbrev": "1" + } + }, + "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, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "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 + }, + "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 + }, + "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, + "requires": { + "boolbase": "^1.0.0" + } + }, + "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 + }, + "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, + "requires": { + "bn.js": "4.11.6", + "strip-hex-prefix": "1.0.0" + }, + "dependencies": { + "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 + } + } + }, + "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 + }, + "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 + }, + "object-inspect": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "dev": true + }, + "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 + }, + "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", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + } + }, + "object.getownpropertydescriptors": { + "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.2.0", + "es-abstract": "^1.21.2", + "safe-array-concat": "^1.0.0" + } + }, + "obliterator": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/obliterator/-/obliterator-2.0.4.tgz", + "integrity": "sha512-lgHwxlxV1qIg1Eap7LgIeoBWIMFibOjbrYPIPJZcI1mmGAI2m3lNYpK12Y+GBdPQ0U1hRwSord7GIaawz962qQ==", + "dev": true + }, + "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, + "requires": { + "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", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "optionator": { + "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" + } + }, + "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, + "requires": { + "lcid": "^1.0.0" + } + }, + "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 + }, + "outdent": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/outdent/-/outdent-0.5.0.tgz", + "integrity": "sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==", + "dev": true + }, + "p-cancelable": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", + "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==", + "dev": true + }, + "p-filter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-filter/-/p-filter-2.1.0.tgz", + "integrity": "sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==", + "dev": true, + "requires": { + "p-map": "^2.0.0" + }, + "dependencies": { + "p-map": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", + "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", + "dev": true + } + } + }, + "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, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "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, + "requires": { + "p-limit": "^2.2.0" + }, + "dependencies": { + "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, + "requires": { + "p-try": "^2.0.0" + } + } + } + }, + "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, + "requires": { + "aggregate-error": "^3.0.0" + } + }, + "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 + }, + "pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true + }, + "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, + "requires": { + "no-case": "^2.2.0" + } + }, + "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, + "requires": { + "callsites": "^3.0.0" + } + }, + "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 + }, + "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 + }, + "parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + } + }, + "parse5": { + "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" + } + }, + "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, + "requires": { + "domhandler": "^5.0.2", + "parse5": "^7.0.0" + } + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true + }, + "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, + "requires": { + "camel-case": "^3.0.0", + "upper-case-first": "^1.1.0" + } + }, + "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, + "requires": { + "no-case": "^2.2.0" + } + }, + "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 + }, + "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 + }, + "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 + }, + "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 + }, + "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 + }, + "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 + }, + "pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true + }, + "pbkdf2": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", + "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", + "dev": true, + "requires": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "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 + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + }, + "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 + }, + "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, + "requires": { + "pinkie": "^2.0.0" + } + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + } + }, + "pluralize": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", + "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", + "dev": true + }, + "preferred-pm": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/preferred-pm/-/preferred-pm-3.0.3.tgz", + "integrity": "sha512-+wZgbxNES/KlJs9q40F/1sfOd/j7f1O9JaHcW5Dsn3aUUOZg3L2bjpVUcKV2jvtElYfoTuQiNeMfQJ4kwUAhCQ==", + "dev": true, + "requires": { + "find-up": "^5.0.0", + "find-yarn-workspace-root2": "1.2.16", + "path-exists": "^4.0.0", + "which-pm": "2.0.0" + }, + "dependencies": { + "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, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "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, + "requires": { + "p-locate": "^5.0.0" + } + }, + "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, + "requires": { + "p-limit": "^3.0.2" + } + } + } + }, + "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 + }, + "prettier": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz", + "integrity": "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==", + "dev": true + }, + "prettier-plugin-solidity": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/prettier-plugin-solidity/-/prettier-plugin-solidity-1.1.3.tgz", + "integrity": "sha512-fQ9yucPi2sBbA2U2Xjh6m4isUTJ7S7QLc/XDDsktqqxYfTwdYKJ0EnnywXHwCGAaYbQNK+HIYPL1OemxuMsgeg==", + "dev": true, + "requires": { + "@solidity-parser/parser": "^0.16.0", + "semver": "^7.3.8", + "solidity-comments-extractor": "^0.0.7" + }, + "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" + } + } + } + }, + "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 + }, + "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 + }, + "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, + "requires": { + "asap": "~2.0.6" + } + }, + "proper-lockfile": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", + "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.4", + "retry": "^0.12.0", + "signal-exit": "^3.0.2" + } + }, + "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, + "requires": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + } + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==", + "dev": true + }, + "psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", + "dev": true + }, + "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, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "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 + }, + "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 + }, + "qs": { + "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", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz", + "integrity": "sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==", + "dev": true, + "requires": { + "decode-uri-component": "^0.2.0", + "object-assign": "^4.1.0", + "strict-uri-encode": "^1.0.0" + } + }, + "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 + }, + "quick-lru": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", + "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", + "dev": true + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "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 + }, + "raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dev": true, + "requires": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + }, + "read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "dev": true, + "requires": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "dependencies": { + "type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "dev": true + } + } + }, + "read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "dev": true, + "requires": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + }, + "dependencies": { + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + } + } + }, + "read-yaml-file": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-yaml-file/-/read-yaml-file-1.1.0.tgz", + "integrity": "sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.5", + "js-yaml": "^3.6.1", + "pify": "^4.0.1", + "strip-bom": "^3.0.0" + } + }, + "readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "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, + "requires": { + "resolve": "^1.1.6" + } + }, + "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, + "requires": { + "minimatch": "^3.0.5" + } + }, + "redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "requires": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "dependencies": { + "strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "requires": { + "min-indent": "^1.0.0" + } + } + } + }, + "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 + }, + "regexp.prototype.flags": { + "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.2.0", + "functions-have-names": "^1.2.3" + } + }, + "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, + "requires": { + "req-from": "^2.0.0" + } + }, + "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, + "requires": { + "resolve-from": "^3.0.0" + }, + "dependencies": { + "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 + } + } + }, + "request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "dev": true, + "requires": { + "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" + }, + "dependencies": { + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "dev": true + } + } + }, + "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, + "requires": { + "lodash": "^4.17.19" + } + }, + "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==", + "dev": true, + "requires": { + "request-promise-core": "1.1.4", + "stealthy-require": "^1.1.1", + "tough-cookie": "^2.3.3" + } + }, + "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 + }, + "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 + }, + "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 + }, + "resolve": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, + "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 + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + }, + "responselike": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", + "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", + "dev": true, + "requires": { + "lowercase-keys": "^2.0.0" + }, + "dependencies": { + "lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "dev": true + } + } + }, + "retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + }, + "dependencies": { + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "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" + } + } + } + }, + "ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "dev": true, + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, + "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 + }, + "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, + "requires": { + "bn.js": "^5.2.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 + } + } + }, + "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, + "requires": { + "queue-microtask": "^1.2.2" + } + }, + "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, + "requires": { + "queue-microtask": "^1.2.2" + } + }, + "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 + }, + "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", + "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", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "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, + "requires": { + "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" + }, + "dependencies": { + "esprima": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", + "integrity": "sha512-OarPfz0lFCiW4/AV2Oy1Rp9qu0iusTKqykwTspGCZtPxmF81JR4MmIebvF1F9+UOKth2ZubLQ4XGGaU+hSn99A==", + "dev": true + }, + "glob": { + "version": "5.0.15", + "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", + "integrity": "sha512-c9IPMazfRITpmAAKi22dK1VKxGDX9ehhqfABDriL/lzO92xcUKEJPQHrVA/2YHSNFB4iFlykVmWvwo48nr3OxA==", + "dev": true, + "requires": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha512-DyYHfIYwAJmjAjSSPKANxI8bFY9YtFrgkAfinBojQ8YJTOuOuav64tMUJv584SES4xl74PmuaevIyaLESHdTAA==", + "dev": true + }, + "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 + }, + "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, + "requires": { + "has-flag": "^1.0.0" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "scrypt-js": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz", + "integrity": "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==", + "dev": true + }, + "secp256k1": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-4.0.3.tgz", + "integrity": "sha512-NLZVf+ROMxwtEj3Xa562qgv2BK5e2WNmXPiOdVIPLgs6lyTzMvBq0aWTYMI5XCP9jZMVKOcqZLw/Wc4vDkuxhA==", + "dev": true, + "requires": { + "elliptic": "^6.5.4", + "node-addon-api": "^2.0.0", + "node-gyp-build": "^4.2.0" + } + }, + "semver": { + "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" + }, + "dependencies": { + "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" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dev": true, + "requires": { + "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" + }, + "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" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + } + } + }, + "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 + } + } + }, + "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, + "requires": { + "no-case": "^2.2.0", + "upper-case-first": "^1.1.2" + } + }, + "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, + "requires": { + "randombytes": "^2.1.0" + } + }, + "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, + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + } + }, + "servify": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/servify/-/servify-0.1.12.tgz", + "integrity": "sha512-/xE6GvsKKqyo1BAY+KxOWXcLpPsUUyji7Qg3bVD7hh1eRze5bR1uYiuDA/k3Gof1s9BTzQZEJK8sNcNGFIzeWw==", + "dev": true, + "requires": { + "body-parser": "^1.16.0", + "cors": "^2.8.1", + "express": "^4.14.0", + "request": "^2.79.0", + "xhr": "^2.3.3" + } + }, + "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 + }, + "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 + }, + "setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true + }, + "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, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "sha1": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/sha1/-/sha1-1.1.1.tgz", + "integrity": "sha512-dZBS6OrMjtgVkopB1Gmo4RQCDKiZsqcpAQpkV/aaj+FCrCg8r4I4qMkDPQjBgLIxlmu9k4nUbWq6ohXahOneYA==", + "dev": true, + "requires": { + "charenc": ">= 0.0.1", + "crypt": ">= 0.0.1" + } + }, + "sha3": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/sha3/-/sha3-2.1.4.tgz", + "integrity": "sha512-S8cNxbyb0UGUM2VhRD4Poe5N58gJnJsLJ5vC7FYWGUmGhcsj4++WaIOBFVDxlG0W3To6xBuiRh+i0Qp2oNCOtg==", + "dev": true, + "requires": { + "buffer": "6.0.3" + }, + "dependencies": { + "buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + } + } + }, + "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 + }, + "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, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "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 + }, + "shelljs": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", + "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", + "dev": true, + "requires": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + }, + "dependencies": { + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "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" + } + } + } + }, + "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, + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, + "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 + }, + "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 + }, + "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, + "requires": { + "decompress-response": "^3.3.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + }, + "dependencies": { + "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" + } + } + } + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "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, + "requires": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "dependencies": { + "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, + "requires": { + "color-convert": "^2.0.1" + } + }, + "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, + "requires": { + "color-name": "~1.1.4" + } + }, + "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 + }, + "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 + } + } + }, + "smartwrap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/smartwrap/-/smartwrap-2.0.2.tgz", + "integrity": "sha512-vCsKNQxb7PnCNd2wY1WClWifAc2lwqsG8OaswpJkVJsvMGcnEntdTCDajZCkk93Ay1U3t/9puJmb525Rg5MZBA==", + "dev": true, + "requires": { + "array.prototype.flat": "^1.2.3", + "breakword": "^1.0.5", + "grapheme-splitter": "^1.0.4", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1", + "yargs": "^15.1.0" + }, + "dependencies": { + "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 + }, + "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, + "requires": { + "color-convert": "^2.0.1" + } + }, + "cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "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, + "requires": { + "color-name": "~1.1.4" + } + }, + "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 + }, + "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 + }, + "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, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "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, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "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 + }, + "yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dev": true, + "requires": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.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": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + } + } + } + }, + "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, + "requires": { + "no-case": "^2.2.0" + } + }, + "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, + "requires": { + "fs-extra": "^0.30.0", + "memorystream": "^0.3.1", + "require-from-string": "^1.1.0", + "semver": "^5.3.0", + "yargs": "^4.7.1" + }, + "dependencies": { + "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 + }, + "camelcase": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", + "integrity": "sha512-4nhGqUkc4BqbBBB4Q6zLuD7lzzrHYrjKGeYaEji/3tFR5VdJu9v+LilhGIVe8wxEJPPOeWo7eg8dwY13TZ1BNg==", + "dev": true + }, + "cliui": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", + "integrity": "sha512-0yayqDxWQbqk3ojkYqUKqaAQ6AfNKeKWRNA8kR0WXzAsdHpP4BIaOmMAG87JGuO6qcobyW4GjxHd9PmhEd+T9w==", + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wrap-ansi": "^2.0.0" + } + }, + "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, + "requires": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "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, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^2.1.0", + "klaw": "^1.0.0", + "path-is-absolute": "^1.0.0", + "rimraf": "^2.2.8" + } + }, + "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 + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "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" + } + }, + "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, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "jsonfile": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", + "integrity": "sha512-PKllAqbgLgxHaj8TElYymKCAgrASebJrWpTnEkOaTowt23VKXXN0sUeriJ+eh7y6ufb/CC5ap11pz71/cM0hUw==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "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, + "requires": { + "pinkie-promise": "^2.0.0" + } + }, + "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, + "requires": { + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true + }, + "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, + "requires": { + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" + } + }, + "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, + "requires": { + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" + } + }, + "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 + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "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, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "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, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "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 + }, + "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, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + } + }, + "y18n": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.2.tgz", + "integrity": "sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ==", + "dev": true + }, + "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, + "requires": { + "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" + } + }, + "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, + "requires": { + "camelcase": "^3.0.0", + "lodash.assign": "^4.0.6" + } + } + } + }, + "solhint": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/solhint/-/solhint-3.4.1.tgz", + "integrity": "sha512-pzZn2RlZhws1XwvLPVSsxfHrwsteFf5eySOhpAytzXwKQYbTCJV6z8EevYDiSVKMpWrvbKpEtJ055CuEmzp4Xg==", + "dev": true, + "requires": { + "@solidity-parser/parser": "^0.16.0", + "ajv": "^6.12.6", + "antlr4": "^4.11.0", + "ast-parents": "^0.0.1", + "chalk": "^4.1.2", + "commander": "^10.0.0", + "cosmiconfig": "^8.0.0", + "fast-diff": "^1.2.0", + "glob": "^8.0.3", + "ignore": "^5.2.4", + "js-yaml": "^4.1.0", + "lodash": "^4.17.21", + "pluralize": "^8.0.0", + "prettier": "^2.8.3", + "semver": "^6.3.0", + "strip-ansi": "^6.0.1", + "table": "^6.8.1", + "text-table": "^0.2.0" + }, + "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-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "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, + "requires": { + "color-convert": "^2.0.1" + } + }, + "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 + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "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, + "requires": { + "color-name": "~1.1.4" + } + }, + "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 + }, + "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 + }, + "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, + "requires": { + "argparse": "^2.0.1" + } + }, + "prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "dev": true, + "optional": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "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, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "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, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "solhint-plugin-openzeppelin": { + "version": "file:scripts/solhint-custom" + }, + "solidity-ast": { + "version": "0.4.50", + "resolved": "https://registry.npmjs.org/solidity-ast/-/solidity-ast-0.4.50.tgz", + "integrity": "sha512-WpIhaUibbjcBY4bg8TO2UXFWl8PQPhtH1QtMYJUqFUGxx0rRiEFsVLV+ow8XiWEnSPeu4xPp1/K43P4esxuK1Q==", + "dev": true, + "requires": { + "array.prototype.findlast": "^1.2.2" + } + }, + "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, + "requires": { + "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" + } + }, + "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==", + "dev": true, + "optional": true + }, + "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==", + "dev": true, + "optional": true + }, + "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 + }, + "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==", + "dev": true, + "optional": true + }, + "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==", + "dev": true, + "optional": true + }, + "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==", + "dev": true, + "optional": true + }, + "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==", + "dev": true, + "optional": true + }, + "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==", + "dev": true, + "optional": true + }, + "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==", + "dev": true, + "optional": true + }, + "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==", + "dev": true, + "optional": true + }, + "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==", + "dev": true, + "optional": true + }, + "solidity-coverage": { + "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.16.0", + "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" + }, + "dependencies": { + "@solidity-parser/parser": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.16.1.tgz", + "integrity": "sha512-PdhRFNhbTtu3x8Axm0uYpqOy/lODYQK+MlYSgqIsq2L8SFYEHJPHNUiOTAJbDGzNjjr1/n9AcIayxafR/fWmYw==", + "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", + "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", + "dev": true + }, + "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 + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "chokidar": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.0.tgz", + "integrity": "sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A==", + "dev": true, + "requires": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.1.1", + "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" + } + }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + } + }, + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "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 + }, + "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, + "requires": { + "locate-path": "^3.0.0" + } + }, + "flat": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.1.tgz", + "integrity": "sha512-FmTtBsHskrU6FJ2VxCnsDb84wu9zhmO3cUX2kGFb5tuwhfXxGciiT0oRY+cck35QmG+NmGh5eLz6lLCpWTqwpA==", + "dev": true, + "requires": { + "is-buffer": "~2.0.3" + } + }, + "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" + } + }, + "fsevents": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", + "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "dev": true, + "optional": true + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "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" + } + }, + "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, + "requires": { + "@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" + } + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "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, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "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, + "requires": { + "chalk": "^2.4.2" + } + }, + "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, + "requires": { + "minimist": "^1.2.5" + } + }, + "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, + "requires": { + "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" + }, + "dependencies": { + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "requires": { + "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" + } + }, + "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" + } + } + } + }, + "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 + }, + "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, + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, + "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, + "requires": { + "p-try": "^2.0.0" + } + }, + "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, + "requires": { + "p-limit": "^2.0.0" + } + }, + "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 + }, + "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, + "requires": { + "picomatch": "^2.0.4" + } + }, + "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, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "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, + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "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 + }, + "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, + "requires": { + "has-flag": "^3.0.0" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "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, + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + } + }, + "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 + }, + "yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "dev": true, + "requires": { + "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" + } + }, + "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, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, + "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, + "requires": { + "flat": "^4.1.0", + "lodash": "^4.17.15", + "yargs": "^13.3.0" + } + } + } + }, + "solidity-docgen": { + "version": "0.6.0-beta.35", + "resolved": "https://registry.npmjs.org/solidity-docgen/-/solidity-docgen-0.6.0-beta.35.tgz", + "integrity": "sha512-9QdwK1THk/MWIdq1PEW/6dvtND0pUqpFTsbKwwU9YQIMYuRhH1lek9SsgnsGGYtdJ0VTrXXcVT30q20a8Y610A==", + "dev": true, + "requires": { + "handlebars": "^4.7.7", + "solidity-ast": "^0.4.38" + } + }, + "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, + "requires": { + "amdefine": ">=0.0.4" + } + }, + "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, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + }, + "dependencies": { + "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 + } + } + }, + "spawndamnit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/spawndamnit/-/spawndamnit-2.0.0.tgz", + "integrity": "sha512-j4JKEcncSjFlqIwU5L/rp2N5SIPsdxaRsIv678+TZxZ0SRDJTm8JrxJMjE/XuiEZNEir3S8l0Fa3Ke339WI4qA==", + "dev": true, + "requires": { + "cross-spawn": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "dependencies": { + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==", + "dev": true, + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "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, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "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 + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", + "dev": true + } + } + }, + "spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "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 + }, + "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, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.13", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.13.tgz", + "integrity": "sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w==", + "dev": true + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "sshpk": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", + "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==", + "dev": true, + "requires": { + "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" + }, + "dependencies": { + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", + "dev": true + } + } + }, + "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, + "requires": { + "type-fest": "^0.7.1" + }, + "dependencies": { + "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 + } + } + }, + "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 + }, + "stream-transform": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/stream-transform/-/stream-transform-2.1.3.tgz", + "integrity": "sha512-9GHUiM5hMiCi6Y03jD2ARC1ettBXkQBoQAe7nJsPknnI0ow10aXjTnew8QtYQmLjzn974BnmWEAJgCY6ZP1DeQ==", + "dev": true, + "requires": { + "mixme": "^0.5.1" + } + }, + "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", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "string.prototype.trim": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz", + "integrity": "sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "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, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "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, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "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, + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "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 + }, + "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, + "requires": { + "is-hex-prefixed": "1.0.0" + } + }, + "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 + }, + "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 + }, + "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" + } + }, + "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, + "requires": { + "lower-case": "^1.1.1", + "upper-case": "^1.1.1" + } + }, + "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, + "requires": { + "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" + }, + "dependencies": { + "@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, + "requires": { + "defer-to-connect": "^2.0.0" + } + }, + "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 + }, + "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, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "got": { + "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", + "@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" + } + }, + "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, + "requires": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.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 + }, + "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 + }, + "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 + } + } + }, + "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, + "requires": { + "http-response-object": "^3.0.1", + "sync-rpc": "^1.2.1", + "then-request": "^6.0.0" + } + }, + "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, + "requires": { + "get-port": "^3.1.0" + } + }, + "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, + "requires": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "dependencies": { + "ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "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 + }, + "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 + }, + "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 + }, + "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 + }, + "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, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "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, + "requires": { + "ansi-regex": "^5.0.1" + } + } + } + }, + "tar": { + "version": "4.4.19", + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.19.tgz", + "integrity": "sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA==", + "dev": true, + "requires": { + "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" + } + }, + "term-size": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz", + "integrity": "sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==", + "dev": true + }, + "testrpc": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/testrpc/-/testrpc-0.0.1.tgz", + "integrity": "sha512-afH1hO+SQ/VPlmaLUFj2636QMeDvPCeQMc/9RBMW0IfjNe9gFD9Ra3ShqYkB7py0do1ZcCna/9acHyzTJ+GcNA==", + "dev": true + }, + "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 + }, + "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, + "requires": { + "@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" + }, + "dependencies": { + "@types/node": { + "version": "8.10.66", + "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.66.tgz", + "integrity": "sha512-tktOkFUA4kXx2hhhrB8bIFb5TbwzS4uOhKEmwiD+NoiL0qtP2OQ9mFldbgD4dV1djrlBYP6eBuQZiWjuHUpqFw==", + "dev": true + } + } + }, + "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 + }, + "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, + "requires": { + "no-case": "^2.2.0", + "upper-case": "^1.0.3" + } + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.2" + } + }, + "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, + "requires": { + "is-number": "^7.0.0" + } + }, + "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 + }, + "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, + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + }, + "dependencies": { + "punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "dev": true + } + } + }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true + }, + "treeify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/treeify/-/treeify-1.1.0.tgz", + "integrity": "sha512-1m4RA7xVAJrSGrrXGs0L3YTwyvBs2S8PbRHaLZAkFw7JR8oIFwYtysxlBZhYIa7xSyiYJKZ3iGrrk55cGA3i9A==", + "dev": true + }, + "trim-newlines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", + "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", + "dev": true + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "tsort": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/tsort/-/tsort-0.0.1.tgz", + "integrity": "sha512-Tyrf5mxF8Ofs1tNoxA13lFeZ2Zrbd6cKbuH3V+MQ5sb6DtBj5FjrXVsRWT8YvNAQTqNoz66dz1WsbigI22aEnw==", + "dev": true + }, + "tty-table": { + "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.3", + "kleur": "^4.1.5", + "smartwrap": "^2.0.2", + "strip-ansi": "^6.0.1", + "wcwidth": "^1.0.1", + "yargs": "^17.7.1" + }, + "dependencies": { + "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 + }, + "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, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "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, + "requires": { + "color-name": "~1.1.4" + } + }, + "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 + }, + "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 + }, + "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, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "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, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "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, + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", + "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", + "dev": true + }, + "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 + }, + "type": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", + "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", + "dev": true + }, + "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, + "requires": { + "prelude-ls": "^1.2.1" + } + }, + "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 + }, + "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 + }, + "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, + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "typed-array-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", + "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "is-typed-array": "^1.1.9" + } + }, + "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 + }, + "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, + "requires": { + "is-typedarray": "^1.0.0" + } + }, + "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 + }, + "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 + }, + "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, + "requires": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + } + }, + "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 + }, + "undici": { + "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" + } + }, + "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 + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true + }, + "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 + }, + "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, + "requires": { + "upper-case": "^1.1.1" + } + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "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 + }, + "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, + "requires": { + "node-gyp-build": "^4.3.0" + } + }, + "utf8": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/utf8/-/utf8-3.0.0.tgz", + "integrity": "sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==", + "dev": true + }, + "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, + "requires": { + "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" + } + }, + "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 + }, + "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 + }, + "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 + }, + "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, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "varint": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/varint/-/varint-5.0.2.tgz", + "integrity": "sha512-lKxKYG6H03yCZUpAGOPOsMcGxd1RHCu1iKvEHYDPmTyq2HueGhD73ssNBqqQWfvYs04G9iUFRvmAVLW20Jw6ow==", + "dev": true + }, + "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 + }, + "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, + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dev": true, + "requires": { + "defaults": "^1.0.3" + } + }, + "web3": { + "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.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.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", + "got": "12.1.0", + "swarm-js": "^0.1.40" + } + }, + "web3-core": { + "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.10.0", + "web3-core-method": "1.10.0", + "web3-core-requestmanager": "1.10.0", + "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 + } + } + }, + "web3-core-helpers": { + "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.10.0", + "web3-utils": "1.10.0" + } + }, + "web3-core-method": { + "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.10.0", + "web3-core-promievent": "1.10.0", + "web3-core-subscriptions": "1.10.0", + "web3-utils": "1.10.0" + } + }, + "web3-core-promievent": { + "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.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.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.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.10.0" + } + }, + "web3-eth": { + "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.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.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.10.0" + } + }, + "web3-eth-accounts": { + "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", + "@ethereumjs/tx": "3.3.2", + "eth-lib": "0.2.8", + "ethereumjs-util": "^7.1.5", + "scrypt-js": "^3.0.1", + "uuid": "^9.0.0", + "web3-core": "1.10.0", + "web3-core-helpers": "1.10.0", + "web3-core-method": "1.10.0", + "web3-utils": "1.10.0" + }, + "dependencies": { + "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-eth-contract": { + "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.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.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.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.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.10.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 + } + } + }, + "web3-eth-personal": { + "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.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.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.10.0", + "web3-core-method": "1.10.0", + "web3-utils": "1.10.0" + } + }, + "web3-providers-http": { + "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.10.0" + } + }, + "web3-providers-ipc": { + "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.10.0" + } + }, + "web3-providers-ws": { + "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.10.0", + "websocket": "^1.0.32" + } + }, + "web3-shh": { + "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.10.0", + "web3-core-method": "1.10.0", + "web3-core-subscriptions": "1.10.0", + "web3-net": "1.10.0" + } + }, + "web3-utils": { + "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", + "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 + } + } + }, + "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 + }, + "websocket": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.34.tgz", + "integrity": "sha512-PRDso2sGwF6kM75QykIesBijKSVceR6jL2G8NGYyq2XrItNC2P5/qL5XeR056GhA+Ly7JMFvJb9I312mJfmqnQ==", + "dev": true, + "requires": { + "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" + }, + "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 + } + } + }, + "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, + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "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, + "requires": { + "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" + } + }, + "which-module": { + "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": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-pm/-/which-pm-2.0.0.tgz", + "integrity": "sha512-Lhs9Pmyph0p5n5Z3mVnN0yWcbQYUAD7rbQUiMsQxOJ3T57k7RFe35SUwWMf7dsbDZks1uOmw4AecB/JMDj3v/w==", + "dev": true, + "requires": { + "load-yaml-file": "^0.2.0", + "path-exists": "^4.0.0" + } + }, + "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, + "requires": { + "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" + } + }, + "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, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "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 + }, + "word-wrap": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz", + "integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==", + "dev": true + }, + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true + }, + "workerpool": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", + "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", + "dev": true + }, + "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, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "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 + }, + "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, + "requires": { + "color-convert": "^2.0.1" + } + }, + "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, + "requires": { + "color-name": "~1.1.4" + } + }, + "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 + }, + "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 + }, + "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, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "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, + "requires": { + "ansi-regex": "^5.0.1" + } + } + } + }, + "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 + }, + "ws": { + "version": "7.5.9", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", + "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "dev": true, + "requires": {} + }, + "xhr": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/xhr/-/xhr-2.6.0.tgz", + "integrity": "sha512-/eCGLb5rxjx5e3mF1A7s+pLlR6CGyqWN91fv1JgER5mVWg1MZmlhBvy9kjcsOdRk8RrIujotWyJamfyrp+WIcA==", + "dev": true, + "requires": { + "global": "~4.4.0", + "is-function": "^1.0.1", + "parse-headers": "^2.0.0", + "xtend": "^4.0.0" + } + }, + "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, + "requires": { + "buffer-to-arraybuffer": "^0.0.5", + "object-assign": "^4.1.1", + "query-string": "^5.0.1", + "simple-get": "^2.7.0", + "timed-out": "^4.0.1", + "url-set-query": "^1.0.0", + "xhr": "^2.0.4" + } + }, + "xhr-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, + "requires": { + "xhr-request": "^1.1.0" + } + }, + "xmlhttprequest": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz", + "integrity": "sha512-58Im/U0mlVBLM38NdZjHyhuMtCqa61469k2YP/AaPbvCoV9aQGUpbJBj1QRm2ytRiVQBD/fsw7L2bJGDVQswBA==", + "dev": true + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true + }, + "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 + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "yargs": { + "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", + "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" + }, + "dependencies": { + "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 + }, + "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 + }, + "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, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "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, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "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 + } + } + }, + "yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "dependencies": { + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + } + } + }, + "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, + "requires": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "dependencies": { + "camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true + }, + "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 + }, + "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 + } + } + }, + "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 + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 000000000..e0dddef55 --- /dev/null +++ b/package.json @@ -0,0 +1,95 @@ +{ + "name": "openzeppelin-solidity", + "description": "Secure Smart Contract library for Solidity", + "version": "5.0.0-rc.0", + "files": [ + "/contracts/**/*.sol", + "/build/contracts/*.json", + "!/contracts/mocks/**/*" + ], + "scripts": { + "compile": "hardhat compile", + "coverage": "env COVERAGE=true hardhat coverage", + "docs": "npm run prepare-docs && oz-docs", + "docs:watch": "oz-docs watch contracts docs/templates docs/config.js", + "prepare-docs": "scripts/prepare-docs.sh", + "lint": "npm run lint:js && npm run lint:sol", + "lint:fix": "npm run lint:js:fix && npm run lint:sol:fix", + "lint:js": "prettier --log-level warn --ignore-path .gitignore '**/*.{js,ts}' --check && eslint --ignore-path .gitignore .", + "lint:js:fix": "prettier --log-level warn --ignore-path .gitignore '**/*.{js,ts}' --write && eslint --ignore-path .gitignore . --fix", + "lint:sol": "prettier --log-level warn --ignore-path .gitignore '{contracts,test}/**/*.sol' --check && solhint '{contracts,test}/**/*.sol'", + "lint:sol:fix": "prettier --log-level warn --ignore-path .gitignore '{contracts,test}/**/*.sol' --write", + "clean": "hardhat clean && rimraf build contracts/build", + "prepare": "scripts/prepare.sh", + "prepack": "scripts/prepack.sh", + "generate": "scripts/generate/run.js", + "release": "scripts/release/release.sh", + "version": "scripts/release/version.sh", + "test": "hardhat test", + "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 ." + }, + "repository": { + "type": "git", + "url": "https://github.com/OpenZeppelin/openzeppelin-contracts.git" + }, + "keywords": [ + "solidity", + "ethereum", + "smart", + "contracts", + "security", + "zeppelin" + ], + "author": "OpenZeppelin Community ", + "license": "MIT", + "bugs": { + "url": "https://github.com/OpenZeppelin/openzeppelin-contracts/issues" + }, + "homepage": "https://openzeppelin.com/contracts/", + "devDependencies": { + "@changesets/changelog-github": "^0.4.8", + "@changesets/cli": "^2.26.0", + "@changesets/pre": "^1.0.14", + "@changesets/read": "^0.5.9", + "@nomicfoundation/hardhat-network-helpers": "^1.0.3", + "@nomiclabs/hardhat-truffle5": "^2.0.5", + "@nomiclabs/hardhat-web3": "^2.0.0", + "@openzeppelin/docs-utils": "^0.1.4", + "@openzeppelin/test-helpers": "^0.5.13", + "@openzeppelin/upgrades-core": "^1.20.6", + "array.prototype.at": "^1.1.1", + "chai": "^4.2.0", + "eslint": "^8.30.0", + "eslint-config-prettier": "^9.0.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-exposed": "^0.3.11", + "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", + "p-limit": "^3.1.0", + "prettier": "^3.0.0", + "prettier-plugin-solidity": "^1.1.0", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "solhint": "^3.3.6", + "solhint-plugin-openzeppelin": "file:scripts/solhint-custom", + "solidity-ast": "^0.4.50", + "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/remappings.txt b/remappings.txt new file mode 100644 index 000000000..304d1386a --- /dev/null +++ b/remappings.txt @@ -0,0 +1 @@ +@openzeppelin/contracts/=contracts/ diff --git a/renovate.json b/renovate.json new file mode 100644 index 000000000..c0b97d8d7 --- /dev/null +++ b/renovate.json @@ -0,0 +1,4 @@ +{ + "extends": ["github>OpenZeppelin/configs"], + "labels": ["ignore-changeset"] +} diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 000000000..fd0ec3019 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +certora-cli==4.8.0 diff --git a/scripts/checks/compare-layout.js b/scripts/checks/compare-layout.js new file mode 100644 index 000000000..4368b77fb --- /dev/null +++ b/scripts/checks/compare-layout.js @@ -0,0 +1,20 @@ +const fs = require('fs'); +const { getStorageUpgradeReport } = require('@openzeppelin/upgrades-core/dist/storage'); + +const { ref, head } = require('yargs').argv; + +const oldLayout = JSON.parse(fs.readFileSync(ref)); +const newLayout = JSON.parse(fs.readFileSync(head)); + +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; + } + } else { + console.log(`WARNING: ${name} is missing from the current branch`); + } +} diff --git a/scripts/checks/compareGasReports.js b/scripts/checks/compareGasReports.js new file mode 100755 index 000000000..160c8cc52 --- /dev/null +++ b/scripts/checks/compareGasReports.js @@ -0,0 +1,243 @@ +#!/usr/bin/env node + +const fs = require('fs'); +const chalk = require('chalk'); +const { argv } = require('yargs') + .env() + .options({ + style: { + type: 'string', + choices: ['shell', 'markdown'], + default: 'shell', + }, + hideEqual: { + type: 'boolean', + default: true, + }, + strictTesting: { + type: 'boolean', + default: false, + }, + }); + +// Deduce base tx cost from the percentage denominator +const BASE_TX_COST = 21000; + +// Utilities +function sum(...args) { + return args.reduce((a, b) => a + b, 0); +} + +function average(...args) { + return sum(...args) / args.length; +} + +function variation(current, previous, offset = 0) { + return { + value: current, + delta: current - previous, + prcnt: (100 * (current - previous)) / (previous - offset), + }; +} + +// Report class +class Report { + // Read report file + static load(filepath) { + return JSON.parse(fs.readFileSync(filepath, 'utf8')); + } + + // Compare two reports + static compare(update, ref, opts = { hideEqual: true, strictTesting: false }) { + if (JSON.stringify(update.config.metadata) !== JSON.stringify(ref.config.metadata)) { + throw new Error('Reports produced with non matching metadata'); + } + + const deployments = update.info.deployments + .map(contract => + Object.assign(contract, { previousVersion: ref.info.deployments.find(({ name }) => name === contract.name) }), + ) + .filter(contract => contract.gasData?.length && contract.previousVersion?.gasData?.length) + .flatMap(contract => [ + { + contract: contract.name, + method: '[bytecode length]', + avg: variation(contract.bytecode.length / 2 - 1, contract.previousVersion.bytecode.length / 2 - 1), + }, + { + contract: contract.name, + method: '[construction cost]', + avg: variation( + ...[contract.gasData, contract.previousVersion.gasData].map(x => Math.round(average(...x))), + BASE_TX_COST, + ), + }, + ]) + .sort((a, b) => `${a.contract}:${a.method}`.localeCompare(`${b.contract}:${b.method}`)); + + const methods = Object.keys(update.info.methods) + .filter(key => ref.info.methods[key]) + .filter(key => update.info.methods[key].numberOfCalls > 0) + .filter( + key => !opts.strictTesting || update.info.methods[key].numberOfCalls === ref.info.methods[key].numberOfCalls, + ) + .map(key => ({ + contract: ref.info.methods[key].contract, + method: ref.info.methods[key].fnSig, + min: variation(...[update, ref].map(x => Math.min(...x.info.methods[key].gasData)), BASE_TX_COST), + max: variation(...[update, ref].map(x => Math.max(...x.info.methods[key].gasData)), BASE_TX_COST), + avg: variation(...[update, ref].map(x => Math.round(average(...x.info.methods[key].gasData))), BASE_TX_COST), + })) + .sort((a, b) => `${a.contract}:${a.method}`.localeCompare(`${b.contract}:${b.method}`)); + + return [] + .concat(deployments, methods) + .filter(row => !opts.hideEqual || row.min?.delta || row.max?.delta || row.avg?.delta); + } +} + +// Display +function center(text, length) { + return text.padStart((text.length + length) / 2).padEnd(length); +} + +function plusSign(num) { + return num > 0 ? '+' : ''; +} + +function formatCellShell(cell) { + const format = chalk[cell?.delta > 0 ? 'red' : cell?.delta < 0 ? 'green' : 'reset']; + return [ + format((!isFinite(cell?.value) ? '-' : cell.value.toString()).padStart(8)), + format((!isFinite(cell?.delta) ? '-' : plusSign(cell.delta) + cell.delta.toString()).padStart(8)), + format((!isFinite(cell?.prcnt) ? '-' : plusSign(cell.prcnt) + cell.prcnt.toFixed(2) + '%').padStart(8)), + ]; +} + +function formatCmpShell(rows) { + const contractLength = Math.max(8, ...rows.map(({ contract }) => contract.length)); + const methodLength = Math.max(7, ...rows.map(({ method }) => method.length)); + + const COLS = [ + { txt: '', length: 0 }, + { txt: 'Contract', length: contractLength }, + { txt: 'Method', length: methodLength }, + { txt: 'Min', length: 30 }, + { txt: 'Max', length: 30 }, + { txt: 'Avg', length: 30 }, + { txt: '', length: 0 }, + ]; + const HEADER = COLS.map(entry => chalk.bold(center(entry.txt, entry.length || 0))) + .join(' | ') + .trim(); + const SEPARATOR = COLS.map(({ length }) => (length > 0 ? '-'.repeat(length + 2) : '')) + .join('|') + .trim(); + + return [ + '', + HEADER, + ...rows.map(entry => + [ + '', + chalk.grey(entry.contract.padEnd(contractLength)), + entry.method.padEnd(methodLength), + ...formatCellShell(entry.min), + ...formatCellShell(entry.max), + ...formatCellShell(entry.avg), + '', + ] + .join(' | ') + .trim(), + ), + '', + ] + .join(`\n${SEPARATOR}\n`) + .trim(); +} + +function alignPattern(align) { + switch (align) { + case 'left': + case undefined: + return ':-'; + case 'right': + return '-:'; + case 'center': + return ':-:'; + } +} + +function trend(value) { + return value > 0 ? ':x:' : value < 0 ? ':heavy_check_mark:' : ':heavy_minus_sign:'; +} + +function formatCellMarkdown(cell) { + return [ + !isFinite(cell?.value) ? '-' : cell.value.toString(), + !isFinite(cell?.delta) ? '-' : plusSign(cell.delta) + cell.delta.toString(), + !isFinite(cell?.prcnt) ? '-' : plusSign(cell.prcnt) + cell.prcnt.toFixed(2) + '%' + trend(cell.delta), + ]; +} + +function formatCmpMarkdown(rows) { + const COLS = [ + { txt: '' }, + { txt: 'Contract', align: 'left' }, + { txt: 'Method', align: 'left' }, + { txt: 'Min', align: 'right' }, + { txt: '(+/-)', align: 'right' }, + { txt: '%', align: 'right' }, + { txt: 'Max', align: 'right' }, + { txt: '(+/-)', align: 'right' }, + { txt: '%', align: 'right' }, + { txt: 'Avg', align: 'right' }, + { txt: '(+/-)', align: 'right' }, + { txt: '%', align: 'right' }, + { txt: '' }, + ]; + const HEADER = COLS.map(entry => entry.txt) + .join(' | ') + .trim(); + const SEPARATOR = COLS.map(entry => (entry.txt ? alignPattern(entry.align) : '')) + .join('|') + .trim(); + + return [ + '# Changes to gas costs', + '', + HEADER, + SEPARATOR, + rows + .map(entry => + [ + '', + entry.contract, + entry.method, + ...formatCellMarkdown(entry.min), + ...formatCellMarkdown(entry.max), + ...formatCellMarkdown(entry.avg), + '', + ] + .join(' | ') + .trim(), + ) + .join('\n'), + '', + ] + .join('\n') + .trim(); +} + +// MAIN +const report = Report.compare(Report.load(argv._[0]), Report.load(argv._[1]), argv); + +switch (argv.style) { + case 'markdown': + console.log(formatCmpMarkdown(report)); + break; + case 'shell': + default: + console.log(formatCmpShell(report)); + break; +} diff --git a/scripts/checks/extract-layout.js b/scripts/checks/extract-layout.js new file mode 100644 index 000000000..d0c9cf36b --- /dev/null +++ b/scripts/checks/extract-layout.js @@ -0,0 +1,40 @@ +const fs = require('fs'); +const { findAll } = require('solidity-ast/utils'); +const { astDereferencer } = require('@openzeppelin/upgrades-core/dist/ast-dereferencer'); +const { solcInputOutputDecoder } = require('@openzeppelin/upgrades-core/dist/src-decoder'); +const { extractStorageLayout } = require('@openzeppelin/upgrades-core/dist/storage/extract'); + +const { _ } = require('yargs').argv; + +const skipPath = ['contracts/mocks/', 'contracts-exposed/']; +const skipKind = ['interface', 'library']; + +function extractLayouts(path) { + const layout = {}; + const { input, output } = JSON.parse(fs.readFileSync(path)); + + const decoder = solcInputOutputDecoder(input, output); + const deref = astDereferencer(output); + + for (const src in output.contracts) { + if (skipPath.some(prefix => src.startsWith(prefix))) { + continue; + } + + for (const contractDef of findAll('ContractDefinition', output.sources[src].ast)) { + if (skipKind.includes(contractDef.contractKind)) { + continue; + } + + layout[contractDef.name] = extractStorageLayout( + contractDef, + decoder, + deref, + output.contracts[src][contractDef.name].storageLayout, + ); + } + } + return layout; +} + +console.log(JSON.stringify(Object.assign(..._.map(extractLayouts)))); diff --git a/scripts/checks/generation.sh b/scripts/checks/generation.sh new file mode 100755 index 000000000..00d609f94 --- /dev/null +++ b/scripts/checks/generation.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +set -euo pipefail + +npm run generate +git diff -R --exit-code diff --git a/scripts/checks/inheritance-ordering.js b/scripts/checks/inheritance-ordering.js new file mode 100755 index 000000000..72aa37ef7 --- /dev/null +++ b/scripts/checks/inheritance-ordering.js @@ -0,0 +1,54 @@ +#!/usr/bin/env node + +const path = require('path'); +const graphlib = require('graphlib'); +const { findAll } = require('solidity-ast/utils'); +const { _: artifacts } = require('yargs').argv; + +for (const artifact of artifacts) { + const { output: solcOutput } = require(path.resolve(__dirname, '../..', artifact)); + + const graph = new graphlib.Graph({ directed: true }); + const names = {}; + const linearized = []; + + for (const source in solcOutput.contracts) { + if (['contracts-exposed/', 'contracts/mocks/'].some(pattern => source.startsWith(pattern))) { + continue; + } + + for (const contractDef of findAll('ContractDefinition', solcOutput.sources[source].ast)) { + names[contractDef.id] = contractDef.name; + linearized.push(contractDef.linearizedBaseContracts); + + contractDef.linearizedBaseContracts.forEach((c1, i, contracts) => + contracts.slice(i + 1).forEach(c2 => { + graph.setEdge(c1, c2); + }), + ); + } + } + + /// graphlib.alg.findCycles will not find minimal cycles. + /// We are only interested int cycles of lengths 2 (needs proof) + graph.nodes().forEach((x, i, nodes) => + nodes + .slice(i + 1) + .filter(y => graph.hasEdge(x, y) && graph.hasEdge(y, x)) + .forEach(y => { + console.log(`Conflict between ${names[x]} and ${names[y]} detected in the following dependency chains:`); + linearized + .filter(chain => chain.includes(parseInt(x)) && chain.includes(parseInt(y))) + .forEach(chain => { + const comp = chain.indexOf(parseInt(x)) < chain.indexOf(parseInt(y)) ? '>' : '<'; + console.log(`- ${names[x]} ${comp} ${names[y]} in ${names[chain.find(Boolean)]}`); + // console.log(`- ${names[x]} ${comp} ${names[y]}: ${chain.reverse().map(id => names[id]).join(', ')}`); + }); + process.exitCode = 1; + }), + ); +} + +if (!process.exitCode) { + console.log('Contract ordering is consistent.'); +} diff --git a/scripts/gen-nav.js b/scripts/gen-nav.js new file mode 100644 index 000000000..de3d0daba --- /dev/null +++ b/scripts/gen-nav.js @@ -0,0 +1,41 @@ +#!/usr/bin/env node + +const path = require('path'); +const glob = require('glob'); +const startCase = require('lodash.startcase'); + +const baseDir = process.argv[2]; + +const files = glob.sync(baseDir + '/**/*.adoc').map(f => path.relative(baseDir, f)); + +console.log('.API'); + +function getPageTitle(directory) { + switch (directory) { + case 'metatx': + return 'Meta Transactions'; + case 'common': + return 'Common (Tokens)'; + default: + return startCase(directory); + } +} + +const links = files.map(file => { + const doc = file.replace(baseDir, ''); + const title = path.parse(file).name; + + return { + xref: `* xref:${doc}[${getPageTitle(title)}]`, + title, + }; +}); + +// Case-insensitive sort based on titles (so 'token/ERC20' gets sorted as 'erc20') +const sortedLinks = links.sort(function (a, b) { + return a.title.toLowerCase().localeCompare(b.title.toLowerCase(), undefined, { numeric: true }); +}); + +for (const link of sortedLinks) { + console.log(link.xref); +} diff --git a/scripts/generate/format-lines.js b/scripts/generate/format-lines.js new file mode 100644 index 000000000..fa3d6b120 --- /dev/null +++ b/scripts/generate/format-lines.js @@ -0,0 +1,16 @@ +function formatLines(...lines) { + return [...indentEach(0, lines)].join('\n') + '\n'; +} + +function* indentEach(indent, lines) { + for (const line of lines) { + if (Array.isArray(line)) { + yield* indentEach(indent + 1, line); + } else { + const padding = ' '.repeat(indent); + yield* line.split('\n').map(subline => (subline === '' ? '' : padding + subline)); + } + } +} + +module.exports = formatLines; diff --git a/scripts/generate/run.js b/scripts/generate/run.js new file mode 100755 index 000000000..53589455a --- /dev/null +++ b/scripts/generate/run.js @@ -0,0 +1,49 @@ +#!/usr/bin/env node + +const cp = require('child_process'); +const fs = require('fs'); +const path = require('path'); +const format = require('./format-lines'); + +function getVersion(path) { + try { + return fs.readFileSync(path, 'utf8').match(/\/\/ OpenZeppelin Contracts \(last updated v[^)]+\)/)[0]; + } catch (err) { + return null; + } +} + +function generateFromTemplate(file, template, outputPrefix = '') { + const script = path.relative(path.join(__dirname, '../..'), __filename); + const input = path.join(path.dirname(script), template); + const output = path.join(outputPrefix, file); + const version = getVersion(output); + const content = format( + '// SPDX-License-Identifier: MIT', + ...(version ? [version + ` (${file})`] : []), + `// This file was procedurally generated from ${input}.`, + '', + require(template), + ); + + 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/structs/Checkpoints.sol': './templates/Checkpoints.js', + 'utils/StorageSlot.sol': './templates/StorageSlot.js', +})) { + generateFromTemplate(file, template, './contracts/'); +} + +// Tests +for (const [file, template] of Object.entries({ + '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 new file mode 100644 index 000000000..321a77489 --- /dev/null +++ b/scripts/generate/templates/Checkpoints.js @@ -0,0 +1,247 @@ +const format = require('../format-lines'); +const { OPTS } = require('./Checkpoints.opts.js'); + +// TEMPLATE +const header = `\ +pragma solidity ^0.8.20; + +import {Math} from "../math/Math.sol"; + +/** + * @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.Trace*\` in your contract, and store a new + * checkpoint for the current transaction block using the {push} function. + */ +`; + +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}; +} + +struct ${opts.checkpointTypeName} { + ${opts.keyTypeName} ${opts.keyFieldName}; + ${opts.valueTypeName} ${opts.valueFieldName}; +} + +/** + * @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, + ${opts.keyTypeName} key, + ${opts.valueTypeName} value +) internal returns (${opts.valueTypeName}, ${opts.valueTypeName}) { + return _insert(self.${opts.checkpointFieldName}, key, value); +} + +/** + * @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; + uint256 pos = _lowerBinaryLookup(self.${opts.checkpointFieldName}, key, 0, len); + return pos == len ? 0 : _unsafeAccess(self.${opts.checkpointFieldName}, pos).${opts.valueFieldName}; +} + +/** + * @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; + 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 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). + */ +function upperLookupRecent(${opts.historyTypeName} storage self, ${opts.keyTypeName} key) internal view returns (${opts.valueTypeName}) { + 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)._key) { + 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 Returns the value in the most recent checkpoint, or zero if there are no checkpoints. + */ +function latest(${opts.historyTypeName} storage self) internal view returns (${opts.valueTypeName}) { + uint256 pos = self.${opts.checkpointFieldName}.length; + return pos == 0 ? 0 : _unsafeAccess(self.${opts.checkpointFieldName}, pos - 1).${opts.valueFieldName}; +} + +/** + * @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(${opts.historyTypeName} storage self) + internal + view + returns ( + bool exists, + ${opts.keyTypeName} ${opts.keyFieldName}, + ${opts.valueTypeName} ${opts.valueFieldName} + ) +{ + uint256 pos = self.${opts.checkpointFieldName}.length; + if (pos == 0) { + return (false, 0, 0); + } else { + ${opts.checkpointTypeName} memory ckpt = _unsafeAccess(self.${opts.checkpointFieldName}, pos - 1); + return (true, ckpt.${opts.keyFieldName}, ckpt.${opts.valueFieldName}); + } +} + +/** + * @dev Returns the number of checkpoint. + */ +function length(${opts.historyTypeName} storage self) internal view returns (uint256) { + return self.${opts.checkpointFieldName}.length; +} + +/** + * @dev Returns checkpoint at given position. + */ +function at(${opts.historyTypeName} storage self, uint32 pos) internal view returns (${opts.checkpointTypeName} memory) { + return self.${opts.checkpointFieldName}[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( + ${opts.checkpointTypeName}[] storage self, + ${opts.keyTypeName} key, + ${opts.valueTypeName} value +) private returns (${opts.valueTypeName}, ${opts.valueTypeName}) { + uint256 pos = self.length; + + if (pos > 0) { + // Copying to memory is important here. + ${opts.checkpointTypeName} memory last = _unsafeAccess(self, pos - 1); + + // Checkpoint keys must be non-decreasing. + if(last.${opts.keyFieldName} > key) { + revert CheckpointUnorderedInsertion(); + } + + // Update or push new checkpoint + if (last.${opts.keyFieldName} == key) { + _unsafeAccess(self, pos - 1).${opts.valueFieldName} = value; + } else { + self.push(${opts.checkpointTypeName}({${opts.keyFieldName}: key, ${opts.valueFieldName}: value})); + } + return (last.${opts.valueFieldName}, value); + } else { + self.push(${opts.checkpointTypeName}({${opts.keyFieldName}: key, ${opts.valueFieldName}: 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( + ${opts.checkpointTypeName}[] storage self, + ${opts.keyTypeName} key, + uint256 low, + uint256 high +) private view returns (uint256) { + while (low < high) { + uint256 mid = Math.average(low, high); + if (_unsafeAccess(self, mid).${opts.keyFieldName} > 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( + ${opts.checkpointTypeName}[] storage self, + ${opts.keyTypeName} key, + uint256 low, + uint256 high +) private view returns (uint256) { + while (low < high) { + uint256 mid = Math.average(low, high); + if (_unsafeAccess(self, mid).${opts.keyFieldName} < 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(${opts.checkpointTypeName}[] storage self, uint256 pos) + private + pure + returns (${opts.checkpointTypeName} storage result) +{ + assembly { + mstore(0, self.slot) + result.slot := add(keccak256(0, 0x20), pos) + } +} +`; +/* eslint-enable max-len */ + +// GENERATE +module.exports = format( + header.trimEnd(), + 'library Checkpoints {', + errors, + OPTS.flatMap(opts => template(opts)), + '}', +); diff --git a/scripts/generate/templates/Checkpoints.opts.js b/scripts/generate/templates/Checkpoints.opts.js new file mode 100644 index 000000000..08b7b910b --- /dev/null +++ b/scripts/generate/templates/Checkpoints.opts.js @@ -0,0 +1,17 @@ +// OPTIONS +const VALUE_SIZES = [224, 208, 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 = { + VALUE_SIZES, + OPTS: VALUE_SIZES.map(size => defaultOpts(size)), +}; diff --git a/scripts/generate/templates/Checkpoints.t.js b/scripts/generate/templates/Checkpoints.t.js new file mode 100644 index 000000000..d21beb53e --- /dev/null +++ b/scripts/generate/templates/Checkpoints.t.js @@ -0,0 +1,146 @@ +const format = require('../format-lines'); +const { capitalize } = require('../../helpers'); +const { OPTS } = require('./Checkpoints.opts.js'); + +// TEMPLATE +const header = `\ +pragma solidity ^0.8.20; + +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 */ +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 +// 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); +} + +// 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]; + if (lastKey > 0) { + 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); +} +`; + +// GENERATE +module.exports = format( + header, + ...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 new file mode 100644 index 000000000..7dbe6ca7f --- /dev/null +++ b/scripts/generate/templates/EnumerableMap.js @@ -0,0 +1,283 @@ +const format = require('../format-lines'); +const { fromBytes32, toBytes32 } = require('./conversion'); + +const TYPES = [ + { name: 'UintToUintMap', keyType: 'uint256', valueType: 'uint256' }, + { name: 'UintToAddressMap', keyType: 'uint256', valueType: 'address' }, + { name: 'AddressToUintMap', keyType: 'address', valueType: 'uint256' }, + { name: 'Bytes32ToUintMap', keyType: 'bytes32', valueType: 'uint256' }, +]; + +/* eslint-disable max-len */ +const header = `\ +pragma solidity ^0.8.20; + +import {EnumerableSet} from "./EnumerableSet.sol"; + +/** + * @dev Library for managing an enumerable variant of Solidity's + * https://solidity.readthedocs.io/en/latest/types.html#mapping-types[\`mapping\`] + * type. + * + * Maps have the following properties: + * + * - Entries are added, removed, and checked for existence in constant time + * (O(1)). + * - Entries are enumerated in O(n). No guarantees are made on the ordering. + * + * \`\`\`solidity + * contract Example { + * // Add the library methods + * using EnumerableMap for EnumerableMap.UintToAddressMap; + * + * // Declare a set state variable + * EnumerableMap.UintToAddressMap private myMap; + * } + * \`\`\` + * + * The following map types are supported: + * + * - \`uint256 -> address\` (\`UintToAddressMap\`) since v3.0.0 + * - \`address -> uint256\` (\`AddressToUintMap\`) since v4.6.0 + * - \`bytes32 -> bytes32\` (\`Bytes32ToBytes32Map\`) since v4.6.0 + * - \`uint256 -> uint256\` (\`UintToUintMap\`) since v4.7.0 + * - \`bytes32 -> uint256\` (\`Bytes32ToUintMap\`) since v4.7.0 + * + * [WARNING] + * ==== + * Trying to delete such a structure from storage will likely result in data corruption, rendering the structure + * unusable. + * See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info. + * + * In order to clean an EnumerableMap, you can either remove all elements one by one or create a fresh instance using an + * array of EnumerableMap. + * ==== + */ +`; +/* eslint-enable max-len */ + +const defaultMap = () => `\ +// To implement this library for multiple types with as little code repetition as possible, we write it in +// terms of a generic Map type with bytes32 keys and values. The Map implementation uses private functions, +// and user-facing implementations such as \`UintToAddressMap\` are just wrappers around the underlying Map. +// 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; + mapping(bytes32 key => bytes32) _values; +} + +/** + * @dev Adds a key-value pair to a map, or updates the value for an existing + * key. O(1). + * + * 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) { + map._values[key] = value; + return map._keys.add(key); +} + +/** + * @dev Removes a key-value pair from a map. O(1). + * + * Returns true if the key was removed from the map, that is if it was present. + */ +function remove(Bytes32ToBytes32Map storage map, bytes32 key) internal returns (bool) { + delete map._values[key]; + return map._keys.remove(key); +} + +/** + * @dev Returns true if the key is in the map. O(1). + */ +function contains(Bytes32ToBytes32Map storage map, bytes32 key) internal view returns (bool) { + return map._keys.contains(key); +} + +/** + * @dev Returns the number of key-value pairs in the map. O(1). + */ +function length(Bytes32ToBytes32Map storage map) internal view returns (uint256) { + return map._keys.length(); +} + +/** + * @dev Returns the key-value pair stored at position \`index\` in the map. O(1). + * + * Note that there are no guarantees on the ordering of entries inside the + * array, and it may change when more entries are added or removed. + * + * Requirements: + * + * - \`index\` must be strictly less than {length}. + */ +function at(Bytes32ToBytes32Map storage map, uint256 index) internal view returns (bytes32, bytes32) { + bytes32 key = map._keys.at(index); + return (key, map._values[key]); +} + +/** + * @dev Tries to returns the value associated with \`key\`. O(1). + * Does not revert if \`key\` is not in the map. + */ +function tryGet(Bytes32ToBytes32Map storage map, bytes32 key) internal view returns (bool, bytes32) { + bytes32 value = map._values[key]; + if (value == bytes32(0)) { + return (contains(map, key), bytes32(0)); + } else { + return (true, value); + } +} + +/** + * @dev Returns the value associated with \`key\`. O(1). + * + * Requirements: + * + * - \`key\` must be in the map. + */ +function get(Bytes32ToBytes32Map storage map, bytes32 key) internal view returns (bytes32) { + bytes32 value = map._values[key]; + if(value == 0 && !contains(map, key)) { + revert EnumerableMapNonexistentKey(key); + } + return value; +} + +/** + * @dev Return the an array containing all the keys + * + * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed + * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that + * this function has an unbounded cost, and using it as part of a state-changing function may render the function + * uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. + */ +function keys(Bytes32ToBytes32Map storage map) internal view returns (bytes32[] memory) { + return map._keys.values(); +} +`; + +const customMap = ({ name, keyType, valueType }) => `\ +// ${name} + +struct ${name} { + Bytes32ToBytes32Map _inner; +} + +/** + * @dev Adds a key-value pair to a map, or updates the value for an existing + * key. O(1). + * + * Returns true if the key was added to the map, that is if it was not + * already present. + */ +function set( + ${name} storage map, + ${keyType} key, + ${valueType} value +) internal returns (bool) { + return set(map._inner, ${toBytes32(keyType, 'key')}, ${toBytes32(valueType, 'value')}); +} + +/** + * @dev Removes a value from a map. O(1). + * + * Returns true if the key was removed from the map, that is if it was present. + */ +function remove(${name} storage map, ${keyType} key) internal returns (bool) { + return remove(map._inner, ${toBytes32(keyType, 'key')}); +} + +/** + * @dev Returns true if the key is in the map. O(1). + */ +function contains(${name} storage map, ${keyType} key) internal view returns (bool) { + return contains(map._inner, ${toBytes32(keyType, 'key')}); +} + +/** + * @dev Returns the number of elements in the map. O(1). + */ +function length(${name} storage map) internal view returns (uint256) { + return length(map._inner); +} + +/** + * @dev Returns the element stored at position \`index\` in the map. O(1). + * Note that there are no guarantees on the ordering of values inside the + * array, and it may change when more values are added or removed. + * + * Requirements: + * + * - \`index\` must be strictly less than {length}. + */ +function at(${name} storage map, uint256 index) internal view returns (${keyType}, ${valueType}) { + (bytes32 key, bytes32 value) = at(map._inner, index); + return (${fromBytes32(keyType, 'key')}, ${fromBytes32(valueType, 'value')}); +} + +/** + * @dev Tries to returns the value associated with \`key\`. O(1). + * Does not revert if \`key\` is not in the map. + */ +function tryGet(${name} storage map, ${keyType} key) internal view returns (bool, ${valueType}) { + (bool success, bytes32 value) = tryGet(map._inner, ${toBytes32(keyType, 'key')}); + return (success, ${fromBytes32(valueType, 'value')}); +} + +/** + * @dev Returns the value associated with \`key\`. O(1). + * + * Requirements: + * + * - \`key\` must be in the map. + */ +function get(${name} storage map, ${keyType} key) internal view returns (${valueType}) { + return ${fromBytes32(valueType, `get(map._inner, ${toBytes32(keyType, 'key')})`)}; +} + +/** + * @dev Return the an array containing all the keys + * + * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed + * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that + * this function has an unbounded cost, and using it as part of a state-changing function may render the function + * uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. + */ +function keys(${name} storage map) internal view returns (${keyType}[] memory) { + bytes32[] memory store = keys(map._inner); + ${keyType}[] memory result; + + /// @solidity memory-safe-assembly + assembly { + result := store + } + + return result; +} +`; + +// GENERATE +module.exports = format( + header.trimEnd(), + 'library EnumerableMap {', + [ + 'using EnumerableSet for EnumerableSet.Bytes32Set;', + '', + defaultMap(), + TYPES.map(details => customMap(details).trimEnd()).join('\n\n'), + ], + '}', +); diff --git a/scripts/generate/templates/EnumerableSet.js b/scripts/generate/templates/EnumerableSet.js new file mode 100644 index 000000000..cb9bffb2c --- /dev/null +++ b/scripts/generate/templates/EnumerableSet.js @@ -0,0 +1,250 @@ +const format = require('../format-lines'); +const { fromBytes32, toBytes32 } = require('./conversion'); + +const TYPES = [ + { name: 'Bytes32Set', type: 'bytes32' }, + { name: 'AddressSet', type: 'address' }, + { name: 'UintSet', type: 'uint256' }, +]; + +/* eslint-disable max-len */ +const header = `\ +pragma solidity ^0.8.20; + +/** + * @dev Library for managing + * https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive + * types. + * + * Sets have the following properties: + * + * - Elements are added, removed, and checked for existence in constant time + * (O(1)). + * - Elements are enumerated in O(n). No guarantees are made on the ordering. + * + * \`\`\`solidity + * contract Example { + * // Add the library methods + * using EnumerableSet for EnumerableSet.AddressSet; + * + * // Declare a set state variable + * EnumerableSet.AddressSet private mySet; + * } + * \`\`\` + * + * As of v3.3.0, sets of type \`bytes32\` (\`Bytes32Set\`), \`address\` (\`AddressSet\`) + * and \`uint256\` (\`UintSet\`) are supported. + * + * [WARNING] + * ==== + * Trying to delete such a structure from storage will likely result in data corruption, rendering the structure + * unusable. + * See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info. + * + * In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an + * array of EnumerableSet. + * ==== + */ +`; +/* eslint-enable max-len */ + +const defaultSet = () => `\ +// To implement this library for multiple types with as little code +// repetition as possible, we write it in terms of a generic Set type with +// bytes32 values. +// The Set implementation uses private functions, and user-facing +// implementations (such as AddressSet) are just wrappers around the +// underlying Set. +// This means that we can only create new EnumerableSets for types that fit +// in bytes32. + +struct Set { + // Storage of set values + bytes32[] _values; + // Position is the index of the value in the \`values\` array plus 1. + // Position 0 is used to mean a value is not in the set. + mapping(bytes32 value => uint256) _positions; +} + +/** + * @dev Add a value to a set. O(1). + * + * Returns true if the value was added to the set, that is if it was not + * already present. + */ +function _add(Set storage set, bytes32 value) private returns (bool) { + if (!_contains(set, value)) { + set._values.push(value); + // The value is stored at length-1, but we add 1 to all indexes + // and use 0 as a sentinel value + set._positions[value] = set._values.length; + return true; + } else { + return false; + } +} + +/** + * @dev Removes a value from a set. O(1). + * + * Returns true if the value was removed from the set, that is if it was + * present. + */ +function _remove(Set storage set, bytes32 value) private returns (bool) { + // We cache the value's position to prevent multiple reads from the same storage slot + uint256 position = set._positions[value]; + + if (position != 0) { + // Equivalent to contains(set, value) + // To delete an element from the _values array in O(1), we swap the element to delete with the last one in + // the array, and then remove the last element (sometimes called as 'swap and pop'). + // This modifies the order of the array, as noted in {at}. + + uint256 valueIndex = position - 1; + uint256 lastIndex = set._values.length - 1; + + if (valueIndex != lastIndex) { + bytes32 lastValue = set._values[lastIndex]; + + // Move the lastValue to the index where the value to delete is + set._values[valueIndex] = lastValue; + // Update the tracked position of the lastValue (that was just moved) + set._positions[lastValue] = position; + } + + // Delete the slot where the moved value was stored + set._values.pop(); + + // Delete the tracked position for the deleted slot + delete set._positions[value]; + + return true; + } else { + return false; + } +} + +/** + * @dev Returns true if the value is in the set. O(1). + */ +function _contains(Set storage set, bytes32 value) private view returns (bool) { + return set._positions[value] != 0; +} + +/** + * @dev Returns the number of values on the set. O(1). + */ +function _length(Set storage set) private view returns (uint256) { + return set._values.length; +} + +/** + * @dev Returns the value stored at position \`index\` in the set. O(1). + * + * Note that there are no guarantees on the ordering of values inside the + * array, and it may change when more values are added or removed. + * + * Requirements: + * + * - \`index\` must be strictly less than {length}. + */ +function _at(Set storage set, uint256 index) private view returns (bytes32) { + return set._values[index]; +} + +/** + * @dev Return the entire set in an array + * + * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed + * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that + * this function has an unbounded cost, and using it as part of a state-changing function may render the function + * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. + */ +function _values(Set storage set) private view returns (bytes32[] memory) { + return set._values; +} +`; + +const customSet = ({ name, type }) => `\ +// ${name} + +struct ${name} { + Set _inner; +} + +/** + * @dev Add a value to a set. O(1). + * + * Returns true if the value was added to the set, that is if it was not + * already present. + */ +function add(${name} storage set, ${type} value) internal returns (bool) { + return _add(set._inner, ${toBytes32(type, 'value')}); +} + +/** + * @dev Removes a value from a set. O(1). + * + * Returns true if the value was removed from the set, that is if it was + * present. + */ +function remove(${name} storage set, ${type} value) internal returns (bool) { + return _remove(set._inner, ${toBytes32(type, 'value')}); +} + +/** + * @dev Returns true if the value is in the set. O(1). + */ +function contains(${name} storage set, ${type} value) internal view returns (bool) { + return _contains(set._inner, ${toBytes32(type, 'value')}); +} + +/** + * @dev Returns the number of values in the set. O(1). + */ +function length(${name} storage set) internal view returns (uint256) { + return _length(set._inner); +} + +/** + * @dev Returns the value stored at position \`index\` in the set. O(1). + * + * Note that there are no guarantees on the ordering of values inside the + * array, and it may change when more values are added or removed. + * + * Requirements: + * + * - \`index\` must be strictly less than {length}. + */ +function at(${name} storage set, uint256 index) internal view returns (${type}) { + return ${fromBytes32(type, '_at(set._inner, index)')}; +} + +/** + * @dev Return the entire set in an array + * + * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed + * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that + * this function has an unbounded cost, and using it as part of a state-changing function may render the function + * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. + */ +function values(${name} storage set) internal view returns (${type}[] memory) { + bytes32[] memory store = _values(set._inner); + ${type}[] memory result; + + /// @solidity memory-safe-assembly + assembly { + result := store + } + + return result; +} +`; + +// GENERATE +module.exports = format( + header.trimEnd(), + 'library EnumerableSet {', + [defaultSet(), TYPES.map(details => customSet(details).trimEnd()).join('\n\n')], + '}', +); diff --git a/scripts/generate/templates/SafeCast.js b/scripts/generate/templates/SafeCast.js new file mode 100644 index 000000000..f1954a753 --- /dev/null +++ b/scripts/generate/templates/SafeCast.js @@ -0,0 +1,126 @@ +const format = require('../format-lines'); +const { range } = require('../../helpers'); + +const LENGTHS = range(8, 256, 8).reverse(); // 248 β†’ 8 (in steps of 8) + +const header = `\ +pragma solidity ^0.8.20; + +/** + * @dev Wrappers over Solidity's uintXX/intXX casting operators with added overflow + * checks. + * + * Downcasting from uint256/int256 in Solidity does not revert on overflow. This can + * easily result in undesired exploitation or bugs, since developers usually + * assume that overflows raise errors. \`SafeCast\` restores this intuition by + * reverting the transaction when such an operation overflows. + * + * Using this library instead of the unchecked operations eliminates an entire + * class of bugs, so it's recommended to use it always. + */ +`; + +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 + * overflow (when the input is greater than largest uint${length}). + * + * Counterpart to Solidity's \`uint${length}\` operator. + * + * Requirements: + * + * - input must fit into ${length} bits + */ +function toUint${length}(uint256 value) internal pure returns (uint${length}) { + if (value > type(uint${length}).max) { + revert SafeCastOverflowedUintDowncast(${length}, value); + } + return uint${length}(value); +} +`; + +/* eslint-disable max-len */ +const toIntDownCast = length => `\ +/** + * @dev Returns the downcasted int${length} from int256, reverting on + * overflow (when the input is less than smallest int${length} or + * greater than largest int${length}). + * + * Counterpart to Solidity's \`int${length}\` operator. + * + * Requirements: + * + * - input must fit into ${length} bits + */ +function toInt${length}(int256 value) internal pure returns (int${length} downcasted) { + downcasted = int${length}(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(${length}, value); + } +} +`; +/* eslint-enable max-len */ + +const toInt = length => `\ +/** + * @dev Converts an unsigned uint${length} into a signed int${length}. + * + * Requirements: + * + * - input must be less than or equal to maxInt${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 + if (value > uint${length}(type(int${length}).max)) { + revert SafeCastOverflowedUintToInt(value); + } + return int${length}(value); +} +`; + +const toUint = length => `\ +/** + * @dev Converts a signed int${length} into an unsigned uint${length}. + * + * Requirements: + * + * - input must be greater than or equal to 0. + */ +function toUint${length}(int${length} value) internal pure returns (uint${length}) { + if (value < 0) { + revert SafeCastOverflowedIntToUint(value); + } + return uint${length}(value); +} +`; + +// GENERATE +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 new file mode 100644 index 000000000..1c90b5e75 --- /dev/null +++ b/scripts/generate/templates/StorageSlot.js @@ -0,0 +1,78 @@ +const format = require('../format-lines'); +const { capitalize } = require('../../helpers'); + +const TYPES = [ + { 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 header = `\ +pragma solidity ^0.8.20; + +/** + * @dev Library for reading and writing primitive types to specific storage slots. + * + * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts. + * This library helps with reading and writing to such slots without the need for inline assembly. + * + * The functions in this library return Slot structs that contain a \`value\` member that can be used to read or write. + * + * Example usage to set ERC1967 implementation slot: + * \`\`\`solidity + * contract ERC1967 { + * bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; + * + * function _getImplementation() internal view returns (address) { + * return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value; + * } + * + * function _setImplementation(address newImplementation) internal { + * require(newImplementation.code.length > 0); + * StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation; + * } + * } + * \`\`\` + */ +`; + +const struct = type => `\ +struct ${type.struct} { + ${type.type} value; +} +`; + +const get = type => `\ +/** + * @dev Returns an \`${type.struct}\` with member \`value\` located at \`slot\`. + */ +function get${type.struct}(bytes32 slot) internal pure returns (${type.struct} storage r) { + /// @solidity memory-safe-assembly + assembly { + r.slot := slot + } +} +`; + +const getStorage = type => `\ +/** + * @dev Returns an \`${type.struct}\` representation of the ${type.type} storage pointer \`store\`. + */ +function get${type.struct}(${type.type} storage store) internal pure returns (${type.struct} storage r) { + /// @solidity memory-safe-assembly + assembly { + r.slot := store.slot + } +} +`; + +// GENERATE +module.exports = format( + header.trimEnd(), + 'library StorageSlot {', + [...TYPES.map(struct), ...TYPES.flatMap(type => [get(type), type.isValueType ? '' : getStorage(type)])], + '}', +); diff --git a/scripts/generate/templates/conversion.js b/scripts/generate/templates/conversion.js new file mode 100644 index 000000000..9221f7c21 --- /dev/null +++ b/scripts/generate/templates/conversion.js @@ -0,0 +1,30 @@ +function toBytes32(type, value) { + switch (type) { + case 'bytes32': + return value; + case 'uint256': + return `bytes32(${value})`; + case 'address': + return `bytes32(uint256(uint160(${value})))`; + default: + throw new Error(`Conversion from ${type} to bytes32 not supported`); + } +} + +function fromBytes32(type, value) { + switch (type) { + case 'bytes32': + return value; + case 'uint256': + return `uint256(${value})`; + case 'address': + return `address(uint160(uint256(${value})))`; + default: + throw new Error(`Conversion from bytes32 to ${type} not supported`); + } +} + +module.exports = { + toBytes32, + fromBytes32, +}; diff --git a/scripts/git-user-config.sh b/scripts/git-user-config.sh new file mode 100644 index 000000000..e7b81c3eb --- /dev/null +++ b/scripts/git-user-config.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +set -euo pipefail -x + +git config user.name 'github-actions' +git config user.email '41898282+github-actions[bot]@users.noreply.github.com' diff --git a/scripts/helpers.js b/scripts/helpers.js new file mode 100644 index 000000000..fb9aad4fc --- /dev/null +++ b/scripts/helpers.js @@ -0,0 +1,37 @@ +function chunk(array, size = 1) { + return Array.range(Math.ceil(array.length / size)).map(i => array.slice(i * size, i * size + size)); +} + +function range(start, stop = undefined, step = 1) { + if (!stop) { + stop = start; + start = 0; + } + return start < stop + ? Array(Math.ceil((stop - start) / step)) + .fill() + .map((_, i) => start + i * step) + : []; +} + +function unique(array, op = x => x) { + return array.filter((obj, i) => array.findIndex(entry => op(obj) === op(entry)) === i); +} + +function zip(...args) { + return Array(Math.max(...args.map(arg => arg.length))) + .fill(null) + .map((_, i) => args.map(arg => arg[i])); +} + +function capitalize(str) { + return str.charAt(0).toUpperCase() + str.slice(1); +} + +module.exports = { + chunk, + range, + unique, + zip, + capitalize, +}; diff --git a/scripts/prepack.sh b/scripts/prepack.sh new file mode 100755 index 000000000..6f1bd4c32 --- /dev/null +++ b/scripts/prepack.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +set -euo pipefail +shopt -s globstar + +# cross platform `mkdir -p` +node -e 'fs.mkdirSync("build/contracts", { recursive: true })' + +cp artifacts/contracts/**/*.json build/contracts +rm build/contracts/*.dbg.json + +node scripts/remove-ignored-artifacts.js diff --git a/scripts/prepare-contracts-package.sh b/scripts/prepare-contracts-package.sh new file mode 100755 index 000000000..3f62fd419 --- /dev/null +++ b/scripts/prepare-contracts-package.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash + +# cd to the root of the repo +cd "$(git rev-parse --show-toplevel)" + +# avoids re-compilation during publishing of both packages +if [[ ! -v ALREADY_COMPILED ]]; then + npm run clean + npm run prepare + npm run prepack +fi + +cp README.md contracts/ +mkdir contracts/build contracts/build/contracts +cp -r build/contracts/*.json contracts/build/contracts diff --git a/scripts/prepare-docs.sh b/scripts/prepare-docs.sh new file mode 100755 index 000000000..d1317b092 --- /dev/null +++ b/scripts/prepare-docs.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash + +set -euo pipefail +shopt -s globstar + +OUTDIR="$(node -p 'require("./docs/config.js").outputDir')" + +if [ ! -d node_modules ]; then + npm ci +fi + +rm -rf "$OUTDIR" + +hardhat docgen + +# copy examples and adjust imports +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/scripts/prepare.sh b/scripts/prepare.sh new file mode 100755 index 000000000..7014a7076 --- /dev/null +++ b/scripts/prepare.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +set -euo pipefail + +if [ "${SKIP_COMPILE:-}" == true ]; then + exit +fi + +npm run clean +env COMPILE_MODE=production npm run compile diff --git a/scripts/release/format-changelog.js b/scripts/release/format-changelog.js new file mode 100755 index 000000000..b8bcc8c71 --- /dev/null +++ b/scripts/release/format-changelog.js @@ -0,0 +1,33 @@ +#!/usr/bin/env node + +// Adjusts the format of the changelog that changesets generates. +// This is run automatically when npm version is run. + +const fs = require('fs'); +const changelog = fs.readFileSync('CHANGELOG.md', 'utf8'); + +// Groups: +// - 1: Pull Request Number and URL +// - 2: Changeset entry +const RELEASE_LINE_REGEX = /^- (\[#.*?\]\(.*?\))?.*?! - (.*)$/gm; + +// Captures vX.Y.Z or vX.Y.Z-rc.W +const VERSION_TITLE_REGEX = /^## (\d+\.\d+\.\d+(-rc\.\d+)?)$/gm; + +const isPrerelease = process.env.PRERELEASE === 'true'; + +const formatted = changelog + // Remove titles + .replace(/^### Major Changes\n\n/gm, '') + .replace(/^### Minor Changes\n\n/gm, '') + .replace(/^### Patch Changes\n\n/gm, '') + // Remove extra whitespace between items + .replace(/^(- \[.*\n)\n(?=-)/gm, '$1') + // Format each release line + .replace(RELEASE_LINE_REGEX, (_, pr, entry) => (pr ? `- ${entry} (${pr})` : `- ${entry}`)) + // Add date to new version + .replace(VERSION_TITLE_REGEX, `\n## $1 (${new Date().toISOString().split('T')[0]})`) + // Conditionally allow vX.Y.Z.rc-.W sections only in prerelease + .replace(/^## \d\.\d\.\d-rc\S+[^]+?(?=^#)/gm, section => (isPrerelease ? section : '')); + +fs.writeFileSync('CHANGELOG.md', formatted); diff --git a/scripts/release/synchronize-versions.js b/scripts/release/synchronize-versions.js new file mode 100755 index 000000000..15aa25993 --- /dev/null +++ b/scripts/release/synchronize-versions.js @@ -0,0 +1,15 @@ +#!/usr/bin/env node + +// Synchronizes the version in contracts/package.json with the one in package.json. +// This is run automatically when npm version is run. + +const fs = require('fs'); + +setVersion('package.json', 'contracts/package.json'); + +function setVersion(from, to) { + const fromJson = JSON.parse(fs.readFileSync(from)); + const toJson = JSON.parse(fs.readFileSync(to)); + toJson.version = fromJson.version; + fs.writeFileSync(to, JSON.stringify(toJson, null, 2) + '\n'); +} diff --git a/scripts/release/update-comment.js b/scripts/release/update-comment.js new file mode 100755 index 000000000..9d6df2694 --- /dev/null +++ b/scripts/release/update-comment.js @@ -0,0 +1,34 @@ +#!/usr/bin/env node +const fs = require('fs'); +const proc = require('child_process'); +const semver = require('semver'); +const run = (cmd, ...args) => proc.execFileSync(cmd, args, { encoding: 'utf8' }).trim(); + +const gitStatus = run('git', 'status', '--porcelain', '-uno', 'contracts/**/*.sol'); +if (gitStatus.length > 0) { + console.error('Contracts directory is not clean'); + process.exit(1); +} + +const { version } = require('../../package.json'); + +// Get latest tag according to semver. +const [tag] = run('git', 'tag') + .split(/\r?\n/) + .filter(semver.coerce) // check version can be processed + .filter(v => semver.satisfies(v, `< ${version}`)) // ignores prereleases unless currently a prerelease + .sort(semver.rcompare); + +// Ordering tag β†’ HEAD is important here. +const files = run('git', 'diff', tag, 'HEAD', '--name-only', 'contracts/**/*.sol') + .split(/\r?\n/) + .filter(file => file && !file.match(/mock/i) && fs.existsSync(file)); + +for (const file of files) { + const current = fs.readFileSync(file, 'utf8'); + const updated = current.replace( + /(\/\/ SPDX-License-Identifier:.*)$(\n\/\/ OpenZeppelin Contracts .*$)?/m, + `$1\n// OpenZeppelin Contracts (last updated v${version}) (${file.replace('contracts/', '')})`, + ); + fs.writeFileSync(file, updated); +} diff --git a/scripts/release/version.sh b/scripts/release/version.sh new file mode 100755 index 000000000..7b0ddead3 --- /dev/null +++ b/scripts/release/version.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +set -euo pipefail + +changeset version + +scripts/release/format-changelog.js +scripts/release/synchronize-versions.js +scripts/release/update-comment.js + +oz-docs update-version diff --git a/scripts/release/workflow/exit-prerelease.sh b/scripts/release/workflow/exit-prerelease.sh new file mode 100644 index 000000000..bcf9b9ae9 --- /dev/null +++ b/scripts/release/workflow/exit-prerelease.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +set -euo pipefail + +npx changeset pre exit rc +git add . +git commit -m "Exit release candidate" +git push origin diff --git a/scripts/release/workflow/github-release.js b/scripts/release/workflow/github-release.js new file mode 100644 index 000000000..92a47d9d7 --- /dev/null +++ b/scripts/release/workflow/github-release.js @@ -0,0 +1,47 @@ +const { readFileSync } = require('fs'); +const { join } = require('path'); +const { version } = require(join(__dirname, '../../../package.json')); + +module.exports = async ({ github, context }) => { + const changelog = readFileSync('CHANGELOG.md', 'utf8'); + + await github.rest.repos.createRelease({ + owner: context.repo.owner, + repo: context.repo.repo, + tag_name: `v${version}`, + body: extractSection(changelog, version), + prerelease: process.env.PRERELEASE === 'true', + }); +}; + +// From https://github.com/frangio/extract-changelog/blob/master/src/utils/word-regexp.ts +function makeWordRegExp(word) { + const start = word.length > 0 && /\b/.test(word[0]) ? '\\b' : ''; + const end = word.length > 0 && /\b/.test(word[word.length - 1]) ? '\\b' : ''; + return new RegExp(start + [...word].map(c => (/[a-z0-9]/i.test(c) ? c : '\\' + c)).join('') + end); +} + +// From https://github.com/frangio/extract-changelog/blob/master/src/core.ts +function extractSection(document, wantedHeading) { + // ATX Headings as defined in GitHub Flavored Markdown (https://github.github.com/gfm/#atx-headings) + const heading = /^ {0,3}(?#{1,6})(?: [ \t\v\f]*(?.*?)[ \t\v\f]*)?(?:[\n\r]+|$)/gm; + + const wantedHeadingRe = makeWordRegExp(wantedHeading); + + let start, end; + + for (const m of document.matchAll(heading)) { + if (!start) { + if (m.groups.text.search(wantedHeadingRe) === 0) { + start = m; + } + } else if (m.groups.lead.length <= start.groups.lead.length) { + end = m; + break; + } + } + + if (start) { + return document.slice(start.index + start[0].length, end?.index); + } +} diff --git a/scripts/release/workflow/integrity-check.sh b/scripts/release/workflow/integrity-check.sh new file mode 100644 index 000000000..86e99f929 --- /dev/null +++ b/scripts/release/workflow/integrity-check.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash + +set -euo pipefail + +CHECKSUMS="$RUNNER_TEMP/checksums.txt" + +# Extract tarball content into a tmp directory +tar xf "$TARBALL" -C "$RUNNER_TEMP" + +# Move to extracted directory +cd "$RUNNER_TEMP/package" + +# Checksum all Solidity files +find . -type f -name "*.sol" | xargs shasum > "$CHECKSUMS" + +# Back to directory with git contents +cd "$GITHUB_WORKSPACE/contracts" + +# Check against tarball contents +shasum -c "$CHECKSUMS" diff --git a/scripts/release/workflow/pack.sh b/scripts/release/workflow/pack.sh new file mode 100644 index 000000000..ce30712f8 --- /dev/null +++ b/scripts/release/workflow/pack.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash + +set -euo pipefail + +dist_tag() { + PACKAGE_JSON_NAME="$(jq -r .name ./package.json)" + LATEST_NPM_VERSION="$(npm info "$PACKAGE_JSON_NAME" version)" + PACKAGE_JSON_VERSION="$(jq -r .version ./package.json)" + + if [ "$PRERELEASE" = "true" ]; then + echo "next" + elif npx semver -r ">$LATEST_NPM_VERSION" "$PACKAGE_JSON_VERSION" > /dev/null; then + echo "latest" + else + # This is a patch for an older version + # npm can't publish without a tag + echo "tmp" + fi +} + +cd contracts +TARBALL="$(npm pack | tee /dev/stderr | tail -1)" +echo "tarball_name=$TARBALL" >> $GITHUB_OUTPUT +echo "tarball=$(pwd)/$TARBALL" >> $GITHUB_OUTPUT +echo "tag=$(dist_tag)" >> $GITHUB_OUTPUT +cd .. diff --git a/scripts/release/workflow/publish.sh b/scripts/release/workflow/publish.sh new file mode 100644 index 000000000..e490e5d00 --- /dev/null +++ b/scripts/release/workflow/publish.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash + +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() { + npm dist-tag rm "$PACKAGE_JSON_NAME" "$1" +} + +if [ "$TAG" = tmp ]; then + delete_tag "$TAG" +elif [ "$TAG" = latest ]; then + # 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 diff --git a/scripts/release/workflow/rerun.js b/scripts/release/workflow/rerun.js new file mode 100644 index 000000000..f48ce6ea2 --- /dev/null +++ b/scripts/release/workflow/rerun.js @@ -0,0 +1,7 @@ +module.exports = ({ github, context }) => + github.rest.actions.createWorkflowDispatch({ + owner: context.repo.owner, + repo: context.repo.repo, + workflow_id: 'release-cycle.yml', + ref: process.env.REF || process.env.GITHUB_REF_NAME, + }); diff --git a/scripts/release/workflow/set-changesets-pr-title.js b/scripts/release/workflow/set-changesets-pr-title.js new file mode 100644 index 000000000..59b03b22a --- /dev/null +++ b/scripts/release/workflow/set-changesets-pr-title.js @@ -0,0 +1,17 @@ +const { coerce, inc, rsort } = require('semver'); +const { join } = require('path'); +const { version } = require(join(__dirname, '../../../package.json')); + +module.exports = async ({ core }) => { + // Variables not in the context + const refName = process.env.GITHUB_REF_NAME; + + // Compare package.json version's next patch vs. first version patch + // A recently opened branch will give the next patch for the previous minor + // So, we get the max against the patch 0 of the release branch's version + const branchPatch0 = coerce(refName.replace('release-v', '')).version; + const packageJsonNextPatch = inc(version, 'patch'); + const [nextVersion] = rsort([branchPatch0, packageJsonNextPatch], false); + + core.exportVariable('TITLE', `Release v${nextVersion}`); +}; diff --git a/scripts/release/workflow/start.sh b/scripts/release/workflow/start.sh new file mode 100644 index 000000000..7683ec5bc --- /dev/null +++ b/scripts/release/workflow/start.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash + +set -euo pipefail + +# Set changeset status location +# This is needed because `changeset status --output` only works with relative routes +CHANGESETS_STATUS_JSON="$(realpath --relative-to=. "$RUNNER_TEMP/status.json")" + +# Save changeset status to temp file +npx changeset status --output="$CHANGESETS_STATUS_JSON" + +# Defensive assertion. SHOULD NOT BE REACHED +if [ "$(jq '.releases | length' "$CHANGESETS_STATUS_JSON")" != 1 ]; then + echo "::error file=$CHANGESETS_STATUS_JSON::The status doesn't contain only 1 release" + exit 1; +fi; + +# Create branch +BRANCH_SUFFIX="$(jq -r '.releases[0].newVersion | gsub("\\.\\d+$"; "")' $CHANGESETS_STATUS_JSON)" +RELEASE_BRANCH="release-v$BRANCH_SUFFIX" +git checkout -b "$RELEASE_BRANCH" + +# Output branch +echo "branch=$RELEASE_BRANCH" >> $GITHUB_OUTPUT + +# Enter in prerelease state +npx changeset pre enter rc +git add . +git commit -m "Start release candidate" + +# Push branch +if ! git push origin "$RELEASE_BRANCH"; then + echo "::error file=scripts/release/start.sh::Can't push $RELEASE_BRANCH. Did you forget to run this workflow from $RELEASE_BRANCH?" + exit 1 +fi diff --git a/scripts/release/workflow/state.js b/scripts/release/workflow/state.js new file mode 100644 index 000000000..914e8de02 --- /dev/null +++ b/scripts/release/workflow/state.js @@ -0,0 +1,112 @@ +const { readPreState } = require('@changesets/pre'); +const { default: readChangesets } = require('@changesets/read'); +const { join } = require('path'); +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 }); + + function setOutput(key, value) { + core.info(`State ${key} = ${value}`); + core.setOutput(key, value); + } + + // Jobs to trigger + setOutput('start', shouldRunStart(state)); + setOutput('promote', shouldRunPromote(state)); + setOutput('changesets', shouldRunChangesets(state)); + setOutput('publish', shouldRunPublish(state)); + setOutput('merge', shouldRunMerge(state)); + + // Global Variables + setOutput('is_prerelease', state.prerelease); +}; + +function shouldRunStart({ isMaster, isWorkflowDispatch, botRun }) { + return isMaster && isWorkflowDispatch && !botRun; +} + +function shouldRunPromote({ isReleaseBranch, isWorkflowDispatch, botRun }) { + return isReleaseBranch && isWorkflowDispatch && !botRun; +} + +function shouldRunChangesets({ isReleaseBranch, isPush, isWorkflowDispatch, botRun }) { + return (isReleaseBranch && isPush) || (isReleaseBranch && isWorkflowDispatch && botRun); +} + +function shouldRunPublish({ isReleaseBranch, isPush, hasPendingChangesets, isPublishedOnNpm }) { + return isReleaseBranch && isPush && !hasPendingChangesets && !isPublishedOnNpm; +} + +function shouldRunMerge({ + isReleaseBranch, + isPush, + prerelease, + isCurrentFinalVersion, + hasPendingChangesets, + prBackExists, +}) { + return isReleaseBranch && isPush && !prerelease && isCurrentFinalVersion && !hasPendingChangesets && !prBackExists; +} + +async function getState({ github, context, core }) { + // Variables not in the context + const refName = process.env.GITHUB_REF_NAME; + const botRun = process.env.TRIGGERING_ACTOR === 'github-actions[bot]'; + + const { changesets, preState } = await readChangesetState(); + + // Static vars + const state = { + refName, + hasPendingChangesets: changesets.length > 0, + prerelease: preState?.mode === 'pre', + isMaster: refName === 'master', + isReleaseBranch: refName.startsWith('release-v'), + isWorkflowDispatch: context.eventName === 'workflow_dispatch', + isPush: context.eventName === 'push', + isCurrentFinalVersion: !version.includes('-rc.'), + botRun, + }; + + // Async vars + const { data: prs } = await github.rest.pulls.list({ + owner: context.repo.owner, + repo: context.repo.repo, + head: `${context.repo.owner}:merge/${state.refName}`, + base: 'master', + state: 'open', + }); + + 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}`); + + return state; +} + +// From https://github.com/changesets/action/blob/v1.4.1/src/readChangesetState.ts +async function readChangesetState(cwd = process.cwd()) { + const preState = await readPreState(cwd); + const isInPreMode = preState !== undefined && preState.mode === 'pre'; + + let changesets = await readChangesets(cwd); + + if (isInPreMode) { + changesets = changesets.filter(x => !preState.changesets.includes(x.id)); + } + + return { + preState: isInPreMode ? preState : undefined, + changesets, + }; +} + +async function isPublishedOnNpm(package, version) { + const res = await fetch(`https://registry.npmjs.com/${package}/${version}`); + return res.ok; +} diff --git a/scripts/remove-ignored-artifacts.js b/scripts/remove-ignored-artifacts.js new file mode 100644 index 000000000..f3e45b8d1 --- /dev/null +++ b/scripts/remove-ignored-artifacts.js @@ -0,0 +1,45 @@ +#!/usr/bin/env node + +// This script removes the build artifacts of ignored contracts. + +const fs = require('fs'); +const path = require('path'); +const match = require('micromatch'); + +function readJSON(path) { + return JSON.parse(fs.readFileSync(path)); +} + +const pkgFiles = readJSON('package.json').files; + +// Get only negated patterns. +const ignorePatterns = pkgFiles + .filter(pat => pat.startsWith('!')) + // Remove the negation part. Makes micromatch usage more intuitive. + .map(pat => pat.slice(1)); + +const ignorePatternsSubtrees = ignorePatterns + // Add **/* to ignore all files contained in the directories. + .concat(ignorePatterns.map(pat => path.join(pat, '**/*'))) + .map(p => p.replace(/^\//, '')); + +const artifactsDir = 'build/contracts'; +const buildinfo = 'artifacts/build-info'; +const filenames = fs.readdirSync(buildinfo); + +let n = 0; + +for (const filename of filenames) { + const solcOutput = readJSON(path.join(buildinfo, filename)).output; + for (const sourcePath in solcOutput.contracts) { + const ignore = match.any(sourcePath, ignorePatternsSubtrees); + if (ignore) { + for (const contract in solcOutput.contracts[sourcePath]) { + fs.unlinkSync(path.join(artifactsDir, contract + '.json')); + n += 1; + } + } + } +} + +console.error(`Removed ${n} mock artifacts`); diff --git a/scripts/solhint-custom/index.js b/scripts/solhint-custom/index.js new file mode 100644 index 000000000..9625027ee --- /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) { + // TODO: expand visibility and fix + if (node.visibility === 'private' && /^_/.test(node.name)) { + 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/scripts/update-docs-branch.js b/scripts/update-docs-branch.js new file mode 100644 index 000000000..26922ec49 --- /dev/null +++ b/scripts/update-docs-branch.js @@ -0,0 +1,58 @@ +const proc = require('child_process'); +const read = cmd => proc.execSync(cmd, { encoding: 'utf8' }).trim(); +const run = cmd => { + proc.execSync(cmd, { stdio: 'inherit' }); +}; +const tryRead = cmd => { + try { + return read(cmd); + } catch (e) { + return undefined; + } +}; + +const releaseBranchRegex = /^release-v(?(?\d+)\.(?\d+)(?:\.(?\d+))?)$/; + +const currentBranch = read('git rev-parse --abbrev-ref HEAD'); +const match = currentBranch.match(releaseBranchRegex); + +if (!match) { + console.error('Not currently on a release branch'); + process.exit(1); +} + +const current = match.groups; +const docsBranch = `docs-v${current.major}.x`; + +// Fetch remotes and find the docs branch if it exists +run('git fetch --all --no-tags'); +const matchingDocsBranches = tryRead(`git rev-parse --glob='*/${docsBranch}'`); + +if (!matchingDocsBranches) { + // Create the branch + run(`git checkout --orphan ${docsBranch}`); +} else { + const [publishedRef, ...others] = new Set(matchingDocsBranches.split('\n')); + if (others.length > 0) { + console.error( + `Found conflicting ${docsBranch} branches.\n` + + 'Either local branch is outdated or there are multiple matching remote branches.', + ); + process.exit(1); + } + const publishedVersion = JSON.parse(read(`git show ${publishedRef}:package.json`)).version; + const publishedMinor = publishedVersion.match(/\d+\.(?\d+)\.\d+/).groups.minor; + if (current.minor < publishedMinor) { + console.error('Refusing to update docs: newer version is published'); + process.exit(0); + } + + run('git checkout --quiet --detach'); + run(`git reset --soft ${publishedRef}`); + run(`git checkout ${docsBranch}`); +} + +run('npm run prepare-docs'); +run('git add -f docs'); // --force needed because generated docs files are gitignored +run('git commit -m "Update docs"'); +run(`git checkout ${currentBranch}`); 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..05de96d34 --- /dev/null +++ b/scripts/upgradeable/transpile.sh @@ -0,0 +1,42 @@ +#!/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 +# -n: use namespaces +# -N: exclude from namespaces transformation +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/ERC1967Utils.sol' \ + -x '!contracts/proxy/utils/UUPSUpgradeable.sol' \ + -x '!contracts/proxy/beacon/IBeacon.sol' \ + -p 'contracts/**/presets/**/*' \ + -n \ + -N 'contracts/mocks/**/*' + +# delete compilation artifacts of vanilla code +npm run clean diff --git a/scripts/upgradeable/upgradeable.patch b/scripts/upgradeable/upgradeable.patch new file mode 100644 index 000000000..125272a25 --- /dev/null +++ b/scripts/upgradeable/upgradeable.patch @@ -0,0 +1,342 @@ +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 38197f3a..bc934d1c 100644 +--- a/README.md ++++ b/README.md +@@ -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. + ++> **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 +@@ -26,7 +29,7 @@ + #### Hardhat, Truffle (npm) + + ``` +-$ 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. +@@ -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. + + ``` +-$ forge install OpenZeppelin/openzeppelin-contracts ++$ 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 + +@@ -50,10 +53,11 @@ Once installed, you can use the contracts in the library by importing them: + ```solidity + pragma solidity ^0.8.20; + +-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") { ++contract MyCollectible is ERC721Upgradeable { ++ function initialize() initializer public { ++ __ERC721_init("MyCollectible", "MCO"); + } + } + ``` +diff --git a/contracts/package.json b/contracts/package.json +index df141192..1cf90ad1 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.9.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/utils/cryptography/EIP712.sol b/contracts/utils/cryptography/EIP712.sol +index 3800804a..90c1db78 100644 +--- a/contracts/utils/cryptography/EIP712.sol ++++ b/contracts/utils/cryptography/EIP712.sol +@@ -4,7 +4,6 @@ + pragma solidity ^0.8.20; + + import {MessageHashUtils} from "./MessageHashUtils.sol"; +-import {ShortStrings, ShortString} from "../ShortStrings.sol"; + 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 from the immutable values, which is cheaper than accessing a cached version in cold storage. +- * +- * @custom:oz-upgrades-unsafe-allow state-variable-immutable + */ + 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. +@@ -64,29 +53,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))); + } + + /** +@@ -125,6 +108,10 @@ 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 + _EIP712Name(), +@@ -139,22 +126,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). ++ * 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 _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. + * +- * 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. + */ +- // 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; ++ } ++ ++ /** ++ * @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 ffa868ac..e254d962 100644 +--- a/package.json ++++ b/package.json +@@ -33,7 +33,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 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) { + 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/slither.config.json b/slither.config.json new file mode 100644 index 000000000..069da1f3a --- /dev/null +++ b/slither.config.json @@ -0,0 +1,5 @@ +{ + "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" +} 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'])), +}; diff --git a/test/TESTING.md b/test/TESTING.md new file mode 100644 index 000000000..a5ee9323f --- /dev/null +++ b/test/TESTING.md @@ -0,0 +1,3 @@ +## Testing + +Unit test are critical to OpenZeppelin Contracts. They help ensure code quality and mitigate against security vulnerabilities. The directory structure within the `/test` directory corresponds to the `/contracts` directory. diff --git a/test/access/AccessControl.behavior.js b/test/access/AccessControl.behavior.js new file mode 100644 index 000000000..cc3e8d63f --- /dev/null +++ b/test/access/AccessControl.behavior.js @@ -0,0 +1,909 @@ +const { expectEvent, constants, BN } = require('@openzeppelin/test-helpers'); +const { expectRevertCustomError } = require('../helpers/customError'); +const { expect } = require('chai'); + +const { time } = require('@nomicfoundation/hardhat-network-helpers'); + +const { shouldSupportInterfaces } = require('../utils/introspection/SupportsInterface.behavior'); +const { network } = require('hardhat'); +const { ZERO_ADDRESS } = require('@openzeppelin/test-helpers/src/constants'); + +const DEFAULT_ADMIN_ROLE = '0x0000000000000000000000000000000000000000000000000000000000000000'; +const ROLE = web3.utils.soliditySha3('ROLE'); +const OTHER_ROLE = web3.utils.soliditySha3('OTHER_ROLE'); +const ZERO = web3.utils.toBN(0); + +function shouldBehaveLikeAccessControl(admin, authorized, other, otherAdmin) { + shouldSupportInterfaces(['AccessControl']); + + describe('default admin', function () { + it('deployer has default admin role', async function () { + expect(await this.accessControl.hasRole(DEFAULT_ADMIN_ROLE, admin)).to.equal(true); + }); + + it("other roles's admin is the default admin role", async function () { + expect(await this.accessControl.getRoleAdmin(ROLE)).to.equal(DEFAULT_ADMIN_ROLE); + }); + + it("default admin role's admin is itself", async function () { + expect(await this.accessControl.getRoleAdmin(DEFAULT_ADMIN_ROLE)).to.equal(DEFAULT_ADMIN_ROLE); + }); + }); + + describe('granting', function () { + beforeEach(async function () { + await this.accessControl.grantRole(ROLE, authorized, { from: admin }); + }); + + it('non-admin cannot grant role to other accounts', async function () { + await expectRevertCustomError( + this.accessControl.grantRole(ROLE, authorized, { from: other }), + 'AccessControlUnauthorizedAccount', + [other, DEFAULT_ADMIN_ROLE], + ); + }); + + it('accounts can be granted a role multiple times', async function () { + await this.accessControl.grantRole(ROLE, authorized, { from: admin }); + const receipt = await this.accessControl.grantRole(ROLE, authorized, { from: admin }); + expectEvent.notEmitted(receipt, 'RoleGranted'); + }); + }); + + describe('revoking', function () { + it('roles that are not had can be revoked', async function () { + expect(await this.accessControl.hasRole(ROLE, authorized)).to.equal(false); + + const receipt = await this.accessControl.revokeRole(ROLE, authorized, { from: admin }); + expectEvent.notEmitted(receipt, 'RoleRevoked'); + }); + + context('with granted role', function () { + beforeEach(async function () { + await this.accessControl.grantRole(ROLE, authorized, { from: admin }); + }); + + it('admin can revoke role', async function () { + const receipt = await this.accessControl.revokeRole(ROLE, authorized, { from: admin }); + expectEvent(receipt, 'RoleRevoked', { account: authorized, role: ROLE, sender: admin }); + + expect(await this.accessControl.hasRole(ROLE, authorized)).to.equal(false); + }); + + it('non-admin cannot revoke role', async function () { + await expectRevertCustomError( + this.accessControl.revokeRole(ROLE, authorized, { from: other }), + 'AccessControlUnauthorizedAccount', + [other, DEFAULT_ADMIN_ROLE], + ); + }); + + it('a role can be revoked multiple times', async function () { + await this.accessControl.revokeRole(ROLE, authorized, { from: admin }); + + const receipt = await this.accessControl.revokeRole(ROLE, authorized, { from: admin }); + expectEvent.notEmitted(receipt, 'RoleRevoked'); + }); + }); + }); + + describe('renouncing', function () { + it('roles that are not had can be renounced', async function () { + const receipt = await this.accessControl.renounceRole(ROLE, authorized, { from: authorized }); + expectEvent.notEmitted(receipt, 'RoleRevoked'); + }); + + context('with granted role', function () { + beforeEach(async function () { + await this.accessControl.grantRole(ROLE, authorized, { from: admin }); + }); + + it('bearer can renounce role', async function () { + const receipt = await this.accessControl.renounceRole(ROLE, authorized, { from: authorized }); + expectEvent(receipt, 'RoleRevoked', { account: authorized, role: ROLE, sender: authorized }); + + expect(await this.accessControl.hasRole(ROLE, authorized)).to.equal(false); + }); + + it('only the sender can renounce their roles', async function () { + await expectRevertCustomError( + this.accessControl.renounceRole(ROLE, authorized, { from: admin }), + 'AccessControlBadConfirmation', + [], + ); + }); + + it('a role can be renounced multiple times', async function () { + await this.accessControl.renounceRole(ROLE, authorized, { from: authorized }); + + const receipt = await this.accessControl.renounceRole(ROLE, authorized, { from: authorized }); + expectEvent.notEmitted(receipt, 'RoleRevoked'); + }); + }); + }); + + describe('setting role admin', function () { + beforeEach(async function () { + const receipt = await this.accessControl.$_setRoleAdmin(ROLE, OTHER_ROLE); + expectEvent(receipt, 'RoleAdminChanged', { + role: ROLE, + previousAdminRole: DEFAULT_ADMIN_ROLE, + newAdminRole: OTHER_ROLE, + }); + + await this.accessControl.grantRole(OTHER_ROLE, otherAdmin, { from: admin }); + }); + + it("a role's admin role can be changed", async function () { + expect(await this.accessControl.getRoleAdmin(ROLE)).to.equal(OTHER_ROLE); + }); + + it('the new admin can grant roles', async function () { + const receipt = await this.accessControl.grantRole(ROLE, authorized, { from: otherAdmin }); + expectEvent(receipt, 'RoleGranted', { account: authorized, role: ROLE, sender: otherAdmin }); + }); + + it('the new admin can revoke roles', async function () { + await this.accessControl.grantRole(ROLE, authorized, { from: otherAdmin }); + const receipt = await this.accessControl.revokeRole(ROLE, authorized, { from: otherAdmin }); + expectEvent(receipt, 'RoleRevoked', { account: authorized, role: ROLE, sender: otherAdmin }); + }); + + it("a role's previous admins no longer grant roles", async function () { + await expectRevertCustomError( + this.accessControl.grantRole(ROLE, authorized, { from: admin }), + 'AccessControlUnauthorizedAccount', + [admin.toLowerCase(), OTHER_ROLE], + ); + }); + + it("a role's previous admins no longer revoke roles", async function () { + await expectRevertCustomError( + this.accessControl.revokeRole(ROLE, authorized, { from: admin }), + 'AccessControlUnauthorizedAccount', + [admin.toLowerCase(), OTHER_ROLE], + ); + }); + }); + + describe('onlyRole modifier', function () { + beforeEach(async function () { + await this.accessControl.grantRole(ROLE, authorized, { from: admin }); + }); + + it('do not revert if sender has role', async function () { + await this.accessControl.methods['$_checkRole(bytes32)'](ROLE, { from: authorized }); + }); + + it("revert if sender doesn't have role #1", async function () { + await expectRevertCustomError( + this.accessControl.methods['$_checkRole(bytes32)'](ROLE, { from: other }), + 'AccessControlUnauthorizedAccount', + [other, ROLE], + ); + }); + + it("revert if sender doesn't have role #2", async function () { + await expectRevertCustomError( + this.accessControl.methods['$_checkRole(bytes32)'](OTHER_ROLE, { from: authorized }), + 'AccessControlUnauthorizedAccount', + [authorized.toLowerCase(), OTHER_ROLE], + ); + }); + }); + + 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) { + shouldSupportInterfaces(['AccessControlEnumerable']); + + describe('enumerating', function () { + it('role bearers can be enumerated', async function () { + await this.accessControl.grantRole(ROLE, authorized, { from: admin }); + await this.accessControl.grantRole(ROLE, other, { from: admin }); + await this.accessControl.grantRole(ROLE, otherAuthorized, { from: admin }); + await this.accessControl.revokeRole(ROLE, other, { from: admin }); + + const memberCount = await this.accessControl.getRoleMemberCount(ROLE); + expect(memberCount).to.bignumber.equal('2'); + + const bearers = []; + for (let i = 0; i < memberCount; ++i) { + bearers.push(await this.accessControl.getRoleMember(ROLE, i)); + } + + expect(bearers).to.have.members([authorized, otherAuthorized]); + }); + it('role enumeration should be in sync after renounceRole call', async function () { + expect(await this.accessControl.getRoleMemberCount(ROLE)).to.bignumber.equal('0'); + await this.accessControl.grantRole(ROLE, admin, { from: admin }); + expect(await this.accessControl.getRoleMemberCount(ROLE)).to.bignumber.equal('1'); + await this.accessControl.renounceRole(ROLE, admin, { from: admin }); + expect(await this.accessControl.getRoleMemberCount(ROLE)).to.bignumber.equal('0'); + }); + }); +} + +function shouldBehaveLikeAccessControlDefaultAdminRules(delay, defaultAdmin, newDefaultAdmin, other) { + shouldSupportInterfaces(['AccessControlDefaultAdminRules']); + + for (const getter of ['owner', 'defaultAdmin']) { + describe(`${getter}()`, function () { + it('has a default set to the initial default admin', async function () { + const value = await this.accessControl[getter](); + expect(value).to.equal(defaultAdmin); + expect(await this.accessControl.hasRole(DEFAULT_ADMIN_ROLE, value)).to.be.true; + }); + + it('changes if the default admin changes', async function () { + // Starts an admin transfer + await this.accessControl.beginDefaultAdminTransfer(newDefaultAdmin, { from: defaultAdmin }); + + // Wait for acceptance + const acceptSchedule = web3.utils.toBN(await time.latest()).add(delay); + await time.setNextBlockTimestamp(acceptSchedule.addn(1)); + await this.accessControl.acceptDefaultAdminTransfer({ from: newDefaultAdmin }); + + const value = await this.accessControl[getter](); + expect(value).to.equal(newDefaultAdmin); + }); + }); + } + + describe('pendingDefaultAdmin()', function () { + it('returns 0 if no pending default admin transfer', async function () { + const { newAdmin, schedule } = await this.accessControl.pendingDefaultAdmin(); + expect(newAdmin).to.eq(ZERO_ADDRESS); + expect(schedule).to.be.bignumber.eq(ZERO); + }); + + describe('when there is a scheduled default admin transfer', function () { + beforeEach('begins admin transfer', async function () { + await this.accessControl.beginDefaultAdminTransfer(newDefaultAdmin, { from: defaultAdmin }); + }); + + for (const [fromSchedule, tag] of [ + [-1, 'before'], + [0, 'exactly when'], + [1, 'after'], + ]) { + 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); + await network.provider.send('evm_mine'); // Mine a block to force the timestamp + + const { newAdmin, schedule } = await this.accessControl.pendingDefaultAdmin(); + expect(newAdmin).to.eq(newDefaultAdmin); + expect(schedule).to.be.bignumber.eq(firstSchedule); + }); + } + + 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)); + + // Accepts + await this.accessControl.acceptDefaultAdminTransfer({ from: newDefaultAdmin }); + + const { newAdmin, schedule } = await this.accessControl.pendingDefaultAdmin(); + expect(newAdmin).to.eq(ZERO_ADDRESS); + expect(schedule).to.be.bignumber.eq(ZERO); + }); + }); + }); + + describe('defaultAdminDelay()', function () { + it('returns the current delay', async function () { + expect(await this.accessControl.defaultAdminDelay()).to.be.bignumber.eq(delay); + }); + + describe('when there is a scheduled delay change', function () { + const newDelay = web3.utils.toBN(0xdead); // Any change + + beforeEach('begins delay change', async function () { + await this.accessControl.changeDefaultAdminDelay(newDelay, { from: defaultAdmin }); + }); + + for (const [fromSchedule, tag, expectedDelay, delayTag] of [ + [-1, 'before', delay, 'old'], + [0, 'exactly when', delay, 'old'], + [1, 'after', newDelay, 'new'], + ]) { + it(`returns ${delayTag} delay ${tag} delay schedule passes`, async function () { + // Wait until schedule + fromSchedule + const { schedule } = await this.accessControl.pendingDefaultAdminDelay(); + await time.setNextBlockTimestamp(schedule.toNumber() + fromSchedule); + await network.provider.send('evm_mine'); // Mine a block to force the timestamp + + const currentDelay = await this.accessControl.defaultAdminDelay(); + expect(currentDelay).to.be.bignumber.eq(expectedDelay); + }); + } + }); + }); + + describe('pendingDefaultAdminDelay()', function () { + it('returns 0 if not set', async function () { + const { newDelay, schedule } = await this.accessControl.pendingDefaultAdminDelay(); + expect(newDelay).to.be.bignumber.eq(ZERO); + expect(schedule).to.be.bignumber.eq(ZERO); + }); + + describe('when there is a scheduled delay change', function () { + const newDelay = web3.utils.toBN(0xdead); // Any change + + beforeEach('begins admin transfer', async function () { + await this.accessControl.changeDefaultAdminDelay(newDelay, { from: defaultAdmin }); + }); + + for (const [fromSchedule, tag, expectedDelay, delayTag, expectZeroSchedule] of [ + [-1, 'before', newDelay, 'new'], + [0, 'exactly when', newDelay, 'new'], + [1, 'after', ZERO, 'zero', true], + ]) { + it(`returns ${delayTag} delay ${tag} delay schedule passes`, async function () { + // Wait until schedule + fromSchedule + const { schedule: firstSchedule } = await this.accessControl.pendingDefaultAdminDelay(); + await time.setNextBlockTimestamp(firstSchedule.toNumber() + fromSchedule); + await network.provider.send('evm_mine'); // Mine a block to force the timestamp + + const { newDelay, schedule } = await this.accessControl.pendingDefaultAdminDelay(); + expect(newDelay).to.be.bignumber.eq(expectedDelay); + expect(schedule).to.be.bignumber.eq(expectZeroSchedule ? ZERO : firstSchedule); + }); + } + }); + }); + + describe('defaultAdminDelayIncreaseWait()', function () { + it('should return 5 days (default)', async function () { + expect(await this.accessControl.defaultAdminDelayIncreaseWait()).to.be.bignumber.eq( + web3.utils.toBN(time.duration.days(5)), + ); + }); + }); + + it('should revert if granting default admin role', async function () { + await expectRevertCustomError( + this.accessControl.grantRole(DEFAULT_ADMIN_ROLE, defaultAdmin, { from: defaultAdmin }), + 'AccessControlEnforcedDefaultAdminRules', + [], + ); + }); + + it('should revert if revoking default admin role', async function () { + await expectRevertCustomError( + this.accessControl.revokeRole(DEFAULT_ADMIN_ROLE, defaultAdmin, { from: defaultAdmin }), + 'AccessControlEnforcedDefaultAdminRules', + [], + ); + }); + + it("should revert if defaultAdmin's admin is changed", async function () { + await expectRevertCustomError( + this.accessControl.$_setRoleAdmin(DEFAULT_ADMIN_ROLE, OTHER_ROLE), + 'AccessControlEnforcedDefaultAdminRules', + [], + ); + }); + + it('should not grant the default admin role twice', async function () { + await expectRevertCustomError( + this.accessControl.$_grantRole(DEFAULT_ADMIN_ROLE, defaultAdmin), + 'AccessControlEnforcedDefaultAdminRules', + [], + ); + }); + + describe('begins a default admin transfer', function () { + let receipt; + let acceptSchedule; + + it('reverts if called by non default admin accounts', async function () { + await expectRevertCustomError( + this.accessControl.beginDefaultAdminTransfer(newDefaultAdmin, { from: other }), + 'AccessControlUnauthorizedAccount', + [other, DEFAULT_ADMIN_ROLE], + ); + }); + + describe('when there is no pending delay nor pending admin transfer', function () { + beforeEach('begins admin transfer', async function () { + receipt = await this.accessControl.beginDefaultAdminTransfer(newDefaultAdmin, { from: defaultAdmin }); + acceptSchedule = web3.utils.toBN(await time.latest()).add(delay); + }); + + it('should set pending default admin and schedule', async function () { + const { newAdmin, schedule } = await this.accessControl.pendingDefaultAdmin(); + expect(newAdmin).to.equal(newDefaultAdmin); + expect(schedule).to.be.bignumber.equal(acceptSchedule); + expectEvent(receipt, 'DefaultAdminTransferScheduled', { + newAdmin, + acceptSchedule, + }); + }); + }); + + describe('when there is a pending admin transfer', function () { + beforeEach('sets a pending default admin transfer', async function () { + await this.accessControl.beginDefaultAdminTransfer(newDefaultAdmin, { from: defaultAdmin }); + acceptSchedule = web3.utils.toBN(await time.latest()).add(delay); + }); + + for (const [fromSchedule, tag] of [ + [-1, 'before'], + [0, 'exactly when'], + [1, 'after'], + ]) { + it(`should be able to begin a transfer again ${tag} acceptSchedule passes`, async function () { + // Wait until schedule + fromSchedule + await time.setNextBlockTimestamp(acceptSchedule.toNumber() + fromSchedule); + + // defaultAdmin changes its mind and begin again to another address + const receipt = await this.accessControl.beginDefaultAdminTransfer(other, { from: defaultAdmin }); + const newSchedule = web3.utils.toBN(await time.latest()).add(delay); + const { newAdmin, schedule } = await this.accessControl.pendingDefaultAdmin(); + expect(newAdmin).to.equal(other); + expect(schedule).to.be.bignumber.equal(newSchedule); + + // Cancellation is always emitted since it was never accepted + expectEvent(receipt, 'DefaultAdminTransferCanceled'); + }); + } + + it('should not emit a cancellation event if the new default admin accepted', async function () { + // Wait until the acceptSchedule has passed + await time.setNextBlockTimestamp(acceptSchedule.addn(1)); + + // Accept and restart + await this.accessControl.acceptDefaultAdminTransfer({ from: newDefaultAdmin }); + const receipt = await this.accessControl.beginDefaultAdminTransfer(other, { from: newDefaultAdmin }); + + expectEvent.notEmitted(receipt, 'DefaultAdminTransferCanceled'); + }); + }); + + describe('when there is a pending delay', function () { + const newDelay = web3.utils.toBN(time.duration.hours(3)); + + beforeEach('schedule a delay change', async function () { + await this.accessControl.changeDefaultAdminDelay(newDelay, { from: defaultAdmin }); + const pendingDefaultAdminDelay = await this.accessControl.pendingDefaultAdminDelay(); + acceptSchedule = pendingDefaultAdminDelay.schedule; + }); + + for (const [fromSchedule, schedulePassed, expectedDelay, delayTag] of [ + [-1, 'before', delay, 'old'], + [0, 'exactly when', delay, 'old'], + [1, 'after', newDelay, 'new'], + ]) { + it(`should set the ${delayTag} delay and apply it to next default admin transfer schedule ${schedulePassed} acceptSchedule passed`, async function () { + // Wait until the expected fromSchedule time + await time.setNextBlockTimestamp(acceptSchedule.toNumber() + fromSchedule); + + // Start the new default admin transfer and get its schedule + const receipt = await this.accessControl.beginDefaultAdminTransfer(newDefaultAdmin, { from: defaultAdmin }); + const expectedAcceptSchedule = web3.utils.toBN(await time.latest()).add(expectedDelay); + + // Check that the schedule corresponds with the new delay + const { newAdmin, schedule: transferSchedule } = await this.accessControl.pendingDefaultAdmin(); + expect(newAdmin).to.equal(newDefaultAdmin); + expect(transferSchedule).to.be.bignumber.equal(expectedAcceptSchedule); + + expectEvent(receipt, 'DefaultAdminTransferScheduled', { + newAdmin, + acceptSchedule: expectedAcceptSchedule, + }); + }); + } + }); + }); + + describe('accepts transfer admin', function () { + let acceptSchedule; + + beforeEach(async function () { + await this.accessControl.beginDefaultAdminTransfer(newDefaultAdmin, { from: defaultAdmin }); + acceptSchedule = web3.utils.toBN(await time.latest()).add(delay); + }); + + it('should revert if caller is not pending default admin', async function () { + await time.setNextBlockTimestamp(acceptSchedule.addn(1)); + await expectRevertCustomError( + this.accessControl.acceptDefaultAdminTransfer({ from: other }), + 'AccessControlInvalidDefaultAdmin', + [other], + ); + }); + + describe('when caller is pending default admin and delay has passed', function () { + beforeEach(async function () { + await time.setNextBlockTimestamp(acceptSchedule.addn(1)); + }); + + it('accepts a transfer and changes default admin', async function () { + const receipt = await this.accessControl.acceptDefaultAdminTransfer({ from: newDefaultAdmin }); + + // Storage changes + expect(await this.accessControl.hasRole(DEFAULT_ADMIN_ROLE, defaultAdmin)).to.be.false; + expect(await this.accessControl.hasRole(DEFAULT_ADMIN_ROLE, newDefaultAdmin)).to.be.true; + expect(await this.accessControl.owner()).to.equal(newDefaultAdmin); + + // Emit events + expectEvent(receipt, 'RoleRevoked', { + role: DEFAULT_ADMIN_ROLE, + account: defaultAdmin, + }); + expectEvent(receipt, 'RoleGranted', { + role: DEFAULT_ADMIN_ROLE, + account: newDefaultAdmin, + }); + + // Resets pending default admin and schedule + const { newAdmin, schedule } = await this.accessControl.pendingDefaultAdmin(); + expect(newAdmin).to.equal(constants.ZERO_ADDRESS); + expect(schedule).to.be.bignumber.equal(ZERO); + }); + }); + + describe('schedule not passed', function () { + for (const [fromSchedule, tag] of [ + [-1, 'less'], + [0, 'equal'], + ]) { + it(`should revert if block.timestamp is ${tag} to schedule`, async function () { + await time.setNextBlockTimestamp(acceptSchedule.toNumber() + fromSchedule); + await expectRevertCustomError( + this.accessControl.acceptDefaultAdminTransfer({ from: newDefaultAdmin }), + 'AccessControlEnforcedDefaultAdminDelay', + [acceptSchedule], + ); + }); + } + }); + }); + + describe('cancels a default admin transfer', function () { + it('reverts if called by non default admin accounts', async function () { + await expectRevertCustomError( + this.accessControl.cancelDefaultAdminTransfer({ from: other }), + 'AccessControlUnauthorizedAccount', + [other, DEFAULT_ADMIN_ROLE], + ); + }); + + describe('when there is a pending default admin transfer', function () { + let acceptSchedule; + + beforeEach(async function () { + await this.accessControl.beginDefaultAdminTransfer(newDefaultAdmin, { from: defaultAdmin }); + acceptSchedule = web3.utils.toBN(await time.latest()).add(delay); + }); + + for (const [fromSchedule, tag] of [ + [-1, 'before'], + [0, 'exactly when'], + [1, 'after'], + ]) { + it(`resets pending default admin and schedule ${tag} transfer schedule passes`, async function () { + // Advance until passed delay + await time.setNextBlockTimestamp(acceptSchedule.toNumber() + fromSchedule); + + const receipt = await this.accessControl.cancelDefaultAdminTransfer({ from: defaultAdmin }); + + const { newAdmin, schedule } = await this.accessControl.pendingDefaultAdmin(); + expect(newAdmin).to.equal(constants.ZERO_ADDRESS); + expect(schedule).to.be.bignumber.equal(ZERO); + + expectEvent(receipt, 'DefaultAdminTransferCanceled'); + }); + } + + it('should revert if the previous default admin tries to accept', async function () { + await this.accessControl.cancelDefaultAdminTransfer({ from: defaultAdmin }); + + // Advance until passed delay + await time.setNextBlockTimestamp(acceptSchedule.addn(1)); + + // Previous pending default admin should not be able to accept after cancellation. + await expectRevertCustomError( + this.accessControl.acceptDefaultAdminTransfer({ from: newDefaultAdmin }), + 'AccessControlInvalidDefaultAdmin', + [newDefaultAdmin], + ); + }); + }); + + describe('when there is no pending default admin transfer', async function () { + it('should succeed without changes', async function () { + const receipt = await this.accessControl.cancelDefaultAdminTransfer({ from: defaultAdmin }); + + const { newAdmin, schedule } = await this.accessControl.pendingDefaultAdmin(); + expect(newAdmin).to.equal(constants.ZERO_ADDRESS); + expect(schedule).to.be.bignumber.equal(ZERO); + + expectEvent.notEmitted(receipt, 'DefaultAdminTransferCanceled'); + }); + }); + }); + + describe('renounces admin', function () { + let expectedSchedule; + let delayPassed; + let delayNotPassed; + + beforeEach(async function () { + await this.accessControl.beginDefaultAdminTransfer(constants.ZERO_ADDRESS, { from: defaultAdmin }); + 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 () { + await time.setNextBlockTimestamp(delayPassed); + await expectRevertCustomError( + this.accessControl.renounceRole(DEFAULT_ADMIN_ROLE, other, { from: defaultAdmin }), + 'AccessControlBadConfirmation', + [], + ); + }); + + 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); + + // 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, defaultAdmin, { from: defaultAdmin }); + + expect(await this.accessControl.hasRole(DEFAULT_ADMIN_ROLE, defaultAdmin)).to.be.false; + expect(await this.accessControl.defaultAdmin()).to.be.equal(constants.ZERO_ADDRESS); + expectEvent(receipt, 'RoleRevoked', { + role: DEFAULT_ADMIN_ROLE, + 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 () { + await time.setNextBlockTimestamp(delayPassed); + await this.accessControl.renounceRole(DEFAULT_ADMIN_ROLE, defaultAdmin, { from: defaultAdmin }); + + const grantRoleReceipt = await this.accessControl.$_grantRole(DEFAULT_ADMIN_ROLE, other); + expectEvent(grantRoleReceipt, 'RoleGranted', { + role: DEFAULT_ADMIN_ROLE, + account: other, + }); + }); + + describe('schedule not passed', function () { + for (const [fromSchedule, tag] of [ + [-1, 'less'], + [0, 'equal'], + ]) { + it(`reverts if block.timestamp is ${tag} to schedule`, async function () { + await time.setNextBlockTimestamp(delayNotPassed.toNumber() + fromSchedule); + await expectRevertCustomError( + this.accessControl.renounceRole(DEFAULT_ADMIN_ROLE, defaultAdmin, { from: defaultAdmin }), + 'AccessControlEnforcedDefaultAdminDelay', + [expectedSchedule], + ); + }); + } + }); + }); + + describe('changes delay', function () { + it('reverts if called by non default admin accounts', async function () { + await expectRevertCustomError( + this.accessControl.changeDefaultAdminDelay(time.duration.hours(4), { + from: other, + }), + 'AccessControlUnauthorizedAccount', + [other, DEFAULT_ADMIN_ROLE], + ); + }); + + for (const [newDefaultAdminDelay, delayChangeType] of [ + [web3.utils.toBN(delay).subn(time.duration.hours(1)), 'decreased'], + [web3.utils.toBN(delay).addn(time.duration.hours(1)), 'increased'], + [web3.utils.toBN(delay).addn(time.duration.days(5)), 'increased to more than 5 days'], + ]) { + describe(`when the delay is ${delayChangeType}`, function () { + it('begins the delay change to the new delay', async function () { + // Begins the change + const receipt = await this.accessControl.changeDefaultAdminDelay(newDefaultAdminDelay, { + from: defaultAdmin, + }); + + // Calculate expected values + const cap = await this.accessControl.defaultAdminDelayIncreaseWait(); + const changeDelay = newDefaultAdminDelay.lte(delay) + ? delay.sub(newDefaultAdminDelay) + : BN.min(newDefaultAdminDelay, cap); + const timestamp = web3.utils.toBN(await time.latest()); + const effectSchedule = timestamp.add(changeDelay); + + // Assert + const { newDelay, schedule } = await this.accessControl.pendingDefaultAdminDelay(); + expect(newDelay).to.be.bignumber.eq(newDefaultAdminDelay); + expect(schedule).to.be.bignumber.eq(effectSchedule); + expectEvent(receipt, 'DefaultAdminDelayChangeScheduled', { + newDelay, + effectSchedule, + }); + }); + + describe('scheduling again', function () { + beforeEach('schedule once', async function () { + await this.accessControl.changeDefaultAdminDelay(newDefaultAdminDelay, { from: defaultAdmin }); + }); + + for (const [fromSchedule, tag] of [ + [-1, 'before'], + [0, 'exactly when'], + [1, 'after'], + ]) { + const passed = fromSchedule > 0; + + it(`succeeds ${tag} the delay schedule passes`, async function () { + // Wait until schedule + fromSchedule + const { schedule: firstSchedule } = await this.accessControl.pendingDefaultAdminDelay(); + await time.setNextBlockTimestamp(firstSchedule.toNumber() + fromSchedule); + + // Default admin changes its mind and begins another delay change + const anotherNewDefaultAdminDelay = newDefaultAdminDelay.addn(time.duration.hours(2)); + const receipt = await this.accessControl.changeDefaultAdminDelay(anotherNewDefaultAdminDelay, { + from: defaultAdmin, + }); + + // Calculate expected values + const cap = await this.accessControl.defaultAdminDelayIncreaseWait(); + const timestamp = web3.utils.toBN(await time.latest()); + const effectSchedule = timestamp.add(BN.min(cap, anotherNewDefaultAdminDelay)); + + // Assert + const { newDelay, schedule } = await this.accessControl.pendingDefaultAdminDelay(); + expect(newDelay).to.be.bignumber.eq(anotherNewDefaultAdminDelay); + expect(schedule).to.be.bignumber.eq(effectSchedule); + expectEvent(receipt, 'DefaultAdminDelayChangeScheduled', { + newDelay, + effectSchedule, + }); + }); + + const emit = passed ? 'not emit' : 'emit'; + it(`should ${emit} a cancellation event ${tag} the delay schedule passes`, async function () { + // Wait until schedule + fromSchedule + const { schedule: firstSchedule } = await this.accessControl.pendingDefaultAdminDelay(); + await time.setNextBlockTimestamp(firstSchedule.toNumber() + fromSchedule); + + // Default admin changes its mind and begins another delay change + const anotherNewDefaultAdminDelay = newDefaultAdminDelay.addn(time.duration.hours(2)); + const receipt = await this.accessControl.changeDefaultAdminDelay(anotherNewDefaultAdminDelay, { + from: defaultAdmin, + }); + + const eventMatcher = passed ? expectEvent.notEmitted : expectEvent; + eventMatcher(receipt, 'DefaultAdminDelayChangeCanceled'); + }); + } + }); + }); + } + }); + + describe('rollbacks a delay change', function () { + it('reverts if called by non default admin accounts', async function () { + await expectRevertCustomError( + this.accessControl.rollbackDefaultAdminDelay({ from: other }), + 'AccessControlUnauthorizedAccount', + [other, DEFAULT_ADMIN_ROLE], + ); + }); + + describe('when there is a pending delay', function () { + beforeEach('set pending delay', async function () { + await this.accessControl.changeDefaultAdminDelay(time.duration.hours(12), { from: defaultAdmin }); + }); + + for (const [fromSchedule, tag] of [ + [-1, 'before'], + [0, 'exactly when'], + [1, 'after'], + ]) { + const passed = fromSchedule > 0; + + it(`resets pending delay and schedule ${tag} delay change schedule passes`, async function () { + // Wait until schedule + fromSchedule + const { schedule: firstSchedule } = await this.accessControl.pendingDefaultAdminDelay(); + await time.setNextBlockTimestamp(firstSchedule.toNumber() + fromSchedule); + + await this.accessControl.rollbackDefaultAdminDelay({ from: defaultAdmin }); + + const { newDelay, schedule } = await this.accessControl.pendingDefaultAdminDelay(); + expect(newDelay).to.be.bignumber.eq(ZERO); + expect(schedule).to.be.bignumber.eq(ZERO); + }); + + const emit = passed ? 'not emit' : 'emit'; + it(`should ${emit} a cancellation event ${tag} the delay schedule passes`, async function () { + // Wait until schedule + fromSchedule + const { schedule: firstSchedule } = await this.accessControl.pendingDefaultAdminDelay(); + await time.setNextBlockTimestamp(firstSchedule.toNumber() + fromSchedule); + + const receipt = await this.accessControl.rollbackDefaultAdminDelay({ from: defaultAdmin }); + + const eventMatcher = passed ? expectEvent.notEmitted : expectEvent; + eventMatcher(receipt, 'DefaultAdminDelayChangeCanceled'); + }); + } + }); + + describe('when there is no pending delay', function () { + it('succeeds without changes', async function () { + await this.accessControl.rollbackDefaultAdminDelay({ from: defaultAdmin }); + + const { newDelay, schedule } = await this.accessControl.pendingDefaultAdminDelay(); + expect(newDelay).to.be.bignumber.eq(ZERO); + expect(schedule).to.be.bignumber.eq(ZERO); + }); + }); + }); +} + +module.exports = { + DEFAULT_ADMIN_ROLE, + shouldBehaveLikeAccessControl, + shouldBehaveLikeAccessControlEnumerable, + shouldBehaveLikeAccessControlDefaultAdminRules, +}; diff --git a/test/access/AccessControl.test.js b/test/access/AccessControl.test.js new file mode 100644 index 000000000..14463b505 --- /dev/null +++ b/test/access/AccessControl.test.js @@ -0,0 +1,12 @@ +const { DEFAULT_ADMIN_ROLE, shouldBehaveLikeAccessControl } = require('./AccessControl.behavior.js'); + +const AccessControl = artifacts.require('$AccessControl'); + +contract('AccessControl', function (accounts) { + beforeEach(async function () { + this.accessControl = await AccessControl.new({ from: accounts[0] }); + await this.accessControl.$_grantRole(DEFAULT_ADMIN_ROLE, accounts[0]); + }); + + shouldBehaveLikeAccessControl(...accounts); +}); diff --git a/test/access/Ownable.test.js b/test/access/Ownable.test.js new file mode 100644 index 000000000..f85daec5d --- /dev/null +++ b/test/access/Ownable.test.js @@ -0,0 +1,72 @@ +const { constants, expectEvent } = require('@openzeppelin/test-helpers'); +const { expectRevertCustomError } = require('../helpers/customError'); + +const { ZERO_ADDRESS } = constants; + +const { expect } = require('chai'); + +const Ownable = artifacts.require('$Ownable'); + +contract('Ownable', function (accounts) { + const [owner, other] = accounts; + + beforeEach(async function () { + this.ownable = await Ownable.new(owner); + }); + + it('rejects zero address for initialOwner', async function () { + await expectRevertCustomError(Ownable.new(constants.ZERO_ADDRESS), 'OwnableInvalidOwner', [constants.ZERO_ADDRESS]); + }); + + it('has an owner', async function () { + expect(await this.ownable.owner()).to.equal(owner); + }); + + describe('transfer ownership', function () { + it('changes owner after transfer', async function () { + const receipt = await this.ownable.transferOwnership(other, { from: owner }); + expectEvent(receipt, 'OwnershipTransferred'); + + expect(await this.ownable.owner()).to.equal(other); + }); + + it('prevents non-owners from transferring', async function () { + await expectRevertCustomError( + this.ownable.transferOwnership(other, { from: other }), + 'OwnableUnauthorizedAccount', + [other], + ); + }); + + it('guards ownership against stuck state', async function () { + await expectRevertCustomError( + this.ownable.transferOwnership(ZERO_ADDRESS, { from: owner }), + 'OwnableInvalidOwner', + [ZERO_ADDRESS], + ); + }); + }); + + describe('renounce ownership', function () { + it('loses ownership after renouncement', async function () { + const receipt = await this.ownable.renounceOwnership({ from: owner }); + expectEvent(receipt, 'OwnershipTransferred'); + + expect(await this.ownable.owner()).to.equal(ZERO_ADDRESS); + }); + + it('prevents non-owners from renouncement', async function () { + await expectRevertCustomError(this.ownable.renounceOwnership({ from: other }), 'OwnableUnauthorizedAccount', [ + other, + ]); + }); + + it('allows to recover access using the internal _transferOwnership', async function () { + await this.ownable.renounceOwnership({ from: owner }); + const receipt = await this.ownable.$_transferOwnership(other); + expectEvent(receipt, 'OwnershipTransferred'); + + expect(await this.ownable.owner()).to.equal(other); + }); + }); +}); diff --git a/test/access/Ownable2Step.test.js b/test/access/Ownable2Step.test.js new file mode 100644 index 000000000..bdbac48fa --- /dev/null +++ b/test/access/Ownable2Step.test.js @@ -0,0 +1,70 @@ +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'); + +contract('Ownable2Step', function (accounts) { + const [owner, accountA, accountB] = accounts; + + beforeEach(async function () { + this.ownable2Step = await Ownable2Step.new(owner); + }); + + describe('transfer ownership', function () { + it('starting a transfer does not change owner', async function () { + const receipt = await this.ownable2Step.transferOwnership(accountA, { from: owner }); + expectEvent(receipt, 'OwnershipTransferStarted', { previousOwner: owner, newOwner: accountA }); + expect(await this.ownable2Step.owner()).to.equal(owner); + expect(await this.ownable2Step.pendingOwner()).to.equal(accountA); + }); + + it('changes owner after transfer', async function () { + await this.ownable2Step.transferOwnership(accountA, { from: owner }); + const receipt = await this.ownable2Step.acceptOwnership({ from: accountA }); + expectEvent(receipt, 'OwnershipTransferred', { previousOwner: owner, newOwner: accountA }); + expect(await this.ownable2Step.owner()).to.equal(accountA); + expect(await this.ownable2Step.pendingOwner()).to.not.equal(accountA); + }); + + it('guards transfer against invalid user', async function () { + await this.ownable2Step.transferOwnership(accountA, { from: owner }); + await expectRevertCustomError( + this.ownable2Step.acceptOwnership({ from: accountB }), + 'OwnableUnauthorizedAccount', + [accountB], + ); + }); + }); + + 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 ... + // without it is difficult to cleanly renounce with the two step process + // see: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3620#discussion_r957930388 + expect(await this.ownable2Step.owner()).to.equal(ZERO_ADDRESS); + }); + + it('pending owner resets after renouncing ownership', async function () { + await this.ownable2Step.transferOwnership(accountA, { from: owner }); + expect(await this.ownable2Step.pendingOwner()).to.equal(accountA); + await this.ownable2Step.renounceOwnership({ from: owner }); + expect(await this.ownable2Step.pendingOwner()).to.equal(ZERO_ADDRESS); + await expectRevertCustomError( + this.ownable2Step.acceptOwnership({ from: accountA }), + 'OwnableUnauthorizedAccount', + [accountA], + ); + }); + + it('allows to recover access using the internal _transferOwnership', async function () { + await this.ownable2Step.renounceOwnership({ from: owner }); + const receipt = await this.ownable2Step.$_transferOwnership(accountA); + expectEvent(receipt, 'OwnershipTransferred'); + + expect(await this.ownable2Step.owner()).to.equal(accountA); + }); + }); +}); diff --git a/test/access/extensions/AccessControlDefaultAdminRules.test.js b/test/access/extensions/AccessControlDefaultAdminRules.test.js new file mode 100644 index 000000000..af34143f1 --- /dev/null +++ b/test/access/extensions/AccessControlDefaultAdminRules.test.js @@ -0,0 +1,26 @@ +const { time, constants, expectRevert } = require('@openzeppelin/test-helpers'); +const { + shouldBehaveLikeAccessControl, + shouldBehaveLikeAccessControlDefaultAdminRules, +} = require('../AccessControl.behavior.js'); + +const AccessControlDefaultAdminRules = artifacts.require('$AccessControlDefaultAdminRules'); + +contract('AccessControlDefaultAdminRules', function (accounts) { + const delay = web3.utils.toBN(time.duration.hours(10)); + + beforeEach(async function () { + 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), + 'AccessControlInvalidDefaultAdmin', + [constants.ZERO_ADDRESS], + ); + }); + + shouldBehaveLikeAccessControl(...accounts); + shouldBehaveLikeAccessControlDefaultAdminRules(delay, ...accounts); +}); diff --git a/test/access/extensions/AccessControlEnumerable.test.js b/test/access/extensions/AccessControlEnumerable.test.js new file mode 100644 index 000000000..7dfb0bce8 --- /dev/null +++ b/test/access/extensions/AccessControlEnumerable.test.js @@ -0,0 +1,17 @@ +const { + DEFAULT_ADMIN_ROLE, + shouldBehaveLikeAccessControl, + shouldBehaveLikeAccessControlEnumerable, +} = require('../AccessControl.behavior.js'); + +const AccessControlEnumerable = artifacts.require('$AccessControlEnumerable'); + +contract('AccessControlEnumerable', function (accounts) { + beforeEach(async function () { + this.accessControl = await AccessControlEnumerable.new({ from: accounts[0] }); + await this.accessControl.$_grantRole(DEFAULT_ADMIN_ROLE, accounts[0]); + }); + + shouldBehaveLikeAccessControl(...accounts); + shouldBehaveLikeAccessControlEnumerable(...accounts); +}); diff --git a/test/access/manager/AccessManager.test.js b/test/access/manager/AccessManager.test.js new file mode 100644 index 000000000..1da7b3ced --- /dev/null +++ b/test/access/manager/AccessManager.test.js @@ -0,0 +1,1161 @@ +const { web3 } = require('hardhat'); +const { constants, expectEvent, time } = require('@openzeppelin/test-helpers'); +const { expectRevertCustomError } = require('../../helpers/customError'); +const { selector } = require('../../helpers/methods'); +const { clockFromReceipt } = require('../../helpers/time'); +const { product } = require('../../helpers/iterate'); +const helpers = require('@nomicfoundation/hardhat-network-helpers'); + +const AccessManager = artifacts.require('$AccessManager'); +const AccessManagedTarget = artifacts.require('$AccessManagedTarget'); +const Ownable = artifacts.require('$Ownable'); + +const MAX_UINT64 = web3.utils.toBN((2n ** 64n - 1n).toString()); + +const ROLES = { + ADMIN: web3.utils.toBN(0), + SOME_ADMIN: web3.utils.toBN(17), + SOME: web3.utils.toBN(42), + PUBLIC: MAX_UINT64, +}; +Object.assign(ROLES, Object.fromEntries(Object.entries(ROLES).map(([key, value]) => [value, key]))); + +const executeDelay = web3.utils.toBN(10); +const grantDelay = web3.utils.toBN(10); +const MINSETBACK = time.duration.days(5); + +const formatAccess = access => [access[0], access[1].toString()]; + +contract('AccessManager', function (accounts) { + const [admin, manager, member, user, other] = accounts; + + beforeEach(async function () { + this.manager = await AccessManager.new(admin); + + // add member to role + await this.manager.$_setRoleAdmin(ROLES.SOME, ROLES.SOME_ADMIN); + await this.manager.$_setRoleGuardian(ROLES.SOME, ROLES.SOME_ADMIN); + await this.manager.$_grantRole(ROLES.SOME_ADMIN, manager, 0, 0); + await this.manager.$_grantRole(ROLES.SOME, member, 0, 0); + }); + + it('rejects zero address for initialAdmin', async function () { + await expectRevertCustomError(AccessManager.new(constants.ZERO_ADDRESS), 'AccessManagerInvalidInitialAdmin', [ + constants.ZERO_ADDRESS, + ]); + }); + + it('default minsetback is 1 day', async function () { + expect(await this.manager.minSetback()).to.be.bignumber.equal(MINSETBACK); + }); + + it('roles are correctly initialized', async function () { + // role admin + expect(await this.manager.getRoleAdmin(ROLES.ADMIN)).to.be.bignumber.equal(ROLES.ADMIN); + expect(await this.manager.getRoleAdmin(ROLES.SOME_ADMIN)).to.be.bignumber.equal(ROLES.ADMIN); + expect(await this.manager.getRoleAdmin(ROLES.SOME)).to.be.bignumber.equal(ROLES.SOME_ADMIN); + expect(await this.manager.getRoleAdmin(ROLES.PUBLIC)).to.be.bignumber.equal(ROLES.ADMIN); + // role guardian + expect(await this.manager.getRoleGuardian(ROLES.ADMIN)).to.be.bignumber.equal(ROLES.ADMIN); + expect(await this.manager.getRoleGuardian(ROLES.SOME_ADMIN)).to.be.bignumber.equal(ROLES.ADMIN); + expect(await this.manager.getRoleGuardian(ROLES.SOME)).to.be.bignumber.equal(ROLES.SOME_ADMIN); + expect(await this.manager.getRoleGuardian(ROLES.PUBLIC)).to.be.bignumber.equal(ROLES.ADMIN); + // role members + expect(await this.manager.hasRole(ROLES.ADMIN, admin).then(formatAccess)).to.be.deep.equal([true, '0']); + expect(await this.manager.hasRole(ROLES.ADMIN, manager).then(formatAccess)).to.be.deep.equal([false, '0']); + expect(await this.manager.hasRole(ROLES.ADMIN, member).then(formatAccess)).to.be.deep.equal([false, '0']); + expect(await this.manager.hasRole(ROLES.ADMIN, user).then(formatAccess)).to.be.deep.equal([false, '0']); + expect(await this.manager.hasRole(ROLES.SOME_ADMIN, admin).then(formatAccess)).to.be.deep.equal([false, '0']); + expect(await this.manager.hasRole(ROLES.SOME_ADMIN, manager).then(formatAccess)).to.be.deep.equal([true, '0']); + expect(await this.manager.hasRole(ROLES.SOME_ADMIN, member).then(formatAccess)).to.be.deep.equal([false, '0']); + expect(await this.manager.hasRole(ROLES.SOME_ADMIN, user).then(formatAccess)).to.be.deep.equal([false, '0']); + expect(await this.manager.hasRole(ROLES.SOME, admin).then(formatAccess)).to.be.deep.equal([false, '0']); + expect(await this.manager.hasRole(ROLES.SOME, manager).then(formatAccess)).to.be.deep.equal([false, '0']); + expect(await this.manager.hasRole(ROLES.SOME, member).then(formatAccess)).to.be.deep.equal([true, '0']); + expect(await this.manager.hasRole(ROLES.SOME, user).then(formatAccess)).to.be.deep.equal([false, '0']); + expect(await this.manager.hasRole(ROLES.PUBLIC, admin).then(formatAccess)).to.be.deep.equal([true, '0']); + expect(await this.manager.hasRole(ROLES.PUBLIC, manager).then(formatAccess)).to.be.deep.equal([true, '0']); + expect(await this.manager.hasRole(ROLES.PUBLIC, member).then(formatAccess)).to.be.deep.equal([true, '0']); + expect(await this.manager.hasRole(ROLES.PUBLIC, user).then(formatAccess)).to.be.deep.equal([true, '0']); + }); + + describe('Roles management', function () { + describe('label role', function () { + it('admin can emit a label event', async function () { + expectEvent(await this.manager.labelRole(ROLES.SOME, 'Some label', { from: admin }), 'RoleLabel', { + roleId: ROLES.SOME, + label: 'Some label', + }); + }); + + it('admin can re-emit a label event', async function () { + await this.manager.labelRole(ROLES.SOME, 'Some label', { from: admin }); + + expectEvent(await this.manager.labelRole(ROLES.SOME, 'Updated label', { from: admin }), 'RoleLabel', { + roleId: ROLES.SOME, + label: 'Updated label', + }); + }); + + it('emitting a label is restricted', async function () { + await expectRevertCustomError( + this.manager.labelRole(ROLES.SOME, 'Invalid label', { from: other }), + 'AccessManagerUnauthorizedAccount', + [other, ROLES.ADMIN], + ); + }); + }); + + describe('grant role', function () { + describe('without a grant delay', function () { + it('without an execute delay', async function () { + expect(await this.manager.hasRole(ROLES.SOME, user).then(formatAccess)).to.be.deep.equal([false, '0']); + + const { receipt } = await this.manager.grantRole(ROLES.SOME, user, 0, { from: manager }); + const timestamp = await clockFromReceipt.timestamp(receipt).then(web3.utils.toBN); + expectEvent(receipt, 'RoleGranted', { + roleId: ROLES.SOME, + account: user, + since: timestamp, + delay: '0', + newMember: true, + }); + + expect(await this.manager.hasRole(ROLES.SOME, user).then(formatAccess)).to.be.deep.equal([true, '0']); + + const access = await this.manager.getAccess(ROLES.SOME, user); + expect(access[0]).to.be.bignumber.equal(timestamp); // inRoleSince + expect(access[1]).to.be.bignumber.equal('0'); // currentDelay + expect(access[2]).to.be.bignumber.equal('0'); // pendingDelay + expect(access[3]).to.be.bignumber.equal('0'); // effect + }); + + it('with an execute delay', async function () { + expect(await this.manager.hasRole(ROLES.SOME, user).then(formatAccess)).to.be.deep.equal([false, '0']); + + const { receipt } = await this.manager.grantRole(ROLES.SOME, user, executeDelay, { from: manager }); + const timestamp = await clockFromReceipt.timestamp(receipt).then(web3.utils.toBN); + expectEvent(receipt, 'RoleGranted', { + roleId: ROLES.SOME, + account: user, + since: timestamp, + delay: executeDelay, + newMember: true, + }); + + expect(await this.manager.hasRole(ROLES.SOME, user).then(formatAccess)).to.be.deep.equal([ + true, + executeDelay.toString(), + ]); + + const access = await this.manager.getAccess(ROLES.SOME, user); + expect(access[0]).to.be.bignumber.equal(timestamp); // inRoleSince + expect(access[1]).to.be.bignumber.equal(executeDelay); // currentDelay + expect(access[2]).to.be.bignumber.equal('0'); // pendingDelay + expect(access[3]).to.be.bignumber.equal('0'); // effect + }); + + it('to a user that is already in the role', async function () { + expect(await this.manager.hasRole(ROLES.SOME, member).then(formatAccess)).to.be.deep.equal([true, '0']); + await this.manager.grantRole(ROLES.SOME, member, 0, { from: manager }); + expect(await this.manager.hasRole(ROLES.SOME, member).then(formatAccess)).to.be.deep.equal([true, '0']); + }); + + it('to a user that is scheduled for joining the role', async function () { + await this.manager.$_grantRole(ROLES.SOME, user, 10, 0); // grant delay 10 + expect(await this.manager.hasRole(ROLES.SOME, user).then(formatAccess)).to.be.deep.equal([false, '0']); + await this.manager.grantRole(ROLES.SOME, user, 0, { from: manager }); + expect(await this.manager.hasRole(ROLES.SOME, user).then(formatAccess)).to.be.deep.equal([false, '0']); + }); + + it('grant role is restricted', async function () { + await expectRevertCustomError( + this.manager.grantRole(ROLES.SOME, user, 0, { from: other }), + 'AccessManagerUnauthorizedAccount', + [other, ROLES.SOME_ADMIN], + ); + }); + }); + + describe('with a grant delay', function () { + beforeEach(async function () { + await this.manager.$_setGrantDelay(ROLES.SOME, grantDelay); + await time.increase(MINSETBACK); + }); + + it('granted role is not active immediately', async function () { + const { receipt } = await this.manager.grantRole(ROLES.SOME, user, 0, { from: manager }); + const timestamp = await clockFromReceipt.timestamp(receipt).then(web3.utils.toBN); + expectEvent(receipt, 'RoleGranted', { + roleId: ROLES.SOME, + account: user, + since: timestamp.add(grantDelay), + delay: '0', + newMember: true, + }); + + expect(await this.manager.hasRole(ROLES.SOME, user).then(formatAccess)).to.be.deep.equal([false, '0']); + + const access = await this.manager.getAccess(ROLES.SOME, user); + expect(access[0]).to.be.bignumber.equal(timestamp.add(grantDelay)); // inRoleSince + expect(access[1]).to.be.bignumber.equal('0'); // currentDelay + expect(access[2]).to.be.bignumber.equal('0'); // pendingDelay + expect(access[3]).to.be.bignumber.equal('0'); // effect + }); + + it('granted role is active after the delay', async function () { + const { receipt } = await this.manager.grantRole(ROLES.SOME, user, 0, { from: manager }); + const timestamp = await clockFromReceipt.timestamp(receipt).then(web3.utils.toBN); + expectEvent(receipt, 'RoleGranted', { + roleId: ROLES.SOME, + account: user, + since: timestamp.add(grantDelay), + delay: '0', + newMember: true, + }); + + await time.increase(grantDelay); + + expect(await this.manager.hasRole(ROLES.SOME, user).then(formatAccess)).to.be.deep.equal([true, '0']); + + const access = await this.manager.getAccess(ROLES.SOME, user); + expect(access[0]).to.be.bignumber.equal(timestamp.add(grantDelay)); // inRoleSince + expect(access[1]).to.be.bignumber.equal('0'); // currentDelay + expect(access[2]).to.be.bignumber.equal('0'); // pendingDelay + expect(access[3]).to.be.bignumber.equal('0'); // effect + }); + }); + + it('cannot grant public role', async function () { + await expectRevertCustomError( + this.manager.$_grantRole(ROLES.PUBLIC, other, 0, executeDelay, { from: manager }), + 'AccessManagerLockedRole', + [ROLES.PUBLIC], + ); + }); + }); + + describe('revoke role', function () { + it('from a user that is already in the role', async function () { + expect(await this.manager.hasRole(ROLES.SOME, member).then(formatAccess)).to.be.deep.equal([true, '0']); + + const { receipt } = await this.manager.revokeRole(ROLES.SOME, member, { from: manager }); + expectEvent(receipt, 'RoleRevoked', { roleId: ROLES.SOME, account: member }); + + expect(await this.manager.hasRole(ROLES.SOME, member).then(formatAccess)).to.be.deep.equal([false, '0']); + + const access = await this.manager.getAccess(ROLES.SOME, user); + expect(access[0]).to.be.bignumber.equal('0'); // inRoleSince + expect(access[1]).to.be.bignumber.equal('0'); // currentDelay + expect(access[2]).to.be.bignumber.equal('0'); // pendingDelay + expect(access[3]).to.be.bignumber.equal('0'); // effect + }); + + it('from a user that is scheduled for joining the role', async function () { + await this.manager.$_grantRole(ROLES.SOME, user, 10, 0); // grant delay 10 + + expect(await this.manager.hasRole(ROLES.SOME, user).then(formatAccess)).to.be.deep.equal([false, '0']); + + const { receipt } = await this.manager.revokeRole(ROLES.SOME, user, { from: manager }); + expectEvent(receipt, 'RoleRevoked', { roleId: ROLES.SOME, account: user }); + + expect(await this.manager.hasRole(ROLES.SOME, user).then(formatAccess)).to.be.deep.equal([false, '0']); + + const access = await this.manager.getAccess(ROLES.SOME, user); + expect(access[0]).to.be.bignumber.equal('0'); // inRoleSince + expect(access[1]).to.be.bignumber.equal('0'); // currentDelay + expect(access[2]).to.be.bignumber.equal('0'); // pendingDelay + expect(access[3]).to.be.bignumber.equal('0'); // effect + }); + + it('from a user that is not in the role', async function () { + expect(await this.manager.hasRole(ROLES.SOME, user).then(formatAccess)).to.be.deep.equal([false, '0']); + await this.manager.revokeRole(ROLES.SOME, user, { from: manager }); + expect(await this.manager.hasRole(ROLES.SOME, user).then(formatAccess)).to.be.deep.equal([false, '0']); + }); + + it('revoke role is restricted', async function () { + await expectRevertCustomError( + this.manager.revokeRole(ROLES.SOME, member, { from: other }), + 'AccessManagerUnauthorizedAccount', + [other, ROLES.SOME_ADMIN], + ); + }); + }); + + describe('renounce role', function () { + it('for a user that is already in the role', async function () { + expect(await this.manager.hasRole(ROLES.SOME, member).then(formatAccess)).to.be.deep.equal([true, '0']); + + const { receipt } = await this.manager.renounceRole(ROLES.SOME, member, { from: member }); + expectEvent(receipt, 'RoleRevoked', { roleId: ROLES.SOME, account: member }); + + expect(await this.manager.hasRole(ROLES.SOME, member).then(formatAccess)).to.be.deep.equal([false, '0']); + + const access = await this.manager.getAccess(ROLES.SOME, member); + expect(access[0]).to.be.bignumber.equal('0'); // inRoleSince + expect(access[1]).to.be.bignumber.equal('0'); // currentDelay + expect(access[2]).to.be.bignumber.equal('0'); // pendingDelay + expect(access[3]).to.be.bignumber.equal('0'); // effect + }); + + it('for a user that is schedule for joining the role', async function () { + await this.manager.$_grantRole(ROLES.SOME, user, 10, 0); // grant delay 10 + + expect(await this.manager.hasRole(ROLES.SOME, user).then(formatAccess)).to.be.deep.equal([false, '0']); + + const { receipt } = await this.manager.renounceRole(ROLES.SOME, user, { from: user }); + expectEvent(receipt, 'RoleRevoked', { roleId: ROLES.SOME, account: user }); + + expect(await this.manager.hasRole(ROLES.SOME, user).then(formatAccess)).to.be.deep.equal([false, '0']); + + const access = await this.manager.getAccess(ROLES.SOME, user); + expect(access[0]).to.be.bignumber.equal('0'); // inRoleSince + expect(access[1]).to.be.bignumber.equal('0'); // currentDelay + expect(access[2]).to.be.bignumber.equal('0'); // pendingDelay + expect(access[3]).to.be.bignumber.equal('0'); // effect + }); + + it('for a user that is not in the role', async function () { + await this.manager.renounceRole(ROLES.SOME, user, { from: user }); + }); + + it('bad user confirmation', async function () { + await expectRevertCustomError( + this.manager.renounceRole(ROLES.SOME, member, { from: user }), + 'AccessManagerBadConfirmation', + [], + ); + }); + }); + + describe('change role admin', function () { + it("admin can set any role's admin", async function () { + expect(await this.manager.getRoleAdmin(ROLES.SOME)).to.be.bignumber.equal(ROLES.SOME_ADMIN); + + const { receipt } = await this.manager.setRoleAdmin(ROLES.SOME, ROLES.ADMIN, { from: admin }); + expectEvent(receipt, 'RoleAdminChanged', { roleId: ROLES.SOME, admin: ROLES.ADMIN }); + + expect(await this.manager.getRoleAdmin(ROLES.SOME)).to.be.bignumber.equal(ROLES.ADMIN); + }); + + it("setting a role's admin is restricted", async function () { + await expectRevertCustomError( + this.manager.setRoleAdmin(ROLES.SOME, ROLES.SOME, { from: manager }), + 'AccessManagerUnauthorizedAccount', + [manager, ROLES.ADMIN], + ); + }); + }); + + describe('change role guardian', function () { + it("admin can set any role's admin", async function () { + expect(await this.manager.getRoleGuardian(ROLES.SOME)).to.be.bignumber.equal(ROLES.SOME_ADMIN); + + const { receipt } = await this.manager.setRoleGuardian(ROLES.SOME, ROLES.ADMIN, { from: admin }); + expectEvent(receipt, 'RoleGuardianChanged', { roleId: ROLES.SOME, guardian: ROLES.ADMIN }); + + expect(await this.manager.getRoleGuardian(ROLES.SOME)).to.be.bignumber.equal(ROLES.ADMIN); + }); + + it("setting a role's admin is restricted", async function () { + await expectRevertCustomError( + this.manager.setRoleGuardian(ROLES.SOME, ROLES.SOME, { from: other }), + 'AccessManagerUnauthorizedAccount', + [other, ROLES.ADMIN], + ); + }); + }); + + describe('change execution delay', function () { + it('increasing the delay has immediate effect', async function () { + const oldDelay = web3.utils.toBN(10); + const newDelay = web3.utils.toBN(100); + + // role is already granted (with no delay) in the initial setup. this update takes time. + await this.manager.$_grantRole(ROLES.SOME, member, 0, oldDelay); + + const accessBefore = await this.manager.getAccess(ROLES.SOME, member); + expect(accessBefore[1]).to.be.bignumber.equal(oldDelay); // currentDelay + expect(accessBefore[2]).to.be.bignumber.equal('0'); // pendingDelay + expect(accessBefore[3]).to.be.bignumber.equal('0'); // effect + + const { receipt } = await this.manager.grantRole(ROLES.SOME, member, newDelay, { + from: manager, + }); + const timestamp = await clockFromReceipt.timestamp(receipt).then(web3.utils.toBN); + + expectEvent(receipt, 'RoleGranted', { + roleId: ROLES.SOME, + account: member, + since: timestamp, + delay: newDelay, + newMember: false, + }); + + // immediate effect + const accessAfter = await this.manager.getAccess(ROLES.SOME, member); + expect(accessAfter[1]).to.be.bignumber.equal(newDelay); // currentDelay + expect(accessAfter[2]).to.be.bignumber.equal('0'); // pendingDelay + expect(accessAfter[3]).to.be.bignumber.equal('0'); // effect + }); + + it('decreasing the delay takes time', async function () { + const oldDelay = web3.utils.toBN(100); + const newDelay = web3.utils.toBN(10); + + // role is already granted (with no delay) in the initial setup. this update takes time. + await this.manager.$_grantRole(ROLES.SOME, member, 0, oldDelay); + + const accessBefore = await this.manager.getAccess(ROLES.SOME, member); + expect(accessBefore[1]).to.be.bignumber.equal(oldDelay); // currentDelay + expect(accessBefore[2]).to.be.bignumber.equal('0'); // pendingDelay + expect(accessBefore[3]).to.be.bignumber.equal('0'); // effect + + const { receipt } = await this.manager.grantRole(ROLES.SOME, member, newDelay, { + from: manager, + }); + const timestamp = await clockFromReceipt.timestamp(receipt).then(web3.utils.toBN); + const setback = oldDelay.sub(newDelay); + + expectEvent(receipt, 'RoleGranted', { + roleId: ROLES.SOME, + account: member, + since: timestamp.add(setback), + delay: newDelay, + newMember: false, + }); + + // no immediate effect + const accessAfter = await this.manager.getAccess(ROLES.SOME, member); + expect(accessAfter[1]).to.be.bignumber.equal(oldDelay); // currentDelay + expect(accessAfter[2]).to.be.bignumber.equal(newDelay); // pendingDelay + expect(accessAfter[3]).to.be.bignumber.equal(timestamp.add(setback)); // effect + + // delayed effect + await time.increase(setback); + const accessAfterSetback = await this.manager.getAccess(ROLES.SOME, member); + expect(accessAfterSetback[1]).to.be.bignumber.equal(newDelay); // currentDelay + expect(accessAfterSetback[2]).to.be.bignumber.equal('0'); // pendingDelay + expect(accessAfterSetback[3]).to.be.bignumber.equal('0'); // effect + }); + + it('can set a user execution delay during the grant delay', async function () { + await this.manager.$_grantRole(ROLES.SOME, other, 10, 0); + // here: "other" is pending to get the role, but doesn't yet have it. + + const { receipt } = await this.manager.grantRole(ROLES.SOME, other, executeDelay, { from: manager }); + const timestamp = await clockFromReceipt.timestamp(receipt).then(web3.utils.toBN); + + // increasing the execution delay from 0 to executeDelay is immediate + expectEvent(receipt, 'RoleGranted', { + roleId: ROLES.SOME, + account: other, + since: timestamp, + delay: executeDelay, + newMember: false, + }); + }); + }); + + describe('change grant delay', function () { + it('increasing the delay has immediate effect', async function () { + const oldDelay = web3.utils.toBN(10); + const newDelay = web3.utils.toBN(100); + + await this.manager.$_setGrantDelay(ROLES.SOME, oldDelay); + await time.increase(MINSETBACK); + + expect(await this.manager.getRoleGrantDelay(ROLES.SOME)).to.be.bignumber.equal(oldDelay); + + const { receipt } = await this.manager.setGrantDelay(ROLES.SOME, newDelay, { from: admin }); + const timestamp = await clockFromReceipt.timestamp(receipt).then(web3.utils.toBN); + const setback = web3.utils.BN.max(MINSETBACK, oldDelay.sub(newDelay)); + + expect(setback).to.be.bignumber.equal(MINSETBACK); + expectEvent(receipt, 'RoleGrantDelayChanged', { + roleId: ROLES.SOME, + delay: newDelay, + since: timestamp.add(setback), + }); + + expect(await this.manager.getRoleGrantDelay(ROLES.SOME)).to.be.bignumber.equal(oldDelay); + await time.increase(setback); + expect(await this.manager.getRoleGrantDelay(ROLES.SOME)).to.be.bignumber.equal(newDelay); + }); + + it('increasing the delay has delay effect #1', async function () { + const oldDelay = web3.utils.toBN(100); + const newDelay = web3.utils.toBN(10); + + await this.manager.$_setGrantDelay(ROLES.SOME, oldDelay); + await time.increase(MINSETBACK); + + expect(await this.manager.getRoleGrantDelay(ROLES.SOME)).to.be.bignumber.equal(oldDelay); + + const { receipt } = await this.manager.setGrantDelay(ROLES.SOME, newDelay, { from: admin }); + const timestamp = await clockFromReceipt.timestamp(receipt).then(web3.utils.toBN); + const setback = web3.utils.BN.max(MINSETBACK, oldDelay.sub(newDelay)); + + expect(setback).to.be.bignumber.equal(MINSETBACK); + expectEvent(receipt, 'RoleGrantDelayChanged', { + roleId: ROLES.SOME, + delay: newDelay, + since: timestamp.add(setback), + }); + + expect(await this.manager.getRoleGrantDelay(ROLES.SOME)).to.be.bignumber.equal(oldDelay); + await time.increase(setback); + expect(await this.manager.getRoleGrantDelay(ROLES.SOME)).to.be.bignumber.equal(newDelay); + }); + + it('increasing the delay has delay effect #2', async function () { + const oldDelay = time.duration.days(30); // more than the minsetback + const newDelay = web3.utils.toBN(10); + + await this.manager.$_setGrantDelay(ROLES.SOME, oldDelay); + await time.increase(MINSETBACK); + + expect(await this.manager.getRoleGrantDelay(ROLES.SOME)).to.be.bignumber.equal(oldDelay); + + const { receipt } = await this.manager.setGrantDelay(ROLES.SOME, newDelay, { from: admin }); + const timestamp = await clockFromReceipt.timestamp(receipt).then(web3.utils.toBN); + const setback = web3.utils.BN.max(MINSETBACK, oldDelay.sub(newDelay)); + + expect(setback).to.be.bignumber.gt(MINSETBACK); + expectEvent(receipt, 'RoleGrantDelayChanged', { + roleId: ROLES.SOME, + delay: newDelay, + since: timestamp.add(setback), + }); + + expect(await this.manager.getRoleGrantDelay(ROLES.SOME)).to.be.bignumber.equal(oldDelay); + await time.increase(setback); + expect(await this.manager.getRoleGrantDelay(ROLES.SOME)).to.be.bignumber.equal(newDelay); + }); + + it('changing the grant delay is restricted', async function () { + await expectRevertCustomError( + this.manager.setGrantDelay(ROLES.SOME, grantDelay, { from: other }), + 'AccessManagerUnauthorizedAccount', + [ROLES.ADMIN, other], + ); + }); + }); + }); + + describe('with AccessManaged target contract', function () { + beforeEach('deploy target contract', async function () { + this.target = await AccessManagedTarget.new(this.manager.address); + // helpers for indirect calls + this.callData = selector('fnRestricted()'); + this.call = [this.target.address, this.callData]; + this.opId = web3.utils.keccak256( + web3.eth.abi.encodeParameters(['address', 'address', 'bytes'], [user, ...this.call]), + ); + this.direct = (opts = {}) => this.target.fnRestricted({ from: user, ...opts }); + this.schedule = (opts = {}) => this.manager.schedule(...this.call, 0, { from: user, ...opts }); + this.execute = (opts = {}) => this.manager.execute(...this.call, { from: user, ...opts }); + this.cancel = (opts = {}) => this.manager.cancel(user, ...this.call, { from: user, ...opts }); + }); + + describe('Change function permissions', function () { + const sigs = ['someFunction()', 'someOtherFunction(uint256)', 'oneMoreFunction(address,uint8)'].map(selector); + + it('admin can set function role', async function () { + for (const sig of sigs) { + expect(await this.manager.getTargetFunctionRole(this.target.address, sig)).to.be.bignumber.equal(ROLES.ADMIN); + } + + const { receipt: receipt1 } = await this.manager.setTargetFunctionRole(this.target.address, sigs, ROLES.SOME, { + from: admin, + }); + + for (const sig of sigs) { + expectEvent(receipt1, 'TargetFunctionRoleUpdated', { + target: this.target.address, + selector: sig, + roleId: ROLES.SOME, + }); + expect(await this.manager.getTargetFunctionRole(this.target.address, sig)).to.be.bignumber.equal(ROLES.SOME); + } + + const { receipt: receipt2 } = await this.manager.setTargetFunctionRole( + this.target.address, + [sigs[1]], + ROLES.SOME_ADMIN, + { + from: admin, + }, + ); + expectEvent(receipt2, 'TargetFunctionRoleUpdated', { + target: this.target.address, + selector: sigs[1], + roleId: ROLES.SOME_ADMIN, + }); + + for (const sig of sigs) { + expect(await this.manager.getTargetFunctionRole(this.target.address, sig)).to.be.bignumber.equal( + sig == sigs[1] ? ROLES.SOME_ADMIN : ROLES.SOME, + ); + } + }); + + it('non-admin cannot set function role', async function () { + await expectRevertCustomError( + this.manager.setTargetFunctionRole(this.target.address, sigs, ROLES.SOME, { from: other }), + 'AccessManagerUnauthorizedAccount', + [other, ROLES.ADMIN], + ); + }); + }); + + // WIP + describe('Calling restricted & unrestricted functions', function () { + for (const [callerRoles, fnRole, closed, delay] of product( + [[], [ROLES.SOME]], + [undefined, ROLES.ADMIN, ROLES.SOME, ROLES.PUBLIC], + [false, true], + [null, executeDelay], + )) { + // can we call with a delay ? + const indirectSuccess = (fnRole == ROLES.PUBLIC || callerRoles.includes(fnRole)) && !closed; + + // can we call without a delay ? + const directSuccess = (fnRole == ROLES.PUBLIC || (callerRoles.includes(fnRole) && !delay)) && !closed; + + const description = [ + 'Caller in roles', + '[' + (callerRoles ?? []).map(roleId => ROLES[roleId]).join(', ') + ']', + delay ? 'with a delay' : 'without a delay', + '+', + 'functions open to roles', + '[' + (ROLES[fnRole] ?? '') + ']', + closed ? `(closed)` : '', + ].join(' '); + + describe(description, function () { + beforeEach(async function () { + // setup + await Promise.all([ + this.manager.$_setTargetClosed(this.target.address, closed), + fnRole && this.manager.$_setTargetFunctionRole(this.target.address, selector('fnRestricted()'), fnRole), + fnRole && this.manager.$_setTargetFunctionRole(this.target.address, selector('fnUnrestricted()'), fnRole), + ...callerRoles + .filter(roleId => roleId != ROLES.PUBLIC) + .map(roleId => this.manager.$_grantRole(roleId, user, 0, delay ?? 0)), + ]); + + // post setup checks + expect(await this.manager.isTargetClosed(this.target.address)).to.be.equal(closed); + + if (fnRole) { + expect( + await this.manager.getTargetFunctionRole(this.target.address, selector('fnRestricted()')), + ).to.be.bignumber.equal(fnRole); + expect( + await this.manager.getTargetFunctionRole(this.target.address, selector('fnUnrestricted()')), + ).to.be.bignumber.equal(fnRole); + } + + for (const roleId of callerRoles) { + const access = await this.manager.getAccess(roleId, user); + if (roleId == ROLES.PUBLIC) { + expect(access[0]).to.be.bignumber.equal('0'); // inRoleSince + expect(access[1]).to.be.bignumber.equal('0'); // currentDelay + expect(access[2]).to.be.bignumber.equal('0'); // pendingDelay + expect(access[3]).to.be.bignumber.equal('0'); // effect + } else { + expect(access[0]).to.be.bignumber.gt('0'); // inRoleSince + expect(access[1]).to.be.bignumber.eq(String(delay ?? 0)); // currentDelay + expect(access[2]).to.be.bignumber.equal('0'); // pendingDelay + expect(access[3]).to.be.bignumber.equal('0'); // effect + } + } + }); + + it('canCall', async function () { + const result = await this.manager.canCall(user, this.target.address, selector('fnRestricted()')); + expect(result[0]).to.be.equal(directSuccess); + expect(result[1]).to.be.bignumber.equal(!directSuccess && indirectSuccess ? delay ?? '0' : '0'); + }); + + it('Calling a non restricted function never revert', async function () { + expectEvent(await this.target.fnUnrestricted({ from: user }), 'CalledUnrestricted', { + caller: user, + }); + }); + + it(`Calling a restricted function directly should ${ + directSuccess ? 'succeed' : 'revert' + }`, async function () { + const promise = this.direct(); + + if (directSuccess) { + expectEvent(await promise, 'CalledRestricted', { caller: user }); + } else if (indirectSuccess) { + await expectRevertCustomError(promise, 'AccessManagerNotScheduled', [this.opId]); + } else { + await expectRevertCustomError(promise, 'AccessManagedUnauthorized', [user]); + } + }); + + it('Calling indirectly: only execute', async function () { + // execute without schedule + if (directSuccess) { + const nonceBefore = await this.manager.getNonce(this.opId); + const { receipt, tx } = await this.execute(); + + expectEvent.notEmitted(receipt, 'OperationExecuted', { operationId: this.opId }); + await expectEvent.inTransaction(tx, this.target, 'CalledRestricted', { caller: this.manager.address }); + + // nonce is not modified + expect(await this.manager.getNonce(this.opId)).to.be.bignumber.equal(nonceBefore); + } else if (indirectSuccess) { + await expectRevertCustomError(this.execute(), 'AccessManagerNotScheduled', [this.opId]); + } else { + await expectRevertCustomError(this.execute(), 'AccessManagerUnauthorizedCall', [user, ...this.call]); + } + }); + + it('Calling indirectly: schedule and execute', async function () { + if (directSuccess || indirectSuccess) { + const nonceBefore = await this.manager.getNonce(this.opId); + const { receipt } = await this.schedule(); + const timestamp = await clockFromReceipt.timestamp(receipt).then(web3.utils.toBN); + + expectEvent(receipt, 'OperationScheduled', { + operationId: this.opId, + caller: user, + target: this.call[0], + data: this.call[1], + }); + + // if can call directly, delay should be 0. Otherwise, the delay should be applied + expect(await this.manager.getSchedule(this.opId)).to.be.bignumber.equal( + timestamp.add(directSuccess ? web3.utils.toBN(0) : delay), + ); + + // nonce is incremented + expect(await this.manager.getNonce(this.opId)).to.be.bignumber.equal(nonceBefore.addn(1)); + + // execute without wait + if (directSuccess) { + const { receipt, tx } = await this.execute(); + + await expectEvent.inTransaction(tx, this.target, 'CalledRestricted', { caller: this.manager.address }); + if (delay && fnRole !== ROLES.PUBLIC) { + expectEvent(receipt, 'OperationExecuted', { operationId: this.opId }); + expect(await this.manager.getSchedule(this.opId)).to.be.bignumber.equal('0'); + } + + // nonce is not modified by execute + expect(await this.manager.getNonce(this.opId)).to.be.bignumber.equal(nonceBefore.addn(1)); + } else if (indirectSuccess) { + await expectRevertCustomError(this.execute(), 'AccessManagerNotReady', [this.opId]); + } else { + await expectRevertCustomError(this.execute(), 'AccessManagerUnauthorizedCall', [user, ...this.call]); + } + } else { + await expectRevertCustomError(this.schedule(), 'AccessManagerUnauthorizedCall', [user, ...this.call]); + } + }); + + it('Calling indirectly: schedule wait and execute', async function () { + if (directSuccess || indirectSuccess) { + const nonceBefore = await this.manager.getNonce(this.opId); + const { receipt } = await this.schedule(); + const timestamp = await clockFromReceipt.timestamp(receipt).then(web3.utils.toBN); + + expectEvent(receipt, 'OperationScheduled', { + operationId: this.opId, + caller: user, + target: this.call[0], + data: this.call[1], + }); + + // if can call directly, delay should be 0. Otherwise, the delay should be applied + expect(await this.manager.getSchedule(this.opId)).to.be.bignumber.equal( + timestamp.add(directSuccess ? web3.utils.toBN(0) : delay), + ); + + // nonce is incremented + expect(await this.manager.getNonce(this.opId)).to.be.bignumber.equal(nonceBefore.addn(1)); + + // wait + await time.increase(delay ?? 0); + + // execute without wait + if (directSuccess || indirectSuccess) { + const { receipt, tx } = await this.execute(); + + await expectEvent.inTransaction(tx, this.target, 'CalledRestricted', { caller: this.manager.address }); + if (delay && fnRole !== ROLES.PUBLIC) { + expectEvent(receipt, 'OperationExecuted', { operationId: this.opId }); + expect(await this.manager.getSchedule(this.opId)).to.be.bignumber.equal('0'); + } + + // nonce is not modified by execute + expect(await this.manager.getNonce(this.opId)).to.be.bignumber.equal(nonceBefore.addn(1)); + } else { + await expectRevertCustomError(this.execute(), 'AccessManagerUnauthorizedCall', [user, ...this.call]); + } + } else { + await expectRevertCustomError(this.schedule(), 'AccessManagerUnauthorizedCall', [user, ...this.call]); + } + }); + + it('Calling directly: schedule and call', async function () { + if (directSuccess || indirectSuccess) { + const nonceBefore = await this.manager.getNonce(this.opId); + const { receipt } = await this.schedule(); + const timestamp = await clockFromReceipt.timestamp(receipt).then(web3.utils.toBN); + + expectEvent(receipt, 'OperationScheduled', { + operationId: this.opId, + caller: user, + target: this.call[0], + data: this.call[1], + }); + + // if can call directly, delay should be 0. Otherwise, the delay should be applied + const schedule = timestamp.add(directSuccess ? web3.utils.toBN(0) : delay); + expect(await this.manager.getSchedule(this.opId)).to.be.bignumber.equal(schedule); + + // nonce is incremented + expect(await this.manager.getNonce(this.opId)).to.be.bignumber.equal(nonceBefore.addn(1)); + + // execute without wait + const promise = this.direct(); + if (directSuccess) { + expectEvent(await promise, 'CalledRestricted', { caller: user }); + + // schedule is not reset + expect(await this.manager.getSchedule(this.opId)).to.be.bignumber.equal(schedule); + + // nonce is not modified by execute + expect(await this.manager.getNonce(this.opId)).to.be.bignumber.equal(nonceBefore.addn(1)); + } else if (indirectSuccess) { + await expectRevertCustomError(promise, 'AccessManagerNotReady', [this.opId]); + } else { + await expectRevertCustomError(promise, 'AccessManagerUnauthorizedCall', [user, ...this.call]); + } + } else { + await expectRevertCustomError(this.schedule(), 'AccessManagerUnauthorizedCall', [user, ...this.call]); + } + }); + + it('Calling directly: schedule wait and call', async function () { + if (directSuccess || indirectSuccess) { + const nonceBefore = await this.manager.getNonce(this.opId); + const { receipt } = await this.schedule(); + const timestamp = await clockFromReceipt.timestamp(receipt).then(web3.utils.toBN); + + expectEvent(receipt, 'OperationScheduled', { + operationId: this.opId, + caller: user, + target: this.call[0], + data: this.call[1], + }); + + // if can call directly, delay should be 0. Otherwise, the delay should be applied + const schedule = timestamp.add(directSuccess ? web3.utils.toBN(0) : delay); + expect(await this.manager.getSchedule(this.opId)).to.be.bignumber.equal(schedule); + + // nonce is incremented + expect(await this.manager.getNonce(this.opId)).to.be.bignumber.equal(nonceBefore.addn(1)); + + // wait + await time.increase(delay ?? 0); + + // execute without wait + const promise = await this.direct(); + if (directSuccess) { + expectEvent(await promise, 'CalledRestricted', { caller: user }); + + // schedule is not reset + expect(await this.manager.getSchedule(this.opId)).to.be.bignumber.equal(schedule); + + // nonce is not modified by execute + expect(await this.manager.getNonce(this.opId)).to.be.bignumber.equal(nonceBefore.addn(1)); + } else if (indirectSuccess) { + const receipt = await promise; + + expectEvent(receipt, 'CalledRestricted', { caller: user }); + await expectEvent.inTransaction(receipt.tx, this.manager, 'OperationExecuted', { + operationId: this.opId, + }); + + // schedule is reset + expect(await this.manager.getSchedule(this.opId)).to.be.bignumber.equal('0'); + + // nonce is not modified by execute + expect(await this.manager.getNonce(this.opId)).to.be.bignumber.equal(nonceBefore.addn(1)); + } else { + await expectRevertCustomError(this.direct(), 'AccessManagerUnauthorizedCall', [user, ...this.call]); + } + } else { + await expectRevertCustomError(this.schedule(), 'AccessManagerUnauthorizedCall', [user, ...this.call]); + } + }); + + it('Scheduling for later than needed'); // TODO + }); + } + }); + + describe('Indirect execution corner-cases', async function () { + beforeEach(async function () { + await this.manager.$_setTargetFunctionRole(this.target.address, this.callData, ROLES.SOME); + await this.manager.$_grantRole(ROLES.SOME, user, 0, executeDelay); + }); + + it('Checking canCall when caller is the manager depend on the _executionId', async function () { + const result = await this.manager.canCall(this.manager.address, this.target.address, '0x00000000'); + expect(result[0]).to.be.false; + expect(result[1]).to.be.bignumber.equal('0'); + }); + + it('Cannot execute earlier', async function () { + const { receipt } = await this.schedule(); + const timestamp = await clockFromReceipt.timestamp(receipt).then(web3.utils.toBN); + + expect(await this.manager.getSchedule(this.opId)).to.be.bignumber.equal(timestamp.add(executeDelay)); + + // too early + await helpers.time.setNextBlockTimestamp(timestamp.add(executeDelay).subn(1)); + await expectRevertCustomError(this.execute(), 'AccessManagerNotReady', [this.opId]); + + // the revert happened one second before the execution delay expired + expect(await time.latest()).to.be.bignumber.equal(timestamp.add(executeDelay).subn(1)); + + // ok + await helpers.time.setNextBlockTimestamp(timestamp.add(executeDelay)); + await this.execute(); + + // the success happened when the delay was reached (earliest possible) + expect(await time.latest()).to.be.bignumber.equal(timestamp.add(executeDelay)); + }); + + it('Cannot schedule an already scheduled operation', async function () { + const { receipt } = await this.schedule(); + expectEvent(receipt, 'OperationScheduled', { + operationId: this.opId, + caller: user, + target: this.call[0], + data: this.call[1], + }); + + await expectRevertCustomError(this.schedule(), 'AccessManagerAlreadyScheduled', [this.opId]); + }); + + it('Cannot cancel an operation that is not scheduled', async function () { + await expectRevertCustomError(this.cancel(), 'AccessManagerNotScheduled', [this.opId]); + }); + + it('Cannot cancel an operation that is already executed', async function () { + await this.schedule(); + await time.increase(executeDelay); + await this.execute(); + + await expectRevertCustomError(this.cancel(), 'AccessManagerNotScheduled', [this.opId]); + }); + + it('Scheduler can cancel', async function () { + await this.schedule(); + + expect(await this.manager.getSchedule(this.opId)).to.not.be.bignumber.equal('0'); + + expectEvent(await this.cancel({ from: manager }), 'OperationCanceled', { operationId: this.opId }); + + expect(await this.manager.getSchedule(this.opId)).to.be.bignumber.equal('0'); + }); + + it('Guardian can cancel', async function () { + await this.schedule(); + + expect(await this.manager.getSchedule(this.opId)).to.not.be.bignumber.equal('0'); + + expectEvent(await this.cancel({ from: manager }), 'OperationCanceled', { operationId: this.opId }); + + expect(await this.manager.getSchedule(this.opId)).to.be.bignumber.equal('0'); + }); + + it('Cancel is restricted', async function () { + await this.schedule(); + + expect(await this.manager.getSchedule(this.opId)).to.not.be.bignumber.equal('0'); + + await expectRevertCustomError(this.cancel({ from: other }), 'AccessManagerUnauthorizedCancel', [ + other, + user, + ...this.call, + ]); + + expect(await this.manager.getSchedule(this.opId)).to.not.be.bignumber.equal('0'); + }); + + it('Can re-schedule after execution', async function () { + await this.schedule(); + await time.increase(executeDelay); + await this.execute(); + + // reschedule + const { receipt } = await this.schedule(); + expectEvent(receipt, 'OperationScheduled', { + operationId: this.opId, + caller: user, + target: this.call[0], + data: this.call[1], + }); + }); + + it('Can re-schedule after cancel', async function () { + await this.schedule(); + await this.cancel(); + + // reschedule + const { receipt } = await this.schedule(); + expectEvent(receipt, 'OperationScheduled', { + operationId: this.opId, + caller: user, + target: this.call[0], + data: this.call[1], + }); + }); + }); + }); + + describe('with Ownable target contract', function () { + const roleId = web3.utils.toBN(1); + + beforeEach(async function () { + this.ownable = await Ownable.new(this.manager.address); + + // add user to role + await this.manager.$_grantRole(roleId, user, 0, 0); + }); + + it('initial state', async function () { + expect(await this.ownable.owner()).to.be.equal(this.manager.address); + }); + + describe('Contract is closed', function () { + beforeEach(async function () { + await this.manager.$_setTargetClosed(this.ownable.address, true); + }); + + it('directly call: reverts', async function () { + await expectRevertCustomError(this.ownable.$_checkOwner({ from: user }), 'OwnableUnauthorizedAccount', [user]); + }); + + it('relayed call (with role): reverts', async function () { + await expectRevertCustomError( + this.manager.execute(this.ownable.address, selector('$_checkOwner()'), { from: user }), + 'AccessManagerUnauthorizedCall', + [user, this.ownable.address, selector('$_checkOwner()')], + ); + }); + + it('relayed call (without role): reverts', async function () { + await expectRevertCustomError( + this.manager.execute(this.ownable.address, selector('$_checkOwner()'), { from: other }), + 'AccessManagerUnauthorizedCall', + [other, this.ownable.address, selector('$_checkOwner()')], + ); + }); + }); + + describe('Contract is managed', function () { + describe('function is open to specific role', function () { + beforeEach(async function () { + await this.manager.$_setTargetFunctionRole(this.ownable.address, selector('$_checkOwner()'), roleId); + }); + + it('directly call: reverts', async function () { + await expectRevertCustomError(this.ownable.$_checkOwner({ from: user }), 'OwnableUnauthorizedAccount', [ + user, + ]); + }); + + it('relayed call (with role): success', async function () { + await this.manager.execute(this.ownable.address, selector('$_checkOwner()'), { from: user }); + }); + + it('relayed call (without role): reverts', async function () { + await expectRevertCustomError( + this.manager.execute(this.ownable.address, selector('$_checkOwner()'), { from: other }), + 'AccessManagerUnauthorizedCall', + [other, this.ownable.address, selector('$_checkOwner()')], + ); + }); + }); + + describe('function is open to public role', function () { + beforeEach(async function () { + await this.manager.$_setTargetFunctionRole(this.ownable.address, selector('$_checkOwner()'), ROLES.PUBLIC); + }); + + it('directly call: reverts', async function () { + await expectRevertCustomError(this.ownable.$_checkOwner({ from: user }), 'OwnableUnauthorizedAccount', [ + user, + ]); + }); + + it('relayed call (with role): success', async function () { + await this.manager.execute(this.ownable.address, selector('$_checkOwner()'), { from: user }); + }); + + it('relayed call (without role): success', async function () { + await this.manager.execute(this.ownable.address, selector('$_checkOwner()'), { from: other }); + }); + }); + }); + }); + + describe('authority update', function () { + beforeEach(async function () { + this.newManager = await AccessManager.new(admin); + this.target = await AccessManagedTarget.new(this.manager.address); + }); + + it('admin can change authority', async function () { + expect(await this.target.authority()).to.be.equal(this.manager.address); + + const { tx } = await this.manager.updateAuthority(this.target.address, this.newManager.address, { from: admin }); + await expectEvent.inTransaction(tx, this.target, 'AuthorityUpdated', { authority: this.newManager.address }); + + expect(await this.target.authority()).to.be.equal(this.newManager.address); + }); + + it('cannot set an address without code as the authority', async function () { + await expectRevertCustomError( + this.manager.updateAuthority(this.target.address, user, { from: admin }), + 'AccessManagedInvalidAuthority', + [user], + ); + }); + + it('updateAuthority is restricted on manager', async function () { + await expectRevertCustomError( + this.manager.updateAuthority(this.target.address, this.newManager.address, { from: other }), + 'AccessManagerUnauthorizedAccount', + [other, ROLES.ADMIN], + ); + }); + + it('setAuthority is restricted on AccessManaged', async function () { + await expectRevertCustomError( + this.target.setAuthority(this.newManager.address, { from: admin }), + 'AccessManagedUnauthorized', + [admin], + ); + }); + }); + + // TODO: + // - check opening/closing a contract + // - check updating the contract delay + // - check the delay applies to admin function + describe.skip('contract modes', function () {}); +}); diff --git a/test/finance/VestingWallet.behavior.js b/test/finance/VestingWallet.behavior.js new file mode 100644 index 000000000..afd4c0495 --- /dev/null +++ b/test/finance/VestingWallet.behavior.js @@ -0,0 +1,59 @@ +const { time } = require('@nomicfoundation/hardhat-network-helpers'); +const { expectEvent } = require('@openzeppelin/test-helpers'); +const { expect } = require('chai'); + +function releasedEvent(token, amount) { + return token ? ['ERC20Released', { token: token.address, amount }] : ['EtherReleased', { amount }]; +} + +function shouldBehaveLikeVesting(beneficiary) { + it('check vesting schedule', async function () { + const [vestedAmount, releasable, ...args] = this.token + ? ['vestedAmount(address,uint64)', 'releasable(address)', this.token.address] + : ['vestedAmount(uint64)', 'releasable()']; + + for (const timestamp of this.schedule) { + await time.increaseTo(timestamp); + const vesting = this.vestingFn(timestamp); + + expect(await this.mock.methods[vestedAmount](...args, timestamp)).to.be.bignumber.equal(vesting); + + expect(await this.mock.methods[releasable](...args)).to.be.bignumber.equal(vesting); + } + }); + + it('execute vesting schedule', async function () { + const [release, ...args] = this.token ? ['release(address)', this.token.address] : ['release()']; + + let released = web3.utils.toBN(0); + const before = await this.getBalance(beneficiary); + + { + const receipt = await this.mock.methods[release](...args); + + await expectEvent.inTransaction(receipt.tx, this.mock, ...releasedEvent(this.token, '0')); + + await this.checkRelease(receipt, beneficiary, '0'); + + expect(await this.getBalance(beneficiary)).to.be.bignumber.equal(before); + } + + for (const timestamp of this.schedule) { + await time.setNextBlockTimestamp(timestamp); + const vested = this.vestingFn(timestamp); + + const receipt = await this.mock.methods[release](...args); + await expectEvent.inTransaction(receipt.tx, this.mock, ...releasedEvent(this.token, vested.sub(released))); + + await this.checkRelease(receipt, beneficiary, vested.sub(released)); + + expect(await this.getBalance(beneficiary)).to.be.bignumber.equal(before.add(vested)); + + released = vested; + } + }); +} + +module.exports = { + shouldBehaveLikeVesting, +}; diff --git a/test/finance/VestingWallet.test.js b/test/finance/VestingWallet.test.js new file mode 100644 index 000000000..918e56345 --- /dev/null +++ b/test/finance/VestingWallet.test.js @@ -0,0 +1,69 @@ +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'); + +const { shouldBehaveLikeVesting } = require('./VestingWallet.behavior'); + +contract('VestingWallet', function (accounts) { + const [sender, beneficiary] = accounts; + + const amount = web3.utils.toBN(web3.utils.toWei('100')); + const duration = web3.utils.toBN(4 * 365 * 86400); // 4 years + + beforeEach(async function () { + this.start = (await time.latest()).addn(3600); // in 1 hour + this.mock = await VestingWallet.new(beneficiary, this.start, duration); + }); + + it('rejects zero address for beneficiary', async function () { + await expectRevertCustomError( + VestingWallet.new(constants.ZERO_ADDRESS, this.start, duration), + 'OwnableInvalidOwner', + [constants.ZERO_ADDRESS], + ); + }); + + it('check vesting contract', async function () { + 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)); + }); + + 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 => BNmin(amount, amount.mul(timestamp.sub(this.start)).div(duration)); + }); + + describe('Eth vesting', function () { + beforeEach(async function () { + await web3.eth.sendTransaction({ from: sender, to: this.mock.address, value: amount }); + this.getBalance = account => web3.eth.getBalance(account).then(web3.utils.toBN); + this.checkRelease = () => {}; + }); + + shouldBehaveLikeVesting(beneficiary); + }); + + describe('ERC20 vesting', function () { + beforeEach(async function () { + this.token = await ERC20.new('Name', 'Symbol'); + this.getBalance = account => this.token.balanceOf(account); + this.checkRelease = (receipt, to, value) => + expectEvent.inTransaction(receipt.tx, this.token, 'Transfer', { from: this.mock.address, to, value }); + + await this.token.$_mint(this.mock.address, amount); + }); + + shouldBehaveLikeVesting(beneficiary); + }); + }); +}); diff --git a/test/governance/Governor.t.sol b/test/governance/Governor.t.sol new file mode 100644 index 000000000..f63124536 --- /dev/null +++ b/test/governance/Governor.t.sol @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {Test} from "forge-std/Test.sol"; +import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; +import {Governor} from "@openzeppelin/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 new file mode 100644 index 000000000..45cceba9a --- /dev/null +++ b/test/governance/Governor.test.js @@ -0,0 +1,1024 @@ +const { constants, expectEvent, expectRevert } = require('@openzeppelin/test-helpers'); +const { expect } = require('chai'); +const ethSigUtil = require('eth-sig-util'); +const Wallet = require('ethereumjs-wallet').default; + +const Enums = require('../helpers/enums'); +const { getDomain, domainType } = require('../helpers/eip712'); +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'); +const ERC721 = artifacts.require('$ERC721'); +const ERC1155 = artifacts.require('$ERC1155'); +const ERC1271WalletMock = artifacts.require('ERC1271WalletMock'); + +const TOKENS = [ + { Token: artifacts.require('$ERC20Votes'), mode: 'blocknumber' }, + { Token: artifacts.require('$ERC20VotesTimestampMock'), mode: 'timestamp' }, + { Token: artifacts.require('$ERC20VotesLegacyMock'), mode: 'blocknumber' }, +]; + +contract('Governor', function (accounts) { + const [owner, proposer, 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.chainId = await web3.eth.getChainId(); + try { + this.token = await Token.new(tokenName, tokenSymbol, tokenName, version); + } catch { + // ERC20VotesLegacyMock has a different construction that uses version='1' by default. + this.token = await Token.new(tokenName, tokenSymbol, tokenName); + } + this.mock = await Governor.new( + name, // name + votingDelay, // initialVotingDelay + votingPeriod, // initialVotingPeriod + 0, // initialProposalThreshold + this.token.address, // tokenAddress + 10, // quorumNumeratorValue + ); + 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 }); + + this.proposal = this.helper.setProposal( + [ + { + target: this.receiver.address, + data: this.receiver.contract.methods.mockFunction().encodeABI(), + value, + }, + ], + '', + ); + }); + + shouldSupportInterfaces(['ERC165', 'ERC1155Receiver', 'Governor']); + 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.COUNTING_MODE()).to.be.equal('support=bravo&quorum=for,abstain'); + }); + + it('nominal workflow', async function () { + // Before + 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); + expect(await web3.eth.getBalance(this.mock.address)).to.be.bignumber.equal(value); + expect(await web3.eth.getBalance(this.receiver.address)).to.be.bignumber.equal('0'); + + expect(await this.mock.proposalEta(this.proposal.id)).to.be.bignumber.equal('0'); + expect(await this.mock.proposalNeedsQueuing(this.proposal.id)).to.be.equal(false); + + // Run proposal + const txPropose = await this.helper.propose({ from: proposer }); + + expectEvent(txPropose, 'ProposalCreated', { + proposalId: this.proposal.id, + proposer, + targets: this.proposal.targets, + // values: this.proposal.values, + signatures: this.proposal.signatures, + calldatas: this.proposal.data, + 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, + }); + + await this.helper.waitForSnapshot(); + + expectEvent( + await this.helper.vote({ support: Enums.VoteType.For, reason: 'This is nice' }, { from: voter1 }), + 'VoteCast', + { + voter: voter1, + support: Enums.VoteType.For, + reason: 'This is nice', + weight: web3.utils.toWei('10'), + }, + ); + + expectEvent(await this.helper.vote({ support: Enums.VoteType.For }, { from: voter2 }), 'VoteCast', { + voter: voter2, + support: Enums.VoteType.For, + weight: web3.utils.toWei('7'), + }); + + expectEvent(await this.helper.vote({ support: Enums.VoteType.Against }, { from: voter3 }), 'VoteCast', { + voter: voter3, + support: Enums.VoteType.Against, + weight: web3.utils.toWei('5'), + }); + + expectEvent(await this.helper.vote({ support: Enums.VoteType.Abstain }, { from: voter4 }), 'VoteCast', { + voter: voter4, + support: Enums.VoteType.Abstain, + weight: web3.utils.toWei('2'), + }); + + await this.helper.waitForDeadline(); + + const txExecute = await this.helper.execute(); + + expectEvent(txExecute, 'ProposalExecuted', { proposalId: this.proposal.id }); + + 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.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.receiver.address)).to.be.bignumber.equal(value); + + expect(await this.mock.proposalEta(this.proposal.id)).to.be.bignumber.equal('0'); + expect(await this.mock.proposalNeedsQueuing(this.proposal.id)).to.be.equal(false); + }); + + it('send ethers', async function () { + const empty = web3.utils.toChecksumAddress(web3.utils.randomHex(20)); + + this.proposal = this.helper.setProposal( + [ + { + target: empty, + value, + }, + ], + '', + ); + + // Before + expect(await web3.eth.getBalance(this.mock.address)).to.be.bignumber.equal(value); + expect(await web3.eth.getBalance(empty)).to.be.bignumber.equal('0'); + + // Run proposal + 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.execute(); + + // After + expect(await web3.eth.getBalance(this.mock.address)).to.be.bignumber.equal('0'); + 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 () { + await this.helper.propose(); + await expectRevertCustomError(this.helper.propose(), 'GovernorUnexpectedProposalState', [ + this.proposal.id, + Enums.ProposalState.Pending, + 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 () { + it('if proposal does not exist', async function () { + await expectRevertCustomError( + this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }), + 'GovernorNonexistentProposal', + [this.proposal.id], + ); + }); + + it('if voting has not started', async function () { + await this.helper.propose(); + await expectRevertCustomError( + this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }), + '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 expectRevertCustomError( + this.helper.vote({ support: web3.utils.toBN('255') }), + 'GovernorInvalidVoteType', + [], + ); + }); + + it('if vote was already casted', async function () { + await this.helper.propose(); + 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('if voting is over', async function () { + await this.helper.propose(); + await this.helper.waitForDeadline(); + await expectRevertCustomError( + this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }), + 'GovernorUnexpectedProposalState', + [this.proposal.id, Enums.ProposalState.Defeated, proposalStatesToBitMap([Enums.ProposalState.Active])], + ); + }); + }); + + 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 }), + ); + + 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); + const tamperedSig = web3.utils.hexToBytes(sig); + tamperedSig[42] ^= 0xff; + return web3.utils.bytesToHex(tamperedSig); + }, + }; + + await expectRevertCustomError(this.helper.vote(voteParams), 'GovernorInvalidSignature', [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, + }; + + await expectRevertCustomError( + this.helper.vote(voteParams), + // The signature check implies the nonce can't be tampered without changing the signer + 'GovernorInvalidSignature', + [voteParams.voter], + ); + }); + }); + + 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]); + }); + + 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 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 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 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.helper.setProposal( + [ + { + target: this.receiver.address, + data: this.receiver.contract.methods.mockFunctionRevertsNoReason().encodeABI(), + }, + ], + '', + ); + + await this.helper.propose(); + await this.helper.waitForSnapshot(); + await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }); + await this.helper.waitForDeadline(); + await expectRevertCustomError(this.helper.execute(), 'FailedInnerCall', []); + }); + + it('if receiver revert with reason', async function () { + this.helper.setProposal( + [ + { + target: this.receiver.address, + data: this.receiver.contract.methods.mockFunctionRevertsReason().encodeABI(), + }, + ], + '', + ); + + 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.execute(), 'CallReceiverMock: reverting'); + }); + + it('if proposal was 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.execute(); + 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 expectRevertCustomError(this.mock.state(this.proposal.id), 'GovernorNonexistentProposal', [ + this.proposal.id, + ]); + }); + + it('Pending & Active', async function () { + await this.helper.propose(); + expect(await this.mock.state(this.proposal.id)).to.be.bignumber.equal(Enums.ProposalState.Pending); + await this.helper.waitForSnapshot(); + expect(await this.mock.state(this.proposal.id)).to.be.bignumber.equal(Enums.ProposalState.Pending); + await this.helper.waitForSnapshot(+1); + expect(await this.mock.state(this.proposal.id)).to.be.bignumber.equal(Enums.ProposalState.Active); + }); + + it('Defeated', async function () { + await this.helper.propose(); + await this.helper.waitForDeadline(); + expect(await this.mock.state(this.proposal.id)).to.be.bignumber.equal(Enums.ProposalState.Active); + await this.helper.waitForDeadline(+1); + expect(await this.mock.state(this.proposal.id)).to.be.bignumber.equal(Enums.ProposalState.Defeated); + }); + + it('Succeeded', async function () { + await this.helper.propose(); + await this.helper.waitForSnapshot(); + await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }); + await this.helper.waitForDeadline(); + expect(await this.mock.state(this.proposal.id)).to.be.bignumber.equal(Enums.ProposalState.Active); + await this.helper.waitForDeadline(+1); + expect(await this.mock.state(this.proposal.id)).to.be.bignumber.equal(Enums.ProposalState.Succeeded); + }); + + it('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.execute(); + expect(await this.mock.state(this.proposal.id)).to.be.bignumber.equal(Enums.ProposalState.Executed); + }); + }); + + describe('cancel', function () { + describe('internal', function () { + it('before proposal', async function () { + await expectRevertCustomError(this.helper.cancel('internal'), 'GovernorNonexistentProposal', [ + this.proposal.id, + ]); + }); + + it('after proposal', async function () { + await this.helper.propose(); + + await this.helper.cancel('internal'); + expect(await this.mock.state(this.proposal.id)).to.be.bignumber.equal(Enums.ProposalState.Canceled); + + await this.helper.waitForSnapshot(); + await expectRevertCustomError( + this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }), + 'GovernorUnexpectedProposalState', + [this.proposal.id, Enums.ProposalState.Canceled, proposalStatesToBitMap([Enums.ProposalState.Active])], + ); + }); + + it('after vote', async function () { + await this.helper.propose(); + await this.helper.waitForSnapshot(); + await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }); + + await this.helper.cancel('internal'); + expect(await this.mock.state(this.proposal.id)).to.be.bignumber.equal(Enums.ProposalState.Canceled); + + await this.helper.waitForDeadline(); + await expectRevertCustomError(this.helper.execute(), 'GovernorUnexpectedProposalState', [ + this.proposal.id, + Enums.ProposalState.Canceled, + proposalStatesToBitMap([Enums.ProposalState.Succeeded, Enums.ProposalState.Queued]), + ]); + }); + + it('after deadline', 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.cancel('internal'); + 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('after execution', 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.execute(); + + 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 expectRevertCustomError(this.helper.cancel('external'), 'GovernorNonexistentProposal', [ + this.proposal.id, + ]); + }); + + it('after proposal', async function () { + await this.helper.propose(); + + await this.helper.cancel('external'); + }); + + it('after proposal - restricted to proposer', async function () { + await this.helper.propose(); + + 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 expectRevertCustomError(this.helper.cancel('external'), 'GovernorUnexpectedProposalState', [ + this.proposal.id, + Enums.ProposalState.Active, + proposalStatesToBitMap([Enums.ProposalState.Pending]), + ]); + }); + + it('after vote', async function () { + await this.helper.propose(); + await this.helper.waitForSnapshot(); + await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }); + + await expectRevertCustomError(this.helper.cancel('external'), 'GovernorUnexpectedProposalState', [ + this.proposal.id, + Enums.ProposalState.Active, + proposalStatesToBitMap([Enums.ProposalState.Pending]), + ]); + }); + + it('after deadline', 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 expectRevertCustomError(this.helper.cancel('external'), 'GovernorUnexpectedProposalState', [ + this.proposal.id, + Enums.ProposalState.Succeeded, + proposalStatesToBitMap([Enums.ProposalState.Pending]), + ]); + }); + + it('after execution', 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.execute(); + + await expectRevertCustomError(this.helper.cancel('external'), 'GovernorUnexpectedProposalState', [ + this.proposal.id, + Enums.ProposalState.Executed, + proposalStatesToBitMap([Enums.ProposalState.Pending]), + ]); + }); + }); + }); + + describe('proposal length', function () { + it('empty', async function () { + this.helper.setProposal([], ''); + await expectRevertCustomError(this.helper.propose(), 'GovernorInvalidProposalLength', [0, 0, 0]); + }); + + it('mismatch #1', async function () { + this.helper.setProposal( + { + targets: [], + values: [web3.utils.toWei('0')], + data: [this.receiver.contract.methods.mockFunction().encodeABI()], + }, + '', + ); + await expectRevertCustomError(this.helper.propose(), 'GovernorInvalidProposalLength', [0, 1, 1]); + }); + + it('mismatch #2', async function () { + this.helper.setProposal( + { + targets: [this.receiver.address], + values: [], + data: [this.receiver.contract.methods.mockFunction().encodeABI()], + }, + '', + ); + await expectRevertCustomError(this.helper.propose(), 'GovernorInvalidProposalLength', [1, 1, 0]); + }); + + it('mismatch #3', async function () { + this.helper.setProposal( + { + targets: [this.receiver.address], + values: [web3.utils.toWei('0')], + data: [], + }, + '', + ); + await expectRevertCustomError(this.helper.propose(), 'GovernorInvalidProposalLength', [1, 0, 1]); + }); + }); + + 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 expectRevertCustomError(this.helper.propose({ from: voter1 }), 'GovernorRestrictedProposer', [ + voter1, + ]); + }); + }); + }); + + describe('onlyGovernance updates', function () { + it('setVotingDelay is protected', async function () { + await expectRevertCustomError(this.mock.setVotingDelay('0', { from: owner }), 'GovernorOnlyExecutor', [ + owner, + ]); + }); + + it('setVotingPeriod is protected', async function () { + await expectRevertCustomError(this.mock.setVotingPeriod('32', { from: owner }), 'GovernorOnlyExecutor', [ + owner, + ]); + }); + + it('setProposalThreshold is protected', async function () { + await expectRevertCustomError( + this.mock.setProposalThreshold('1000000000000000000', { from: owner }), + 'GovernorOnlyExecutor', + [owner], + ); + }); + + it('can setVotingDelay through governance', async function () { + this.helper.setProposal( + [ + { + target: this.mock.address, + data: this.mock.contract.methods.setVotingDelay('0').encodeABI(), + }, + ], + '', + ); + + 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.execute(), 'VotingDelaySet', { oldVotingDelay: '4', newVotingDelay: '0' }); + + expect(await this.mock.votingDelay()).to.be.bignumber.equal('0'); + }); + + it('can setVotingPeriod through governance', async function () { + this.helper.setProposal( + [ + { + target: this.mock.address, + data: this.mock.contract.methods.setVotingPeriod('32').encodeABI(), + }, + ], + '', + ); + + 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.execute(), 'VotingPeriodSet', { oldVotingPeriod: '16', newVotingPeriod: '32' }); + + expect(await this.mock.votingPeriod()).to.be.bignumber.equal('32'); + }); + + 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(votingPeriod).encodeABI(), + }, + ], + '', + ); + + await this.helper.propose(); + await this.helper.waitForSnapshot(); + await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }); + await this.helper.waitForDeadline(); + + await expectRevertCustomError(this.helper.execute(), 'GovernorInvalidVotingPeriod', [votingPeriod]); + }); + + it('can setProposalThreshold to 0 through governance', async function () { + this.helper.setProposal( + [ + { + target: this.mock.address, + data: this.mock.contract.methods.setProposalThreshold('1000000000000000000').encodeABI(), + }, + ], + '', + ); + + 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.execute(), 'ProposalThresholdSet', { + oldProposalThreshold: '0', + newProposalThreshold: '1000000000000000000', + }); + + expect(await this.mock.proposalThreshold()).to.be.bignumber.equal('1000000000000000000'); + }); + }); + + describe('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 receive an ERC721 safeTransfer', async function () { + await this.token.safeTransferFrom(owner, this.mock.address, tokenId, { from: owner }); + }); + }); + + 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 receive ERC1155 safeTransfer', async function () { + await this.token.safeTransferFrom( + owner, + this.mock.address, + ...Object.entries(tokenIds)[0], // id + amount + '0x', + { from: owner }, + ); + }); + + it('can receive ERC1155 safeBatchTransfer', async function () { + await this.token.safeBatchTransferFrom( + owner, + this.mock.address, + Object.keys(tokenIds), + Object.values(tokenIds), + '0x', + { from: owner }, + ); + }); + }); + }); + }); + } +}); diff --git a/test/governance/TimelockController.test.js b/test/governance/TimelockController.test.js new file mode 100644 index 000000000..ce051e787 --- /dev/null +++ b/test/governance/TimelockController.test.js @@ -0,0 +1,1286 @@ +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'); + +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'); +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); + +const salt = '0x025e7b0be353a74631ad648c667493c0e1cd31caa4cc2d3520fdc171ea0cc726'; // a random value + +function genOperation(target, value, data, predecessor, salt) { + const id = web3.utils.keccak256( + web3.eth.abi.encodeParameters( + ['address', 'uint256', 'bytes', 'uint256', 'bytes32'], + [target, value, data, predecessor, salt], + ), + ); + return { id, target, value, data, predecessor, salt }; +} + +function genOperationBatch(targets, values, payloads, predecessor, salt) { + const id = web3.utils.keccak256( + web3.eth.abi.encodeParameters( + ['address[]', 'uint256[]', 'bytes[]', 'uint256', 'bytes32'], + [targets, values, payloads, predecessor, salt], + ), + ); + return { id, targets, values, payloads, predecessor, salt }; +} + +contract('TimelockController', function (accounts) { + const [, admin, proposer, canceller, executor, other] = 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'); + + beforeEach(async function () { + // Deploy new timelock + this.mock = await TimelockController.new(MINDELAY, [proposer], [executor], admin); + + expect(await this.mock.hasRole(CANCELLER_ROLE, proposer)).to.be.equal(true); + await this.mock.revokeRole(CANCELLER_ROLE, proposer, { from: admin }); + await this.mock.grantRole(CANCELLER_ROLE, canceller, { from: admin }); + + // Mocks + this.callreceivermock = await CallReceiverMock.new({ from: admin }); + this.implementation2 = await Implementation2.new({ from: admin }); + }); + + shouldSupportInterfaces(['ERC1155Receiver']); + + it('initial state', async function () { + expect(await this.mock.getMinDelay()).to.be.bignumber.equal(MINDELAY); + + 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); + + expect( + await Promise.all([PROPOSER_ROLE, CANCELLER_ROLE, EXECUTOR_ROLE].map(role => this.mock.hasRole(role, proposer))), + ).to.be.deep.equal([true, false, false]); + + expect( + await Promise.all([PROPOSER_ROLE, CANCELLER_ROLE, EXECUTOR_ROLE].map(role => this.mock.hasRole(role, canceller))), + ).to.be.deep.equal([false, true, false]); + + expect( + await Promise.all([PROPOSER_ROLE, CANCELLER_ROLE, EXECUTOR_ROLE].map(role => this.mock.hasRole(role, executor))), + ).to.be.deep.equal([false, false, true]); + }); + + it('optional admin', async function () { + const mock = await TimelockController.new(MINDELAY, [proposer], [executor], ZERO_ADDRESS, { from: other }); + + 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 () { + describe('operation hashing', function () { + it('hashOperation', async function () { + this.operation = genOperation( + '0x29cebefe301c6ce1bb36b58654fea275e1cacc83', + '0xf94fdd6e21da21d2', + '0xa3bc5104', + '0xba41db3be0a9929145cfe480bd0f1f003689104d275ae912099f925df424ef94', + '0x60d9109846ab510ed75c15f979ae366a8a2ace11d34ba9788c13ac296db50e6e', + ); + expect( + await this.mock.hashOperation( + this.operation.target, + this.operation.value, + this.operation.data, + this.operation.predecessor, + this.operation.salt, + ), + ).to.be.equal(this.operation.id); + }); + + it('hashOperationBatch', async function () { + this.operation = genOperationBatch( + Array(8).fill('0x2d5f21620e56531c1d59c2df9b8e95d129571f71'), + Array(8).fill('0x2b993cfce932ccee'), + Array(8).fill('0xcf51966b'), + '0xce8f45069cc71d25f71ba05062de1a3974f9849b004de64a70998bca9d29c2e7', + '0x8952d74c110f72bfe5accdf828c74d53a7dfb71235dfa8a1e8c75d8576b372ff', + ); + expect( + await this.mock.hashOperationBatch( + this.operation.targets, + this.operation.values, + this.operation.payloads, + this.operation.predecessor, + this.operation.salt, + ), + ).to.be.equal(this.operation.id); + }); + }); + describe('simple', function () { + describe('schedule', function () { + beforeEach(async function () { + this.operation = genOperation( + '0x31754f590B97fD975Eb86938f18Cc304E264D2F2', + 0, + '0x3bf92ccc', + ZERO_BYTES32, + salt, + ); + }); + + it('proposer can schedule', async function () { + const receipt = await this.mock.schedule( + this.operation.target, + this.operation.value, + this.operation.data, + this.operation.predecessor, + this.operation.salt, + MINDELAY, + { from: proposer }, + ); + expectEvent(receipt, 'CallScheduled', { + id: this.operation.id, + index: web3.utils.toBN(0), + target: this.operation.target, + value: web3.utils.toBN(this.operation.value), + data: this.operation.data, + predecessor: this.operation.predecessor, + delay: MINDELAY, + }); + + expectEvent(receipt, 'CallSalt', { + id: this.operation.id, + salt: this.operation.salt, + }); + + const block = await web3.eth.getBlock(receipt.receipt.blockHash); + + expect(await this.mock.getTimestamp(this.operation.id)).to.be.bignumber.equal( + web3.utils.toBN(block.timestamp).add(MINDELAY), + ); + }); + + it('prevent overwriting active operation', async function () { + await this.mock.schedule( + this.operation.target, + this.operation.value, + this.operation.data, + this.operation.predecessor, + this.operation.salt, + MINDELAY, + { from: proposer }, + ); + + await expectRevertCustomError( + this.mock.schedule( + this.operation.target, + this.operation.value, + this.operation.data, + this.operation.predecessor, + this.operation.salt, + MINDELAY, + { from: proposer }, + ), + 'TimelockUnexpectedOperationState', + [this.operation.id, proposalStatesToBitMap(OperationState.Unset)], + ); + }); + + it('prevent non-proposer from committing', async function () { + await expectRevertCustomError( + this.mock.schedule( + this.operation.target, + this.operation.value, + this.operation.data, + this.operation.predecessor, + this.operation.salt, + MINDELAY, + { from: other }, + ), + `AccessControlUnauthorizedAccount`, + [other, PROPOSER_ROLE], + ); + }); + + it('enforce minimum delay', async function () { + await expectRevertCustomError( + this.mock.schedule( + this.operation.target, + this.operation.value, + this.operation.data, + this.operation.predecessor, + this.operation.salt, + MINDELAY - 1, + { from: proposer }, + ), + 'TimelockInsufficientDelay', + [MINDELAY, MINDELAY - 1], + ); + }); + + it('schedule operation with salt zero', async function () { + const { receipt } = await this.mock.schedule( + this.operation.target, + this.operation.value, + this.operation.data, + this.operation.predecessor, + ZERO_BYTES32, + MINDELAY, + { from: proposer }, + ); + expectEvent.notEmitted(receipt, 'CallSalt'); + }); + }); + + describe('execute', function () { + beforeEach(async function () { + this.operation = genOperation( + '0xAe22104DCD970750610E6FE15E623468A98b15f7', + 0, + '0x13e414de', + ZERO_BYTES32, + '0xc1059ed2dc130227aa1d1d539ac94c641306905c020436c636e19e3fab56fc7f', + ); + }); + + it('revert if operation is not scheduled', async function () { + await expectRevertCustomError( + this.mock.execute( + this.operation.target, + this.operation.value, + this.operation.data, + this.operation.predecessor, + this.operation.salt, + { from: executor }, + ), + 'TimelockUnexpectedOperationState', + [this.operation.id, proposalStatesToBitMap(OperationState.Ready)], + ); + }); + + describe('with scheduled operation', function () { + beforeEach(async function () { + ({ receipt: this.receipt, logs: this.logs } = await this.mock.schedule( + this.operation.target, + this.operation.value, + this.operation.data, + this.operation.predecessor, + this.operation.salt, + MINDELAY, + { from: proposer }, + )); + }); + + it('revert if execution comes too early 1/2', async function () { + await expectRevertCustomError( + this.mock.execute( + this.operation.target, + this.operation.value, + this.operation.data, + this.operation.predecessor, + this.operation.salt, + { from: executor }, + ), + 'TimelockUnexpectedOperationState', + [this.operation.id, proposalStatesToBitMap(OperationState.Ready)], + ); + }); + + it('revert if execution comes too early 2/2', async function () { + const timestamp = await this.mock.getTimestamp(this.operation.id); + await time.increaseTo(timestamp - 5); // -1 is too tight, test sometime fails + + await expectRevertCustomError( + this.mock.execute( + this.operation.target, + this.operation.value, + this.operation.data, + this.operation.predecessor, + this.operation.salt, + { from: executor }, + ), + 'TimelockUnexpectedOperationState', + [this.operation.id, proposalStatesToBitMap(OperationState.Ready)], + ); + }); + + describe('on time', function () { + beforeEach(async function () { + const timestamp = await this.mock.getTimestamp(this.operation.id); + await time.increaseTo(timestamp); + }); + + it('executor can reveal', async function () { + const receipt = await this.mock.execute( + this.operation.target, + this.operation.value, + this.operation.data, + this.operation.predecessor, + this.operation.salt, + { from: executor }, + ); + expectEvent(receipt, 'CallExecuted', { + id: this.operation.id, + index: web3.utils.toBN(0), + target: this.operation.target, + value: web3.utils.toBN(this.operation.value), + data: this.operation.data, + }); + }); + + it('prevent non-executor from revealing', async function () { + await expectRevertCustomError( + this.mock.execute( + this.operation.target, + this.operation.value, + this.operation.data, + this.operation.predecessor, + this.operation.salt, + { from: other }, + ), + `AccessControlUnauthorizedAccount`, + [other, 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 expectRevertCustomError( + this.mock.execute( + reentrantOperation.target, + reentrantOperation.value, + reentrantOperation.data, + reentrantOperation.predecessor, + reentrantOperation.salt, + { from: executor }, + ), + 'TimelockUnexpectedOperationState', + [reentrantOperation.id, proposalStatesToBitMap(OperationState.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, + }); + }); + }); + }); + }); + }); + + describe('batch', function () { + describe('schedule', function () { + beforeEach(async function () { + this.operation = genOperationBatch( + Array(8).fill('0xEd912250835c812D4516BBD80BdaEA1bB63a293C'), + Array(8).fill(0), + Array(8).fill('0x2fcb7a88'), + ZERO_BYTES32, + '0x6cf9d042ade5de78bed9ffd075eb4b2a4f6b1736932c2dc8af517d6e066f51f5', + ); + }); + + it('proposer can schedule', async function () { + const receipt = await this.mock.scheduleBatch( + this.operation.targets, + this.operation.values, + this.operation.payloads, + this.operation.predecessor, + this.operation.salt, + MINDELAY, + { from: proposer }, + ); + for (const i in this.operation.targets) { + expectEvent(receipt, 'CallScheduled', { + id: this.operation.id, + index: web3.utils.toBN(i), + target: this.operation.targets[i], + value: web3.utils.toBN(this.operation.values[i]), + data: this.operation.payloads[i], + predecessor: this.operation.predecessor, + delay: MINDELAY, + }); + + expectEvent(receipt, 'CallSalt', { + id: this.operation.id, + salt: this.operation.salt, + }); + } + + const block = await web3.eth.getBlock(receipt.receipt.blockHash); + + expect(await this.mock.getTimestamp(this.operation.id)).to.be.bignumber.equal( + web3.utils.toBN(block.timestamp).add(MINDELAY), + ); + }); + + it('prevent overwriting active operation', async function () { + await this.mock.scheduleBatch( + this.operation.targets, + this.operation.values, + this.operation.payloads, + this.operation.predecessor, + this.operation.salt, + MINDELAY, + { from: proposer }, + ); + + await expectRevertCustomError( + this.mock.scheduleBatch( + this.operation.targets, + this.operation.values, + this.operation.payloads, + this.operation.predecessor, + this.operation.salt, + MINDELAY, + { from: proposer }, + ), + 'TimelockUnexpectedOperationState', + [this.operation.id, proposalStatesToBitMap(OperationState.Unset)], + ); + }); + + it('length of batch parameter must match #1', async function () { + await expectRevertCustomError( + this.mock.scheduleBatch( + this.operation.targets, + [], + this.operation.payloads, + this.operation.predecessor, + this.operation.salt, + MINDELAY, + { from: proposer }, + ), + 'TimelockInvalidOperationLength', + [this.operation.targets.length, this.operation.payloads.length, 0], + ); + }); + + it('length of batch parameter must match #1', async function () { + await expectRevertCustomError( + this.mock.scheduleBatch( + this.operation.targets, + this.operation.values, + [], + this.operation.predecessor, + this.operation.salt, + MINDELAY, + { from: proposer }, + ), + 'TimelockInvalidOperationLength', + [this.operation.targets.length, 0, this.operation.payloads.length], + ); + }); + + it('prevent non-proposer from committing', async function () { + await expectRevertCustomError( + this.mock.scheduleBatch( + this.operation.targets, + this.operation.values, + this.operation.payloads, + this.operation.predecessor, + this.operation.salt, + MINDELAY, + { from: other }, + ), + `AccessControlUnauthorizedAccount`, + [other, PROPOSER_ROLE], + ); + }); + + it('enforce minimum delay', async function () { + await expectRevertCustomError( + this.mock.scheduleBatch( + this.operation.targets, + this.operation.values, + this.operation.payloads, + this.operation.predecessor, + this.operation.salt, + MINDELAY - 1, + { from: proposer }, + ), + 'TimelockInsufficientDelay', + [MINDELAY, MINDELAY - 1], + ); + }); + }); + + describe('execute', function () { + beforeEach(async function () { + this.operation = genOperationBatch( + Array(8).fill('0x76E53CcEb05131Ef5248553bEBDb8F70536830b1'), + Array(8).fill(0), + Array(8).fill('0x58a60f63'), + ZERO_BYTES32, + '0x9545eeabc7a7586689191f78a5532443698538e54211b5bd4d7dc0fc0102b5c7', + ); + }); + + it('revert if operation is not scheduled', async function () { + await expectRevertCustomError( + this.mock.executeBatch( + this.operation.targets, + this.operation.values, + this.operation.payloads, + this.operation.predecessor, + this.operation.salt, + { from: executor }, + ), + 'TimelockUnexpectedOperationState', + [this.operation.id, proposalStatesToBitMap(OperationState.Ready)], + ); + }); + + describe('with scheduled operation', function () { + beforeEach(async function () { + ({ receipt: this.receipt, logs: this.logs } = await this.mock.scheduleBatch( + this.operation.targets, + this.operation.values, + this.operation.payloads, + this.operation.predecessor, + this.operation.salt, + MINDELAY, + { from: proposer }, + )); + }); + + it('revert if execution comes too early 1/2', async function () { + await expectRevertCustomError( + this.mock.executeBatch( + this.operation.targets, + this.operation.values, + this.operation.payloads, + this.operation.predecessor, + this.operation.salt, + { from: executor }, + ), + 'TimelockUnexpectedOperationState', + [this.operation.id, proposalStatesToBitMap(OperationState.Ready)], + ); + }); + + it('revert if execution comes too early 2/2', async function () { + const timestamp = await this.mock.getTimestamp(this.operation.id); + await time.increaseTo(timestamp - 5); // -1 is to tight, test sometime fails + + await expectRevertCustomError( + this.mock.executeBatch( + this.operation.targets, + this.operation.values, + this.operation.payloads, + this.operation.predecessor, + this.operation.salt, + { from: executor }, + ), + 'TimelockUnexpectedOperationState', + [this.operation.id, proposalStatesToBitMap(OperationState.Ready)], + ); + }); + + describe('on time', function () { + beforeEach(async function () { + const timestamp = await this.mock.getTimestamp(this.operation.id); + await time.increaseTo(timestamp); + }); + + it('executor can reveal', async function () { + const receipt = await this.mock.executeBatch( + this.operation.targets, + this.operation.values, + this.operation.payloads, + this.operation.predecessor, + this.operation.salt, + { from: executor }, + ); + for (const i in this.operation.targets) { + expectEvent(receipt, 'CallExecuted', { + id: this.operation.id, + index: web3.utils.toBN(i), + target: this.operation.targets[i], + value: web3.utils.toBN(this.operation.values[i]), + data: this.operation.payloads[i], + }); + } + }); + + it('prevent non-executor from revealing', async function () { + await expectRevertCustomError( + this.mock.executeBatch( + this.operation.targets, + this.operation.values, + this.operation.payloads, + this.operation.predecessor, + this.operation.salt, + { from: other }, + ), + `AccessControlUnauthorizedAccount`, + [other, EXECUTOR_ROLE], + ); + }); + + it('length mismatch #1', async function () { + await expectRevertCustomError( + this.mock.executeBatch( + [], + this.operation.values, + this.operation.payloads, + this.operation.predecessor, + this.operation.salt, + { from: executor }, + ), + 'TimelockInvalidOperationLength', + [0, this.operation.payloads.length, this.operation.values.length], + ); + }); + + it('length mismatch #2', async function () { + await expectRevertCustomError( + this.mock.executeBatch( + this.operation.targets, + [], + this.operation.payloads, + this.operation.predecessor, + this.operation.salt, + { from: executor }, + ), + 'TimelockInvalidOperationLength', + [this.operation.targets.length, this.operation.payloads.length, 0], + ); + }); + + it('length mismatch #3', async function () { + await expectRevertCustomError( + this.mock.executeBatch( + this.operation.targets, + this.operation.values, + [], + this.operation.predecessor, + this.operation.salt, + { from: executor }, + ), + 'TimelockInvalidOperationLength', + [this.operation.targets.length, 0, this.operation.values.length], + ); + }); + + 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 expectRevertCustomError( + this.mock.executeBatch( + reentrantBatchOperation.targets, + reentrantBatchOperation.values, + reentrantBatchOperation.payloads, + reentrantBatchOperation.predecessor, + reentrantBatchOperation.salt, + { from: executor }, + ), + 'TimelockUnexpectedOperationState', + [reentrantBatchOperation.id, proposalStatesToBitMap(OperationState.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], + }); + } + }); + }); + }); + + it('partial execution', async function () { + const operation = genOperationBatch( + [this.callreceivermock.address, this.callreceivermock.address, this.callreceivermock.address], + [0, 0, 0], + [ + this.callreceivermock.contract.methods.mockFunction().encodeABI(), + this.callreceivermock.contract.methods.mockFunctionRevertsNoReason().encodeABI(), + this.callreceivermock.contract.methods.mockFunction().encodeABI(), + ], + ZERO_BYTES32, + '0x8ac04aa0d6d66b8812fb41d39638d37af0a9ab11da507afd65c509f8ed079d3e', + ); + + await this.mock.scheduleBatch( + operation.targets, + operation.values, + operation.payloads, + operation.predecessor, + operation.salt, + MINDELAY, + { from: proposer }, + ); + await time.increase(MINDELAY); + await expectRevertCustomError( + this.mock.executeBatch( + operation.targets, + operation.values, + operation.payloads, + operation.predecessor, + operation.salt, + { from: executor }, + ), + 'FailedInnerCall', + [], + ); + }); + }); + }); + + describe('cancel', function () { + beforeEach(async function () { + this.operation = genOperation( + '0xC6837c44AA376dbe1d2709F13879E040CAb653ca', + 0, + '0x296e58dd', + ZERO_BYTES32, + '0xa2485763600634800df9fc9646fb2c112cf98649c55f63dd1d9c7d13a64399d9', + ); + ({ receipt: this.receipt, logs: this.logs } = await this.mock.schedule( + this.operation.target, + this.operation.value, + this.operation.data, + this.operation.predecessor, + this.operation.salt, + MINDELAY, + { from: proposer }, + )); + }); + + it('canceller can cancel', async function () { + const receipt = await this.mock.cancel(this.operation.id, { from: canceller }); + expectEvent(receipt, 'Cancelled', { id: this.operation.id }); + }); + + it('cannot cancel invalid operation', async function () { + await expectRevertCustomError( + this.mock.cancel(constants.ZERO_BYTES32, { from: canceller }), + 'TimelockUnexpectedOperationState', + [constants.ZERO_BYTES32, proposalStatesToBitMap([OperationState.Waiting, OperationState.Ready])], + ); + }); + + it('prevent non-canceller from canceling', async function () { + await expectRevertCustomError( + this.mock.cancel(this.operation.id, { from: other }), + `AccessControlUnauthorizedAccount`, + [other, CANCELLER_ROLE], + ); + }); + }); + }); + + describe('maintenance', function () { + it('prevent unauthorized maintenance', async function () { + await expectRevertCustomError(this.mock.updateDelay(0, { from: other }), 'TimelockUnauthorizedCaller', [other]); + }); + + it('timelock scheduled maintenance', async function () { + const newDelay = time.duration.hours(6); + const operation = genOperation( + this.mock.address, + 0, + this.mock.contract.methods.updateDelay(newDelay.toString()).encodeABI(), + ZERO_BYTES32, + '0xf8e775b2c5f4d66fb5c7fa800f35ef518c262b6014b3c0aee6ea21bff157f108', + ); + + await this.mock.schedule( + operation.target, + operation.value, + operation.data, + operation.predecessor, + operation.salt, + MINDELAY, + { from: proposer }, + ); + await time.increase(MINDELAY); + const receipt = await this.mock.execute( + operation.target, + operation.value, + operation.data, + operation.predecessor, + operation.salt, + { from: executor }, + ); + expectEvent(receipt, 'MinDelayChange', { newDuration: newDelay.toString(), oldDuration: MINDELAY }); + + expect(await this.mock.getMinDelay()).to.be.bignumber.equal(newDelay); + }); + }); + + describe('dependency', function () { + beforeEach(async function () { + this.operation1 = genOperation( + '0xdE66bD4c97304200A95aE0AadA32d6d01A867E39', + 0, + '0x01dc731a', + ZERO_BYTES32, + '0x64e932133c7677402ead2926f86205e2ca4686aebecf5a8077627092b9bb2feb', + ); + this.operation2 = genOperation( + '0x3c7944a3F1ee7fc8c5A5134ba7c79D11c3A1FCa3', + 0, + '0x8f531849', + this.operation1.id, + '0x036e1311cac523f9548e6461e29fb1f8f9196b91910a41711ea22f5de48df07d', + ); + await this.mock.schedule( + this.operation1.target, + this.operation1.value, + this.operation1.data, + this.operation1.predecessor, + this.operation1.salt, + MINDELAY, + { from: proposer }, + ); + await this.mock.schedule( + this.operation2.target, + this.operation2.value, + this.operation2.data, + this.operation2.predecessor, + this.operation2.salt, + MINDELAY, + { from: proposer }, + ); + await time.increase(MINDELAY); + }); + + it('cannot execute before dependency', async function () { + await expectRevertCustomError( + this.mock.execute( + this.operation2.target, + this.operation2.value, + this.operation2.data, + this.operation2.predecessor, + this.operation2.salt, + { from: executor }, + ), + 'TimelockUnexecutedPredecessor', + [this.operation1.id], + ); + }); + + it('can execute after dependency', async function () { + await this.mock.execute( + this.operation1.target, + this.operation1.value, + this.operation1.data, + this.operation1.predecessor, + this.operation1.salt, + { from: executor }, + ); + await this.mock.execute( + this.operation2.target, + this.operation2.value, + this.operation2.data, + this.operation2.predecessor, + this.operation2.salt, + { from: executor }, + ); + }); + }); + + describe('usage scenario', function () { + this.timeout(10000); + + it('call', async function () { + const operation = genOperation( + this.implementation2.address, + 0, + this.implementation2.contract.methods.setValue(42).encodeABI(), + ZERO_BYTES32, + '0x8043596363daefc89977b25f9d9b4d06c3910959ef0c4d213557a903e1b555e2', + ); + + await this.mock.schedule( + operation.target, + operation.value, + operation.data, + operation.predecessor, + operation.salt, + MINDELAY, + { from: proposer }, + ); + await time.increase(MINDELAY); + await this.mock.execute( + operation.target, + operation.value, + operation.data, + operation.predecessor, + operation.salt, + { from: executor }, + ); + + expect(await this.implementation2.getValue()).to.be.bignumber.equal(web3.utils.toBN(42)); + }); + + it('call reverting', async function () { + const operation = genOperation( + this.callreceivermock.address, + 0, + this.callreceivermock.contract.methods.mockFunctionRevertsNoReason().encodeABI(), + ZERO_BYTES32, + '0xb1b1b276fdf1a28d1e00537ea73b04d56639128b08063c1a2f70a52e38cba693', + ); + + await this.mock.schedule( + operation.target, + operation.value, + operation.data, + operation.predecessor, + operation.salt, + MINDELAY, + { from: proposer }, + ); + await time.increase(MINDELAY); + await expectRevertCustomError( + this.mock.execute(operation.target, operation.value, operation.data, operation.predecessor, operation.salt, { + from: executor, + }), + 'FailedInnerCall', + [], + ); + }); + + it('call throw', async function () { + const operation = genOperation( + this.callreceivermock.address, + 0, + this.callreceivermock.contract.methods.mockFunctionThrows().encodeABI(), + ZERO_BYTES32, + '0xe5ca79f295fc8327ee8a765fe19afb58f4a0cbc5053642bfdd7e73bc68e0fc67', + ); + + await this.mock.schedule( + operation.target, + operation.value, + operation.data, + operation.predecessor, + operation.salt, + MINDELAY, + { from: proposer }, + ); + await time.increase(MINDELAY); + // 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, + }), + ); + }); + + it('call out of gas', async function () { + const operation = genOperation( + this.callreceivermock.address, + 0, + this.callreceivermock.contract.methods.mockFunctionOutOfGas().encodeABI(), + ZERO_BYTES32, + '0xf3274ce7c394c5b629d5215723563a744b817e1730cca5587c567099a14578fd', + ); + + await this.mock.schedule( + operation.target, + operation.value, + operation.data, + operation.predecessor, + operation.salt, + MINDELAY, + { from: proposer }, + ); + await time.increase(MINDELAY); + await expectRevertCustomError( + this.mock.execute(operation.target, operation.value, operation.data, operation.predecessor, operation.salt, { + from: executor, + gas: '100000', + }), + 'FailedInnerCall', + [], + ); + }); + + it('call payable with eth', async function () { + const operation = genOperation( + this.callreceivermock.address, + 1, + this.callreceivermock.contract.methods.mockFunction().encodeABI(), + ZERO_BYTES32, + '0x5ab73cd33477dcd36c1e05e28362719d0ed59a7b9ff14939de63a43073dc1f44', + ); + + await this.mock.schedule( + operation.target, + operation.value, + operation.data, + operation.predecessor, + operation.salt, + MINDELAY, + { from: proposer }, + ); + await time.increase(MINDELAY); + + 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 this.mock.execute( + operation.target, + operation.value, + operation.data, + operation.predecessor, + operation.salt, + { from: executor, value: 1 }, + ); + + 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(1)); + }); + + it('call nonpayable with eth', async function () { + const operation = genOperation( + this.callreceivermock.address, + 1, + this.callreceivermock.contract.methods.mockFunctionNonPayable().encodeABI(), + ZERO_BYTES32, + '0xb78edbd920c7867f187e5aa6294ae5a656cfbf0dea1ccdca3751b740d0f2bdf8', + ); + + await this.mock.schedule( + operation.target, + operation.value, + operation.data, + operation.predecessor, + operation.salt, + MINDELAY, + { from: proposer }, + ); + await time.increase(MINDELAY); + + 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 expectRevertCustomError( + this.mock.execute(operation.target, operation.value, operation.data, operation.predecessor, operation.salt, { + from: executor, + }), + 'FailedInnerCall', + [], + ); + + 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)); + }); + + it('call reverting with eth', async function () { + const operation = genOperation( + this.callreceivermock.address, + 1, + this.callreceivermock.contract.methods.mockFunctionRevertsNoReason().encodeABI(), + ZERO_BYTES32, + '0xdedb4563ef0095db01d81d3f2decf57cf83e4a72aa792af14c43a792b56f4de6', + ); + + await this.mock.schedule( + operation.target, + operation.value, + operation.data, + operation.predecessor, + operation.salt, + MINDELAY, + { from: proposer }, + ); + await time.increase(MINDELAY); + + 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 expectRevertCustomError( + this.mock.execute(operation.target, operation.value, operation.data, operation.predecessor, operation.salt, { + from: executor, + }), + 'FailedInnerCall', + [], + ); + + 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)); + }); + }); + + describe('safe receive', function () { + describe('ERC721', function () { + const name = 'Non Fungible Token'; + const symbol = 'NFT'; + const tokenId = new BN(1); + + beforeEach(async function () { + this.token = await ERC721.new(name, symbol); + await this.token.$_mint(other, tokenId); + }); + + it('can receive an ERC721 safeTransfer', async function () { + await this.token.safeTransferFrom(other, this.mock.address, tokenId, { from: other }); + }); + }); + + describe('ERC1155', function () { + const uri = 'https://token-cdn-domain/{id}.json'; + const tokenIds = { + 1: new BN(1000), + 2: new BN(2000), + 3: new BN(3000), + }; + + beforeEach(async function () { + this.token = await ERC1155.new(uri); + await this.token.$_mintBatch(other, Object.keys(tokenIds), Object.values(tokenIds), '0x'); + }); + + it('can receive ERC1155 safeTransfer', async function () { + await this.token.safeTransferFrom( + other, + this.mock.address, + ...Object.entries(tokenIds)[0], // id + amount + '0x', + { from: other }, + ); + }); + + it('can receive ERC1155 safeBatchTransfer', async function () { + await this.token.safeBatchTransferFrom( + other, + this.mock.address, + Object.keys(tokenIds), + Object.values(tokenIds), + '0x', + { from: other }, + ); + }); + }); + }); +}); diff --git a/test/governance/extensions/GovernorERC721.test.js b/test/governance/extensions/GovernorERC721.test.js new file mode 100644 index 000000000..22265cc25 --- /dev/null +++ b/test/governance/extensions/GovernorERC721.test.js @@ -0,0 +1,116 @@ +const { expectEvent } = require('@openzeppelin/test-helpers'); +const { expect } = require('chai'); + +const Enums = require('../../helpers/enums'); +const { GovernorHelper } = require('../../helpers/governance'); + +const Governor = artifacts.require('$GovernorVoteMocks'); +const CallReceiver = artifacts.require('CallReceiverMock'); + +const TOKENS = [ + { Token: artifacts.require('$ERC721Votes'), mode: 'blocknumber' }, + { Token: artifacts.require('$ERC721VotesTimestampMock'), mode: 'timestamp' }, +]; + +contract('GovernorERC721', function (accounts) { + const [owner, voter1, voter2, voter3, voter4] = accounts; + + const name = 'OZ-Governor'; + const version = '1'; + const tokenName = 'MockNFToken'; + const tokenSymbol = 'MTKN'; + const NFT0 = web3.utils.toBN(0); + const NFT1 = web3.utils.toBN(1); + const NFT2 = web3.utils.toBN(2); + const NFT3 = web3.utils.toBN(3); + const NFT4 = web3.utils.toBN(4); + 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 Promise.all([NFT0, NFT1, NFT2, NFT3, NFT4].map(tokenId => this.token.$_mint(owner, tokenId))); + await this.helper.delegate({ token: this.token, to: voter1, tokenId: NFT0 }, { from: owner }); + await this.helper.delegate({ token: this.token, to: voter2, tokenId: NFT1 }, { from: owner }); + await this.helper.delegate({ token: this.token, to: voter2, tokenId: NFT2 }, { from: owner }); + await this.helper.delegate({ token: this.token, to: voter3, tokenId: NFT3 }, { from: owner }); + await this.helper.delegate({ token: this.token, to: voter4, tokenId: NFT4 }, { 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 ERC721 token', async function () { + await this.helper.propose(); + await this.helper.waitForSnapshot(); + + expectEvent(await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }), 'VoteCast', { + voter: voter1, + support: Enums.VoteType.For, + weight: '1', + }); + + expectEvent(await this.helper.vote({ support: Enums.VoteType.For }, { from: voter2 }), 'VoteCast', { + voter: voter2, + support: Enums.VoteType.For, + weight: '2', + }); + + expectEvent(await this.helper.vote({ support: Enums.VoteType.Against }, { from: voter3 }), 'VoteCast', { + voter: voter3, + support: Enums.VoteType.Against, + weight: '1', + }); + + expectEvent(await this.helper.vote({ support: Enums.VoteType.Abstain }, { from: voter4 }), 'VoteCast', { + voter: voter4, + support: Enums.VoteType.Abstain, + weight: '1', + }); + + 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('3'); + expect(results.againstVotes).to.be.bignumber.equal('1'); + expect(results.abstainVotes).to.be.bignumber.equal('1'); + }); + }); + }); + } +}); diff --git a/test/governance/extensions/GovernorPreventLateQuorum.test.js b/test/governance/extensions/GovernorPreventLateQuorum.test.js new file mode 100644 index 000000000..17ae05a73 --- /dev/null +++ b/test/governance/extensions/GovernorPreventLateQuorum.test.js @@ -0,0 +1,195 @@ +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'); + +const TOKENS = [ + { Token: artifacts.require('$ERC20Votes'), mode: 'blocknumber' }, + { Token: artifacts.require('$ERC20VotesTimestampMock'), mode: 'timestamp' }, +]; + +contract('GovernorPreventLateQuorum', function (accounts) { + const [owner, proposer, 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 lateQuorumVoteExtension = web3.utils.toBN(8); + const quorum = web3.utils.toWei('1'); + 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, + votingDelay, + votingPeriod, + 0, + this.token.address, + lateQuorumVoteExtension, + quorum, + ); + 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(quorum); + expect(await this.mock.lateQuorumVoteExtension()).to.be.bignumber.equal(lateQuorumVoteExtension); + }); + + it('nominal workflow unaffected', async function () { + const txPropose = await this.helper.propose({ from: proposer }); + 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')); + }); + + const voteStart = web3.utils.toBN(await clockFromReceipt[mode](txPropose.receipt)).add(votingDelay); + const voteEnd = web3.utils + .toBN(await clockFromReceipt[mode](txPropose.receipt)) + .add(votingDelay) + .add(votingPeriod); + expect(await this.mock.proposalSnapshot(this.proposal.id)).to.be.bignumber.equal(voteStart); + expect(await this.mock.proposalDeadline(this.proposal.id)).to.be.bignumber.equal(voteEnd); + + expectEvent(txPropose, 'ProposalCreated', { + proposalId: this.proposal.id, + proposer, + targets: this.proposal.targets, + // values: this.proposal.values.map(value => web3.utils.toBN(value)), + signatures: this.proposal.signatures, + calldatas: this.proposal.data, + voteStart, + voteEnd, + description: this.proposal.description, + }); + }); + + it('Delay is extended to prevent last minute take-over', async function () { + const txPropose = await this.helper.propose({ from: proposer }); + + // compute original schedule + const startBlock = web3.utils.toBN(await clockFromReceipt[mode](txPropose.receipt)).add(votingDelay); + const endBlock = web3.utils + .toBN(await clockFromReceipt[mode](txPropose.receipt)) + .add(votingDelay) + .add(votingPeriod); + expect(await this.mock.proposalSnapshot(this.proposal.id)).to.be.bignumber.equal(startBlock); + expect(await this.mock.proposalDeadline(this.proposal.id)).to.be.bignumber.equal(endBlock); + + // wait for the last minute to vote + await this.helper.waitForDeadline(-1); + const txVote = await this.helper.vote({ support: Enums.VoteType.For }, { from: voter2 }); + + // cannot execute yet + expect(await this.mock.state(this.proposal.id)).to.be.bignumber.equal(Enums.ProposalState.Active); + + // compute new extended schedule + const extendedDeadline = web3.utils + .toBN(await clockFromReceipt[mode](txVote.receipt)) + .add(lateQuorumVoteExtension); + expect(await this.mock.proposalSnapshot(this.proposal.id)).to.be.bignumber.equal(startBlock); + expect(await this.mock.proposalDeadline(this.proposal.id)).to.be.bignumber.equal(extendedDeadline); + + // still possible to vote + await this.helper.vote({ support: Enums.VoteType.Against }, { from: voter1 }); + + await this.helper.waitForDeadline(); + expect(await this.mock.state(this.proposal.id)).to.be.bignumber.equal(Enums.ProposalState.Active); + await this.helper.waitForDeadline(+1); + expect(await this.mock.state(this.proposal.id)).to.be.bignumber.equal(Enums.ProposalState.Defeated); + + // check extension event + expectEvent(txVote, 'ProposalExtended', { proposalId: this.proposal.id, extendedDeadline }); + }); + + describe('onlyGovernance updates', function () { + it('setLateQuorumVoteExtension is protected', async function () { + await expectRevertCustomError( + this.mock.setLateQuorumVoteExtension(0, { from: owner }), + 'GovernorOnlyExecutor', + [owner], + ); + }); + + it('can setLateQuorumVoteExtension through governance', async function () { + this.helper.setProposal( + [ + { + target: this.mock.address, + data: this.mock.contract.methods.setLateQuorumVoteExtension('0').encodeABI(), + }, + ], + '', + ); + + 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.execute(), 'LateQuorumVoteExtensionSet', { + oldVoteExtension: lateQuorumVoteExtension, + newVoteExtension: '0', + }); + + expect(await this.mock.lateQuorumVoteExtension()).to.be.bignumber.equal('0'); + }); + }); + }); + } +}); 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/GovernorTimelockAccess.test.js b/test/governance/extensions/GovernorTimelockAccess.test.js new file mode 100644 index 000000000..59ddf6dac --- /dev/null +++ b/test/governance/extensions/GovernorTimelockAccess.test.js @@ -0,0 +1,407 @@ +const { expectEvent } = require('@openzeppelin/test-helpers'); +const { expect } = require('chai'); + +const Enums = require('../../helpers/enums'); +const { GovernorHelper, proposalStatesToBitMap } = require('../../helpers/governance'); +const { expectRevertCustomError } = require('../../helpers/customError'); +const { clockFromReceipt } = require('../../helpers/time'); +const { selector } = require('../../helpers/methods'); + +const AccessManager = artifacts.require('$AccessManager'); +const Governor = artifacts.require('$GovernorTimelockAccessMock'); +const AccessManagedTarget = artifacts.require('$AccessManagedTarget'); + +const TOKENS = [ + // { Token: artifacts.require('$ERC20Votes'), mode: 'blocknumber' }, + { Token: artifacts.require('$ERC20VotesTimestampMock'), mode: 'timestamp' }, +]; + +const hashOperation = (caller, target, data) => + web3.utils.keccak256(web3.eth.abi.encodeParameters(['address', 'address', 'bytes'], [caller, target, data])); + +contract('GovernorTimelockAccess', function (accounts) { + const [admin, 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.token = await Token.new(tokenName, tokenSymbol, tokenName, version); + this.manager = await AccessManager.new(admin); + this.mock = await Governor.new( + name, + votingDelay, + votingPeriod, + 0, // proposal threshold + this.manager.address, + 0, // base delay + this.token.address, + 0, // quorum + ); + this.receiver = await AccessManagedTarget.new(this.manager.address); + + this.helper = new GovernorHelper(this.mock, mode); + + await web3.eth.sendTransaction({ from: admin, to: this.mock.address, value }); + + await this.token.$_mint(admin, tokenSupply); + await this.helper.delegate({ token: this.token, to: voter1, value: web3.utils.toWei('10') }, { from: admin }); + await this.helper.delegate({ token: this.token, to: voter2, value: web3.utils.toWei('7') }, { from: admin }); + await this.helper.delegate({ token: this.token, to: voter3, value: web3.utils.toWei('5') }, { from: admin }); + await this.helper.delegate({ token: this.token, to: voter4, value: web3.utils.toWei('2') }, { from: admin }); + + // default proposals + this.restricted = {}; + this.restricted.selector = this.receiver.contract.methods.fnRestricted().encodeABI(); + this.restricted.operation = { + target: this.receiver.address, + value: '0', + data: this.restricted.selector, + }; + this.restricted.operationId = hashOperation( + this.mock.address, + this.restricted.operation.target, + this.restricted.operation.data, + ); + + this.unrestricted = {}; + this.unrestricted.selector = this.receiver.contract.methods.fnUnrestricted().encodeABI(); + this.unrestricted.operation = { + target: this.receiver.address, + value: '0', + data: this.unrestricted.selector, + }; + this.unrestricted.operationId = hashOperation( + this.mock.address, + this.unrestricted.operation.target, + this.unrestricted.operation.data, + ); + }); + + it('accepts ether transfers', async function () { + await web3.eth.sendTransaction({ from: admin, to: this.mock.address, value: 1 }); + }); + + it('post 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.accessManager()).to.be.equal(this.manager.address); + }); + + describe('base delay only', function () { + for (const [delay, queue] of [ + [0, true], + [0, false], + [1000, true], + ]) { + it(`delay ${delay}, ${queue ? 'with' : 'without'} queuing`, async function () { + await this.mock.$_setBaseDelaySeconds(delay); + + this.proposal = await this.helper.setProposal([this.unrestricted.operation], 'descr'); + + await this.helper.propose(); + await this.helper.waitForSnapshot(); + await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }); + await this.helper.waitForDeadline(); + if (queue) { + const txQueue = await this.helper.queue(); + expectEvent(txQueue, 'ProposalQueued', { proposalId: this.proposal.id }); + } + if (delay > 0) { + await this.helper.waitForEta(); + } + const txExecute = await this.helper.execute(); + expectEvent(txExecute, 'ProposalExecuted', { proposalId: this.proposal.id }); + expectEvent.inTransaction(txExecute, this.receiver, 'CalledUnrestricted'); + }); + } + }); + + it('single operation with access manager delay', async function () { + const delay = 1000; + const roleId = '1'; + + await this.manager.setTargetFunctionRole(this.receiver.address, [this.restricted.selector], roleId, { + from: admin, + }); + await this.manager.grantRole(roleId, this.mock.address, delay, { from: admin }); + + this.proposal = await this.helper.setProposal([this.restricted.operation], 'descr'); + + await this.helper.propose(); + await this.helper.waitForSnapshot(); + await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }); + await this.helper.waitForDeadline(); + const txQueue = await this.helper.queue(); + await this.helper.waitForEta(); + const txExecute = await this.helper.execute(); + + expectEvent(txQueue, 'ProposalQueued', { proposalId: this.proposal.id }); + await expectEvent.inTransaction(txQueue.tx, this.manager, 'OperationScheduled', { + operationId: this.restricted.operationId, + nonce: '1', + schedule: web3.utils.toBN(await clockFromReceipt.timestamp(txQueue.receipt)).addn(delay), + caller: this.mock.address, + target: this.restricted.operation.target, + data: this.restricted.operation.data, + }); + + expectEvent(txExecute, 'ProposalExecuted', { proposalId: this.proposal.id }); + await expectEvent.inTransaction(txExecute.tx, this.manager, 'OperationExecuted', { + operationId: this.restricted.operationId, + nonce: '1', + }); + await expectEvent.inTransaction(txExecute.tx, this.receiver, 'CalledRestricted'); + }); + + it('bundle of varied operations', async function () { + const managerDelay = 1000; + const roleId = '1'; + + const baseDelay = managerDelay * 2; + + await this.mock.$_setBaseDelaySeconds(baseDelay); + + await this.manager.setTargetFunctionRole(this.receiver.address, [this.restricted.selector], roleId, { + from: admin, + }); + await this.manager.grantRole(roleId, this.mock.address, managerDelay, { from: admin }); + + this.proposal = await this.helper.setProposal( + [this.restricted.operation, this.unrestricted.operation], + 'descr', + ); + + await this.helper.propose(); + await this.helper.waitForSnapshot(); + await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }); + await this.helper.waitForDeadline(); + const txQueue = await this.helper.queue(); + await this.helper.waitForEta(); + const txExecute = await this.helper.execute(); + + expectEvent(txQueue, 'ProposalQueued', { proposalId: this.proposal.id }); + await expectEvent.inTransaction(txQueue.tx, this.manager, 'OperationScheduled', { + operationId: this.restricted.operationId, + nonce: '1', + schedule: web3.utils.toBN(await clockFromReceipt.timestamp(txQueue.receipt)).addn(baseDelay), + caller: this.mock.address, + target: this.restricted.operation.target, + data: this.restricted.operation.data, + }); + + expectEvent(txExecute, 'ProposalExecuted', { proposalId: this.proposal.id }); + await expectEvent.inTransaction(txExecute.tx, this.manager, 'OperationExecuted', { + operationId: this.restricted.operationId, + nonce: '1', + }); + await expectEvent.inTransaction(txExecute.tx, this.receiver, 'CalledRestricted'); + await expectEvent.inTransaction(txExecute.tx, this.receiver, 'CalledUnrestricted'); + }); + + describe('cancel', function () { + const delay = 1000; + const roleId = '1'; + + beforeEach(async function () { + await this.manager.setTargetFunctionRole(this.receiver.address, [this.restricted.selector], roleId, { + from: admin, + }); + await this.manager.grantRole(roleId, this.mock.address, delay, { from: admin }); + }); + + it('cancellation after queue (internal)', async function () { + this.proposal = await this.helper.setProposal([this.restricted.operation], 'descr'); + + 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(); + + const txCancel = await this.helper.cancel('internal'); + expectEvent(txCancel, 'ProposalCanceled', { proposalId: this.proposal.id }); + await expectEvent.inTransaction(txCancel.tx, this.manager, 'OperationCanceled', { + operationId: this.restricted.operationId, + nonce: '1', + }); + + await this.helper.waitForEta(); + await expectRevertCustomError(this.helper.execute(), 'GovernorUnexpectedProposalState', [ + this.proposal.id, + Enums.ProposalState.Canceled, + proposalStatesToBitMap([Enums.ProposalState.Succeeded, Enums.ProposalState.Queued]), + ]); + }); + + it('cancel calls already canceled by guardian', async function () { + const operationA = { target: this.receiver.address, data: this.restricted.selector + '00' }; + const operationB = { target: this.receiver.address, data: this.restricted.selector + '01' }; + const operationC = { target: this.receiver.address, data: this.restricted.selector + '02' }; + const operationAId = hashOperation(this.mock.address, operationA.target, operationA.data); + const operationBId = hashOperation(this.mock.address, operationB.target, operationB.data); + + const proposal1 = new GovernorHelper(this.mock, mode); + const proposal2 = new GovernorHelper(this.mock, mode); + proposal1.setProposal([operationA, operationB], 'proposal A+B'); + proposal2.setProposal([operationA, operationC], 'proposal A+C'); + + for (const p of [proposal1, proposal2]) { + await p.propose(); + await p.waitForSnapshot(); + await p.vote({ support: Enums.VoteType.For }, { from: voter1 }); + await p.waitForDeadline(); + } + + // Can queue the first proposal + await proposal1.queue(); + + // Cannot queue the second proposal: operation A already scheduled with delay + await expectRevertCustomError(proposal2.queue(), 'AccessManagerAlreadyScheduled', [operationAId]); + + // Admin cancels operation B on the manager + await this.manager.cancel(this.mock.address, operationB.target, operationB.data, { from: admin }); + + // Still cannot queue the second proposal: operation A already scheduled with delay + await expectRevertCustomError(proposal2.queue(), 'AccessManagerAlreadyScheduled', [operationAId]); + + await proposal1.waitForEta(); + + // Cannot execute first proposal: operation B has been canceled + await expectRevertCustomError(proposal1.execute(), 'AccessManagerNotScheduled', [operationBId]); + + // Cancel the first proposal to release operation A + await proposal1.cancel('internal'); + + // can finally queue the second proposal + await proposal2.queue(); + + await proposal2.waitForEta(); + + // Can execute second proposal + await proposal2.execute(); + }); + }); + + describe('ignore AccessManager', function () { + it('defaults', async function () { + expect(await this.mock.isAccessManagerIgnored(this.receiver.address, this.restricted.selector)).to.equal( + false, + ); + expect(await this.mock.isAccessManagerIgnored(this.mock.address, '0x12341234')).to.equal(true); + }); + + it('internal setter', async function () { + const p1 = { target: this.receiver.address, selector: this.restricted.selector, ignored: true }; + const tx1 = await this.mock.$_setAccessManagerIgnored(p1.target, p1.selector, p1.ignored); + expect(await this.mock.isAccessManagerIgnored(p1.target, p1.selector)).to.equal(p1.ignored); + expectEvent(tx1, 'AccessManagerIgnoredSet', p1); + + const p2 = { target: this.mock.address, selector: '0x12341234', ignored: false }; + const tx2 = await this.mock.$_setAccessManagerIgnored(p2.target, p2.selector, p2.ignored); + expect(await this.mock.isAccessManagerIgnored(p2.target, p2.selector)).to.equal(p2.ignored); + expectEvent(tx2, 'AccessManagerIgnoredSet', p2); + }); + + it('external setter', async function () { + const setAccessManagerIgnored = (...args) => + this.mock.contract.methods.setAccessManagerIgnored(...args).encodeABI(); + + await this.helper.setProposal( + [ + { + target: this.mock.address, + data: setAccessManagerIgnored( + this.receiver.address, + [this.restricted.selector, this.unrestricted.selector], + true, + ), + value: '0', + }, + { + target: this.mock.address, + data: setAccessManagerIgnored(this.mock.address, ['0x12341234', '0x67896789'], false), + value: '0', + }, + ], + 'descr', + ); + + await this.helper.propose(); + await this.helper.waitForSnapshot(); + await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }); + await this.helper.waitForDeadline(); + const tx = await this.helper.execute(); + + expectEvent(tx, 'AccessManagerIgnoredSet'); + + expect(await this.mock.isAccessManagerIgnored(this.receiver.address, this.restricted.selector)).to.equal( + true, + ); + expect(await this.mock.isAccessManagerIgnored(this.receiver.address, this.unrestricted.selector)).to.equal( + true, + ); + + expect(await this.mock.isAccessManagerIgnored(this.mock.address, '0x12341234')).to.equal(false); + expect(await this.mock.isAccessManagerIgnored(this.mock.address, '0x67896789')).to.equal(false); + }); + + it('locked function', async function () { + const setAccessManagerIgnored = selector('setAccessManagerIgnored(address,bytes4[],bool)'); + await expectRevertCustomError( + this.mock.$_setAccessManagerIgnored(this.mock.address, setAccessManagerIgnored, true), + 'GovernorLockedIgnore', + [], + ); + await this.mock.$_setAccessManagerIgnored(this.receiver.address, setAccessManagerIgnored, true); + }); + + it('ignores access manager', async function () { + const amount = 100; + + const target = this.token.address; + const data = this.token.contract.methods.transfer(voter4, amount).encodeABI(); + const selector = data.slice(0, 10); + await this.token.$_mint(this.mock.address, amount); + + const roleId = '1'; + await this.manager.setTargetFunctionRole(target, [selector], roleId, { from: admin }); + await this.manager.grantRole(roleId, this.mock.address, 0, { from: admin }); + + await this.helper.setProposal([{ target, data, value: '0' }], '1'); + await this.helper.propose(); + await this.helper.waitForSnapshot(); + await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }); + await this.helper.waitForDeadline(); + await expectRevertCustomError(this.helper.execute(), 'ERC20InsufficientBalance', [ + this.manager.address, + 0, + amount, + ]); + + await this.mock.$_setAccessManagerIgnored(target, selector, true); + + await this.helper.setProposal([{ target, data, value: '0' }], '2'); + await this.helper.propose(); + await this.helper.waitForSnapshot(); + await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }); + await this.helper.waitForDeadline(); + const tx = await this.helper.execute(); + expectEvent.inTransaction(tx, this.token, 'Transfer', { from: this.mock.address }); + }); + }); + }); + } +}); diff --git a/test/governance/extensions/GovernorTimelockCompound.test.js b/test/governance/extensions/GovernorTimelockCompound.test.js new file mode 100644 index 000000000..e9d6f8373 --- /dev/null +++ b/test/governance/extensions/GovernorTimelockCompound.test.js @@ -0,0 +1,441 @@ +const { constants, expectEvent, expectRevert } = require('@openzeppelin/test-helpers'); +const { expect } = require('chai'); + +const Enums = require('../../helpers/enums'); +const { GovernorHelper, proposalStatesToBitMap } = require('../../helpers/governance'); +const { expectRevertCustomError } = require('../../helpers/customError'); +const { computeCreateAddress } = require('../../helpers/create'); +const { clockFromReceipt } = require('../../helpers/time'); + +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'); + +const TOKENS = [ + { Token: artifacts.require('$ERC20Votes'), mode: 'blocknumber' }, + { Token: artifacts.require('$ERC20VotesTimestampMock'), mode: 'timestamp' }, +]; + +contract('GovernorTimelockCompound', function (accounts) { + const [owner, 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 value = web3.utils.toWei('1'); + + const defaultDelay = 2 * 86400; + + 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, defaultDelay); + 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 }); + + 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("doesn't accept ether transfers", async function () { + await expectRevert.unspecified(web3.eth.sendTransaction({ from: owner, to: this.mock.address, value: 1 })); + }); + + it('post 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.timelock()).to.be.equal(this.timelock.address); + expect(await this.timelock.admin()).to.be.equal(this.mock.address); + }); + + it('nominal', async function () { + expect(await this.mock.proposalEta(this.proposal.id)).to.be.bignumber.equal('0'); + expect(await this.mock.proposalNeedsQueuing(this.proposal.id)).to.be.equal(true); + + 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(); + + const eta = web3.utils.toBN(await clockFromReceipt.timestamp(txQueue.receipt)).addn(defaultDelay); + expect(await this.mock.proposalEta(this.proposal.id)).to.be.bignumber.equal(eta); + expect(await this.mock.proposalNeedsQueuing(this.proposal.id)).to.be.equal(true); + + await this.helper.waitForEta(); + const txExecute = await this.helper.execute(); + + expectEvent(txQueue, 'ProposalQueued', { proposalId: this.proposal.id }); + await expectEvent.inTransaction(txQueue.tx, this.timelock, 'QueueTransaction', { eta }); + + expectEvent(txExecute, 'ProposalExecuted', { proposalId: this.proposal.id }); + await expectEvent.inTransaction(txExecute.tx, this.timelock, 'ExecuteTransaction', { eta }); + await expectEvent.inTransaction(txExecute.tx, this.receiver, 'MockFunctionCalled'); + }); + + 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 proposal contains duplicate calls', async function () { + const action = { + target: this.token.address, + data: this.token.contract.methods.approve(this.receiver.address, constants.MAX_UINT256).encodeABI(), + }; + 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 expectRevertCustomError(this.helper.queue(), 'GovernorAlreadyQueuedProposal', [id]); + await expectRevertCustomError(this.helper.execute(), 'GovernorNotQueuedProposal', [id]); + }); + }); + + 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(), 'GovernorNotQueuedProposal', [this.proposal.id]); + }); + + 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 expectRevert( + this.helper.execute(), + "Timelock::executeTransaction: Transaction hasn't surpassed time lock", + ); + }); + + it('if too late', 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(+30 * 86400); + + expect(await this.mock.state(this.proposal.id)).to.be.bignumber.equal(Enums.ProposalState.Expired); + + 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 () { + 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]), + ]); + }); + }); + + 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 () { + 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 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]), + ]); + }); + }); + + 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', + }); + }); + }); + + describe('updateTimelock', function () { + beforeEach(async function () { + this.newTimelock = await Timelock.new(this.mock.address, 7 * 86400); + }); + + 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.timelock.address, + data: this.timelock.contract.methods.setPendingAdmin(owner).encodeABI(), + }, + { + 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('can transfer timelock to new governor', async function () { + const newGovernor = await Governor.new(name, 8, 32, 0, this.timelock.address, this.token.address, 0); + this.helper.setProposal( + [ + { + target: this.timelock.address, + data: this.timelock.contract.methods.setPendingAdmin(newGovernor.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(); + + await expectEvent.inTransaction(txExecute.tx, this.timelock, 'NewPendingAdmin', { + newPendingAdmin: newGovernor.address, + }); + + await newGovernor.__acceptAdmin(); + expect(await this.timelock.admin()).to.be.bignumber.equal(newGovernor.address); + }); + }); + }); + } +}); diff --git a/test/governance/extensions/GovernorTimelockControl.test.js b/test/governance/extensions/GovernorTimelockControl.test.js new file mode 100644 index 000000000..ec03d6144 --- /dev/null +++ b/test/governance/extensions/GovernorTimelockControl.test.js @@ -0,0 +1,515 @@ +const { constants, expectEvent, expectRevert, time } = require('@openzeppelin/test-helpers'); +const { expect } = require('chai'); + +const Enums = require('../../helpers/enums'); +const { GovernorHelper, proposalStatesToBitMap, timelockSalt } = require('../../helpers/governance'); +const { expectRevertCustomError } = require('../../helpers/customError'); +const { clockFromReceipt } = require('../../helpers/time'); + +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' }, + { Token: artifacts.require('$ERC20VotesTimestampMock'), mode: 'timestamp' }, +]; + +contract('GovernorTimelockControl', function (accounts) { + const [owner, voter1, voter2, voter3, voter4, other] = 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'); + + const delay = 3600; + + 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(delay, [], [], 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); + + this.PROPOSER_ROLE = await this.timelock.PROPOSER_ROLE(); + this.EXECUTOR_ROLE = await this.timelock.EXECUTOR_ROLE(); + this.CANCELLER_ROLE = await this.timelock.CANCELLER_ROLE(); + + 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]), + ); + }); + + it("doesn't accept ether transfers", async function () { + await expectRevert.unspecified(web3.eth.sendTransaction({ from: owner, to: this.mock.address, value: 1 })); + }); + + it('post 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.timelock()).to.be.equal(this.timelock.address); + }); + + it('nominal', async function () { + expect(await this.mock.proposalEta(this.proposal.id)).to.be.bignumber.equal('0'); + expect(await this.mock.proposalNeedsQueuing(this.proposal.id)).to.be.equal(true); + + 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 eta = web3.utils.toBN(await clockFromReceipt.timestamp(txQueue.receipt)).addn(delay); + expect(await this.mock.proposalEta(this.proposal.id)).to.be.bignumber.equal(eta); + expect(await this.mock.proposalNeedsQueuing(this.proposal.id)).to.be.equal(true); + + const txExecute = await this.helper.execute(); + + 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'); + }); + + 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]), + ]); + }); + }); + + 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, + proposalStatesToBitMap(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, + proposalStatesToBitMap(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', + timelockSalt(this.mock.address, 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 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('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(); + + expect(await this.mock.state(this.proposal.id)).to.be.bignumber.equal(Enums.ProposalState.Queued); + + 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.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(), + }, + ], + '', + ); + + 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; + + await this.timelock.schedule(target, value, data, predecessor, salt, delay, { from: owner }); + + await time.increase(delay); + + 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( + delay, + [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); + }); + }); + + 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 () { + 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. + }); + }); + } +}); diff --git a/test/governance/extensions/GovernorVotesQuorumFraction.test.js b/test/governance/extensions/GovernorVotesQuorumFraction.test.js new file mode 100644 index 000000000..ece9c78d6 --- /dev/null +++ b/test/governance/extensions/GovernorVotesQuorumFraction.test.js @@ -0,0 +1,167 @@ +const { expectEvent, time } = require('@openzeppelin/test-helpers'); +const { expect } = require('chai'); + +const Enums = require('../../helpers/enums'); +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'); + +const TOKENS = [ + { Token: artifacts.require('$ERC20Votes'), mode: 'blocknumber' }, + { Token: artifacts.require('$ERC20VotesTimestampMock'), mode: 'timestamp' }, +]; + +contract('GovernorVotesQuorumFraction', 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.toBN(web3.utils.toWei('100')); + const ratio = web3.utils.toBN(8); // percents + const newRatio = web3.utils.toBN(6); // percents + 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, votingDelay, votingPeriod, 0, this.token.address, ratio); + 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'); + expect(await this.mock.quorumNumerator()).to.be.bignumber.equal(ratio); + expect(await this.mock.quorumDenominator()).to.be.bignumber.equal('100'); + expect(await clock[mode]().then(timepoint => this.mock.quorum(timepoint - 1))).to.be.bignumber.equal( + tokenSupply.mul(ratio).divn(100), + ); + }); + + it('quroum reached', 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.execute(); + }); + + it('quroum not reached', async function () { + await this.helper.propose(); + await this.helper.waitForSnapshot(); + await this.helper.vote({ support: Enums.VoteType.For }, { from: voter2 }); + await this.helper.waitForDeadline(); + 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 expectRevertCustomError( + this.mock.updateQuorumNumerator(newRatio, { from: owner }), + 'GovernorOnlyExecutor', + [owner], + ); + }); + + it('can updateQuorumNumerator through governance', async function () { + this.helper.setProposal( + [ + { + target: this.mock.address, + data: this.mock.contract.methods.updateQuorumNumerator(newRatio).encodeABI(), + }, + ], + '', + ); + + 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.execute(), 'QuorumNumeratorUpdated', { + oldQuorumNumerator: ratio, + newQuorumNumerator: newRatio, + }); + + expect(await this.mock.quorumNumerator()).to.be.bignumber.equal(newRatio); + expect(await this.mock.quorumDenominator()).to.be.bignumber.equal('100'); + + // it takes one block for the new quorum to take effect + expect(await clock[mode]().then(blockNumber => this.mock.quorum(blockNumber - 1))).to.be.bignumber.equal( + tokenSupply.mul(ratio).divn(100), + ); + + await time.advanceBlock(); + + expect(await clock[mode]().then(blockNumber => this.mock.quorum(blockNumber - 1))).to.be.bignumber.equal( + tokenSupply.mul(newRatio).divn(100), + ); + }); + + it('cannot updateQuorumNumerator over the maximum', async function () { + const quorumNumerator = 101; + this.helper.setProposal( + [ + { + target: this.mock.address, + data: this.mock.contract.methods.updateQuorumNumerator(quorumNumerator).encodeABI(), + }, + ], + '', + ); + + await this.helper.propose(); + await this.helper.waitForSnapshot(); + await this.helper.vote({ support: Enums.VoteType.For }, { from: voter1 }); + await this.helper.waitForDeadline(); + + const quorumDenominator = await this.mock.quorumDenominator(); + + await expectRevertCustomError(this.helper.execute(), 'GovernorInvalidQuorumFraction', [ + quorumNumerator, + quorumDenominator, + ]); + }); + }); + }); + } +}); diff --git a/test/governance/extensions/GovernorWithParams.test.js b/test/governance/extensions/GovernorWithParams.test.js new file mode 100644 index 000000000..35da3a0f3 --- /dev/null +++ b/test/governance/extensions/GovernorWithParams.test.js @@ -0,0 +1,277 @@ +const { expectEvent } = require('@openzeppelin/test-helpers'); +const { expect } = require('chai'); +const ethSigUtil = require('eth-sig-util'); +const Wallet = require('ethereumjs-wallet').default; + +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'); +const ERC1271WalletMock = artifacts.require('ERC1271WalletMock'); + +const rawParams = { + uintParam: web3.utils.toBN('42'), + strParam: 'These are my params', +}; + +const encodedParams = web3.eth.abi.encodeParameters(['uint256', 'string'], Object.values(rawParams)); + +const TOKENS = [ + { Token: artifacts.require('$ERC20Votes'), mode: 'blocknumber' }, + { Token: artifacts.require('$ERC20VotesTimestampMock'), mode: 'timestamp' }, +]; + +contract('GovernorWithParams', function (accounts) { + const [owner, proposer, 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.chainId = await web3.eth.getChainId(); + 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); + }); + + it('nominal is unaffected', async function () { + 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.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 web3.eth.getBalance(this.mock.address)).to.be.bignumber.equal('0'); + expect(await web3.eth.getBalance(this.receiver.address)).to.be.bignumber.equal(value); + }); + + it('Voting with params is properly supported', async function () { + await this.helper.propose({ from: proposer }); + await this.helper.waitForSnapshot(); + + const weight = web3.utils.toBN(web3.utils.toWei('7')).sub(rawParams.uintParam); + + const tx = await this.helper.vote( + { + support: Enums.VoteType.For, + reason: 'no particular reason', + params: encodedParams, + }, + { from: voter2 }, + ); + + expectEvent(tx, 'CountParams', { ...rawParams }); + expectEvent(tx, 'VoteCastWithParams', { + voter: voter2, + 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); + }); + + describe('voting 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: '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, + })); + + this.sign = privateKey => async (contract, message) => + ethSigUtil.signTypedMessage(privateKey, { data: await this.data(contract, message) }); + }); + + 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); + + 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('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(wallet.address); + + // Run proposal + await this.helper.propose(); + await this.helper.waitForSnapshot(); + const tx = await this.helper.vote({ + support: Enums.VoteType.For, + voter: wallet.address, + nonce, + reason: 'no particular reason', + params: encodedParams, + signature: this.sign(ERC1271WalletOwner.getPrivateKey()), + }); + + expectEvent(tx, 'CountParams', { ...rawParams }); + expectEvent(tx, 'VoteCastWithParams', { + voter: wallet.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(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 signature(...params); + const tamperedSig = web3.utils.hexToBytes(sig); + tamperedSig[42] ^= 0xff; + return web3.utils.bytesToHex(tamperedSig); + }, + reason: 'no particular reason', + params: encodedParams, + }; + + 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.sign(this.voterBySig.getPrivateKey()), + reason: 'no particular reason', + params: encodedParams, + }; + + await expectRevertCustomError( + this.helper.vote(voteParams), + // The signature check implies the nonce can't be tampered without changing the signer + 'GovernorInvalidSignature', + [voteParams.voter], + ); + }); + }); + }); + } +}); diff --git a/test/governance/utils/EIP6372.behavior.js b/test/governance/utils/EIP6372.behavior.js new file mode 100644 index 000000000..022ec3568 --- /dev/null +++ b/test/governance/utils/EIP6372.behavior.js @@ -0,0 +1,23 @@ +const { clock } = require('../../helpers/time'); + +function shouldBehaveLikeEIP6372(mode = 'blocknumber') { + describe('should implement EIP6372', function () { + beforeEach(async function () { + this.mock = this.mock ?? this.token ?? this.votes; + }); + + it('clock is correct', async function () { + expect(await this.mock.clock()).to.be.bignumber.equal(await clock[mode]().then(web3.utils.toBN)); + }); + + it('CLOCK_MODE is correct', async function () { + const params = new URLSearchParams(await this.mock.CLOCK_MODE()); + expect(params.get('mode')).to.be.equal(mode); + expect(params.get('from')).to.be.equal(mode == 'blocknumber' ? 'default' : null); + }); + }); +} + +module.exports = { + shouldBehaveLikeEIP6372, +}; diff --git a/test/governance/utils/Votes.behavior.js b/test/governance/utils/Votes.behavior.js new file mode 100644 index 000000000..5836cc351 --- /dev/null +++ b/test/governance/utils/Votes.behavior.js @@ -0,0 +1,360 @@ +const { constants, expectEvent, time } = require('@openzeppelin/test-helpers'); + +const { MAX_UINT256, ZERO_ADDRESS } = constants; + +const { fromRpcSig } = require('ethereumjs-util'); +const ethSigUtil = require('eth-sig-util'); +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' }, + { name: 'nonce', type: 'uint256' }, + { name: 'expiry', type: 'uint256' }, +]; + +const buildAndSignDelegation = (contract, message, pk) => + getDomain(contract) + .then(domain => ({ + primaryType: 'Delegation', + types: { EIP712Domain: domainType(domain), Delegation }, + domain, + message, + })) + .then(data => fromRpcSig(ethSigUtil.signTypedMessage(pk, { data }))); + +function shouldBehaveLikeVotes(accounts, tokens, { mode = 'blocknumber', fungible = true }) { + shouldBehaveLikeEIP6372(mode); + + 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(accounts[0])).to.be.bignumber.equal('0'); + }); + + describe('delegation with signature', function () { + const token = tokens[0]; + + 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('delegation with tokens', async function () { + await this.votes.$_mint(accounts[1], token); + const weight = getWeight(token); + + expect(await this.votes.delegates(accounts[1])).to.be.equal(ZERO_ADDRESS); + + const { receipt } = await this.votes.delegate(accounts[1], { from: accounts[1] }); + const timepoint = await clockFromReceipt[mode](receipt); + + expectEvent(receipt, 'DelegateChanged', { + delegator: accounts[1], + fromDelegate: ZERO_ADDRESS, + toDelegate: accounts[1], + }); + expectEvent(receipt, 'DelegateVotesChanged', { + delegate: accounts[1], + previousVotes: '0', + newVotes: weight, + }); + + 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], timepoint - 1)).to.be.bignumber.equal('0'); + await time.advanceBlock(); + expect(await this.votes.getPastVotes(accounts[1], timepoint)).to.be.bignumber.equal(weight); + }); + + 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); + + 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'); + + const { receipt } = await this.votes.delegate(accounts[2], { from: accounts[1] }); + const timepoint = await clockFromReceipt[mode](receipt); + + expectEvent(receipt, 'DelegateChanged', { + delegator: accounts[1], + fromDelegate: accounts[1], + toDelegate: accounts[2], + }); + expectEvent(receipt, 'DelegateVotesChanged', { + delegate: accounts[1], + previousVotes: weight, + newVotes: '0', + }); + expectEvent(receipt, 'DelegateVotesChanged', { + delegate: accounts[2], + previousVotes: '0', + newVotes: weight, + }); + + 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); + + expect(await this.votes.getPastVotes(accounts[1], timepoint - 1)).to.be.bignumber.equal(weight); + expect(await this.votes.getPastVotes(accounts[2], timepoint - 1)).to.be.bignumber.equal('0'); + await time.advanceBlock(); + expect(await this.votes.getPastVotes(accounts[1], timepoint)).to.be.bignumber.equal('0'); + expect(await this.votes.getPastVotes(accounts[2], timepoint)).to.be.bignumber.equal(weight); + }); + + describe('with signature', function () { + const delegator = Wallet.generate(); + const [delegatee, other] = accounts; + const nonce = 0; + delegator.address = web3.utils.toChecksumAddress(delegator.getAddressString()); + + it('accept signed delegation', async function () { + await this.votes.$_mint(delegator.address, token); + const weight = getWeight(token); + + const { v, r, s } = await buildAndSignDelegation( + this.votes, + { + delegatee, + nonce, + expiry: MAX_UINT256, + }, + delegator.getPrivateKey(), + ); + + 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); + const timepoint = await clockFromReceipt[mode](receipt); + + expectEvent(receipt, 'DelegateChanged', { + delegator: delegator.address, + fromDelegate: ZERO_ADDRESS, + toDelegate: delegatee, + }); + expectEvent(receipt, 'DelegateVotesChanged', { + delegate: delegatee, + previousVotes: '0', + newVotes: weight, + }); + + 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, timepoint - 1)).to.be.bignumber.equal('0'); + await time.advanceBlock(); + expect(await this.votes.getPastVotes(delegatee, timepoint)).to.be.bignumber.equal(weight); + }); + + it('rejects reused signature', async function () { + const { v, r, s } = await buildAndSignDelegation( + this.votes, + { + delegatee, + nonce, + expiry: MAX_UINT256, + }, + delegator.getPrivateKey(), + ); + + await this.votes.delegateBySig(delegatee, nonce, MAX_UINT256, v, r, s); + + await expectRevertCustomError( + this.votes.delegateBySig(delegatee, nonce, MAX_UINT256, v, r, s), + 'InvalidAccountNonce', + [delegator.address, nonce + 1], + ); + }); + + it('rejects bad delegatee', async function () { + const { v, r, s } = await buildAndSignDelegation( + this.votes, + { + delegatee, + nonce, + expiry: MAX_UINT256, + }, + delegator.getPrivateKey(), + ); + + 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); + }); + + it('rejects bad nonce', async function () { + const { v, r, s } = await buildAndSignDelegation( + this.votes, + { + delegatee, + nonce: nonce + 1, + expiry: MAX_UINT256, + }, + delegator.getPrivateKey(), + ); + + await expectRevertCustomError( + this.votes.delegateBySig(delegatee, nonce + 1, MAX_UINT256, v, r, s), + 'InvalidAccountNonce', + [delegator.address, 0], + ); + }); + + it('rejects expired permit', async function () { + const expiry = (await time.latest()) - time.duration.weeks(1); + const { v, r, s } = await buildAndSignDelegation( + this.votes, + { + delegatee, + nonce, + expiry, + }, + delegator.getPrivateKey(), + ); + + await expectRevertCustomError( + this.votes.delegateBySig(delegatee, nonce, expiry, v, r, s), + 'VotesExpiredSignature', + [expiry], + ); + }); + }); + }); + + describe('getPastTotalSupply', function () { + beforeEach(async function () { + await this.votes.delegate(accounts[1], { from: accounts[1] }); + }); + + it('reverts if block number >= current block', async function () { + 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 () { + expect(await this.votes.getPastTotalSupply(0)).to.be.bignumber.equal('0'); + }); + + it('returns the correct checkpointed total supply', async function () { + const weight = tokens.map(token => getWeight(token)); + + // t0 = mint #0 + const t0 = await this.votes.$_mint(accounts[1], tokens[0]); + await time.advanceBlock(); + // t1 = mint #1 + const t1 = await this.votes.$_mint(accounts[1], tokens[1]); + await time.advanceBlock(); + // t2 = burn #1 + const t2 = await this.votes.$_burn(...(fungible ? [accounts[1]] : []), tokens[1]); + await time.advanceBlock(); + // t3 = mint #2 + const t3 = await this.votes.$_mint(accounts[1], tokens[2]); + await time.advanceBlock(); + // t4 = burn #0 + const t4 = await this.votes.$_burn(...(fungible ? [accounts[1]] : []), tokens[0]); + await time.advanceBlock(); + // t5 = burn #2 + const t5 = await this.votes.$_burn(...(fungible ? [accounts[1]] : []), tokens[2]); + await time.advanceBlock(); + + t0.timepoint = await clockFromReceipt[mode](t0.receipt); + 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); + t5.timepoint = await clockFromReceipt[mode](t5.receipt); + + expect(await this.votes.getPastTotalSupply(t0.timepoint - 1)).to.be.bignumber.equal('0'); + expect(await this.votes.getPastTotalSupply(t0.timepoint)).to.be.bignumber.equal(weight[0]); + expect(await this.votes.getPastTotalSupply(t0.timepoint + 1)).to.be.bignumber.equal(weight[0]); + expect(await this.votes.getPastTotalSupply(t1.timepoint)).to.be.bignumber.equal(weight[0].add(weight[1])); + expect(await this.votes.getPastTotalSupply(t1.timepoint + 1)).to.be.bignumber.equal(weight[0].add(weight[1])); + expect(await this.votes.getPastTotalSupply(t2.timepoint)).to.be.bignumber.equal(weight[0]); + expect(await this.votes.getPastTotalSupply(t2.timepoint + 1)).to.be.bignumber.equal(weight[0]); + expect(await this.votes.getPastTotalSupply(t3.timepoint)).to.be.bignumber.equal(weight[0].add(weight[2])); + expect(await this.votes.getPastTotalSupply(t3.timepoint + 1)).to.be.bignumber.equal(weight[0].add(weight[2])); + 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 expectRevertCustomError(this.votes.getPastTotalSupply(t5.timepoint + 1), 'ERC5805FutureLookup', [ + t5.timepoint + 1, // timepoint + t5.timepoint + 1, // clock + ]); + }); + }); + + // 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(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 () { + 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 () { + 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 { receipt } = await this.votes.delegate(accounts[2], { from: accounts[1] }); + const timepoint = await clockFromReceipt[mode](receipt); + await time.advanceBlock(); + await time.advanceBlock(); + + const latest = await this.votes.getVotes(accounts[2]); + expect(await this.votes.getPastVotes(accounts[2], timepoint)).to.be.bignumber.equal(latest); + expect(await this.votes.getPastVotes(accounts[2], timepoint + 1)).to.be.bignumber.equal(latest); + }); + + it('returns zero if < first checkpoint block', async function () { + await time.advanceBlock(); + const { receipt } = await this.votes.delegate(accounts[2], { from: accounts[1] }); + const timepoint = await clockFromReceipt[mode](receipt); + await time.advanceBlock(); + await time.advanceBlock(); + + expect(await this.votes.getPastVotes(accounts[2], timepoint - 1)).to.be.bignumber.equal('0'); + }); + }); + }); + }); +} + +module.exports = { + shouldBehaveLikeVotes, +}; diff --git a/test/governance/utils/Votes.test.js b/test/governance/utils/Votes.test.js new file mode 100644 index 000000000..b2b80f9fe --- /dev/null +++ b/test/governance/utils/Votes.test.js @@ -0,0 +1,92 @@ +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'); + +const { shouldBehaveLikeVotes } = require('./Votes.behavior'); + +const MODES = { + blocknumber: artifacts.require('$VotesMock'), + timestamp: artifacts.require('$VotesTimestampMock'), +}; + +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'), + }; + + const name = 'My Vote'; + const version = '1'; + + for (const [mode, artifact] of Object.entries(MODES)) { + describe(`vote with ${mode}`, function () { + beforeEach(async function () { + this.votes = await artifact.new(name, version); + }); + + shouldBehaveLikeVotes(accounts, Object.values(amounts), { mode, fungible: true }); + + it('starts with zero votes', async function () { + expect(await this.votes.getTotalSupply()).to.be.bignumber.equal('0'); + }); + + describe('performs voting operations', function () { + beforeEach(async function () { + 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 () { + const lastTxTimepoint = await clockFromReceipt[mode](this.txs.at(-1).receipt); + const clock = await this.votes.clock(); + await expectRevertCustomError(this.votes.getPastTotalSupply(lastTxTimepoint + 1), 'ERC5805FutureLookup', [ + lastTxTimepoint + 1, + clock, + ]); + }); + + it('delegates', async function () { + 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.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 () { + const totalSupply = BNsum(...Object.values(amounts)); + expect(await this.votes.getTotalSupply()).to.be.bignumber.equal(totalSupply); + }); + }); + }); + } +}); 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/chainid.js b/test/helpers/chainid.js new file mode 100644 index 000000000..58757eb80 --- /dev/null +++ b/test/helpers/chainid.js @@ -0,0 +1,10 @@ +const hre = require('hardhat'); + +async function getChainId() { + const chainIdHex = await hre.network.provider.send('eth_chainId', []); + return new hre.web3.utils.BN(chainIdHex, 'hex'); +} + +module.exports = { + getChainId, +}; diff --git a/test/helpers/constants.js b/test/helpers/constants.js new file mode 100644 index 000000000..0f4d028cf --- /dev/null +++ b/test/helpers/constants.js @@ -0,0 +1,7 @@ +const MAX_UINT48 = web3.utils.toBN(1).shln(48).subn(1).toString(); +const MAX_UINT64 = web3.utils.toBN(1).shln(64).subn(1).toString(); + +module.exports = { + MAX_UINT48, + MAX_UINT64, +}; diff --git a/test/helpers/create.js b/test/helpers/create.js new file mode 100644 index 000000000..98a0d4c47 --- /dev/null +++ b/test/helpers/create.js @@ -0,0 +1,22 @@ +const RLP = require('rlp'); + +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/customError.js b/test/helpers/customError.js new file mode 100644 index 000000000..ea5c36820 --- /dev/null +++ b/test/helpers/customError.js @@ -0,0 +1,43 @@ +const { expect } = require('chai'); + +/** Revert handler that supports custom errors. */ +async function expectRevertCustomError(promise, expectedErrorName, args) { + 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)' + + // Attempt to parse as a custom error + const match = message.match(/custom error '(?\w+)\((?.*)\)'/); + if (!match) { + expect.fail(`Could not parse as custom error. ${message}`); + } + // 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 = { + expectRevertCustomError, +}; diff --git a/test/helpers/eip712.js b/test/helpers/eip712.js new file mode 100644 index 000000000..b12a6233e --- /dev/null +++ b/test/helpers/eip712.js @@ -0,0 +1,67 @@ +const ethSigUtil = require('eth-sig-util'); +const keccak256 = require('keccak256'); + +const EIP712Domain = [ + { name: 'name', type: 'string' }, + { name: 'version', type: 'string' }, + { name: 'chainId', type: 'uint256' }, + { name: 'verifyingContract', type: 'address' }, + { name: 'salt', type: 'bytes32' }, +]; + +const Permit = [ + { name: 'owner', type: 'address' }, + { name: 'spender', type: 'address' }, + { name: 'value', type: 'uint256' }, + { name: 'nonce', type: 'uint256' }, + { name: 'deadline', type: 'uint256' }, +]; + +function bufferToHexString(buffer) { + return '0x' + buffer.toString('hex'); +} + +function hexStringToBuffer(hexstr) { + return Buffer.from(hexstr.replace(/^0x/, ''), 'hex'); +} + +async function getDomain(contract) { + const { fields, name, version, chainId, verifyingContract, salt, extensions } = await contract.eip712Domain(); + + if (extensions.length > 0) { + throw Error('Extensions not implemented'); + } + + const domain = { name, version, chainId, verifyingContract, salt }; + for (const [i, { name }] of EIP712Domain.entries()) { + if (!(fields & (1 << i))) { + delete domain[name]; + } + } + + return domain; +} + +function domainType(domain) { + return EIP712Domain.filter(({ name }) => domain[name] !== undefined); +} + +function domainSeparator(domain) { + return bufferToHexString( + ethSigUtil.TypedDataUtils.hashStruct('EIP712Domain', domain, { EIP712Domain: domainType(domain) }), + ); +} + +function hashTypedData(domain, structHash) { + return bufferToHexString( + keccak256(Buffer.concat(['0x1901', domainSeparator(domain), structHash].map(str => hexStringToBuffer(str)))), + ); +} + +module.exports = { + Permit, + getDomain, + domainType, + domainSeparator, + hashTypedData, +}; diff --git a/test/helpers/enums.js b/test/helpers/enums.js new file mode 100644 index 000000000..6280e0f31 --- /dev/null +++ b/test/helpers/enums.js @@ -0,0 +1,11 @@ +function Enum(...options) { + return Object.fromEntries(options.map((key, i) => [key, web3.utils.toBN(i)])); +} + +module.exports = { + Enum, + ProposalState: Enum('Pending', 'Active', 'Canceled', 'Defeated', 'Succeeded', 'Queued', 'Expired', 'Executed'), + VoteType: Enum('Against', 'For', 'Abstain'), + Rounding: Enum('Floor', 'Ceil', 'Trunc', 'Expand'), + OperationState: Enum('Unset', 'Waiting', 'Ready', 'Done'), +}; diff --git a/test/helpers/erc1967.js b/test/helpers/erc1967.js new file mode 100644 index 000000000..4ad92c55c --- /dev/null +++ b/test/helpers/erc1967.js @@ -0,0 +1,43 @@ +const { getStorageAt, setStorageAt } = require('@nomicfoundation/hardhat-network-helpers'); + +const ImplementationLabel = 'eip1967.proxy.implementation'; +const AdminLabel = 'eip1967.proxy.admin'; +const BeaconLabel = 'eip1967.proxy.beacon'; + +function labelToSlot(label) { + return '0x' + web3.utils.toBN(web3.utils.keccak256(label)).subn(1).toString(16); +} + +function getSlot(address, slot) { + 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.substring(slotValue.length - 40)); +} + +module.exports = { + ImplementationLabel, + AdminLabel, + BeaconLabel, + ImplementationSlot: labelToSlot(ImplementationLabel), + AdminSlot: labelToSlot(AdminLabel), + BeaconSlot: labelToSlot(BeaconLabel), + setSlot, + getSlot, + getAddressInSlot, +}; diff --git a/test/helpers/governance.js b/test/helpers/governance.js new file mode 100644 index 000000000..fc4e30095 --- /dev/null +++ b/test/helpers/governance.js @@ -0,0 +1,253 @@ +const { web3 } = require('hardhat'); +const { forward } = require('../helpers/time'); +const { ProposalState } = require('./enums'); + +function zip(...args) { + return Array(Math.max(...args.map(array => array.length))) + .fill() + .map((_, i) => args.map(array => array[i])); +} + +function concatHex(...args) { + return web3.utils.bytesToHex([].concat(...args.map(h => web3.utils.hexToBytes(h || '0x')))); +} + +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; + this.mode = mode; + } + + delegate(delegation = {}, opts = null) { + return Promise.all([ + delegation.token.delegate(delegation.to, { from: delegation.to }), + delegation.value && delegation.token.transfer(...concatOpts([delegation.to, delegation.value]), opts), + delegation.tokenId && + delegation.token + .ownerOf(delegation.tokenId) + .then(owner => + delegation.token.transferFrom(...concatOpts([owner, delegation.to, delegation.tokenId], opts)), + ), + ]); + } + + propose(opts = null) { + const proposal = this.currentProposal; + + return this.governor.methods[ + proposal.useCompatibilityInterface + ? 'propose(address[],uint256[],string[],bytes[],string)' + : 'propose(address[],uint256[],bytes[],string)' + ](...concatOpts(proposal.fullProposal, opts)); + } + + queue(opts = null) { + const proposal = this.currentProposal; + + return proposal.useCompatibilityInterface + ? this.governor.methods['queue(uint256)'](...concatOpts([proposal.id], opts)) + : this.governor.methods['queue(address[],uint256[],bytes[],bytes32)']( + ...concatOpts(proposal.shortProposal, opts), + ); + } + + execute(opts = null) { + const proposal = this.currentProposal; + + return proposal.useCompatibilityInterface + ? this.governor.methods['execute(uint256)'](...concatOpts([proposal.id], opts)) + : this.governor.methods['execute(address[],uint256[],bytes[],bytes32)']( + ...concatOpts(proposal.shortProposal, opts), + ); + } + + cancel(visibility = 'external', opts = null) { + const proposal = this.currentProposal; + + switch (visibility) { + case 'external': + if (proposal.useCompatibilityInterface) { + return this.governor.methods['cancel(uint256)'](...concatOpts([proposal.id], opts)); + } else { + return this.governor.methods['cancel(address[],uint256[],bytes[],bytes32)']( + ...concatOpts(proposal.shortProposal, opts), + ); + } + case 'internal': + return this.governor.methods['$_cancel(address[],uint256[],bytes[],bytes32)']( + ...concatOpts(proposal.shortProposal, opts), + ); + default: + throw new Error(`unsupported visibility "${visibility}"`); + } + } + + vote(vote = {}, opts = null) { + const proposal = this.currentProposal; + + return vote.signature + ? // if signature, and either params or reason β†’ + vote.params || vote.reason + ? this.sign(vote).then(signature => + this.governor.castVoteWithReasonAndParamsBySig( + ...concatOpts( + [proposal.id, vote.support, vote.voter, vote.reason || '', vote.params || '', signature], + opts, + ), + ), + ) + : this.sign(vote).then(signature => + this.governor.castVoteBySig(...concatOpts([proposal.id, vote.support, vote.voter, signature], opts)), + ) + : vote.params + ? // otherwise if params + this.governor.castVoteWithReasonAndParams( + ...concatOpts([proposal.id, vote.support, vote.reason || '', vote.params], opts), + ) + : vote.reason + ? // otherwise if reason + this.governor.castVoteWithReason(...concatOpts([proposal.id, vote.support, vote.reason], opts)) + : 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); + return forward[this.mode](timepoint.addn(offset)); + } + + async waitForDeadline(offset = 0) { + const proposal = this.currentProposal; + const timepoint = await this.governor.proposalDeadline(proposal.id); + return forward[this.mode](timepoint.addn(offset)); + } + + async waitForEta(offset = 0) { + const proposal = this.currentProposal; + const timestamp = await this.governor.proposalEta(proposal.id); + return forward.timestamp(timestamp.addn(offset)); + } + + /** + * Specify a proposal either as + * 1) an array of objects [{ target, value, data, signature? }] + * 2) an object of arrays { targets: [], values: [], data: [], signatures?: [] } + */ + setProposal(actions, description) { + let targets, values, signatures, data, useCompatibilityInterface; + + if (Array.isArray(actions)) { + useCompatibilityInterface = actions.some(a => 'signature' in a); + targets = actions.map(a => a.target); + values = actions.map(a => a.value || '0'); + signatures = actions.map(a => a.signature || ''); + data = actions.map(a => a.data || '0x'); + } else { + useCompatibilityInterface = Array.isArray(actions.signatures); + ({ targets, values, signatures = [], data } = actions); + } + + const fulldata = zip( + signatures.map(s => s && web3.eth.abi.encodeFunctionSignature(s)), + data, + ).map(hexs => concatHex(...hexs)); + + const descriptionHash = web3.utils.keccak256(description); + + // condensed version for queueing end executing + const shortProposal = [targets, values, fulldata, descriptionHash]; + + // full version for proposing + const fullProposal = [targets, values, ...(useCompatibilityInterface ? [signatures] : []), data, description]; + + // proposal id + const id = web3.utils.toBN( + web3.utils.keccak256( + web3.eth.abi.encodeParameters(['address[]', 'uint256[]', 'bytes[]', 'bytes32'], shortProposal), + ), + ); + + this.currentProposal = { + id, + targets, + values, + signatures, + data, + fulldata, + description, + descriptionHash, + shortProposal, + fullProposal, + useCompatibilityInterface, + }; + + return this.currentProposal; + } +} + +/** + * 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, + timelockSalt, +}; diff --git a/test/helpers/iterate.js b/test/helpers/iterate.js new file mode 100644 index 000000000..7f6e0e678 --- /dev/null +++ b/test/helpers/iterate.js @@ -0,0 +1,16 @@ +// Map values in an object +const mapValues = (obj, fn) => Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, fn(v)])); + +// Array of number or bigint +const max = (...values) => values.slice(1).reduce((x, y) => (x > y ? x : y), values[0]); +const min = (...values) => values.slice(1).reduce((x, y) => (x < y ? x : y), values[0]); + +// Cartesian product of a list of arrays +const product = (...arrays) => arrays.reduce((a, b) => a.flatMap(ai => b.map(bi => [...ai, bi])), [[]]); + +module.exports = { + mapValues, + max, + min, + product, +}; diff --git a/test/helpers/math.js b/test/helpers/math.js new file mode 100644 index 000000000..2bc654c51 --- /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/helpers/methods.js b/test/helpers/methods.js new file mode 100644 index 000000000..cb30d8727 --- /dev/null +++ b/test/helpers/methods.js @@ -0,0 +1,5 @@ +const { soliditySha3 } = require('web3-utils'); + +module.exports = { + selector: signature => soliditySha3(signature).substring(0, 10), +}; diff --git a/test/helpers/sign.js b/test/helpers/sign.js new file mode 100644 index 000000000..d537116bb --- /dev/null +++ b/test/helpers/sign.js @@ -0,0 +1,63 @@ +function toEthSignedMessageHash(messageHex) { + const messageBuffer = Buffer.from(messageHex.substring(2), 'hex'); + const prefix = Buffer.from(`\u0019Ethereum Signed Message:\n${messageBuffer.length}`); + return web3.utils.sha3(Buffer.concat([prefix, messageBuffer])); +} + +/** + * Create a signed data with intended validator according to the version 0 of EIP-191 + * @param validatorAddress The address of the validator + * @param dataHex The data to be concatenated with the prefix and signed + */ +function toDataWithIntendedValidatorHash(validatorAddress, dataHex) { + const validatorBuffer = Buffer.from(web3.utils.hexToBytes(validatorAddress)); + const dataBuffer = Buffer.from(web3.utils.hexToBytes(dataHex)); + const preambleBuffer = Buffer.from('\x19'); + const versionBuffer = Buffer.from('\x00'); + const ethMessage = Buffer.concat([preambleBuffer, versionBuffer, validatorBuffer, dataBuffer]); + + return web3.utils.sha3(ethMessage); +} + +/** + * Create a signer between a contract and a signer for a voucher of method, args, and redeemer + * Note that `method` is the web3 method, not the truffle-contract method + * @param contract TruffleContract + * @param signer address + * @param redeemer address + * @param methodName string + * @param methodArgs any[] + */ +const getSignFor = + (contract, signer) => + (redeemer, methodName, methodArgs = []) => { + const parts = [contract.address, redeemer]; + + const REAL_SIGNATURE_SIZE = 2 * 65; // 65 bytes in hexadecimal string length + const PADDED_SIGNATURE_SIZE = 2 * 96; // 96 bytes in hexadecimal string length + const DUMMY_SIGNATURE = `0x${web3.utils.padLeft('', REAL_SIGNATURE_SIZE)}`; + + // if we have a method, add it to the parts that we're signing + if (methodName) { + if (methodArgs.length > 0) { + parts.push( + contract.contract.methods[methodName](...methodArgs.concat([DUMMY_SIGNATURE])) + .encodeABI() + .slice(0, -1 * PADDED_SIGNATURE_SIZE), + ); + } else { + const abi = contract.abi.find(abi => abi.name === methodName); + parts.push(abi.signature); + } + } + + // return the signature of the "Ethereum Signed Message" hash of the hash of `parts` + const messageHex = web3.utils.soliditySha3(...parts); + return web3.eth.sign(messageHex, signer); + }; + +module.exports = { + toEthSignedMessageHash, + toDataWithIntendedValidatorHash, + getSignFor, +}; diff --git a/test/helpers/time.js b/test/helpers/time.js new file mode 100644 index 000000000..30df8dc32 --- /dev/null +++ b/test/helpers/time.js @@ -0,0 +1,17 @@ +const ozHelpers = require('@openzeppelin/test-helpers'); +const helpers = require('@nomicfoundation/hardhat-network-helpers'); + +module.exports = { + clock: { + blocknumber: () => helpers.time.latestBlock(), + timestamp: () => helpers.time.latest(), + }, + clockFromReceipt: { + blocknumber: receipt => Promise.resolve(receipt.blockNumber), + timestamp: receipt => web3.eth.getBlock(receipt.blockNumber).then(block => block.timestamp), + }, + forward: { + blocknumber: ozHelpers.time.advanceBlockTo, + timestamp: helpers.time.increaseTo, + }, +}; diff --git a/test/helpers/txpool.js b/test/helpers/txpool.js new file mode 100644 index 000000000..ecdba5462 --- /dev/null +++ b/test/helpers/txpool.js @@ -0,0 +1,38 @@ +const { network } = require('hardhat'); +const { promisify } = require('util'); + +const queue = promisify(setImmediate); + +async function countPendingTransactions() { + return parseInt(await network.provider.send('eth_getBlockTransactionCountByNumber', ['pending'])); +} + +async function batchInBlock(txs) { + try { + // disable auto-mining + await network.provider.send('evm_setAutomine', [false]); + // send all transactions + const promises = txs.map(fn => fn()); + // wait for node to have all pending transactions + while (txs.length > (await countPendingTransactions())) { + await queue(); + } + // mine one block + await network.provider.send('evm_mine'); + // fetch receipts + const receipts = await Promise.all(promises); + // Sanity check, all tx should be in the same block + const minedBlocks = new Set(receipts.map(({ receipt }) => receipt.blockNumber)); + expect(minedBlocks.size).to.equal(1); + + return receipts; + } finally { + // enable auto-mining + await network.provider.send('evm_setAutomine', [true]); + } +} + +module.exports = { + countPendingTransactions, + batchInBlock, +}; diff --git a/test/metatx/ERC2771Context.test.js b/test/metatx/ERC2771Context.test.js new file mode 100644 index 000000000..b0ebccca8 --- /dev/null +++ b/test/metatx/ERC2771Context.test.js @@ -0,0 +1,134 @@ +const ethSigUtil = require('eth-sig-util'); +const Wallet = require('ethereumjs-wallet').default; +const { getDomain, domainType } = require('../helpers/eip712'); +const { MAX_UINT48 } = require('../helpers/constants'); + +const { expectEvent } = require('@openzeppelin/test-helpers'); +const { expect } = require('chai'); + +const ERC2771ContextMock = artifacts.require('ERC2771ContextMock'); +const ERC2771Forwarder = artifacts.require('ERC2771Forwarder'); +const ContextMockCaller = artifacts.require('ContextMockCaller'); + +const { shouldBehaveLikeRegularContext } = require('../utils/Context.behavior'); + +contract('ERC2771Context', function (accounts) { + const [, trustedForwarder] = accounts; + + beforeEach(async function () { + this.forwarder = await ERC2771Forwarder.new('ERC2771Forwarder'); + this.recipient = await ERC2771ContextMock.new(this.forwarder.address); + + 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' }, + ], + }; + }); + + it('recognize trusted forwarder', async function () { + 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 () { + beforeEach(async function () { + this.context = this.recipient; // The Context behavior expects the contract in this.context + this.caller = await ContextMockCaller.new(); + }); + + shouldBehaveLikeRegularContext(...accounts); + }); + + context('when receiving a relayed call', function () { + beforeEach(async function () { + this.wallet = Wallet.generate(); + this.sender = web3.utils.toChecksumAddress(this.wallet.getAddressString()); + this.data = { + types: this.types, + domain: this.domain, + primaryType: 'ForwardRequest', + }; + }); + + describe('msgSender', function () { + it('returns the relayed transaction original sender', async function () { + const data = this.recipient.contract.methods.msgSender().encodeABI(); + + const req = { + from: this.sender, + to: this.recipient.address, + value: '0', + gas: '100000', + nonce: (await this.forwarder.nonces(this.sender)).toString(), + deadline: MAX_UINT48, + data, + }; + + 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); + 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(trustedForwarder); + + const { receipt } = await recipient.msgSender({ from: trustedForwarder }); + + await expectEvent(receipt, 'Sender', { sender: trustedForwarder }); + }); + }); + + describe('msgData', function () { + it('returns the relayed transaction original data', async function () { + const integerValue = '42'; + const stringValue = 'OpenZeppelin'; + const data = this.recipient.contract.methods.msgData(integerValue, stringValue).encodeABI(); + + const req = { + from: this.sender, + to: this.recipient.address, + value: '0', + gas: '100000', + nonce: (await this.forwarder.nonces(this.sender)).toString(), + deadline: MAX_UINT48, + data, + }; + + 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); + 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 }); + }); + }); +}); diff --git a/test/metatx/ERC2771Forwarder.t.sol b/test/metatx/ERC2771Forwarder.t.sol new file mode 100644 index 000000000..d69b4750a --- /dev/null +++ b/test/metatx/ERC2771Forwarder.t.sol @@ -0,0 +1,165 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {Test} from "forge-std/Test.sol"; +import {ERC2771Forwarder} from "@openzeppelin/contracts/metatx/ERC2771Forwarder.sol"; +import {CallReceiverMockTrustingForwarder, CallReceiverMock} from "@openzeppelin/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; + CallReceiverMockTrustingForwarder 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 CallReceiverMockTrustingForwarder(address(_erc2771Forwarder)); + + _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); + } +} diff --git a/test/metatx/ERC2771Forwarder.test.js b/test/metatx/ERC2771Forwarder.test.js new file mode 100644 index 000000000..209c84b2f --- /dev/null +++ b/test/metatx/ERC2771Forwarder.test.js @@ -0,0 +1,542 @@ +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 CallReceiverMockTrustingForwarder = artifacts.require('CallReceiverMockTrustingForwarder'); + +contract('ERC2771Forwarder', function (accounts) { + const [, refundReceiver, another] = accounts; + + const tamperedValues = { + from: 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.receiver = await CallReceiverMockTrustingForwarder.new(this.forwarder.address); + this.request = { + from: this.alice.address, + to: this.receiver.address, + value: '0', + gas: '100000', + data: this.receiver.contract.methods.mockFunction().encodeABI(), + 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.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()); + }); + + 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 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; + 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); + 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, + }); + 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 req = { + ...this.requestData, + data: this.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 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; + 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 () { + 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'); + 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 () { + // 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 () { + 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); + + 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 () { + 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) { + 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, + }); + } + }); + + 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 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; + + 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), + ); + }); + }); + + 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); + }); + }); + }); +}); diff --git a/test/proxy/Clones.behaviour.js b/test/proxy/Clones.behaviour.js new file mode 100644 index 000000000..b5fd3c51b --- /dev/null +++ b/test/proxy/Clones.behaviour.js @@ -0,0 +1,136 @@ +const { expectRevert } = require('@openzeppelin/test-helpers'); + +const { expect } = require('chai'); + +const DummyImplementation = artifacts.require('DummyImplementation'); + +module.exports = function shouldBehaveLikeClone(createClone) { + before('deploy implementation', async function () { + this.implementation = web3.utils.toChecksumAddress((await DummyImplementation.new()).address); + }); + + const assertProxyInitialization = function ({ value, balance }) { + it('initializes the proxy', async function () { + const dummy = new DummyImplementation(this.proxy); + expect(await dummy.value()).to.be.bignumber.equal(value.toString()); + }); + + it('has expected balance', async function () { + expect(await web3.eth.getBalance(this.proxy)).to.be.bignumber.equal(balance.toString()); + }); + }; + + describe('initialization without parameters', function () { + describe('non payable', function () { + const expectedInitializedValue = 10; + const initializeData = new DummyImplementation('').contract.methods['initializeNonPayable()']().encodeABI(); + + describe('when not sending balance', function () { + beforeEach('creating proxy', async function () { + this.proxy = (await createClone(this.implementation, initializeData)).address; + }); + + assertProxyInitialization({ + value: expectedInitializedValue, + balance: 0, + }); + }); + + describe('when sending some balance', function () { + const value = 10e5; + + it('reverts', async function () { + await expectRevert.unspecified(createClone(this.implementation, initializeData, { value })); + }); + }); + }); + + describe('payable', function () { + const expectedInitializedValue = 100; + const initializeData = new DummyImplementation('').contract.methods['initializePayable()']().encodeABI(); + + describe('when not sending balance', function () { + beforeEach('creating proxy', async function () { + this.proxy = (await createClone(this.implementation, initializeData)).address; + }); + + assertProxyInitialization({ + value: expectedInitializedValue, + balance: 0, + }); + }); + + describe('when sending some balance', function () { + const value = 10e5; + + beforeEach('creating proxy', async function () { + this.proxy = (await createClone(this.implementation, initializeData, { value })).address; + }); + + assertProxyInitialization({ + value: expectedInitializedValue, + balance: value, + }); + }); + }); + }); + + describe('initialization with parameters', function () { + describe('non payable', function () { + const expectedInitializedValue = 10; + const initializeData = new DummyImplementation('').contract.methods + .initializeNonPayableWithValue(expectedInitializedValue) + .encodeABI(); + + describe('when not sending balance', function () { + beforeEach('creating proxy', async function () { + this.proxy = (await createClone(this.implementation, initializeData)).address; + }); + + assertProxyInitialization({ + value: expectedInitializedValue, + balance: 0, + }); + }); + + describe('when sending some balance', function () { + const value = 10e5; + + it('reverts', async function () { + await expectRevert.unspecified(createClone(this.implementation, initializeData, { value })); + }); + }); + }); + + describe('payable', function () { + const expectedInitializedValue = 42; + const initializeData = new DummyImplementation('').contract.methods + .initializePayableWithValue(expectedInitializedValue) + .encodeABI(); + + describe('when not sending balance', function () { + beforeEach('creating proxy', async function () { + this.proxy = (await createClone(this.implementation, initializeData)).address; + }); + + assertProxyInitialization({ + value: expectedInitializedValue, + balance: 0, + }); + }); + + describe('when sending some balance', function () { + const value = 10e5; + + beforeEach('creating proxy', async function () { + this.proxy = (await createClone(this.implementation, initializeData, { value })).address; + }); + + assertProxyInitialization({ + value: expectedInitializedValue, + balance: value, + }); + }); + }); + }); +}; diff --git a/test/proxy/Clones.test.js b/test/proxy/Clones.test.js new file mode 100644 index 000000000..0862778f7 --- /dev/null +++ b/test/proxy/Clones.test.js @@ -0,0 +1,62 @@ +const { expectEvent } = require('@openzeppelin/test-helpers'); +const { expect } = require('chai'); +const { computeCreate2Address } = require('../helpers/create'); +const { expectRevertCustomError } = require('../helpers/customError'); + +const shouldBehaveLikeClone = require('./Clones.behaviour'); + +const Clones = artifacts.require('$Clones'); + +contract('Clones', function (accounts) { + const [deployer] = accounts; + + describe('clone', function () { + shouldBehaveLikeClone(async (implementation, initData, opts = {}) => { + const factory = await Clones.new(); + const receipt = await factory.$clone(implementation); + const address = receipt.logs.find(({ event }) => event === 'return$clone').args.instance; + await web3.eth.sendTransaction({ from: deployer, to: address, value: opts.value, data: initData }); + return { address }; + }); + }); + + describe('cloneDeterministic', function () { + shouldBehaveLikeClone(async (implementation, initData, opts = {}) => { + const salt = web3.utils.randomHex(32); + const factory = await Clones.new(); + const receipt = await factory.$cloneDeterministic(implementation, salt); + const address = receipt.logs.find(({ event }) => event === 'return$cloneDeterministic').args.instance; + await web3.eth.sendTransaction({ from: deployer, to: address, value: opts.value, data: initData }); + return { address }; + }); + + it('address already used', async function () { + const implementation = web3.utils.randomHex(20); + const salt = web3.utils.randomHex(32); + const factory = await Clones.new(); + // deploy once + expectEvent(await factory.$cloneDeterministic(implementation, salt), 'return$cloneDeterministic'); + // deploy twice + await expectRevertCustomError(factory.$cloneDeterministic(implementation, salt), 'ERC1167FailedCreateClone', []); + }); + + it('address prediction', async function () { + const implementation = web3.utils.randomHex(20); + const salt = web3.utils.randomHex(32); + const factory = await Clones.new(); + const predicted = await factory.$predictDeterministicAddress(implementation, salt); + + const creationCode = [ + '0x3d602d80600a3d3981f3363d3d373d3d3d363d73', + implementation.replace(/0x/, '').toLowerCase(), + '5af43d82803e903d91602b57fd5bf3', + ].join(''); + + expect(computeCreate2Address(salt, creationCode, factory.address)).to.be.equal(predicted); + + expectEvent(await factory.$cloneDeterministic(implementation, salt), 'return$cloneDeterministic', { + instance: predicted, + }); + }); + }); +}); diff --git a/test/proxy/ERC1967/ERC1967Proxy.test.js b/test/proxy/ERC1967/ERC1967Proxy.test.js new file mode 100644 index 000000000..81cc43507 --- /dev/null +++ b/test/proxy/ERC1967/ERC1967Proxy.test.js @@ -0,0 +1,12 @@ +const shouldBehaveLikeProxy = require('../Proxy.behaviour'); + +const ERC1967Proxy = artifacts.require('ERC1967Proxy'); + +contract('ERC1967Proxy', function (accounts) { + // `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, accounts); +}); diff --git a/test/proxy/ERC1967/ERC1967Utils.test.js b/test/proxy/ERC1967/ERC1967Utils.test.js new file mode 100644 index 000000000..975b08d81 --- /dev/null +++ b/test/proxy/ERC1967/ERC1967Utils.test.js @@ -0,0 +1,172 @@ +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'); +const UpgradeableBeaconReentrantMock = artifacts.require('UpgradeableBeaconReentrantMock'); + +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'); + }); + }); + + 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/Proxy.behaviour.js b/test/proxy/Proxy.behaviour.js new file mode 100644 index 000000000..acce6d188 --- /dev/null +++ b/test/proxy/Proxy.behaviour.js @@ -0,0 +1,182 @@ +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, accounts) { + it('cannot be initialized with a non-contract address', async function () { + const nonContractAddress = accounts[0]; + const initializeData = Buffer.from(''); + await expectRevert.unspecified(createProxy(nonContractAddress, initializeData)); + }); + + before('deploy implementation', async function () { + this.implementation = web3.utils.toChecksumAddress((await DummyImplementation.new()).address); + }); + + const assertProxyInitialization = function ({ value, balance }) { + it('sets the implementation address', async function () { + const implementationSlot = await getSlot(this.proxy, ImplementationSlot); + const implementationAddress = web3.utils.toChecksumAddress(implementationSlot.substr(-40)); + expect(implementationAddress).to.be.equal(this.implementation); + }); + + it('initializes the proxy', async function () { + const dummy = new DummyImplementation(this.proxy); + expect(await dummy.value()).to.be.bignumber.equal(value.toString()); + }); + + it('has expected balance', async function () { + expect(await web3.eth.getBalance(this.proxy)).to.be.bignumber.equal(balance.toString()); + }); + }; + + describe('without initialization', function () { + const initializeData = Buffer.from(''); + + describe('when not sending balance', function () { + beforeEach('creating proxy', async function () { + this.proxy = (await createProxy(this.implementation, initializeData)).address; + }); + + assertProxyInitialization({ value: 0, balance: 0 }); + }); + + describe('when sending some balance', function () { + const value = 10e5; + + it('reverts', async function () { + await expectRevertCustomError( + createProxy(this.implementation, initializeData, { value }), + 'ERC1967NonPayable', + [], + ); + }); + }); + }); + + describe('initialization without parameters', function () { + describe('non payable', function () { + const expectedInitializedValue = 10; + const initializeData = new DummyImplementation('').contract.methods['initializeNonPayable()']().encodeABI(); + + describe('when not sending balance', function () { + beforeEach('creating proxy', async function () { + this.proxy = (await createProxy(this.implementation, initializeData)).address; + }); + + assertProxyInitialization({ + value: expectedInitializedValue, + balance: 0, + }); + }); + + describe('when sending some balance', function () { + const value = 10e5; + + it('reverts', async function () { + await expectRevert.unspecified(createProxy(this.implementation, initializeData, { value })); + }); + }); + }); + + describe('payable', function () { + const expectedInitializedValue = 100; + const initializeData = new DummyImplementation('').contract.methods['initializePayable()']().encodeABI(); + + describe('when not sending balance', function () { + beforeEach('creating proxy', async function () { + this.proxy = (await createProxy(this.implementation, initializeData)).address; + }); + + assertProxyInitialization({ + value: expectedInitializedValue, + balance: 0, + }); + }); + + describe('when sending some balance', function () { + const value = 10e5; + + beforeEach('creating proxy', async function () { + this.proxy = (await createProxy(this.implementation, initializeData, { value })).address; + }); + + assertProxyInitialization({ + value: expectedInitializedValue, + balance: value, + }); + }); + }); + }); + + describe('initialization with parameters', function () { + describe('non payable', function () { + const expectedInitializedValue = 10; + const initializeData = new DummyImplementation('').contract.methods + .initializeNonPayableWithValue(expectedInitializedValue) + .encodeABI(); + + describe('when not sending balance', function () { + beforeEach('creating proxy', async function () { + this.proxy = (await createProxy(this.implementation, initializeData)).address; + }); + + assertProxyInitialization({ + value: expectedInitializedValue, + balance: 0, + }); + }); + + describe('when sending some balance', function () { + const value = 10e5; + + it('reverts', async function () { + await expectRevert.unspecified(createProxy(this.implementation, initializeData, { value })); + }); + }); + }); + + describe('payable', function () { + const expectedInitializedValue = 42; + const initializeData = new DummyImplementation('').contract.methods + .initializePayableWithValue(expectedInitializedValue) + .encodeABI(); + + describe('when not sending balance', function () { + beforeEach('creating proxy', async function () { + this.proxy = (await createProxy(this.implementation, initializeData)).address; + }); + + assertProxyInitialization({ + value: expectedInitializedValue, + balance: 0, + }); + }); + + describe('when sending some balance', function () { + const value = 10e5; + + beforeEach('creating proxy', async function () { + this.proxy = (await createProxy(this.implementation, initializeData, { value })).address; + }); + + assertProxyInitialization({ + value: expectedInitializedValue, + balance: value, + }); + }); + }); + + describe('reverting initialization', function () { + const initializeData = new DummyImplementation('').contract.methods.reverts().encodeABI(); + + it('reverts', async function () { + await expectRevert(createProxy(this.implementation, initializeData), 'DummyImplementation reverted'); + }); + }); + }); +}; diff --git a/test/proxy/beacon/BeaconProxy.test.js b/test/proxy/beacon/BeaconProxy.test.js new file mode 100644 index 000000000..d583d0ffb --- /dev/null +++ b/test/proxy/beacon/BeaconProxy.test.js @@ -0,0 +1,152 @@ +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'); +const BeaconProxy = artifacts.require('BeaconProxy'); +const DummyImplementation = artifacts.require('DummyImplementation'); +const DummyImplementationV2 = artifacts.require('DummyImplementationV2'); +const BadBeaconNoImpl = artifacts.require('BadBeaconNoImpl'); +const BadBeaconNotContract = artifacts.require('BadBeaconNotContract'); + +contract('BeaconProxy', function (accounts) { + const [upgradeableBeaconAdmin, anotherAccount] = accounts; + + describe('bad beacon is not accepted', async function () { + it('non-contract beacon', async function () { + await expectRevertCustomError(BeaconProxy.new(anotherAccount, '0x'), 'ERC1967InvalidBeacon', [anotherAccount]); + }); + + it('non-compliant beacon', async function () { + const beacon = await BadBeaconNoImpl.new(); + await expectRevert.unspecified(BeaconProxy.new(beacon.address, '0x')); + }); + + it('non-contract implementation', async function () { + const beacon = await BadBeaconNotContract.new(); + const implementation = await beacon.implementation(); + await expectRevertCustomError(BeaconProxy.new(beacon.address, '0x'), 'ERC1967InvalidImplementation', [ + implementation, + ]); + }); + }); + + before('deploy implementation', async function () { + this.implementationV0 = await DummyImplementation.new(); + this.implementationV1 = await DummyImplementationV2.new(); + }); + + describe('initialization', function () { + before(function () { + this.assertInitialized = async ({ value, balance }) => { + const beaconSlot = await getSlot(this.proxy, BeaconSlot); + const beaconAddress = web3.utils.toChecksumAddress(beaconSlot.substr(-40)); + expect(beaconAddress).to.equal(this.beacon.address); + + const dummy = new DummyImplementation(this.proxy.address); + expect(await dummy.value()).to.bignumber.eq(value); + + expect(await web3.eth.getBalance(this.proxy.address)).to.bignumber.eq(balance); + }; + }); + + beforeEach('deploy beacon', async function () { + this.beacon = await UpgradeableBeacon.new(this.implementationV0.address, upgradeableBeaconAdmin); + }); + + it('no initialization', async function () { + const data = Buffer.from(''); + this.proxy = await BeaconProxy.new(this.beacon.address, data); + await this.assertInitialized({ value: '0', balance: '0' }); + }); + + it('non-payable initialization', async function () { + const value = '55'; + const data = this.implementationV0.contract.methods.initializeNonPayableWithValue(value).encodeABI(); + this.proxy = await BeaconProxy.new(this.beacon.address, data); + await this.assertInitialized({ value, balance: '0' }); + }); + + it('payable initialization', async function () { + const value = '55'; + const data = this.implementationV0.contract.methods.initializePayableWithValue(value).encodeABI(); + const balance = '100'; + this.proxy = await BeaconProxy.new(this.beacon.address, data, { value: balance }); + await this.assertInitialized({ value, balance }); + }); + + 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'); + }); + }); + + it('upgrade a proxy by upgrading its beacon', async function () { + const beacon = await UpgradeableBeacon.new(this.implementationV0.address, upgradeableBeaconAdmin); + + const value = '10'; + const data = this.implementationV0.contract.methods.initializeNonPayableWithValue(value).encodeABI(); + const proxy = await BeaconProxy.new(beacon.address, data); + + const dummy = new DummyImplementation(proxy.address); + + // test initial values + expect(await dummy.value()).to.bignumber.eq(value); + + // test initial version + expect(await dummy.version()).to.eq('V1'); + + // upgrade beacon + await beacon.upgradeTo(this.implementationV1.address, { from: upgradeableBeaconAdmin }); + + // test upgraded version + expect(await dummy.version()).to.eq('V2'); + }); + + it('upgrade 2 proxies by upgrading shared beacon', async function () { + const value1 = '10'; + const value2 = '42'; + + const beacon = await UpgradeableBeacon.new(this.implementationV0.address, upgradeableBeaconAdmin); + + const proxy1InitializeData = this.implementationV0.contract.methods + .initializeNonPayableWithValue(value1) + .encodeABI(); + const proxy1 = await BeaconProxy.new(beacon.address, proxy1InitializeData); + + const proxy2InitializeData = this.implementationV0.contract.methods + .initializeNonPayableWithValue(value2) + .encodeABI(); + const proxy2 = await BeaconProxy.new(beacon.address, proxy2InitializeData); + + const dummy1 = new DummyImplementation(proxy1.address); + const dummy2 = new DummyImplementation(proxy2.address); + + // test initial values + expect(await dummy1.value()).to.bignumber.eq(value1); + expect(await dummy2.value()).to.bignumber.eq(value2); + + // test initial version + expect(await dummy1.version()).to.eq('V1'); + expect(await dummy2.version()).to.eq('V1'); + + // upgrade beacon + await beacon.upgradeTo(this.implementationV1.address, { from: upgradeableBeaconAdmin }); + + // test upgraded version + expect(await dummy1.version()).to.eq('V2'); + expect(await dummy2.version()).to.eq('V2'); + }); +}); diff --git a/test/proxy/beacon/UpgradeableBeacon.test.js b/test/proxy/beacon/UpgradeableBeacon.test.js new file mode 100644 index 000000000..0737f6fdf --- /dev/null +++ b/test/proxy/beacon/UpgradeableBeacon.test.js @@ -0,0 +1,54 @@ +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'); + +contract('UpgradeableBeacon', function (accounts) { + const [owner, other] = accounts; + + it('cannot be created with non-contract implementation', async function () { + await expectRevertCustomError(UpgradeableBeacon.new(other, owner), 'BeaconInvalidImplementation', [other]); + }); + + context('once deployed', async function () { + beforeEach('deploying beacon', async function () { + this.v1 = await Implementation1.new(); + 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); + }); + + it('can be upgraded by the owner', async function () { + const v2 = await Implementation2.new(); + const receipt = await this.beacon.upgradeTo(v2.address, { from: owner }); + expectEvent(receipt, 'Upgraded', { implementation: v2.address }); + expect(await this.beacon.implementation()).to.equal(v2.address); + }); + + it('cannot be upgraded to a non-contract', async function () { + 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 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 new file mode 100644 index 000000000..4d1a54f6a --- /dev/null +++ b/test/proxy/transparent/ProxyAdmin.test.js @@ -0,0 +1,103 @@ +const { expectRevert } = require('@openzeppelin/test-helpers'); +const { expect } = require('chai'); +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'); + +const { getAddressInSlot, ImplementationSlot } = require('../../helpers/erc1967'); +const { expectRevertCustomError } = require('../../helpers/customError'); +const { computeCreateAddress } = require('../../helpers/create'); + +contract('ProxyAdmin', function (accounts) { + const [proxyAdminOwner, anotherAccount] = accounts; + + before('set implementations', async function () { + this.implementationV1 = await ImplV1.new(); + this.implementationV2 = await ImplV2.new(); + }); + + beforeEach(async function () { + const initializeData = Buffer.from(''); + 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); + }); + + it('has an owner', async function () { + expect(await this.proxyAdmin.owner()).to.equal(proxyAdminOwner); + }); + + 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.upgradeAndCall(this.proxy.address, this.implementationV2.address, '0x', { + from: anotherAccount, + }), + 'OwnableUnauthorizedAccount', + [anotherAccount], + ); + }); + }); + + context('with authorized account', function () { + it('upgrades implementation', async function () { + 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); + }); + }); + }); + + describe('with data', function () { + context('with unauthorized account', function () { + it('fails to upgrade', async function () { + const callData = new ImplV1('').contract.methods.initializeNonPayableWithValue(1337).encodeABI(); + await expectRevertCustomError( + this.proxyAdmin.upgradeAndCall(this.proxy.address, this.implementationV2.address, callData, { + from: anotherAccount, + }), + 'OwnableUnauthorizedAccount', + [anotherAccount], + ); + }); + }); + + context('with authorized account', function () { + context('with invalid callData', function () { + it('fails to upgrade', async function () { + const callData = '0x12345678'; + await expectRevert.unspecified( + this.proxyAdmin.upgradeAndCall(this.proxy.address, this.implementationV2.address, callData, { + from: proxyAdminOwner, + }), + ); + }); + }); + + context('with valid callData', function () { + it('upgrades implementation', async function () { + const callData = new ImplV1('').contract.methods.initializeNonPayableWithValue(1337).encodeABI(); + await this.proxyAdmin.upgradeAndCall(this.proxy.address, this.implementationV2.address, callData, { + from: proxyAdminOwner, + }); + const implementationAddress = await getAddressInSlot(this.proxy, ImplementationSlot); + expect(implementationAddress).to.be.equal(this.implementationV2.address); + }); + }); + }); + }); +}); diff --git a/test/proxy/transparent/TransparentUpgradeableProxy.behaviour.js b/test/proxy/transparent/TransparentUpgradeableProxy.behaviour.js new file mode 100644 index 000000000..103af7fc3 --- /dev/null +++ b/test/proxy/transparent/TransparentUpgradeableProxy.behaviour.js @@ -0,0 +1,413 @@ +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'); +const { computeCreateAddress } = require('../../helpers/create'); +const { impersonate } = require('../../helpers/account'); + +const Implementation1 = artifacts.require('Implementation1'); +const Implementation2 = artifacts.require('Implementation2'); +const Implementation3 = artifacts.require('Implementation3'); +const Implementation4 = artifacts.require('Implementation4'); +const MigratableMockV1 = artifacts.require('MigratableMockV1'); +const MigratableMockV2 = artifacts.require('MigratableMockV2'); +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, 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; + this.implementationV1 = (await DummyImplementation.new()).address; + }); + + beforeEach(async function () { + const initializeData = Buffer.from(''); + const { proxy, proxyAdminAddress } = await createProxyWithImpersonatedProxyAdmin( + this.implementationV0, + initializeData, + ); + this.proxy = proxy; + this.proxyAdminAddress = proxyAdminAddress; + }); + + describe('implementation', function () { + it('returns the current implementation address', async function () { + const implementationAddress = await getAddressInSlot(this.proxy, ImplementationSlot); + expect(implementationAddress).to.be.equal(this.implementationV0); + }); + + it('delegates to the implementation', async function () { + const dummy = new DummyImplementation(this.proxy.address); + const value = await dummy.get(); + + expect(value).to.equal(true); + }); + }); + + describe('proxy admin', function () { + it('emits AdminChanged event during construction', async function () { + await expectEvent.inConstruction(this.proxy, 'AdminChanged', { + previousAdmin: ZERO_ADDRESS, + newAdmin: this.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.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(this.proxyAdminAddress); + expectEvent( + await this.proxy.upgradeToAndCall(this.implementationV1, '0x', { from: this.proxyAdminAddress }), + 'Upgraded', + { + implementation: this.implementationV1, + }, + ); + }); + }); + + describe('upgradeToAndCall', function () { + describe('without migrations', function () { + beforeEach(async function () { + this.behavior = await InitializableMock.new(); + }); + + describe('when the call does not fail', function () { + const initializeData = new InitializableMock('').contract.methods['initializeWithX(uint256)'](42).encodeABI(); + + describe('when the sender is the admin', function () { + const value = 1e5; + + beforeEach(async function () { + this.receipt = await this.proxy.upgradeToAndCall(this.behavior.address, initializeData, { + from: this.proxyAdminAddress, + value, + }); + }); + + it('upgrades to the requested implementation', async function () { + const implementationAddress = await getAddressInSlot(this.proxy, ImplementationSlot); + expect(implementationAddress).to.be.equal(this.behavior.address); + }); + + it('emits an event', function () { + expectEvent(this.receipt, 'Upgraded', { implementation: this.behavior.address }); + }); + + it('calls the initializer function', async function () { + 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.proxy.address); + expect(balance.toString()).to.be.bignumber.equal(value.toString()); + }); + + it('uses the storage of the proxy', async function () { + // storage layout should look as follows: + // - 0: Initializable storage ++ initializerRan ++ onlyInitializingRan + // - 1: x + const storedValue = await web3.eth.getStorageAt(this.proxy.address, 1); + expect(parseInt(storedValue)).to.eq(42); + }); + }); + + describe('when the sender is not the admin', function () { + it('reverts', async function () { + await expectRevert.unspecified( + this.proxy.upgradeToAndCall(this.behavior.address, initializeData, { from: anotherAccount }), + ); + }); + }); + }); + + describe('when the call does fail', function () { + const initializeData = new InitializableMock('').contract.methods.fail().encodeABI(); + + it('reverts', async function () { + await expectRevert.unspecified( + this.proxy.upgradeToAndCall(this.behavior.address, initializeData, { from: this.proxyAdminAddress }), + ); + }); + }); + }); + + describe('with migrations', function () { + describe('when the sender is the admin', function () { + const value = 1e5; + + describe('when upgrading to V1', function () { + const v1MigrationData = new MigratableMockV1('').contract.methods.initialize(42).encodeABI(); + + beforeEach(async function () { + this.behaviorV1 = await MigratableMockV1.new(); + 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 () { + const implementation = await getAddressInSlot(this.proxy, ImplementationSlot); + expect(implementation).to.be.equal(this.behaviorV1.address); + expectEvent(this.receipt, 'Upgraded', { implementation: this.behaviorV1.address }); + }); + + it("calls the 'initialize' function and sends given value to the proxy", async function () { + 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.proxy.address); + expect(new BN(balance)).to.be.bignumber.equal(this.balancePreviousV1.addn(value)); + }); + + describe('when upgrading to V2', function () { + const v2MigrationData = new MigratableMockV2('').contract.methods.migrate(10, 42).encodeABI(); + + beforeEach(async function () { + this.behaviorV2 = await MigratableMockV2.new(); + this.balancePreviousV2 = new BN(await web3.eth.getBalance(this.proxy.address)); + this.receipt = await this.proxy.upgradeToAndCall(this.behaviorV2.address, v2MigrationData, { + from: this.proxyAdminAddress, + value, + }); + }); + + it('upgrades to the requested version and emits an event', async function () { + const implementation = await getAddressInSlot(this.proxy, ImplementationSlot); + expect(implementation).to.be.equal(this.behaviorV2.address); + expectEvent(this.receipt, 'Upgraded', { implementation: this.behaviorV2.address }); + }); + + it("calls the 'migrate' function and sends given value to the proxy", async function () { + const migratable = new MigratableMockV2(this.proxy.address); + + const x = await migratable.x(); + expect(x).to.be.bignumber.equal('10'); + + const y = await migratable.y(); + expect(y).to.be.bignumber.equal('42'); + + const balance = new BN(await web3.eth.getBalance(this.proxy.address)); + expect(balance).to.be.bignumber.equal(this.balancePreviousV2.addn(value)); + }); + + describe('when upgrading to V3', function () { + const v3MigrationData = new MigratableMockV3('').contract.methods['migrate()']().encodeABI(); + + beforeEach(async function () { + this.behaviorV3 = await MigratableMockV3.new(); + this.balancePreviousV3 = new BN(await web3.eth.getBalance(this.proxy.address)); + this.receipt = await this.proxy.upgradeToAndCall(this.behaviorV3.address, v3MigrationData, { + from: this.proxyAdminAddress, + value, + }); + }); + + it('upgrades to the requested version and emits an event', async function () { + const implementation = await getAddressInSlot(this.proxy, ImplementationSlot); + expect(implementation).to.be.equal(this.behaviorV3.address); + expectEvent(this.receipt, 'Upgraded', { implementation: this.behaviorV3.address }); + }); + + it("calls the 'migrate' function and sends given value to the proxy", async function () { + const migratable = new MigratableMockV3(this.proxy.address); + + const x = await migratable.x(); + expect(x).to.be.bignumber.equal('42'); + + const y = await migratable.y(); + expect(y).to.be.bignumber.equal('10'); + + const balance = new BN(await web3.eth.getBalance(this.proxy.address)); + expect(balance).to.be.bignumber.equal(this.balancePreviousV3.addn(value)); + }); + }); + }); + }); + }); + + describe('when the sender is not the admin', function () { + const from = anotherAccount; + + it('reverts', async function () { + const behaviorV1 = await MigratableMockV1.new(); + const v1MigrationData = new MigratableMockV1('').contract.methods.initialize(42).encodeABI(); + await expectRevert.unspecified(this.proxy.upgradeToAndCall(behaviorV1.address, v1MigrationData, { from })); + }); + }); + }); + }); + + describe('transparent proxy', function () { + beforeEach('creating proxy', async function () { + const initializeData = Buffer.from(''); + this.clashingImplV0 = (await ClashingImplementation.new()).address; + this.clashingImplV1 = (await ClashingImplementation.new()).address; + 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: this.proxyAdminAddress }), + 'ProxyDeniedAdminAccess', + [], + ); + }); + + describe('when function names clash', function () { + it('executes the proxy function if the sender is the admin', async function () { + 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.upgradeToAndCall(this.clashingImplV1, '0x', { + from: anotherAccount, + }); + expectEvent.notEmitted(receipt, 'Upgraded'); + expectEvent.inTransaction(receipt.tx, this.clashing, 'ClashingImplementationCall'); + }); + }); + }); + + describe('regression', () => { + const initializeData = Buffer.from(''); + + it('should add new function', async () => { + const instance1 = await Implementation1.new(); + 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.upgradeToAndCall(instance2.address, '0x', { from: proxyAdminAddress }); + + const proxyInstance2 = new Implementation2(proxy.address); + const res = await proxyInstance2.getValue(); + expect(res.toString()).to.eq('42'); + }); + + it('should remove function', async () => { + const instance2 = await Implementation2.new(); + const { proxy, proxyAdminAddress } = await createProxyWithImpersonatedProxyAdmin( + instance2.address, + initializeData, + ); + + const proxyInstance2 = new Implementation2(proxy.address); + await proxyInstance2.setValue(42); + const res = await proxyInstance2.getValue(); + expect(res.toString()).to.eq('42'); + + const instance1 = await Implementation1.new(); + await proxy.upgradeToAndCall(instance1.address, '0x', { from: proxyAdminAddress }); + + const proxyInstance1 = new Implementation2(proxy.address); + await expectRevert.unspecified(proxyInstance1.getValue()); + }); + + it('should change function signature', async () => { + const instance1 = await Implementation1.new(); + 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.upgradeToAndCall(instance3.address, '0x', { from: proxyAdminAddress }); + const proxyInstance3 = new Implementation3(proxy.address); + + const res = await proxyInstance3.getValue(8); + expect(res.toString()).to.eq('50'); + }); + + it('should add fallback function', async () => { + const initializeData = Buffer.from(''); + const instance1 = await Implementation1.new(); + const { proxy, proxyAdminAddress } = await createProxyWithImpersonatedProxyAdmin( + instance1.address, + initializeData, + ); + + const instance4 = await Implementation4.new(); + await proxy.upgradeToAndCall(instance4.address, '0x', { from: proxyAdminAddress }); + const proxyInstance4 = new Implementation4(proxy.address); + + const data = '0x'; + await web3.eth.sendTransaction({ to: proxy.address, from: anotherAccount, data }); + + const res = await proxyInstance4.getValue(); + expect(res.toString()).to.eq('1'); + }); + + it('should remove fallback function', async () => { + const instance4 = await Implementation4.new(); + const { proxy, proxyAdminAddress } = await createProxyWithImpersonatedProxyAdmin( + instance4.address, + initializeData, + ); + + const instance2 = await Implementation2.new(); + await proxy.upgradeToAndCall(instance2.address, '0x', { from: proxyAdminAddress }); + + const data = '0x'; + await expectRevert.unspecified(web3.eth.sendTransaction({ to: proxy.address, from: anotherAccount, data })); + + const proxyInstance2 = new Implementation2(proxy.address); + const res = await proxyInstance2.getValue(); + expect(res.toString()).to.eq('0'); + }); + }); +}; diff --git a/test/proxy/transparent/TransparentUpgradeableProxy.test.js b/test/proxy/transparent/TransparentUpgradeableProxy.test.js new file mode 100644 index 000000000..f45e392f6 --- /dev/null +++ b/test/proxy/transparent/TransparentUpgradeableProxy.test.js @@ -0,0 +1,24 @@ +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 [owner, ...otherAccounts] = accounts; + + // `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, otherAccounts); + shouldBehaveLikeTransparentUpgradeableProxy(createProxy, owner, otherAccounts); +}); diff --git a/test/proxy/utils/Initializable.test.js b/test/proxy/utils/Initializable.test.js new file mode 100644 index 000000000..b9ff3b052 --- /dev/null +++ b/test/proxy/utils/Initializable.test.js @@ -0,0 +1,220 @@ +const { expectEvent } = require('@openzeppelin/test-helpers'); +const { expect } = require('chai'); +const { expectRevertCustomError } = require('../../helpers/customError'); +const { MAX_UINT64 } = require('../../helpers/constants'); + +const InitializableMock = artifacts.require('InitializableMock'); +const ConstructorInitializableMock = artifacts.require('ConstructorInitializableMock'); +const ChildConstructorInitializableMock = artifacts.require('ChildConstructorInitializableMock'); +const ReinitializerMock = artifacts.require('ReinitializerMock'); +const SampleChild = artifacts.require('SampleChild'); +const DisableBad1 = artifacts.require('DisableBad1'); +const DisableBad2 = artifacts.require('DisableBad2'); +const DisableOk = artifacts.require('DisableOk'); + +contract('Initializable', function () { + describe('basic testing without inheritance', function () { + beforeEach('deploying', async function () { + this.contract = await InitializableMock.new(); + }); + + describe('before initialize', function () { + it('initializer has not run', async function () { + expect(await this.contract.initializerRan()).to.equal(false); + }); + + it('_initializing returns false before initialization', async function () { + expect(await this.contract.isInitializing()).to.equal(false); + }); + }); + + describe('after initialize', function () { + beforeEach('initializing', async function () { + await this.contract.initialize(); + }); + + it('initializer has run', async function () { + expect(await this.contract.initializerRan()).to.equal(true); + }); + + it('_initializing returns false after initialization', async function () { + expect(await this.contract.isInitializing()).to.equal(false); + }); + + it('initializer does not run again', async function () { + await expectRevertCustomError(this.contract.initialize(), 'InvalidInitialization', []); + }); + }); + + describe('nested under an initializer', function () { + it('initializer modifier reverts', async function () { + await expectRevertCustomError(this.contract.initializerNested(), 'InvalidInitialization', []); + }); + + it('onlyInitializing modifier succeeds', async function () { + await this.contract.onlyInitializingNested(); + expect(await this.contract.onlyInitializingRan()).to.equal(true); + }); + }); + + it('cannot call onlyInitializable function outside the scope of an initializable function', async function () { + await expectRevertCustomError(this.contract.initializeOnlyInitializing(), 'NotInitializing', []); + }); + }); + + it('nested initializer can run during construction', async function () { + const contract2 = await ConstructorInitializableMock.new(); + expect(await contract2.initializerRan()).to.equal(true); + expect(await contract2.onlyInitializingRan()).to.equal(true); + }); + + it('multiple constructor levels can be initializers', async function () { + const contract2 = await ChildConstructorInitializableMock.new(); + expect(await contract2.initializerRan()).to.equal(true); + expect(await contract2.childInitializerRan()).to.equal(true); + expect(await contract2.onlyInitializingRan()).to.equal(true); + }); + + describe('reinitialization', function () { + beforeEach('deploying', async function () { + this.contract = await ReinitializerMock.new(); + }); + + it('can reinitialize', async function () { + expect(await this.contract.counter()).to.be.bignumber.equal('0'); + await this.contract.initialize(); + expect(await this.contract.counter()).to.be.bignumber.equal('1'); + await this.contract.reinitialize(2); + expect(await this.contract.counter()).to.be.bignumber.equal('2'); + await this.contract.reinitialize(3); + expect(await this.contract.counter()).to.be.bignumber.equal('3'); + }); + + it('can jump multiple steps', async function () { + expect(await this.contract.counter()).to.be.bignumber.equal('0'); + await this.contract.initialize(); + expect(await this.contract.counter()).to.be.bignumber.equal('1'); + await this.contract.reinitialize(128); + expect(await this.contract.counter()).to.be.bignumber.equal('2'); + }); + + it('cannot nest reinitializers', async function () { + expect(await this.contract.counter()).to.be.bignumber.equal('0'); + await expectRevertCustomError(this.contract.nestedReinitialize(2, 2), 'InvalidInitialization', []); + await expectRevertCustomError(this.contract.nestedReinitialize(2, 3), 'InvalidInitialization', []); + await expectRevertCustomError(this.contract.nestedReinitialize(3, 2), 'InvalidInitialization', []); + }); + + it('can chain reinitializers', async function () { + expect(await this.contract.counter()).to.be.bignumber.equal('0'); + await this.contract.chainReinitialize(2, 3); + expect(await this.contract.counter()).to.be.bignumber.equal('2'); + }); + + it('_getInitializedVersion returns right version', async function () { + await this.contract.initialize(); + expect(await this.contract.getInitializedVersion()).to.be.bignumber.equal('1'); + await this.contract.reinitialize(12); + expect(await this.contract.getInitializedVersion()).to.be.bignumber.equal('12'); + }); + + describe('contract locking', function () { + it('prevents initialization', async function () { + await this.contract.disableInitializers(); + await expectRevertCustomError(this.contract.initialize(), 'InvalidInitialization', []); + }); + + it('prevents re-initialization', async function () { + await this.contract.disableInitializers(); + await expectRevertCustomError(this.contract.reinitialize(255), 'InvalidInitialization', []); + }); + + it('can lock contract after initialization', async function () { + await this.contract.initialize(); + await this.contract.disableInitializers(); + await expectRevertCustomError(this.contract.reinitialize(255), 'InvalidInitialization', []); + }); + }); + }); + + describe('events', function () { + it('constructor initialization emits event', async function () { + const contract = await ConstructorInitializableMock.new(); + + await expectEvent.inTransaction(contract.transactionHash, contract, 'Initialized', { version: '1' }); + }); + + it('initialization emits event', async function () { + const contract = await ReinitializerMock.new(); + + const { receipt } = await contract.initialize(); + expect(receipt.logs.filter(({ event }) => event === 'Initialized').length).to.be.equal(1); + expectEvent(receipt, 'Initialized', { version: '1' }); + }); + + it('reinitialization emits event', async function () { + const contract = await ReinitializerMock.new(); + + const { receipt } = await contract.reinitialize(128); + expect(receipt.logs.filter(({ event }) => event === 'Initialized').length).to.be.equal(1); + expectEvent(receipt, 'Initialized', { version: '128' }); + }); + + it('chained reinitialization emits multiple events', async function () { + const contract = await ReinitializerMock.new(); + + const { receipt } = await contract.chainReinitialize(2, 3); + expect(receipt.logs.filter(({ event }) => event === 'Initialized').length).to.be.equal(2); + expectEvent(receipt, 'Initialized', { version: '2' }); + expectEvent(receipt, 'Initialized', { version: '3' }); + }); + }); + + describe('complex testing with inheritance', function () { + const mother = '12'; + const gramps = '56'; + const father = '34'; + const child = '78'; + + beforeEach('deploying', async function () { + this.contract = await SampleChild.new(); + }); + + beforeEach('initializing', async function () { + await this.contract.initialize(mother, gramps, father, child); + }); + + it('initializes human', async function () { + expect(await this.contract.isHuman()).to.be.equal(true); + }); + + it('initializes mother', async function () { + expect(await this.contract.mother()).to.be.bignumber.equal(mother); + }); + + it('initializes gramps', async function () { + expect(await this.contract.gramps()).to.be.bignumber.equal(gramps); + }); + + it('initializes father', async function () { + expect(await this.contract.father()).to.be.bignumber.equal(father); + }); + + it('initializes child', async function () { + expect(await this.contract.child()).to.be.bignumber.equal(child); + }); + }); + + describe('disabling initialization', function () { + it('old and new patterns in bad sequence', async function () { + await expectRevertCustomError(DisableBad1.new(), 'InvalidInitialization', []); + await expectRevertCustomError(DisableBad2.new(), 'InvalidInitialization', []); + }); + + it('old and new patterns in good sequence', async function () { + const ok = await DisableOk.new(); + await expectEvent.inConstruction(ok, 'Initialized', { version: '1' }); + await expectEvent.inConstruction(ok, 'Initialized', { version: MAX_UINT64 }); + }); + }); +}); diff --git a/test/proxy/utils/UUPSUpgradeable.test.js b/test/proxy/utils/UUPSUpgradeable.test.js new file mode 100644 index 000000000..0baa90520 --- /dev/null +++ b/test/proxy/utils/UUPSUpgradeable.test.js @@ -0,0 +1,131 @@ +const { expectEvent } = require('@openzeppelin/test-helpers'); +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 NonUpgradeableMock = artifacts.require('NonUpgradeableMock'); +const UUPSUnsupportedProxiableUUID = artifacts.require('UUPSUnsupportedProxiableUUID'); +const Clones = artifacts.require('$Clones'); + +contract('UUPSUpgradeable', function () { + before(async function () { + this.implInitial = await UUPSUpgradeableMock.new(); + this.implUpgradeOk = await UUPSUpgradeableMock.new(); + this.implUpgradeUnsafe = await UUPSUpgradeableUnsafeMock.new(); + this.implUpgradeNonUUPS = await NonUpgradeableMock.new(); + this.implUnsupportedUUID = await UUPSUnsupportedProxiableUUID.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 () { + const { address } = await ERC1967Proxy.new(this.implInitial.address, '0x'); + 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.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); + }); + + it('upgrade to upgradeable implementation with call', async function () { + expect(await this.instance.current()).to.be.bignumber.equal('0'); + + const { receipt } = await this.instance.upgradeToAndCall( + this.implUpgradeOk.address, + this.implUpgradeOk.contract.methods.increment().encodeABI(), + ); + 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.upgradeToAndCall(this.implUpgradeOk.address, '0x'), + '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 () { + 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( + instance.upgradeToAndCall(this.implUpgradeUnsafe.address, '0x'), + '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( + instance.upgradeToAndCall(this.implUpgradeUnsafe.address, '0x'), + 'UUPSUnauthorizedCallContext', + [], + ); + }); + + it('rejects upgrading to an unsupported UUID', async function () { + await expectRevertCustomError( + 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.upgradeToAndCall(this.implUpgradeUnsafe.address, '0x'); + 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 + it('reject upgrade to non uups implementation', async function () { + await expectRevertCustomError( + this.instance.upgradeToAndCall(this.implUpgradeNonUUPS.address, '0x'), + 'ERC1967InvalidImplementation', + [this.implUpgradeNonUUPS.address], + ); + }); + + it('reject proxy address as implementation', async function () { + const { address } = await ERC1967Proxy.new(this.implInitial.address, '0x'); + const otherInstance = await UUPSUpgradeableMock.at(address); + + await expectRevertCustomError( + this.instance.upgradeToAndCall(otherInstance.address, '0x'), + 'ERC1967InvalidImplementation', + [otherInstance.address], + ); + }); +}); diff --git a/test/token/ERC1155/ERC1155.behavior.js b/test/token/ERC1155/ERC1155.behavior.js new file mode 100644 index 000000000..8df30a814 --- /dev/null +++ b/test/token/ERC1155/ERC1155.behavior.js @@ -0,0 +1,905 @@ +const { BN, constants, expectEvent, expectRevert } = require('@openzeppelin/test-helpers'); +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', 'RevertWithoutMessage', 'RevertWithMessage', 'RevertWithCustomError', 'Panic'); + +function shouldBehaveLikeERC1155([minter, firstTokenHolder, secondTokenHolder, multiTokenHolder, recipient, proxy]) { + const firstTokenId = new BN(1); + const secondTokenId = new BN(2); + const unknownTokenId = new BN(3); + + const firstTokenValue = new BN(1000); + const secondTokenValue = new BN(2000); + + const RECEIVER_SINGLE_MAGIC_VALUE = '0xf23a6e61'; + const RECEIVER_BATCH_MAGIC_VALUE = '0xbc197c81'; + + describe('like an ERC1155', function () { + describe('balanceOf', function () { + 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 () { + it('returns zero for given addresses', async function () { + expect(await this.token.balanceOf(firstTokenHolder, firstTokenId)).to.be.bignumber.equal('0'); + + expect(await this.token.balanceOf(secondTokenHolder, secondTokenId)).to.be.bignumber.equal('0'); + + expect(await this.token.balanceOf(firstTokenHolder, unknownTokenId)).to.be.bignumber.equal('0'); + }); + }); + + context('when accounts own some tokens', function () { + beforeEach(async function () { + await this.token.$_mint(firstTokenHolder, firstTokenId, firstTokenValue, '0x', { + from: minter, + }); + 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(firstTokenValue); + + expect(await this.token.balanceOf(secondTokenHolder, secondTokenId)).to.be.bignumber.equal(secondTokenValue); + + expect(await this.token.balanceOf(firstTokenHolder, unknownTokenId)).to.be.bignumber.equal('0'); + }); + }); + }); + + describe('balanceOfBatch', function () { + it("reverts when input arrays don't match up", async function () { + const accounts1 = [firstTokenHolder, secondTokenHolder, firstTokenHolder, secondTokenHolder]; + const ids1 = [firstTokenId, secondTokenId, unknownTokenId]; + await expectRevertCustomError(this.token.balanceOfBatch(accounts1, ids1), 'ERC1155InvalidArrayLength', [ + accounts1.length, + ids1.length, + ]); + + 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 () { + 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 () { + it('returns zeros for each account', async function () { + const result = await this.token.balanceOfBatch( + [firstTokenHolder, secondTokenHolder, firstTokenHolder], + [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 own some tokens', function () { + beforeEach(async function () { + await this.token.$_mint(firstTokenHolder, firstTokenId, firstTokenValue, '0x', { + from: minter, + }); + await this.token.$_mint(secondTokenHolder, secondTokenId, secondTokenValue, '0x', { + from: minter, + }); + }); + + it('returns amounts owned by each account in order passed', async function () { + const result = await this.token.balanceOfBatch( + [secondTokenHolder, firstTokenHolder, firstTokenHolder], + [secondTokenId, firstTokenId, unknownTokenId], + ); + expect(result).to.be.an('array'); + 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'); + }); + + it('returns multiple times the balance of the same address when asked', async function () { + const result = await this.token.balanceOfBatch( + [firstTokenHolder, secondTokenHolder, firstTokenHolder], + [firstTokenId, secondTokenId, firstTokenId], + ); + expect(result).to.be.an('array'); + expect(result[0]).to.be.a.bignumber.equal(result[2]); + 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); + }); + }); + }); + + describe('setApprovalForAll', function () { + let receipt; + beforeEach(async function () { + receipt = await this.token.setApprovalForAll(proxy, true, { from: multiTokenHolder }); + }); + + it('sets approval status which can be queried via isApprovedForAll', async function () { + expect(await this.token.isApprovedForAll(multiTokenHolder, proxy)).to.be.equal(true); + }); + + it('emits an ApprovalForAll log', function () { + expectEvent(receipt, 'ApprovalForAll', { account: multiTokenHolder, operator: proxy, approved: true }); + }); + + it('can unset approval for an operator', async function () { + await this.token.setApprovalForAll(proxy, false, { from: multiTokenHolder }); + expect(await this.token.isApprovedForAll(multiTokenHolder, proxy)).to.be.equal(false); + }); + + it('reverts if attempting to approve zero address as an operator', async function () { + await expectRevertCustomError( + this.token.setApprovalForAll(constants.ZERO_ADDRESS, true, { from: multiTokenHolder }), + 'ERC1155InvalidOperator', + [constants.ZERO_ADDRESS], + ); + }); + }); + + describe('safeTransferFrom', function () { + beforeEach(async function () { + await this.token.$_mint(multiTokenHolder, firstTokenId, firstTokenValue, '0x', { + from: minter, + }); + 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, firstTokenValue.addn(1), '0x', { + from: multiTokenHolder, + }), + 'ERC1155InsufficientBalance', + [multiTokenHolder, firstTokenValue, firstTokenValue.addn(1), firstTokenId], + ); + }); + + it('reverts when transferring to zero address', async function () { + await expectRevertCustomError( + this.token.safeTransferFrom(multiTokenHolder, ZERO_ADDRESS, firstTokenId, firstTokenValue, '0x', { + from: multiTokenHolder, + }), + 'ERC1155InvalidReceiver', + [ZERO_ADDRESS], + ); + }); + + function transferWasSuccessful({ operator, from, id, value }) { + it('debits transferred balance from sender', async function () { + const newBalance = await this.token.balanceOf(from, id); + expect(newBalance).to.be.a.bignumber.equal('0'); + }); + + it('credits transferred balance to receiver', async function () { + const newBalance = await this.token.balanceOf(this.toWhom, id); + expect(newBalance).to.be.a.bignumber.equal(value); + }); + + it('emits a TransferSingle log', function () { + expectEvent(this.transferLogs, 'TransferSingle', { + operator, + from, + to: this.toWhom, + id, + value, + }); + }); + } + + context('when called by the multiTokenHolder', async function () { + beforeEach(async function () { + this.toWhom = recipient; + this.transferLogs = await this.token.safeTransferFrom( + multiTokenHolder, + recipient, + firstTokenId, + firstTokenValue, + '0x', + { + from: multiTokenHolder, + }, + ); + }); + + transferWasSuccessful.call(this, { + operator: multiTokenHolder, + from: multiTokenHolder, + id: firstTokenId, + 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(secondTokenValue); + + const balance2 = await this.token.balanceOf(recipient, secondTokenId); + expect(balance2).to.be.a.bignumber.equal('0'); + }); + }); + + context('when called by an operator on behalf of the multiTokenHolder', function () { + context('when operator is not approved by multiTokenHolder', function () { + beforeEach(async function () { + await this.token.setApprovalForAll(proxy, false, { from: multiTokenHolder }); + }); + + it('reverts', async function () { + await expectRevertCustomError( + this.token.safeTransferFrom(multiTokenHolder, recipient, firstTokenId, firstTokenValue, '0x', { + from: proxy, + }), + 'ERC1155MissingApprovalForAll', + [proxy, multiTokenHolder], + ); + }); + }); + + context('when operator is approved by multiTokenHolder', function () { + beforeEach(async function () { + this.toWhom = recipient; + await this.token.setApprovalForAll(proxy, true, { from: multiTokenHolder }); + this.transferLogs = await this.token.safeTransferFrom( + multiTokenHolder, + recipient, + firstTokenId, + firstTokenValue, + '0x', + { + from: proxy, + }, + ); + }); + + transferWasSuccessful.call(this, { + operator: proxy, + from: multiTokenHolder, + id: firstTokenId, + value: firstTokenValue, + }); + + it("preserves operator's balances not involved in the transfer", async function () { + const balance1 = await this.token.balanceOf(proxy, firstTokenId); + expect(balance1).to.be.a.bignumber.equal('0'); + + const balance2 = await this.token.balanceOf(proxy, secondTokenId); + expect(balance2).to.be.a.bignumber.equal('0'); + }); + }); + }); + + context('when sending to a valid receiver', function () { + beforeEach(async function () { + this.receiver = await ERC1155ReceiverMock.new( + RECEIVER_SINGLE_MAGIC_VALUE, + RECEIVER_BATCH_MAGIC_VALUE, + RevertType.None, + ); + }); + + context('without data', function () { + beforeEach(async function () { + this.toWhom = this.receiver.address; + this.transferReceipt = await this.token.safeTransferFrom( + multiTokenHolder, + this.receiver.address, + firstTokenId, + firstTokenValue, + '0x', + { from: multiTokenHolder }, + ); + this.transferLogs = this.transferReceipt; + }); + + transferWasSuccessful.call(this, { + operator: multiTokenHolder, + from: multiTokenHolder, + id: firstTokenId, + value: firstTokenValue, + }); + + it('calls onERC1155Received', async function () { + await expectEvent.inTransaction(this.transferReceipt.tx, ERC1155ReceiverMock, 'Received', { + operator: multiTokenHolder, + from: multiTokenHolder, + id: firstTokenId, + value: firstTokenValue, + data: null, + }); + }); + }); + + context('with data', function () { + const data = '0xf00dd00d'; + beforeEach(async function () { + this.toWhom = this.receiver.address; + this.transferReceipt = await this.token.safeTransferFrom( + multiTokenHolder, + this.receiver.address, + firstTokenId, + firstTokenValue, + data, + { from: multiTokenHolder }, + ); + this.transferLogs = this.transferReceipt; + }); + + transferWasSuccessful.call(this, { + operator: multiTokenHolder, + from: multiTokenHolder, + id: firstTokenId, + value: firstTokenValue, + }); + + it('calls onERC1155Received', async function () { + await expectEvent.inTransaction(this.transferReceipt.tx, ERC1155ReceiverMock, 'Received', { + operator: multiTokenHolder, + from: multiTokenHolder, + id: firstTokenId, + value: firstTokenValue, + data, + }); + }); + }); + }); + + context('to a receiver contract returning unexpected value', function () { + beforeEach(async function () { + this.receiver = await ERC1155ReceiverMock.new('0x00c0ffee', RECEIVER_BATCH_MAGIC_VALUE, RevertType.None); + }); + + it('reverts', async function () { + await expectRevertCustomError( + this.token.safeTransferFrom(multiTokenHolder, this.receiver.address, firstTokenId, firstTokenValue, '0x', { + from: multiTokenHolder, + }), + 'ERC1155InvalidReceiver', + [this.receiver.address], + ); + }); + }); + + context('to a receiver contract that reverts', function () { + context('with a revert string', function () { + beforeEach(async function () { + this.receiver = await ERC1155ReceiverMock.new( + RECEIVER_SINGLE_MAGIC_VALUE, + RECEIVER_BATCH_MAGIC_VALUE, + RevertType.RevertWithMessage, + ); + }); + + it('reverts', async function () { + await expectRevert( + this.token.safeTransferFrom( + multiTokenHolder, + this.receiver.address, + firstTokenId, + firstTokenValue, + '0x', + { + from: multiTokenHolder, + }, + ), + 'ERC1155ReceiverMock: reverting on receive', + ); + }); + }); + + context('without a revert string', function () { + beforeEach(async function () { + this.receiver = await ERC1155ReceiverMock.new( + RECEIVER_SINGLE_MAGIC_VALUE, + RECEIVER_BATCH_MAGIC_VALUE, + RevertType.RevertWithoutMessage, + ); + }); + + it('reverts', async function () { + await expectRevertCustomError( + this.token.safeTransferFrom( + multiTokenHolder, + this.receiver.address, + firstTokenId, + firstTokenValue, + '0x', + { + from: multiTokenHolder, + }, + ), + 'ERC1155InvalidReceiver', + [this.receiver.address], + ); + }); + }); + + context('with a custom error', function () { + beforeEach(async function () { + this.receiver = await ERC1155ReceiverMock.new( + RECEIVER_SINGLE_MAGIC_VALUE, + RECEIVER_BATCH_MAGIC_VALUE, + RevertType.RevertWithCustomError, + ); + }); + + it('reverts', async function () { + await expectRevertCustomError( + this.token.safeTransferFrom( + multiTokenHolder, + this.receiver.address, + firstTokenId, + firstTokenValue, + '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, + firstTokenValue, + '0x', + { + from: multiTokenHolder, + }, + ), + ); + }); + }); + }); + + context('to a contract that does not implement the required function', function () { + it('reverts', async function () { + const invalidReceiver = this.token; + await expectRevert.unspecified( + this.token.safeTransferFrom( + multiTokenHolder, + invalidReceiver.address, + firstTokenId, + firstTokenValue, + '0x', + { + from: multiTokenHolder, + }, + ), + ); + }); + }); + }); + + describe('safeBatchTransferFrom', function () { + beforeEach(async function () { + await this.token.$_mint(multiTokenHolder, firstTokenId, firstTokenValue, '0x', { + from: minter, + }); + await this.token.$_mint(multiTokenHolder, secondTokenId, secondTokenValue, '0x', { + from: minter, + }); + }); + + it('reverts when transferring value more than any of balances', async function () { + await expectRevertCustomError( + this.token.safeBatchTransferFrom( + multiTokenHolder, + recipient, + [firstTokenId, secondTokenId], + [firstTokenValue, secondTokenValue.addn(1)], + '0x', + { from: multiTokenHolder }, + ), + 'ERC1155InsufficientBalance', + [multiTokenHolder, secondTokenValue, secondTokenValue.addn(1), secondTokenId], + ); + }); + + it("reverts when ids array length doesn't match values array length", async function () { + const ids1 = [firstTokenId]; + const tokenValues1 = [firstTokenValue, secondTokenValue]; + + await expectRevertCustomError( + this.token.safeBatchTransferFrom(multiTokenHolder, recipient, ids1, tokenValues1, '0x', { + from: multiTokenHolder, + }), + 'ERC1155InvalidArrayLength', + [ids1.length, tokenValues1.length], + ); + + const ids2 = [firstTokenId, secondTokenId]; + const tokenValues2 = [firstTokenValue]; + await expectRevertCustomError( + this.token.safeBatchTransferFrom(multiTokenHolder, recipient, ids2, tokenValues2, '0x', { + from: multiTokenHolder, + }), + 'ERC1155InvalidArrayLength', + [ids2.length, tokenValues2.length], + ); + }); + + it('reverts when transferring to zero address', async function () { + await expectRevertCustomError( + this.token.safeBatchTransferFrom( + multiTokenHolder, + ZERO_ADDRESS, + [firstTokenId, secondTokenId], + [firstTokenValue, secondTokenValue], + '0x', + { from: multiTokenHolder }, + ), + 'ERC1155InvalidReceiver', + [ZERO_ADDRESS], + ); + }); + + it('reverts when transferring from zero address', async function () { + await expectRevertCustomError( + this.token.$_safeBatchTransferFrom(ZERO_ADDRESS, multiTokenHolder, [firstTokenId], [firstTokenValue], '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); + for (const newBalance of newBalances) { + expect(newBalance).to.be.a.bignumber.equal('0'); + } + }); + + it('credits transferred balances to receiver', async function () { + const newBalances = await this.token.balanceOfBatch(new Array(ids.length).fill(this.toWhom), ids); + for (let i = 0; i < newBalances.length; i++) { + expect(newBalances[i]).to.be.a.bignumber.equal(values[i]); + } + }); + + it('emits a TransferBatch log', function () { + expectEvent(this.transferLogs, 'TransferBatch', { + operator, + from, + to: this.toWhom, + // ids, + // values, + }); + }); + } + + context('when called by the multiTokenHolder', async function () { + beforeEach(async function () { + this.toWhom = recipient; + this.transferLogs = await this.token.safeBatchTransferFrom( + multiTokenHolder, + recipient, + [firstTokenId, secondTokenId], + [firstTokenValue, secondTokenValue], + '0x', + { from: multiTokenHolder }, + ); + }); + + batchTransferWasSuccessful.call(this, { + operator: multiTokenHolder, + from: multiTokenHolder, + ids: [firstTokenId, secondTokenId], + values: [firstTokenValue, secondTokenValue], + }); + }); + + context('when called by an operator on behalf of the multiTokenHolder', function () { + context('when operator is not approved by multiTokenHolder', function () { + beforeEach(async function () { + await this.token.setApprovalForAll(proxy, false, { from: multiTokenHolder }); + }); + + it('reverts', async function () { + await expectRevertCustomError( + this.token.safeBatchTransferFrom( + multiTokenHolder, + recipient, + [firstTokenId, secondTokenId], + [firstTokenValue, secondTokenValue], + '0x', + { from: proxy }, + ), + 'ERC1155MissingApprovalForAll', + [proxy, multiTokenHolder], + ); + }); + }); + + context('when operator is approved by multiTokenHolder', function () { + beforeEach(async function () { + this.toWhom = recipient; + await this.token.setApprovalForAll(proxy, true, { from: multiTokenHolder }); + this.transferLogs = await this.token.safeBatchTransferFrom( + multiTokenHolder, + recipient, + [firstTokenId, secondTokenId], + [firstTokenValue, secondTokenValue], + '0x', + { from: proxy }, + ); + }); + + batchTransferWasSuccessful.call(this, { + operator: proxy, + from: multiTokenHolder, + ids: [firstTokenId, secondTokenId], + values: [firstTokenValue, secondTokenValue], + }); + + it("preserves operator's balances not involved in the transfer", async function () { + const balance1 = await this.token.balanceOf(proxy, firstTokenId); + expect(balance1).to.be.a.bignumber.equal('0'); + const balance2 = await this.token.balanceOf(proxy, secondTokenId); + expect(balance2).to.be.a.bignumber.equal('0'); + }); + }); + }); + + context('when sending to a valid receiver', function () { + beforeEach(async function () { + this.receiver = await ERC1155ReceiverMock.new( + RECEIVER_SINGLE_MAGIC_VALUE, + RECEIVER_BATCH_MAGIC_VALUE, + RevertType.None, + ); + }); + + context('without data', function () { + beforeEach(async function () { + this.toWhom = this.receiver.address; + this.transferReceipt = await this.token.safeBatchTransferFrom( + multiTokenHolder, + this.receiver.address, + [firstTokenId, secondTokenId], + [firstTokenValue, secondTokenValue], + '0x', + { from: multiTokenHolder }, + ); + this.transferLogs = this.transferReceipt; + }); + + batchTransferWasSuccessful.call(this, { + operator: multiTokenHolder, + from: multiTokenHolder, + ids: [firstTokenId, secondTokenId], + values: [firstTokenValue, secondTokenValue], + }); + + it('calls onERC1155BatchReceived', async function () { + await expectEvent.inTransaction(this.transferReceipt.tx, ERC1155ReceiverMock, 'BatchReceived', { + operator: multiTokenHolder, + from: multiTokenHolder, + // ids: [firstTokenId, secondTokenId], + // values: [firstTokenValue, secondTokenValue], + data: null, + }); + }); + }); + + context('with data', function () { + const data = '0xf00dd00d'; + beforeEach(async function () { + this.toWhom = this.receiver.address; + this.transferReceipt = await this.token.safeBatchTransferFrom( + multiTokenHolder, + this.receiver.address, + [firstTokenId, secondTokenId], + [firstTokenValue, secondTokenValue], + data, + { from: multiTokenHolder }, + ); + this.transferLogs = this.transferReceipt; + }); + + batchTransferWasSuccessful.call(this, { + operator: multiTokenHolder, + from: multiTokenHolder, + ids: [firstTokenId, secondTokenId], + values: [firstTokenValue, secondTokenValue], + }); + + it('calls onERC1155Received', async function () { + await expectEvent.inTransaction(this.transferReceipt.tx, ERC1155ReceiverMock, 'BatchReceived', { + operator: multiTokenHolder, + from: multiTokenHolder, + // ids: [firstTokenId, secondTokenId], + // values: [firstTokenValue, secondTokenValue], + data, + }); + }); + }); + }); + + context('to a receiver contract returning unexpected value', function () { + beforeEach(async function () { + this.receiver = await ERC1155ReceiverMock.new( + RECEIVER_SINGLE_MAGIC_VALUE, + RECEIVER_SINGLE_MAGIC_VALUE, + RevertType.None, + ); + }); + + it('reverts', async function () { + await expectRevertCustomError( + this.token.safeBatchTransferFrom( + multiTokenHolder, + this.receiver.address, + [firstTokenId, secondTokenId], + [firstTokenValue, secondTokenValue], + '0x', + { from: multiTokenHolder }, + ), + 'ERC1155InvalidReceiver', + [this.receiver.address], + ); + }); + }); + + context('to a receiver contract that reverts', function () { + context('with a revert string', function () { + beforeEach(async function () { + this.receiver = await ERC1155ReceiverMock.new( + RECEIVER_SINGLE_MAGIC_VALUE, + RECEIVER_BATCH_MAGIC_VALUE, + RevertType.RevertWithMessage, + ); + }); + + it('reverts', async function () { + await expectRevert( + this.token.safeBatchTransferFrom( + multiTokenHolder, + this.receiver.address, + [firstTokenId, secondTokenId], + [firstTokenValue, secondTokenValue], + '0x', + { from: multiTokenHolder }, + ), + 'ERC1155ReceiverMock: reverting on batch receive', + ); + }); + }); + + context('without a revert string', function () { + beforeEach(async function () { + this.receiver = await ERC1155ReceiverMock.new( + RECEIVER_SINGLE_MAGIC_VALUE, + RECEIVER_BATCH_MAGIC_VALUE, + RevertType.RevertWithoutMessage, + ); + }); + + it('reverts', async function () { + await expectRevertCustomError( + this.token.safeBatchTransferFrom( + multiTokenHolder, + this.receiver.address, + [firstTokenId, secondTokenId], + [firstTokenValue, secondTokenValue], + '0x', + { from: multiTokenHolder }, + ), + 'ERC1155InvalidReceiver', + [this.receiver.address], + ); + }); + }); + + context('with a custom error', function () { + beforeEach(async function () { + this.receiver = await ERC1155ReceiverMock.new( + RECEIVER_SINGLE_MAGIC_VALUE, + RECEIVER_BATCH_MAGIC_VALUE, + RevertType.RevertWithCustomError, + ); + }); + + it('reverts', async function () { + await expectRevertCustomError( + this.token.safeBatchTransferFrom( + multiTokenHolder, + this.receiver.address, + [firstTokenId, secondTokenId], + [firstTokenValue, secondTokenValue], + '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.safeBatchTransferFrom( + multiTokenHolder, + this.receiver.address, + [firstTokenId, secondTokenId], + [firstTokenValue, secondTokenValue], + '0x', + { from: multiTokenHolder }, + ), + ); + }); + }); + }); + + context('to a contract that does not implement the required function', function () { + it('reverts', async function () { + const invalidReceiver = this.token; + await expectRevert.unspecified( + this.token.safeBatchTransferFrom( + multiTokenHolder, + invalidReceiver.address, + [firstTokenId, secondTokenId], + [firstTokenValue, secondTokenValue], + '0x', + { from: multiTokenHolder }, + ), + ); + }); + }); + }); + + shouldSupportInterfaces(['ERC165', 'ERC1155']); + }); +} + +module.exports = { + shouldBehaveLikeERC1155, +}; diff --git a/test/token/ERC1155/ERC1155.test.js b/test/token/ERC1155/ERC1155.test.js new file mode 100644 index 000000000..58d747a4b --- /dev/null +++ b/test/token/ERC1155/ERC1155.test.js @@ -0,0 +1,252 @@ +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'); + +contract('ERC1155', function (accounts) { + const [operator, tokenHolder, tokenBatchHolder, ...otherAccounts] = accounts; + + const initialURI = 'https://token-cdn-domain/{id}.json'; + + beforeEach(async function () { + this.token = await ERC1155Mock.new(initialURI); + }); + + shouldBehaveLikeERC1155(otherAccounts); + + describe('internal functions', function () { + const tokenId = new BN(1990); + const mintValue = new BN(9001); + const burnValue = new BN(3000); + + const tokenBatchIds = [new BN(2000), new BN(2010), new BN(2020)]; + 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, mintValue, data), + 'ERC1155InvalidReceiver', + [ZERO_ADDRESS], + ); + }); + + context('with minted tokens', function () { + beforeEach(async function () { + this.receipt = await this.token.$_mint(tokenHolder, tokenId, mintValue, data, { from: operator }); + }); + + it('emits a TransferSingle event', function () { + expectEvent(this.receipt, 'TransferSingle', { + operator, + from: ZERO_ADDRESS, + to: tokenHolder, + id: tokenId, + value: mintValue, + }); + }); + + it('credits the minted token value', async function () { + expect(await this.token.balanceOf(tokenHolder, tokenId)).to.be.bignumber.equal(mintValue); + }); + }); + }); + + describe('_mintBatch', function () { + it('reverts with a zero destination address', async function () { + await expectRevertCustomError( + this.token.$_mintBatch(ZERO_ADDRESS, tokenBatchIds, mintValues, data), + 'ERC1155InvalidReceiver', + [ZERO_ADDRESS], + ); + }); + + it('reverts if length of inputs do not match', async function () { + await expectRevertCustomError( + this.token.$_mintBatch(tokenBatchHolder, tokenBatchIds, mintValues.slice(1), data), + 'ERC1155InvalidArrayLength', + [tokenBatchIds.length, mintValues.length - 1], + ); + + await expectRevertCustomError( + this.token.$_mintBatch(tokenBatchHolder, tokenBatchIds.slice(1), mintValues, data), + 'ERC1155InvalidArrayLength', + [tokenBatchIds.length - 1, mintValues.length], + ); + }); + + context('with minted batch of tokens', function () { + beforeEach(async function () { + this.receipt = await this.token.$_mintBatch(tokenBatchHolder, tokenBatchIds, mintValues, data, { + from: operator, + }); + }); + + it('emits a TransferBatch event', function () { + expectEvent(this.receipt, 'TransferBatch', { + operator, + from: ZERO_ADDRESS, + to: tokenBatchHolder, + }); + }); + + it('credits the minted batch of tokens', async function () { + const holderBatchBalances = await this.token.balanceOfBatch( + new Array(tokenBatchIds.length).fill(tokenBatchHolder), + tokenBatchIds, + ); + + for (let i = 0; i < holderBatchBalances.length; i++) { + expect(holderBatchBalances[i]).to.be.bignumber.equal(mintValues[i]); + } + }); + }); + }); + + describe('_burn', function () { + it("reverts when burning the zero account's tokens", async function () { + 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, mintValue), + 'ERC1155InsufficientBalance', + [tokenHolder, 0, mintValue, tokenId], + ); + }); + + it('reverts when burning more than available tokens', async function () { + await this.token.$_mint(tokenHolder, tokenId, mintValue, data, { from: operator }); + + await expectRevertCustomError( + this.token.$_burn(tokenHolder, tokenId, mintValue.addn(1)), + 'ERC1155InsufficientBalance', + [tokenHolder, mintValue, mintValue.addn(1), tokenId], + ); + }); + + context('with minted-then-burnt tokens', function () { + beforeEach(async function () { + 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 () { + expectEvent(this.receipt, 'TransferSingle', { + operator, + from: tokenHolder, + to: ZERO_ADDRESS, + id: tokenId, + value: burnValue, + }); + }); + + it('accounts for both minting and burning', async function () { + expect(await this.token.balanceOf(tokenHolder, tokenId)).to.be.bignumber.equal(mintValue.sub(burnValue)); + }); + }); + }); + + describe('_burnBatch', function () { + it("reverts when burning the zero account's tokens", async function () { + await expectRevertCustomError( + this.token.$_burnBatch(ZERO_ADDRESS, tokenBatchIds, burnValues), + 'ERC1155InvalidSender', + [ZERO_ADDRESS], + ); + }); + + it('reverts if length of inputs do not match', async function () { + await expectRevertCustomError( + this.token.$_burnBatch(tokenBatchHolder, tokenBatchIds, burnValues.slice(1)), + 'ERC1155InvalidArrayLength', + [tokenBatchIds.length, burnValues.length - 1], + ); + + await expectRevertCustomError( + this.token.$_burnBatch(tokenBatchHolder, tokenBatchIds.slice(1), burnValues), + 'ERC1155InvalidArrayLength', + [tokenBatchIds.length - 1, burnValues.length], + ); + }); + + it('reverts when burning a non-existent token id', async function () { + await expectRevertCustomError( + this.token.$_burnBatch(tokenBatchHolder, tokenBatchIds, burnValues), + 'ERC1155InsufficientBalance', + [tokenBatchHolder, 0, tokenBatchIds[0], burnValues[0]], + ); + }); + + context('with minted-then-burnt tokens', function () { + beforeEach(async function () { + 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 () { + expectEvent(this.receipt, 'TransferBatch', { + operator, + from: tokenBatchHolder, + to: ZERO_ADDRESS, + // ids: tokenBatchIds, + // values: burnValues, + }); + }); + + it('accounts for both minting and burning', async function () { + const holderBatchBalances = await this.token.balanceOfBatch( + new Array(tokenBatchIds.length).fill(tokenBatchHolder), + tokenBatchIds, + ); + + for (let i = 0; i < holderBatchBalances.length; i++) { + expect(holderBatchBalances[i]).to.be.bignumber.equal(mintValues[i].sub(burnValues[i])); + } + }); + }); + }); + }); + + describe('ERC1155MetadataURI', function () { + const firstTokenID = new BN('42'); + const secondTokenID = new BN('1337'); + + it('emits no URI event in constructor', async function () { + await expectEvent.notEmitted.inConstruction(this.token, 'URI'); + }); + + it('sets the initial URI for all token types', async function () { + expect(await this.token.uri(firstTokenID)).to.be.equal(initialURI); + expect(await this.token.uri(secondTokenID)).to.be.equal(initialURI); + }); + + describe('_setURI', function () { + const newURI = 'https://token-cdn-domain/{locale}/{id}.json'; + + it('emits no URI event', async function () { + const receipt = await this.token.$_setURI(newURI); + + expectEvent.notEmitted(receipt, 'URI'); + }); + + it('sets the new URI for all token types', async function () { + await this.token.$_setURI(newURI); + + expect(await this.token.uri(firstTokenID)).to.be.equal(newURI); + expect(await this.token.uri(secondTokenID)).to.be.equal(newURI); + }); + }); + }); +}); diff --git a/test/token/ERC1155/extensions/ERC1155Burnable.test.js b/test/token/ERC1155/extensions/ERC1155Burnable.test.js new file mode 100644 index 000000000..fc94db052 --- /dev/null +++ b/test/token/ERC1155/extensions/ERC1155Burnable.test.js @@ -0,0 +1,71 @@ +const { BN } = require('@openzeppelin/test-helpers'); + +const { expect } = require('chai'); + +const { expectRevertCustomError } = require('../../../helpers/customError'); + +const ERC1155Burnable = artifacts.require('$ERC1155Burnable'); + +contract('ERC1155Burnable', function (accounts) { + const [holder, operator, other] = accounts; + + const uri = 'https://token.com'; + + const tokenIds = [new BN('42'), new BN('1137')]; + const values = [new BN('3000'), new BN('9902')]; + + beforeEach(async function () { + this.token = await ERC1155Burnable.new(uri); + + 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], 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], 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], values[0].subn(1), { from: other }), + 'ERC1155MissingApprovalForAll', + [other, holder], + ); + }); + }); + + describe('burnBatch', function () { + it('holder can burn their tokens', async function () { + 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'); + }); + + 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, [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'); + }); + + it("unapproved accounts cannot burn the holder's tokens", async function () { + await expectRevertCustomError( + 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 new file mode 100644 index 000000000..248ea5684 --- /dev/null +++ b/test/token/ERC1155/extensions/ERC1155Pausable.test.js @@ -0,0 +1,113 @@ +const { BN } = require('@openzeppelin/test-helpers'); + +const { expect } = require('chai'); +const { expectRevertCustomError } = require('../../../helpers/customError'); + +const ERC1155Pausable = artifacts.require('$ERC1155Pausable'); + +contract('ERC1155Pausable', function (accounts) { + const [holder, operator, receiver, other] = accounts; + + const uri = 'https://token.com'; + + beforeEach(async function () { + this.token = await ERC1155Pausable.new(uri); + }); + + context('when token is paused', function () { + const firstTokenId = new BN('37'); + const firstTokenValue = new BN('42'); + + const secondTokenId = new BN('19842'); + const secondTokenValue = new BN('23'); + + beforeEach(async function () { + await this.token.setApprovalForAll(operator, true, { from: holder }); + 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, firstTokenValue, '0x', { from: holder }), + 'EnforcedPause', + [], + ); + }); + + it('reverts when trying to safeTransferFrom from operator', async function () { + await expectRevertCustomError( + this.token.safeTransferFrom(holder, receiver, firstTokenId, firstTokenValue, '0x', { from: operator }), + 'EnforcedPause', + [], + ); + }); + + it('reverts when trying to safeBatchTransferFrom from holder', async function () { + await expectRevertCustomError( + this.token.safeBatchTransferFrom(holder, receiver, [firstTokenId], [firstTokenValue], '0x', { from: holder }), + 'EnforcedPause', + [], + ); + }); + + it('reverts when trying to safeBatchTransferFrom from operator', async function () { + await expectRevertCustomError( + this.token.safeBatchTransferFrom(holder, receiver, [firstTokenId], [firstTokenValue], '0x', { + from: operator, + }), + 'EnforcedPause', + [], + ); + }); + + it('reverts when trying to mint', async function () { + await expectRevertCustomError( + this.token.$_mint(holder, secondTokenId, secondTokenValue, '0x'), + 'EnforcedPause', + [], + ); + }); + + it('reverts when trying to mintBatch', async function () { + await expectRevertCustomError( + this.token.$_mintBatch(holder, [secondTokenId], [secondTokenValue], '0x'), + 'EnforcedPause', + [], + ); + }); + + it('reverts when trying to burn', async function () { + await expectRevertCustomError(this.token.$_burn(holder, firstTokenId, firstTokenValue), 'EnforcedPause', []); + }); + + it('reverts when trying to burnBatch', async function () { + await expectRevertCustomError( + this.token.$_burnBatch(holder, [firstTokenId], [firstTokenValue]), + 'EnforcedPause', + [], + ); + }); + + describe('setApprovalForAll', function () { + it('approves an operator', async function () { + await this.token.setApprovalForAll(other, true, { from: holder }); + expect(await this.token.isApprovedForAll(holder, other)).to.equal(true); + }); + }); + + describe('balanceOf', 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(firstTokenValue); + }); + }); + + describe('isApprovedForAll', function () { + it('returns the approval of the operator', async function () { + expect(await this.token.isApprovedForAll(holder, operator)).to.equal(true); + }); + }); + }); +}); diff --git a/test/token/ERC1155/extensions/ERC1155Supply.test.js b/test/token/ERC1155/extensions/ERC1155Supply.test.js new file mode 100644 index 000000000..bf86920f6 --- /dev/null +++ b/test/token/ERC1155/extensions/ERC1155Supply.test.js @@ -0,0 +1,116 @@ +const { BN, constants } = require('@openzeppelin/test-helpers'); + +const { expect } = require('chai'); + +const { ZERO_ADDRESS } = constants; + +const ERC1155Supply = artifacts.require('$ERC1155Supply'); + +contract('ERC1155Supply', function (accounts) { + const [holder] = accounts; + + const uri = 'https://token.com'; + + const firstTokenId = new BN('37'); + const firstTokenValue = new BN('42'); + + const secondTokenId = new BN('19842'); + const secondTokenValue = new BN('23'); + + beforeEach(async function () { + this.token = await ERC1155Supply.new(uri); + }); + + context('before mint', function () { + it('exist', async function () { + expect(await this.token.exists(firstTokenId)).to.be.equal(false); + }); + + it('totalSupply', async function () { + expect(await this.token.methods['totalSupply(uint256)'](firstTokenId)).to.be.bignumber.equal('0'); + expect(await this.token.methods['totalSupply()']()).to.be.bignumber.equal('0'); + }); + }); + + context('after mint', function () { + context('single', function () { + beforeEach(async function () { + await this.token.$_mint(holder, firstTokenId, firstTokenValue, '0x'); + }); + + it('exist', async function () { + expect(await this.token.exists(firstTokenId)).to.be.equal(true); + }); + + it('totalSupply', async function () { + 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], [firstTokenValue, secondTokenValue], '0x'); + }); + + it('exist', async function () { + expect(await this.token.exists(firstTokenId)).to.be.equal(true); + expect(await this.token.exists(secondTokenId)).to.be.equal(true); + }); + + it('totalSupply', async function () { + 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( + firstTokenValue.add(secondTokenValue), + ); + }); + }); + }); + + context('after burn', function () { + context('single', function () { + beforeEach(async function () { + await this.token.$_mint(holder, firstTokenId, firstTokenValue, '0x'); + await this.token.$_burn(holder, firstTokenId, firstTokenValue); + }); + + it('exist', async function () { + expect(await this.token.exists(firstTokenId)).to.be.equal(false); + }); + + it('totalSupply', async function () { + expect(await this.token.methods['totalSupply(uint256)'](firstTokenId)).to.be.bignumber.equal('0'); + expect(await this.token.methods['totalSupply()']()).to.be.bignumber.equal('0'); + }); + }); + + context('batch', function () { + beforeEach(async function () { + await this.token.$_mintBatch(holder, [firstTokenId, secondTokenId], [firstTokenValue, secondTokenValue], '0x'); + await this.token.$_burnBatch(holder, [firstTokenId, secondTokenId], [firstTokenValue, secondTokenValue]); + }); + + it('exist', async function () { + expect(await this.token.exists(firstTokenId)).to.be.equal(false); + expect(await this.token.exists(secondTokenId)).to.be.equal(false); + }); + + it('totalSupply', async function () { + 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, firstTokenValue, '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'); + }); + }); +}); diff --git a/test/token/ERC1155/extensions/ERC1155URIStorage.test.js b/test/token/ERC1155/extensions/ERC1155URIStorage.test.js new file mode 100644 index 000000000..58ac67bc6 --- /dev/null +++ b/test/token/ERC1155/extensions/ERC1155URIStorage.test.js @@ -0,0 +1,66 @@ +const { BN, expectEvent } = require('@openzeppelin/test-helpers'); + +const { expect } = require('chai'); +const { artifacts } = require('hardhat'); + +const ERC1155URIStorage = artifacts.require('$ERC1155URIStorage'); + +contract(['ERC1155URIStorage'], function (accounts) { + const [holder] = accounts; + + const erc1155Uri = 'https://token.com/nfts/'; + const baseUri = 'https://token.com/'; + + const tokenId = new BN('1'); + 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, value, '0x'); + }); + + it('can request the token uri, returning the erc1155 uri if no token uri was set', async function () { + const receivedTokenUri = await this.token.uri(tokenId); + + expect(receivedTokenUri).to.be.equal(erc1155Uri); + }); + + it('can request the token uri, returning the concatenated uri if a token uri was set', async function () { + const tokenUri = '1234/'; + const receipt = await this.token.$_setURI(tokenId, tokenUri); + + const receivedTokenUri = await this.token.uri(tokenId); + + const expectedUri = `${baseUri}${tokenUri}`; + expect(receivedTokenUri).to.be.equal(expectedUri); + expectEvent(receipt, 'URI', { value: expectedUri, id: tokenId }); + }); + }); + + describe('with base uri set to the empty string', function () { + beforeEach(async function () { + this.token = await ERC1155URIStorage.new(''); + + 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 () { + const receivedTokenUri = await this.token.uri(tokenId); + + expect(receivedTokenUri).to.be.equal(''); + }); + + it('can request the token uri, returning the token uri if a token uri was set', async function () { + const tokenUri = 'ipfs://1234/'; + const receipt = await this.token.$_setURI(tokenId, tokenUri); + + const receivedTokenUri = await this.token.uri(tokenId); + + expect(receivedTokenUri).to.be.equal(tokenUri); + expectEvent(receipt, 'URI', { value: tokenUri, id: tokenId }); + }); + }); +}); diff --git a/test/token/ERC1155/utils/ERC1155Holder.test.js b/test/token/ERC1155/utils/ERC1155Holder.test.js new file mode 100644 index 000000000..ee818eae8 --- /dev/null +++ b/test/token/ERC1155/utils/ERC1155Holder.test.js @@ -0,0 +1,64 @@ +const { BN } = require('@openzeppelin/test-helpers'); + +const ERC1155Holder = artifacts.require('$ERC1155Holder'); +const ERC1155 = artifacts.require('$ERC1155'); + +const { expect } = require('chai'); + +const { shouldSupportInterfaces } = require('../../../utils/introspection/SupportsInterface.behavior'); + +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 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, multiTokenValues, '0x'); + }); + + shouldSupportInterfaces(['ERC165', 'ERC1155Receiver']); + + it('receives ERC1155 tokens from a single ID', async function () { + await this.multiToken.safeTransferFrom( + creator, + this.holder.address, + multiTokenIds[0], + multiTokenValues[0], + transferData, + { from: creator }, + ); + + expect(await this.multiToken.balanceOf(this.holder.address, multiTokenIds[0])).to.be.bignumber.equal( + multiTokenValues[0], + ); + + for (let i = 1; i < multiTokenIds.length; i++) { + expect(await this.multiToken.balanceOf(this.holder.address, multiTokenIds[i])).to.be.bignumber.equal(new BN(0)); + } + }); + + it('receives ERC1155 tokens from a multiple IDs', async function () { + for (let i = 0; i < multiTokenIds.length; i++) { + expect(await this.multiToken.balanceOf(this.holder.address, multiTokenIds[i])).to.be.bignumber.equal(new BN(0)); + } + + await this.multiToken.safeBatchTransferFrom( + creator, + this.holder.address, + multiTokenIds, + 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( + multiTokenValues[i], + ); + } + }); +}); diff --git a/test/token/ERC20/ERC20.behavior.js b/test/token/ERC20/ERC20.behavior.js new file mode 100644 index 000000000..b6f8617b2 --- /dev/null +++ b/test/token/ERC20/ERC20.behavior.js @@ -0,0 +1,340 @@ +const { BN, constants, expectEvent } = require('@openzeppelin/test-helpers'); +const { expect } = require('chai'); +const { ZERO_ADDRESS, MAX_UINT256 } = constants; + +const { expectRevertCustomError } = require('../../helpers/customError'); + +function shouldBehaveLikeERC20(initialSupply, accounts, opts = {}) { + const [initialHolder, recipient, anotherAccount] = accounts; + const { forcedApproval } = opts; + + describe('total supply', function () { + it('returns the total token value', async function () { + expect(await this.token.totalSupply()).to.be.bignumber.equal(initialSupply); + }); + }); + + describe('balanceOf', function () { + describe('when the requested account has no tokens', function () { + it('returns zero', async function () { + expect(await this.token.balanceOf(anotherAccount)).to.be.bignumber.equal('0'); + }); + }); + + describe('when the requested account has some tokens', function () { + it('returns the total token value', async function () { + expect(await this.token.balanceOf(initialHolder)).to.be.bignumber.equal(initialSupply); + }); + }); + }); + + describe('transfer', function () { + shouldBehaveLikeERC20Transfer(initialHolder, recipient, initialSupply, function (from, to, value) { + return this.token.transfer(to, value, { from }); + }); + }); + + describe('transfer from', function () { + const spender = recipient; + + describe('when the token owner is not the zero address', function () { + const tokenOwner = initialHolder; + + describe('when the recipient is not the zero address', function () { + const to = anotherAccount; + + describe('when the spender has enough allowance', function () { + beforeEach(async function () { + await this.token.approve(spender, initialSupply, { from: initialHolder }); + }); + + describe('when the token owner has enough balance', function () { + const value = initialSupply; + + 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(value); + }); + + it('decreases the spender allowance', async function () { + 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, value, { from: spender }), 'Transfer', { + from: tokenOwner, + to: to, + value: value, + }); + }); + + if (forcedApproval) { + it('emits an approval event', async function () { + expectEvent(await this.token.transferFrom(tokenOwner, to, value, { 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, value, { from: spender }), + 'Approval', + ); + }); + } + }); + + describe('when the token owner does not have enough balance', function () { + const value = initialSupply; + + beforeEach('reducing balance', async function () { + await this.token.transfer(to, 1, { from: tokenOwner }); + }); + + it('reverts', async function () { + await expectRevertCustomError( + this.token.transferFrom(tokenOwner, to, value, { from: spender }), + 'ERC20InsufficientBalance', + [tokenOwner, value - 1, value], + ); + }); + }); + }); + + describe('when the spender does not have enough allowance', function () { + const allowance = initialSupply.subn(1); + + beforeEach(async function () { + await this.token.approve(spender, allowance, { from: tokenOwner }); + }); + + describe('when the token owner has enough balance', function () { + const value = initialSupply; + + it('reverts', async function () { + await expectRevertCustomError( + this.token.transferFrom(tokenOwner, to, value, { from: spender }), + 'ERC20InsufficientAllowance', + [spender, allowance, value], + ); + }); + }); + + describe('when the token owner does not have enough balance', function () { + const value = allowance; + + beforeEach('reducing balance', async function () { + await this.token.transfer(to, 2, { from: tokenOwner }); + }); + + it('reverts', async function () { + await expectRevertCustomError( + this.token.transferFrom(tokenOwner, to, value, { from: spender }), + 'ERC20InsufficientBalance', + [tokenOwner, value - 1, value], + ); + }); + }); + }); + + describe('when the spender has unlimited allowance', function () { + beforeEach(async function () { + await this.token.approve(spender, MAX_UINT256, { from: initialHolder }); + }); + + it('does not decrease the spender allowance', async function () { + await this.token.transferFrom(tokenOwner, to, 1, { from: spender }); + + expect(await this.token.allowance(tokenOwner, spender)).to.be.bignumber.equal(MAX_UINT256); + }); + + it('does not emit an approval event', async function () { + expectEvent.notEmitted(await this.token.transferFrom(tokenOwner, to, 1, { from: spender }), 'Approval'); + }); + }); + }); + + describe('when the recipient is the zero address', function () { + const value = initialSupply; + const to = ZERO_ADDRESS; + + beforeEach(async function () { + await this.token.approve(spender, value, { from: tokenOwner }); + }); + + it('reverts', async function () { + await expectRevertCustomError( + this.token.transferFrom(tokenOwner, to, value, { from: spender }), + 'ERC20InvalidReceiver', + [ZERO_ADDRESS], + ); + }); + }); + }); + + describe('when the token owner is the zero address', function () { + const value = 0; + const tokenOwner = ZERO_ADDRESS; + const to = recipient; + + it('reverts', async function () { + await expectRevertCustomError( + this.token.transferFrom(tokenOwner, to, value, { from: spender }), + 'ERC20InvalidApprover', + [ZERO_ADDRESS], + ); + }); + }); + }); + + describe('approve', function () { + shouldBehaveLikeERC20Approve(initialHolder, recipient, initialSupply, function (owner, spender, value) { + return this.token.approve(spender, value, { from: owner }); + }); + }); +} + +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 value = balance.addn(1); + + it('reverts', async function () { + await expectRevertCustomError(transfer.call(this, from, to, value), 'ERC20InsufficientBalance', [ + from, + balance, + value, + ]); + }); + }); + + describe('when the sender transfers all balance', function () { + const value = balance; + + 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(value); + }); + + it('emits a transfer event', async function () { + expectEvent(await transfer.call(this, from, to, value), 'Transfer', { from, to, value: value }); + }); + }); + + describe('when the sender transfers zero tokens', function () { + const value = new BN('0'); + + 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); + + expect(await this.token.balanceOf(to)).to.be.bignumber.equal('0'); + }); + + it('emits a transfer event', async function () { + expectEvent(await transfer.call(this, from, to, value), 'Transfer', { from, to, value: value }); + }); + }); + }); + + describe('when the recipient is the zero address', function () { + it('reverts', async function () { + await expectRevertCustomError(transfer.call(this, from, ZERO_ADDRESS, balance), 'ERC20InvalidReceiver', [ + ZERO_ADDRESS, + ]); + }); + }); +} + +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 value = supply; + + it('emits an approval event', async function () { + expectEvent(await approve.call(this, owner, spender, value), 'Approval', { + owner: owner, + spender: spender, + value: value, + }); + }); + + 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(value); + }); + }); + + 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 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(value); + }); + }); + }); + + describe('when the sender does not have enough balance', function () { + const value = supply.addn(1); + + it('emits an approval event', async function () { + expectEvent(await approve.call(this, owner, spender, value), 'Approval', { + owner: owner, + spender: spender, + value: value, + }); + }); + + 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(value); + }); + }); + + 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 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(value); + }); + }); + }); + }); + + describe('when the spender is the zero address', function () { + it('reverts', async function () { + await expectRevertCustomError(approve.call(this, owner, ZERO_ADDRESS, supply), `ERC20InvalidSpender`, [ + ZERO_ADDRESS, + ]); + }); + }); +} + +module.exports = { + shouldBehaveLikeERC20, + shouldBehaveLikeERC20Transfer, + shouldBehaveLikeERC20Approve, +}; diff --git a/test/token/ERC20/ERC20.test.js b/test/token/ERC20/ERC20.test.js new file mode 100644 index 000000000..2191fd8cb --- /dev/null +++ b/test/token/ERC20/ERC20.test.js @@ -0,0 +1,202 @@ +const { BN, constants, expectEvent, expectRevert } = require('@openzeppelin/test-helpers'); +const { expect } = require('chai'); +const { ZERO_ADDRESS } = constants; + +const { + shouldBehaveLikeERC20, + shouldBehaveLikeERC20Transfer, + shouldBehaveLikeERC20Approve, +} = require('./ERC20.behavior'); +const { expectRevertCustomError } = require('../../helpers/customError'); + +const TOKENS = [ + { Token: artifacts.require('$ERC20') }, + { Token: artifacts.require('$ERC20ApprovalMock'), forcedApproval: true }, +]; + +contract('ERC20', function (accounts) { + const [initialHolder, recipient] = accounts; + + const name = 'My Token'; + const symbol = 'MTKN'; + const initialSupply = new BN(100); + + 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); + }); + + shouldBehaveLikeERC20(initialSupply, accounts, { forcedApproval }); + + 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('_mint', function () { + const value = new BN(50); + it('rejects a null account', async function () { + await expectRevertCustomError(this.token.$_mint(ZERO_ADDRESS, value), '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('for a non zero account', function () { + beforeEach('minting', async function () { + this.receipt = await this.token.$_mint(recipient, value); + }); + + it('increments totalSupply', async function () { + 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(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(value); + }); + }); + }); + + 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 () { + it('rejects burning more than balance', async function () { + await expectRevertCustomError( + this.token.$_burn(initialHolder, initialSupply.addn(1)), + 'ERC20InsufficientBalance', + [initialHolder, initialSupply, initialSupply.addn(1)], + ); + }); + + const describeBurn = function (description, value) { + describe(description, function () { + beforeEach('burning', async function () { + this.receipt = await this.token.$_burn(initialHolder, value); + }); + + it('decrements totalSupply', async function () { + 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(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(value); + }); + }); + }; + + describeBurn('for entire balance', initialSupply); + describeBurn('for less value than balance', initialSupply.subn(1)); + }); + }); + + describe('_update', function () { + 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, value), 'Transfer', { + from: ZERO_ADDRESS, + to: initialHolder, + value: value, + }); + 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, value), 'Transfer', { + from: initialHolder, + to: ZERO_ADDRESS, + value: value, + }); + 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, value); + + expect(await this.token.totalSupply()).to.be.bignumber.equal(totalSupply); + expectEvent(await this.token.$_update(ZERO_ADDRESS, ZERO_ADDRESS, value), 'Transfer', { + from: ZERO_ADDRESS, + to: ZERO_ADDRESS, + value: value, + }); + }); + }); + + describe('_transfer', function () { + shouldBehaveLikeERC20Transfer(initialHolder, recipient, initialSupply, function (from, to, value) { + return this.token.$_transfer(from, to, value); + }); + + 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, value) { + return this.token.$_approve(owner, spender, value); + }); + + 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/ERC20Burnable.behavior.js b/test/token/ERC20/extensions/ERC20Burnable.behavior.js new file mode 100644 index 000000000..937491bdf --- /dev/null +++ b/test/token/ERC20/extensions/ERC20Burnable.behavior.js @@ -0,0 +1,116 @@ +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 () { + 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 value', function () { + shouldBurn(new BN(100)); + }); + + function shouldBurn(value) { + beforeEach(async function () { + this.receipt = await this.token.burn(value, { from: owner }); + }); + + 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: value, + }); + }); + } + }); + + 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(value, { from: owner }), 'ERC20InsufficientBalance', [ + owner, + initialBalance, + value, + ]); + }); + }); + }); + + describe('burnFrom', function () { + describe('on success', function () { + context('for a zero value', function () { + shouldBurnFrom(new BN(0)); + }); + + context('for a non-zero value', function () { + shouldBurnFrom(new BN(100)); + }); + + 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, value, { from: burner }); + }); + + 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(value)); + }); + + it('emits a transfer event', async function () { + expectEvent(this.receipt, 'Transfer', { + from: owner, + to: ZERO_ADDRESS, + value: value, + }); + }); + } + }); + + 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, value, { from: owner }); + await expectRevertCustomError(this.token.burnFrom(owner, value, { from: burner }), 'ERC20InsufficientBalance', [ + owner, + initialBalance, + value, + ]); + }); + }); + + describe('when the given value is greater than the allowance', function () { + const allowance = new BN(100); + + it('reverts', async function () { + await this.token.approve(burner, allowance, { from: owner }); + await expectRevertCustomError( + this.token.burnFrom(owner, allowance.addn(1), { from: burner }), + 'ERC20InsufficientAllowance', + [burner, allowance, allowance.addn(1)], + ); + }); + }); + }); +} + +module.exports = { + shouldBehaveLikeERC20Burnable, +}; diff --git a/test/token/ERC20/extensions/ERC20Burnable.test.js b/test/token/ERC20/extensions/ERC20Burnable.test.js new file mode 100644 index 000000000..00acc81ed --- /dev/null +++ b/test/token/ERC20/extensions/ERC20Burnable.test.js @@ -0,0 +1,20 @@ +const { BN } = require('@openzeppelin/test-helpers'); + +const { shouldBehaveLikeERC20Burnable } = require('./ERC20Burnable.behavior'); +const ERC20Burnable = artifacts.require('$ERC20Burnable'); + +contract('ERC20Burnable', function (accounts) { + const [owner, ...otherAccounts] = accounts; + + const initialBalance = new BN(1000); + + const name = 'My Token'; + const symbol = 'MTKN'; + + beforeEach(async function () { + this.token = await ERC20Burnable.new(name, symbol, { from: owner }); + await this.token.$_mint(owner, initialBalance); + }); + + shouldBehaveLikeERC20Burnable(owner, initialBalance, otherAccounts); +}); diff --git a/test/token/ERC20/extensions/ERC20Capped.behavior.js b/test/token/ERC20/extensions/ERC20Capped.behavior.js new file mode 100644 index 000000000..5af5c3ddc --- /dev/null +++ b/test/token/ERC20/extensions/ERC20Capped.behavior.js @@ -0,0 +1,31 @@ +const { expect } = require('chai'); +const { expectRevertCustomError } = require('../../../helpers/customError'); + +function shouldBehaveLikeERC20Capped(accounts, cap) { + describe('capped token', function () { + const user = accounts[0]; + + it('starts with the correct cap', async function () { + expect(await this.token.cap()).to.be.bignumber.equal(cap); + }); + + 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 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]); + }); + + it('fails to mint after cap is reached', async function () { + await this.token.$_mint(user, cap); + await expectRevertCustomError(this.token.$_mint(user, 1), 'ERC20ExceededCap', [cap.addn(1), cap]); + }); + }); +} + +module.exports = { + shouldBehaveLikeERC20Capped, +}; diff --git a/test/token/ERC20/extensions/ERC20Capped.test.js b/test/token/ERC20/extensions/ERC20Capped.test.js new file mode 100644 index 000000000..1f4a2bee3 --- /dev/null +++ b/test/token/ERC20/extensions/ERC20Capped.test.js @@ -0,0 +1,24 @@ +const { ether } = require('@openzeppelin/test-helpers'); +const { shouldBehaveLikeERC20Capped } = require('./ERC20Capped.behavior'); +const { expectRevertCustomError } = require('../../../helpers/customError'); + +const ERC20Capped = artifacts.require('$ERC20Capped'); + +contract('ERC20Capped', function (accounts) { + const cap = ether('1000'); + + const name = 'My Token'; + const symbol = 'MTKN'; + + it('requires a non-zero cap', async function () { + await expectRevertCustomError(ERC20Capped.new(name, symbol, 0), 'ERC20InvalidCap', [0]); + }); + + context('once deployed', async function () { + beforeEach(async function () { + this.token = await ERC20Capped.new(name, symbol, cap); + }); + + shouldBehaveLikeERC20Capped(accounts, cap); + }); +}); diff --git a/test/token/ERC20/extensions/ERC20FlashMint.test.js b/test/token/ERC20/extensions/ERC20FlashMint.test.js new file mode 100644 index 000000000..13d5b3ef4 --- /dev/null +++ b/test/token/ERC20/extensions/ERC20FlashMint.test.js @@ -0,0 +1,210 @@ +/* eslint-disable */ + +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'); +const ERC3156FlashBorrowerMock = artifacts.require('ERC3156FlashBorrowerMock'); + +contract('ERC20FlashMint', function (accounts) { + const [initialHolder, other, anotherAccount] = accounts; + + const name = 'My Token'; + const symbol = 'MTKN'; + + const initialSupply = new BN(100); + const loanValue = new BN(10000000000000); + + beforeEach(async function () { + this.token = await ERC20FlashMintMock.new(name, symbol); + await this.token.$_mint(initialHolder, initialSupply); + }); + + describe('maxFlashLoan', function () { + it('token match', async function () { + expect(await this.token.maxFlashLoan(this.token.address)).to.be.bignumber.equal(MAX_UINT256.sub(initialSupply)); + }); + + it('token mismatch', async function () { + expect(await this.token.maxFlashLoan(ZERO_ADDRESS)).to.be.bignumber.equal('0'); + }); + }); + + describe('flashFee', function () { + it('token match', async function () { + 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, loanValue), 'ERC3156UnsupportedToken', [ + ZERO_ADDRESS, + ]); + }); + }); + + describe('flashFeeReceiver', function () { + it('default receiver', async function () { + expect(await this.token.$_flashFeeReceiver()).to.be.eq(ZERO_ADDRESS); + }); + }); + + 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, loanValue, '0x'); + + await expectEvent.inTransaction(tx, this.token, 'Transfer', { + from: ZERO_ADDRESS, + to: receiver.address, + value: loanValue, + }); + await expectEvent.inTransaction(tx, this.token, 'Transfer', { + from: receiver.address, + to: ZERO_ADDRESS, + value: loanValue, + }); + await expectEvent.inTransaction(tx, receiver, 'BalanceOf', { + token: this.token.address, + account: receiver.address, + value: loanValue, + }); + await expectEvent.inTransaction(tx, receiver, 'TotalSupply', { + token: this.token.address, + value: initialSupply.add(loanValue), + }); + + expect(await this.token.totalSupply()).to.be.bignumber.equal(initialSupply); + expect(await this.token.balanceOf(receiver.address)).to.be.bignumber.equal('0'); + expect(await this.token.allowance(receiver.address, this.token.address)).to.be.bignumber.equal('0'); + }); + + it('missing return value', async function () { + const receiver = await ERC3156FlashBorrowerMock.new(false, true); + await expectRevertCustomError( + this.token.flashLoan(receiver.address, this.token.address, loanValue, '0x'), + 'ERC3156InvalidReceiver', + [receiver.address], + ); + }); + + it('missing approval', async function () { + const receiver = await ERC3156FlashBorrowerMock.new(true, false); + await expectRevertCustomError( + this.token.flashLoan(receiver.address, this.token.address, loanValue, '0x'), + 'ERC20InsufficientAllowance', + [this.token.address, 0, loanValue], + ); + }); + + it('unavailable funds', async function () { + 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, loanValue, data), + 'ERC20InsufficientBalance', + [receiver.address, loanValue - 10, loanValue], + ); + }); + + it('more than maxFlashLoan', async function () { + const receiver = await ERC3156FlashBorrowerMock.new(true, true); + const data = this.token.contract.methods.transfer(other, 10).encodeABI(); + // _mint overflow reverts using a panic code. No reason string. + await expectRevert.unspecified(this.token.flashLoan(receiver.address, this.token.address, MAX_UINT256, data)); + }); + + describe('custom flash fee & custom fee receiver', function () { + const receiverInitialBalance = new BN(200000); + const flashFee = new BN(5000); + + beforeEach('init receiver balance & set flash fee', async function () { + this.receiver = await ERC3156FlashBorrowerMock.new(true, true); + const receipt = await this.token.$_mint(this.receiver.address, receiverInitialBalance); + await expectEvent(receipt, 'Transfer', { + from: ZERO_ADDRESS, + to: this.receiver.address, + value: receiverInitialBalance, + }); + 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, 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, loanValue, '0x'); + await expectEvent.inTransaction(tx, this.token, 'Transfer', { + from: ZERO_ADDRESS, + to: this.receiver.address, + value: loanValue, + }); + await expectEvent.inTransaction(tx, this.token, 'Transfer', { + from: this.receiver.address, + to: ZERO_ADDRESS, + value: loanValue.add(flashFee), + }); + await expectEvent.inTransaction(tx, this.receiver, 'BalanceOf', { + token: this.token.address, + account: this.receiver.address, + value: receiverInitialBalance.add(loanValue), + }); + await expectEvent.inTransaction(tx, this.receiver, 'TotalSupply', { + token: this.token.address, + value: initialSupply.add(receiverInitialBalance).add(loanValue), + }); + + expect(await this.token.totalSupply()).to.be.bignumber.equal( + initialSupply.add(receiverInitialBalance).sub(flashFee), + ); + expect(await this.token.balanceOf(this.receiver.address)).to.be.bignumber.equal( + receiverInitialBalance.sub(flashFee), + ); + expect(await this.token.balanceOf(await this.token.$_flashFeeReceiver())).to.be.bignumber.equal('0'); + expect(await this.token.allowance(this.receiver.address, this.token.address)).to.be.bignumber.equal('0'); + }); + + it('custom flash fee receiver', async function () { + const flashFeeReceiverAddress = anotherAccount; + await this.token.setFlashFeeReceiver(flashFeeReceiverAddress); + expect(await this.token.$_flashFeeReceiver()).to.be.eq(flashFeeReceiverAddress); + + expect(await this.token.balanceOf(flashFeeReceiverAddress)).to.be.bignumber.equal('0'); + + 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: loanValue, + }); + await expectEvent.inTransaction(tx, this.token, 'Transfer', { + from: this.receiver.address, + to: ZERO_ADDRESS, + value: loanValue, + }); + await expectEvent.inTransaction(tx, this.token, 'Transfer', { + from: this.receiver.address, + to: flashFeeReceiverAddress, + value: flashFee, + }); + await expectEvent.inTransaction(tx, this.receiver, 'BalanceOf', { + token: this.token.address, + account: this.receiver.address, + value: receiverInitialBalance.add(loanValue), + }); + await expectEvent.inTransaction(tx, this.receiver, 'TotalSupply', { + token: this.token.address, + value: initialSupply.add(receiverInitialBalance).add(loanValue), + }); + + expect(await this.token.totalSupply()).to.be.bignumber.equal(initialSupply.add(receiverInitialBalance)); + expect(await this.token.balanceOf(this.receiver.address)).to.be.bignumber.equal( + receiverInitialBalance.sub(flashFee), + ); + expect(await this.token.balanceOf(flashFeeReceiverAddress)).to.be.bignumber.equal(flashFee); + expect(await this.token.allowance(this.receiver.address, flashFeeReceiverAddress)).to.be.bignumber.equal('0'); + }); + }); + }); +}); diff --git a/test/token/ERC20/extensions/ERC20Pausable.test.js b/test/token/ERC20/extensions/ERC20Pausable.test.js new file mode 100644 index 000000000..92c90b9b8 --- /dev/null +++ b/test/token/ERC20/extensions/ERC20Pausable.test.js @@ -0,0 +1,136 @@ +const { BN } = require('@openzeppelin/test-helpers'); + +const { expect } = require('chai'); +const { expectRevertCustomError } = require('../../../helpers/customError'); + +const ERC20Pausable = artifacts.require('$ERC20Pausable'); + +contract('ERC20Pausable', function (accounts) { + const [holder, recipient, anotherAccount] = accounts; + + const initialSupply = new BN(100); + + const name = 'My Token'; + const symbol = 'MTKN'; + + beforeEach(async function () { + this.token = await ERC20Pausable.new(name, symbol); + await this.token.$_mint(holder, initialSupply); + }); + + describe('pausable token', function () { + describe('transfer', function () { + it('allows to transfer when unpaused', async function () { + await this.token.transfer(recipient, initialSupply, { from: holder }); + + expect(await this.token.balanceOf(holder)).to.be.bignumber.equal('0'); + expect(await this.token.balanceOf(recipient)).to.be.bignumber.equal(initialSupply); + }); + + it('allows to transfer when paused and then unpaused', async function () { + await this.token.$_pause(); + await this.token.$_unpause(); + + await this.token.transfer(recipient, initialSupply, { from: holder }); + + expect(await this.token.balanceOf(holder)).to.be.bignumber.equal('0'); + expect(await this.token.balanceOf(recipient)).to.be.bignumber.equal(initialSupply); + }); + + it('reverts when trying to transfer when paused', async function () { + await this.token.$_pause(); + + await expectRevertCustomError( + this.token.transfer(recipient, initialSupply, { from: holder }), + 'EnforcedPause', + [], + ); + }); + }); + + describe('transfer from', function () { + const allowance = new BN(40); + + beforeEach(async function () { + await this.token.approve(anotherAccount, allowance, { from: holder }); + }); + + it('allows to transfer from when unpaused', async function () { + await this.token.transferFrom(holder, recipient, allowance, { from: anotherAccount }); + + expect(await this.token.balanceOf(recipient)).to.be.bignumber.equal(allowance); + expect(await this.token.balanceOf(holder)).to.be.bignumber.equal(initialSupply.sub(allowance)); + }); + + it('allows to transfer when paused and then unpaused', async function () { + await this.token.$_pause(); + await this.token.$_unpause(); + + await this.token.transferFrom(holder, recipient, allowance, { from: anotherAccount }); + + expect(await this.token.balanceOf(recipient)).to.be.bignumber.equal(allowance); + expect(await this.token.balanceOf(holder)).to.be.bignumber.equal(initialSupply.sub(allowance)); + }); + + it('reverts when trying to transfer from when paused', async function () { + await this.token.$_pause(); + + await expectRevertCustomError( + this.token.transferFrom(holder, recipient, allowance, { from: anotherAccount }), + 'EnforcedPause', + [], + ); + }); + }); + + describe('mint', function () { + const value = new BN('42'); + + it('allows to mint when unpaused', async function () { + await this.token.$_mint(recipient, value); + + 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, value); + + 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, value), 'EnforcedPause', []); + }); + }); + + describe('burn', function () { + const value = new BN('42'); + + it('allows to burn when unpaused', async function () { + await this.token.$_burn(holder, value); + + 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, value); + + 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, value), 'EnforcedPause', []); + }); + }); + }); +}); diff --git a/test/token/ERC20/extensions/ERC20Permit.test.js b/test/token/ERC20/extensions/ERC20Permit.test.js new file mode 100644 index 000000000..388716d53 --- /dev/null +++ b/test/token/ERC20/extensions/ERC20Permit.test.js @@ -0,0 +1,118 @@ +/* eslint-disable */ + +const { BN, constants, time } = require('@openzeppelin/test-helpers'); +const { expect } = require('chai'); +const { MAX_UINT256 } = constants; + +const { fromRpcSig } = require('ethereumjs-util'); +const ethSigUtil = require('eth-sig-util'); +const Wallet = require('ethereumjs-wallet').default; + +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 initialSupply = new BN(100); + + beforeEach(async function () { + this.chainId = await getChainId(); + + this.token = await ERC20Permit.new(name, symbol, name); + await this.token.$_mint(initialHolder, initialSupply); + }); + + it('initial nonce is 0', async function () { + expect(await this.token.nonces(initialHolder)).to.be.bignumber.equal('0'); + }); + + it('domain separator', async function () { + expect(await this.token.DOMAIN_SEPARATOR()).to.equal(await getDomain(this.token).then(domainSeparator)); + }); + + describe('permit', function () { + const wallet = Wallet.generate(); + + const owner = wallet.getAddressString(); + const value = new BN(42); + const nonce = 0; + const maxDeadline = MAX_UINT256; + + const buildData = (contract, deadline = maxDeadline) => + getDomain(contract).then(domain => ({ + primaryType: 'Permit', + types: { EIP712Domain: domainType(domain), Permit }, + domain, + message: { owner, spender, value, nonce, deadline }, + })); + + it('accepts owner signature', async function () { + const { v, r, s } = await buildData(this.token) + .then(data => ethSigUtil.signTypedMessage(wallet.getPrivateKey(), { data })) + .then(fromRpcSig); + + await this.token.permit(owner, spender, value, maxDeadline, v, r, s); + + expect(await this.token.nonces(owner)).to.be.bignumber.equal('1'); + expect(await this.token.allowance(owner, spender)).to.be.bignumber.equal(value); + }); + + it('rejects reused signature', async function () { + 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); + + 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), + 'ERC2612InvalidSigner', + [ethSigUtil.recoverTypedSignature({ data: typedMessage, sig }), owner], + ); + }); + + it('rejects other signature', async function () { + const otherWallet = Wallet.generate(); + + const { v, r, s } = await buildData(this.token) + .then(data => ethSigUtil.signTypedMessage(otherWallet.getPrivateKey(), { data })) + .then(fromRpcSig); + + await expectRevertCustomError( + this.token.permit(owner, spender, value, maxDeadline, v, r, s), + 'ERC2612InvalidSigner', + [await otherWallet.getAddressString(), owner], + ); + }); + + it('rejects expired permit', async function () { + const deadline = (await time.latest()) - time.duration.weeks(1); + + const { v, r, s } = await buildData(this.token, deadline) + .then(data => ethSigUtil.signTypedMessage(wallet.getPrivateKey(), { data })) + .then(fromRpcSig); + + 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 new file mode 100644 index 000000000..faf1a15ad --- /dev/null +++ b/test/token/ERC20/extensions/ERC20Votes.test.js @@ -0,0 +1,593 @@ +/* eslint-disable */ + +const { BN, constants, expectEvent, time } = require('@openzeppelin/test-helpers'); +const { expect } = require('chai'); +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; + +const { batchInBlock } = require('../../../helpers/txpool'); +const { getDomain, domainType } = require('../../../helpers/eip712'); +const { clock, clockFromReceipt } = require('../../../helpers/time'); +const { expectRevertCustomError } = require('../../../helpers/customError'); + +const Delegation = [ + { name: 'delegatee', type: 'address' }, + { name: 'nonce', type: 'uint256' }, + { name: 'expiry', type: 'uint256' }, +]; + +const MODES = { + blocknumber: artifacts.require('$ERC20Votes'), + timestamp: artifacts.require('$ERC20VotesTimestampMock'), +}; + +contract('ERC20Votes', 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('minting restriction', async function () { + const value = web3.utils.toBN(1).shln(208); + await expectRevertCustomError(this.token.$_mint(holder, value), 'ERC20ExceededSafeSupply', [ + value, + value.subn(1), + ]); + }); + + 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, + previousVotes: '0', + newVotes: supply, + }); + + expect(await this.token.delegates(holder)).to.be.equal(holder); + + expect(await this.token.getVotes(holder)).to.be.bignumber.equal(supply); + expect(await this.token.getPastVotes(holder, timepoint - 1)).to.be.bignumber.equal('0'); + await time.advanceBlock(); + expect(await this.token.getPastVotes(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, + previousVotes: '0', + newVotes: supply, + }); + + expect(await this.token.delegates(delegatorAddress)).to.be.equal(delegatorAddress); + + expect(await this.token.getVotes(delegatorAddress)).to.be.bignumber.equal(supply); + expect(await this.token.getPastVotes(delegatorAddress, timepoint - 1)).to.be.bignumber.equal('0'); + await time.advanceBlock(); + expect(await this.token.getPastVotes(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 expectRevertCustomError( + this.token.delegateBySig(delegatorAddress, nonce, MAX_UINT256, v, r, s), + 'InvalidAccountNonce', + [delegatorAddress, nonce + 1], + ); + }); + + 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 sig = await buildData(this.token, { + delegatee: delegatorAddress, + nonce, + expiry: MAX_UINT256, + }).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 expectRevertCustomError( + this.token.delegateBySig(delegatorAddress, nonce + 1, MAX_UINT256, v, r, s), + 'InvalidAccountNonce', + [ethSigUtil.recoverTypedSignature({ data: typedMessage, sig }), 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 expectRevertCustomError( + this.token.delegateBySig(delegatorAddress, nonce, expiry, v, r, s), + 'VotesExpiredSignature', + [expiry], + ); + }); + }); + }); + + 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, + previousVotes: supply, + newVotes: '0', + }); + expectEvent(receipt, 'DelegateVotesChanged', { + delegate: holderDelegatee, + previousVotes: '0', + newVotes: supply, + }); + + expect(await this.token.delegates(holder)).to.be.equal(holderDelegatee); + + expect(await this.token.getVotes(holder)).to.be.bignumber.equal('0'); + expect(await this.token.getVotes(holderDelegatee)).to.be.bignumber.equal(supply); + expect(await this.token.getPastVotes(holder, timepoint - 1)).to.be.bignumber.equal(supply); + expect(await this.token.getPastVotes(holderDelegatee, timepoint - 1)).to.be.bignumber.equal('0'); + await time.advanceBlock(); + expect(await this.token.getPastVotes(holder, timepoint)).to.be.bignumber.equal('0'); + expect(await this.token.getPastVotes(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, + previousVotes: supply, + newVotes: 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, previousVotes: '0', newVotes: '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, + previousVotes: supply, + newVotes: supply.subn(1), + }); + expectEvent(receipt, 'DelegateVotesChanged', { delegate: recipient, previousVotes: '0', newVotes: '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.getVotes(holder)).to.be.bignumber.equal(this.holderVotes); + expect(await this.token.getVotes(recipient)).to.be.bignumber.equal(this.recipientVotes); + + // need to advance 2 blocks to see the effect of a transfer on "getPastVotes" + const timepoint = await clock[mode](); + await time.advanceBlock(); + expect(await this.token.getPastVotes(holder, timepoint)).to.be.bignumber.equal(this.holderVotes); + expect(await this.token.getPastVotes(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.getPastVotes(other1, t1.timepoint)).to.be.bignumber.equal('100'); + expect(await this.token.getPastVotes(other1, t2.timepoint)).to.be.bignumber.equal('90'); + expect(await this.token.getPastVotes(other1, t3.timepoint)).to.be.bignumber.equal('80'); + expect(await this.token.getPastVotes(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('getPastVotes', function () { + it('reverts if block number >= current block', async function () { + 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 () { + expect(await this.token.getPastVotes(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.getPastVotes(other1, timepoint)).to.be.bignumber.equal( + '10000000000000000000000000', + ); + expect(await this.token.getPastVotes(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.getPastVotes(other1, timepoint - 1)).to.be.bignumber.equal('0'); + expect(await this.token.getPastVotes(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.getPastVotes(other1, t1.timepoint - 1)).to.be.bignumber.equal('0'); + expect(await this.token.getPastVotes(other1, t1.timepoint)).to.be.bignumber.equal( + '10000000000000000000000000', + ); + expect(await this.token.getPastVotes(other1, t1.timepoint + 1)).to.be.bignumber.equal( + '10000000000000000000000000', + ); + expect(await this.token.getPastVotes(other1, t2.timepoint)).to.be.bignumber.equal( + '9999999999999999999999990', + ); + expect(await this.token.getPastVotes(other1, t2.timepoint + 1)).to.be.bignumber.equal( + '9999999999999999999999990', + ); + expect(await this.token.getPastVotes(other1, t3.timepoint)).to.be.bignumber.equal( + '9999999999999999999999980', + ); + expect(await this.token.getPastVotes(other1, t3.timepoint + 1)).to.be.bignumber.equal( + '9999999999999999999999980', + ); + expect(await this.token.getPastVotes(other1, t4.timepoint)).to.be.bignumber.equal( + '10000000000000000000000000', + ); + expect(await this.token.getPastVotes(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 () { + 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 () { + 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/extensions/ERC20Wrapper.test.js b/test/token/ERC20/extensions/ERC20Wrapper.test.js new file mode 100644 index 000000000..c54a9e007 --- /dev/null +++ b/test/token/ERC20/extensions/ERC20Wrapper.test.js @@ -0,0 +1,211 @@ +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('ERC20Wrapper', function (accounts) { + const [initialHolder, receiver] = accounts; + + const name = 'My Token'; + const symbol = 'MTKN'; + + const initialSupply = new BN(100); + + beforeEach(async function () { + this.underlying = await ERC20Decimals.new(name, symbol, 9); + await this.underlying.$_mint(initialHolder, initialSupply); + + this.token = await ERC20Wrapper.new(`Wrapped ${name}`, `W${symbol}`, this.underlying.address); + }); + + afterEach(async function () { + expect(await this.underlying.balanceOf(this.token.address)).to.be.bignumber.equal(await this.token.totalSupply()); + }); + + it('has a name', async function () { + expect(await this.token.name()).to.equal(`Wrapped ${name}`); + }); + + it('has a symbol', async function () { + expect(await this.token.symbol()).to.equal(`W${symbol}`); + }); + + it('has the same decimals as the underlying token', async function () { + expect(await this.token.decimals()).to.be.bignumber.equal('9'); + }); + + it('decimals default back to 18 if token has no metadata', async function () { + const noDecimals = await NotAnERC20.new(); + const otherToken = await ERC20Wrapper.new(`Wrapped ${name}`, `W${symbol}`, noDecimals.address); + expect(await otherToken.decimals()).to.be.bignumber.equal('18'); + }); + + it('has underlying', async function () { + expect(await this.token.underlying()).to.be.bignumber.equal(this.underlying.address); + }); + + describe('deposit', function () { + it('valid', async function () { + await this.underlying.approve(this.token.address, initialSupply, { from: initialHolder }); + const { tx } = await this.token.depositFor(initialHolder, initialSupply, { from: initialHolder }); + await expectEvent.inTransaction(tx, this.underlying, 'Transfer', { + from: initialHolder, + to: this.token.address, + value: initialSupply, + }); + await expectEvent.inTransaction(tx, this.token, 'Transfer', { + from: ZERO_ADDRESS, + to: initialHolder, + value: initialSupply, + }); + }); + + it('missing approval', async function () { + await expectRevertCustomError( + this.token.depositFor(initialHolder, initialSupply, { from: initialHolder }), + 'ERC20InsufficientAllowance', + [this.token.address, 0, initialSupply], + ); + }); + + it('missing balance', async function () { + await this.underlying.approve(this.token.address, MAX_UINT256, { from: initialHolder }); + await expectRevertCustomError( + this.token.depositFor(initialHolder, MAX_UINT256, { from: initialHolder }), + 'ERC20InsufficientBalance', + [initialHolder, initialSupply, MAX_UINT256], + ); + }); + + it('to other account', async function () { + await this.underlying.approve(this.token.address, 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, + value: initialSupply, + }); + await expectEvent.inTransaction(tx, this.token, 'Transfer', { + from: ZERO_ADDRESS, + to: receiver, + 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 () { + beforeEach(async function () { + await this.underlying.approve(this.token.address, initialSupply, { from: initialHolder }); + await this.token.depositFor(initialHolder, initialSupply, { from: initialHolder }); + }); + + it('missing balance', async function () { + await expectRevertCustomError( + this.token.withdrawTo(initialHolder, MAX_UINT256, { from: initialHolder }), + 'ERC20InsufficientBalance', + [initialHolder, initialSupply, MAX_UINT256], + ); + }); + + it('valid', async function () { + const value = new BN(42); + + const { tx } = await this.token.withdrawTo(initialHolder, value, { from: initialHolder }); + await expectEvent.inTransaction(tx, this.underlying, 'Transfer', { + from: this.token.address, + to: initialHolder, + value: value, + }); + await expectEvent.inTransaction(tx, this.token, 'Transfer', { + from: initialHolder, + to: ZERO_ADDRESS, + value: value, + }); + }); + + it('entire balance', async function () { + const { tx } = await this.token.withdrawTo(initialHolder, initialSupply, { from: initialHolder }); + await expectEvent.inTransaction(tx, this.underlying, 'Transfer', { + from: this.token.address, + to: initialHolder, + value: initialSupply, + }); + await expectEvent.inTransaction(tx, this.token, 'Transfer', { + from: initialHolder, + to: ZERO_ADDRESS, + value: initialSupply, + }); + }); + + it('to other account', async function () { + const { tx } = await this.token.withdrawTo(receiver, initialSupply, { from: initialHolder }); + await expectEvent.inTransaction(tx, this.underlying, 'Transfer', { + from: this.token.address, + to: receiver, + value: initialSupply, + }); + await expectEvent.inTransaction(tx, this.token, 'Transfer', { + from: initialHolder, + to: ZERO_ADDRESS, + 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 () { + it('nothing to recover', async function () { + await this.underlying.approve(this.token.address, initialSupply, { from: initialHolder }); + await this.token.depositFor(initialHolder, initialSupply, { from: initialHolder }); + + const { tx } = await this.token.$_recover(receiver); + await expectEvent.inTransaction(tx, this.token, 'Transfer', { + from: ZERO_ADDRESS, + to: receiver, + value: '0', + }); + }); + + it('something to recover', async function () { + await this.underlying.transfer(this.token.address, initialSupply, { from: initialHolder }); + + const { tx } = await this.token.$_recover(receiver); + await expectEvent.inTransaction(tx, this.token, 'Transfer', { + from: ZERO_ADDRESS, + to: receiver, + value: initialSupply, + }); + }); + }); + + describe('erc20 behaviour', function () { + beforeEach(async function () { + await this.underlying.approve(this.token.address, initialSupply, { from: initialHolder }); + await this.token.depositFor(initialHolder, initialSupply, { from: initialHolder }); + }); + + shouldBehaveLikeERC20(initialSupply, accounts); + }); +}); diff --git a/test/token/ERC20/extensions/ERC4626.t.sol b/test/token/ERC20/extensions/ERC4626.t.sol new file mode 100644 index 000000000..72b0daca1 --- /dev/null +++ b/test/token/ERC20/extensions/ERC4626.t.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {ERC4626Test} from "erc4626-tests/ERC4626.test.sol"; + +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import {ERC4626} from "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol"; + +import {ERC20Mock} from "@openzeppelin/contracts/mocks/token/ERC20Mock.sol"; +import {ERC4626Mock} from "@openzeppelin/contracts/mocks/token/ERC4626Mock.sol"; +import {ERC4626OffsetMock} from "@openzeppelin/contracts/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(_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 new file mode 100644 index 000000000..499cf0628 --- /dev/null +++ b/test/token/ERC20/extensions/ERC4626.test.js @@ -0,0 +1,1109 @@ +const { constants, expectEvent, expectRevert } = require('@openzeppelin/test-helpers'); +const { expect } = require('chai'); + +const { Enum } = require('../../../helpers/enums'); +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'); +const ERC20Reentrant = artifacts.require('$ERC20Reentrant'); + +contract('ERC4626', function (accounts) { + const [holder, recipient, spender, other, user1, user2] = accounts; + + const name = 'My Token'; + const symbol = 'MTKN'; + const decimals = web3.utils.toBN(18); + + it('inherit decimals if from asset', async function () { + for (const decimals of [0, 9, 12, 18, 36].map(web3.utils.toBN)) { + const token = await ERC20Decimals.new('', '', decimals); + const vault = await ERC4626.new('', '', token.address); + expect(await vault.decimals()).to.be.bignumber.equal(decimals); + } + }); + + 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)', + ); + } + }); + + describe('reentrancy', async function () { + const reenterType = Enum('No', 'Before', 'After'); + + const value = web3.utils.toBN(1000000000000000000); + const reenterValue = 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, 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); + }); + + // 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, reenterValue); + + // Schedules a reentrancy from the token contract + await token.scheduleReenter( + reenterType.Before, + vault.address, + vault.contract.methods.deposit(reenterValue, holder).encodeABI(), + ); + + // Initial share price + 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(value, holder, { from: holder }); + + // Main deposit event + await expectEvent(receipt, 'Deposit', { + sender: holder, + owner: holder, + assets: value, + shares: sharesForDeposit, + }); + // Reentrant deposit event β†’ uses the same price + await expectEvent(receipt, 'Deposit', { + sender: token.address, + owner: holder, + assets: reenterValue, + shares: sharesForReenter, + }); + + // Assert prices is kept + const sharesAfter = await vault.previewDeposit(value, { 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 `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(reenterValue, holder, token.address).encodeABI(), + ); + + // Initial share price + 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(value, holder, holder, { from: holder }); + + // Main withdraw event + await expectEvent(receipt, 'Withdraw', { + sender: holder, + receiver: holder, + owner: holder, + assets: value, + shares: sharesForWithdraw, + }); + // Reentrant withdraw event β†’ uses the same price + await expectEvent(receipt, 'Withdraw', { + sender: token.address, + receiver: holder, + owner: token.address, + assets: reenterValue, + shares: sharesForReenter, + }); + + // Assert price is kept + 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 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, reenterValue).encodeABI(), + ); + + // Price before + const sharesBefore = await vault.previewDeposit(value); + + // Deposit, triggering the _beforeTokenTransfer hook + const receipt = await vault.deposit(value, holder, { from: holder }); + + // Price is as previewed + await expectEvent(receipt, 'Deposit', { + sender: holder, + owner: holder, + assets: value, + shares: sharesBefore, + }); + + // Price was modified during reentrancy + 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 value of assets) + it('share price change during reentracy does not affect withdraw', async function () { + 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, reenterValue).encodeABI(), + ); + + // Price before + const sharesBefore = await vault.previewWithdraw(value); + + // Withdraw, triggering the _afterTokenTransfer hook + 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: value, + shares: sharesBefore, + }); + + // Price was modified during reentrancy + const sharesAfter = await vault.previewWithdraw(value); + expect(sharesAfter).to.be.bignumber.gt(sharesBefore); + }); + }); + + 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); + + const virtualAssets = web3.utils.toBN(1); + const virtualShares = web3.utils.toBN(10).pow(offset); + + describe(`offset: ${offset}`, function () { + beforeEach(async function () { + this.token = await ERC20Decimals.new(name, symbol, decimals); + this.vault = await ERC4626OffsetMock.new(name + ' Vault', symbol + 'V', this.token.address, offset); + + await this.token.$_mint(holder, constants.MAX_INT256); // 50% of maximum + await this.token.approve(this.vault.address, constants.MAX_UINT256, { from: holder }); + await this.vault.approve(spender, constants.MAX_UINT256, { from: holder }); + }); + + it('metadata', async function () { + expect(await this.vault.name()).to.be.equal(name + ' Vault'); + expect(await this.vault.symbol()).to.be.equal(symbol + 'V'); + expect(await this.vault.decimals()).to.be.bignumber.equal(decimals.add(offset)); + expect(await this.vault.asset()).to.be.equal(this.token.address); + }); + + describe('empty vault: no assets & no shares', function () { + it('status', async function () { + expect(await this.vault.totalAssets()).to.be.bignumber.equal('0'); + }); + + it('deposit', async function () { + expect(await this.vault.maxDeposit(holder)).to.be.bignumber.equal(constants.MAX_UINT256); + expect(await this.vault.previewDeposit(parseToken(1))).to.be.bignumber.equal(parseShare(1)); + + const { tx } = await this.vault.deposit(parseToken(1), recipient, { from: holder }); + + await expectEvent.inTransaction(tx, this.token, 'Transfer', { + from: holder, + to: this.vault.address, + value: parseToken(1), + }); + + await expectEvent.inTransaction(tx, this.vault, 'Transfer', { + from: constants.ZERO_ADDRESS, + to: recipient, + value: parseShare(1), + }); + + await expectEvent.inTransaction(tx, this.vault, 'Deposit', { + sender: holder, + owner: recipient, + assets: parseToken(1), + shares: parseShare(1), + }); + }); + + it('mint', async function () { + expect(await this.vault.maxMint(holder)).to.be.bignumber.equal(constants.MAX_UINT256); + expect(await this.vault.previewMint(parseShare(1))).to.be.bignumber.equal(parseToken(1)); + + const { tx } = await this.vault.mint(parseShare(1), recipient, { from: holder }); + + await expectEvent.inTransaction(tx, this.token, 'Transfer', { + from: holder, + to: this.vault.address, + value: parseToken(1), + }); + + await expectEvent.inTransaction(tx, this.vault, 'Transfer', { + from: constants.ZERO_ADDRESS, + to: recipient, + value: parseShare(1), + }); + + await expectEvent.inTransaction(tx, this.vault, 'Deposit', { + sender: holder, + owner: recipient, + assets: parseToken(1), + shares: parseShare(1), + }); + }); + + it('withdraw', async function () { + expect(await this.vault.maxWithdraw(holder)).to.be.bignumber.equal('0'); + expect(await this.vault.previewWithdraw('0')).to.be.bignumber.equal('0'); + + const { tx } = await this.vault.withdraw('0', recipient, holder, { from: holder }); + + await expectEvent.inTransaction(tx, this.token, 'Transfer', { + from: this.vault.address, + to: recipient, + value: '0', + }); + + await expectEvent.inTransaction(tx, this.vault, 'Transfer', { + from: holder, + to: constants.ZERO_ADDRESS, + value: '0', + }); + + await expectEvent.inTransaction(tx, this.vault, 'Withdraw', { + sender: holder, + receiver: recipient, + owner: holder, + assets: '0', + shares: '0', + }); + }); + + it('redeem', async function () { + expect(await this.vault.maxRedeem(holder)).to.be.bignumber.equal('0'); + expect(await this.vault.previewRedeem('0')).to.be.bignumber.equal('0'); + + const { tx } = await this.vault.redeem('0', recipient, holder, { from: holder }); + + await expectEvent.inTransaction(tx, this.token, 'Transfer', { + from: this.vault.address, + to: recipient, + value: '0', + }); + + await expectEvent.inTransaction(tx, this.vault, 'Transfer', { + from: holder, + to: constants.ZERO_ADDRESS, + value: '0', + }); + + await expectEvent.inTransaction(tx, this.vault, 'Withdraw', { + sender: holder, + receiver: recipient, + owner: holder, + assets: '0', + shares: '0', + }); + }); + }); + + describe('inflation attack: offset price by direct deposit of assets', function () { + beforeEach(async function () { + // Donate 1 token to the vault to offset the price + await this.token.$_mint(this.vault.address, parseToken(1)); + }); + + it('status', async function () { + expect(await this.vault.totalSupply()).to.be.bignumber.equal('0'); + expect(await this.vault.totalAssets()).to.be.bignumber.equal(parseToken(1)); + }); + + /** + * | offset | deposited assets | redeemable assets | + * |--------|----------------------|----------------------| + * | 0 | 1.000000000000000000 | 0. | + * | 6 | 1.000000000000000000 | 0.999999000000000000 | + * | 18 | 1.000000000000000000 | 0.999999999999999999 | + * + * Attack is possible, but made difficult by the offset. For the attack to be successful + * the attacker needs to frontrun a deposit 10**offset times bigger than what the victim + * was trying to deposit + */ + it('deposit', async function () { + const effectiveAssets = await this.vault.totalAssets().then(x => x.add(virtualAssets)); + const effectiveShares = await this.vault.totalSupply().then(x => x.add(virtualShares)); + + const depositAssets = parseToken(1); + const expectedShares = depositAssets.mul(effectiveShares).div(effectiveAssets); + + expect(await this.vault.maxDeposit(holder)).to.be.bignumber.equal(constants.MAX_UINT256); + expect(await this.vault.previewDeposit(depositAssets)).to.be.bignumber.equal(expectedShares); + + const { tx } = await this.vault.deposit(depositAssets, recipient, { from: holder }); + + await expectEvent.inTransaction(tx, this.token, 'Transfer', { + from: holder, + to: this.vault.address, + value: depositAssets, + }); + + await expectEvent.inTransaction(tx, this.vault, 'Transfer', { + from: constants.ZERO_ADDRESS, + to: recipient, + value: expectedShares, + }); + + await expectEvent.inTransaction(tx, this.vault, 'Deposit', { + sender: holder, + owner: recipient, + assets: depositAssets, + shares: expectedShares, + }); + }); + + /** + * | offset | deposited assets | redeemable assets | + * |--------|----------------------|----------------------| + * | 0 | 1000000000000000001. | 1000000000000000001. | + * | 6 | 1000000000000000001. | 1000000000000000001. | + * | 18 | 1000000000000000001. | 1000000000000000001. | + * + * Using mint protects against inflation attack, but makes minting shares very expensive. + * The ER20 allowance for the underlying asset is needed to protect the user from (too) + * large deposits. + */ + it('mint', async function () { + const effectiveAssets = await this.vault.totalAssets().then(x => x.add(virtualAssets)); + const effectiveShares = await this.vault.totalSupply().then(x => x.add(virtualShares)); + + const mintShares = parseShare(1); + const expectedAssets = mintShares.mul(effectiveAssets).div(effectiveShares); + + expect(await this.vault.maxMint(holder)).to.be.bignumber.equal(constants.MAX_UINT256); + expect(await this.vault.previewMint(mintShares)).to.be.bignumber.equal(expectedAssets); + + const { tx } = await this.vault.mint(mintShares, recipient, { from: holder }); + + await expectEvent.inTransaction(tx, this.token, 'Transfer', { + from: holder, + to: this.vault.address, + value: expectedAssets, + }); + + await expectEvent.inTransaction(tx, this.vault, 'Transfer', { + from: constants.ZERO_ADDRESS, + to: recipient, + value: mintShares, + }); + + await expectEvent.inTransaction(tx, this.vault, 'Deposit', { + sender: holder, + owner: recipient, + assets: expectedAssets, + shares: mintShares, + }); + }); + + it('withdraw', async function () { + expect(await this.vault.maxWithdraw(holder)).to.be.bignumber.equal('0'); + expect(await this.vault.previewWithdraw('0')).to.be.bignumber.equal('0'); + + const { tx } = await this.vault.withdraw('0', recipient, holder, { from: holder }); + + await expectEvent.inTransaction(tx, this.token, 'Transfer', { + from: this.vault.address, + to: recipient, + value: '0', + }); + + await expectEvent.inTransaction(tx, this.vault, 'Transfer', { + from: holder, + to: constants.ZERO_ADDRESS, + value: '0', + }); + + await expectEvent.inTransaction(tx, this.vault, 'Withdraw', { + sender: holder, + receiver: recipient, + owner: holder, + assets: '0', + shares: '0', + }); + }); + + it('redeem', async function () { + expect(await this.vault.maxRedeem(holder)).to.be.bignumber.equal('0'); + expect(await this.vault.previewRedeem('0')).to.be.bignumber.equal('0'); + + const { tx } = await this.vault.redeem('0', recipient, holder, { from: holder }); + + await expectEvent.inTransaction(tx, this.token, 'Transfer', { + from: this.vault.address, + to: recipient, + value: '0', + }); + + await expectEvent.inTransaction(tx, this.vault, 'Transfer', { + from: holder, + to: constants.ZERO_ADDRESS, + value: '0', + }); + + await expectEvent.inTransaction(tx, this.vault, 'Withdraw', { + sender: holder, + receiver: recipient, + owner: holder, + assets: '0', + shares: '0', + }); + }); + }); + + describe('full vault: assets & shares', function () { + beforeEach(async function () { + // Add 1 token of underlying asset and 100 shares to the vault + await this.token.$_mint(this.vault.address, parseToken(1)); + await this.vault.$_mint(holder, parseShare(100)); + }); + + it('status', async function () { + expect(await this.vault.totalSupply()).to.be.bignumber.equal(parseShare(100)); + expect(await this.vault.totalAssets()).to.be.bignumber.equal(parseToken(1)); + }); + + /** + * | offset | deposited assets | redeemable assets | + * |--------|--------------------- |----------------------| + * | 0 | 1.000000000000000000 | 0.999999999999999999 | + * | 6 | 1.000000000000000000 | 0.999999999999999999 | + * | 18 | 1.000000000000000000 | 0.999999999999999999 | + * + * Virtual shares & assets captures part of the value + */ + it('deposit', async function () { + const effectiveAssets = await this.vault.totalAssets().then(x => x.add(virtualAssets)); + const effectiveShares = await this.vault.totalSupply().then(x => x.add(virtualShares)); + + const depositAssets = parseToken(1); + const expectedShares = depositAssets.mul(effectiveShares).div(effectiveAssets); + + expect(await this.vault.maxDeposit(holder)).to.be.bignumber.equal(constants.MAX_UINT256); + expect(await this.vault.previewDeposit(depositAssets)).to.be.bignumber.equal(expectedShares); + + const { tx } = await this.vault.deposit(depositAssets, recipient, { from: holder }); + + await expectEvent.inTransaction(tx, this.token, 'Transfer', { + from: holder, + to: this.vault.address, + value: depositAssets, + }); + + await expectEvent.inTransaction(tx, this.vault, 'Transfer', { + from: constants.ZERO_ADDRESS, + to: recipient, + value: expectedShares, + }); + + await expectEvent.inTransaction(tx, this.vault, 'Deposit', { + sender: holder, + owner: recipient, + assets: depositAssets, + shares: expectedShares, + }); + }); + + /** + * | offset | deposited assets | redeemable assets | + * |--------|--------------------- |----------------------| + * | 0 | 0.010000000000000001 | 0.010000000000000000 | + * | 6 | 0.010000000000000001 | 0.010000000000000000 | + * | 18 | 0.010000000000000001 | 0.010000000000000000 | + * + * Virtual shares & assets captures part of the value + */ + it('mint', async function () { + const effectiveAssets = await this.vault.totalAssets().then(x => x.add(virtualAssets)); + const effectiveShares = await this.vault.totalSupply().then(x => x.add(virtualShares)); + + const mintShares = parseShare(1); + const expectedAssets = mintShares.mul(effectiveAssets).div(effectiveShares).addn(1); // add for the rounding + + expect(await this.vault.maxMint(holder)).to.be.bignumber.equal(constants.MAX_UINT256); + expect(await this.vault.previewMint(mintShares)).to.be.bignumber.equal(expectedAssets); + + const { tx } = await this.vault.mint(mintShares, recipient, { from: holder }); + + await expectEvent.inTransaction(tx, this.token, 'Transfer', { + from: holder, + to: this.vault.address, + value: expectedAssets, + }); + + await expectEvent.inTransaction(tx, this.vault, 'Transfer', { + from: constants.ZERO_ADDRESS, + to: recipient, + value: mintShares, + }); + + await expectEvent.inTransaction(tx, this.vault, 'Deposit', { + sender: holder, + owner: recipient, + assets: expectedAssets, + shares: mintShares, + }); + }); + + it('withdraw', async function () { + const effectiveAssets = await this.vault.totalAssets().then(x => x.add(virtualAssets)); + const effectiveShares = await this.vault.totalSupply().then(x => x.add(virtualShares)); + + const withdrawAssets = parseToken(1); + const expectedShares = withdrawAssets.mul(effectiveShares).div(effectiveAssets).addn(1); // add for the rounding + + expect(await this.vault.maxWithdraw(holder)).to.be.bignumber.equal(withdrawAssets); + expect(await this.vault.previewWithdraw(withdrawAssets)).to.be.bignumber.equal(expectedShares); + + const { tx } = await this.vault.withdraw(withdrawAssets, recipient, holder, { from: holder }); + + await expectEvent.inTransaction(tx, this.token, 'Transfer', { + from: this.vault.address, + to: recipient, + value: withdrawAssets, + }); + + await expectEvent.inTransaction(tx, this.vault, 'Transfer', { + from: holder, + to: constants.ZERO_ADDRESS, + value: expectedShares, + }); + + await expectEvent.inTransaction(tx, this.vault, 'Withdraw', { + sender: holder, + receiver: recipient, + owner: holder, + assets: withdrawAssets, + shares: expectedShares, + }); + }); + + it('withdraw with approval', async function () { + const assets = await this.vault.previewWithdraw(parseToken(1)); + await expectRevertCustomError( + this.vault.withdraw(parseToken(1), recipient, holder, { from: other }), + 'ERC20InsufficientAllowance', + [other, 0, assets], + ); + + await this.vault.withdraw(parseToken(1), recipient, holder, { from: spender }); + }); + + it('redeem', async function () { + const effectiveAssets = await this.vault.totalAssets().then(x => x.add(virtualAssets)); + const effectiveShares = await this.vault.totalSupply().then(x => x.add(virtualShares)); + + const redeemShares = parseShare(100); + const expectedAssets = redeemShares.mul(effectiveAssets).div(effectiveShares); + + expect(await this.vault.maxRedeem(holder)).to.be.bignumber.equal(redeemShares); + expect(await this.vault.previewRedeem(redeemShares)).to.be.bignumber.equal(expectedAssets); + + const { tx } = await this.vault.redeem(redeemShares, recipient, holder, { from: holder }); + + await expectEvent.inTransaction(tx, this.token, 'Transfer', { + from: this.vault.address, + to: recipient, + value: expectedAssets, + }); + + await expectEvent.inTransaction(tx, this.vault, 'Transfer', { + from: holder, + to: constants.ZERO_ADDRESS, + value: redeemShares, + }); + + await expectEvent.inTransaction(tx, this.vault, 'Withdraw', { + sender: holder, + receiver: recipient, + owner: holder, + assets: expectedAssets, + shares: redeemShares, + }); + }); + + it('redeem with approval', async function () { + await expectRevertCustomError( + this.vault.redeem(parseShare(100), recipient, holder, { from: other }), + 'ERC20InsufficientAllowance', + [other, 0, parseShare(100)], + ); + + await this.vault.redeem(parseShare(100), recipient, holder, { from: spender }); + }); + }); + }); + } + + describe('ERC4626Fees', function () { + const feeBasisPoints = web3.utils.toBN(5e3); + const valueWithoutFees = web3.utils.toBN(10000); + const fees = valueWithoutFees.mul(feeBasisPoints).divn(1e4); + const valueWithFees = valueWithoutFees.add(fees); + + describe('input fees', function () { + beforeEach(async function () { + this.token = await ERC20Decimals.new(name, symbol, 18); + this.vault = await ERC4626FeesMock.new( + name + ' Vault', + symbol + 'V', + this.token.address, + feeBasisPoints, + other, + 0, + constants.ZERO_ADDRESS, + ); + + await this.token.$_mint(holder, constants.MAX_INT256); + await this.token.approve(this.vault.address, constants.MAX_INT256, { from: holder }); + }); + + it('deposit', async function () { + 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(valueWithoutFees)).to.be.bignumber.equal(valueWithFees); + ({ tx: this.tx } = await this.vault.mint(valueWithoutFees, recipient, { from: holder })); + }); + + afterEach(async function () { + // get total + await expectEvent.inTransaction(this.tx, this.token, 'Transfer', { + from: holder, + to: this.vault.address, + value: valueWithFees, + }); + + // redirect fees + await expectEvent.inTransaction(this.tx, this.token, 'Transfer', { + from: this.vault.address, + to: other, + value: fees, + }); + + // mint shares + await expectEvent.inTransaction(this.tx, this.vault, 'Transfer', { + from: constants.ZERO_ADDRESS, + to: recipient, + value: valueWithoutFees, + }); + + // deposit event + await expectEvent.inTransaction(this.tx, this.vault, 'Deposit', { + sender: holder, + owner: recipient, + assets: valueWithFees, + shares: valueWithoutFees, + }); + }); + }); + + describe('output fees', function () { + beforeEach(async function () { + this.token = await ERC20Decimals.new(name, symbol, 18); + this.vault = await ERC4626FeesMock.new( + name + ' Vault', + symbol + 'V', + this.token.address, + 0, + constants.ZERO_ADDRESS, + 5e3, // 5% + other, + ); + + await this.token.$_mint(this.vault.address, constants.MAX_INT256); + await this.vault.$_mint(holder, constants.MAX_INT256); + }); + + it('redeem', async function () { + 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(valueWithoutFees)).to.be.bignumber.equal(valueWithFees); + ({ tx: this.tx } = await this.vault.withdraw(valueWithoutFees, recipient, holder, { from: holder })); + }); + + afterEach(async function () { + // withdraw principal + await expectEvent.inTransaction(this.tx, this.token, 'Transfer', { + from: this.vault.address, + to: recipient, + value: valueWithoutFees, + }); + + // redirect fees + await expectEvent.inTransaction(this.tx, this.token, 'Transfer', { + from: this.vault.address, + to: other, + value: fees, + }); + + // mint shares + await expectEvent.inTransaction(this.tx, this.vault, 'Transfer', { + from: holder, + to: constants.ZERO_ADDRESS, + value: valueWithFees, + }); + + // withdraw event + await expectEvent.inTransaction(this.tx, this.vault, 'Withdraw', { + sender: holder, + receiver: recipient, + owner: holder, + assets: valueWithoutFees, + shares: valueWithFees, + }); + }); + }); + }); + + /// Scenario inspired by solmate ERC4626 tests: + /// https://github.com/transmissions11/solmate/blob/main/src/test/ERC4626.t.sol + it('multiple mint, deposit, redeem & withdrawal', async function () { + // test designed with both asset using similar decimals + this.token = await ERC20Decimals.new(name, symbol, 18); + this.vault = await ERC4626.new(name + ' Vault', symbol + 'V', this.token.address); + + await this.token.$_mint(user1, 4000); + await this.token.$_mint(user2, 7001); + await this.token.approve(this.vault.address, 4000, { from: user1 }); + await this.token.approve(this.vault.address, 7001, { from: user2 }); + + // 1. Alice mints 2000 shares (costs 2000 tokens) + { + const { tx } = await this.vault.mint(2000, user1, { from: user1 }); + await expectEvent.inTransaction(tx, this.token, 'Transfer', { + from: user1, + to: this.vault.address, + value: '2000', + }); + await expectEvent.inTransaction(tx, this.vault, 'Transfer', { + from: constants.ZERO_ADDRESS, + to: user1, + value: '2000', + }); + + expect(await this.vault.previewDeposit(2000)).to.be.bignumber.equal('2000'); + expect(await this.vault.balanceOf(user1)).to.be.bignumber.equal('2000'); + 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'); + } + + // 2. Bob deposits 4000 tokens (mints 4000 shares) + { + const { tx } = await this.vault.mint(4000, user2, { from: user2 }); + await expectEvent.inTransaction(tx, this.token, 'Transfer', { + from: user2, + to: this.vault.address, + value: '4000', + }); + await expectEvent.inTransaction(tx, this.vault, 'Transfer', { + from: constants.ZERO_ADDRESS, + to: user2, + value: '4000', + }); + + expect(await this.vault.previewDeposit(4000)).to.be.bignumber.equal('4000'); + expect(await this.vault.balanceOf(user1)).to.be.bignumber.equal('2000'); + 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'); + } + + // 3. Vault mutates by +3000 tokens (simulated yield returned from strategy) + await this.token.$_mint(this.vault.address, 3000); + + expect(await this.vault.balanceOf(user1)).to.be.bignumber.equal('2000'); + 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'); + + // 4. Alice deposits 2000 tokens (mints 1333 shares) + { + const { tx } = await this.vault.deposit(2000, user1, { from: user1 }); + await expectEvent.inTransaction(tx, this.token, 'Transfer', { + from: user1, + to: this.vault.address, + value: '2000', + }); + await expectEvent.inTransaction(tx, this.vault, 'Transfer', { + from: constants.ZERO_ADDRESS, + to: user1, + value: '1333', + }); + + expect(await this.vault.balanceOf(user1)).to.be.bignumber.equal('3333'); + 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'); + } + + // 5. Bob mints 2000 shares (costs 3001 assets) + // 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', { + from: user2, + to: this.vault.address, + value: '3000', // used to be 3001 + }); + await expectEvent.inTransaction(tx, this.vault, 'Transfer', { + from: constants.ZERO_ADDRESS, + to: user2, + value: '2000', + }); + + expect(await this.vault.balanceOf(user1)).to.be.bignumber.equal('3333'); + 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 + } + + // 6. Vault mutates by +3000 tokens + // NOTE: Vault holds 17001 tokens, but sum of assetsOf() is 17000. + await this.token.$_mint(this.vault.address, 3000); + + expect(await this.vault.balanceOf(user1)).to.be.bignumber.equal('3333'); + 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 + + // 7. Alice redeem 1333 shares (2428 assets) + { + const { tx } = await this.vault.redeem(1333, user1, user1, { from: user1 }); + await expectEvent.inTransaction(tx, this.vault, 'Transfer', { + from: user1, + to: constants.ZERO_ADDRESS, + value: '1333', + }); + await expectEvent.inTransaction(tx, this.token, 'Transfer', { + from: this.vault.address, + to: user1, + value: '2427', // used to be 2428 + }); + + expect(await this.vault.balanceOf(user1)).to.be.bignumber.equal('2000'); + 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'); + } + + // 8. Bob withdraws 2929 assets (1608 shares) + { + const { tx } = await this.vault.withdraw(2929, user2, user2, { from: user2 }); + await expectEvent.inTransaction(tx, this.vault, 'Transfer', { + from: user2, + to: constants.ZERO_ADDRESS, + value: '1608', + }); + await expectEvent.inTransaction(tx, this.token, 'Transfer', { + from: this.vault.address, + to: user2, + value: '2929', + }); + + expect(await this.vault.balanceOf(user1)).to.be.bignumber.equal('2000'); + 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'); + } + + // 9. Alice withdraws 3643 assets (2000 shares) + // 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', { + from: user1, + to: constants.ZERO_ADDRESS, + value: '2000', + }); + await expectEvent.inTransaction(tx, this.token, 'Transfer', { + from: this.vault.address, + to: user1, + value: '3643', + }); + + expect(await this.vault.balanceOf(user1)).to.be.bignumber.equal('0'); + 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'); + } + + // 10. Bob redeem 4392 shares (8001 tokens) + { + const { tx } = await this.vault.redeem(4392, user2, user2, { from: user2 }); + await expectEvent.inTransaction(tx, this.vault, 'Transfer', { + from: user2, + to: constants.ZERO_ADDRESS, + value: '4392', + }); + await expectEvent.inTransaction(tx, this.token, 'Transfer', { + from: this.vault.address, + to: user2, + value: '8000', // used to be 8001 + }); + + expect(await this.vault.balanceOf(user1)).to.be.bignumber.equal('0'); + 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/ERC20/utils/SafeERC20.test.js b/test/token/ERC20/utils/SafeERC20.test.js new file mode 100644 index 000000000..4ff27f14d --- /dev/null +++ b/test/token/ERC20/utils/SafeERC20.test.js @@ -0,0 +1,240 @@ +const { constants, expectEvent, expectRevert } = require('@openzeppelin/test-helpers'); + +const SafeERC20 = artifacts.require('$SafeERC20'); +const ERC20ReturnFalseMock = artifacts.require('$ERC20ReturnFalseMock'); +const ERC20ReturnTrueMock = artifacts.require('$ERC20'); // default implementation returns true +const ERC20NoReturnMock = artifacts.require('$ERC20NoReturnMock'); +const ERC20ForceApproveMock = artifacts.require('$ERC20ForceApproveMock'); + +const { expectRevertCustomError } = require('../../../helpers/customError'); + +const name = 'ERC20Mock'; +const symbol = 'ERC20Mock'; + +contract('SafeERC20', function (accounts) { + const [hasNoCode, receiver, spender] = accounts; + + before(async function () { + this.mock = await SafeERC20.new(); + }); + + describe('with address that has no contract code', function () { + beforeEach(async function () { + this.token = { address: hasNoCode }; + }); + + 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 () { + beforeEach(async function () { + this.token = await ERC20ReturnFalseMock.new(name, symbol); + }); + + 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 () { + beforeEach(async function () { + this.token = await ERC20ReturnTrueMock.new(name, symbol); + }); + + shouldOnlyRevertOnErrors(accounts); + }); + + describe('with token that returns no boolean values', function () { + beforeEach(async function () { + this.token = await ERC20NoReturnMock.new(name, symbol); + }); + + shouldOnlyRevertOnErrors(accounts); + }); + + describe('with usdt approval beaviour', function () { + const spender = hasNoCode; + + beforeEach(async function () { + this.token = await ERC20ForceApproveMock.new(name, symbol); + }); + + describe('with initial approval', function () { + beforeEach(async function () { + await this.token.$_approve(this.mock.address, spender, 100); + }); + + it('safeIncreaseAllowance works', async function () { + await this.mock.$safeIncreaseAllowance(this.token.address, spender, 10); + expect(this.token.allowance(this.mock.address, spender, 90)); + }); + + 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)); + }); + }); + }); +}); + +function shouldOnlyRevertOnErrors([owner, receiver, spender]) { + describe('transfers', function () { + beforeEach(async function () { + await this.token.$_mint(owner, 100); + await this.token.$_mint(this.mock.address, 100); + await this.token.approve(this.mock.address, constants.MAX_UINT256, { from: owner }); + }); + + it("doesn't revert on transfer", async function () { + const { tx } = await this.mock.$safeTransfer(this.token.address, receiver, 10); + await expectEvent.inTransaction(tx, this.token, 'Transfer', { + from: this.mock.address, + to: receiver, + value: '10', + }); + }); + + it("doesn't revert on transferFrom", async function () { + const { tx } = await this.mock.$safeTransferFrom(this.token.address, owner, receiver, 10); + await expectEvent.inTransaction(tx, this.token, 'Transfer', { + from: owner, + to: receiver, + value: '10', + }); + }); + }); + + describe('approvals', function () { + context('with zero allowance', function () { + beforeEach(async function () { + await this.token.$_approve(this.mock.address, spender, 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'); + }); + + it("doesn't revert when force approving a zero allowance", async function () { + await this.mock.$forceApprove(this.token.address, spender, 0); + expect(await this.token.allowance(this.mock.address, spender)).to.be.bignumber.equal('0'); + }); + + it("doesn't revert when increasing the allowance", async function () { + await this.mock.$safeIncreaseAllowance(this.token.address, spender, 10); + expect(await this.token.allowance(this.mock.address, spender)).to.be.bignumber.equal('10'); + }); + + it('reverts when decreasing the allowance', async function () { + await expectRevertCustomError( + this.mock.$safeDecreaseAllowance(this.token.address, spender, 10), + 'SafeERC20FailedDecreaseAllowance', + [spender, 0, 10], + ); + }); + }); + + context('with non-zero allowance', function () { + beforeEach(async function () { + await this.token.$_approve(this.mock.address, spender, 100); + }); + + 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'); + }); + + it("doesn't revert when force approving a zero allowance", async function () { + await this.mock.$forceApprove(this.token.address, spender, 0); + expect(await this.token.allowance(this.mock.address, spender)).to.be.bignumber.equal('0'); + }); + + it("doesn't revert when increasing the allowance", async function () { + await this.mock.$safeIncreaseAllowance(this.token.address, spender, 10); + expect(await this.token.allowance(this.mock.address, spender)).to.be.bignumber.equal('110'); + }); + + it("doesn't revert when decreasing the allowance to a positive value", async function () { + await this.mock.$safeDecreaseAllowance(this.token.address, spender, 50); + expect(await this.token.allowance(this.mock.address, spender)).to.be.bignumber.equal('50'); + }); + + it('reverts when decreasing the allowance to a negative value', async function () { + await expectRevertCustomError( + this.mock.$safeDecreaseAllowance(this.token.address, spender, 200), + 'SafeERC20FailedDecreaseAllowance', + [spender, 100, 200], + ); + }); + }); + }); +} diff --git a/test/token/ERC721/ERC721.behavior.js b/test/token/ERC721/ERC721.behavior.js new file mode 100644 index 000000000..10f848265 --- /dev/null +++ b/test/token/ERC721/ERC721.behavior.js @@ -0,0 +1,978 @@ +const { BN, constants, expectEvent, expectRevert } = require('@openzeppelin/test-helpers'); +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 ERC721ReceiverMock = artifacts.require('ERC721ReceiverMock'); +const NonERC721ReceiverMock = artifacts.require('CallReceiverMock'); + +const RevertType = Enum('None', 'RevertWithoutMessage', 'RevertWithMessage', 'RevertWithCustomError', 'Panic'); + +const firstTokenId = new BN('5042'); +const secondTokenId = new BN('79217'); +const nonExistentTokenId = new BN('13'); +const fourthTokenId = new BN(4); +const baseURI = 'https://api.example.com/v1/'; + +const RECEIVER_MAGIC_VALUE = '0x150b7a02'; + +function shouldBehaveLikeERC721(owner, newOwner, approved, anotherApproved, operator, other) { + shouldSupportInterfaces(['ERC165', 'ERC721']); + + context('with minted tokens', function () { + beforeEach(async function () { + await this.token.$_mint(owner, firstTokenId); + await this.token.$_mint(owner, secondTokenId); + this.toWhom = other; // default to other for toWhom in context-dependent tests + }); + + describe('balanceOf', function () { + context('when the given address owns some tokens', function () { + it('returns the amount of tokens owned by the given address', async function () { + expect(await this.token.balanceOf(owner)).to.be.bignumber.equal('2'); + }); + }); + + context('when the given address does not own any tokens', function () { + it('returns 0', async function () { + expect(await this.token.balanceOf(other)).to.be.bignumber.equal('0'); + }); + }); + + context('when querying the zero address', function () { + it('throws', async function () { + await expectRevertCustomError(this.token.balanceOf(ZERO_ADDRESS), 'ERC721InvalidOwner', [ZERO_ADDRESS]); + }); + }); + }); + + describe('ownerOf', function () { + context('when the given token ID was tracked by this token', function () { + const tokenId = firstTokenId; + + it('returns the owner of the given token ID', async function () { + expect(await this.token.ownerOf(tokenId)).to.be.equal(owner); + }); + }); + + context('when the given token ID was not tracked by this token', function () { + const tokenId = nonExistentTokenId; + + it('reverts', async function () { + await expectRevertCustomError(this.token.ownerOf(tokenId), 'ERC721NonexistentToken', [tokenId]); + }); + }); + }); + + describe('transfers', function () { + const tokenId = firstTokenId; + const data = '0x42'; + + let receipt = null; + + beforeEach(async function () { + await this.token.approve(approved, tokenId, { from: owner }); + await this.token.setApprovalForAll(operator, true, { from: owner }); + }); + + const transferWasSuccessful = function ({ owner, tokenId }) { + it('transfers the ownership of the given token ID to the given address', async function () { + expect(await this.token.ownerOf(tokenId)).to.be.equal(this.toWhom); + }); + + it('emits a Transfer event', async function () { + expectEvent(receipt, 'Transfer', { from: owner, to: this.toWhom, tokenId: tokenId }); + }); + + it('clears the approval for the token ID with no event', async function () { + expect(await this.token.getApproved(tokenId)).to.be.equal(ZERO_ADDRESS); + expectEvent.notEmitted(receipt, 'Approval'); + }); + + it('adjusts owners balances', async function () { + expect(await this.token.balanceOf(owner)).to.be.bignumber.equal('1'); + }); + + it('adjusts owners tokens by index', async function () { + if (!this.token.tokenOfOwnerByIndex) return; + + expect(await this.token.tokenOfOwnerByIndex(this.toWhom, 0)).to.be.bignumber.equal(tokenId); + + expect(await this.token.tokenOfOwnerByIndex(owner, 0)).to.be.bignumber.not.equal(tokenId); + }); + }; + + const shouldTransferTokensByUsers = function (transferFunction, opts = {}) { + context('when called by the owner', function () { + beforeEach(async function () { + receipt = await transferFunction.call(this, owner, this.toWhom, tokenId, { from: owner }); + }); + transferWasSuccessful({ owner, tokenId, approved }); + }); + + context('when called by the approved individual', function () { + beforeEach(async function () { + receipt = await transferFunction.call(this, owner, this.toWhom, tokenId, { from: approved }); + }); + transferWasSuccessful({ owner, tokenId, approved }); + }); + + context('when called by the operator', function () { + beforeEach(async function () { + receipt = await transferFunction.call(this, owner, this.toWhom, tokenId, { from: operator }); + }); + transferWasSuccessful({ owner, tokenId, approved }); + }); + + context('when called by the owner without an approved user', function () { + beforeEach(async function () { + await this.token.approve(ZERO_ADDRESS, tokenId, { from: owner }); + receipt = await transferFunction.call(this, owner, this.toWhom, tokenId, { from: operator }); + }); + transferWasSuccessful({ owner, tokenId, approved: null }); + }); + + context('when sent to the owner', function () { + beforeEach(async function () { + receipt = await transferFunction.call(this, owner, owner, tokenId, { from: owner }); + }); + + it('keeps ownership of the token', async function () { + expect(await this.token.ownerOf(tokenId)).to.be.equal(owner); + }); + + it('clears the approval for the token ID', async function () { + expect(await this.token.getApproved(tokenId)).to.be.equal(ZERO_ADDRESS); + }); + + it('emits only a transfer event', async function () { + expectEvent(receipt, 'Transfer', { + from: owner, + to: owner, + tokenId: tokenId, + }); + }); + + it('keeps the owner balance', async function () { + expect(await this.token.balanceOf(owner)).to.be.bignumber.equal('2'); + }); + + it('keeps same tokens by index', async function () { + if (!this.token.tokenOfOwnerByIndex) return; + const tokensListed = await Promise.all([0, 1].map(i => this.token.tokenOfOwnerByIndex(owner, i))); + expect(tokensListed.map(t => t.toNumber())).to.have.members([ + firstTokenId.toNumber(), + secondTokenId.toNumber(), + ]); + }); + }); + + context('when the address of the previous owner is incorrect', function () { + it('reverts', async function () { + await expectRevertCustomError( + transferFunction.call(this, other, other, tokenId, { from: owner }), + 'ERC721IncorrectOwner', + [other, tokenId, owner], + ); + }); + }); + + context('when the sender is not authorized for the token id', function () { + if (opts.unrestricted) { + it('does not revert', async function () { + await transferFunction.call(this, owner, other, tokenId, { from: other }); + }); + } else { + it('reverts', async function () { + await expectRevertCustomError( + transferFunction.call(this, owner, other, tokenId, { from: other }), + 'ERC721InsufficientApproval', + [other, tokenId], + ); + }); + } + }); + + context('when the given token ID does not exist', function () { + it('reverts', async function () { + await expectRevertCustomError( + transferFunction.call(this, owner, other, nonExistentTokenId, { from: owner }), + 'ERC721NonexistentToken', + [nonExistentTokenId], + ); + }); + }); + + context('when the address to transfer the token to is the zero address', function () { + it('reverts', async function () { + await expectRevertCustomError( + transferFunction.call(this, owner, ZERO_ADDRESS, tokenId, { from: owner }), + 'ERC721InvalidReceiver', + [ZERO_ADDRESS], + ); + }); + }); + }; + + const shouldTransferSafely = function (transferFun, data, opts = {}) { + describe('to a user account', function () { + shouldTransferTokensByUsers(transferFun, opts); + }); + + describe('to a valid receiver contract', function () { + beforeEach(async function () { + this.receiver = await ERC721ReceiverMock.new(RECEIVER_MAGIC_VALUE, RevertType.None); + this.toWhom = this.receiver.address; + }); + + shouldTransferTokensByUsers(transferFun, opts); + + it('calls onERC721Received', async function () { + const receipt = await transferFun.call(this, owner, this.receiver.address, tokenId, { from: owner }); + + await expectEvent.inTransaction(receipt.tx, ERC721ReceiverMock, 'Received', { + operator: owner, + from: owner, + tokenId: tokenId, + data: data, + }); + }); + + it('calls onERC721Received from approved', async function () { + const receipt = await transferFun.call(this, owner, this.receiver.address, tokenId, { from: approved }); + + await expectEvent.inTransaction(receipt.tx, ERC721ReceiverMock, 'Received', { + operator: approved, + from: owner, + tokenId: tokenId, + data: data, + }); + }); + + describe('with an invalid token id', function () { + it('reverts', async function () { + await expectRevertCustomError( + transferFun.call(this, owner, this.receiver.address, nonExistentTokenId, { from: owner }), + 'ERC721NonexistentToken', + [nonExistentTokenId], + ); + }); + }); + }); + }; + + for (const { fnName, opts } of [ + { fnName: 'transferFrom', opts: {} }, + { fnName: '$_transfer', opts: { unrestricted: true } }, + ]) { + describe(`via ${fnName}`, function () { + shouldTransferTokensByUsers(function (from, to, tokenId, opts) { + return this.token[fnName](from, to, tokenId, opts); + }, opts); + }); + } + + for (const { fnName, opts } of [ + { fnName: 'safeTransferFrom', opts: {} }, + { fnName: '$_safeTransfer', opts: { unrestricted: true } }, + ]) { + describe(`via ${fnName}`, function () { + const safeTransferFromWithData = function (from, to, tokenId, opts) { + return this.token.methods[fnName + '(address,address,uint256,bytes)'](from, to, tokenId, data, opts); + }; + + const safeTransferFromWithoutData = function (from, to, tokenId, opts) { + return this.token.methods[fnName + '(address,address,uint256)'](from, to, tokenId, opts); + }; + + describe('with data', function () { + shouldTransferSafely(safeTransferFromWithData, data, opts); + }); + + describe('without data', function () { + shouldTransferSafely(safeTransferFromWithoutData, null, opts); + }); + + describe('to a receiver contract returning unexpected value', function () { + it('reverts', async function () { + const invalidReceiver = await ERC721ReceiverMock.new('0x42', RevertType.None); + await expectRevertCustomError( + this.token.methods[fnName + '(address,address,uint256)'](owner, invalidReceiver.address, tokenId, { + from: owner, + }), + 'ERC721InvalidReceiver', + [invalidReceiver.address], + ); + }); + }); + + describe('to a receiver contract that reverts with message', function () { + it('reverts', async function () { + const revertingReceiver = await ERC721ReceiverMock.new( + RECEIVER_MAGIC_VALUE, + RevertType.RevertWithMessage, + ); + await expectRevert( + this.token.methods[fnName + '(address,address,uint256)'](owner, revertingReceiver.address, tokenId, { + from: owner, + }), + 'ERC721ReceiverMock: reverting', + ); + }); + }); + + describe('to a receiver contract that reverts without message', function () { + it('reverts', async function () { + const revertingReceiver = await ERC721ReceiverMock.new( + RECEIVER_MAGIC_VALUE, + RevertType.RevertWithoutMessage, + ); + await expectRevertCustomError( + this.token.methods[fnName + '(address,address,uint256)'](owner, revertingReceiver.address, tokenId, { + from: owner, + }), + 'ERC721InvalidReceiver', + [revertingReceiver.address], + ); + }); + }); + + 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.methods[fnName + '(address,address,uint256)'](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, RevertType.Panic); + await expectRevert.unspecified( + this.token.methods[fnName + '(address,address,uint256)'](owner, revertingReceiver.address, tokenId, { + from: owner, + }), + ); + }); + }); + + describe('to a contract that does not implement the required function', function () { + it('reverts', async function () { + const nonReceiver = await NonERC721ReceiverMock.new(); + await expectRevertCustomError( + this.token.methods[fnName + '(address,address,uint256)'](owner, nonReceiver.address, tokenId, { + from: owner, + }), + 'ERC721InvalidReceiver', + [nonReceiver.address], + ); + }); + }); + }); + } + }); + + describe('safe mint', function () { + const tokenId = fourthTokenId; + const data = '0x42'; + + 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, RevertType.None); + const receipt = await this.token.$_safeMint(this.receiver.address, tokenId, data); + + await expectEvent.inTransaction(receipt.tx, ERC721ReceiverMock, 'Received', { + from: ZERO_ADDRESS, + tokenId: tokenId, + data: data, + }); + }); + + it('calls onERC721Received β€” without data', async function () { + 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', { + from: ZERO_ADDRESS, + tokenId: tokenId, + }); + }); + + context('to a receiver contract returning unexpected value', function () { + it('reverts', async function () { + const invalidReceiver = await ERC721ReceiverMock.new('0x42', RevertType.None); + await expectRevertCustomError( + this.token.$_safeMint(invalidReceiver.address, tokenId), + 'ERC721InvalidReceiver', + [invalidReceiver.address], + ); + }); + }); + + context('to a receiver contract that reverts with message', function () { + it('reverts', async function () { + const revertingReceiver = await ERC721ReceiverMock.new(RECEIVER_MAGIC_VALUE, RevertType.RevertWithMessage); + await expectRevert( + this.token.$_safeMint(revertingReceiver.address, tokenId), + 'ERC721ReceiverMock: reverting', + ); + }); + }); + + context('to a receiver contract that reverts without message', function () { + it('reverts', async function () { + const revertingReceiver = await ERC721ReceiverMock.new( + RECEIVER_MAGIC_VALUE, + RevertType.RevertWithoutMessage, + ); + await expectRevertCustomError( + this.token.$_safeMint(revertingReceiver.address, tokenId), + 'ERC721InvalidReceiver', + [revertingReceiver.address], + ); + }); + }); + + 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, RevertType.Panic); + await expectRevert.unspecified(this.token.$_safeMint(revertingReceiver.address, tokenId)); + }); + }); + + context('to a contract that does not implement the required function', function () { + it('reverts', async function () { + const nonReceiver = await NonERC721ReceiverMock.new(); + await expectRevertCustomError( + this.token.$_safeMint(nonReceiver.address, tokenId), + 'ERC721InvalidReceiver', + [nonReceiver.address], + ); + }); + }); + }); + }); + + describe('approve', function () { + const tokenId = firstTokenId; + + let receipt = null; + + const itClearsApproval = function () { + it('clears approval for the token', async function () { + expect(await this.token.getApproved(tokenId)).to.be.equal(ZERO_ADDRESS); + }); + }; + + const itApproves = function (address) { + it('sets the approval for the target address', async function () { + expect(await this.token.getApproved(tokenId)).to.be.equal(address); + }); + }; + + const itEmitsApprovalEvent = function (address) { + it('emits an approval event', async function () { + expectEvent(receipt, 'Approval', { + owner: owner, + approved: address, + tokenId: tokenId, + }); + }); + }; + + context('when clearing approval', function () { + context('when there was no prior approval', function () { + beforeEach(async function () { + receipt = await this.token.approve(ZERO_ADDRESS, tokenId, { from: owner }); + }); + + itClearsApproval(); + itEmitsApprovalEvent(ZERO_ADDRESS); + }); + + context('when there was a prior approval', function () { + beforeEach(async function () { + await this.token.approve(approved, tokenId, { from: owner }); + receipt = await this.token.approve(ZERO_ADDRESS, tokenId, { from: owner }); + }); + + itClearsApproval(); + itEmitsApprovalEvent(ZERO_ADDRESS); + }); + }); + + context('when approving a non-zero address', function () { + context('when there was no prior approval', function () { + beforeEach(async function () { + receipt = await this.token.approve(approved, tokenId, { from: owner }); + }); + + itApproves(approved); + itEmitsApprovalEvent(approved); + }); + + context('when there was a prior approval to the same address', function () { + beforeEach(async function () { + await this.token.approve(approved, tokenId, { from: owner }); + receipt = await this.token.approve(approved, tokenId, { from: owner }); + }); + + itApproves(approved); + itEmitsApprovalEvent(approved); + }); + + context('when there was a prior approval to a different address', function () { + beforeEach(async function () { + await this.token.approve(anotherApproved, tokenId, { from: owner }); + receipt = await this.token.approve(anotherApproved, tokenId, { from: owner }); + }); + + itApproves(anotherApproved); + itEmitsApprovalEvent(anotherApproved); + }); + }); + + context('when the sender does not own the given token ID', function () { + it('reverts', async function () { + await expectRevertCustomError( + this.token.approve(approved, tokenId, { from: other }), + 'ERC721InvalidApprover', + [other], + ); + }); + }); + + 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 expectRevertCustomError( + this.token.approve(anotherApproved, tokenId, { from: approved }), + 'ERC721InvalidApprover', + [approved], + ); + }); + }); + + context('when the sender is an operator', function () { + beforeEach(async function () { + await this.token.setApprovalForAll(operator, true, { from: owner }); + receipt = await this.token.approve(approved, tokenId, { from: operator }); + }); + + itApproves(approved); + itEmitsApprovalEvent(approved); + }); + + context('when the given token ID does not exist', function () { + it('reverts', async function () { + await expectRevertCustomError( + this.token.approve(approved, nonExistentTokenId, { from: operator }), + 'ERC721NonexistentToken', + [nonExistentTokenId], + ); + }); + }); + }); + + describe('setApprovalForAll', function () { + context('when the operator willing to approve is not the owner', function () { + context('when there is no operator approval set by the sender', function () { + it('approves the operator', async function () { + await this.token.setApprovalForAll(operator, true, { from: owner }); + + expect(await this.token.isApprovedForAll(owner, operator)).to.equal(true); + }); + + it('emits an approval event', async function () { + const receipt = await this.token.setApprovalForAll(operator, true, { from: owner }); + + expectEvent(receipt, 'ApprovalForAll', { + owner: owner, + operator: operator, + approved: true, + }); + }); + }); + + context('when the operator was set as not approved', function () { + beforeEach(async function () { + await this.token.setApprovalForAll(operator, false, { from: owner }); + }); + + it('approves the operator', async function () { + await this.token.setApprovalForAll(operator, true, { from: owner }); + + expect(await this.token.isApprovedForAll(owner, operator)).to.equal(true); + }); + + it('emits an approval event', async function () { + const receipt = await this.token.setApprovalForAll(operator, true, { from: owner }); + + expectEvent(receipt, 'ApprovalForAll', { + owner: owner, + operator: operator, + approved: true, + }); + }); + + it('can unset the operator approval', async function () { + await this.token.setApprovalForAll(operator, false, { from: owner }); + + expect(await this.token.isApprovedForAll(owner, operator)).to.equal(false); + }); + }); + + context('when the operator was already approved', function () { + beforeEach(async function () { + await this.token.setApprovalForAll(operator, true, { from: owner }); + }); + + it('keeps the approval to the given address', async function () { + await this.token.setApprovalForAll(operator, true, { from: owner }); + + expect(await this.token.isApprovedForAll(owner, operator)).to.equal(true); + }); + + it('emits an approval event', async function () { + const receipt = await this.token.setApprovalForAll(operator, true, { from: owner }); + + expectEvent(receipt, 'ApprovalForAll', { + owner: owner, + operator: operator, + approved: true, + }); + }); + }); + }); + + context('when the operator is address zero', function () { + it('reverts', async function () { + await expectRevertCustomError( + this.token.setApprovalForAll(constants.ZERO_ADDRESS, true, { from: owner }), + 'ERC721InvalidOperator', + [constants.ZERO_ADDRESS], + ); + }); + }); + }); + + describe('getApproved', async function () { + context('when token is not minted', async function () { + it('reverts', async function () { + await expectRevertCustomError(this.token.getApproved(nonExistentTokenId), 'ERC721NonexistentToken', [ + nonExistentTokenId, + ]); + }); + }); + + context('when token has been minted ', async function () { + it('should return the zero address', async function () { + expect(await this.token.getApproved(firstTokenId)).to.be.equal(ZERO_ADDRESS); + }); + + context('when account has been approved', async function () { + beforeEach(async function () { + await this.token.approve(approved, firstTokenId, { from: owner }); + }); + + it('returns approved account', async function () { + expect(await this.token.getApproved(firstTokenId)).to.be.equal(approved); + }); + }); + }); + }); + }); + + describe('_mint(address, uint256)', function () { + it('reverts with a null destination address', async function () { + await expectRevertCustomError(this.token.$_mint(ZERO_ADDRESS, firstTokenId), 'ERC721InvalidReceiver', [ + ZERO_ADDRESS, + ]); + }); + + context('with minted token', async function () { + beforeEach(async function () { + this.receipt = await this.token.$_mint(owner, firstTokenId); + }); + + it('emits a Transfer event', function () { + expectEvent(this.receipt, 'Transfer', { from: ZERO_ADDRESS, to: owner, tokenId: firstTokenId }); + }); + + it('creates the token', async function () { + expect(await this.token.balanceOf(owner)).to.be.bignumber.equal('1'); + expect(await this.token.ownerOf(firstTokenId)).to.equal(owner); + }); + + it('reverts when adding a token id that already exists', async function () { + 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 expectRevertCustomError(this.token.$_burn(nonExistentTokenId), 'ERC721NonexistentToken', [ + nonExistentTokenId, + ]); + }); + + context('with minted tokens', function () { + beforeEach(async function () { + await this.token.$_mint(owner, firstTokenId); + await this.token.$_mint(owner, secondTokenId); + }); + + context('with burnt token', function () { + beforeEach(async function () { + this.receipt = await this.token.$_burn(firstTokenId); + }); + + it('emits a Transfer event', function () { + expectEvent(this.receipt, 'Transfer', { from: owner, to: ZERO_ADDRESS, tokenId: firstTokenId }); + }); + + it('deletes the token', async function () { + expect(await this.token.balanceOf(owner)).to.be.bignumber.equal('1'); + await expectRevertCustomError(this.token.ownerOf(firstTokenId), 'ERC721NonexistentToken', [firstTokenId]); + }); + + it('reverts when burning a token id that has been deleted', async function () { + await expectRevertCustomError(this.token.$_burn(firstTokenId), 'ERC721NonexistentToken', [firstTokenId]); + }); + }); + }); + }); +} + +function shouldBehaveLikeERC721Enumerable(owner, newOwner, approved, anotherApproved, operator, other) { + shouldSupportInterfaces(['ERC721Enumerable']); + + context('with minted tokens', function () { + beforeEach(async function () { + await this.token.$_mint(owner, firstTokenId); + await this.token.$_mint(owner, secondTokenId); + this.toWhom = other; // default to other for toWhom in context-dependent tests + }); + + describe('totalSupply', function () { + it('returns total token supply', async function () { + expect(await this.token.totalSupply()).to.be.bignumber.equal('2'); + }); + }); + + describe('tokenOfOwnerByIndex', function () { + describe('when the given index is lower than the amount of tokens owned by the given address', function () { + it('returns the token ID placed at the given index', async function () { + expect(await this.token.tokenOfOwnerByIndex(owner, 0)).to.be.bignumber.equal(firstTokenId); + }); + }); + + describe('when the index is greater than or equal to the total tokens owned by the given address', function () { + it('reverts', async function () { + 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 expectRevertCustomError(this.token.tokenOfOwnerByIndex(other, 0), 'ERC721OutOfBoundsIndex', [other, 0]); + }); + }); + + describe('after transferring all tokens to another user', function () { + beforeEach(async function () { + await this.token.transferFrom(owner, other, firstTokenId, { from: owner }); + await this.token.transferFrom(owner, other, secondTokenId, { from: owner }); + }); + + it('returns correct token IDs for target', async function () { + expect(await this.token.balanceOf(other)).to.be.bignumber.equal('2'); + const tokensListed = await Promise.all([0, 1].map(i => this.token.tokenOfOwnerByIndex(other, i))); + expect(tokensListed.map(t => t.toNumber())).to.have.members([ + firstTokenId.toNumber(), + secondTokenId.toNumber(), + ]); + }); + + it('returns empty collection for original owner', async function () { + expect(await this.token.balanceOf(owner)).to.be.bignumber.equal('0'); + await expectRevertCustomError(this.token.tokenOfOwnerByIndex(owner, 0), 'ERC721OutOfBoundsIndex', [owner, 0]); + }); + }); + }); + + describe('tokenByIndex', function () { + it('returns all tokens', async function () { + const tokensListed = await Promise.all([0, 1].map(i => this.token.tokenByIndex(i))); + expect(tokensListed.map(t => t.toNumber())).to.have.members([ + firstTokenId.toNumber(), + secondTokenId.toNumber(), + ]); + }); + + it('reverts if index is greater than supply', async function () { + await expectRevertCustomError(this.token.tokenByIndex(2), 'ERC721OutOfBoundsIndex', [ZERO_ADDRESS, 2]); + }); + + [firstTokenId, secondTokenId].forEach(function (tokenId) { + it(`returns all tokens after burning token ${tokenId} and minting new tokens`, async function () { + const newTokenId = new BN(300); + const anotherNewTokenId = new BN(400); + + await this.token.$_burn(tokenId); + await this.token.$_mint(newOwner, newTokenId); + await this.token.$_mint(newOwner, anotherNewTokenId); + + expect(await this.token.totalSupply()).to.be.bignumber.equal('3'); + + const tokensListed = await Promise.all([0, 1, 2].map(i => this.token.tokenByIndex(i))); + const expectedTokens = [firstTokenId, secondTokenId, newTokenId, anotherNewTokenId].filter( + x => x !== tokenId, + ); + expect(tokensListed.map(t => t.toNumber())).to.have.members(expectedTokens.map(t => t.toNumber())); + }); + }); + }); + }); + + describe('_mint(address, uint256)', function () { + it('reverts with a null destination address', async function () { + await expectRevertCustomError(this.token.$_mint(ZERO_ADDRESS, firstTokenId), 'ERC721InvalidReceiver', [ + ZERO_ADDRESS, + ]); + }); + + context('with minted token', async function () { + beforeEach(async function () { + this.receipt = await this.token.$_mint(owner, firstTokenId); + }); + + it('adjusts owner tokens by index', async function () { + expect(await this.token.tokenOfOwnerByIndex(owner, 0)).to.be.bignumber.equal(firstTokenId); + }); + + it('adjusts all tokens list', async function () { + expect(await this.token.tokenByIndex(0)).to.be.bignumber.equal(firstTokenId); + }); + }); + }); + + describe('_burn', function () { + it('reverts when burning a non-existent token id', async function () { + await expectRevertCustomError(this.token.$_burn(firstTokenId), 'ERC721NonexistentToken', [firstTokenId]); + }); + + context('with minted tokens', function () { + beforeEach(async function () { + await this.token.$_mint(owner, firstTokenId); + await this.token.$_mint(owner, secondTokenId); + }); + + context('with burnt token', function () { + beforeEach(async function () { + this.receipt = await this.token.$_burn(firstTokenId); + }); + + it('removes that token from the token list of the owner', async function () { + expect(await this.token.tokenOfOwnerByIndex(owner, 0)).to.be.bignumber.equal(secondTokenId); + }); + + it('adjusts all tokens list', async function () { + expect(await this.token.tokenByIndex(0)).to.be.bignumber.equal(secondTokenId); + }); + + it('burns all tokens', async function () { + await this.token.$_burn(secondTokenId, { from: owner }); + expect(await this.token.totalSupply()).to.be.bignumber.equal('0'); + await expectRevertCustomError(this.token.tokenByIndex(0), 'ERC721OutOfBoundsIndex', [ZERO_ADDRESS, 0]); + }); + }); + }); + }); +} + +function shouldBehaveLikeERC721Metadata(name, symbol, owner) { + shouldSupportInterfaces(['ERC721Metadata']); + + describe('metadata', function () { + it('has a name', async function () { + expect(await this.token.name()).to.be.equal(name); + }); + + it('has a symbol', async function () { + expect(await this.token.symbol()).to.be.equal(symbol); + }); + + describe('token URI', function () { + beforeEach(async function () { + await this.token.$_mint(owner, firstTokenId); + }); + + it('return empty string by default', async function () { + expect(await this.token.tokenURI(firstTokenId)).to.be.equal(''); + }); + + it('reverts when queried for non existent token id', async function () { + await expectRevertCustomError(this.token.tokenURI(nonExistentTokenId), 'ERC721NonexistentToken', [ + nonExistentTokenId, + ]); + }); + + describe('base URI', function () { + beforeEach(function () { + if (this.token.setBaseURI === undefined) { + this.skip(); + } + }); + + it('base URI can be set', async function () { + await this.token.setBaseURI(baseURI); + expect(await this.token.baseURI()).to.equal(baseURI); + }); + + it('base URI is added as a prefix to the token URI', async function () { + await this.token.setBaseURI(baseURI); + expect(await this.token.tokenURI(firstTokenId)).to.be.equal(baseURI + firstTokenId.toString()); + }); + + it('token URI can be changed by changing the base URI', async function () { + await this.token.setBaseURI(baseURI); + const newBaseURI = 'https://api.example.com/v2/'; + await this.token.setBaseURI(newBaseURI); + expect(await this.token.tokenURI(firstTokenId)).to.be.equal(newBaseURI + firstTokenId.toString()); + }); + }); + }); + }); +} + +module.exports = { + shouldBehaveLikeERC721, + shouldBehaveLikeERC721Enumerable, + shouldBehaveLikeERC721Metadata, +}; diff --git a/test/token/ERC721/ERC721.test.js b/test/token/ERC721/ERC721.test.js new file mode 100644 index 000000000..372dd5069 --- /dev/null +++ b/test/token/ERC721/ERC721.test.js @@ -0,0 +1,15 @@ +const { shouldBehaveLikeERC721, shouldBehaveLikeERC721Metadata } = require('./ERC721.behavior'); + +const ERC721 = artifacts.require('$ERC721'); + +contract('ERC721', function (accounts) { + const name = 'Non Fungible Token'; + const symbol = 'NFT'; + + beforeEach(async function () { + this.token = await ERC721.new(name, symbol); + }); + + shouldBehaveLikeERC721(...accounts); + shouldBehaveLikeERC721Metadata(name, symbol, ...accounts); +}); diff --git a/test/token/ERC721/ERC721Enumerable.test.js b/test/token/ERC721/ERC721Enumerable.test.js new file mode 100644 index 000000000..31c28d177 --- /dev/null +++ b/test/token/ERC721/ERC721Enumerable.test.js @@ -0,0 +1,20 @@ +const { + shouldBehaveLikeERC721, + shouldBehaveLikeERC721Metadata, + shouldBehaveLikeERC721Enumerable, +} = require('./ERC721.behavior'); + +const ERC721Enumerable = artifacts.require('$ERC721Enumerable'); + +contract('ERC721Enumerable', function (accounts) { + const name = 'Non Fungible Token'; + const symbol = 'NFT'; + + beforeEach(async function () { + this.token = await ERC721Enumerable.new(name, symbol); + }); + + 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 new file mode 100644 index 000000000..df059e090 --- /dev/null +++ b/test/token/ERC721/extensions/ERC721Burnable.test.js @@ -0,0 +1,82 @@ +const { BN, constants, expectEvent } = require('@openzeppelin/test-helpers'); + +const { expect } = require('chai'); +const { expectRevertCustomError } = require('../../../helpers/customError'); + +const ERC721Burnable = artifacts.require('$ERC721Burnable'); + +contract('ERC721Burnable', function (accounts) { + const [owner, approved, another] = accounts; + + const firstTokenId = new BN(1); + const secondTokenId = new BN(2); + const unknownTokenId = new BN(3); + + const name = 'Non Fungible Token'; + const symbol = 'NFT'; + + beforeEach(async function () { + this.token = await ERC721Burnable.new(name, symbol); + }); + + describe('like a burnable ERC721', function () { + beforeEach(async function () { + await this.token.$_mint(owner, firstTokenId); + await this.token.$_mint(owner, secondTokenId); + }); + + describe('burn', function () { + const tokenId = firstTokenId; + let receipt = null; + + describe('when successful', function () { + beforeEach(async function () { + receipt = await this.token.burn(tokenId, { from: owner }); + }); + + it('burns the given token ID and adjusts the balance of the owner', async function () { + await expectRevertCustomError(this.token.ownerOf(tokenId), 'ERC721NonexistentToken', [tokenId]); + expect(await this.token.balanceOf(owner)).to.be.bignumber.equal('1'); + }); + + it('emits a burn event', async function () { + expectEvent(receipt, 'Transfer', { + from: owner, + to: constants.ZERO_ADDRESS, + tokenId: tokenId, + }); + }); + }); + + describe('when there is a previous approval burned', function () { + beforeEach(async function () { + await this.token.approve(approved, tokenId, { from: owner }); + receipt = await this.token.burn(tokenId, { from: owner }); + }); + + context('getApproved', function () { + it('reverts', async function () { + await expectRevertCustomError(this.token.getApproved(tokenId), 'ERC721NonexistentToken', [tokenId]); + }); + }); + }); + + 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', [ + unknownTokenId, + ]); + }); + }); + }); + }); +}); diff --git a/test/token/ERC721/extensions/ERC721Consecutive.t.sol b/test/token/ERC721/extensions/ERC721Consecutive.t.sol new file mode 100644 index 000000000..eca15e717 --- /dev/null +++ b/test/token/ERC721/extensions/ERC721Consecutive.t.sol @@ -0,0 +1,181 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +// solhint-disable func-name-mixedcase + +import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; +import {ERC721Consecutive} from "@openzeppelin/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); + accounts[0] = account; + return accounts; +} + +contract ERC721ConsecutiveTarget is StdUtils, ERC721Consecutive { + uint96 private immutable _offset; + uint256 public totalMinted = 0; + + 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())); + _mintConsecutive(receiver, batchSize); + totalMinted += batchSize; + } + } + + 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, uint96 startingId) public { + vm.assume(receiver != address(0)); + + 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, + uint96 startingId + ) public { + vm.assume(receiver != address(0)); + + uint256 startingTokenId = bound(startingId, 0, 5000); + + ERC721ConsecutiveTarget token = new ERC721ConsecutiveTarget(toSingleton(receiver), batches, startingTokenId); + + if (token.totalMinted() > 0) { + uint256 validTokenId = bound( + unboundedTokenId[0], + startingTokenId, + startingTokenId + token.totalMinted() - 1 + ); + assertEq(token.ownerOf(validTokenId), receiver); + } + + 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, + uint96 startingId + ) public { + vm.assume(receiver != address(0)); + + 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, startingTokenId, startingTokenId + supply - 1); + token.burn(tokenId); + + // balance should have decreased + assertEq(token.balanceOf(receiver), supply - 1); + + // token should be burnt + vm.expectRevert(); + token.ownerOf(tokenId); + } + + function test_transfer( + address[2] calldata accounts, + uint256[2] calldata unboundedBatches, + 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], startingTokenId, 5000); + batches[1] = bound(unboundedBatches[1], startingTokenId, 5000); + + ERC721ConsecutiveTarget token = new ERC721ConsecutiveTarget(receivers, batches, startingTokenId); + + 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]); + assertEq(token.balanceOf(accounts[0]), batches[0]); + assertEq(token.balanceOf(accounts[1]), batches[1]); + + vm.prank(accounts[0]); + token.transferFrom(accounts[0], accounts[1], tokenId0); + + assertEq(token.ownerOf(tokenId0), accounts[1]); + assertEq(token.ownerOf(tokenId1), accounts[1]); + assertEq(token.balanceOf(accounts[0]), batches[0] - 1); + assertEq(token.balanceOf(accounts[1]), batches[1] + 1); + + vm.prank(accounts[1]); + token.transferFrom(accounts[1], accounts[0], tokenId1); + + assertEq(token.ownerOf(tokenId0), accounts[1]); + assertEq(token.ownerOf(tokenId1), accounts[0]); + 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 new file mode 100644 index 000000000..d9e33aff2 --- /dev/null +++ b/test/token/ERC721/extensions/ERC721Consecutive.test.js @@ -0,0 +1,221 @@ +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'); +const ERC721ConsecutiveNoConstructorMintMock = artifacts.require('$ERC721ConsecutiveNoConstructorMintMock'); + +contract('ERC721Consecutive', function (accounts) { + const [user1, user2, user3, receiver] = accounts; + + const name = 'Non Fungible Token'; + const symbol = 'NFT'; + const batches = [ + { receiver: user1, amount: 0 }, + { receiver: user1, amount: 1 }, + { receiver: user1, amount: 2 }, + { receiver: user2, amount: 5 }, + { receiver: user3, amount: 0 }, + { receiver: user1, amount: 7 }, + ]; + const delegates = [user1, user3]; + + 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 = 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; + } + }); + + 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) { + 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 = sum(...batches.filter(({ receiver }) => receiver === account).map(({ amount }) => amount)); + + 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)); + + 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)); + } + }); + + 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 () { + it('consecutive minting is not possible after construction', async function () { + await expectRevertCustomError(this.token.$_mintConsecutive(user1, 10), 'ERC721ForbiddenBatchMint', []); + }); + + it('simple minting is possible after construction', async function () { + const tokenId = sum(...batches.map(b => b.amount)) + offset; + + await expectRevertCustomError(this.token.ownerOf(tokenId), 'ERC721NonexistentToken', [tokenId]); + + 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 = sum(...batches.map(b => b.amount)) + offset - 1; + + expect(await this.token.ownerOf(tokenId)).to.be.not.equal(constants.ZERO_ADDRESS); + + await expectRevertCustomError(this.token.$_mint(user1, tokenId), 'ERC721InvalidSender', [ZERO_ADDRESS]); + }); + }); + + describe('ERC721 behavior', function () { + const tokenId = web3.utils.toBN(offset + 1); + + it('core takes over ownership on transfer', async function () { + await this.token.transferFrom(user1, receiver, tokenId, { from: user1 }); + + expect(await this.token.ownerOf(tokenId)).to.be.equal(receiver); + }); + + 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 expectRevertCustomError(this.token.ownerOf(tokenId), 'ERC721NonexistentToken', [tokenId]); + + 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); + }); + + it('tokens can be burned and re-minted #2', async function () { + const tokenId = web3.utils.toBN(sum(...batches.map(({ amount }) => amount)) + offset); + + await expectRevertCustomError(this.token.ownerOf(tokenId), 'ERC721NonexistentToken', [tokenId]); + + // mint + await this.token.$_mint(user1, tokenId); + + expect(await this.token.ownerOf(tokenId), user1); + + // burn + expectEvent(await this.token.$_burn(tokenId, { from: user1 }), 'Transfer', { + from: user1, + to: constants.ZERO_ADDRESS, + tokenId, + }); + + await expectRevertCustomError(this.token.ownerOf(tokenId), 'ERC721NonexistentToken', [tokenId]); + + // re-mint + expectEvent(await this.token.$_mint(user2, tokenId), 'Transfer', { + from: constants.ZERO_ADDRESS, + to: user2, + tokenId, + }); + + expect(await this.token.ownerOf(tokenId), user2); + }); + }); + }); + } + + describe('invalid use', function () { + it('cannot mint a batch larger than 5000', async function () { + await expectRevertCustomError( + ERC721ConsecutiveMock.new(name, symbol, 0, [], [user1], ['5001']), + 'ERC721ExceededMaxBatchMint', + [5000, 5001], + ); + }); + + it('cannot use single minting during construction', async function () { + await expectRevertCustomError( + ERC721ConsecutiveNoConstructorMintMock.new(name, symbol), + 'ERC721ForbiddenMint', + [], + ); + }); + + it('cannot use single minting during construction', async function () { + await expectRevertCustomError( + ERC721ConsecutiveNoConstructorMintMock.new(name, symbol), + 'ERC721ForbiddenMint', + [], + ); + }); + + it('consecutive mint not compatible with enumerability', async function () { + await expectRevertCustomError( + ERC721ConsecutiveEnumerableMock.new( + name, + symbol, + batches.map(({ receiver }) => receiver), + batches.map(({ amount }) => amount), + ), + 'ERC721EnumerableForbiddenBatchMint', + [], + ); + }); + }); +}); diff --git a/test/token/ERC721/extensions/ERC721Pausable.test.js b/test/token/ERC721/extensions/ERC721Pausable.test.js new file mode 100644 index 000000000..5d77149f2 --- /dev/null +++ b/test/token/ERC721/extensions/ERC721Pausable.test.js @@ -0,0 +1,90 @@ +const { BN, constants } = require('@openzeppelin/test-helpers'); + +const { expect } = require('chai'); +const { expectRevertCustomError } = require('../../../helpers/customError'); + +const ERC721Pausable = artifacts.require('$ERC721Pausable'); + +contract('ERC721Pausable', function (accounts) { + const [owner, receiver, operator] = accounts; + + const name = 'Non Fungible Token'; + const symbol = 'NFT'; + + beforeEach(async function () { + this.token = await ERC721Pausable.new(name, symbol); + }); + + context('when token is paused', function () { + const firstTokenId = new BN(1); + const secondTokenId = new BN(1337); + + const mockData = '0x42'; + + beforeEach(async function () { + await this.token.$_mint(owner, firstTokenId, { from: owner }); + await this.token.$_pause(); + }); + + it('reverts when trying to transferFrom', async function () { + await expectRevertCustomError( + this.token.transferFrom(owner, receiver, firstTokenId, { from: owner }), + 'EnforcedPause', + [], + ); + }); + + it('reverts when trying to safeTransferFrom', async function () { + await expectRevertCustomError( + this.token.safeTransferFrom(owner, receiver, firstTokenId, { from: owner }), + 'EnforcedPause', + [], + ); + }); + + it('reverts when trying to safeTransferFrom with data', async function () { + await expectRevertCustomError( + this.token.methods['safeTransferFrom(address,address,uint256,bytes)'](owner, receiver, firstTokenId, mockData, { + from: owner, + }), + 'EnforcedPause', + [], + ); + }); + + it('reverts when trying to mint', async function () { + await expectRevertCustomError(this.token.$_mint(receiver, secondTokenId), 'EnforcedPause', []); + }); + + it('reverts when trying to burn', async function () { + await expectRevertCustomError(this.token.$_burn(firstTokenId), 'EnforcedPause', []); + }); + + describe('getApproved', function () { + it('returns approved address', async function () { + const approvedAccount = await this.token.getApproved(firstTokenId); + expect(approvedAccount).to.equal(constants.ZERO_ADDRESS); + }); + }); + + describe('balanceOf', function () { + it('returns the amount of tokens owned by the given address', async function () { + const balance = await this.token.balanceOf(owner); + expect(balance).to.be.bignumber.equal('1'); + }); + }); + + describe('ownerOf', function () { + it('returns the amount of tokens owned by the given address', async function () { + const ownerOfToken = await this.token.ownerOf(firstTokenId); + expect(ownerOfToken).to.equal(owner); + }); + }); + + describe('isApprovedForAll', function () { + it('returns the approval of the operator', async function () { + expect(await this.token.isApprovedForAll(owner, operator)).to.equal(false); + }); + }); + }); +}); diff --git a/test/token/ERC721/extensions/ERC721Royalty.test.js b/test/token/ERC721/extensions/ERC721Royalty.test.js new file mode 100644 index 000000000..78cba9858 --- /dev/null +++ b/test/token/ERC721/extensions/ERC721Royalty.test.js @@ -0,0 +1,47 @@ +require('@openzeppelin/test-helpers'); + +const { shouldBehaveLikeERC2981 } = require('../../common/ERC2981.behavior'); + +const ERC721Royalty = artifacts.require('$ERC721Royalty'); + +contract('ERC721Royalty', function (accounts) { + const [account1, account2, recipient] = accounts; + const tokenId1 = web3.utils.toBN('1'); + const tokenId2 = web3.utils.toBN('2'); + const royalty = web3.utils.toBN('200'); + const salePrice = web3.utils.toBN('1000'); + + beforeEach(async function () { + this.token = await ERC721Royalty.new('My Token', 'TKN'); + + await this.token.$_mint(account1, tokenId1); + await this.token.$_mint(account1, tokenId2); + this.account1 = account1; + this.account2 = account2; + this.tokenId1 = tokenId1; + this.tokenId2 = tokenId2; + this.salePrice = salePrice; + }); + + describe('token specific functions', function () { + beforeEach(async function () { + await this.token.$_setTokenRoyalty(tokenId1, recipient, royalty); + }); + + it('royalty information are kept during burn and re-mint', async function () { + await this.token.$_burn(tokenId1); + + const tokenInfoA = await this.token.royaltyInfo(tokenId1, salePrice); + expect(tokenInfoA[0]).to.be.equal(recipient); + expect(tokenInfoA[1]).to.be.bignumber.equal(salePrice.mul(royalty).divn(1e4)); + + await this.token.$_mint(account2, tokenId1); + + const tokenInfoB = await this.token.royaltyInfo(tokenId1, salePrice); + expect(tokenInfoB[0]).to.be.equal(recipient); + expect(tokenInfoB[1]).to.be.bignumber.equal(salePrice.mul(royalty).divn(1e4)); + }); + }); + + shouldBehaveLikeERC2981(); +}); diff --git a/test/token/ERC721/extensions/ERC721URIStorage.test.js b/test/token/ERC721/extensions/ERC721URIStorage.test.js new file mode 100644 index 000000000..8c882fab0 --- /dev/null +++ b/test/token/ERC721/extensions/ERC721URIStorage.test.js @@ -0,0 +1,114 @@ +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'); + +contract('ERC721URIStorage', function (accounts) { + const [owner] = accounts; + + const name = 'Non Fungible Token'; + const symbol = 'NFT'; + + const firstTokenId = new BN('5042'); + const nonExistentTokenId = new BN('13'); + + beforeEach(async function () { + this.token = await ERC721URIStorageMock.new(name, symbol); + }); + + shouldSupportInterfaces(['0x49064906']); + + describe('token URI', function () { + beforeEach(async function () { + await this.token.$_mint(owner, firstTokenId); + }); + + const baseURI = 'https://api.example.com/v1/'; + const sampleUri = 'mock://mytoken'; + + it('it is empty by default', async function () { + expect(await this.token.tokenURI(firstTokenId)).to.be.equal(''); + }); + + it('reverts when queried for non existent token id', async function () { + await expectRevertCustomError(this.token.tokenURI(nonExistentTokenId), 'ERC721NonexistentToken', [ + nonExistentTokenId, + ]); + }); + + it('can be set for a token id', async function () { + await this.token.$_setTokenURI(firstTokenId, sampleUri); + expect(await this.token.tokenURI(firstTokenId)).to.be.equal(sampleUri); + }); + + it('setting the uri emits an event', async function () { + expectEvent(await this.token.$_setTokenURI(firstTokenId, sampleUri), 'MetadataUpdate', { + _tokenId: firstTokenId, + }); + }); + + it('setting the uri for non existent token id is allowed', async function () { + expectEvent(await this.token.$_setTokenURI(nonExistentTokenId, sampleUri), 'MetadataUpdate', { + _tokenId: nonExistentTokenId, + }); + + // value will be accessible after mint + await this.token.$_mint(owner, nonExistentTokenId); + expect(await this.token.tokenURI(nonExistentTokenId)).to.be.equal(sampleUri); + }); + + it('base URI can be set', async function () { + await this.token.setBaseURI(baseURI); + expect(await this.token.$_baseURI()).to.equal(baseURI); + }); + + it('base URI is added as a prefix to the token URI', async function () { + await this.token.setBaseURI(baseURI); + await this.token.$_setTokenURI(firstTokenId, sampleUri); + + expect(await this.token.tokenURI(firstTokenId)).to.be.equal(baseURI + sampleUri); + }); + + it('token URI can be changed by changing the base URI', async function () { + await this.token.setBaseURI(baseURI); + await this.token.$_setTokenURI(firstTokenId, sampleUri); + + const newBaseURI = 'https://api.example.com/v2/'; + await this.token.setBaseURI(newBaseURI); + expect(await this.token.tokenURI(firstTokenId)).to.be.equal(newBaseURI + sampleUri); + }); + + it('tokenId is appended to base URI for tokens with no URI', async function () { + await this.token.setBaseURI(baseURI); + + expect(await this.token.tokenURI(firstTokenId)).to.be.equal(baseURI + firstTokenId); + }); + + it('tokens without URI can be burnt ', async function () { + await this.token.$_burn(firstTokenId); + + await expectRevertCustomError(this.token.tokenURI(firstTokenId), 'ERC721NonexistentToken', [firstTokenId]); + }); + + it('tokens with URI can be burnt ', async function () { + await this.token.$_setTokenURI(firstTokenId, sampleUri); + + await this.token.$_burn(firstTokenId); + + await expectRevertCustomError(this.token.tokenURI(firstTokenId), 'ERC721NonexistentToken', [firstTokenId]); + }); + + it('tokens URI is kept if token is burnt and reminted ', async function () { + await this.token.$_setTokenURI(firstTokenId, sampleUri); + + await this.token.$_burn(firstTokenId); + await expectRevertCustomError(this.token.tokenURI(firstTokenId), 'ERC721NonexistentToken', [firstTokenId]); + + await this.token.$_mint(owner, firstTokenId); + expect(await this.token.tokenURI(firstTokenId)).to.be.equal(sampleUri); + }); + }); +}); diff --git a/test/token/ERC721/extensions/ERC721Votes.test.js b/test/token/ERC721/extensions/ERC721Votes.test.js new file mode 100644 index 000000000..45020baf9 --- /dev/null +++ b/test/token/ERC721/extensions/ERC721Votes.test.js @@ -0,0 +1,183 @@ +/* eslint-disable */ + +const { expectEvent, time } = require('@openzeppelin/test-helpers'); +const { expect } = require('chai'); + +const { clock, clockFromReceipt } = require('../../../helpers/time'); + +const { shouldBehaveLikeVotes } = require('../../../governance/utils/Votes.behavior'); + +const MODES = { + blocknumber: artifacts.require('$ERC721Votes'), + // no timestamp mode for ERC721Votes yet +}; + +contract('ERC721Votes', function (accounts) { + const [account1, account2, other1, other2] = accounts; + + const name = 'My Vote'; + const symbol = 'MTKN'; + const version = '1'; + const tokens = ['10000000000000000000000000', '10', '20', '30'].map(n => web3.utils.toBN(n)); + + for (const [mode, artifact] of Object.entries(MODES)) { + describe(`vote with ${mode}`, function () { + beforeEach(async function () { + this.votes = await artifact.new(name, symbol, name, version); + }); + + // includes EIP6372 behavior check + shouldBehaveLikeVotes(accounts, tokens, { mode, fungible: false }); + + describe('balanceOf', function () { + beforeEach(async function () { + 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 () { + expect(await this.votes.balanceOf(account1)).to.be.bignumber.equal('4'); + }); + }); + + describe('transfers', function () { + beforeEach(async function () { + await this.votes.$_mint(account1, tokens[0]); + }); + + it('no delegation', async function () { + 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'; + this.account2Votes = '0'; + }); + + it('sender delegation', async function () { + await this.votes.delegate(account1, { from: account1 }); + + 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, previousVotes: '1', newVotes: '0' }); + + 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.account1Votes = '0'; + this.account2Votes = '0'; + }); + + it('receiver delegation', async function () { + await this.votes.delegate(account2, { from: account2 }); + + 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, previousVotes: '0', newVotes: '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.account1Votes = '0'; + this.account2Votes = '1'; + }); + + it('full delegation', async function () { + await this.votes.delegate(account1, { from: account1 }); + await this.votes.delegate(account2, { from: account2 }); + + 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, previousVotes: '1', newVotes: '0' }); + expectEvent(receipt, 'DelegateVotesChanged', { delegate: account2, previousVotes: '0', newVotes: '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.account1Votes = '0'; + this.account2Votes = '1'; + }); + + 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, tokens[0], { from: account1 }); + const timepoint = await clockFromReceipt[mode](receipt); + + await time.advanceBlock(); + await time.advanceBlock(); + + expect(await this.votes.getPastTotalSupply(timepoint - 1)).to.be.bignumber.equal('1'); + expect(await this.votes.getPastTotalSupply(timepoint + 1)).to.be.bignumber.equal('1'); + + this.account1Votes = '0'; + this.account2Votes = '0'; + }); + + it('generally returns the voting balance at the appropriate checkpoint', async function () { + 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, tokens[0], { from: account1 }); + await time.advanceBlock(); + await time.advanceBlock(); + 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, tokens[2], { 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.votes.getPastVotes(other1, t1.timepoint - 1)).to.be.bignumber.equal('0'); + expect(await this.votes.getPastVotes(other1, t1.timepoint)).to.be.bignumber.equal(total); + expect(await this.votes.getPastVotes(other1, t1.timepoint + 1)).to.be.bignumber.equal(total); + expect(await this.votes.getPastVotes(other1, t2.timepoint)).to.be.bignumber.equal('3'); + expect(await this.votes.getPastVotes(other1, t2.timepoint + 1)).to.be.bignumber.equal('3'); + expect(await this.votes.getPastVotes(other1, t3.timepoint)).to.be.bignumber.equal('2'); + expect(await this.votes.getPastVotes(other1, t3.timepoint + 1)).to.be.bignumber.equal('2'); + expect(await this.votes.getPastVotes(other1, t4.timepoint)).to.be.bignumber.equal('3'); + expect(await this.votes.getPastVotes(other1, t4.timepoint + 1)).to.be.bignumber.equal('3'); + + this.account1Votes = '0'; + this.account2Votes = '0'; + }); + + afterEach(async function () { + expect(await this.votes.getVotes(account1)).to.be.bignumber.equal(this.account1Votes); + expect(await this.votes.getVotes(account2)).to.be.bignumber.equal(this.account2Votes); + + // need to advance 2 blocks to see the effect of a transfer on "getPastVotes" + const timepoint = await clock[mode](); + await time.advanceBlock(); + expect(await this.votes.getPastVotes(account1, timepoint)).to.be.bignumber.equal(this.account1Votes); + expect(await this.votes.getPastVotes(account2, timepoint)).to.be.bignumber.equal(this.account2Votes); + }); + }); + }); + } +}); diff --git a/test/token/ERC721/extensions/ERC721Wrapper.test.js b/test/token/ERC721/extensions/ERC721Wrapper.test.js new file mode 100644 index 000000000..683997744 --- /dev/null +++ b/test/token/ERC721/extensions/ERC721Wrapper.test.js @@ -0,0 +1,289 @@ +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'); + +contract('ERC721Wrapper', function (accounts) { + const [initialHolder, anotherAccount, approvedAccount] = accounts; + + const name = 'My Token'; + const symbol = 'MTKN'; + const firstTokenId = new BN(1); + const secondTokenId = new BN(2); + + beforeEach(async function () { + this.underlying = await ERC721.new(name, symbol); + this.token = await ERC721Wrapper.new(`Wrapped ${name}`, `W${symbol}`, this.underlying.address); + + await this.underlying.$_safeMint(initialHolder, firstTokenId); + await this.underlying.$_safeMint(initialHolder, secondTokenId); + }); + + it('has a name', async function () { + expect(await this.token.name()).to.equal(`Wrapped ${name}`); + }); + + it('has a symbol', async function () { + expect(await this.token.symbol()).to.equal(`W${symbol}`); + }); + + it('has underlying', async function () { + expect(await this.token.underlying()).to.be.bignumber.equal(this.underlying.address); + }); + + describe('depositFor', function () { + it('works with token approval', async function () { + await this.underlying.approve(this.token.address, firstTokenId, { from: initialHolder }); + + const { tx } = await this.token.depositFor(initialHolder, [firstTokenId], { from: initialHolder }); + + await expectEvent.inTransaction(tx, this.underlying, 'Transfer', { + from: initialHolder, + to: this.token.address, + tokenId: firstTokenId, + }); + await expectEvent.inTransaction(tx, this.token, 'Transfer', { + from: constants.ZERO_ADDRESS, + to: initialHolder, + tokenId: firstTokenId, + }); + }); + + it('works with approval for all', async function () { + await this.underlying.setApprovalForAll(this.token.address, true, { from: initialHolder }); + + const { tx } = await this.token.depositFor(initialHolder, [firstTokenId], { from: initialHolder }); + + await expectEvent.inTransaction(tx, this.underlying, 'Transfer', { + from: initialHolder, + to: this.token.address, + tokenId: firstTokenId, + }); + await expectEvent.inTransaction(tx, this.token, 'Transfer', { + from: constants.ZERO_ADDRESS, + to: initialHolder, + tokenId: firstTokenId, + }); + }); + + it('works sending to another account', async function () { + await this.underlying.approve(this.token.address, firstTokenId, { from: initialHolder }); + + const { tx } = await this.token.depositFor(anotherAccount, [firstTokenId], { from: initialHolder }); + + await expectEvent.inTransaction(tx, this.underlying, 'Transfer', { + from: initialHolder, + to: this.token.address, + tokenId: firstTokenId, + }); + await expectEvent.inTransaction(tx, this.token, 'Transfer', { + from: constants.ZERO_ADDRESS, + to: anotherAccount, + tokenId: firstTokenId, + }); + }); + + it('works with multiple tokens', async function () { + await this.underlying.approve(this.token.address, firstTokenId, { from: initialHolder }); + await this.underlying.approve(this.token.address, secondTokenId, { from: initialHolder }); + + const { tx } = await this.token.depositFor(initialHolder, [firstTokenId, secondTokenId], { from: initialHolder }); + + await expectEvent.inTransaction(tx, this.underlying, 'Transfer', { + from: initialHolder, + to: this.token.address, + tokenId: firstTokenId, + }); + await expectEvent.inTransaction(tx, this.token, 'Transfer', { + from: constants.ZERO_ADDRESS, + to: initialHolder, + tokenId: firstTokenId, + }); + await expectEvent.inTransaction(tx, this.underlying, 'Transfer', { + from: initialHolder, + to: this.token.address, + tokenId: secondTokenId, + }); + await expectEvent.inTransaction(tx, this.token, 'Transfer', { + from: constants.ZERO_ADDRESS, + to: initialHolder, + tokenId: secondTokenId, + }); + }); + + it('reverts with missing approval', async function () { + await expectRevertCustomError( + this.token.depositFor(initialHolder, [firstTokenId], { from: initialHolder }), + 'ERC721InsufficientApproval', + [this.token.address, firstTokenId], + ); + }); + }); + + describe('withdrawTo', function () { + beforeEach(async function () { + await this.underlying.approve(this.token.address, firstTokenId, { from: initialHolder }); + await this.token.depositFor(initialHolder, [firstTokenId], { from: initialHolder }); + }); + + it('works for an owner', async function () { + const { tx } = await this.token.withdrawTo(initialHolder, [firstTokenId], { from: initialHolder }); + + await expectEvent.inTransaction(tx, this.underlying, 'Transfer', { + from: this.token.address, + to: initialHolder, + tokenId: firstTokenId, + }); + await expectEvent.inTransaction(tx, this.token, 'Transfer', { + from: initialHolder, + to: constants.ZERO_ADDRESS, + tokenId: firstTokenId, + }); + }); + + it('works for an approved', async function () { + await this.token.approve(approvedAccount, firstTokenId, { from: initialHolder }); + + const { tx } = await this.token.withdrawTo(initialHolder, [firstTokenId], { from: approvedAccount }); + + await expectEvent.inTransaction(tx, this.underlying, 'Transfer', { + from: this.token.address, + to: initialHolder, + tokenId: firstTokenId, + }); + await expectEvent.inTransaction(tx, this.token, 'Transfer', { + from: initialHolder, + to: constants.ZERO_ADDRESS, + tokenId: firstTokenId, + }); + }); + + it('works for an approved for all', async function () { + await this.token.setApprovalForAll(approvedAccount, true, { from: initialHolder }); + + const { tx } = await this.token.withdrawTo(initialHolder, [firstTokenId], { from: approvedAccount }); + + await expectEvent.inTransaction(tx, this.underlying, 'Transfer', { + from: this.token.address, + to: initialHolder, + tokenId: firstTokenId, + }); + await expectEvent.inTransaction(tx, this.token, 'Transfer', { + from: initialHolder, + to: constants.ZERO_ADDRESS, + tokenId: firstTokenId, + }); + }); + + it("doesn't work for a non-owner nor approved", async function () { + await expectRevertCustomError( + this.token.withdrawTo(initialHolder, [firstTokenId], { from: anotherAccount }), + 'ERC721InsufficientApproval', + [anotherAccount, firstTokenId], + ); + }); + + it('works with multiple tokens', async function () { + await this.underlying.approve(this.token.address, secondTokenId, { from: initialHolder }); + await this.token.depositFor(initialHolder, [secondTokenId], { from: initialHolder }); + + const { tx } = await this.token.withdrawTo(initialHolder, [firstTokenId, secondTokenId], { from: initialHolder }); + + await expectEvent.inTransaction(tx, this.underlying, 'Transfer', { + from: this.token.address, + to: initialHolder, + tokenId: firstTokenId, + }); + await expectEvent.inTransaction(tx, this.underlying, 'Transfer', { + from: this.token.address, + to: initialHolder, + tokenId: secondTokenId, + }); + await expectEvent.inTransaction(tx, this.token, 'Transfer', { + from: initialHolder, + to: constants.ZERO_ADDRESS, + tokenId: firstTokenId, + }); + await expectEvent.inTransaction(tx, this.token, 'Transfer', { + from: initialHolder, + to: constants.ZERO_ADDRESS, + tokenId: secondTokenId, + }); + }); + + it('works to another account', async function () { + const { tx } = await this.token.withdrawTo(anotherAccount, [firstTokenId], { from: initialHolder }); + + await expectEvent.inTransaction(tx, this.underlying, 'Transfer', { + from: this.token.address, + to: anotherAccount, + tokenId: firstTokenId, + }); + await expectEvent.inTransaction(tx, this.token, 'Transfer', { + from: initialHolder, + to: constants.ZERO_ADDRESS, + tokenId: firstTokenId, + }); + }); + }); + + describe('onERC721Received', function () { + it('only allows calls from underlying', async function () { + await expectRevertCustomError( + this.token.onERC721Received( + initialHolder, + this.token.address, + firstTokenId, + anotherAccount, // Correct data + { from: anotherAccount }, + ), + 'ERC721UnsupportedToken', + [anotherAccount], + ); + }); + + it('mints a token to from', async function () { + const { tx } = await this.underlying.safeTransferFrom(initialHolder, this.token.address, firstTokenId, { + from: initialHolder, + }); + + await expectEvent.inTransaction(tx, this.token, 'Transfer', { + from: constants.ZERO_ADDRESS, + to: initialHolder, + tokenId: firstTokenId, + }); + }); + }); + + describe('_recover', function () { + it('works if there is something to recover', async function () { + // Should use `transferFrom` to avoid `onERC721Received` minting + await this.underlying.transferFrom(initialHolder, this.token.address, firstTokenId, { from: initialHolder }); + + const { tx } = await this.token.$_recover(anotherAccount, firstTokenId); + + await expectEvent.inTransaction(tx, this.token, 'Transfer', { + from: constants.ZERO_ADDRESS, + to: anotherAccount, + tokenId: firstTokenId, + }); + }); + + it('reverts if there is nothing to recover', async function () { + 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(...accounts); + }); +}); diff --git a/test/token/ERC721/utils/ERC721Holder.test.js b/test/token/ERC721/utils/ERC721Holder.test.js new file mode 100644 index 000000000..4aa2b7948 --- /dev/null +++ b/test/token/ERC721/utils/ERC721Holder.test.js @@ -0,0 +1,22 @@ +const { expect } = require('chai'); + +const ERC721Holder = artifacts.require('$ERC721Holder'); +const ERC721 = artifacts.require('$ERC721'); + +contract('ERC721Holder', function (accounts) { + const [owner] = accounts; + + const name = 'Non Fungible Token'; + const symbol = 'NFT'; + const tokenId = web3.utils.toBN(1); + + it('receives an ERC721 token', async function () { + const token = await ERC721.new(name, symbol); + await token.$_mint(owner, tokenId); + + const receiver = await ERC721Holder.new(); + await token.safeTransferFrom(owner, receiver.address, tokenId, { from: owner }); + + expect(await token.ownerOf(tokenId)).to.be.equal(receiver.address); + }); +}); diff --git a/test/token/common/ERC2981.behavior.js b/test/token/common/ERC2981.behavior.js new file mode 100644 index 000000000..15efa239f --- /dev/null +++ b/test/token/common/ERC2981.behavior.js @@ -0,0 +1,169 @@ +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'); + + shouldSupportInterfaces(['ERC2981']); + + describe('default royalty', function () { + beforeEach(async function () { + await this.token.$_setDefaultRoyalty(this.account1, royaltyFraction); + }); + + it('checks royalty is set', async function () { + const royalty = new BN((this.salePrice * royaltyFraction) / 10000); + + const initInfo = await this.token.royaltyInfo(this.tokenId1, this.salePrice); + + expect(initInfo[0]).to.be.equal(this.account1); + expect(initInfo[1]).to.be.bignumber.equal(royalty); + }); + + it('updates royalty amount', async function () { + const newPercentage = new BN('25'); + + // Updated royalty check + await this.token.$_setDefaultRoyalty(this.account1, newPercentage); + const royalty = new BN((this.salePrice * newPercentage) / 10000); + const newInfo = await this.token.royaltyInfo(this.tokenId1, this.salePrice); + + expect(newInfo[0]).to.be.equal(this.account1); + expect(newInfo[1]).to.be.bignumber.equal(royalty); + }); + + it('holds same royalty value for different tokens', async function () { + const newPercentage = new BN('20'); + await this.token.$_setDefaultRoyalty(this.account1, newPercentage); + + const token1Info = await this.token.royaltyInfo(this.tokenId1, this.salePrice); + const token2Info = await this.token.royaltyInfo(this.tokenId2, this.salePrice); + + expect(token1Info[1]).to.be.bignumber.equal(token2Info[1]); + }); + + it('Remove royalty information', async function () { + const newValue = new BN('0'); + await this.token.$_deleteDefaultRoyalty(); + + const token1Info = await this.token.royaltyInfo(this.tokenId1, this.salePrice); + const token2Info = await this.token.royaltyInfo(this.tokenId2, this.salePrice); + // Test royalty info is still persistent across all tokens + expect(token1Info[0]).to.be.bignumber.equal(token2Info[0]); + expect(token1Info[1]).to.be.bignumber.equal(token2Info[1]); + // Test information was deleted + expect(token1Info[0]).to.be.equal(ZERO_ADDRESS); + expect(token1Info[1]).to.be.bignumber.equal(newValue); + }); + + it('reverts if invalid parameters', async function () { + const royaltyDenominator = await this.token.$_feeDenominator(); + await expectRevertCustomError( + this.token.$_setDefaultRoyalty(ZERO_ADDRESS, royaltyFraction), + 'ERC2981InvalidDefaultRoyaltyReceiver', + [ZERO_ADDRESS], + ); + + const anotherRoyaltyFraction = new BN('11000'); + await expectRevertCustomError( + this.token.$_setDefaultRoyalty(this.account1, anotherRoyaltyFraction), + 'ERC2981InvalidDefaultRoyalty', + [anotherRoyaltyFraction, royaltyDenominator], + ); + }); + }); + + describe('token based royalty', function () { + beforeEach(async function () { + await this.token.$_setTokenRoyalty(this.tokenId1, this.account1, royaltyFraction); + }); + + it('updates royalty amount', async function () { + const newPercentage = new BN('25'); + let royalty = new BN((this.salePrice * royaltyFraction) / 10000); + // Initial royalty check + const initInfo = await this.token.royaltyInfo(this.tokenId1, this.salePrice); + + expect(initInfo[0]).to.be.equal(this.account1); + expect(initInfo[1]).to.be.bignumber.equal(royalty); + + // Updated royalty check + await this.token.$_setTokenRoyalty(this.tokenId1, this.account1, newPercentage); + royalty = new BN((this.salePrice * newPercentage) / 10000); + const newInfo = await this.token.royaltyInfo(this.tokenId1, this.salePrice); + + expect(newInfo[0]).to.be.equal(this.account1); + expect(newInfo[1]).to.be.bignumber.equal(royalty); + }); + + it('holds different values for different tokens', async function () { + const newPercentage = new BN('20'); + await this.token.$_setTokenRoyalty(this.tokenId2, this.account1, newPercentage); + + const token1Info = await this.token.royaltyInfo(this.tokenId1, this.salePrice); + const token2Info = await this.token.royaltyInfo(this.tokenId2, this.salePrice); + + // must be different even at the same this.salePrice + expect(token1Info[1]).to.not.be.equal(token2Info.royaltyFraction); + }); + + it('reverts if invalid parameters', async function () { + const royaltyDenominator = await this.token.$_feeDenominator(); + await expectRevertCustomError( + this.token.$_setTokenRoyalty(this.tokenId1, ZERO_ADDRESS, royaltyFraction), + 'ERC2981InvalidTokenRoyaltyReceiver', + [this.tokenId1.toString(), ZERO_ADDRESS], + ); + + const anotherRoyaltyFraction = new BN('11000'); + await expectRevertCustomError( + this.token.$_setTokenRoyalty(this.tokenId1, this.account1, anotherRoyaltyFraction), + 'ERC2981InvalidTokenRoyalty', + [this.tokenId1.toString(), anotherRoyaltyFraction, royaltyDenominator], + ); + }); + + it('can reset token after setting royalty', async function () { + const newPercentage = new BN('30'); + const royalty = new BN((this.salePrice * newPercentage) / 10000); + await this.token.$_setTokenRoyalty(this.tokenId1, this.account2, newPercentage); + + const tokenInfo = await this.token.royaltyInfo(this.tokenId1, this.salePrice); + + // Tokens must have own information + expect(tokenInfo[1]).to.be.bignumber.equal(royalty); + expect(tokenInfo[0]).to.be.equal(this.account2); + + await this.token.$_setTokenRoyalty(this.tokenId2, this.account1, new BN('0')); + const result = await this.token.royaltyInfo(this.tokenId2, this.salePrice); + // Token must not share default information + expect(result[0]).to.be.equal(this.account1); + expect(result[1]).to.be.bignumber.equal(new BN('0')); + }); + + it('can hold default and token royalty information', async function () { + const newPercentage = new BN('30'); + const royalty = new BN((this.salePrice * newPercentage) / 10000); + + await this.token.$_setTokenRoyalty(this.tokenId2, this.account2, newPercentage); + + const token1Info = await this.token.royaltyInfo(this.tokenId1, this.salePrice); + const token2Info = await this.token.royaltyInfo(this.tokenId2, this.salePrice); + // Tokens must not have same values + expect(token1Info[1]).to.not.be.bignumber.equal(token2Info[1]); + expect(token1Info[0]).to.not.be.equal(token2Info[0]); + + // Updated token must have new values + expect(token2Info[0]).to.be.equal(this.account2); + expect(token2Info[1]).to.be.bignumber.equal(royalty); + }); + }); +} + +module.exports = { + shouldBehaveLikeERC2981, +}; diff --git a/test/utils/Address.test.js b/test/utils/Address.test.js new file mode 100644 index 000000000..57453abd5 --- /dev/null +++ b/test/utils/Address.test.js @@ -0,0 +1,340 @@ +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 EtherReceiver = artifacts.require('EtherReceiverMock'); +const CallReceiverMock = artifacts.require('CallReceiverMock'); + +contract('Address', function (accounts) { + const [recipient, other] = accounts; + + beforeEach(async function () { + this.mock = await Address.new(); + }); + + describe('sendValue', function () { + beforeEach(async function () { + this.recipientTracker = await balance.tracker(recipient); + }); + + context('when sender contract has no funds', function () { + it('sends 0 wei', async function () { + await this.mock.$sendValue(other, 0); + + expect(await this.recipientTracker.delta()).to.be.bignumber.equal('0'); + }); + + it('reverts when sending non-zero amounts', async function () { + await expectRevertCustomError(this.mock.$sendValue(other, 1), 'AddressInsufficientBalance', [ + this.mock.address, + ]); + }); + }); + + context('when sender contract has funds', function () { + const funds = ether('1'); + beforeEach(async function () { + await send.ether(other, this.mock.address, funds); + }); + + it('sends 0 wei', async function () { + await this.mock.$sendValue(recipient, 0); + expect(await this.recipientTracker.delta()).to.be.bignumber.equal('0'); + }); + + it('sends non-zero amounts', async function () { + await this.mock.$sendValue(recipient, funds.subn(1)); + expect(await this.recipientTracker.delta()).to.be.bignumber.equal(funds.subn(1)); + }); + + it('sends the whole balance', async function () { + await this.mock.$sendValue(recipient, funds); + expect(await this.recipientTracker.delta()).to.be.bignumber.equal(funds); + expect(await balance.current(this.mock.address)).to.be.bignumber.equal('0'); + }); + + it('reverts when sending more than the balance', async function () { + await expectRevertCustomError(this.mock.$sendValue(recipient, funds.addn(1)), 'AddressInsufficientBalance', [ + this.mock.address, + ]); + }); + + context('with contract recipient', function () { + beforeEach(async function () { + this.target = await EtherReceiver.new(); + }); + + it('sends funds', async function () { + const tracker = await balance.tracker(this.target.address); + + await this.target.setAcceptEther(true); + await this.mock.$sendValue(this.target.address, funds); + + expect(await tracker.delta()).to.be.bignumber.equal(funds); + }); + + it('reverts on recipient revert', async function () { + await this.target.setAcceptEther(false); + await expectRevertCustomError(this.mock.$sendValue(this.target.address, funds), 'FailedInnerCall', []); + }); + }); + }); + }); + + describe('functionCall', function () { + beforeEach(async function () { + this.target = await CallReceiverMock.new(); + }); + + context('with valid contract receiver', function () { + it('calls the requested function', async function () { + const abiEncodedCall = this.target.contract.methods.mockFunction().encodeABI(); + + const receipt = await this.mock.$functionCall(this.target.address, abiEncodedCall); + + expectEvent(receipt, 'return$functionCall', { + ret0: web3.eth.abi.encodeParameters(['string'], ['0x1234']), + }); + 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(); + + await expectRevertCustomError( + this.mock.$functionCall(this.target.address, abiEncodedCall), + 'FailedInnerCall', + [], + ); + }); + + it('reverts when the called function reverts, bubbling up the revert reason', async function () { + const abiEncodedCall = this.target.contract.methods.mockFunctionRevertsReason().encodeABI(); + + await expectRevert(this.mock.$functionCall(this.target.address, abiEncodedCall), 'CallReceiverMock: reverting'); + }); + + it('reverts when the called function runs out of gas', async function () { + const abiEncodedCall = this.target.contract.methods.mockFunctionOutOfGas().encodeABI(); + + await expectRevertCustomError( + this.mock.$functionCall(this.target.address, abiEncodedCall, { gas: '120000' }), + 'FailedInnerCall', + [], + ); + }); + + it('reverts when the called function throws', async function () { + const abiEncodedCall = this.target.contract.methods.mockFunctionThrows().encodeABI(); + + await expectRevert.unspecified(this.mock.$functionCall(this.target.address, abiEncodedCall)); + }); + + it('reverts when function does not exist', async function () { + const abiEncodedCall = web3.eth.abi.encodeFunctionCall( + { + name: 'mockFunctionDoesNotExist', + type: 'function', + inputs: [], + }, + [], + ); + + await expectRevertCustomError( + this.mock.$functionCall(this.target.address, abiEncodedCall), + 'FailedInnerCall', + [], + ); + }); + }); + + context('with non-contract receiver', function () { + it('reverts when address is not a contract', async function () { + const [recipient] = accounts; + const abiEncodedCall = this.target.contract.methods.mockFunction().encodeABI(); + + await expectRevertCustomError(this.mock.$functionCall(recipient, abiEncodedCall), 'AddressEmptyCode', [ + recipient, + ]); + }); + }); + }); + + describe('functionCallWithValue', function () { + beforeEach(async function () { + this.target = await CallReceiverMock.new(); + }); + + context('with zero value', function () { + it('calls the requested function', async function () { + const abiEncodedCall = this.target.contract.methods.mockFunction().encodeABI(); + + const receipt = await this.mock.$functionCallWithValue(this.target.address, abiEncodedCall, 0); + expectEvent(receipt, 'return$functionCallWithValue', { + ret0: web3.eth.abi.encodeParameters(['string'], ['0x1234']), + }); + await expectEvent.inTransaction(receipt.tx, CallReceiverMock, 'MockFunctionCalled'); + }); + }); + + context('with non-zero value', function () { + const amount = ether('1.2'); + + it('reverts if insufficient sender balance', async function () { + const abiEncodedCall = this.target.contract.methods.mockFunction().encodeABI(); + + await expectRevertCustomError( + this.mock.$functionCallWithValue(this.target.address, abiEncodedCall, amount), + 'AddressInsufficientBalance', + [this.mock.address], + ); + }); + + it('calls the requested function with existing value', async function () { + const abiEncodedCall = this.target.contract.methods.mockFunction().encodeABI(); + + const tracker = await balance.tracker(this.target.address); + + await send.ether(other, this.mock.address, amount); + + const receipt = await this.mock.$functionCallWithValue(this.target.address, abiEncodedCall, amount); + expectEvent(receipt, 'return$functionCallWithValue', { + ret0: web3.eth.abi.encodeParameters(['string'], ['0x1234']), + }); + await expectEvent.inTransaction(receipt.tx, CallReceiverMock, 'MockFunctionCalled'); + + expect(await tracker.delta()).to.be.bignumber.equal(amount); + }); + + it('calls the requested function with transaction funds', async function () { + const abiEncodedCall = this.target.contract.methods.mockFunction().encodeABI(); + + const tracker = await balance.tracker(this.target.address); + + expect(await balance.current(this.mock.address)).to.be.bignumber.equal('0'); + + const receipt = await this.mock.$functionCallWithValue(this.target.address, abiEncodedCall, amount, { + from: other, + value: amount, + }); + expectEvent(receipt, 'return$functionCallWithValue', { + ret0: web3.eth.abi.encodeParameters(['string'], ['0x1234']), + }); + await expectEvent.inTransaction(receipt.tx, CallReceiverMock, 'MockFunctionCalled'); + + expect(await tracker.delta()).to.be.bignumber.equal(amount); + }); + + it('reverts when calling non-payable functions', async function () { + const abiEncodedCall = this.target.contract.methods.mockFunctionNonPayable().encodeABI(); + + await send.ether(other, this.mock.address, amount); + await expectRevertCustomError( + this.mock.$functionCallWithValue(this.target.address, abiEncodedCall, amount), + 'FailedInnerCall', + [], + ); + }); + }); + }); + + describe('functionStaticCall', function () { + beforeEach(async function () { + this.target = await CallReceiverMock.new(); + }); + + it('calls the requested function', async function () { + const abiEncodedCall = this.target.contract.methods.mockStaticFunction().encodeABI(); + + expect(await this.mock.$functionStaticCall(this.target.address, abiEncodedCall)).to.be.equal( + web3.eth.abi.encodeParameters(['string'], ['0x1234']), + ); + }); + + it('reverts on a non-static function', async function () { + const abiEncodedCall = this.target.contract.methods.mockFunction().encodeABI(); + + await expectRevertCustomError( + this.mock.$functionStaticCall(this.target.address, abiEncodedCall), + 'FailedInnerCall', + [], + ); + }); + + it('bubbles up revert reason', async function () { + const abiEncodedCall = this.target.contract.methods.mockFunctionRevertsReason().encodeABI(); + + await expectRevert( + this.mock.$functionStaticCall(this.target.address, abiEncodedCall), + 'CallReceiverMock: reverting', + ); + }); + + it('reverts when address is not a contract', async function () { + const [recipient] = accounts; + const abiEncodedCall = this.target.contract.methods.mockFunction().encodeABI(); + + await expectRevertCustomError(this.mock.$functionStaticCall(recipient, abiEncodedCall), 'AddressEmptyCode', [ + recipient, + ]); + }); + }); + + describe('functionDelegateCall', function () { + beforeEach(async function () { + this.target = await CallReceiverMock.new(); + }); + + it('delegate calls the requested function', async function () { + // pseudorandom values + const slot = '0x93e4c53af435ddf777c3de84bb9a953a777788500e229a468ea1036496ab66a0'; + const value = '0x6a465d1c49869f71fb65562bcbd7e08c8044074927f0297127203f2a9924ff5b'; + + const abiEncodedCall = this.target.contract.methods.mockFunctionWritesStorage(slot, value).encodeABI(); + + expect(await web3.eth.getStorageAt(this.mock.address, slot)).to.be.equal(constants.ZERO_BYTES32); + + expectEvent( + await this.mock.$functionDelegateCall(this.target.address, abiEncodedCall), + 'return$functionDelegateCall', + { ret0: web3.eth.abi.encodeParameters(['string'], ['0x1234']) }, + ); + + expect(await web3.eth.getStorageAt(this.mock.address, slot)).to.be.equal(value); + }); + + it('bubbles up revert reason', async function () { + const abiEncodedCall = this.target.contract.methods.mockFunctionRevertsReason().encodeABI(); + + await expectRevert( + this.mock.$functionDelegateCall(this.target.address, abiEncodedCall), + 'CallReceiverMock: reverting', + ); + }); + + it('reverts when address is not a contract', async function () { + const [recipient] = accounts; + const abiEncodedCall = this.target.contract.methods.mockFunction().encodeABI(); + + await expectRevertCustomError(this.mock.$functionDelegateCall(recipient, abiEncodedCall), 'AddressEmptyCode', [ + recipient, + ]); + }); + }); + + describe('verifyCallResult', function () { + it('returns returndata on success', async function () { + const returndata = '0x123abc'; + expect(await this.mock.$verifyCallResult(true, returndata)).to.equal(returndata); + }); + }); +}); diff --git a/test/utils/Arrays.test.js b/test/utils/Arrays.test.js new file mode 100644 index 000000000..d939d59bd --- /dev/null +++ b/test/utils/Arrays.test.js @@ -0,0 +1,123 @@ +require('@openzeppelin/test-helpers'); + +const { expect } = require('chai'); + +const AddressArraysMock = artifacts.require('AddressArraysMock'); +const Bytes32ArraysMock = artifacts.require('Bytes32ArraysMock'); +const Uint256ArraysMock = artifacts.require('Uint256ArraysMock'); + +contract('Arrays', function () { + describe('findUpperBound', function () { + context('Even number of elements', function () { + const EVEN_ELEMENTS_ARRAY = [11, 12, 13, 14, 15, 16, 17, 18, 19, 20]; + + beforeEach(async function () { + this.arrays = await Uint256ArraysMock.new(EVEN_ELEMENTS_ARRAY); + }); + + it('returns correct index for the basic case', async function () { + expect(await this.arrays.findUpperBound(16)).to.be.bignumber.equal('5'); + }); + + it('returns 0 for the first element', async function () { + expect(await this.arrays.findUpperBound(11)).to.be.bignumber.equal('0'); + }); + + it('returns index of the last element', async function () { + expect(await this.arrays.findUpperBound(20)).to.be.bignumber.equal('9'); + }); + + it('returns first index after last element if searched value is over the upper boundary', async function () { + expect(await this.arrays.findUpperBound(32)).to.be.bignumber.equal('10'); + }); + + it('returns 0 for the element under the lower boundary', async function () { + expect(await this.arrays.findUpperBound(2)).to.be.bignumber.equal('0'); + }); + }); + + context('Odd number of elements', function () { + const ODD_ELEMENTS_ARRAY = [11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21]; + + beforeEach(async function () { + this.arrays = await Uint256ArraysMock.new(ODD_ELEMENTS_ARRAY); + }); + + it('returns correct index for the basic case', async function () { + expect(await this.arrays.findUpperBound(16)).to.be.bignumber.equal('5'); + }); + + it('returns 0 for the first element', async function () { + expect(await this.arrays.findUpperBound(11)).to.be.bignumber.equal('0'); + }); + + it('returns index of the last element', async function () { + expect(await this.arrays.findUpperBound(21)).to.be.bignumber.equal('10'); + }); + + it('returns first index after last element if searched value is over the upper boundary', async function () { + expect(await this.arrays.findUpperBound(32)).to.be.bignumber.equal('11'); + }); + + it('returns 0 for the element under the lower boundary', async function () { + expect(await this.arrays.findUpperBound(2)).to.be.bignumber.equal('0'); + }); + }); + + context('Array with gap', function () { + const WITH_GAP_ARRAY = [11, 12, 13, 14, 15, 20, 21, 22, 23, 24]; + + beforeEach(async function () { + this.arrays = await Uint256ArraysMock.new(WITH_GAP_ARRAY); + }); + + it('returns index of first element in next filled range', async function () { + expect(await this.arrays.findUpperBound(17)).to.be.bignumber.equal('5'); + }); + }); + + context('Empty array', function () { + beforeEach(async function () { + this.arrays = await Uint256ArraysMock.new([]); + }); + + it('always returns 0 for empty array', async function () { + expect(await this.arrays.findUpperBound(10)).to.be.bignumber.equal('0'); + }); + }); + }); + + describe('unsafeAccess', function () { + for (const { type, artifact, elements } of [ + { + type: 'address', + artifact: AddressArraysMock, + elements: Array(10) + .fill() + .map(() => web3.utils.randomHex(20)), + }, + { + type: 'bytes32', + artifact: Bytes32ArraysMock, + elements: Array(10) + .fill() + .map(() => web3.utils.randomHex(32)), + }, + { + type: 'uint256', + artifact: Uint256ArraysMock, + elements: Array(10) + .fill() + .map(() => web3.utils.randomHex(32)), + }, + ]) { + it(type, async function () { + const contract = await artifact.new(elements); + + for (const i in elements) { + expect(await contract.unsafeAccess(i)).to.be.bignumber.equal(elements[i]); + } + }); + } + }); +}); diff --git a/test/utils/Base64.test.js b/test/utils/Base64.test.js new file mode 100644 index 000000000..dfff0b0d0 --- /dev/null +++ b/test/utils/Base64.test.js @@ -0,0 +1,33 @@ +const { expect } = require('chai'); + +const Base64 = artifacts.require('$Base64'); + +contract('Strings', function () { + beforeEach(async function () { + this.base64 = await Base64.new(); + }); + + describe('from bytes - base64', function () { + it('converts to base64 encoded string with double padding', async function () { + const TEST_MESSAGE = 'test'; + const input = web3.utils.asciiToHex(TEST_MESSAGE); + expect(await this.base64.$encode(input)).to.equal('dGVzdA=='); + }); + + it('converts to base64 encoded string with single padding', async function () { + const TEST_MESSAGE = 'test1'; + const input = web3.utils.asciiToHex(TEST_MESSAGE); + expect(await this.base64.$encode(input)).to.equal('dGVzdDE='); + }); + + it('converts to base64 encoded string without padding', async function () { + const TEST_MESSAGE = 'test12'; + const input = web3.utils.asciiToHex(TEST_MESSAGE); + expect(await this.base64.$encode(input)).to.equal('dGVzdDEy'); + }); + + it('empty bytes', async function () { + expect(await this.base64.$encode([])).to.equal(''); + }); + }); +}); diff --git a/test/utils/Context.behavior.js b/test/utils/Context.behavior.js new file mode 100644 index 000000000..08f7558d7 --- /dev/null +++ b/test/utils/Context.behavior.js @@ -0,0 +1,42 @@ +const { BN, expectEvent } = require('@openzeppelin/test-helpers'); + +const ContextMock = artifacts.require('ContextMock'); + +function shouldBehaveLikeRegularContext(sender) { + describe('msgSender', function () { + it('returns the transaction sender when called from an EOA', async function () { + const receipt = await this.context.msgSender({ from: sender }); + expectEvent(receipt, 'Sender', { sender }); + }); + + it('returns the transaction sender when from another contract', async function () { + const { tx } = await this.caller.callSender(this.context.address, { from: sender }); + await expectEvent.inTransaction(tx, ContextMock, 'Sender', { sender: this.caller.address }); + }); + }); + + describe('msgData', function () { + const integerValue = new BN('42'); + const stringValue = 'OpenZeppelin'; + + let callData; + + beforeEach(async function () { + callData = this.context.contract.methods.msgData(integerValue.toString(), stringValue).encodeABI(); + }); + + it('returns the transaction data when called from an EOA', async function () { + const receipt = await this.context.msgData(integerValue, stringValue); + expectEvent(receipt, 'Data', { data: callData, integerValue, stringValue }); + }); + + it('returns the transaction sender when from another contract', async function () { + const { tx } = await this.caller.callData(this.context.address, integerValue, stringValue); + await expectEvent.inTransaction(tx, ContextMock, 'Data', { data: callData, integerValue, stringValue }); + }); + }); +} + +module.exports = { + shouldBehaveLikeRegularContext, +}; diff --git a/test/utils/Context.test.js b/test/utils/Context.test.js new file mode 100644 index 000000000..f372f7420 --- /dev/null +++ b/test/utils/Context.test.js @@ -0,0 +1,17 @@ +require('@openzeppelin/test-helpers'); + +const ContextMock = artifacts.require('ContextMock'); +const ContextMockCaller = artifacts.require('ContextMockCaller'); + +const { shouldBehaveLikeRegularContext } = require('./Context.behavior'); + +contract('Context', function (accounts) { + const [sender] = accounts; + + beforeEach(async function () { + this.context = await ContextMock.new(); + this.caller = await ContextMockCaller.new(); + }); + + shouldBehaveLikeRegularContext(sender); +}); diff --git a/test/utils/Create2.test.js b/test/utils/Create2.test.js new file mode 100644 index 000000000..336ab1acc --- /dev/null +++ b/test/utils/Create2.test.js @@ -0,0 +1,102 @@ +const { balance, ether, expectEvent, expectRevert, send } = require('@openzeppelin/test-helpers'); +const { expect } = require('chai'); +const { computeCreate2Address } = require('../helpers/create'); +const { expectRevertCustomError } = require('../helpers/customError'); + +const Create2 = artifacts.require('$Create2'); +const VestingWallet = artifacts.require('VestingWallet'); +// 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; + + const salt = 'salt message'; + const saltHex = web3.utils.soliditySha3(salt); + + const encodedParams = web3.eth.abi.encodeParameters(['address', 'uint64', 'uint64'], [other, 0, 0]).slice(2); + + const constructorByteCode = `${VestingWallet.bytecode}${encodedParams}`; + + beforeEach(async function () { + this.factory = await Create2.new(); + }); + describe('computeAddress', function () { + it('computes the correct contract address', async function () { + const onChainComputed = await this.factory.$computeAddress(saltHex, web3.utils.keccak256(constructorByteCode)); + const offChainComputed = computeCreate2Address(saltHex, constructorByteCode, this.factory.address); + expect(onChainComputed).to.equal(offChainComputed); + }); + + it('computes the correct contract address with deployer', async function () { + const onChainComputed = await this.factory.$computeAddress( + saltHex, + web3.utils.keccak256(constructorByteCode), + deployerAccount, + ); + const offChainComputed = computeCreate2Address(saltHex, constructorByteCode, deployerAccount); + expect(onChainComputed).to.equal(offChainComputed); + }); + }); + + describe('deploy', function () { + it('deploys a contract without constructor', async function () { + const offChainComputed = computeCreate2Address(saltHex, ConstructorLessContract.bytecode, this.factory.address); + + expectEvent(await this.factory.$deploy(0, saltHex, ConstructorLessContract.bytecode), 'return$deploy', { + addr: offChainComputed, + }); + + expect(ConstructorLessContract.bytecode).to.include((await web3.eth.getCode(offChainComputed)).slice(2)); + }); + + it('deploys a contract with constructor arguments', async function () { + const offChainComputed = computeCreate2Address(saltHex, constructorByteCode, this.factory.address); + + expectEvent(await this.factory.$deploy(0, saltHex, constructorByteCode), 'return$deploy', { + addr: offChainComputed, + }); + + 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 () { + const deposit = ether('2'); + await send.ether(deployerAccount, this.factory.address, deposit); + expect(await balance.current(this.factory.address)).to.be.bignumber.equal(deposit); + + const offChainComputed = computeCreate2Address(saltHex, constructorByteCode, this.factory.address); + + expectEvent(await this.factory.$deploy(deposit, saltHex, constructorByteCode), 'return$deploy', { + addr: offChainComputed, + }); + + expect(await balance.current(offChainComputed)).to.be.bignumber.equal(deposit); + }); + + it('fails deploying a contract in an existent address', async function () { + expectEvent(await this.factory.$deploy(0, saltHex, constructorByteCode), 'return$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 expectRevertCustomError(this.factory.$deploy(0, saltHex, '0x'), 'Create2EmptyBytecode', []); + }); + + it('fails deploying a contract if factory contract does not have sufficient balance', async function () { + 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 new file mode 100644 index 000000000..65443cd0a --- /dev/null +++ b/test/utils/Multicall.test.js @@ -0,0 +1,69 @@ +const { BN } = require('@openzeppelin/test-helpers'); +const { expectRevertCustomError } = require('../helpers/customError'); + +const ERC20MulticallMock = artifacts.require('$ERC20MulticallMock'); + +contract('Multicall', function (accounts) { + const [deployer, alice, bob] = accounts; + const amount = 12000; + + beforeEach(async function () { + this.multicallToken = await ERC20MulticallMock.new('name', 'symbol'); + await this.multicallToken.$_mint(deployer, amount); + }); + + it('batches function calls', async function () { + expect(await this.multicallToken.balanceOf(alice)).to.be.bignumber.equal(new BN('0')); + expect(await this.multicallToken.balanceOf(bob)).to.be.bignumber.equal(new BN('0')); + + await this.multicallToken.multicall( + [ + this.multicallToken.contract.methods.transfer(alice, amount / 2).encodeABI(), + this.multicallToken.contract.methods.transfer(bob, amount / 3).encodeABI(), + ], + { from: deployer }, + ); + + expect(await this.multicallToken.balanceOf(alice)).to.be.bignumber.equal(new BN(amount / 2)); + expect(await this.multicallToken.balanceOf(bob)).to.be.bignumber.equal(new BN(amount / 3)); + }); + + it('returns an array with the result of each call', async function () { + const MulticallTest = artifacts.require('MulticallTest'); + const multicallTest = await MulticallTest.new({ from: deployer }); + await this.multicallToken.transfer(multicallTest.address, amount, { from: deployer }); + expect(await this.multicallToken.balanceOf(multicallTest.address)).to.be.bignumber.equal(new BN(amount)); + + const recipients = [alice, bob]; + const amounts = [amount / 2, amount / 3].map(n => new BN(n)); + + await multicallTest.checkReturnValues(this.multicallToken.address, recipients, amounts); + }); + + it('reverts previous calls', async function () { + expect(await this.multicallToken.balanceOf(alice)).to.be.bignumber.equal(new BN('0')); + + const call = this.multicallToken.multicall( + [ + this.multicallToken.contract.methods.transfer(alice, amount).encodeABI(), + this.multicallToken.contract.methods.transfer(bob, amount).encodeABI(), + ], + { from: deployer }, + ); + + await expectRevertCustomError(call, 'ERC20InsufficientBalance', [deployer, 0, amount]); + expect(await this.multicallToken.balanceOf(alice)).to.be.bignumber.equal(new BN('0')); + }); + + it('bubbles up revert reasons', async function () { + const call = this.multicallToken.multicall( + [ + this.multicallToken.contract.methods.transfer(alice, amount).encodeABI(), + this.multicallToken.contract.methods.transfer(bob, amount).encodeABI(), + ], + { from: deployer }, + ); + + await expectRevertCustomError(call, 'ERC20InsufficientBalance', [deployer, 0, amount]); + }); +}); diff --git a/test/utils/Nonces.test.js b/test/utils/Nonces.test.js new file mode 100644 index 000000000..67a3087e3 --- /dev/null +++ b/test/utils/Nonces.test.js @@ -0,0 +1,71 @@ +const expectEvent = require('@openzeppelin/test-helpers/src/expectEvent'); +const { expectRevertCustomError } = require('../helpers/customError'); + +require('@openzeppelin/test-helpers'); + +const Nonces = artifacts.require('$Nonces'); + +contract('Nonces', function (accounts) { + const [sender, other] = accounts; + + beforeEach(async function () { + this.nonces = await Nonces.new(); + }); + + it('gets 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'); + }); + + 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'); + + 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'); + }); + }); + + describe('_useCheckedNonce', function () { + it('increments a nonce', async function () { + const currentNonce = await this.nonces.nonces(sender); + expect(currentNonce).to.be.bignumber.equal('0'); + + await this.nonces.$_useCheckedNonce(sender, 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); + + 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/Pausable.test.js b/test/utils/Pausable.test.js new file mode 100644 index 000000000..e60a62c74 --- /dev/null +++ b/test/utils/Pausable.test.js @@ -0,0 +1,86 @@ +const { expectEvent } = require('@openzeppelin/test-helpers'); +const { expect } = require('chai'); + +const { expectRevertCustomError } = require('../helpers/customError'); + +const PausableMock = artifacts.require('PausableMock'); + +contract('Pausable', function (accounts) { + const [pauser] = accounts; + + beforeEach(async function () { + this.pausable = await PausableMock.new(); + }); + + context('when unpaused', function () { + beforeEach(async function () { + expect(await this.pausable.paused()).to.equal(false); + }); + + it('can perform normal process in non-pause', async function () { + expect(await this.pausable.count()).to.be.bignumber.equal('0'); + + await this.pausable.normalProcess(); + expect(await this.pausable.count()).to.be.bignumber.equal('1'); + }); + + it('cannot take drastic measure in non-pause', async function () { + await expectRevertCustomError(this.pausable.drasticMeasure(), 'ExpectedPause', []); + expect(await this.pausable.drasticMeasureTaken()).to.equal(false); + }); + + context('when paused', function () { + beforeEach(async function () { + this.receipt = await this.pausable.pause({ from: pauser }); + }); + + it('emits a Paused event', function () { + expectEvent(this.receipt, 'Paused', { account: pauser }); + }); + + it('cannot perform normal process in pause', async function () { + await expectRevertCustomError(this.pausable.normalProcess(), 'EnforcedPause', []); + }); + + it('can take a drastic measure in a pause', async function () { + await this.pausable.drasticMeasure(); + expect(await this.pausable.drasticMeasureTaken()).to.equal(true); + }); + + it('reverts when re-pausing', async function () { + await expectRevertCustomError(this.pausable.pause(), 'EnforcedPause', []); + }); + + describe('unpausing', function () { + it('is unpausable by the pauser', async function () { + await this.pausable.unpause(); + expect(await this.pausable.paused()).to.equal(false); + }); + + context('when unpaused', function () { + beforeEach(async function () { + this.receipt = await this.pausable.unpause({ from: pauser }); + }); + + it('emits an Unpaused event', function () { + expectEvent(this.receipt, 'Unpaused', { account: pauser }); + }); + + it('should resume allowing normal process', async function () { + expect(await this.pausable.count()).to.be.bignumber.equal('0'); + await this.pausable.normalProcess(); + expect(await this.pausable.count()).to.be.bignumber.equal('1'); + }); + + it('should prevent drastic measure', async function () { + await expectRevertCustomError(this.pausable.drasticMeasure(), 'ExpectedPause', []); + }); + + it('reverts when re-unpausing', async function () { + await expectRevertCustomError(this.pausable.unpause(), 'ExpectedPause', []); + }); + }); + }); + }); + }); +}); diff --git a/test/utils/ReentrancyGuard.test.js b/test/utils/ReentrancyGuard.test.js new file mode 100644 index 000000000..15355c098 --- /dev/null +++ b/test/utils/ReentrancyGuard.test.js @@ -0,0 +1,44 @@ +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'); + +contract('ReentrancyGuard', function () { + beforeEach(async function () { + this.reentrancyMock = await ReentrancyMock.new(); + expect(await this.reentrancyMock.counter()).to.be.bignumber.equal('0'); + }); + + it('nonReentrant function can be called', async function () { + expect(await this.reentrancyMock.counter()).to.be.bignumber.equal('0'); + await this.reentrancyMock.callback(); + expect(await this.reentrancyMock.counter()).to.be.bignumber.equal('1'); + }); + + it('does not allow remote callback', async function () { + const attacker = await ReentrancyAttack.new(); + await expectRevert(this.reentrancyMock.countAndCall(attacker.address), 'ReentrancyAttack: failed call', []); + }); + + it('_reentrancyGuardEntered should be true when guarded', async function () { + await this.reentrancyMock.guardedCheckEntered(); + }); + + it('_reentrancyGuardEntered should be false when unguarded', async function () { + await this.reentrancyMock.unguardedCheckNotEntered(); + }); + + // The following are more side-effects than intended behavior: + // I put them here as documentation, and to monitor any changes + // in the side-effects. + it('does not allow local recursion', async function () { + 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', []); + }); +}); diff --git a/test/utils/ShortStrings.t.sol b/test/utils/ShortStrings.t.sol new file mode 100644 index 000000000..b854d273c --- /dev/null +++ b/test/utils/ShortStrings.t.sol @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {Test} from "forge-std/Test.sol"; + +import {ShortStrings, ShortString} from "@openzeppelin/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; + } +} diff --git a/test/utils/ShortStrings.test.js b/test/utils/ShortStrings.test.js new file mode 100644 index 000000000..189281d38 --- /dev/null +++ b/test/utils/ShortStrings.test.js @@ -0,0 +1,55 @@ +const { expect } = require('chai'); +const { expectRevertCustomError } = require('../helpers/customError'); + +const ShortStrings = artifacts.require('$ShortStrings'); + +function length(sstr) { + return parseInt(sstr.slice(64), 16); +} + +function decode(sstr) { + return web3.utils.toUtf8(sstr).slice(0, length(sstr)); +} + +contract('ShortStrings', function () { + before(async function () { + this.mock = await ShortStrings.new(); + }); + + for (const str of [0, 1, 16, 31, 32, 64, 1024].map(length => 'a'.repeat(length))) { + describe(`with string length ${str.length}`, function () { + it('encode / decode', async function () { + if (str.length < 32) { + const encoded = await this.mock.$toShortString(str); + expect(decode(encoded)).to.be.equal(str); + + const length = await this.mock.$byteLength(encoded); + expect(length.toNumber()).to.be.equal(str.length); + + const decoded = await this.mock.$toString(encoded); + expect(decoded).to.be.equal(str); + } else { + await expectRevertCustomError(this.mock.$toShortString(str), 'StringTooLong', [str]); + } + }); + + it('set / get with fallback', async function () { + const { logs } = await this.mock.$toShortStringWithFallback(str, 0); + const { ret0 } = logs.find(({ event }) => event == 'return$toShortStringWithFallback').args; + + const promise = this.mock.$toString(ret0); + if (str.length < 32) { + expect(await promise).to.be.equal(str); + } else { + await expectRevertCustomError(promise, 'InvalidShortString', []); + } + + const length = await this.mock.$byteLengthWithFallback(ret0, 0); + expect(length.toNumber()).to.be.equal(str.length); + + const recovered = await this.mock.$toStringWithFallback(ret0, 0); + expect(recovered).to.be.equal(str); + }); + }); + } +}); diff --git a/test/utils/StorageSlot.test.js b/test/utils/StorageSlot.test.js new file mode 100644 index 000000000..846512ed2 --- /dev/null +++ b/test/utils/StorageSlot.test.js @@ -0,0 +1,210 @@ +const { constants, BN } = require('@openzeppelin/test-helpers'); + +const { expect } = require('chai'); + +const StorageSlotMock = artifacts.require('StorageSlotMock'); + +const slot = web3.utils.keccak256('some.storage.slot'); +const otherSlot = web3.utils.keccak256('some.other.storage.slot'); + +contract('StorageSlot', function (accounts) { + beforeEach(async function () { + this.store = await StorageSlotMock.new(); + }); + + describe('boolean storage slot', function () { + beforeEach(async function () { + this.value = true; + }); + + it('set', async function () { + await this.store.setBoolean(slot, this.value); + }); + + describe('get', function () { + beforeEach(async function () { + await this.store.setBoolean(slot, this.value); + }); + + it('from right slot', async function () { + expect(await this.store.getBoolean(slot)).to.be.equal(this.value); + }); + + it('from other slot', async function () { + expect(await this.store.getBoolean(otherSlot)).to.be.equal(false); + }); + }); + }); + + describe('address storage slot', function () { + beforeEach(async function () { + this.value = accounts[1]; + }); + + it('set', async function () { + await this.store.setAddress(slot, this.value); + }); + + describe('get', function () { + beforeEach(async function () { + await this.store.setAddress(slot, this.value); + }); + + it('from right slot', async function () { + expect(await this.store.getAddress(slot)).to.be.equal(this.value); + }); + + it('from other slot', async function () { + expect(await this.store.getAddress(otherSlot)).to.be.equal(constants.ZERO_ADDRESS); + }); + }); + }); + + describe('bytes32 storage slot', function () { + beforeEach(async function () { + this.value = web3.utils.keccak256('some byte32 value'); + }); + + it('set', async function () { + await this.store.setBytes32(slot, this.value); + }); + + describe('get', function () { + beforeEach(async function () { + await this.store.setBytes32(slot, this.value); + }); + + it('from right slot', async function () { + expect(await this.store.getBytes32(slot)).to.be.equal(this.value); + }); + + it('from other slot', async function () { + expect(await this.store.getBytes32(otherSlot)).to.be.equal(constants.ZERO_BYTES32); + }); + }); + }); + + describe('uint256 storage slot', function () { + beforeEach(async function () { + this.value = new BN(1742); + }); + + it('set', async function () { + await this.store.setUint256(slot, this.value); + }); + + describe('get', function () { + beforeEach(async function () { + await this.store.setUint256(slot, this.value); + }); + + it('from right slot', async function () { + expect(await this.store.getUint256(slot)).to.be.bignumber.equal(this.value); + }); + + it('from other slot', async function () { + expect(await this.store.getUint256(otherSlot)).to.be.bignumber.equal('0'); + }); + }); + }); + + describe('string storage slot', function () { + beforeEach(async function () { + this.value = 'lorem ipsum'; + }); + + it('set', async function () { + await this.store.setString(slot, this.value); + }); + + describe('get', function () { + beforeEach(async function () { + await this.store.setString(slot, this.value); + }); + + it('from right slot', async function () { + expect(await this.store.getString(slot)).to.be.equal(this.value); + }); + + it('from other slot', async function () { + expect(await this.store.getString(otherSlot)).to.be.equal(''); + }); + }); + }); + + describe('string storage pointer', function () { + beforeEach(async function () { + this.value = 'lorem ipsum'; + }); + + it('set', async function () { + await this.store.setStringStorage(slot, this.value); + }); + + describe('get', function () { + beforeEach(async function () { + await this.store.setStringStorage(slot, this.value); + }); + + it('from right slot', async function () { + expect(await this.store.stringMap(slot)).to.be.equal(this.value); + expect(await this.store.getStringStorage(slot)).to.be.equal(this.value); + }); + + it('from other slot', async function () { + expect(await this.store.stringMap(otherSlot)).to.be.equal(''); + expect(await this.store.getStringStorage(otherSlot)).to.be.equal(''); + }); + }); + }); + + describe('bytes storage slot', function () { + beforeEach(async function () { + this.value = web3.utils.randomHex(128); + }); + + it('set', async function () { + await this.store.setBytes(slot, this.value); + }); + + describe('get', function () { + beforeEach(async function () { + await this.store.setBytes(slot, this.value); + }); + + it('from right slot', async function () { + expect(await this.store.getBytes(slot)).to.be.equal(this.value); + }); + + it('from other slot', async function () { + expect(await this.store.getBytes(otherSlot)).to.be.equal(null); + }); + }); + }); + + describe('bytes storage pointer', function () { + beforeEach(async function () { + this.value = web3.utils.randomHex(128); + }); + + it('set', async function () { + await this.store.setBytesStorage(slot, this.value); + }); + + describe('get', function () { + beforeEach(async function () { + await this.store.setBytesStorage(slot, this.value); + }); + + it('from right slot', async function () { + expect(await this.store.bytesMap(slot)).to.be.equal(this.value); + expect(await this.store.getBytesStorage(slot)).to.be.equal(this.value); + }); + + it('from other slot', async function () { + expect(await this.store.bytesMap(otherSlot)).to.be.equal(null); + expect(await this.store.getBytesStorage(otherSlot)).to.be.equal(null); + }); + }); + }); +}); diff --git a/test/utils/Strings.test.js b/test/utils/Strings.test.js new file mode 100644 index 000000000..2435fc71c --- /dev/null +++ b/test/utils/Strings.test.js @@ -0,0 +1,153 @@ +const { BN, constants } = require('@openzeppelin/test-helpers'); +const { expectRevertCustomError } = require('../helpers/customError'); + +const { expect } = require('chai'); + +const Strings = artifacts.require('$Strings'); + +contract('Strings', function () { + before(async function () { + this.strings = await Strings.new(); + }); + + describe('toString', function () { + const values = [ + '0', + '7', + '10', + '99', + '100', + '101', + '123', + '4132', + '12345', + '1234567', + '1234567890', + '123456789012345', + '12345678901234567890', + '123456789012345678901234567890', + '1234567890123456789012345678901234567890', + '12345678901234567890123456789012345678901234567890', + '123456789012345678901234567890123456789012345678901234567890', + '1234567890123456789012345678901234567890123456789012345678901234567890', + ]; + + describe('uint256', function () { + it('converts MAX_UINT256', async function () { + const value = constants.MAX_UINT256; + expect(await this.strings.methods['$toString(uint256)'](value)).to.equal(value.toString(10)); + }); + + for (const value of values) { + it(`converts ${value}`, async function () { + expect(await this.strings.methods['$toString(uint256)'](value)).to.equal(value); + }); + } + }); + + describe('int256', function () { + it('converts MAX_INT256', async function () { + const value = constants.MAX_INT256; + 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['$toStringSigned(int256)'](value)).to.equal(value.toString(10)); + }); + + for (const value of values) { + it(`convert ${value}`, async function () { + 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['$toStringSigned(int256)'](negated)).to.equal(negated.toString(10)); + }); + } + }); + }); + + describe('toHexString', function () { + it('converts 0', async function () { + expect(await this.strings.methods['$toHexString(uint256)'](0)).to.equal('0x00'); + }); + + it('converts a positive number', async function () { + expect(await this.strings.methods['$toHexString(uint256)'](0x4132)).to.equal('0x4132'); + }); + + it('converts MAX_UINT256', async function () { + expect(await this.strings.methods['$toHexString(uint256)'](constants.MAX_UINT256)).to.equal( + web3.utils.toHex(constants.MAX_UINT256), + ); + }); + }); + + describe('toHexString fixed', function () { + it('converts a positive number (long)', async function () { + expect(await this.strings.methods['$toHexString(uint256,uint256)'](0x4132, 32)).to.equal( + '0x0000000000000000000000000000000000000000000000000000000000004132', + ); + }); + + it('converts a positive number (short)', async function () { + const length = 1; + await expectRevertCustomError( + this.strings.methods['$toHexString(uint256,uint256)'](0x4132, length), + `StringsInsufficientHexLength`, + [0x4132, length], + ); + }); + + it('converts MAX_UINT256', async function () { + expect(await this.strings.methods['$toHexString(uint256,uint256)'](constants.MAX_UINT256, 32)).to.equal( + web3.utils.toHex(constants.MAX_UINT256), + ); + }); + }); + + describe('toHexString address', function () { + it('converts a random address', async function () { + const addr = '0xa9036907dccae6a1e0033479b12e837e5cf5a02f'; + expect(await this.strings.methods['$toHexString(address)'](addr)).to.equal(addr); + }); + + it('converts an address with leading zeros', async function () { + const addr = '0x0000e0ca771e21bd00057f54a68c30d400000000'; + expect(await this.strings.methods['$toHexString(address)'](addr)).to.equal(addr); + }); + }); + + describe('equal', function () { + it('compares two empty strings', async function () { + expect(await this.strings.methods['$equal(string,string)']('', '')).to.equal(true); + }); + + it('compares two equal strings', async function () { + expect(await this.strings.methods['$equal(string,string)']('a', 'a')).to.equal(true); + }); + + it('compares two different strings', async function () { + expect(await this.strings.methods['$equal(string,string)']('a', 'b')).to.equal(false); + }); + + it('compares two different strings of different lengths', async function () { + expect(await this.strings.methods['$equal(string,string)']('a', 'aa')).to.equal(false); + expect(await this.strings.methods['$equal(string,string)']('aa', 'a')).to.equal(false); + }); + + it('compares two different large strings', async function () { + const str1 = 'a'.repeat(201); + const str2 = 'a'.repeat(200) + 'b'; + expect(await this.strings.methods['$equal(string,string)'](str1, str2)).to.equal(false); + }); + + it('compares two equal large strings', async function () { + const str1 = 'a'.repeat(201); + const str2 = 'a'.repeat(201); + expect(await this.strings.methods['$equal(string,string)'](str1, str2)).to.equal(true); + }); + }); +}); diff --git a/test/utils/cryptography/ECDSA.test.js b/test/utils/cryptography/ECDSA.test.js new file mode 100644 index 000000000..f164ef196 --- /dev/null +++ b/test/utils/cryptography/ECDSA.test.js @@ -0,0 +1,245 @@ +require('@openzeppelin/test-helpers'); +const { expectRevertCustomError } = require('../../helpers/customError'); +const { toEthSignedMessageHash } = require('../../helpers/sign'); + +const { expect } = require('chai'); + +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'); + +function to2098Format(signature) { + const long = web3.utils.hexToBytes(signature); + if (long.length !== 65) { + throw new Error('invalid signature length (expected long format)'); + } + if (long[32] >> 7 === 1) { + throw new Error("invalid signature 's' value"); + } + const short = long.slice(0, 64); + short[32] |= long[64] % 27 << 7; // set the first bit of the 32nd byte to the v parity bit + return web3.utils.bytesToHex(short); +} + +function split(signature) { + const raw = web3.utils.hexToBytes(signature); + switch (raw.length) { + case 64: + return [ + web3.utils.bytesToHex(raw.slice(0, 32)), // r + web3.utils.bytesToHex(raw.slice(32, 64)), // vs + ]; + case 65: + return [ + raw[64], // v + web3.utils.bytesToHex(raw.slice(0, 32)), // r + web3.utils.bytesToHex(raw.slice(32, 64)), // s + ]; + default: + expect.fail('Invalid signature length, cannot split'); + } +} + +contract('ECDSA', function (accounts) { + const [other] = accounts; + + beforeEach(async function () { + this.ecdsa = await ECDSA.new(); + }); + + context('recover with invalid signature', function () { + it('with short signature', async function () { + await expectRevertCustomError(this.ecdsa.$recover(TEST_MESSAGE, '0x1234'), 'ECDSAInvalidSignatureLength', [2]); + }); + + it('with long signature', async function () { + await expectRevertCustomError( + // eslint-disable-next-line max-len + this.ecdsa.$recover( + TEST_MESSAGE, + '0x01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789', + ), + 'ECDSAInvalidSignatureLength', + [85], + ); + }); + }); + + context('recover with valid signature', function () { + context('using web3.eth.sign', function () { + it('returns signer address with correct signature', async function () { + // Create the signature + const signature = await web3.eth.sign(TEST_MESSAGE, other); + + // Recover the signer address from the generated message and signature. + expect(await this.ecdsa.$recover(toEthSignedMessageHash(TEST_MESSAGE), signature)).to.equal(other); + }); + + it('returns signer address with correct signature for arbitrary length message', async function () { + // Create the signature + const signature = await web3.eth.sign(NON_HASH_MESSAGE, other); + + // Recover the signer address from the generated message and signature. + expect(await this.ecdsa.$recover(toEthSignedMessageHash(NON_HASH_MESSAGE), signature)).to.equal(other); + }); + + it('returns a different address', async function () { + const signature = await web3.eth.sign(TEST_MESSAGE, other); + expect(await this.ecdsa.$recover(WRONG_MESSAGE, signature)).to.not.equal(other); + }); + + it('reverts with invalid signature', async function () { + // eslint-disable-next-line max-len + const signature = + '0x332ce75a821c982f9127538858900d87d3ec1f9f737338ad67cad133fa48feff48e6fa0c18abc62e42820f05943e47af3e9fbe306ce74d64094bdf1691ee53e01c'; + await expectRevertCustomError(this.ecdsa.$recover(TEST_MESSAGE, signature), 'ECDSAInvalidSignature', []); + }); + }); + + context('with v=27 signature', function () { + // Signature generated outside ganache with method web3.eth.sign(signer, message) + const signer = '0x2cc1166f6212628A0deEf2B33BEFB2187D35b86c'; + // eslint-disable-next-line max-len + const signatureWithoutV = + '0x5d99b6f7f6d1f73d1a26497f2b1c89b24c0993913f86e9a2d02cd69887d9c94f3c880358579d811b21dd1b7fd9bb01c1d81d10e69f0384e675c32b39643be892'; + + it('works with correct v value', async function () { + const v = '1b'; // 27 = 1b. + const signature = signatureWithoutV + v; + expect(await this.ecdsa.$recover(TEST_MESSAGE, signature)).to.equal(signer); + + expect( + await this.ecdsa.methods['$recover(bytes32,uint8,bytes32,bytes32)'](TEST_MESSAGE, ...split(signature)), + ).to.equal(signer); + + expect( + await this.ecdsa.methods['$recover(bytes32,bytes32,bytes32)']( + TEST_MESSAGE, + ...split(to2098Format(signature)), + ), + ).to.equal(signer); + }); + + it('rejects incorrect v value', async function () { + const v = '1c'; // 28 = 1c. + const signature = signatureWithoutV + v; + expect(await this.ecdsa.$recover(TEST_MESSAGE, signature)).to.not.equal(signer); + + expect( + await this.ecdsa.methods['$recover(bytes32,uint8,bytes32,bytes32)'](TEST_MESSAGE, ...split(signature)), + ).to.not.equal(signer); + + expect( + await this.ecdsa.methods['$recover(bytes32,bytes32,bytes32)']( + TEST_MESSAGE, + ...split(to2098Format(signature)), + ), + ).to.not.equal(signer); + }); + + it('reverts wrong v values', async function () { + for (const v of ['00', '01']) { + const signature = signatureWithoutV + v; + await expectRevertCustomError(this.ecdsa.$recover(TEST_MESSAGE, signature), 'ECDSAInvalidSignature', []); + + await expectRevertCustomError( + this.ecdsa.methods['$recover(bytes32,uint8,bytes32,bytes32)'](TEST_MESSAGE, ...split(signature)), + 'ECDSAInvalidSignature', + [], + ); + } + }); + + it('rejects short EIP2098 format', async function () { + const v = '1b'; // 27 = 1b. + const signature = signatureWithoutV + v; + await expectRevertCustomError( + this.ecdsa.$recover(TEST_MESSAGE, to2098Format(signature)), + 'ECDSAInvalidSignatureLength', + [64], + ); + }); + }); + + context('with v=28 signature', function () { + const signer = '0x1E318623aB09Fe6de3C9b8672098464Aeda9100E'; + // eslint-disable-next-line max-len + const signatureWithoutV = + '0x331fe75a821c982f9127538858900d87d3ec1f9f737338ad67cad133fa48feff48e6fa0c18abc62e42820f05943e47af3e9fbe306ce74d64094bdf1691ee53e0'; + + it('works with correct v value', async function () { + const v = '1c'; // 28 = 1c. + const signature = signatureWithoutV + v; + expect(await this.ecdsa.$recover(TEST_MESSAGE, signature)).to.equal(signer); + + expect( + await this.ecdsa.methods['$recover(bytes32,uint8,bytes32,bytes32)'](TEST_MESSAGE, ...split(signature)), + ).to.equal(signer); + + expect( + await this.ecdsa.methods['$recover(bytes32,bytes32,bytes32)']( + TEST_MESSAGE, + ...split(to2098Format(signature)), + ), + ).to.equal(signer); + }); + + it('rejects incorrect v value', async function () { + const v = '1b'; // 27 = 1b. + const signature = signatureWithoutV + v; + expect(await this.ecdsa.$recover(TEST_MESSAGE, signature)).to.not.equal(signer); + + expect( + await this.ecdsa.methods['$recover(bytes32,uint8,bytes32,bytes32)'](TEST_MESSAGE, ...split(signature)), + ).to.not.equal(signer); + + expect( + await this.ecdsa.methods['$recover(bytes32,bytes32,bytes32)']( + TEST_MESSAGE, + ...split(to2098Format(signature)), + ), + ).to.not.equal(signer); + }); + + it('reverts invalid v values', async function () { + for (const v of ['00', '01']) { + const signature = signatureWithoutV + v; + await expectRevertCustomError(this.ecdsa.$recover(TEST_MESSAGE, signature), 'ECDSAInvalidSignature', []); + + await expectRevertCustomError( + this.ecdsa.methods['$recover(bytes32,uint8,bytes32,bytes32)'](TEST_MESSAGE, ...split(signature)), + 'ECDSAInvalidSignature', + [], + ); + } + }); + + it('rejects short EIP2098 format', async function () { + const v = '1c'; // 27 = 1b. + const signature = signatureWithoutV + v; + await expectRevertCustomError( + this.ecdsa.$recover(TEST_MESSAGE, to2098Format(signature)), + 'ECDSAInvalidSignatureLength', + [64], + ); + }); + }); + + it('reverts with high-s value signature', async function () { + const message = '0xb94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9'; + // eslint-disable-next-line max-len + const highSSignature = + '0xe742ff452d41413616a5bf43fe15dd88294e983d3d36206c2712f39083d638bde0a0fc89be718fbc1033e1d30d78be1c68081562ed2e97af876f286f3453231d1b'; + 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/EIP712.test.js b/test/utils/cryptography/EIP712.test.js new file mode 100644 index 000000000..faf01f1a3 --- /dev/null +++ b/test/utils/cryptography/EIP712.test.js @@ -0,0 +1,111 @@ +const ethSigUtil = require('eth-sig-util'); +const Wallet = require('ethereumjs-wallet').default; + +const { getDomain, domainType, domainSeparator, hashTypedData } = require('../../helpers/eip712'); +const { getChainId } = require('../../helpers/chainid'); +const { mapValues } = require('../../helpers/iterate'); + +const EIP712Verifier = artifacts.require('$EIP712Verifier'); +const Clones = artifacts.require('$Clones'); + +contract('EIP712', function (accounts) { + const [mailTo] = accounts; + + const shortName = 'A Name'; + const shortVersion = '1'; + + const longName = 'A'.repeat(40); + const longVersion = 'B'.repeat(40); + + const cases = [ + ['short', shortName, shortVersion], + ['long', longName, longVersion], + ]; + + 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); + + 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 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 () { + 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); + }); + + 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); + }); + }); + } +}); diff --git a/test/utils/cryptography/MerkleProof.test.js b/test/utils/cryptography/MerkleProof.test.js new file mode 100644 index 000000000..5b87bc525 --- /dev/null +++ b/test/utils/cryptography/MerkleProof.test.js @@ -0,0 +1,207 @@ +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'); + +contract('MerkleProof', function () { + beforeEach(async function () { + this.merkleProof = await MerkleProof.new(); + }); + + describe('verify', function () { + it('returns true for a valid Merkle proof', async function () { + const elements = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='.split(''); + const merkleTree = new MerkleTree(elements, keccak256, { hashLeaves: true, sortPairs: true }); + + const root = merkleTree.getHexRoot(); + + const leaf = keccak256(elements[0]); + + const proof = merkleTree.getHexProof(leaf); + + expect(await this.merkleProof.$verify(proof, root, leaf)).to.equal(true); + expect(await this.merkleProof.$verifyCalldata(proof, root, leaf)).to.equal(true); + + // For demonstration, it is also possible to create valid proofs for certain 64-byte values *not* in elements: + const noSuchLeaf = keccak256( + Buffer.concat([keccak256(elements[0]), keccak256(elements[1])].sort(Buffer.compare)), + ); + expect(await this.merkleProof.$verify(proof.slice(1), root, noSuchLeaf)).to.equal(true); + expect(await this.merkleProof.$verifyCalldata(proof.slice(1), root, noSuchLeaf)).to.equal(true); + }); + + it('returns false for an invalid Merkle proof', async function () { + const correctElements = ['a', 'b', 'c']; + const correctMerkleTree = new MerkleTree(correctElements, keccak256, { hashLeaves: true, sortPairs: true }); + + const correctRoot = correctMerkleTree.getHexRoot(); + + const correctLeaf = keccak256(correctElements[0]); + + const badElements = ['d', 'e', 'f']; + const badMerkleTree = new MerkleTree(badElements); + + const badProof = badMerkleTree.getHexProof(badElements[0]); + + expect(await this.merkleProof.$verify(badProof, correctRoot, correctLeaf)).to.equal(false); + expect(await this.merkleProof.$verifyCalldata(badProof, correctRoot, correctLeaf)).to.equal(false); + }); + + it('returns false for a Merkle proof of invalid length', async function () { + const elements = ['a', 'b', 'c']; + const merkleTree = new MerkleTree(elements, keccak256, { hashLeaves: true, sortPairs: true }); + + const root = merkleTree.getHexRoot(); + + const leaf = keccak256(elements[0]); + + const proof = merkleTree.getHexProof(leaf); + const badProof = proof.slice(0, proof.length - 5); + + expect(await this.merkleProof.$verify(badProof, root, leaf)).to.equal(false); + expect(await this.merkleProof.$verifyCalldata(badProof, root, leaf)).to.equal(false); + }); + }); + + describe('multiProofVerify', function () { + it('returns true for a valid Merkle multi proof', async function () { + const leaves = ['a', 'b', 'c', 'd', 'e', 'f'].map(keccak256).sort(Buffer.compare); + const merkleTree = new MerkleTree(leaves, keccak256, { sort: true }); + + const root = merkleTree.getRoot(); + const proofLeaves = ['b', 'f', 'd'].map(keccak256).sort(Buffer.compare); + const proof = merkleTree.getMultiProof(proofLeaves); + const proofFlags = merkleTree.getProofFlags(proofLeaves, proof); + + expect(await this.merkleProof.$multiProofVerify(proof, proofFlags, root, proofLeaves)).to.equal(true); + expect(await this.merkleProof.$multiProofVerifyCalldata(proof, proofFlags, root, proofLeaves)).to.equal(true); + }); + + it('returns false for an invalid Merkle multi proof', async function () { + const leaves = ['a', 'b', 'c', 'd', 'e', 'f'].map(keccak256).sort(Buffer.compare); + const merkleTree = new MerkleTree(leaves, keccak256, { sort: true }); + + const root = merkleTree.getRoot(); + const badProofLeaves = ['g', 'h', 'i'].map(keccak256).sort(Buffer.compare); + const badMerkleTree = new MerkleTree(badProofLeaves); + const badProof = badMerkleTree.getMultiProof(badProofLeaves); + const badProofFlags = badMerkleTree.getProofFlags(badProofLeaves, badProof); + + expect(await this.merkleProof.$multiProofVerify(badProof, badProofFlags, root, badProofLeaves)).to.equal(false); + expect(await this.merkleProof.$multiProofVerifyCalldata(badProof, badProofFlags, root, badProofLeaves)).to.equal( + false, + ); + }); + + it('revert with invalid multi proof #1', async function () { + const fill = Buffer.alloc(32); // This could be anything, we are reconstructing a fake branch + const leaves = ['a', 'b', 'c', 'd'].map(keccak256).sort(Buffer.compare); + const badLeaf = keccak256('e'); + const merkleTree = new MerkleTree(leaves, keccak256, { sort: true }); + + const root = merkleTree.getRoot(); + + await expectRevertCustomError( + this.merkleProof.$multiProofVerify( + [leaves[1], fill, merkleTree.layers[1][1]], + [false, false, false], + root, + [leaves[0], badLeaf], // A, E + ), + 'MerkleProofInvalidMultiproof', + [], + ); + await expectRevertCustomError( + this.merkleProof.$multiProofVerifyCalldata( + [leaves[1], fill, merkleTree.layers[1][1]], + [false, false, false], + root, + [leaves[0], badLeaf], // A, E + ), + 'MerkleProofInvalidMultiproof', + [], + ); + }); + + it('revert with invalid multi proof #2', async function () { + const fill = Buffer.alloc(32); // This could be anything, we are reconstructing a fake branch + const leaves = ['a', 'b', 'c', 'd'].map(keccak256).sort(Buffer.compare); + const badLeaf = keccak256('e'); + const merkleTree = new MerkleTree(leaves, keccak256, { sort: true }); + + const root = merkleTree.getRoot(); + + await expectRevert( + this.merkleProof.$multiProofVerify( + [leaves[1], fill, merkleTree.layers[1][1]], + [false, false, false, false], + root, + [badLeaf, leaves[0]], // A, E + ), + 'reverted with panic code 0x32', + ); + + await expectRevert( + this.merkleProof.$multiProofVerifyCalldata( + [leaves[1], fill, merkleTree.layers[1][1]], + [false, false, false, false], + root, + [badLeaf, leaves[0]], // A, E + ), + 'reverted with panic code 0x32', + ); + }); + + it('limit case: works for tree containing a single leaf', async function () { + const leaves = ['a'].map(keccak256).sort(Buffer.compare); + const merkleTree = new MerkleTree(leaves, keccak256, { sort: true }); + + const root = merkleTree.getRoot(); + const proofLeaves = ['a'].map(keccak256).sort(Buffer.compare); + const proof = merkleTree.getMultiProof(proofLeaves); + const proofFlags = merkleTree.getProofFlags(proofLeaves, proof); + + expect(await this.merkleProof.$multiProofVerify(proof, proofFlags, root, proofLeaves)).to.equal(true); + expect(await this.merkleProof.$multiProofVerifyCalldata(proof, proofFlags, root, proofLeaves)).to.equal(true); + }); + + it('limit case: can prove empty leaves', async function () { + const leaves = ['a', 'b', 'c', 'd'].map(keccak256).sort(Buffer.compare); + const merkleTree = new MerkleTree(leaves, keccak256, { sort: true }); + + const root = merkleTree.getRoot(); + 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', + [], + ); + }); + }); +}); 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), + ); + }); + }); +}); diff --git a/test/utils/cryptography/SignatureChecker.test.js b/test/utils/cryptography/SignatureChecker.test.js new file mode 100644 index 000000000..ba8b100d1 --- /dev/null +++ b/test/utils/cryptography/SignatureChecker.test.js @@ -0,0 +1,87 @@ +const { toEthSignedMessageHash } = require('../../helpers/sign'); + +const { expect } = require('chai'); + +const SignatureChecker = artifacts.require('$SignatureChecker'); +const ERC1271WalletMock = artifacts.require('ERC1271WalletMock'); +const ERC1271MaliciousMock = artifacts.require('ERC1271MaliciousMock'); + +const TEST_MESSAGE = web3.utils.sha3('OpenZeppelin'); +const WRONG_MESSAGE = web3.utils.sha3('Nope'); + +contract('SignatureChecker (ERC1271)', function (accounts) { + const [signer, other] = accounts; + + before('deploying', async function () { + this.signaturechecker = await SignatureChecker.new(); + this.wallet = await ERC1271WalletMock.new(signer); + this.malicious = await ERC1271MaliciousMock.new(); + this.signature = await web3.eth.sign(TEST_MESSAGE, signer); + }); + + context('EOA account', function () { + it('with matching signer and signature', async function () { + expect( + await this.signaturechecker.$isValidSignatureNow(signer, toEthSignedMessageHash(TEST_MESSAGE), this.signature), + ).to.equal(true); + }); + + it('with invalid signer', async function () { + expect( + await this.signaturechecker.$isValidSignatureNow(other, toEthSignedMessageHash(TEST_MESSAGE), this.signature), + ).to.equal(false); + }); + + it('with invalid signature', async function () { + expect( + await this.signaturechecker.$isValidSignatureNow(signer, toEthSignedMessageHash(WRONG_MESSAGE), this.signature), + ).to.equal(false); + }); + }); + + context('ERC1271 wallet', function () { + for (const signature of ['isValidERC1271SignatureNow', 'isValidSignatureNow']) { + context(signature, function () { + it('with matching signer and signature', async function () { + expect( + await this.signaturechecker[`$${signature}`]( + this.wallet.address, + toEthSignedMessageHash(TEST_MESSAGE), + this.signature, + ), + ).to.equal(true); + }); + + it('with invalid signer', async function () { + expect( + await this.signaturechecker[`$${signature}`]( + this.signaturechecker.address, + toEthSignedMessageHash(TEST_MESSAGE), + this.signature, + ), + ).to.equal(false); + }); + + it('with invalid signature', async function () { + expect( + await this.signaturechecker[`$${signature}`]( + this.wallet.address, + toEthSignedMessageHash(WRONG_MESSAGE), + this.signature, + ), + ).to.equal(false); + }); + + it('with malicious wallet', async function () { + expect( + await this.signaturechecker[`$${signature}`]( + this.malicious.address, + toEthSignedMessageHash(TEST_MESSAGE), + this.signature, + ), + ).to.equal(false); + }); + }); + } + }); +}); diff --git a/test/utils/introspection/ERC165.test.js b/test/utils/introspection/ERC165.test.js new file mode 100644 index 000000000..6d531c16d --- /dev/null +++ b/test/utils/introspection/ERC165.test.js @@ -0,0 +1,11 @@ +const { shouldSupportInterfaces } = require('./SupportsInterface.behavior'); + +const ERC165 = artifacts.require('$ERC165'); + +contract('ERC165', function () { + beforeEach(async function () { + this.mock = await ERC165.new(); + }); + + shouldSupportInterfaces(['ERC165']); +}); diff --git a/test/utils/introspection/ERC165Checker.test.js b/test/utils/introspection/ERC165Checker.test.js new file mode 100644 index 000000000..caa220127 --- /dev/null +++ b/test/utils/introspection/ERC165Checker.test.js @@ -0,0 +1,300 @@ +require('@openzeppelin/test-helpers'); + +const { expect } = require('chai'); + +const ERC165Checker = artifacts.require('$ERC165Checker'); +const ERC165MissingData = artifacts.require('ERC165MissingData'); +const ERC165MaliciousData = artifacts.require('ERC165MaliciousData'); +const ERC165NotSupported = artifacts.require('ERC165NotSupported'); +const ERC165InterfacesSupported = artifacts.require('ERC165InterfacesSupported'); +const ERC165ReturnBombMock = artifacts.require('ERC165ReturnBombMock'); + +const DUMMY_ID = '0xdeadbeef'; +const DUMMY_ID_2 = '0xcafebabe'; +const DUMMY_ID_3 = '0xdecafbad'; +const DUMMY_UNSUPPORTED_ID = '0xbaddcafe'; +const DUMMY_UNSUPPORTED_ID_2 = '0xbaadcafe'; +const DUMMY_ACCOUNT = '0x1111111111111111111111111111111111111111'; + +contract('ERC165Checker', function () { + beforeEach(async function () { + this.mock = await ERC165Checker.new(); + }); + + context('ERC165 missing return data', function () { + beforeEach(async function () { + this.target = await ERC165MissingData.new(); + }); + + it('does not support ERC165', async function () { + const supported = await this.mock.$supportsERC165(this.target.address); + expect(supported).to.equal(false); + }); + + it('does not support mock interface via supportsInterface', async function () { + const supported = await this.mock.$supportsInterface(this.target.address, DUMMY_ID); + expect(supported).to.equal(false); + }); + + it('does not support mock interface via supportsAllInterfaces', async function () { + const supported = await this.mock.$supportsAllInterfaces(this.target.address, [DUMMY_ID]); + expect(supported).to.equal(false); + }); + + it('does not support mock interface via getSupportedInterfaces', async function () { + const supported = await this.mock.$getSupportedInterfaces(this.target.address, [DUMMY_ID]); + expect(supported.length).to.equal(1); + expect(supported[0]).to.equal(false); + }); + + it('does not support mock interface via supportsERC165InterfaceUnchecked', async function () { + const supported = await this.mock.$supportsERC165InterfaceUnchecked(this.target.address, DUMMY_ID); + expect(supported).to.equal(false); + }); + }); + + context('ERC165 malicious return data', function () { + beforeEach(async function () { + this.target = await ERC165MaliciousData.new(); + }); + + it('does not support ERC165', async function () { + const supported = await this.mock.$supportsERC165(this.target.address); + expect(supported).to.equal(false); + }); + + it('does not support mock interface via supportsInterface', async function () { + const supported = await this.mock.$supportsInterface(this.target.address, DUMMY_ID); + expect(supported).to.equal(false); + }); + + it('does not support mock interface via supportsAllInterfaces', async function () { + const supported = await this.mock.$supportsAllInterfaces(this.target.address, [DUMMY_ID]); + expect(supported).to.equal(false); + }); + + it('does not support mock interface via getSupportedInterfaces', async function () { + const supported = await this.mock.$getSupportedInterfaces(this.target.address, [DUMMY_ID]); + expect(supported.length).to.equal(1); + expect(supported[0]).to.equal(false); + }); + + it('does not support mock interface via supportsERC165InterfaceUnchecked', async function () { + const supported = await this.mock.$supportsERC165InterfaceUnchecked(this.target.address, DUMMY_ID); + expect(supported).to.equal(true); + }); + }); + + context('ERC165 not supported', function () { + beforeEach(async function () { + this.target = await ERC165NotSupported.new(); + }); + + it('does not support ERC165', async function () { + const supported = await this.mock.$supportsERC165(this.target.address); + expect(supported).to.equal(false); + }); + + it('does not support mock interface via supportsInterface', async function () { + const supported = await this.mock.$supportsInterface(this.target.address, DUMMY_ID); + expect(supported).to.equal(false); + }); + + it('does not support mock interface via supportsAllInterfaces', async function () { + const supported = await this.mock.$supportsAllInterfaces(this.target.address, [DUMMY_ID]); + expect(supported).to.equal(false); + }); + + it('does not support mock interface via getSupportedInterfaces', async function () { + const supported = await this.mock.$getSupportedInterfaces(this.target.address, [DUMMY_ID]); + expect(supported.length).to.equal(1); + expect(supported[0]).to.equal(false); + }); + + it('does not support mock interface via supportsERC165InterfaceUnchecked', async function () { + const supported = await this.mock.$supportsERC165InterfaceUnchecked(this.target.address, DUMMY_ID); + expect(supported).to.equal(false); + }); + }); + + context('ERC165 supported', function () { + beforeEach(async function () { + this.target = await ERC165InterfacesSupported.new([]); + }); + + it('supports ERC165', async function () { + const supported = await this.mock.$supportsERC165(this.target.address); + expect(supported).to.equal(true); + }); + + it('does not support mock interface via supportsInterface', async function () { + const supported = await this.mock.$supportsInterface(this.target.address, DUMMY_ID); + expect(supported).to.equal(false); + }); + + it('does not support mock interface via supportsAllInterfaces', async function () { + const supported = await this.mock.$supportsAllInterfaces(this.target.address, [DUMMY_ID]); + expect(supported).to.equal(false); + }); + + it('does not support mock interface via getSupportedInterfaces', async function () { + const supported = await this.mock.$getSupportedInterfaces(this.target.address, [DUMMY_ID]); + expect(supported.length).to.equal(1); + expect(supported[0]).to.equal(false); + }); + + it('does not support mock interface via supportsERC165InterfaceUnchecked', async function () { + const supported = await this.mock.$supportsERC165InterfaceUnchecked(this.target.address, DUMMY_ID); + expect(supported).to.equal(false); + }); + }); + + context('ERC165 and single interface supported', function () { + beforeEach(async function () { + this.target = await ERC165InterfacesSupported.new([DUMMY_ID]); + }); + + it('supports ERC165', async function () { + const supported = await this.mock.$supportsERC165(this.target.address); + expect(supported).to.equal(true); + }); + + it('supports mock interface via supportsInterface', async function () { + const supported = await this.mock.$supportsInterface(this.target.address, DUMMY_ID); + expect(supported).to.equal(true); + }); + + it('supports mock interface via supportsAllInterfaces', async function () { + const supported = await this.mock.$supportsAllInterfaces(this.target.address, [DUMMY_ID]); + expect(supported).to.equal(true); + }); + + it('supports mock interface via getSupportedInterfaces', async function () { + const supported = await this.mock.$getSupportedInterfaces(this.target.address, [DUMMY_ID]); + expect(supported.length).to.equal(1); + expect(supported[0]).to.equal(true); + }); + + it('supports mock interface via supportsERC165InterfaceUnchecked', async function () { + const supported = await this.mock.$supportsERC165InterfaceUnchecked(this.target.address, DUMMY_ID); + expect(supported).to.equal(true); + }); + }); + + context('ERC165 and many interfaces supported', function () { + beforeEach(async function () { + this.supportedInterfaces = [DUMMY_ID, DUMMY_ID_2, DUMMY_ID_3]; + this.target = await ERC165InterfacesSupported.new(this.supportedInterfaces); + }); + + it('supports ERC165', async function () { + const supported = await this.mock.$supportsERC165(this.target.address); + expect(supported).to.equal(true); + }); + + it('supports each interfaceId via supportsInterface', async function () { + for (const interfaceId of this.supportedInterfaces) { + const supported = await this.mock.$supportsInterface(this.target.address, interfaceId); + expect(supported).to.equal(true); + } + }); + + it('supports all interfaceIds via supportsAllInterfaces', async function () { + const supported = await this.mock.$supportsAllInterfaces(this.target.address, this.supportedInterfaces); + expect(supported).to.equal(true); + }); + + it('supports none of the interfaces queried via supportsAllInterfaces', async function () { + const interfaceIdsToTest = [DUMMY_UNSUPPORTED_ID, DUMMY_UNSUPPORTED_ID_2]; + + const supported = await this.mock.$supportsAllInterfaces(this.target.address, interfaceIdsToTest); + expect(supported).to.equal(false); + }); + + it('supports not all of the interfaces queried via supportsAllInterfaces', async function () { + const interfaceIdsToTest = [...this.supportedInterfaces, DUMMY_UNSUPPORTED_ID]; + + const supported = await this.mock.$supportsAllInterfaces(this.target.address, interfaceIdsToTest); + expect(supported).to.equal(false); + }); + + it('supports all interfaceIds via getSupportedInterfaces', async function () { + const supported = await this.mock.$getSupportedInterfaces(this.target.address, this.supportedInterfaces); + expect(supported.length).to.equal(3); + expect(supported[0]).to.equal(true); + expect(supported[1]).to.equal(true); + expect(supported[2]).to.equal(true); + }); + + it('supports none of the interfaces queried via getSupportedInterfaces', async function () { + const interfaceIdsToTest = [DUMMY_UNSUPPORTED_ID, DUMMY_UNSUPPORTED_ID_2]; + + const supported = await this.mock.$getSupportedInterfaces(this.target.address, interfaceIdsToTest); + expect(supported.length).to.equal(2); + expect(supported[0]).to.equal(false); + expect(supported[1]).to.equal(false); + }); + + it('supports not all of the interfaces queried via getSupportedInterfaces', async function () { + const interfaceIdsToTest = [...this.supportedInterfaces, DUMMY_UNSUPPORTED_ID]; + + const supported = await this.mock.$getSupportedInterfaces(this.target.address, interfaceIdsToTest); + expect(supported.length).to.equal(4); + expect(supported[0]).to.equal(true); + expect(supported[1]).to.equal(true); + expect(supported[2]).to.equal(true); + expect(supported[3]).to.equal(false); + }); + + it('supports each interfaceId via supportsERC165InterfaceUnchecked', async function () { + for (const interfaceId of this.supportedInterfaces) { + const supported = await this.mock.$supportsERC165InterfaceUnchecked(this.target.address, interfaceId); + expect(supported).to.equal(true); + } + }); + }); + + context('account address does not support ERC165', function () { + it('does not support ERC165', async function () { + const supported = await this.mock.$supportsERC165(DUMMY_ACCOUNT); + expect(supported).to.equal(false); + }); + + it('does not support mock interface via supportsInterface', async function () { + const supported = await this.mock.$supportsInterface(DUMMY_ACCOUNT, DUMMY_ID); + expect(supported).to.equal(false); + }); + + it('does not support mock interface via supportsAllInterfaces', async function () { + const supported = await this.mock.$supportsAllInterfaces(DUMMY_ACCOUNT, [DUMMY_ID]); + expect(supported).to.equal(false); + }); + + it('does not support mock interface via getSupportedInterfaces', async function () { + const supported = await this.mock.$getSupportedInterfaces(DUMMY_ACCOUNT, [DUMMY_ID]); + expect(supported.length).to.equal(1); + expect(supported[0]).to.equal(false); + }); + + it('does not support mock interface via supportsERC165InterfaceUnchecked', async function () { + const supported = await this.mock.$supportsERC165InterfaceUnchecked(DUMMY_ACCOUNT, DUMMY_ID); + expect(supported).to.equal(false); + }); + }); + + it('Return bomb resistance', async function () { + this.target = await ERC165ReturnBombMock.new(); + + const tx1 = await this.mock.$supportsInterface.sendTransaction(this.target.address, DUMMY_ID); + expect(tx1.receipt.gasUsed).to.be.lessThan(120000); // 3*30k + 21k + some margin + + const tx2 = await this.mock.$getSupportedInterfaces.sendTransaction(this.target.address, [ + DUMMY_ID, + DUMMY_ID_2, + DUMMY_ID_3, + DUMMY_UNSUPPORTED_ID, + DUMMY_UNSUPPORTED_ID_2, + ]); + expect(tx2.receipt.gasUsed).to.be.lessThan(250000); // (2+5)*30k + 21k + some margin + }); +}); diff --git a/test/utils/introspection/SupportsInterface.behavior.js b/test/utils/introspection/SupportsInterface.behavior.js new file mode 100644 index 000000000..49a30e755 --- /dev/null +++ b/test/utils/introspection/SupportsInterface.behavior.js @@ -0,0 +1,144 @@ +const { makeInterfaceId } = require('@openzeppelin/test-helpers'); + +const { expect } = require('chai'); + +const INVALID_ID = '0xffffffff'; +const INTERFACES = { + ERC165: ['supportsInterface(bytes4)'], + ERC721: [ + 'balanceOf(address)', + 'ownerOf(uint256)', + 'approve(address,uint256)', + 'getApproved(uint256)', + 'setApprovalForAll(address,bool)', + 'isApprovedForAll(address,address)', + 'transferFrom(address,address,uint256)', + 'safeTransferFrom(address,address,uint256)', + 'safeTransferFrom(address,address,uint256,bytes)', + ], + ERC721Enumerable: ['totalSupply()', 'tokenOfOwnerByIndex(address,uint256)', 'tokenByIndex(uint256)'], + ERC721Metadata: ['name()', 'symbol()', 'tokenURI(uint256)'], + ERC1155: [ + 'balanceOf(address,uint256)', + 'balanceOfBatch(address[],uint256[])', + 'setApprovalForAll(address,bool)', + 'isApprovedForAll(address,address)', + 'safeTransferFrom(address,address,uint256,uint256,bytes)', + 'safeBatchTransferFrom(address,address,uint256[],uint256[],bytes)', + ], + ERC1155Receiver: [ + 'onERC1155Received(address,address,uint256,uint256,bytes)', + 'onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)', + ], + AccessControl: [ + 'hasRole(bytes32,address)', + 'getRoleAdmin(bytes32)', + 'grantRole(bytes32,address)', + 'revokeRole(bytes32,address)', + 'renounceRole(bytes32,address)', + ], + AccessControlEnumerable: ['getRoleMember(bytes32,uint256)', 'getRoleMemberCount(bytes32)'], + AccessControlDefaultAdminRules: [ + 'defaultAdminDelay()', + 'pendingDefaultAdminDelay()', + 'defaultAdmin()', + 'pendingDefaultAdmin()', + 'defaultAdminDelayIncreaseWait()', + 'changeDefaultAdminDelay(uint48)', + 'rollbackDefaultAdminDelay()', + 'beginDefaultAdminTransfer(address)', + 'acceptDefaultAdminTransfer()', + 'cancelDefaultAdminTransfer()', + ], + Governor: [ + 'name()', + 'version()', + 'COUNTING_MODE()', + 'hashProposal(address[],uint256[],bytes[],bytes32)', + 'state(uint256)', + 'proposalThreshold()', + 'proposalSnapshot(uint256)', + 'proposalDeadline(uint256)', + 'proposalProposer(uint256)', + 'proposalEta(uint256)', + 'proposalNeedsQueuing(uint256)', + 'votingDelay()', + 'votingPeriod()', + 'quorum(uint256)', + 'getVotes(address,uint256)', + '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)', + ], + ERC2981: ['royaltyInfo(uint256,uint256)'], +}; + +const INTERFACE_IDS = {}; +const FN_SIGNATURES = {}; +for (const k of Object.getOwnPropertyNames(INTERFACES)) { + INTERFACE_IDS[k] = makeInterfaceId.ERC165(INTERFACES[k]); + for (const fnName of INTERFACES[k]) { + // the interface id of a single function is equivalent to its function signature + FN_SIGNATURES[fnName] = makeInterfaceId.ERC165([fnName]); + } +} + +function shouldSupportInterfaces(interfaces = []) { + describe('ERC165', function () { + beforeEach(function () { + this.contractUnderTest = this.mock || this.token || this.holder || this.accessControl; + }); + + 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}`); + } + }); + }); + + 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 () { + for (const k of interfaces) { + // skip interfaces for which we don't have a function list + 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, + `did not find ${fnName}`, + ); + } + } + }); + }); +} + +module.exports = { + shouldSupportInterfaces, +}; diff --git a/test/utils/math/Math.t.sol b/test/utils/math/Math.t.sol new file mode 100644 index 000000000..0b497a858 --- /dev/null +++ b/test/utils/math/Math.t.sol @@ -0,0 +1,216 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {Test} from "forge-std/Test.sol"; + +import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; + +contract MathTest is Test { + // CEILDIV + function testCeilDiv(uint256 a, uint256 b) public { + vm.assume(b > 0); + + uint256 result = Math.ceilDiv(a, b); + + if (result == 0) { + assertEq(a, 0); + } else { + uint256 maxdiv = UINT256_MAX / b; + bool overflow = maxdiv * b < a; + assertTrue(a > b * (result - 1)); + assertTrue(overflow ? result == maxdiv + 1 : a <= b * result); + } + } + + // SQRT + function testSqrt(uint256 input, uint8 r) public { + Math.Rounding rounding = _asRounding(r); + + uint256 result = Math.sqrt(input, rounding); + + // square of result is bigger than input + if (_squareBigger(result, input)) { + assertTrue(Math.unsignedRoundsUp(rounding)); + assertTrue(_squareSmaller(result - 1, input)); + } + // square of result is smaller than input + else if (_squareSmaller(result, input)) { + assertFalse(Math.unsignedRoundsUp(rounding)); + assertTrue(_squareBigger(result + 1, input)); + } + // input is perfect square + else { + assertEq(result * result, input); + } + } + + function _squareBigger(uint256 value, uint256 ref) private pure returns (bool) { + (bool noOverflow, uint256 square) = Math.tryMul(value, value); + return !noOverflow || square > ref; + } + + function _squareSmaller(uint256 value, uint256 ref) private pure returns (bool) { + return value * value < ref; + } + + // LOG2 + function testLog2(uint256 input, uint8 r) public { + Math.Rounding rounding = _asRounding(r); + + uint256 result = Math.log2(input, rounding); + + if (input == 0) { + assertEq(result, 0); + } else if (_powerOf2Bigger(result, input)) { + assertTrue(Math.unsignedRoundsUp(rounding)); + assertTrue(_powerOf2Smaller(result - 1, input)); + } else if (_powerOf2Smaller(result, input)) { + assertFalse(Math.unsignedRoundsUp(rounding)); + assertTrue(_powerOf2Bigger(result + 1, input)); + } else { + assertEq(2 ** result, input); + } + } + + function _powerOf2Bigger(uint256 value, uint256 ref) private pure returns (bool) { + return value >= 256 || 2 ** value > ref; // 2**256 overflows uint256 + } + + function _powerOf2Smaller(uint256 value, uint256 ref) private pure returns (bool) { + return 2 ** value < ref; + } + + // LOG10 + function testLog10(uint256 input, uint8 r) public { + Math.Rounding rounding = _asRounding(r); + + uint256 result = Math.log10(input, rounding); + + if (input == 0) { + assertEq(result, 0); + } else if (_powerOf10Bigger(result, input)) { + assertTrue(Math.unsignedRoundsUp(rounding)); + assertTrue(_powerOf10Smaller(result - 1, input)); + } else if (_powerOf10Smaller(result, input)) { + assertFalse(Math.unsignedRoundsUp(rounding)); + assertTrue(_powerOf10Bigger(result + 1, input)); + } else { + assertEq(10 ** result, input); + } + } + + function _powerOf10Bigger(uint256 value, uint256 ref) private pure returns (bool) { + return value >= 78 || 10 ** value > ref; // 10**78 overflows uint256 + } + + function _powerOf10Smaller(uint256 value, uint256 ref) private pure returns (bool) { + return 10 ** value < ref; + } + + // LOG256 + function testLog256(uint256 input, uint8 r) public { + Math.Rounding rounding = _asRounding(r); + + uint256 result = Math.log256(input, rounding); + + if (input == 0) { + assertEq(result, 0); + } else if (_powerOf256Bigger(result, input)) { + assertTrue(Math.unsignedRoundsUp(rounding)); + assertTrue(_powerOf256Smaller(result - 1, input)); + } else if (_powerOf256Smaller(result, input)) { + assertFalse(Math.unsignedRoundsUp(rounding)); + assertTrue(_powerOf256Bigger(result + 1, input)); + } else { + assertEq(256 ** result, input); + } + } + + function _powerOf256Bigger(uint256 value, uint256 ref) private pure returns (bool) { + return value >= 32 || 256 ** value > ref; // 256**32 overflows uint256 + } + + function _powerOf256Smaller(uint256 value, uint256 ref) private pure returns (bool) { + return 256 ** value < ref; + } + + // MULDIV + function testMulDiv(uint256 x, uint256 y, uint256 d) public { + // Full precision for x * y + (uint256 xyHi, uint256 xyLo) = _mulHighLow(x, y); + + // Assume result won't overflow (see {testMulDivDomain}) + // This also checks that `d` is positive + vm.assume(xyHi < d); + + // Perform muldiv + uint256 q = Math.mulDiv(x, y, d); + + // Full precision for q * d + (uint256 qdHi, uint256 qdLo) = _mulHighLow(q, d); + // Add remainder of x * y / d (computed as rem = (x * y % d)) + (uint256 qdRemLo, uint256 c) = _addCarry(qdLo, _mulmod(x, y, d)); + uint256 qdRemHi = qdHi + c; + + // Full precision check that x * y = q * d + rem + assertEq(xyHi, qdRemHi); + assertEq(xyLo, qdRemLo); + } + + function testMulDivDomain(uint256 x, uint256 y, uint256 d) public { + (uint256 xyHi, ) = _mulHighLow(x, y); + + // Violate {testMulDiv} assumption (covers d is 0 and result overflow) + vm.assume(xyHi >= d); + + // we are outside the scope of {testMulDiv}, we expect muldiv to revert + try this.muldiv(x, y, d) returns (uint256) { + fail(); + } catch {} + } + + // External call + function muldiv(uint256 x, uint256 y, uint256 d) external pure returns (uint256) { + return Math.mulDiv(x, y, d); + } + + // Helpers + function _asRounding(uint8 r) private pure returns (Math.Rounding) { + vm.assume(r < uint8(type(Math.Rounding).max)); + return Math.Rounding(r); + } + + function _mulmod(uint256 x, uint256 y, uint256 z) private pure returns (uint256 r) { + assembly { + r := mulmod(x, y, z) + } + } + + function _mulHighLow(uint256 x, uint256 y) private pure returns (uint256 high, uint256 low) { + (uint256 x0, uint256 x1) = (x & type(uint128).max, x >> 128); + (uint256 y0, uint256 y1) = (y & type(uint128).max, y >> 128); + + // Karatsuba algorithm + // https://en.wikipedia.org/wiki/Karatsuba_algorithm + uint256 z2 = x1 * y1; + uint256 z1a = x1 * y0; + uint256 z1b = x0 * y1; + uint256 z0 = x0 * y0; + + uint256 carry = ((z1a & type(uint128).max) + (z1b & type(uint128).max) + (z0 >> 128)) >> 128; + + high = z2 + (z1a >> 128) + (z1b >> 128) + carry; + + unchecked { + low = x * y; + } + } + + function _addCarry(uint256 x, uint256 y) private pure returns (uint256 res, uint256 carry) { + unchecked { + res = x + y; + } + carry = res < x ? 1 : 0; + } +} diff --git a/test/utils/math/Math.test.js b/test/utils/math/Math.test.js new file mode 100644 index 000000000..7d4a58c81 --- /dev/null +++ b/test/utils/math/Math.test.js @@ -0,0 +1,469 @@ +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'); + +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])) { + 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'); + const MAX_UINT256_SUB1 = MAX_UINT256.sub(new BN('1')); + const MAX_UINT256_SUB2 = MAX_UINT256.sub(new BN('2')); + + beforeEach(async 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); + }); + + it('is correctly detected in second argument position', async function () { + expect(await this.math.$max(min, max)).to.be.bignumber.equal(max); + }); + }); + + describe('min', function () { + it('is correctly detected in first argument position', async function () { + expect(await this.math.$min(min, max)).to.be.bignumber.equal(min); + }); + + it('is correctly detected in second argument position', async function () { + expect(await this.math.$min(max, min)).to.be.bignumber.equal(min); + }); + }); + + describe('average', function () { + function bnAverage(a, b) { + return a.add(b).divn(2); + } + + it('is correctly calculated with two odd numbers', async function () { + const a = new BN('57417'); + const b = new BN('95431'); + expect(await this.math.$average(a, b)).to.be.bignumber.equal(bnAverage(a, b)); + }); + + it('is correctly calculated with two even numbers', async function () { + const a = new BN('42304'); + const b = new BN('84346'); + expect(await this.math.$average(a, b)).to.be.bignumber.equal(bnAverage(a, b)); + }); + + it('is correctly calculated with one even and one odd number', async function () { + const a = new BN('57417'); + const b = new BN('84346'); + expect(await this.math.$average(a, b)).to.be.bignumber.equal(bnAverage(a, b)); + }); + + it('is correctly calculated with two max uint256 numbers', async function () { + const a = MAX_UINT256; + expect(await this.math.$average(a, a)).to.be.bignumber.equal(bnAverage(a, a)); + }); + }); + + 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'); + expect(await this.math.$ceilDiv(a, b)).to.be.bignumber.equal('2'); + }); + + it('rounds up on division with remainders', async function () { + const a = new BN('42'); + const b = new BN('13'); + expect(await this.math.$ceilDiv(a, b)).to.be.bignumber.equal('4'); + }); + + it('does not overflow', async function () { + const b = new BN('2'); + const result = new BN('1').shln(255); + expect(await this.math.$ceilDiv(MAX_UINT256, b)).to.be.bignumber.equal(result); + }); + + it('correctly computes max uint256 divided by 1', async function () { + const b = new BN('1'); + expect(await this.math.$ceilDiv(MAX_UINT256, b)).to.be.bignumber.equal(MAX_UINT256); + }); + }); + + describe('muldiv', function () { + it('divide by 0', async function () { + 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.Floor), 'MathOverflowedMulDiv', []); + }); + + describe('does round down', async function () { + it('small values', async function () { + 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 () { + 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 () { + 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 () { + 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 () { + 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 () { + 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 (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 (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 () { + 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 () { + 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 () { + 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 () { + 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'); + } + }); + }); + }); +}); diff --git a/test/utils/math/SafeCast.test.js b/test/utils/math/SafeCast.test.js new file mode 100644 index 000000000..4b8ec5a72 --- /dev/null +++ b/test/utils/math/SafeCast.test.js @@ -0,0 +1,161 @@ +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'); + +contract('SafeCast', async function () { + beforeEach(async function () { + this.safeCast = await SafeCast.new(); + }); + + function testToUint(bits) { + describe(`toUint${bits}`, () => { + const maxValue = new BN('2').pow(new BN(bits)).subn(1); + + it('downcasts 0', async function () { + expect(await this.safeCast[`$toUint${bits}`](0)).to.be.bignumber.equal('0'); + }); + + it('downcasts 1', async function () { + expect(await this.safeCast[`$toUint${bits}`](1)).to.be.bignumber.equal('1'); + }); + + it(`downcasts 2^${bits} - 1 (${maxValue})`, async function () { + expect(await this.safeCast[`$toUint${bits}`](maxValue)).to.be.bignumber.equal(maxValue); + }); + + it(`reverts when downcasting 2^${bits} (${maxValue.addn(1)})`, async function () { + await expectRevertCustomError( + this.safeCast[`$toUint${bits}`](maxValue.addn(1)), + `SafeCastOverflowedUintDowncast`, + [bits, maxValue.addn(1)], + ); + }); + + it(`reverts when downcasting 2^${bits} + 1 (${maxValue.addn(2)})`, async function () { + await expectRevertCustomError( + this.safeCast[`$toUint${bits}`](maxValue.addn(2)), + `SafeCastOverflowedUintDowncast`, + [bits, maxValue.addn(2)], + ); + }); + }); + } + + range(8, 256, 8).forEach(bits => testToUint(bits)); + + describe('toUint256', () => { + const maxInt256 = new BN('2').pow(new BN(255)).subn(1); + const minInt256 = new BN('2').pow(new BN(255)).neg(); + + it('casts 0', async function () { + expect(await this.safeCast.$toUint256(0)).to.be.bignumber.equal('0'); + }); + + it('casts 1', async function () { + expect(await this.safeCast.$toUint256(1)).to.be.bignumber.equal('1'); + }); + + it(`casts INT256_MAX (${maxInt256})`, async function () { + expect(await this.safeCast.$toUint256(maxInt256)).to.be.bignumber.equal(maxInt256); + }); + + it('reverts when casting -1', async function () { + await expectRevertCustomError(this.safeCast.$toUint256(-1), `SafeCastOverflowedIntToUint`, [-1]); + }); + + it(`reverts when casting INT256_MIN (${minInt256})`, async function () { + await expectRevertCustomError(this.safeCast.$toUint256(minInt256), `SafeCastOverflowedIntToUint`, [minInt256]); + }); + }); + + function testToInt(bits) { + describe(`toInt${bits}`, () => { + const minValue = new BN('-2').pow(new BN(bits - 1)); + const maxValue = new BN('2').pow(new BN(bits - 1)).subn(1); + + it('downcasts 0', async function () { + expect(await this.safeCast[`$toInt${bits}`](0)).to.be.bignumber.equal('0'); + }); + + it('downcasts 1', async function () { + expect(await this.safeCast[`$toInt${bits}`](1)).to.be.bignumber.equal('1'); + }); + + it('downcasts -1', async function () { + expect(await this.safeCast[`$toInt${bits}`](-1)).to.be.bignumber.equal('-1'); + }); + + it(`downcasts -2^${bits - 1} (${minValue})`, async function () { + expect(await this.safeCast[`$toInt${bits}`](minValue)).to.be.bignumber.equal(minValue); + }); + + it(`downcasts 2^${bits - 1} - 1 (${maxValue})`, async function () { + expect(await this.safeCast[`$toInt${bits}`](maxValue)).to.be.bignumber.equal(maxValue); + }); + + it(`reverts when downcasting -2^${bits - 1} - 1 (${minValue.subn(1)})`, async function () { + await expectRevertCustomError( + this.safeCast[`$toInt${bits}`](minValue.subn(1)), + `SafeCastOverflowedIntDowncast`, + [bits, minValue.subn(1)], + ); + }); + + it(`reverts when downcasting -2^${bits - 1} - 2 (${minValue.subn(2)})`, async function () { + await expectRevertCustomError( + this.safeCast[`$toInt${bits}`](minValue.subn(2)), + `SafeCastOverflowedIntDowncast`, + [bits, minValue.subn(2)], + ); + }); + + it(`reverts when downcasting 2^${bits - 1} (${maxValue.addn(1)})`, async function () { + await expectRevertCustomError( + this.safeCast[`$toInt${bits}`](maxValue.addn(1)), + `SafeCastOverflowedIntDowncast`, + [bits, maxValue.addn(1)], + ); + }); + + it(`reverts when downcasting 2^${bits - 1} + 1 (${maxValue.addn(2)})`, async function () { + await expectRevertCustomError( + this.safeCast[`$toInt${bits}`](maxValue.addn(2)), + `SafeCastOverflowedIntDowncast`, + [bits, maxValue.addn(2)], + ); + }); + }); + } + + range(8, 256, 8).forEach(bits => testToInt(bits)); + + describe('toInt256', () => { + const maxUint256 = new BN('2').pow(new BN(256)).subn(1); + const maxInt256 = new BN('2').pow(new BN(255)).subn(1); + + it('casts 0', async function () { + expect(await this.safeCast.$toInt256(0)).to.be.bignumber.equal('0'); + }); + + it('casts 1', async function () { + expect(await this.safeCast.$toInt256(1)).to.be.bignumber.equal('1'); + }); + + it(`casts INT256_MAX (${maxInt256})`, async function () { + expect(await this.safeCast.$toInt256(maxInt256)).to.be.bignumber.equal(maxInt256); + }); + + it(`reverts when casting INT256_MAX + 1 (${maxInt256.addn(1)})`, async function () { + await expectRevertCustomError(this.safeCast.$toInt256(maxInt256.addn(1)), 'SafeCastOverflowedUintToInt', [ + maxInt256.addn(1), + ]); + }); + + it(`reverts when casting UINT256_MAX (${maxUint256})`, async function () { + await expectRevertCustomError(this.safeCast.$toInt256(maxUint256), 'SafeCastOverflowedUintToInt', [maxUint256]); + }); + }); +}); diff --git a/test/utils/math/SignedMath.test.js b/test/utils/math/SignedMath.test.js new file mode 100644 index 000000000..c014e22ba --- /dev/null +++ b/test/utils/math/SignedMath.test.js @@ -0,0 +1,95 @@ +const { BN, constants } = require('@openzeppelin/test-helpers'); +const { expect } = require('chai'); +const { MIN_INT256, MAX_INT256 } = constants; + +const SignedMath = artifacts.require('$SignedMath'); + +contract('SignedMath', function () { + const min = new BN('-1234'); + const max = new BN('5678'); + + beforeEach(async function () { + this.math = await SignedMath.new(); + }); + + 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); + }); + + it('is correctly detected in second argument position', async function () { + expect(await this.math.$max(min, max)).to.be.bignumber.equal(max); + }); + }); + + describe('min', function () { + it('is correctly detected in first argument position', async function () { + expect(await this.math.$min(min, max)).to.be.bignumber.equal(min); + }); + + it('is correctly detected in second argument position', async function () { + expect(await this.math.$min(max, min)).to.be.bignumber.equal(min); + }); + }); + + describe('average', function () { + function bnAverage(a, b) { + return a.add(b).divn(2); + } + + it('is correctly calculated with various input', async function () { + const valuesX = [ + new BN('0'), + new BN('3'), + new BN('-3'), + new BN('4'), + new BN('-4'), + new BN('57417'), + new BN('-57417'), + new BN('42304'), + new BN('-42304'), + MIN_INT256, + MAX_INT256, + ]; + + const valuesY = [ + new BN('0'), + new BN('5'), + new BN('-5'), + new BN('2'), + new BN('-2'), + new BN('57417'), + new BN('-57417'), + new BN('42304'), + new BN('-42304'), + MIN_INT256, + MAX_INT256, + ]; + + for (const x of valuesX) { + for (const y of valuesY) { + expect(await this.math.$average(x, y)).to.be.bignumber.equal( + bnAverage(x, y), + `Bad result for average(${x}, ${y})`, + ); + } + } + }); + }); + + describe('abs', function () { + for (const n of [ + MIN_INT256, + MIN_INT256.addn(1), + new BN('-1'), + new BN('0'), + new BN('1'), + MAX_INT256.subn(1), + MAX_INT256, + ]) { + it(`correctly computes the absolute value of ${n}`, async function () { + expect(await this.math.$abs(n)).to.be.bignumber.equal(n.abs()); + }); + } + }); +}); diff --git a/test/utils/structs/BitMap.test.js b/test/utils/structs/BitMap.test.js new file mode 100644 index 000000000..8a1470c5c --- /dev/null +++ b/test/utils/structs/BitMap.test.js @@ -0,0 +1,145 @@ +const { BN } = require('@openzeppelin/test-helpers'); +const { expect } = require('chai'); + +const BitMap = artifacts.require('$BitMaps'); + +contract('BitMap', function () { + const keyA = new BN('7891'); + const keyB = new BN('451'); + const keyC = new BN('9592328'); + + beforeEach(async function () { + this.bitmap = await BitMap.new(); + }); + + it('starts empty', async function () { + expect(await this.bitmap.$get(0, keyA)).to.equal(false); + expect(await this.bitmap.$get(0, keyB)).to.equal(false); + expect(await this.bitmap.$get(0, keyC)).to.equal(false); + }); + + describe('setTo', function () { + it('set a key to true', async function () { + await this.bitmap.$setTo(0, keyA, true); + expect(await this.bitmap.$get(0, keyA)).to.equal(true); + expect(await this.bitmap.$get(0, keyB)).to.equal(false); + expect(await this.bitmap.$get(0, keyC)).to.equal(false); + }); + + it('set a key to false', async function () { + await this.bitmap.$setTo(0, keyA, true); + await this.bitmap.$setTo(0, keyA, false); + expect(await this.bitmap.$get(0, keyA)).to.equal(false); + expect(await this.bitmap.$get(0, keyB)).to.equal(false); + expect(await this.bitmap.$get(0, keyC)).to.equal(false); + }); + + it('set several consecutive keys', async function () { + await this.bitmap.$setTo(0, keyA.addn(0), true); + await this.bitmap.$setTo(0, keyA.addn(1), true); + await this.bitmap.$setTo(0, keyA.addn(2), true); + await this.bitmap.$setTo(0, keyA.addn(3), true); + await this.bitmap.$setTo(0, keyA.addn(4), true); + await this.bitmap.$setTo(0, keyA.addn(2), false); + await this.bitmap.$setTo(0, keyA.addn(4), false); + expect(await this.bitmap.$get(0, keyA.addn(0))).to.equal(true); + expect(await this.bitmap.$get(0, keyA.addn(1))).to.equal(true); + expect(await this.bitmap.$get(0, keyA.addn(2))).to.equal(false); + expect(await this.bitmap.$get(0, keyA.addn(3))).to.equal(true); + expect(await this.bitmap.$get(0, keyA.addn(4))).to.equal(false); + }); + }); + + describe('set', function () { + it('adds a key', async function () { + await this.bitmap.$set(0, keyA); + expect(await this.bitmap.$get(0, keyA)).to.equal(true); + expect(await this.bitmap.$get(0, keyB)).to.equal(false); + expect(await this.bitmap.$get(0, keyC)).to.equal(false); + }); + + it('adds several keys', async function () { + await this.bitmap.$set(0, keyA); + await this.bitmap.$set(0, keyB); + expect(await this.bitmap.$get(0, keyA)).to.equal(true); + expect(await this.bitmap.$get(0, keyB)).to.equal(true); + expect(await this.bitmap.$get(0, keyC)).to.equal(false); + }); + + it('adds several consecutive keys', async function () { + await this.bitmap.$set(0, keyA.addn(0)); + await this.bitmap.$set(0, keyA.addn(1)); + await this.bitmap.$set(0, keyA.addn(3)); + expect(await this.bitmap.$get(0, keyA.addn(0))).to.equal(true); + expect(await this.bitmap.$get(0, keyA.addn(1))).to.equal(true); + expect(await this.bitmap.$get(0, keyA.addn(2))).to.equal(false); + expect(await this.bitmap.$get(0, keyA.addn(3))).to.equal(true); + expect(await this.bitmap.$get(0, keyA.addn(4))).to.equal(false); + }); + }); + + describe('unset', function () { + it('removes added keys', async function () { + await this.bitmap.$set(0, keyA); + await this.bitmap.$set(0, keyB); + await this.bitmap.$unset(0, keyA); + expect(await this.bitmap.$get(0, keyA)).to.equal(false); + expect(await this.bitmap.$get(0, keyB)).to.equal(true); + expect(await this.bitmap.$get(0, keyC)).to.equal(false); + }); + + it('removes consecutive added keys', async function () { + await this.bitmap.$set(0, keyA.addn(0)); + await this.bitmap.$set(0, keyA.addn(1)); + await this.bitmap.$set(0, keyA.addn(3)); + await this.bitmap.$unset(0, keyA.addn(1)); + expect(await this.bitmap.$get(0, keyA.addn(0))).to.equal(true); + expect(await this.bitmap.$get(0, keyA.addn(1))).to.equal(false); + expect(await this.bitmap.$get(0, keyA.addn(2))).to.equal(false); + expect(await this.bitmap.$get(0, keyA.addn(3))).to.equal(true); + expect(await this.bitmap.$get(0, keyA.addn(4))).to.equal(false); + }); + + it('adds and removes multiple keys', async function () { + // [] + + await this.bitmap.$set(0, keyA); + await this.bitmap.$set(0, keyC); + + // [A, C] + + await this.bitmap.$unset(0, keyA); + await this.bitmap.$unset(0, keyB); + + // [C] + + await this.bitmap.$set(0, keyB); + + // [C, B] + + await this.bitmap.$set(0, keyA); + await this.bitmap.$unset(0, keyC); + + // [A, B] + + await this.bitmap.$set(0, keyA); + await this.bitmap.$set(0, keyB); + + // [A, B] + + await this.bitmap.$set(0, keyC); + await this.bitmap.$unset(0, keyA); + + // [B, C] + + await this.bitmap.$set(0, keyA); + await this.bitmap.$unset(0, keyB); + + // [A, C] + + expect(await this.bitmap.$get(0, keyA)).to.equal(true); + expect(await this.bitmap.$get(0, keyB)).to.equal(false); + expect(await this.bitmap.$get(0, keyC)).to.equal(true); + }); + }); +}); diff --git a/test/utils/structs/Checkpoints.t.sol b/test/utils/structs/Checkpoints.t.sol new file mode 100644 index 000000000..7bdbcfddf --- /dev/null +++ b/test/utils/structs/Checkpoints.t.sol @@ -0,0 +1,332 @@ +// SPDX-License-Identifier: MIT +// This file was procedurally generated from scripts/generate/templates/Checkpoints.t.js. + +pragma solidity ^0.8.20; + +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; + + // 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]; + if (lastKey > 0) { + 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 CheckpointsTrace208Test is Test { + using Checkpoints for Checkpoints.Trace208; + + // 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.Trace208 internal _ckpts; + + // helpers + function _boundUint48(uint48 x, uint48 min, uint48 max) internal view returns (uint48) { + return SafeCast.toUint48(bound(uint256(x), uint256(min), uint256(max))); + } + + function _prepareKeys(uint48[] memory keys, uint48 maxSpread) internal view { + uint48 lastKey = 0; + for (uint256 i = 0; i < keys.length; ++i) { + uint48 key = _boundUint48(keys[i], lastKey, lastKey + maxSpread); + keys[i] = key; + lastKey = key; + } + } + + function _assertLatestCheckpoint(bool exist, uint48 key, uint208 value) internal { + (bool _exist, uint48 _key, uint208 _value) = _ckpts.latestCheckpoint(); + assertEq(_exist, exist); + assertEq(_key, key); + assertEq(_value, value); + } + + // tests + function testPush(uint48[] memory keys, uint208[] memory values, uint48 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) { + uint48 key = keys[i]; + uint208 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) { + uint48 lastKey = keys[keys.length - 1]; + if (lastKey > 0) { + pastKey = _boundUint48(pastKey, 0, lastKey - 1); + + vm.expectRevert(); + this.push(pastKey, values[keys.length % values.length]); + } + } + } + + // used to test reverts + function push(uint48 key, uint208 value) external { + _ckpts.push(key, value); + } + + function testLookup(uint48[] memory keys, uint208[] memory values, uint48 lookup) public { + vm.assume(values.length > 0 && values.length <= keys.length); + _prepareKeys(keys, _KEY_MAX_GAP); + + uint48 lastKey = keys.length == 0 ? 0 : keys[keys.length - 1]; + lookup = _boundUint48(lookup, 0, lastKey + _KEY_MAX_GAP); + + uint208 upper = 0; + uint208 lower = 0; + uint48 lowerKey = type(uint48).max; + for (uint256 i = 0; i < keys.length; ++i) { + uint48 key = keys[i]; + uint208 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]; + if (lastKey > 0) { + 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); + } +} diff --git a/test/utils/structs/Checkpoints.test.js b/test/utils/structs/Checkpoints.test.js new file mode 100644 index 000000000..936ac565a --- /dev/null +++ b/test/utils/structs/Checkpoints.test.js @@ -0,0 +1,158 @@ +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 { expectRevert } = require('@openzeppelin/test-helpers'); + +const $Checkpoints = artifacts.require('$Checkpoints'); + +// The library name may be 'Checkpoints' or 'CheckpointsUpgradeable' +const libraryName = $Checkpoints._json.contractName.replace(/^\$/, ''); + +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(); + }); + + for (const length of VALUE_SIZES) { + 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), + 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('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'); + + 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.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'); + }); + }); + + describe('with checkpoints', function () { + beforeEach('pushing checkpoints', async function () { + this.checkpoints = [ + { key: '2', value: '17' }, + { key: '3', value: '42' }, + { key: '5', value: '101' }, + { key: '7', value: '23' }, + { key: '11', value: '99' }, + ]; + for (const { key, value } of this.checkpoints) { + await this.methods.push(key, value); + } + }); + + 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()); + }); + + it('returns latest value', async function () { + expect(await this.methods.latest()).to.be.bignumber.equal(last(this.checkpoints).value); + + 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 expectRevertCustomError( + this.methods.push(last(this.checkpoints).key - 1, '0'), + 'CheckpointUnorderedInsertion', + [], + ); + }); + + it('can update last value', async function () { + const newValue = '42'; + + // check length before the update + expect(await this.methods.length()).to.be.bignumber.equal(this.checkpoints.length.toString()); + + // update last key + 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.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.lowerLookup(i)).to.be.bignumber.equal(value); + } + }); + + it('upper lookup & upperLookupRecent', async function () { + for (let i = 0; i < 14; ++i) { + const value = last(this.checkpoints.filter(x => i >= x.key))?.value || '0'; + + expect(await this.methods.upperLookup(i)).to.be.bignumber.equal(value); + expect(await this.methods.upperLookupRecent(i)).to.be.bignumber.equal(value); + } + }); + + it('upperLookupRecent with more than 5 checkpoints', async function () { + const moreCheckpoints = [ + { key: '12', value: '22' }, + { key: '13', value: '131' }, + { key: '17', value: '45' }, + { key: '19', value: '31452' }, + { key: '21', value: '0' }, + ]; + const allCheckpoints = [].concat(this.checkpoints, moreCheckpoints); + + for (const { key, value } of moreCheckpoints) { + 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.upperLookup(i)).to.be.bignumber.equal(value); + expect(await this.methods.upperLookupRecent(i)).to.be.bignumber.equal(value); + } + }); + }); + }); + } +}); diff --git a/test/utils/structs/DoubleEndedQueue.test.js b/test/utils/structs/DoubleEndedQueue.test.js new file mode 100644 index 000000000..cbf37d76b --- /dev/null +++ b/test/utils/structs/DoubleEndedQueue.test.js @@ -0,0 +1,99 @@ +const { expectEvent } = require('@openzeppelin/test-helpers'); +const { expectRevertCustomError } = require('../../helpers/customError'); + +const DoubleEndedQueue = artifacts.require('$DoubleEndedQueue'); + +/** Rebuild the content of the deque as a JS array. */ +const getContent = deque => + deque.$length(0).then(bn => + Promise.all( + Array(bn.toNumber()) + .fill() + .map((_, i) => deque.$at(0, i)), + ), + ); + +contract('DoubleEndedQueue', function () { + const bytesA = '0xdeadbeef'.padEnd(66, '0'); + const bytesB = '0x0123456789'.padEnd(66, '0'); + const bytesC = '0x42424242'.padEnd(66, '0'); + const bytesD = '0x171717'.padEnd(66, '0'); + + beforeEach(async function () { + this.deque = await DoubleEndedQueue.new(); + }); + + describe('when empty', function () { + it('getters', async function () { + expect(await this.deque.$empty(0)).to.be.equal(true); + expect(await getContent(this.deque)).to.have.ordered.members([]); + }); + + it('reverts on accesses', async function () { + 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', []); + }); + }); + + describe('when not empty', function () { + beforeEach(async function () { + await this.deque.$pushBack(0, bytesB); + await this.deque.$pushFront(0, bytesA); + await this.deque.$pushBack(0, bytesC); + this.content = [bytesA, bytesB, bytesC]; + }); + + it('getters', async function () { + expect(await this.deque.$empty(0)).to.be.equal(false); + expect(await this.deque.$length(0)).to.be.bignumber.equal(this.content.length.toString()); + expect(await this.deque.$front(0)).to.be.equal(this.content[0]); + expect(await this.deque.$back(0)).to.be.equal(this.content[this.content.length - 1]); + expect(await getContent(this.deque)).to.have.ordered.members(this.content); + }); + + it('out of bounds access', async function () { + await expectRevertCustomError(this.deque.$at(0, this.content.length), 'QueueOutOfBounds', []); + }); + + describe('push', function () { + it('front', async function () { + await this.deque.$pushFront(0, bytesD); + this.content.unshift(bytesD); // add element at the beginning + + expect(await getContent(this.deque)).to.have.ordered.members(this.content); + }); + + it('back', async function () { + await this.deque.$pushBack(0, bytesD); + this.content.push(bytesD); // add element at the end + + expect(await getContent(this.deque)).to.have.ordered.members(this.content); + }); + }); + + describe('pop', function () { + it('front', async function () { + const value = this.content.shift(); // remove first element + expectEvent(await this.deque.$popFront(0), 'return$popFront', { value }); + + expect(await getContent(this.deque)).to.have.ordered.members(this.content); + }); + + it('back', async function () { + const value = this.content.pop(); // remove last element + expectEvent(await this.deque.$popBack(0), 'return$popBack', { value }); + + expect(await getContent(this.deque)).to.have.ordered.members(this.content); + }); + }); + + it('clear', async function () { + await this.deque.$clear(0); + + expect(await this.deque.$empty(0)).to.be.equal(true); + expect(await getContent(this.deque)).to.have.ordered.members([]); + }); + }); +}); diff --git a/test/utils/structs/EnumerableMap.behavior.js b/test/utils/structs/EnumerableMap.behavior.js new file mode 100644 index 000000000..67b19e39a --- /dev/null +++ b/test/utils/structs/EnumerableMap.behavior.js @@ -0,0 +1,178 @@ +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; + const [valueA, valueB, valueC] = values; + + async function expectMembersMatch(map, keys, values) { + expect(keys.length).to.equal(values.length); + + await Promise.all(keys.map(async key => expect(await methods.contains(map, key)).to.equal(true))); + + expect(await methods.length(map)).to.bignumber.equal(keys.length.toString()); + + expect((await Promise.all(keys.map(key => methods.get(map, key)))).map(k => k.toString())).to.have.same.members( + values.map(value => value.toString()), + ); + + // To compare key-value pairs, we zip keys and values, and convert BNs to + // strings to workaround Chai limitations when dealing with nested arrays + expect( + await Promise.all( + [...Array(keys.length).keys()].map(async index => { + const entry = await methods.at(map, index); + return [entry[0].toString(), entry[1].toString()]; + }), + ), + ).to.have.same.deep.members( + zip( + keys.map(k => k.toString()), + values.map(v => v.toString()), + ), + ); + + // This also checks that both arrays have the same length + expect((await methods.keys(map)).map(k => k.toString())).to.have.same.members(keys.map(key => key.toString())); + } + + it('starts empty', async function () { + expect(await methods.contains(this.map, keyA)).to.equal(false); + + await expectMembersMatch(this.map, [], []); + }); + + describe('set', function () { + it('adds a key', async function () { + const receipt = await methods.set(this.map, keyA, valueA); + expectEvent(receipt, events.setReturn, { ret0: true }); + + await expectMembersMatch(this.map, [keyA], [valueA]); + }); + + it('adds several keys', async function () { + await methods.set(this.map, keyA, valueA); + await methods.set(this.map, keyB, valueB); + + await expectMembersMatch(this.map, [keyA, keyB], [valueA, valueB]); + expect(await methods.contains(this.map, keyC)).to.equal(false); + }); + + it('returns false when adding keys already in the set', async function () { + await methods.set(this.map, keyA, valueA); + + const receipt = await methods.set(this.map, keyA, valueA); + expectEvent(receipt, events.setReturn, { ret0: false }); + + await expectMembersMatch(this.map, [keyA], [valueA]); + }); + + it('updates values for keys already in the set', async function () { + await methods.set(this.map, keyA, valueA); + await methods.set(this.map, keyA, valueB); + + await expectMembersMatch(this.map, [keyA], [valueB]); + }); + }); + + describe('remove', function () { + it('removes added keys', async function () { + await methods.set(this.map, keyA, valueA); + + const receipt = await methods.remove(this.map, keyA); + expectEvent(receipt, events.removeReturn, { ret0: true }); + + expect(await methods.contains(this.map, keyA)).to.equal(false); + await expectMembersMatch(this.map, [], []); + }); + + it('returns false when removing keys not in the set', async function () { + const receipt = await methods.remove(this.map, keyA); + expectEvent(receipt, events.removeReturn, { ret0: false }); + + expect(await methods.contains(this.map, keyA)).to.equal(false); + }); + + it('adds and removes multiple keys', async function () { + // [] + + await methods.set(this.map, keyA, valueA); + await methods.set(this.map, keyC, valueC); + + // [A, C] + + await methods.remove(this.map, keyA); + await methods.remove(this.map, keyB); + + // [C] + + await methods.set(this.map, keyB, valueB); + + // [C, B] + + await methods.set(this.map, keyA, valueA); + await methods.remove(this.map, keyC); + + // [A, B] + + await methods.set(this.map, keyA, valueA); + await methods.set(this.map, keyB, valueB); + + // [A, B] + + await methods.set(this.map, keyC, valueC); + await methods.remove(this.map, keyA); + + // [B, C] + + await methods.set(this.map, keyA, valueA); + await methods.remove(this.map, keyB); + + // [A, C] + + await expectMembersMatch(this.map, [keyA, keyC], [valueA, valueC]); + + expect(await methods.contains(this.map, keyA)).to.equal(true); + expect(await methods.contains(this.map, keyB)).to.equal(false); + expect(await methods.contains(this.map, keyC)).to.equal(true); + }); + }); + + describe('read', function () { + beforeEach(async function () { + await methods.set(this.map, keyA, valueA); + }); + + describe('get', function () { + it('existing value', async function () { + expect(await methods.get(this.map, keyA).then(r => r.toString())).to.be.equal(valueA.toString()); + }); + it('missing value', async function () { + const key = web3.utils.toHex(keyB); + await expectRevertCustomError(methods.get(this.map, keyB), 'EnumerableMapNonexistentKey', [ + key.length == 66 ? key : web3.utils.padLeft(key, 64, '0'), + ]); + }); + }); + + describe('tryGet', function () { + it('existing value', async function () { + const result = await methods.tryGet(this.map, keyA); + expect(result['0']).to.be.equal(true); + expect(result['1'].toString()).to.be.equal(valueA.toString()); + }); + it('missing value', async function () { + const result = await methods.tryGet(this.map, keyB); + expect(result['0']).to.be.equal(false); + expect(result['1'].toString()).to.be.equal(zeroValue.toString()); + }); + }); + }); +} + +module.exports = { + shouldBehaveLikeMap, +}; diff --git a/test/utils/structs/EnumerableMap.test.js b/test/utils/structs/EnumerableMap.test.js new file mode 100644 index 000000000..545e12a4f --- /dev/null +++ b/test/utils/structs/EnumerableMap.test.js @@ -0,0 +1,149 @@ +const { BN, constants } = require('@openzeppelin/test-helpers'); +const { mapValues } = require('../../helpers/iterate'); + +const EnumerableMap = artifacts.require('$EnumerableMap'); + +const { shouldBehaveLikeMap } = require('./EnumerableMap.behavior'); + +const getMethods = ms => { + return mapValues( + ms, + m => + (self, ...args) => + self.methods[m](0, ...args), + ); +}; + +// 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; + + const keyA = new BN('7891'); + const keyB = new BN('451'); + const keyC = new BN('9592328'); + + const bytesA = '0xdeadbeef'.padEnd(66, '0'); + const bytesB = '0x0123456789'.padEnd(66, '0'); + const bytesC = '0x42424242'.padEnd(66, '0'); + + beforeEach(async function () { + this.map = await EnumerableMap.new(); + }); + + // AddressToUintMap + describe('AddressToUintMap', function () { + shouldBehaveLikeMap( + [accountA, accountB, accountC], + [keyA, keyB, keyC], + new BN('0'), + getMethods({ + set: '$set(uint256,address,uint256)', + get: '$get(uint256,address)', + tryGet: '$tryGet(uint256,address)', + remove: '$remove(uint256,address)', + length: `$length_${library}_AddressToUintMap(uint256)`, + at: `$at_${library}_AddressToUintMap(uint256,uint256)`, + contains: '$contains(uint256,address)', + keys: `$keys_${library}_AddressToUintMap(uint256)`, + }), + { + setReturn: `return$set_${library}_AddressToUintMap_address_uint256`, + removeReturn: `return$remove_${library}_AddressToUintMap_address`, + }, + ); + }); + + // UintToAddressMap + describe('UintToAddressMap', function () { + shouldBehaveLikeMap( + [keyA, keyB, keyC], + [accountA, accountB, accountC], + constants.ZERO_ADDRESS, + getMethods({ + set: '$set(uint256,uint256,address)', + get: `$get_${library}_UintToAddressMap(uint256,uint256)`, + 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_${library}_UintToAddressMap_uint256_address`, + removeReturn: `return$remove_${library}_UintToAddressMap_uint256`, + }, + ); + }); + + // Bytes32ToBytes32Map + describe('Bytes32ToBytes32Map', function () { + shouldBehaveLikeMap( + [keyA, keyB, keyC].map(k => '0x' + k.toString(16).padEnd(64, '0')), + [bytesA, bytesB, bytesC], + constants.ZERO_BYTES32, + getMethods({ + set: '$set(uint256,bytes32,bytes32)', + get: `$get_${library}_Bytes32ToBytes32Map(uint256,bytes32)`, + 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_${library}_Bytes32ToBytes32Map_bytes32_bytes32`, + removeReturn: `return$remove_${library}_Bytes32ToBytes32Map_bytes32`, + }, + ); + }); + + // UintToUintMap + describe('UintToUintMap', function () { + shouldBehaveLikeMap( + [keyA, keyB, keyC], + [keyA, keyB, keyC].map(k => k.add(new BN('1332'))), + new BN('0'), + getMethods({ + set: '$set(uint256,uint256,uint256)', + get: `$get_${library}_UintToUintMap(uint256,uint256)`, + 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_${library}_UintToUintMap_uint256_uint256`, + removeReturn: `return$remove_${library}_UintToUintMap_uint256`, + }, + ); + }); + + // Bytes32ToUintMap + describe('Bytes32ToUintMap', function () { + shouldBehaveLikeMap( + [bytesA, bytesB, bytesC], + [keyA, keyB, keyC], + new BN('0'), + getMethods({ + set: '$set(uint256,bytes32,uint256)', + get: `$get_${library}_Bytes32ToUintMap(uint256,bytes32)`, + 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_${library}_Bytes32ToUintMap_bytes32_uint256`, + removeReturn: `return$remove_${library}_Bytes32ToUintMap_bytes32`, + }, + ); + }); +}); diff --git a/test/utils/structs/EnumerableSet.behavior.js b/test/utils/structs/EnumerableSet.behavior.js new file mode 100644 index 000000000..f80eb8169 --- /dev/null +++ b/test/utils/structs/EnumerableSet.behavior.js @@ -0,0 +1,129 @@ +const { expectEvent, expectRevert } = require('@openzeppelin/test-helpers'); +const { expect } = require('chai'); + +function shouldBehaveLikeSet(values, methods, events) { + const [valueA, valueB, valueC] = values; + + async function expectMembersMatch(set, values) { + const contains = await Promise.all(values.map(value => methods.contains(set, value))); + expect(contains.every(Boolean)).to.be.equal(true); + + const length = await methods.length(set); + expect(length).to.bignumber.equal(values.length.toString()); + + // To compare values we convert to strings to workaround Chai + // limitations when dealing with nested arrays (required for BNs) + const indexedValues = await Promise.all( + Array(values.length) + .fill() + .map((_, index) => methods.at(set, index)), + ); + expect(indexedValues.map(v => v.toString())).to.have.same.members(values.map(v => v.toString())); + + const returnedValues = await methods.values(set); + expect(returnedValues.map(v => v.toString())).to.have.same.members(values.map(v => v.toString())); + } + + it('starts empty', async function () { + expect(await methods.contains(this.set, valueA)).to.equal(false); + + await expectMembersMatch(this.set, []); + }); + + describe('add', function () { + it('adds a value', async function () { + const receipt = await methods.add(this.set, valueA); + expectEvent(receipt, events.addReturn, { ret0: true }); + + await expectMembersMatch(this.set, [valueA]); + }); + + it('adds several values', async function () { + await methods.add(this.set, valueA); + await methods.add(this.set, valueB); + + await expectMembersMatch(this.set, [valueA, valueB]); + expect(await methods.contains(this.set, valueC)).to.equal(false); + }); + + it('returns false when adding values already in the set', async function () { + await methods.add(this.set, valueA); + + const receipt = await methods.add(this.set, valueA); + expectEvent(receipt, events.addReturn, { ret0: false }); + + await expectMembersMatch(this.set, [valueA]); + }); + }); + + describe('at', function () { + it('reverts when retrieving non-existent elements', async function () { + await expectRevert.unspecified(methods.at(this.set, 0)); + }); + }); + + describe('remove', function () { + it('removes added values', async function () { + await methods.add(this.set, valueA); + + const receipt = await methods.remove(this.set, valueA); + expectEvent(receipt, events.removeReturn, { ret0: true }); + + expect(await methods.contains(this.set, valueA)).to.equal(false); + await expectMembersMatch(this.set, []); + }); + + it('returns false when removing values not in the set', async function () { + const receipt = await methods.remove(this.set, valueA); + expectEvent(receipt, events.removeReturn, { ret0: false }); + + expect(await methods.contains(this.set, valueA)).to.equal(false); + }); + + it('adds and removes multiple values', async function () { + // [] + + await methods.add(this.set, valueA); + await methods.add(this.set, valueC); + + // [A, C] + + await methods.remove(this.set, valueA); + await methods.remove(this.set, valueB); + + // [C] + + await methods.add(this.set, valueB); + + // [C, B] + + await methods.add(this.set, valueA); + await methods.remove(this.set, valueC); + + // [A, B] + + await methods.add(this.set, valueA); + await methods.add(this.set, valueB); + + // [A, B] + + await methods.add(this.set, valueC); + await methods.remove(this.set, valueA); + + // [B, C] + + await methods.add(this.set, valueA); + await methods.remove(this.set, valueB); + + // [A, C] + + await expectMembersMatch(this.set, [valueA, valueC]); + + expect(await methods.contains(this.set, valueB)).to.equal(false); + }); + }); +} + +module.exports = { + shouldBehaveLikeSet, +}; diff --git a/test/utils/structs/EnumerableSet.test.js b/test/utils/structs/EnumerableSet.test.js new file mode 100644 index 000000000..a1840257b --- /dev/null +++ b/test/utils/structs/EnumerableSet.test.js @@ -0,0 +1,79 @@ +const EnumerableSet = artifacts.require('$EnumerableSet'); +const { mapValues } = require('../../helpers/iterate'); + +const { shouldBehaveLikeSet } = require('./EnumerableSet.behavior'); + +const getMethods = ms => { + return mapValues( + ms, + m => + (self, ...args) => + self.methods[m](0, ...args), + ); +}; + +// 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(); + }); + + // Bytes32Set + describe('EnumerableBytes32Set', function () { + shouldBehaveLikeSet( + ['0xdeadbeef', '0x0123456789', '0x42424242'].map(e => e.padEnd(66, '0')), + getMethods({ + add: '$add(uint256,bytes32)', + remove: '$remove(uint256,bytes32)', + contains: '$contains(uint256,bytes32)', + length: `$length_${library}_Bytes32Set(uint256)`, + at: `$at_${library}_Bytes32Set(uint256,uint256)`, + values: `$values_${library}_Bytes32Set(uint256)`, + }), + { + addReturn: `return$add_${library}_Bytes32Set_bytes32`, + removeReturn: `return$remove_${library}_Bytes32Set_bytes32`, + }, + ); + }); + + // AddressSet + describe('EnumerableAddressSet', function () { + shouldBehaveLikeSet( + accounts, + getMethods({ + add: '$add(uint256,address)', + remove: '$remove(uint256,address)', + contains: '$contains(uint256,address)', + length: `$length_${library}_AddressSet(uint256)`, + at: `$at_${library}_AddressSet(uint256,uint256)`, + values: `$values_${library}_AddressSet(uint256)`, + }), + { + addReturn: `return$add_${library}_AddressSet_address`, + removeReturn: `return$remove_${library}_AddressSet_address`, + }, + ); + }); + + // UintSet + describe('EnumerableUintSet', function () { + shouldBehaveLikeSet( + [1234, 5678, 9101112].map(e => web3.utils.toBN(e)), + getMethods({ + add: '$add(uint256,uint256)', + remove: '$remove(uint256,uint256)', + contains: '$contains(uint256,uint256)', + length: `$length_${library}_UintSet(uint256)`, + at: `$at_${library}_UintSet(uint256,uint256)`, + values: `$values_${library}_UintSet(uint256)`, + }), + { + addReturn: `return$add_${library}_UintSet_uint256`, + removeReturn: `return$remove_${library}_UintSet_uint256`, + }, + ); + }); +}); diff --git a/test/utils/types/Time.test.js b/test/utils/types/Time.test.js new file mode 100644 index 000000000..614911738 --- /dev/null +++ b/test/utils/types/Time.test.js @@ -0,0 +1,140 @@ +require('@openzeppelin/test-helpers'); + +const { expect } = require('chai'); +const { clock } = require('../../helpers/time'); +const { product, max } = require('../../helpers/iterate'); + +const Time = artifacts.require('$Time'); + +const MAX_UINT32 = 1n << (32n - 1n); +const MAX_UINT48 = 1n << (48n - 1n); +const SOME_VALUES = [0n, 1n, 2n, 15n, 16n, 17n, 42n]; + +const asUint = (value, size) => { + if (typeof value != 'bigint') { + value = BigInt(value); + } + // chai does not support bigint :/ + if (value < 0 || value >= 1n << BigInt(size)) { + throw new Error(`value is not a valid uint${size}`); + } + return value; +}; + +const unpackDelay = delay => ({ + valueBefore: (asUint(delay, 112) >> 32n) % (1n << 32n), + valueAfter: (asUint(delay, 112) >> 0n) % (1n << 32n), + effect: (asUint(delay, 112) >> 64n) % (1n << 48n), +}); + +const packDelay = ({ valueBefore, valueAfter = 0n, effect = 0n }) => + (asUint(valueAfter, 32) << 0n) + (asUint(valueBefore, 32) << 32n) + (asUint(effect, 48) << 64n); + +const effectSamplesForTimepoint = timepoint => [ + 0n, + timepoint, + ...product([-1n, 1n], [1n, 2n, 17n, 42n]) + .map(([sign, shift]) => timepoint + sign * shift) + .filter(effect => effect > 0n && effect <= MAX_UINT48), + MAX_UINT48, +]; + +contract('Time', function () { + beforeEach(async function () { + this.mock = await Time.new(); + }); + + describe('clocks', function () { + it('timestamp', async function () { + expect(await this.mock.$timestamp()).to.be.bignumber.equal(web3.utils.toBN(await clock.timestamp())); + }); + + it('block number', async function () { + expect(await this.mock.$blockNumber()).to.be.bignumber.equal(web3.utils.toBN(await clock.blocknumber())); + }); + }); + + describe('Delay', function () { + describe('packing and unpacking', function () { + const valueBefore = 17n; + const valueAfter = 42n; + const effect = 69n; + const delay = 1272825341158973505578n; + + it('pack', async function () { + const packed = await this.mock.$pack(valueBefore, valueAfter, effect); + expect(packed).to.be.bignumber.equal(delay.toString()); + + const packed2 = packDelay({ valueBefore, valueAfter, effect }); + expect(packed2).to.be.equal(delay); + }); + + it('unpack', async function () { + const unpacked = await this.mock.$unpack(delay); + expect(unpacked[0]).to.be.bignumber.equal(valueBefore.toString()); + expect(unpacked[1]).to.be.bignumber.equal(valueAfter.toString()); + expect(unpacked[2]).to.be.bignumber.equal(effect.toString()); + + const unpacked2 = unpackDelay(delay); + expect(unpacked2).to.be.deep.equal({ valueBefore, valueAfter, effect }); + }); + }); + + it('toDelay', async function () { + for (const value of [...SOME_VALUES, MAX_UINT32]) { + const delay = await this.mock.$toDelay(value).then(unpackDelay); + expect(delay).to.be.deep.equal({ valueBefore: 0n, valueAfter: value, effect: 0n }); + } + }); + + it('get & getFull', async function () { + const timepoint = await clock.timestamp().then(BigInt); + const valueBefore = 24194n; + const valueAfter = 4214143n; + + for (const effect of effectSamplesForTimepoint(timepoint)) { + const isPast = effect <= timepoint; + + const delay = packDelay({ valueBefore, valueAfter, effect }); + + expect(await this.mock.$get(delay)).to.be.bignumber.equal(String(isPast ? valueAfter : valueBefore)); + + const result = await this.mock.$getFull(delay); + expect(result[0]).to.be.bignumber.equal(String(isPast ? valueAfter : valueBefore)); + expect(result[1]).to.be.bignumber.equal(String(isPast ? 0n : valueAfter)); + expect(result[2]).to.be.bignumber.equal(String(isPast ? 0n : effect)); + } + }); + + it('withUpdate', async function () { + const timepoint = await clock.timestamp().then(BigInt); + const valueBefore = 24194n; + const valueAfter = 4214143n; + const newvalueAfter = 94716n; + + for (const effect of effectSamplesForTimepoint(timepoint)) + for (const minSetback of [...SOME_VALUES, MAX_UINT32]) { + const isPast = effect <= timepoint; + const expectedvalueBefore = isPast ? valueAfter : valueBefore; + const expectedSetback = max(minSetback, expectedvalueBefore - newvalueAfter, 0n); + + const result = await this.mock.$withUpdate( + packDelay({ valueBefore, valueAfter, effect }), + newvalueAfter, + minSetback, + ); + + expect(result[0]).to.be.bignumber.equal( + String( + packDelay({ + valueBefore: expectedvalueBefore, + valueAfter: newvalueAfter, + effect: timepoint + expectedSetback, + }), + ), + ); + expect(result[1]).to.be.bignumber.equal(String(timepoint + expectedSetback)); + } + }); + }); +});