diff --git a/.codecov.yml b/.codecov.yml index 5bee9146a..4cec4ef7d 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -13,3 +13,4 @@ coverage: ignore: - "test" - "contracts/mocks" + - "contracts/vendor" diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index a5418c5e4..000000000 --- a/.eslintrc +++ /dev/null @@ -1,20 +0,0 @@ -{ - "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/workflows/checks.yml b/.github/workflows/checks.yml index c28088563..18a38b3c5 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -41,6 +41,8 @@ jobs: run: npm run test - name: Check linearisation of the inheritance graph run: npm run test:inheritance + - name: Check pragma consistency between files + run: npm run test:pragma - name: Check proceduraly generated contracts are up-to-date run: npm run test:generation - name: Compare gas costs @@ -68,6 +70,8 @@ jobs: run: npm run test - name: Check linearisation of the inheritance graph run: npm run test:inheritance + - name: Check pragma consistency between files + run: npm run test:pragma - name: Check storage layout uses: ./.github/actions/storage-layout continue-on-error: ${{ contains(github.event.pull_request.labels.*.name, 'breaking change') }} @@ -125,8 +129,8 @@ jobs: steps: - uses: actions/checkout@v4 - name: Run CodeSpell - uses: codespell-project/actions-codespell@v2.0 + uses: codespell-project/actions-codespell@v2.1 with: check_hidden: true check_filenames: true - skip: package-lock.json,*.pdf + skip: package-lock.json,*.pdf,vendor diff --git a/.github/workflows/formal-verification.yml b/.github/workflows/formal-verification.yml index 517ec552c..e0475b195 100644 --- a/.github/workflows/formal-verification.yml +++ b/.github/workflows/formal-verification.yml @@ -10,7 +10,7 @@ on: workflow_dispatch: {} env: - PIP_VERSION: '3.10' + PIP_VERSION: '3.11' JAVA_VERSION: '11' SOLC_VERSION: '0.8.20' diff --git a/CHANGELOG.md b/CHANGELOG.md index b652a68fa..3b5eb8448 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,47 @@ # Changelog +## 5.2.0 (2025-01-08) + +### Breaking Changes + +#### Custom error changes + +This version comes with changes to the custom error identifiers. Contracts previously depending on the following errors should be replaced accordingly: + +- Replace `Errors.FailedCall` with a bubbled-up revert reason in `Address.sendValue`. + +### Changes by category + +#### General + +- Update some pragma directives to ensure that all file requirements match that of the files they import. ([#5273](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5273)) + +#### Account + +- `ERC4337Utils`: Add a reusable library to manipulate user operations and interact with ERC-4337 contracts ([#5274](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5274)) +- `ERC7579Utils`: Add a reusable library to interact with ERC-7579 modular accounts ([#5274](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5274)) + +#### Governance + +- `GovernorCountingOverridable`: Add a governor counting module that enables token holders to override the vote of their delegate. ([#5192](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5192)) +- `VotesExtended`: Create an extension of `Votes` which checkpoints balances and delegates. ([#5192](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5192)) + +### Proxy + +- `Clones`: Add `cloneWithImmutableArgs` and `cloneDeterministicWithImmutableArgs` variants that create clones with per-instance immutable arguments. The immutable arguments can be retrieved using `fetchCloneArgs`. The corresponding `predictDeterministicWithImmutableArgs` function is also included. ([#5109](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5109)) + +### Tokens + +- `ERC1363Utils`: Add helper similar to the existing `ERC721Utils` and `ERC1155Utils` ([#5133](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5133)) + +### Utils + +- `Address`: bubble up revert data on `sendValue` failed call ([#5418](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5418)) +- `Bytes`: Add a library of common operation that operate on `bytes` objects. ([#5252](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5252)) +- `CAIP2` and `CAIP10`: Add libraries for formatting and parsing CAIP-2 and CAIP-10 identifiers. ([#5252](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5252)) +- `NoncesKeyed`: Add a variant of `Nonces` that implements the ERC-4337 entrypoint nonce system. ([#5272](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5272)) +- `Packing`: Add variants for packing `bytes10` and `bytes22` ([#5274](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5274)) +- `Strings`: Add `parseUint`, `parseInt`, `parseHexUint` and `parseAddress` to parse strings into numbers and addresses. Also provide variants of these functions that parse substrings, and `tryXxx` variants that do not revert on invalid input. ([#5166](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5166)) ## 5.1.0 (2024-10-17) @@ -52,7 +94,7 @@ This version comes with changes to the custom error identifiers. Contracts previ - `ERC1363`: Add implementation of the token payable standard allowing execution of contract code after transfers and approvals. ([#4631](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4631)) - `ERC20TemporaryApproval`: Add an ERC-20 extension that implements temporary approval using transient storage, based on ERC7674 (draft). ([#5071](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5071)) - `SafeERC20`: Add "relaxed" function for interacting with ERC-1363 functions in a way that is compatible with EOAs. ([#4631](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4631)) -- `SafeERC20`: Document risks of `safeIncreaseAllowance` and `safeDecreaseAllowance` when associated with ERC-7674. +- `SafeERC20`: Document risks of `safeIncreaseAllowance` and `safeDecreaseAllowance` when associated with ERC-7674. ([#5262](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5262)) - `ERC721Utils` and `ERC1155Utils`: Add reusable libraries with functions to perform acceptance checks on `IERC721Receiver` and `IERC1155Receiver` implementers. ([#4845](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4845)) - `ERC1363Utils`: Add helper similar to the existing ERC721Utils and ERC1155Utils. ([#5133](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5133)) diff --git a/GUIDELINES.md b/GUIDELINES.md index 97fa7290c..2c21e956b 100644 --- a/GUIDELINES.md +++ b/GUIDELINES.md @@ -55,7 +55,7 @@ External contributions must be reviewed separately by multiple maintainers. 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). +Automations that make use of sensitive credentials must use secure secret management, and must be strengthened against attacks such as [those on GitHub Actions workflows](https://github.com/nikitastupin/pwnhub). Some other examples of automation are: @@ -131,6 +131,13 @@ In addition to the official Solidity Style Guide we have a number of other conve abstract contract AccessControl is ..., { ``` +* Return values are generally not named, unless they are not immediately clear or there are multiple return values. + + ```solidity + function expiration() public view returns (uint256) { // Good + function hasRole() public view returns (bool isMember, uint32 currentDelay) { // Good + ``` + * Unchecked arithmetic blocks should contain comments explaining why overflow is guaranteed not to happen. If the reason is immediately apparent from the line above the unchecked block, the comment may be omitted. * Custom errors should be declared following the [EIP-6093](https://eips.ethereum.org/EIPS/eip-6093) rationale whenever reasonable. Also, consider the following: diff --git a/audits/2024-10-v5.1.pdf b/audits/2024-10-v5.1.pdf new file mode 100644 index 000000000..4d85dc7ba Binary files /dev/null and b/audits/2024-10-v5.1.pdf differ diff --git a/audits/README.md b/audits/README.md index 369d06402..34bda2af3 100644 --- a/audits/README.md +++ b/audits/README.md @@ -2,6 +2,7 @@ | Date | Version | Commit | Auditor | Scope | Links | | ------------ | ------- | --------- | ------------ | -------------------- | ----------------------------------------------------------- | +| October 2024 | v5.1.0 | TBD | OpenZeppelin | v5.1 Changes | [🔗](./2024-10-v5.1.pdf) | | October 2023 | v5.0.0 | `b5a3e69` | OpenZeppelin | v5.0 Changes | [🔗](./2023-10-v5.0.pdf) | | 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) | diff --git a/contracts/account/README.adoc b/contracts/account/README.adoc new file mode 100644 index 000000000..d2eb9db5e --- /dev/null +++ b/contracts/account/README.adoc @@ -0,0 +1,12 @@ += Account + +[.readme-notice] +NOTE: This document is better viewed at https://docs.openzeppelin.com/contracts/api/account + +This directory includes contracts to build accounts for ERC-4337. + +== Utilities + +{{ERC4337Utils}} + +{{ERC7579Utils}} diff --git a/contracts/account/utils/draft-ERC4337Utils.sol b/contracts/account/utils/draft-ERC4337Utils.sol new file mode 100644 index 000000000..d13d51939 --- /dev/null +++ b/contracts/account/utils/draft-ERC4337Utils.sol @@ -0,0 +1,170 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.2.0) (account/utils/draft-ERC4337Utils.sol) + +pragma solidity ^0.8.20; + +import {PackedUserOperation} from "../../interfaces/draft-IERC4337.sol"; +import {Math} from "../../utils/math/Math.sol"; +import {Packing} from "../../utils/Packing.sol"; + +/** + * @dev Library with common ERC-4337 utility functions. + * + * See https://eips.ethereum.org/EIPS/eip-4337[ERC-4337]. + */ +library ERC4337Utils { + using Packing for *; + + /// @dev For simulation purposes, validateUserOp (and validatePaymasterUserOp) return this value on success. + uint256 internal constant SIG_VALIDATION_SUCCESS = 0; + + /// @dev For simulation purposes, validateUserOp (and validatePaymasterUserOp) must return this value in case of signature failure, instead of revert. + uint256 internal constant SIG_VALIDATION_FAILED = 1; + + /// @dev Parses the validation data into its components. See {packValidationData}. + function parseValidationData( + uint256 validationData + ) internal pure returns (address aggregator, uint48 validAfter, uint48 validUntil) { + validAfter = uint48(bytes32(validationData).extract_32_6(0)); + validUntil = uint48(bytes32(validationData).extract_32_6(6)); + aggregator = address(bytes32(validationData).extract_32_20(12)); + if (validUntil == 0) validUntil = type(uint48).max; + } + + /// @dev Packs the validation data into a single uint256. See {parseValidationData}. + function packValidationData( + address aggregator, + uint48 validAfter, + uint48 validUntil + ) internal pure returns (uint256) { + return uint256(bytes6(validAfter).pack_6_6(bytes6(validUntil)).pack_12_20(bytes20(aggregator))); + } + + /// @dev Same as {packValidationData}, but with a boolean signature success flag. + function packValidationData(bool sigSuccess, uint48 validAfter, uint48 validUntil) internal pure returns (uint256) { + return + packValidationData( + address(uint160(Math.ternary(sigSuccess, SIG_VALIDATION_SUCCESS, SIG_VALIDATION_FAILED))), + validAfter, + validUntil + ); + } + + /** + * @dev Combines two validation data into a single one. + * + * The `aggregator` is set to {SIG_VALIDATION_SUCCESS} if both are successful, while + * the `validAfter` is the maximum and the `validUntil` is the minimum of both. + */ + function combineValidationData(uint256 validationData1, uint256 validationData2) internal pure returns (uint256) { + (address aggregator1, uint48 validAfter1, uint48 validUntil1) = parseValidationData(validationData1); + (address aggregator2, uint48 validAfter2, uint48 validUntil2) = parseValidationData(validationData2); + + bool success = aggregator1 == address(uint160(SIG_VALIDATION_SUCCESS)) && + aggregator2 == address(uint160(SIG_VALIDATION_SUCCESS)); + uint48 validAfter = uint48(Math.max(validAfter1, validAfter2)); + uint48 validUntil = uint48(Math.min(validUntil1, validUntil2)); + return packValidationData(success, validAfter, validUntil); + } + + /// @dev Returns the aggregator of the `validationData` and whether it is out of time range. + function getValidationData(uint256 validationData) internal view returns (address aggregator, bool outOfTimeRange) { + (address aggregator_, uint48 validAfter, uint48 validUntil) = parseValidationData(validationData); + return (aggregator_, block.timestamp < validAfter || validUntil < block.timestamp); + } + + /// @dev Computes the hash of a user operation for a given entrypoint and chainid. + function hash( + PackedUserOperation calldata self, + address entrypoint, + uint256 chainid + ) internal pure returns (bytes32) { + bytes32 result = keccak256( + abi.encode( + keccak256( + abi.encode( + self.sender, + self.nonce, + keccak256(self.initCode), + keccak256(self.callData), + self.accountGasLimits, + self.preVerificationGas, + self.gasFees, + keccak256(self.paymasterAndData) + ) + ), + entrypoint, + chainid + ) + ); + return result; + } + + /// @dev Returns `factory` from the {PackedUserOperation}, or address(0) if the initCode is empty or not properly formatted. + function factory(PackedUserOperation calldata self) internal pure returns (address) { + return self.initCode.length < 20 ? address(0) : address(bytes20(self.initCode[0:20])); + } + + /// @dev Returns `factoryData` from the {PackedUserOperation}, or empty bytes if the initCode is empty or not properly formatted. + function factoryData(PackedUserOperation calldata self) internal pure returns (bytes calldata) { + return self.initCode.length < 20 ? _emptyCalldataBytes() : self.initCode[20:]; + } + + /// @dev Returns `verificationGasLimit` from the {PackedUserOperation}. + function verificationGasLimit(PackedUserOperation calldata self) internal pure returns (uint256) { + return uint128(self.accountGasLimits.extract_32_16(0)); + } + + /// @dev Returns `callGasLimit` from the {PackedUserOperation}. + function callGasLimit(PackedUserOperation calldata self) internal pure returns (uint256) { + return uint128(self.accountGasLimits.extract_32_16(16)); + } + + /// @dev Returns the first section of `gasFees` from the {PackedUserOperation}. + function maxPriorityFeePerGas(PackedUserOperation calldata self) internal pure returns (uint256) { + return uint128(self.gasFees.extract_32_16(0)); + } + + /// @dev Returns the second section of `gasFees` from the {PackedUserOperation}. + function maxFeePerGas(PackedUserOperation calldata self) internal pure returns (uint256) { + return uint128(self.gasFees.extract_32_16(16)); + } + + /// @dev Returns the total gas price for the {PackedUserOperation} (ie. `maxFeePerGas` or `maxPriorityFeePerGas + basefee`). + function gasPrice(PackedUserOperation calldata self) internal view returns (uint256) { + unchecked { + // Following values are "per gas" + uint256 maxPriorityFee = maxPriorityFeePerGas(self); + uint256 maxFee = maxFeePerGas(self); + return Math.min(maxFee, maxPriorityFee + block.basefee); + } + } + + /// @dev Returns the first section of `paymasterAndData` from the {PackedUserOperation}. + function paymaster(PackedUserOperation calldata self) internal pure returns (address) { + return self.paymasterAndData.length < 52 ? address(0) : address(bytes20(self.paymasterAndData[0:20])); + } + + /// @dev Returns the second section of `paymasterAndData` from the {PackedUserOperation}. + function paymasterVerificationGasLimit(PackedUserOperation calldata self) internal pure returns (uint256) { + return self.paymasterAndData.length < 52 ? 0 : uint128(bytes16(self.paymasterAndData[20:36])); + } + + /// @dev Returns the third section of `paymasterAndData` from the {PackedUserOperation}. + function paymasterPostOpGasLimit(PackedUserOperation calldata self) internal pure returns (uint256) { + return self.paymasterAndData.length < 52 ? 0 : uint128(bytes16(self.paymasterAndData[36:52])); + } + + /// @dev Returns the fourth section of `paymasterAndData` from the {PackedUserOperation}. + function paymasterData(PackedUserOperation calldata self) internal pure returns (bytes calldata) { + return self.paymasterAndData.length < 52 ? _emptyCalldataBytes() : self.paymasterAndData[52:]; + } + + // slither-disable-next-line write-after-write + function _emptyCalldataBytes() private pure returns (bytes calldata result) { + assembly ("memory-safe") { + result.offset := 0 + result.length := 0 + } + } +} diff --git a/contracts/account/utils/draft-ERC7579Utils.sol b/contracts/account/utils/draft-ERC7579Utils.sol new file mode 100644 index 000000000..28aa64d5c --- /dev/null +++ b/contracts/account/utils/draft-ERC7579Utils.sol @@ -0,0 +1,278 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.2.0) (account/utils/draft-ERC7579Utils.sol) + +pragma solidity ^0.8.20; + +import {Execution} from "../../interfaces/draft-IERC7579.sol"; +import {Packing} from "../../utils/Packing.sol"; +import {Address} from "../../utils/Address.sol"; + +type Mode is bytes32; +type CallType is bytes1; +type ExecType is bytes1; +type ModeSelector is bytes4; +type ModePayload is bytes22; + +/** + * @dev Library with common ERC-7579 utility functions. + * + * See https://eips.ethereum.org/EIPS/eip-7579[ERC-7579]. + */ +// slither-disable-next-line unused-state +library ERC7579Utils { + using Packing for *; + + /// @dev A single `call` execution. + CallType internal constant CALLTYPE_SINGLE = CallType.wrap(0x00); + + /// @dev A batch of `call` executions. + CallType internal constant CALLTYPE_BATCH = CallType.wrap(0x01); + + /// @dev A `delegatecall` execution. + CallType internal constant CALLTYPE_DELEGATECALL = CallType.wrap(0xFF); + + /// @dev Default execution type that reverts on failure. + ExecType internal constant EXECTYPE_DEFAULT = ExecType.wrap(0x00); + + /// @dev Execution type that does not revert on failure. + ExecType internal constant EXECTYPE_TRY = ExecType.wrap(0x01); + + /** + * @dev Emits when an {EXECTYPE_TRY} execution fails. + * @param batchExecutionIndex The index of the failed call in the execution batch. + * @param returndata The returned data from the failed call. + */ + event ERC7579TryExecuteFail(uint256 batchExecutionIndex, bytes returndata); + + /// @dev The provided {CallType} is not supported. + error ERC7579UnsupportedCallType(CallType callType); + + /// @dev The provided {ExecType} is not supported. + error ERC7579UnsupportedExecType(ExecType execType); + + /// @dev The provided module doesn't match the provided module type. + error ERC7579MismatchedModuleTypeId(uint256 moduleTypeId, address module); + + /// @dev The module is not installed. + error ERC7579UninstalledModule(uint256 moduleTypeId, address module); + + /// @dev The module is already installed. + error ERC7579AlreadyInstalledModule(uint256 moduleTypeId, address module); + + /// @dev The module type is not supported. + error ERC7579UnsupportedModuleType(uint256 moduleTypeId); + + /// @dev Input calldata not properly formatted and possibly malicious. + error ERC7579DecodingError(); + + /// @dev Executes a single call. + function execSingle( + bytes calldata executionCalldata, + ExecType execType + ) internal returns (bytes[] memory returnData) { + (address target, uint256 value, bytes calldata callData) = decodeSingle(executionCalldata); + returnData = new bytes[](1); + returnData[0] = _call(0, execType, target, value, callData); + } + + /// @dev Executes a batch of calls. + function execBatch( + bytes calldata executionCalldata, + ExecType execType + ) internal returns (bytes[] memory returnData) { + Execution[] calldata executionBatch = decodeBatch(executionCalldata); + returnData = new bytes[](executionBatch.length); + for (uint256 i = 0; i < executionBatch.length; ++i) { + returnData[i] = _call( + i, + execType, + executionBatch[i].target, + executionBatch[i].value, + executionBatch[i].callData + ); + } + } + + /// @dev Executes a delegate call. + function execDelegateCall( + bytes calldata executionCalldata, + ExecType execType + ) internal returns (bytes[] memory returnData) { + (address target, bytes calldata callData) = decodeDelegate(executionCalldata); + returnData = new bytes[](1); + returnData[0] = _delegatecall(0, execType, target, callData); + } + + /// @dev Encodes the mode with the provided parameters. See {decodeMode}. + function encodeMode( + CallType callType, + ExecType execType, + ModeSelector selector, + ModePayload payload + ) internal pure returns (Mode mode) { + return + Mode.wrap( + CallType + .unwrap(callType) + .pack_1_1(ExecType.unwrap(execType)) + .pack_2_4(bytes4(0)) + .pack_6_4(ModeSelector.unwrap(selector)) + .pack_10_22(ModePayload.unwrap(payload)) + ); + } + + /// @dev Decodes the mode into its parameters. See {encodeMode}. + function decodeMode( + Mode mode + ) internal pure returns (CallType callType, ExecType execType, ModeSelector selector, ModePayload payload) { + return ( + CallType.wrap(Packing.extract_32_1(Mode.unwrap(mode), 0)), + ExecType.wrap(Packing.extract_32_1(Mode.unwrap(mode), 1)), + ModeSelector.wrap(Packing.extract_32_4(Mode.unwrap(mode), 6)), + ModePayload.wrap(Packing.extract_32_22(Mode.unwrap(mode), 10)) + ); + } + + /// @dev Encodes a single call execution. See {decodeSingle}. + function encodeSingle( + address target, + uint256 value, + bytes calldata callData + ) internal pure returns (bytes memory executionCalldata) { + return abi.encodePacked(target, value, callData); + } + + /// @dev Decodes a single call execution. See {encodeSingle}. + function decodeSingle( + bytes calldata executionCalldata + ) internal pure returns (address target, uint256 value, bytes calldata callData) { + target = address(bytes20(executionCalldata[0:20])); + value = uint256(bytes32(executionCalldata[20:52])); + callData = executionCalldata[52:]; + } + + /// @dev Encodes a delegate call execution. See {decodeDelegate}. + function encodeDelegate( + address target, + bytes calldata callData + ) internal pure returns (bytes memory executionCalldata) { + return abi.encodePacked(target, callData); + } + + /// @dev Decodes a delegate call execution. See {encodeDelegate}. + function decodeDelegate( + bytes calldata executionCalldata + ) internal pure returns (address target, bytes calldata callData) { + target = address(bytes20(executionCalldata[0:20])); + callData = executionCalldata[20:]; + } + + /// @dev Encodes a batch of executions. See {decodeBatch}. + function encodeBatch(Execution[] memory executionBatch) internal pure returns (bytes memory executionCalldata) { + return abi.encode(executionBatch); + } + + /// @dev Decodes a batch of executions. See {encodeBatch}. + /// + /// NOTE: This function runs some checks and will throw a {ERC7579DecodingError} if the input is not properly formatted. + function decodeBatch(bytes calldata executionCalldata) internal pure returns (Execution[] calldata executionBatch) { + unchecked { + uint256 bufferLength = executionCalldata.length; + + // Check executionCalldata is not empty. + if (bufferLength < 32) revert ERC7579DecodingError(); + + // Get the offset of the array (pointer to the array length). + uint256 arrayLengthOffset = uint256(bytes32(executionCalldata[0:32])); + + // The array length (at arrayLengthOffset) should be 32 bytes long. We check that this is within the + // buffer bounds. Since we know bufferLength is at least 32, we can subtract with no overflow risk. + if (arrayLengthOffset > bufferLength - 32) revert ERC7579DecodingError(); + + // Get the array length. arrayLengthOffset + 32 is bounded by bufferLength so it does not overflow. + uint256 arrayLength = uint256(bytes32(executionCalldata[arrayLengthOffset:arrayLengthOffset + 32])); + + // Check that the buffer is long enough to store the array elements as "offset pointer": + // - each element of the array is an "offset pointer" to the data. + // - each "offset pointer" (to an array element) takes 32 bytes. + // - validity of the calldata at that location is checked when the array element is accessed, so we only + // need to check that the buffer is large enough to hold the pointers. + // + // Since we know bufferLength is at least arrayLengthOffset + 32, we can subtract with no overflow risk. + // Solidity limits length of such arrays to 2**64-1, this guarantees `arrayLength * 32` does not overflow. + if (arrayLength > type(uint64).max || bufferLength - arrayLengthOffset - 32 < arrayLength * 32) + revert ERC7579DecodingError(); + + assembly ("memory-safe") { + executionBatch.offset := add(add(executionCalldata.offset, arrayLengthOffset), 32) + executionBatch.length := arrayLength + } + } + } + + /// @dev Executes a `call` to the target with the provided {ExecType}. + function _call( + uint256 index, + ExecType execType, + address target, + uint256 value, + bytes calldata data + ) private returns (bytes memory) { + (bool success, bytes memory returndata) = target.call{value: value}(data); + return _validateExecutionMode(index, execType, success, returndata); + } + + /// @dev Executes a `delegatecall` to the target with the provided {ExecType}. + function _delegatecall( + uint256 index, + ExecType execType, + address target, + bytes calldata data + ) private returns (bytes memory) { + (bool success, bytes memory returndata) = target.delegatecall(data); + return _validateExecutionMode(index, execType, success, returndata); + } + + /// @dev Validates the execution mode and returns the returndata. + function _validateExecutionMode( + uint256 index, + ExecType execType, + bool success, + bytes memory returndata + ) private returns (bytes memory) { + if (execType == ERC7579Utils.EXECTYPE_DEFAULT) { + Address.verifyCallResult(success, returndata); + } else if (execType == ERC7579Utils.EXECTYPE_TRY) { + if (!success) emit ERC7579TryExecuteFail(index, returndata); + } else { + revert ERC7579UnsupportedExecType(execType); + } + return returndata; + } +} + +// Operators +using {eqCallType as ==} for CallType global; +using {eqExecType as ==} for ExecType global; +using {eqModeSelector as ==} for ModeSelector global; +using {eqModePayload as ==} for ModePayload global; + +/// @dev Compares two `CallType` values for equality. +function eqCallType(CallType a, CallType b) pure returns (bool) { + return CallType.unwrap(a) == CallType.unwrap(b); +} + +/// @dev Compares two `ExecType` values for equality. +function eqExecType(ExecType a, ExecType b) pure returns (bool) { + return ExecType.unwrap(a) == ExecType.unwrap(b); +} + +/// @dev Compares two `ModeSelector` values for equality. +function eqModeSelector(ModeSelector a, ModeSelector b) pure returns (bool) { + return ModeSelector.unwrap(a) == ModeSelector.unwrap(b); +} + +/// @dev Compares two `ModePayload` values for equality. +function eqModePayload(ModePayload a, ModePayload b) pure returns (bool) { + return ModePayload.unwrap(a) == ModePayload.unwrap(b); +} diff --git a/contracts/finance/VestingWallet.sol b/contracts/finance/VestingWallet.sol index 0e0321d0d..d29194563 100644 --- a/contracts/finance/VestingWallet.sol +++ b/contracts/finance/VestingWallet.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.1.0) (finance/VestingWallet.sol) +// OpenZeppelin Contracts (last updated v5.2.0) (finance/VestingWallet.sol) pragma solidity ^0.8.20; import {IERC20} from "../token/ERC20/IERC20.sol"; @@ -26,6 +26,11 @@ import {Ownable} from "../access/Ownable.sol"; * * 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. + * + * NOTE: Chains with support for native ERC20s may allow the vesting wallet to withdraw the underlying asset as both an + * ERC20 and as native currency. For example, if chain C supports token A and the wallet gets deposited 100 A, then + * at 50% of the vesting period, the beneficiary can withdraw 50 A as ERC20 and 25 A as native currency (totaling 75 A). + * Consider disabling one of the withdrawal methods. */ contract VestingWallet is Context, Ownable { event EtherReleased(uint256 amount); diff --git a/contracts/governance/Governor.sol b/contracts/governance/Governor.sol index 465a5ef2f..41258afff 100644 --- a/contracts/governance/Governor.sol +++ b/contracts/governance/Governor.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.1.0) (governance/Governor.sol) +// OpenZeppelin Contracts (last updated v5.2.0) (governance/Governor.sol) pragma solidity ^0.8.20; @@ -13,6 +13,7 @@ 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 {Strings} from "../utils/Strings.sol"; import {IGovernor, IERC6372} from "./IGovernor.sol"; /** @@ -259,6 +260,13 @@ abstract contract Governor is Context, ERC165, EIP712, Nonces, IGovernor, IERC72 bytes memory params ) internal virtual returns (uint256); + /** + * @dev Hook that should be called every time the tally for a proposal is updated. + * + * Note: This function must run successfully. Reverts will result in the bricking of governance + */ + function _tallyUpdated(uint256 proposalId) internal virtual {} + /** * @dev Default additional encoded parameters used by castVote methods that don't include them * @@ -648,6 +656,8 @@ abstract contract Governor is Context, ERC165, EIP712, Nonces, IGovernor, IERC72 emit VoteCastWithParams(account, proposalId, support, votedWeight, reason, params); } + _tallyUpdated(proposalId); + return votedWeight; } @@ -731,7 +741,7 @@ abstract contract Governor is Context, ERC165, EIP712, Nonces, IGovernor, IERC72 * * If requirements are not met, reverts with a {GovernorUnexpectedProposalState} error. */ - function _validateStateBitmap(uint256 proposalId, bytes32 allowedStates) private view returns (ProposalState) { + function _validateStateBitmap(uint256 proposalId, bytes32 allowedStates) internal view returns (ProposalState) { ProposalState currentState = state(proposalId); if (_encodeStateBitmap(currentState) & allowedStates == bytes32(0)) { revert GovernorUnexpectedProposalState(proposalId, currentState, allowedStates); @@ -760,67 +770,25 @@ abstract contract Governor is Context, ERC165, EIP712, Nonces, IGovernor, IERC72 address proposer, string memory description ) internal view virtual returns (bool) { - uint256 len = bytes(description).length; + unchecked { + uint256 length = 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 ("memory-safe") { - // - 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) { + // Length is too short to contain a valid proposer suffix + if (length < 52) { return true; } - recovered = (recovered << 4) | value; - } - return recovered == uint160(proposer); - } + // Extract what would be the `#proposer=` marker beginning the suffix + bytes10 marker = bytes10(_unsafeReadBytesOffset(bytes(description), length - 52)); - /** - * @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 isHex, uint8 value) { - 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); + // If the marker is not found, there is no proposer suffix to check + if (marker != bytes10("#proposer=")) { + return true; } + + // Check that the last 42 characters (after the marker) are a properly formatted address. + (bool success, address recovered) = Strings.tryParseAddress(description, length - 42, length); + return !success || recovered == proposer; } } @@ -849,4 +817,17 @@ abstract contract Governor is Context, ERC165, EIP712, Nonces, IGovernor, IERC72 * @inheritdoc IGovernor */ function quorum(uint256 timepoint) public view virtual returns (uint256); + + /** + * @dev Reads a bytes32 from a bytes array without bounds checking. + * + * NOTE: making this function internal would mean it could be used with memory unsafe offset, and marking the + * assembly block as such would prevent some optimizations. + */ + function _unsafeReadBytesOffset(bytes memory buffer, uint256 offset) private pure returns (bytes32 value) { + // This is not memory safe in the general case, but all calls to this private function are within bounds. + assembly ("memory-safe") { + value := mload(add(buffer, add(0x20, offset))) + } + } } diff --git a/contracts/governance/README.adoc b/contracts/governance/README.adoc index 0f556b90d..d390d1be8 100644 --- a/contracts/governance/README.adoc +++ b/contracts/governance/README.adoc @@ -30,6 +30,8 @@ Counting modules determine valid voting options. * {GovernorCountingFractional}: A more modular voting system that allows a user to vote with only part of its voting power, and to split that weight arbitrarily between the 3 different options (Against, For and Abstain). +* {GovernorCountingOverridable}: An extended version of `GovernorCountingSimple` which allows delegatees to override their delegates while the vote is live. + 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. * {GovernorTimelockAccess}: Connects with an instance of an {AccessManager}. This allows restrictions (and delays) enforced by the manager to be considered by the Governor and integrated into the AccessManager's "schedule + execute" workflow. @@ -66,6 +68,8 @@ NOTE: Functions of the `Governor` contract do not include access control. If you {{GovernorCountingFractional}} +{{GovernorCountingOverride}} + {{GovernorVotes}} {{GovernorVotesQuorumFraction}} @@ -88,6 +92,8 @@ NOTE: Functions of the `Governor` contract do not include access control. If you {{Votes}} +{{VotesExtended}} + == 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}. diff --git a/contracts/governance/extensions/GovernorCountingOverridable.sol b/contracts/governance/extensions/GovernorCountingOverridable.sol new file mode 100644 index 000000000..2ed6c1cc5 --- /dev/null +++ b/contracts/governance/extensions/GovernorCountingOverridable.sol @@ -0,0 +1,225 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.2.0) (governance/extensions/GovernorCountingOverridable.sol) + +pragma solidity ^0.8.20; + +import {SignatureChecker} from "../../utils/cryptography/SignatureChecker.sol"; +import {SafeCast} from "../../utils/math/SafeCast.sol"; +import {VotesExtended} from "../utils/VotesExtended.sol"; +import {GovernorVotes} from "./GovernorVotes.sol"; + +/** + * @dev Extension of {Governor} which enables delegators to override the vote of their delegates. This module requires a + * token that inherits {VotesExtended}. + */ +abstract contract GovernorCountingOverridable is GovernorVotes { + bytes32 public constant OVERRIDE_BALLOT_TYPEHASH = + keccak256("OverrideBallot(uint256 proposalId,uint8 support,address voter,uint256 nonce,string reason)"); + + /** + * @dev Supported vote types. Matches Governor Bravo ordering. + */ + enum VoteType { + Against, + For, + Abstain + } + + struct VoteReceipt { + uint8 casted; // 0 if vote was not casted. Otherwise: support + 1 + bool hasOverriden; + uint208 overridenWeight; + } + + struct ProposalVote { + uint256[3] votes; + mapping(address voter => VoteReceipt) voteReceipt; + } + + /// @dev The votes casted by `delegate` were reduced by `weight` after an override vote was casted by the original token holder + event VoteReduced(address indexed delegate, uint256 proposalId, uint8 support, uint256 weight); + + /// @dev A delegated vote on `proposalId` was overridden by `weight` + event OverrideVoteCast(address indexed voter, uint256 proposalId, uint8 support, uint256 weight, string reason); + + error GovernorAlreadyOverridenVote(address account); + + 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,override&quorum=for,abstain&overridable=true"; + } + + /** + * @dev See {IGovernor-hasVoted}. + * + * NOTE: Calling {castVote} (or similar) casts a vote using the voting power that is delegated to the voter. + * Conversely, calling {castOverrideVote} (or similar) uses the voting power of the account itself, from its asset + * balances. Casting an "override vote" does not count as voting and won't be reflected by this getter. Consider + * using {hasVotedOverride} to check if an account has casted an "override vote" for a given proposal id. + */ + function hasVoted(uint256 proposalId, address account) public view virtual override returns (bool) { + return _proposalVotes[proposalId].voteReceipt[account].casted != 0; + } + + /** + * @dev Check if an `account` has overridden their delegate for a proposal. + */ + function hasVotedOverride(uint256 proposalId, address account) public view virtual returns (bool) { + return _proposalVotes[proposalId].voteReceipt[account].hasOverriden; + } + + /** + * @dev Accessor to the internal vote counts. + */ + function proposalVotes( + uint256 proposalId + ) public view virtual returns (uint256 againstVotes, uint256 forVotes, uint256 abstainVotes) { + uint256[3] storage votes = _proposalVotes[proposalId].votes; + return (votes[uint8(VoteType.Against)], votes[uint8(VoteType.For)], votes[uint8(VoteType.Abstain)]); + } + + /** + * @dev See {Governor-_quorumReached}. + */ + function _quorumReached(uint256 proposalId) internal view virtual override returns (bool) { + uint256[3] storage votes = _proposalVotes[proposalId].votes; + return quorum(proposalSnapshot(proposalId)) <= votes[uint8(VoteType.For)] + votes[uint8(VoteType.Abstain)]; + } + + /** + * @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) { + uint256[3] storage votes = _proposalVotes[proposalId].votes; + return votes[uint8(VoteType.For)] > votes[uint8(VoteType.Against)]; + } + + /** + * @dev See {Governor-_countVote}. In this module, the support follows the `VoteType` enum (from Governor Bravo). + * + * NOTE: called by {Governor-_castVote} which emits the {IGovernor-VoteCast} (or {IGovernor-VoteCastWithParams}) + * event. + */ + function _countVote( + uint256 proposalId, + address account, + uint8 support, + uint256 totalWeight, + bytes memory /*params*/ + ) internal virtual override returns (uint256) { + ProposalVote storage proposalVote = _proposalVotes[proposalId]; + + if (support > uint8(VoteType.Abstain)) { + revert GovernorInvalidVoteType(); + } + + if (proposalVote.voteReceipt[account].casted != 0) { + revert GovernorAlreadyCastVote(account); + } + + totalWeight -= proposalVote.voteReceipt[account].overridenWeight; + proposalVote.votes[support] += totalWeight; + proposalVote.voteReceipt[account].casted = support + 1; + + return totalWeight; + } + + /** + * @dev Variant of {Governor-_countVote} that deals with vote overrides. + * + * NOTE: See {hasVoted} for more details about the difference between {castVote} and {castOverrideVote}. + */ + function _countOverride(uint256 proposalId, address account, uint8 support) internal virtual returns (uint256) { + ProposalVote storage proposalVote = _proposalVotes[proposalId]; + + if (support > uint8(VoteType.Abstain)) { + revert GovernorInvalidVoteType(); + } + + if (proposalVote.voteReceipt[account].hasOverriden) { + revert GovernorAlreadyOverridenVote(account); + } + + uint256 snapshot = proposalSnapshot(proposalId); + uint256 overridenWeight = VotesExtended(address(token())).getPastBalanceOf(account, snapshot); + address delegate = VotesExtended(address(token())).getPastDelegate(account, snapshot); + uint8 delegateCasted = proposalVote.voteReceipt[delegate].casted; + + proposalVote.voteReceipt[account].hasOverriden = true; + proposalVote.votes[support] += overridenWeight; + if (delegateCasted == 0) { + proposalVote.voteReceipt[delegate].overridenWeight += SafeCast.toUint208(overridenWeight); + } else { + uint8 delegateSupport = delegateCasted - 1; + proposalVote.votes[delegateSupport] -= overridenWeight; + emit VoteReduced(delegate, proposalId, delegateSupport, overridenWeight); + } + + return overridenWeight; + } + + /// @dev Variant of {Governor-_castVote} that deals with vote overrides. Returns the overridden weight. + function _castOverride( + uint256 proposalId, + address account, + uint8 support, + string calldata reason + ) internal virtual returns (uint256) { + _validateStateBitmap(proposalId, _encodeStateBitmap(ProposalState.Active)); + + uint256 overridenWeight = _countOverride(proposalId, account, support); + + emit OverrideVoteCast(account, proposalId, support, overridenWeight, reason); + + _tallyUpdated(proposalId); + + return overridenWeight; + } + + /// @dev Public function for casting an override vote. Returns the overridden weight. + function castOverrideVote( + uint256 proposalId, + uint8 support, + string calldata reason + ) public virtual returns (uint256) { + address voter = _msgSender(); + return _castOverride(proposalId, voter, support, reason); + } + + /// @dev Public function for casting an override vote using a voter's signature. Returns the overridden weight. + function castOverrideVoteBySig( + uint256 proposalId, + uint8 support, + address voter, + string calldata reason, + bytes calldata signature + ) public virtual returns (uint256) { + bool valid = SignatureChecker.isValidSignatureNow( + voter, + _hashTypedDataV4( + keccak256( + abi.encode( + OVERRIDE_BALLOT_TYPEHASH, + proposalId, + support, + voter, + _useNonce(voter), + keccak256(bytes(reason)) + ) + ) + ), + signature + ); + + if (!valid) { + revert GovernorInvalidSignature(voter); + } + + return _castOverride(proposalId, voter, support, reason); + } +} diff --git a/contracts/governance/extensions/GovernorPreventLateQuorum.sol b/contracts/governance/extensions/GovernorPreventLateQuorum.sol index ff80af648..02b201d8a 100644 --- a/contracts/governance/extensions/GovernorPreventLateQuorum.sol +++ b/contracts/governance/extensions/GovernorPreventLateQuorum.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (governance/extensions/GovernorPreventLateQuorum.sol) +// OpenZeppelin Contracts (last updated v5.2.0) (governance/extensions/GovernorPreventLateQuorum.sol) pragma solidity ^0.8.20; @@ -44,20 +44,12 @@ abstract contract GovernorPreventLateQuorum is Governor { } /** - * @dev Casts a vote and detects if it caused quorum to be reached, potentially extending the voting period. See - * {Governor-_castVote}. + * @dev Vote tally updated and detects if it caused quorum to be reached, potentially extending the voting period. * * 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); - + function _tallyUpdated(uint256 proposalId) internal virtual override { + super._tallyUpdated(proposalId); if (_extendedDeadlines[proposalId] == 0 && _quorumReached(proposalId)) { uint48 extendedDeadline = clock() + lateQuorumVoteExtension(); @@ -67,8 +59,6 @@ abstract contract GovernorPreventLateQuorum is Governor { _extendedDeadlines[proposalId] = extendedDeadline; } - - return result; } /** diff --git a/contracts/governance/utils/Votes.sol b/contracts/governance/utils/Votes.sol index bbbc2264f..f5994f2bd 100644 --- a/contracts/governance/utils/Votes.sol +++ b/contracts/governance/utils/Votes.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.1.0) (governance/utils/Votes.sol) +// OpenZeppelin Contracts (last updated v5.2.0) (governance/utils/Votes.sol) pragma solidity ^0.8.20; import {IERC5805} from "../../interfaces/IERC5805.sol"; @@ -71,6 +71,15 @@ abstract contract Votes is Context, EIP712, Nonces, IERC5805 { return "mode=blocknumber&from=default"; } + /** + * @dev Validate that a timepoint is in the past, and return it as a uint48. + */ + function _validateTimepoint(uint256 timepoint) internal view returns (uint48) { + uint48 currentTimepoint = clock(); + if (timepoint >= currentTimepoint) revert ERC5805FutureLookup(timepoint, currentTimepoint); + return SafeCast.toUint48(timepoint); + } + /** * @dev Returns the current amount of votes that `account` has. */ @@ -87,11 +96,7 @@ abstract contract Votes is Context, EIP712, Nonces, IERC5805 { * - `timepoint` must be in the past. If operating using block numbers, the block must be already mined. */ function getPastVotes(address account, uint256 timepoint) public view virtual returns (uint256) { - uint48 currentTimepoint = clock(); - if (timepoint >= currentTimepoint) { - revert ERC5805FutureLookup(timepoint, currentTimepoint); - } - return _delegateCheckpoints[account].upperLookupRecent(SafeCast.toUint48(timepoint)); + return _delegateCheckpoints[account].upperLookupRecent(_validateTimepoint(timepoint)); } /** @@ -107,11 +112,7 @@ abstract contract Votes is Context, EIP712, Nonces, IERC5805 { * - `timepoint` must be in the past. If operating using block numbers, the block must be already mined. */ function getPastTotalSupply(uint256 timepoint) public view virtual returns (uint256) { - uint48 currentTimepoint = clock(); - if (timepoint >= currentTimepoint) { - revert ERC5805FutureLookup(timepoint, currentTimepoint); - } - return _totalCheckpoints.upperLookupRecent(SafeCast.toUint48(timepoint)); + return _totalCheckpoints.upperLookupRecent(_validateTimepoint(timepoint)); } /** diff --git a/contracts/governance/utils/VotesExtended.sol b/contracts/governance/utils/VotesExtended.sol new file mode 100644 index 000000000..5b6732038 --- /dev/null +++ b/contracts/governance/utils/VotesExtended.sol @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.2.0) (governance/utils/VotesExtended.sol) +pragma solidity ^0.8.20; + +import {Checkpoints} from "../../utils/structs/Checkpoints.sol"; +import {Votes} from "./Votes.sol"; +import {SafeCast} from "../../utils/math/SafeCast.sol"; + +/** + * @dev Extension of {Votes} that adds checkpoints for delegations and balances. + * + * WARNING: While this contract extends {Votes}, valid uses of {Votes} may not be compatible with + * {VotesExtended} without additional considerations. This implementation of {_transferVotingUnits} must + * run AFTER the voting weight movement is registered, such that it is reflected on {_getVotingUnits}. + * + * Said differently, {VotesExtended} MUST be integrated in a way that calls {_transferVotingUnits} AFTER the + * asset transfer is registered and balances are updated: + * + * ```solidity + * contract VotingToken is Token, VotesExtended { + * function transfer(address from, address to, uint256 tokenId) public override { + * super.transfer(from, to, tokenId); // <- Perform the transfer first ... + * _transferVotingUnits(from, to, 1); // <- ... then call _transferVotingUnits. + * } + * + * function _getVotingUnits(address account) internal view override returns (uint256) { + * return balanceOf(account); + * } + * } + * ``` + * + * {ERC20Votes} and {ERC721Votes} follow this pattern and are thus safe to use with {VotesExtended}. + */ +abstract contract VotesExtended is Votes { + using Checkpoints for Checkpoints.Trace160; + using Checkpoints for Checkpoints.Trace208; + + mapping(address delegator => Checkpoints.Trace160) private _userDelegationCheckpoints; + mapping(address account => Checkpoints.Trace208) private _userVotingUnitsCheckpoints; + + /** + * @dev Returns the delegate of an `account` 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 getPastDelegate(address account, uint256 timepoint) public view virtual returns (address) { + return address(_userDelegationCheckpoints[account].upperLookupRecent(_validateTimepoint(timepoint))); + } + + /** + * @dev Returns the `balanceOf` of an `account` 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 getPastBalanceOf(address account, uint256 timepoint) public view virtual returns (uint256) { + return _userVotingUnitsCheckpoints[account].upperLookupRecent(_validateTimepoint(timepoint)); + } + + /// @inheritdoc Votes + function _delegate(address account, address delegatee) internal virtual override { + super._delegate(account, delegatee); + + _userDelegationCheckpoints[account].push(clock(), uint160(delegatee)); + } + + /// @inheritdoc Votes + function _transferVotingUnits(address from, address to, uint256 amount) internal virtual override { + super._transferVotingUnits(from, to, amount); + if (from != to) { + if (from != address(0)) { + _userVotingUnitsCheckpoints[from].push(clock(), SafeCast.toUint208(_getVotingUnits(from))); + } + if (to != address(0)) { + _userVotingUnitsCheckpoints[to].push(clock(), SafeCast.toUint208(_getVotingUnits(to))); + } + } + } +} diff --git a/contracts/interfaces/draft-IERC4337.sol b/contracts/interfaces/draft-IERC4337.sol new file mode 100644 index 000000000..2aa526db6 --- /dev/null +++ b/contracts/interfaces/draft-IERC4337.sol @@ -0,0 +1,253 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.2.0) (interfaces/draft-IERC4337.sol) + +pragma solidity ^0.8.20; + +/** + * @dev A https://github.com/ethereum/ercs/blob/master/ERCS/erc-4337.md#useroperation[user operation] is composed of the following elements: + * - `sender` (`address`): The account making the operation + * - `nonce` (`uint256`): Anti-replay parameter (see “Semi-abstracted Nonce Support” ) + * - `factory` (`address`): account factory, only for new accounts + * - `factoryData` (`bytes`): data for account factory (only if account factory exists) + * - `callData` (`bytes`): The data to pass to the sender during the main execution call + * - `callGasLimit` (`uint256`): The amount of gas to allocate the main execution call + * - `verificationGasLimit` (`uint256`): The amount of gas to allocate for the verification step + * - `preVerificationGas` (`uint256`): Extra gas to pay the bundler + * - `maxFeePerGas` (`uint256`): Maximum fee per gas (similar to EIP-1559 max_fee_per_gas) + * - `maxPriorityFeePerGas` (`uint256`): Maximum priority fee per gas (similar to EIP-1559 max_priority_fee_per_gas) + * - `paymaster` (`address`): Address of paymaster contract, (or empty, if account pays for itself) + * - `paymasterVerificationGasLimit` (`uint256`): The amount of gas to allocate for the paymaster validation code + * - `paymasterPostOpGasLimit` (`uint256`): The amount of gas to allocate for the paymaster post-operation code + * - `paymasterData` (`bytes`): Data for paymaster (only if paymaster exists) + * - `signature` (`bytes`): Data passed into the account to verify authorization + * + * When passed to on-chain contacts, the following packed version is used. + * - `sender` (`address`) + * - `nonce` (`uint256`) + * - `initCode` (`bytes`): concatenation of factory address and factoryData (or empty) + * - `callData` (`bytes`) + * - `accountGasLimits` (`bytes32`): concatenation of verificationGas (16 bytes) and callGas (16 bytes) + * - `preVerificationGas` (`uint256`) + * - `gasFees` (`bytes32`): concatenation of maxPriorityFeePerGas (16 bytes) and maxFeePerGas (16 bytes) + * - `paymasterAndData` (`bytes`): concatenation of paymaster fields (or empty) + * - `signature` (`bytes`) + */ +struct PackedUserOperation { + address sender; + uint256 nonce; + bytes initCode; // `abi.encodePacked(factory, factoryData)` + bytes callData; + bytes32 accountGasLimits; // `abi.encodePacked(verificationGasLimit, callGasLimit)` 16 bytes each + uint256 preVerificationGas; + bytes32 gasFees; // `abi.encodePacked(maxPriorityFeePerGas, maxFeePerGas)` 16 bytes each + bytes paymasterAndData; // `abi.encodePacked(paymaster, paymasterVerificationGasLimit, paymasterPostOpGasLimit, paymasterData)` (20 bytes, 16 bytes, 16 bytes, dynamic) + bytes signature; +} + +/** + * @dev Aggregates and validates multiple signatures for a batch of user operations. + * + * A contract could implement this interface with custom validation schemes that allow signature aggregation, + * enabling significant optimizations and gas savings for execution and transaction data cost. + * + * Bundlers and clients whitelist supported aggregators. + * + * See https://eips.ethereum.org/EIPS/eip-7766[ERC-7766] + */ +interface IAggregator { + /** + * @dev Validates the signature for a user operation. + * Returns an alternative signature that should be used during bundling. + */ + function validateUserOpSignature( + PackedUserOperation calldata userOp + ) external view returns (bytes memory sigForUserOp); + + /** + * @dev Returns an aggregated signature for a batch of user operation's signatures. + */ + function aggregateSignatures( + PackedUserOperation[] calldata userOps + ) external view returns (bytes memory aggregatesSignature); + + /** + * @dev Validates that the aggregated signature is valid for the user operations. + * + * Requirements: + * + * - The aggregated signature MUST match the given list of operations. + */ + function validateSignatures(PackedUserOperation[] calldata userOps, bytes calldata signature) external view; +} + +/** + * @dev Handle nonce management for accounts. + * + * Nonces are used in accounts as a replay protection mechanism and to ensure the order of user operations. + * To avoid limiting the number of operations an account can perform, the interface allows using parallel + * nonces by using a `key` parameter. + * + * See https://eips.ethereum.org/EIPS/eip-4337#semi-abstracted-nonce-support[ERC-4337 semi-abstracted nonce support]. + */ +interface IEntryPointNonces { + /** + * @dev Returns the nonce for a `sender` account and a `key`. + * + * Nonces for a certain `key` are always increasing. + */ + function getNonce(address sender, uint192 key) external view returns (uint256 nonce); +} + +/** + * @dev Handle stake management for entities (i.e. accounts, paymasters, factories). + * + * The EntryPoint must implement the following API to let entities like paymasters have a stake, + * and thus have more flexibility in their storage access + * (see https://eips.ethereum.org/EIPS/eip-4337#reputation-scoring-and-throttlingbanning-for-global-entities[reputation, throttling and banning.]) + */ +interface IEntryPointStake { + /** + * @dev Returns the balance of the account. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @dev Deposits `msg.value` to the account. + */ + function depositTo(address account) external payable; + + /** + * @dev Withdraws `withdrawAmount` from the account to `withdrawAddress`. + */ + function withdrawTo(address payable withdrawAddress, uint256 withdrawAmount) external; + + /** + * @dev Adds stake to the account with an unstake delay of `unstakeDelaySec`. + */ + function addStake(uint32 unstakeDelaySec) external payable; + + /** + * @dev Unlocks the stake of the account. + */ + function unlockStake() external; + + /** + * @dev Withdraws the stake of the account to `withdrawAddress`. + */ + function withdrawStake(address payable withdrawAddress) external; +} + +/** + * @dev Entry point for user operations. + * + * User operations are validated and executed by this contract. + */ +interface IEntryPoint is IEntryPointNonces, IEntryPointStake { + /** + * @dev A user operation at `opIndex` failed with `reason`. + */ + error FailedOp(uint256 opIndex, string reason); + + /** + * @dev A user operation at `opIndex` failed with `reason` and `inner` returned data. + */ + error FailedOpWithRevert(uint256 opIndex, string reason, bytes inner); + + /** + * @dev Batch of aggregated user operations per aggregator. + */ + struct UserOpsPerAggregator { + PackedUserOperation[] userOps; + IAggregator aggregator; + bytes signature; + } + + /** + * @dev Executes a batch of user operations. + * @param beneficiary Address to which gas is refunded up completing the execution. + */ + function handleOps(PackedUserOperation[] calldata ops, address payable beneficiary) external; + + /** + * @dev Executes a batch of aggregated user operations per aggregator. + * @param beneficiary Address to which gas is refunded up completing the execution. + */ + function handleAggregatedOps( + UserOpsPerAggregator[] calldata opsPerAggregator, + address payable beneficiary + ) external; +} + +/** + * @dev Base interface for an ERC-4337 account. + */ +interface IAccount { + /** + * @dev Validates a user operation. + * + * * MUST validate the caller is a trusted EntryPoint + * * MUST validate that the signature is a valid signature of the userOpHash, and SHOULD + * return SIG_VALIDATION_FAILED (and not revert) on signature mismatch. Any other error MUST revert. + * * MUST pay the entryPoint (caller) at least the “missingAccountFunds” (which might + * be zero, in case the current account’s deposit is high enough) + * + * Returns an encoded packed validation data that is composed of the following elements: + * + * - `authorizer` (`address`): 0 for success, 1 for failure, otherwise the address of an authorizer contract + * - `validUntil` (`uint48`): The UserOp is valid only up to this time. Zero for “infinite”. + * - `validAfter` (`uint48`): The UserOp is valid only after this time. + */ + function validateUserOp( + PackedUserOperation calldata userOp, + bytes32 userOpHash, + uint256 missingAccountFunds + ) external returns (uint256 validationData); +} + +/** + * @dev Support for executing user operations by prepending the {executeUserOp} function selector + * to the UserOperation's `callData`. + */ +interface IAccountExecute { + /** + * @dev Executes a user operation. + */ + function executeUserOp(PackedUserOperation calldata userOp, bytes32 userOpHash) external; +} + +/** + * @dev Interface for a paymaster contract that agrees to pay for the gas costs of a user operation. + * + * NOTE: A paymaster must hold a stake to cover the required entrypoint stake and also the gas for the transaction. + */ +interface IPaymaster { + enum PostOpMode { + opSucceeded, + opReverted, + postOpReverted + } + + /** + * @dev Validates whether the paymaster is willing to pay for the user operation. See + * {IAccount-validateUserOp} for additional information on the return value. + * + * NOTE: Bundlers will reject this method if it modifies the state, unless it's whitelisted. + */ + function validatePaymasterUserOp( + PackedUserOperation calldata userOp, + bytes32 userOpHash, + uint256 maxCost + ) external returns (bytes memory context, uint256 validationData); + + /** + * @dev Verifies the sender is the entrypoint. + * @param actualGasCost the actual amount paid (by account or paymaster) for this UserOperation + * @param actualUserOpFeePerGas total gas used by this UserOperation (including preVerification, creation, validation and execution) + */ + function postOp( + PostOpMode mode, + bytes calldata context, + uint256 actualGasCost, + uint256 actualUserOpFeePerGas + ) external; +} diff --git a/contracts/interfaces/draft-IERC7579.sol b/contracts/interfaces/draft-IERC7579.sol new file mode 100644 index 000000000..077d9783f --- /dev/null +++ b/contracts/interfaces/draft-IERC7579.sol @@ -0,0 +1,226 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.2.0) (interfaces/draft-IERC7579.sol) +pragma solidity ^0.8.20; + +import {PackedUserOperation} from "./draft-IERC4337.sol"; + +uint256 constant VALIDATION_SUCCESS = 0; +uint256 constant VALIDATION_FAILED = 1; +uint256 constant MODULE_TYPE_VALIDATOR = 1; +uint256 constant MODULE_TYPE_EXECUTOR = 2; +uint256 constant MODULE_TYPE_FALLBACK = 3; +uint256 constant MODULE_TYPE_HOOK = 4; + +/// @dev Minimal configuration interface for ERC-7579 modules +interface IERC7579Module { + /** + * @dev This function is called by the smart account during installation of the module + * @param data arbitrary data that may be required on the module during `onInstall` initialization + * + * MUST revert on error (e.g. if module is already enabled) + */ + function onInstall(bytes calldata data) external; + + /** + * @dev This function is called by the smart account during uninstallation of the module + * @param data arbitrary data that may be required on the module during `onUninstall` de-initialization + * + * MUST revert on error + */ + function onUninstall(bytes calldata data) external; + + /** + * @dev Returns boolean value if module is a certain type + * @param moduleTypeId the module type ID according the ERC-7579 spec + * + * MUST return true if the module is of the given type and false otherwise + */ + function isModuleType(uint256 moduleTypeId) external view returns (bool); +} + +/** + * @dev ERC-7579 Validation module (type 1). + * + * A module that implements logic to validate user operations and signatures. + */ +interface IERC7579Validator is IERC7579Module { + /** + * @dev Validates a UserOperation + * @param userOp the ERC-4337 PackedUserOperation + * @param userOpHash the hash of the ERC-4337 PackedUserOperation + * + * MUST validate that the signature is a valid signature of the userOpHash + * SHOULD return ERC-4337's SIG_VALIDATION_FAILED (and not revert) on signature mismatch + * See {IAccount-validateUserOp} for additional information on the return value + */ + function validateUserOp(PackedUserOperation calldata userOp, bytes32 userOpHash) external returns (uint256); + + /** + * @dev Validates a signature using ERC-1271 + * @param sender the address that sent the ERC-1271 request to the smart account + * @param hash the hash of the ERC-1271 request + * @param signature the signature of the ERC-1271 request + * + * MUST return the ERC-1271 `MAGIC_VALUE` if the signature is valid + * MUST NOT modify state + */ + function isValidSignatureWithSender( + address sender, + bytes32 hash, + bytes calldata signature + ) external view returns (bytes4); +} + +/** + * @dev ERC-7579 Hooks module (type 4). + * + * A module that implements logic to execute before and after the account executes a user operation, + * either individually or batched. + */ +interface IERC7579Hook is IERC7579Module { + /** + * @dev Called by the smart account before execution + * @param msgSender the address that called the smart account + * @param value the value that was sent to the smart account + * @param msgData the data that was sent to the smart account + * + * MAY return arbitrary data in the `hookData` return value + */ + function preCheck( + address msgSender, + uint256 value, + bytes calldata msgData + ) external returns (bytes memory hookData); + + /** + * @dev Called by the smart account after execution + * @param hookData the data that was returned by the `preCheck` function + * + * MAY validate the `hookData` to validate transaction context of the `preCheck` function + */ + function postCheck(bytes calldata hookData) external; +} + +struct Execution { + address target; + uint256 value; + bytes callData; +} + +/** + * @dev ERC-7579 Execution. + * + * Accounts should implement this interface so that the Entrypoint and ERC-7579 modules can execute operations. + */ +interface IERC7579Execution { + /** + * @dev Executes a transaction on behalf of the account. + * @param mode The encoded execution mode of the transaction. See ModeLib.sol for details + * @param executionCalldata The encoded execution call data + * + * MUST ensure adequate authorization control: e.g. onlyEntryPointOrSelf if used with ERC-4337 + * If a mode is requested that is not supported by the Account, it MUST revert + */ + function execute(bytes32 mode, bytes calldata executionCalldata) external payable; + + /** + * @dev Executes a transaction on behalf of the account. + * This function is intended to be called by Executor Modules + * @param mode The encoded execution mode of the transaction. See ModeLib.sol for details + * @param executionCalldata The encoded execution call data + * @return returnData An array with the returned data of each executed subcall + * + * MUST ensure adequate authorization control: i.e. onlyExecutorModule + * If a mode is requested that is not supported by the Account, it MUST revert + */ + function executeFromExecutor( + bytes32 mode, + bytes calldata executionCalldata + ) external payable returns (bytes[] memory returnData); +} + +/** + * @dev ERC-7579 Account Config. + * + * Accounts should implement this interface to expose information that identifies the account, supported modules and capabilities. + */ +interface IERC7579AccountConfig { + /** + * @dev Returns the account id of the smart account + * @return accountImplementationId the account id of the smart account + * + * MUST return a non-empty string + * The accountId SHOULD be structured like so: + * "vendorname.accountname.semver" + * The id SHOULD be unique across all smart accounts + */ + function accountId() external view returns (string memory accountImplementationId); + + /** + * @dev Function to check if the account supports a certain execution mode (see above) + * @param encodedMode the encoded mode + * + * MUST return true if the account supports the mode and false otherwise + */ + function supportsExecutionMode(bytes32 encodedMode) external view returns (bool); + + /** + * @dev Function to check if the account supports a certain module typeId + * @param moduleTypeId the module type ID according to the ERC-7579 spec + * + * MUST return true if the account supports the module type and false otherwise + */ + function supportsModule(uint256 moduleTypeId) external view returns (bool); +} + +/** + * @dev ERC-7579 Module Config. + * + * Accounts should implement this interface to allow installing and uninstalling modules. + */ +interface IERC7579ModuleConfig { + event ModuleInstalled(uint256 moduleTypeId, address module); + event ModuleUninstalled(uint256 moduleTypeId, address module); + + /** + * @dev Installs a Module of a certain type on the smart account + * @param moduleTypeId the module type ID according to the ERC-7579 spec + * @param module the module address + * @param initData arbitrary data that may be required on the module during `onInstall` + * initialization. + * + * MUST implement authorization control + * MUST call `onInstall` on the module with the `initData` parameter if provided + * MUST emit ModuleInstalled event + * MUST revert if the module is already installed or the initialization on the module failed + */ + function installModule(uint256 moduleTypeId, address module, bytes calldata initData) external; + + /** + * @dev Uninstalls a Module of a certain type on the smart account + * @param moduleTypeId the module type ID according the ERC-7579 spec + * @param module the module address + * @param deInitData arbitrary data that may be required on the module during `onInstall` + * initialization. + * + * MUST implement authorization control + * MUST call `onUninstall` on the module with the `deInitData` parameter if provided + * MUST emit ModuleUninstalled event + * MUST revert if the module is not installed or the deInitialization on the module failed + */ + function uninstallModule(uint256 moduleTypeId, address module, bytes calldata deInitData) external; + + /** + * @dev Returns whether a module is installed on the smart account + * @param moduleTypeId the module type ID according the ERC-7579 spec + * @param module the module address + * @param additionalContext arbitrary data that may be required to determine if the module is installed + * + * MUST return true if the module is installed and false otherwise + */ + function isModuleInstalled( + uint256 moduleTypeId, + address module, + bytes calldata additionalContext + ) external view returns (bool); +} diff --git a/contracts/mocks/DummyImplementation.sol b/contracts/mocks/DummyImplementation.sol index 4925c89df..0f1147407 100644 --- a/contracts/mocks/DummyImplementation.sol +++ b/contracts/mocks/DummyImplementation.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity ^0.8.22; import {ERC1967Utils} from "../proxy/ERC1967/ERC1967Utils.sol"; import {StorageSlot} from "../utils/StorageSlot.sol"; diff --git a/contracts/mocks/MerkleTreeMock.sol b/contracts/mocks/MerkleTreeMock.sol index 2454efa2f..dcde6b658 100644 --- a/contracts/mocks/MerkleTreeMock.sol +++ b/contracts/mocks/MerkleTreeMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.20; import {MerkleTree} from "../utils/structs/MerkleTree.sol"; diff --git a/contracts/mocks/Stateless.sol b/contracts/mocks/Stateless.sol index 846c77d98..0bc89e4ce 100644 --- a/contracts/mocks/Stateless.sol +++ b/contracts/mocks/Stateless.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity ^0.8.24; // We keep these imports and a dummy contract just to we can run the test suite after transpilation. @@ -9,6 +9,9 @@ import {Arrays} from "../utils/Arrays.sol"; import {AuthorityUtils} from "../access/manager/AuthorityUtils.sol"; import {Base64} from "../utils/Base64.sol"; import {BitMaps} from "../utils/structs/BitMaps.sol"; +import {Bytes} from "../utils/Bytes.sol"; +import {CAIP2} from "../utils/CAIP2.sol"; +import {CAIP10} from "../utils/CAIP10.sol"; import {Checkpoints} from "../utils/structs/Checkpoints.sol"; import {CircularBuffer} from "../utils/structs/CircularBuffer.sol"; import {Clones} from "../proxy/Clones.sol"; @@ -22,10 +25,14 @@ import {ERC165} from "../utils/introspection/ERC165.sol"; import {ERC165Checker} from "../utils/introspection/ERC165Checker.sol"; import {ERC1967Utils} from "../proxy/ERC1967/ERC1967Utils.sol"; import {ERC721Holder} from "../token/ERC721/utils/ERC721Holder.sol"; +import {ERC4337Utils} from "../account/utils/draft-ERC4337Utils.sol"; +import {ERC7579Utils} from "../account/utils/draft-ERC7579Utils.sol"; import {Heap} from "../utils/structs/Heap.sol"; import {Math} from "../utils/math/Math.sol"; import {MerkleProof} from "../utils/cryptography/MerkleProof.sol"; import {MessageHashUtils} from "../utils/cryptography/MessageHashUtils.sol"; +import {Nonces} from "../utils/Nonces.sol"; +import {NoncesKeyed} from "../utils/NoncesKeyed.sol"; import {P256} from "../utils/cryptography/P256.sol"; import {Panic} from "../utils/Panic.sol"; import {Packing} from "../utils/Packing.sol"; diff --git a/contracts/mocks/VotesExtendedMock.sol b/contracts/mocks/VotesExtendedMock.sol new file mode 100644 index 000000000..9c456190e --- /dev/null +++ b/contracts/mocks/VotesExtendedMock.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {VotesExtended} from "../governance/utils/VotesExtended.sol"; + +abstract contract VotesExtendedMock is VotesExtended { + 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 VotesExtendedTimestampMock is VotesExtendedMock { + 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/account/utils/ERC7579UtilsMock.sol b/contracts/mocks/account/utils/ERC7579UtilsMock.sol new file mode 100644 index 000000000..e0a1e1a50 --- /dev/null +++ b/contracts/mocks/account/utils/ERC7579UtilsMock.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {CallType, ExecType, ModeSelector, ModePayload} from "../../../account/utils/draft-ERC7579Utils.sol"; + +contract ERC7579UtilsGlobalMock { + function eqCallTypeGlobal(CallType callType1, CallType callType2) internal pure returns (bool) { + return callType1 == callType2; + } + + function eqExecTypeGlobal(ExecType execType1, ExecType execType2) internal pure returns (bool) { + return execType1 == execType2; + } + + function eqModeSelectorGlobal(ModeSelector modeSelector1, ModeSelector modeSelector2) internal pure returns (bool) { + return modeSelector1 == modeSelector2; + } + + function eqModePayloadGlobal(ModePayload modePayload1, ModePayload modePayload2) internal pure returns (bool) { + return modePayload1 == modePayload2; + } +} diff --git a/contracts/mocks/docs/access-control/AccessControlUnrevokableAdmin.sol b/contracts/mocks/docs/access-control/AccessControlNonRevokableAdmin.sol similarity index 100% rename from contracts/mocks/docs/access-control/AccessControlUnrevokableAdmin.sol rename to contracts/mocks/docs/access-control/AccessControlNonRevokableAdmin.sol diff --git a/contracts/mocks/governance/GovernorCountingOverridableMock.sol b/contracts/mocks/governance/GovernorCountingOverridableMock.sol new file mode 100644 index 000000000..bae09d933 --- /dev/null +++ b/contracts/mocks/governance/GovernorCountingOverridableMock.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {Governor} from "../../governance/Governor.sol"; +import {GovernorSettings} from "../../governance/extensions/GovernorSettings.sol"; +import {GovernorVotesQuorumFraction} from "../../governance/extensions/GovernorVotesQuorumFraction.sol"; +import {GovernorCountingOverridable, VotesExtended} from "../../governance/extensions/GovernorCountingOverridable.sol"; + +abstract contract GovernorCountingOverridableMock is + GovernorSettings, + GovernorVotesQuorumFraction, + GovernorCountingOverridable +{ + 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 index fde0863ce..176976f91 100644 --- a/contracts/mocks/governance/GovernorPreventLateQuorumMock.sol +++ b/contracts/mocks/governance/GovernorPreventLateQuorumMock.sol @@ -34,13 +34,7 @@ abstract contract GovernorPreventLateQuorumMock is 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); + function _tallyUpdated(uint256 proposalId) internal override(Governor, GovernorPreventLateQuorum) { + super._tallyUpdated(proposalId); } } diff --git a/contracts/mocks/governance/GovernorStorageMock.sol b/contracts/mocks/governance/GovernorStorageMock.sol index 88c6bf906..26e0e10b5 100644 --- a/contracts/mocks/governance/GovernorStorageMock.sol +++ b/contracts/mocks/governance/GovernorStorageMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {IGovernor, Governor} from "../../governance/Governor.sol"; import {GovernorTimelockControl} from "../../governance/extensions/GovernorTimelockControl.sol"; diff --git a/contracts/mocks/proxy/UUPSUpgradeableMock.sol b/contracts/mocks/proxy/UUPSUpgradeableMock.sol index a5f2d4a25..8c5641e6c 100644 --- a/contracts/mocks/proxy/UUPSUpgradeableMock.sol +++ b/contracts/mocks/proxy/UUPSUpgradeableMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity ^0.8.22; import {UUPSUpgradeable} from "../../proxy/utils/UUPSUpgradeable.sol"; import {ERC1967Utils} from "../../proxy/ERC1967/ERC1967Utils.sol"; diff --git a/contracts/mocks/token/ERC20VotesAdditionalCheckpointsMock.sol b/contracts/mocks/token/ERC20VotesAdditionalCheckpointsMock.sol new file mode 100644 index 000000000..39b3c654b --- /dev/null +++ b/contracts/mocks/token/ERC20VotesAdditionalCheckpointsMock.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {ERC20Votes} from "../../token/ERC20/extensions/ERC20Votes.sol"; +import {VotesExtended, Votes} from "../../governance/utils/VotesExtended.sol"; +import {SafeCast} from "../../utils/math/SafeCast.sol"; + +abstract contract ERC20VotesExtendedMock is ERC20Votes, VotesExtended { + function _delegate(address account, address delegatee) internal virtual override(Votes, VotesExtended) { + return super._delegate(account, delegatee); + } + + function _transferVotingUnits( + address from, + address to, + uint256 amount + ) internal virtual override(Votes, VotesExtended) { + return super._transferVotingUnits(from, to, amount); + } +} + +abstract contract ERC20VotesExtendedTimestampMock is ERC20VotesExtendedMock { + 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 index e0ed163d0..3682eadeb 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -1,7 +1,7 @@ { "name": "@openzeppelin/contracts", "description": "Secure Smart Contract library for Solidity", - "version": "5.1.0", + "version": "5.2.0", "files": [ "**/*.sol", "/build/contracts/*.json", diff --git a/contracts/proxy/Clones.sol b/contracts/proxy/Clones.sol index f276130b9..6b8d9cc6f 100644 --- a/contracts/proxy/Clones.sol +++ b/contracts/proxy/Clones.sol @@ -1,8 +1,9 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.1.0) (proxy/Clones.sol) +// OpenZeppelin Contracts (last updated v5.2.0) (proxy/Clones.sol) pragma solidity ^0.8.20; +import {Create2} from "../utils/Create2.sol"; import {Errors} from "../utils/Errors.sol"; /** @@ -17,6 +18,8 @@ import {Errors} from "../utils/Errors.sol"; * deterministic method. */ library Clones { + error CloneArgumentsTooLong(); + /** * @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`. * @@ -54,7 +57,7 @@ library Clones { * @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 clone. Using the same `implementation` and `salt` multiple times will revert, since * the clones cannot be deployed twice at the same address. */ function cloneDeterministic(address implementation, bytes32 salt) internal returns (address instance) { @@ -118,4 +121,142 @@ library Clones { ) internal view returns (address predicted) { return predictDeterministicAddress(implementation, salt, address(this)); } + + /** + * @dev Deploys and returns the address of a clone that mimics the behavior of `implementation` with custom + * immutable arguments. These are provided through `args` and cannot be changed after deployment. To + * access the arguments within the implementation, use {fetchCloneArgs}. + * + * This function uses the create opcode, which should never revert. + */ + function cloneWithImmutableArgs(address implementation, bytes memory args) internal returns (address instance) { + return cloneWithImmutableArgs(implementation, args, 0); + } + + /** + * @dev Same as {xref-Clones-cloneWithImmutableArgs-address-bytes-}[cloneWithImmutableArgs], but with a `value` + * parameter to send native currency to the new contract. + * + * NOTE: Using a non-zero value at creation will require the contract using this function (e.g. a factory) + * to always have enough balance for new deployments. Consider exposing this function under a payable method. + */ + function cloneWithImmutableArgs( + address implementation, + bytes memory args, + uint256 value + ) internal returns (address instance) { + if (address(this).balance < value) { + revert Errors.InsufficientBalance(address(this).balance, value); + } + bytes memory bytecode = _cloneCodeWithImmutableArgs(implementation, args); + assembly ("memory-safe") { + instance := create(value, add(bytecode, 0x20), mload(bytecode)) + } + if (instance == address(0)) { + revert Errors.FailedDeployment(); + } + } + + /** + * @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation` with custom + * immutable arguments. These are provided through `args` and cannot be changed after deployment. To + * access the arguments within the implementation, use {fetchCloneArgs}. + * + * This function uses the create2 opcode and a `salt` to deterministically deploy the clone. Using the same + * `implementation`, `args` and `salt` multiple times will revert, since the clones cannot be deployed twice + * at the same address. + */ + function cloneDeterministicWithImmutableArgs( + address implementation, + bytes memory args, + bytes32 salt + ) internal returns (address instance) { + return cloneDeterministicWithImmutableArgs(implementation, args, salt, 0); + } + + /** + * @dev Same as {xref-Clones-cloneDeterministicWithImmutableArgs-address-bytes-bytes32-}[cloneDeterministicWithImmutableArgs], + * but with a `value` parameter to send native currency to the new contract. + * + * NOTE: Using a non-zero value at creation will require the contract using this function (e.g. a factory) + * to always have enough balance for new deployments. Consider exposing this function under a payable method. + */ + function cloneDeterministicWithImmutableArgs( + address implementation, + bytes memory args, + bytes32 salt, + uint256 value + ) internal returns (address instance) { + bytes memory bytecode = _cloneCodeWithImmutableArgs(implementation, args); + return Create2.deploy(value, salt, bytecode); + } + + /** + * @dev Computes the address of a clone deployed using {Clones-cloneDeterministicWithImmutableArgs}. + */ + function predictDeterministicAddressWithImmutableArgs( + address implementation, + bytes memory args, + bytes32 salt, + address deployer + ) internal pure returns (address predicted) { + bytes memory bytecode = _cloneCodeWithImmutableArgs(implementation, args); + return Create2.computeAddress(salt, keccak256(bytecode), deployer); + } + + /** + * @dev Computes the address of a clone deployed using {Clones-cloneDeterministicWithImmutableArgs}. + */ + function predictDeterministicAddressWithImmutableArgs( + address implementation, + bytes memory args, + bytes32 salt + ) internal view returns (address predicted) { + return predictDeterministicAddressWithImmutableArgs(implementation, args, salt, address(this)); + } + + /** + * @dev Get the immutable args attached to a clone. + * + * - If `instance` is a clone that was deployed using `clone` or `cloneDeterministic`, this + * function will return an empty array. + * - If `instance` is a clone that was deployed using `cloneWithImmutableArgs` or + * `cloneDeterministicWithImmutableArgs`, this function will return the args array used at + * creation. + * - If `instance` is NOT a clone deployed using this library, the behavior is undefined. This + * function should only be used to check addresses that are known to be clones. + */ + function fetchCloneArgs(address instance) internal view returns (bytes memory) { + bytes memory result = new bytes(instance.code.length - 45); // revert if length is too short + assembly ("memory-safe") { + extcodecopy(instance, add(result, 32), 45, mload(result)) + } + return result; + } + + /** + * @dev Helper that prepares the initcode of the proxy with immutable args. + * + * An assembly variant of this function requires copying the `args` array, which can be efficiently done using + * `mcopy`. Unfortunately, that opcode is not available before cancun. A pure solidity implementation using + * abi.encodePacked is more expensive but also more portable and easier to review. + * + * NOTE: https://eips.ethereum.org/EIPS/eip-170[EIP-170] limits the length of the contract code to 24576 bytes. + * With the proxy code taking 45 bytes, that limits the length of the immutable args to 24531 bytes. + */ + function _cloneCodeWithImmutableArgs( + address implementation, + bytes memory args + ) private pure returns (bytes memory) { + if (args.length > 24531) revert CloneArgumentsTooLong(); + return + abi.encodePacked( + hex"61", + uint16(args.length + 45), + hex"3d81600a3d39f3363d3d373d3d3d363d73", + implementation, + hex"5af43d82803e903d91602b57fd5bf3", + args + ); + } } diff --git a/contracts/proxy/ERC1967/ERC1967Proxy.sol b/contracts/proxy/ERC1967/ERC1967Proxy.sol index 4f51cd957..eb482f6ec 100644 --- a/contracts/proxy/ERC1967/ERC1967Proxy.sol +++ b/contracts/proxy/ERC1967/ERC1967Proxy.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.1.0) (proxy/ERC1967/ERC1967Proxy.sol) +// OpenZeppelin Contracts (last updated v5.2.0) (proxy/ERC1967/ERC1967Proxy.sol) -pragma solidity ^0.8.20; +pragma solidity ^0.8.22; import {Proxy} from "../Proxy.sol"; import {ERC1967Utils} from "./ERC1967Utils.sol"; diff --git a/contracts/proxy/ERC1967/ERC1967Utils.sol b/contracts/proxy/ERC1967/ERC1967Utils.sol index 1f3201352..73fe697f3 100644 --- a/contracts/proxy/ERC1967/ERC1967Utils.sol +++ b/contracts/proxy/ERC1967/ERC1967Utils.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.1.0) (proxy/ERC1967/ERC1967Utils.sol) +// OpenZeppelin Contracts (last updated v5.2.0) (proxy/ERC1967/ERC1967Utils.sol) -pragma solidity ^0.8.21; +pragma solidity ^0.8.22; import {IBeacon} from "../beacon/IBeacon.sol"; import {IERC1967} from "../../interfaces/IERC1967.sol"; diff --git a/contracts/proxy/beacon/BeaconProxy.sol b/contracts/proxy/beacon/BeaconProxy.sol index 2606f21db..36558d67b 100644 --- a/contracts/proxy/beacon/BeaconProxy.sol +++ b/contracts/proxy/beacon/BeaconProxy.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.1.0) (proxy/beacon/BeaconProxy.sol) +// OpenZeppelin Contracts (last updated v5.2.0) (proxy/beacon/BeaconProxy.sol) -pragma solidity ^0.8.20; +pragma solidity ^0.8.22; import {IBeacon} from "./IBeacon.sol"; import {Proxy} from "../Proxy.sol"; diff --git a/contracts/proxy/transparent/ProxyAdmin.sol b/contracts/proxy/transparent/ProxyAdmin.sol index 317723503..eefd49a80 100644 --- a/contracts/proxy/transparent/ProxyAdmin.sol +++ b/contracts/proxy/transparent/ProxyAdmin.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.1.0) (proxy/transparent/ProxyAdmin.sol) +// OpenZeppelin Contracts (last updated v5.2.0) (proxy/transparent/ProxyAdmin.sol) -pragma solidity ^0.8.20; +pragma solidity ^0.8.22; import {ITransparentUpgradeableProxy} from "./TransparentUpgradeableProxy.sol"; import {Ownable} from "../../access/Ownable.sol"; diff --git a/contracts/proxy/transparent/TransparentUpgradeableProxy.sol b/contracts/proxy/transparent/TransparentUpgradeableProxy.sol index a35a725f2..21af0e315 100644 --- a/contracts/proxy/transparent/TransparentUpgradeableProxy.sol +++ b/contracts/proxy/transparent/TransparentUpgradeableProxy.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.1.0) (proxy/transparent/TransparentUpgradeableProxy.sol) +// OpenZeppelin Contracts (last updated v5.2.0) (proxy/transparent/TransparentUpgradeableProxy.sol) -pragma solidity ^0.8.20; +pragma solidity ^0.8.22; import {ERC1967Utils} from "../ERC1967/ERC1967Utils.sol"; import {ERC1967Proxy} from "../ERC1967/ERC1967Proxy.sol"; diff --git a/contracts/proxy/utils/UUPSUpgradeable.sol b/contracts/proxy/utils/UUPSUpgradeable.sol index dc799962c..4719c023b 100644 --- a/contracts/proxy/utils/UUPSUpgradeable.sol +++ b/contracts/proxy/utils/UUPSUpgradeable.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.1.0) (proxy/utils/UUPSUpgradeable.sol) +// OpenZeppelin Contracts (last updated v5.2.0) (proxy/utils/UUPSUpgradeable.sol) -pragma solidity ^0.8.20; +pragma solidity ^0.8.22; import {IERC1822Proxiable} from "../../interfaces/draft-IERC1822.sol"; import {ERC1967Utils} from "../ERC1967/ERC1967Utils.sol"; diff --git a/contracts/token/ERC20/ERC20.sol b/contracts/token/ERC20/ERC20.sol index 0b707604c..471908d6c 100644 --- a/contracts/token/ERC20/ERC20.sol +++ b/contracts/token/ERC20/ERC20.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/ERC20.sol) +// OpenZeppelin Contracts (last updated v5.2.0) (token/ERC20/ERC20.sol) pragma solidity ^0.8.20; @@ -300,7 +300,7 @@ abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors { */ function _spendAllowance(address owner, address spender, uint256 value) internal virtual { uint256 currentAllowance = allowance(owner, spender); - if (currentAllowance != type(uint256).max) { + if (currentAllowance < type(uint256).max) { if (currentAllowance < value) { revert ERC20InsufficientAllowance(spender, currentAllowance, value); } diff --git a/contracts/token/ERC20/extensions/ERC1363.sol b/contracts/token/ERC20/extensions/ERC1363.sol index acc841d78..30ffd0f08 100644 --- a/contracts/token/ERC20/extensions/ERC1363.sol +++ b/contracts/token/ERC20/extensions/ERC1363.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/extensions/ERC1363.sol) +// OpenZeppelin Contracts (last updated v5.2.0) (token/ERC20/extensions/ERC1363.sol) pragma solidity ^0.8.20; @@ -48,7 +48,8 @@ abstract contract ERC1363 is ERC20, ERC165, IERC1363 { /** * @dev Moves a `value` amount of tokens from the caller's account to `to` - * and then calls {IERC1363Receiver-onTransferReceived} on `to`. + * and then calls {IERC1363Receiver-onTransferReceived} on `to`. Returns a flag that indicates + * if the call succeeded. * * Requirements: * @@ -75,7 +76,8 @@ abstract contract ERC1363 is ERC20, ERC165, IERC1363 { /** * @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism - * and then calls {IERC1363Receiver-onTransferReceived} on `to`. + * and then calls {IERC1363Receiver-onTransferReceived} on `to`. Returns a flag that indicates + * if the call succeeded. * * Requirements: * @@ -108,6 +110,7 @@ abstract contract ERC1363 is ERC20, ERC165, IERC1363 { /** * @dev Sets a `value` amount of tokens as the allowance of `spender` over the * caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`. + * Returns a flag that indicates if the call succeeded. * * Requirements: * diff --git a/contracts/token/ERC20/utils/SafeERC20.sol b/contracts/token/ERC20/utils/SafeERC20.sol index eb2f903fb..edac165bc 100644 --- a/contracts/token/ERC20/utils/SafeERC20.sol +++ b/contracts/token/ERC20/utils/SafeERC20.sol @@ -1,11 +1,10 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/utils/SafeERC20.sol) +// OpenZeppelin Contracts (last updated v5.2.0) (token/ERC20/utils/SafeERC20.sol) pragma solidity ^0.8.20; import {IERC20} from "../IERC20.sol"; import {IERC1363} from "../../../interfaces/IERC1363.sol"; -import {Address} from "../../../utils/Address.sol"; /** * @title SafeERC20 diff --git a/contracts/utils/Address.sol b/contracts/utils/Address.sol index a1c8af296..696f94ffa 100644 --- a/contracts/utils/Address.sol +++ b/contracts/utils/Address.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.1.0) (utils/Address.sol) +// OpenZeppelin Contracts (last updated v5.2.0) (utils/Address.sol) pragma solidity ^0.8.20; @@ -35,9 +35,9 @@ library Address { revert Errors.InsufficientBalance(address(this).balance, amount); } - (bool success, ) = recipient.call{value: amount}(""); + (bool success, bytes memory returndata) = recipient.call{value: amount}(""); if (!success) { - revert Errors.FailedCall(); + _revert(returndata); } } diff --git a/contracts/utils/Bytes.sol b/contracts/utils/Bytes.sol new file mode 100644 index 000000000..f0708507f --- /dev/null +++ b/contracts/utils/Bytes.sol @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.2.0) (utils/Bytes.sol) + +pragma solidity ^0.8.24; + +import {Math} from "./math/Math.sol"; + +/** + * @dev Bytes operations. + */ +library Bytes { + /** + * @dev Forward search for `s` in `buffer` + * * If `s` is present in the buffer, returns the index of the first instance + * * If `s` is not present in the buffer, returns type(uint256).max + * + * NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf[Javascript's `Array.indexOf`] + */ + function indexOf(bytes memory buffer, bytes1 s) internal pure returns (uint256) { + return indexOf(buffer, s, 0); + } + + /** + * @dev Forward search for `s` in `buffer` starting at position `pos` + * * If `s` is present in the buffer (at or after `pos`), returns the index of the next instance + * * If `s` is not present in the buffer (at or after `pos`), returns type(uint256).max + * + * NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf[Javascript's `Array.indexOf`] + */ + function indexOf(bytes memory buffer, bytes1 s, uint256 pos) internal pure returns (uint256) { + uint256 length = buffer.length; + for (uint256 i = pos; i < length; ++i) { + if (bytes1(_unsafeReadBytesOffset(buffer, i)) == s) { + return i; + } + } + return type(uint256).max; + } + + /** + * @dev Backward search for `s` in `buffer` + * * If `s` is present in the buffer, returns the index of the last instance + * * If `s` is not present in the buffer, returns type(uint256).max + * + * NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/lastIndexOf[Javascript's `Array.lastIndexOf`] + */ + function lastIndexOf(bytes memory buffer, bytes1 s) internal pure returns (uint256) { + return lastIndexOf(buffer, s, type(uint256).max); + } + + /** + * @dev Backward search for `s` in `buffer` starting at position `pos` + * * If `s` is present in the buffer (at or before `pos`), returns the index of the previous instance + * * If `s` is not present in the buffer (at or before `pos`), returns type(uint256).max + * + * NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/lastIndexOf[Javascript's `Array.lastIndexOf`] + */ + function lastIndexOf(bytes memory buffer, bytes1 s, uint256 pos) internal pure returns (uint256) { + unchecked { + uint256 length = buffer.length; + // NOTE here we cannot do `i = Math.min(pos + 1, length)` because `pos + 1` could overflow + for (uint256 i = Math.min(pos, length - 1) + 1; i > 0; --i) { + if (bytes1(_unsafeReadBytesOffset(buffer, i - 1)) == s) { + return i - 1; + } + } + return type(uint256).max; + } + } + + /** + * @dev Copies the content of `buffer`, from `start` (included) to the end of `buffer` into a new bytes object in + * memory. + * + * NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice[Javascript's `Array.slice`] + */ + function slice(bytes memory buffer, uint256 start) internal pure returns (bytes memory) { + return slice(buffer, start, buffer.length); + } + + /** + * @dev Copies the content of `buffer`, from `start` (included) to `end` (excluded) into a new bytes object in + * memory. + * + * NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice[Javascript's `Array.slice`] + */ + function slice(bytes memory buffer, uint256 start, uint256 end) internal pure returns (bytes memory) { + // sanitize + uint256 length = buffer.length; + end = Math.min(end, length); + start = Math.min(start, end); + + // allocate and copy + bytes memory result = new bytes(end - start); + assembly ("memory-safe") { + mcopy(add(result, 0x20), add(buffer, add(start, 0x20)), sub(end, start)) + } + + return result; + } + + /** + * @dev Reads a bytes32 from a bytes array without bounds checking. + * + * NOTE: making this function internal would mean it could be used with memory unsafe offset, and marking the + * assembly block as such would prevent some optimizations. + */ + function _unsafeReadBytesOffset(bytes memory buffer, uint256 offset) private pure returns (bytes32 value) { + // This is not memory safe in the general case, but all calls to this private function are within bounds. + assembly ("memory-safe") { + value := mload(add(buffer, add(0x20, offset))) + } + } +} diff --git a/contracts/utils/CAIP10.sol b/contracts/utils/CAIP10.sol new file mode 100644 index 000000000..84b35da04 --- /dev/null +++ b/contracts/utils/CAIP10.sol @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.2.0) (utils/CAIP10.sol) + +pragma solidity ^0.8.24; + +import {Bytes} from "./Bytes.sol"; +import {Strings} from "./Strings.sol"; +import {CAIP2} from "./CAIP2.sol"; + +/** + * @dev Helper library to format and parse CAIP-10 identifiers + * + * https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-10.md[CAIP-10] defines account identifiers as: + * account_id: chain_id + ":" + account_address + * chain_id: [-a-z0-9]{3,8}:[-_a-zA-Z0-9]{1,32} (See {CAIP2}) + * account_address: [-.%a-zA-Z0-9]{1,128} + * + * WARNING: According to [CAIP-10's canonicalization section](https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-10.md#canonicalization), + * the implementation remains at the developer's discretion. Please note that case variations may introduce ambiguity. + * For example, when building hashes to identify accounts or data associated to them, multiple representations of the + * same account would derive to different hashes. For EVM chains, we recommend using checksummed addresses for the + * "account_address" part. They can be generated onchain using {Strings-toChecksumHexString}. + */ +library CAIP10 { + using Strings for address; + using Bytes for bytes; + + /// @dev Return the CAIP-10 identifier for an account on the current (local) chain. + function local(address account) internal view returns (string memory) { + return format(CAIP2.local(), account.toChecksumHexString()); + } + + /** + * @dev Return the CAIP-10 identifier for a given caip2 chain and account. + * + * NOTE: This function does not verify that the inputs are properly formatted. + */ + function format(string memory caip2, string memory account) internal pure returns (string memory) { + return string.concat(caip2, ":", account); + } + + /** + * @dev Parse a CAIP-10 identifier into its components. + * + * NOTE: This function does not verify that the CAIP-10 input is properly formatted. The `caip2` return can be + * parsed using the {CAIP2} library. + */ + function parse(string memory caip10) internal pure returns (string memory caip2, string memory account) { + bytes memory buffer = bytes(caip10); + + uint256 pos = buffer.lastIndexOf(":"); + return (string(buffer.slice(0, pos)), string(buffer.slice(pos + 1))); + } +} diff --git a/contracts/utils/CAIP2.sol b/contracts/utils/CAIP2.sol new file mode 100644 index 000000000..d06dd6da7 --- /dev/null +++ b/contracts/utils/CAIP2.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.2.0) (utils/CAIP2.sol) + +pragma solidity ^0.8.24; + +import {Bytes} from "./Bytes.sol"; +import {Strings} from "./Strings.sol"; + +/** + * @dev Helper library to format and parse CAIP-2 identifiers + * + * https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-2.md[CAIP-2] defines chain identifiers as: + * chain_id: namespace + ":" + reference + * namespace: [-a-z0-9]{3,8} + * reference: [-_a-zA-Z0-9]{1,32} + * + * WARNING: In some cases, multiple CAIP-2 identifiers may all be valid representation of a single chain. + * For EVM chains, it is recommended to use `eip155:xxx` as the canonical representation (where `xxx` is + * the EIP-155 chain id). Consider the possible ambiguity when processing CAIP-2 identifiers or when using them + * in the context of hashes. + */ +library CAIP2 { + using Strings for uint256; + using Bytes for bytes; + + /// @dev Return the CAIP-2 identifier for the current (local) chain. + function local() internal view returns (string memory) { + return format("eip155", block.chainid.toString()); + } + + /** + * @dev Return the CAIP-2 identifier for a given namespace and reference. + * + * NOTE: This function does not verify that the inputs are properly formatted. + */ + function format(string memory namespace, string memory ref) internal pure returns (string memory) { + return string.concat(namespace, ":", ref); + } + + /** + * @dev Parse a CAIP-2 identifier into its components. + * + * NOTE: This function does not verify that the CAIP-2 input is properly formatted. + */ + function parse(string memory caip2) internal pure returns (string memory namespace, string memory ref) { + bytes memory buffer = bytes(caip2); + + uint256 pos = buffer.indexOf(":"); + return (string(buffer.slice(0, pos)), string(buffer.slice(pos + 1))); + } +} diff --git a/contracts/utils/NoncesKeyed.sol b/contracts/utils/NoncesKeyed.sol new file mode 100644 index 000000000..df9c5704b --- /dev/null +++ b/contracts/utils/NoncesKeyed.sol @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.2.0) (utils/NoncesKeyed.sol) +pragma solidity ^0.8.20; + +import {Nonces} from "./Nonces.sol"; + +/** + * @dev Alternative to {Nonces}, that supports key-ed nonces. + * + * Follows the https://eips.ethereum.org/EIPS/eip-4337#semi-abstracted-nonce-support[ERC-4337's semi-abstracted nonce system]. + * + * NOTE: This contract inherits from {Nonces} and reuses its storage for the first nonce key (i.e. `0`). This + * makes upgrading from {Nonces} to {NoncesKeyed} safe when using their upgradeable versions (e.g. `NoncesKeyedUpgradeable`). + * Doing so will NOT reset the current state of nonces, avoiding replay attacks where a nonce is reused after the upgrade. + */ +abstract contract NoncesKeyed is Nonces { + mapping(address owner => mapping(uint192 key => uint64)) private _nonces; + + /// @dev Returns the next unused nonce for an address and key. Result contains the key prefix. + function nonces(address owner, uint192 key) public view virtual returns (uint256) { + return key == 0 ? nonces(owner) : _pack(key, _nonces[owner][key]); + } + + /** + * @dev Consumes the next unused nonce for an address and key. + * + * Returns the current value without the key prefix. Consumed nonce is increased, so calling this function twice + * with the same arguments will return different (sequential) results. + */ + function _useNonce(address owner, uint192 key) 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 key == 0 ? _useNonce(owner) : _pack(key, _nonces[owner][key]++); + } + } + + /** + * @dev Same as {_useNonce} but checking that `nonce` is the next valid for `owner`. + * + * This version takes the key and the nonce in a single uint256 parameter: + * - use the first 24 bytes for the key + * - use the last 8 bytes for the nonce + */ + function _useCheckedNonce(address owner, uint256 keyNonce) internal virtual override { + (uint192 key, ) = _unpack(keyNonce); + if (key == 0) { + super._useCheckedNonce(owner, keyNonce); + } else { + uint256 current = _useNonce(owner, key); + if (keyNonce != current) revert InvalidAccountNonce(owner, current); + } + } + + /** + * @dev Same as {_useNonce} but checking that `nonce` is the next valid for `owner`. + * + * This version takes the key and the nonce as two different parameters. + */ + function _useCheckedNonce(address owner, uint192 key, uint64 nonce) internal virtual { + _useCheckedNonce(owner, _pack(key, nonce)); + } + + /// @dev Pack key and nonce into a keyNonce + function _pack(uint192 key, uint64 nonce) private pure returns (uint256) { + return (uint256(key) << 64) | nonce; + } + + /// @dev Unpack a keyNonce into its key and nonce components + function _unpack(uint256 keyNonce) private pure returns (uint192 key, uint64 nonce) { + return (uint192(keyNonce >> 64), uint64(keyNonce)); + } +} diff --git a/contracts/utils/Packing.sol b/contracts/utils/Packing.sol index 069153bef..f7c5d6fcc 100644 --- a/contracts/utils/Packing.sol +++ b/contracts/utils/Packing.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.1.0) (utils/Packing.sol) +// OpenZeppelin Contracts (last updated v5.2.0) (utils/Packing.sol) // This file was procedurally generated from scripts/generate/templates/Packing.js. pragma solidity ^0.8.20; @@ -68,6 +68,38 @@ library Packing { } } + function pack_2_8(bytes2 left, bytes8 right) internal pure returns (bytes10 result) { + assembly ("memory-safe") { + left := and(left, shl(240, not(0))) + right := and(right, shl(192, not(0))) + result := or(left, shr(16, right)) + } + } + + function pack_2_10(bytes2 left, bytes10 right) internal pure returns (bytes12 result) { + assembly ("memory-safe") { + left := and(left, shl(240, not(0))) + right := and(right, shl(176, not(0))) + result := or(left, shr(16, right)) + } + } + + function pack_2_20(bytes2 left, bytes20 right) internal pure returns (bytes22 result) { + assembly ("memory-safe") { + left := and(left, shl(240, not(0))) + right := and(right, shl(96, not(0))) + result := or(left, shr(16, right)) + } + } + + function pack_2_22(bytes2 left, bytes22 right) internal pure returns (bytes24 result) { + assembly ("memory-safe") { + left := and(left, shl(240, not(0))) + right := and(right, shl(80, not(0))) + result := or(left, shr(16, right)) + } + } + function pack_4_2(bytes4 left, bytes2 right) internal pure returns (bytes6 result) { assembly ("memory-safe") { left := and(left, shl(224, not(0))) @@ -84,6 +116,14 @@ library Packing { } } + function pack_4_6(bytes4 left, bytes6 right) internal pure returns (bytes10 result) { + assembly ("memory-safe") { + left := and(left, shl(224, not(0))) + right := and(right, shl(208, not(0))) + result := or(left, shr(32, right)) + } + } + function pack_4_8(bytes4 left, bytes8 right) internal pure returns (bytes12 result) { assembly ("memory-safe") { left := and(left, shl(224, not(0))) @@ -140,6 +180,14 @@ library Packing { } } + function pack_6_4(bytes6 left, bytes4 right) internal pure returns (bytes10 result) { + assembly ("memory-safe") { + left := and(left, shl(208, not(0))) + right := and(right, shl(224, not(0))) + result := or(left, shr(48, right)) + } + } + function pack_6_6(bytes6 left, bytes6 right) internal pure returns (bytes12 result) { assembly ("memory-safe") { left := and(left, shl(208, not(0))) @@ -148,6 +196,38 @@ library Packing { } } + function pack_6_10(bytes6 left, bytes10 right) internal pure returns (bytes16 result) { + assembly ("memory-safe") { + left := and(left, shl(208, not(0))) + right := and(right, shl(176, not(0))) + result := or(left, shr(48, right)) + } + } + + function pack_6_16(bytes6 left, bytes16 right) internal pure returns (bytes22 result) { + assembly ("memory-safe") { + left := and(left, shl(208, not(0))) + right := and(right, shl(128, not(0))) + result := or(left, shr(48, right)) + } + } + + function pack_6_22(bytes6 left, bytes22 right) internal pure returns (bytes28 result) { + assembly ("memory-safe") { + left := and(left, shl(208, not(0))) + right := and(right, shl(80, not(0))) + result := or(left, shr(48, right)) + } + } + + function pack_8_2(bytes8 left, bytes2 right) internal pure returns (bytes10 result) { + assembly ("memory-safe") { + left := and(left, shl(192, not(0))) + right := and(right, shl(240, not(0))) + result := or(left, shr(64, right)) + } + } + function pack_8_4(bytes8 left, bytes4 right) internal pure returns (bytes12 result) { assembly ("memory-safe") { left := and(left, shl(192, not(0))) @@ -196,6 +276,46 @@ library Packing { } } + function pack_10_2(bytes10 left, bytes2 right) internal pure returns (bytes12 result) { + assembly ("memory-safe") { + left := and(left, shl(176, not(0))) + right := and(right, shl(240, not(0))) + result := or(left, shr(80, right)) + } + } + + function pack_10_6(bytes10 left, bytes6 right) internal pure returns (bytes16 result) { + assembly ("memory-safe") { + left := and(left, shl(176, not(0))) + right := and(right, shl(208, not(0))) + result := or(left, shr(80, right)) + } + } + + function pack_10_10(bytes10 left, bytes10 right) internal pure returns (bytes20 result) { + assembly ("memory-safe") { + left := and(left, shl(176, not(0))) + right := and(right, shl(176, not(0))) + result := or(left, shr(80, right)) + } + } + + function pack_10_12(bytes10 left, bytes12 right) internal pure returns (bytes22 result) { + assembly ("memory-safe") { + left := and(left, shl(176, not(0))) + right := and(right, shl(160, not(0))) + result := or(left, shr(80, right)) + } + } + + function pack_10_22(bytes10 left, bytes22 right) internal pure returns (bytes32 result) { + assembly ("memory-safe") { + left := and(left, shl(176, not(0))) + right := and(right, shl(80, not(0))) + result := or(left, shr(80, right)) + } + } + function pack_12_4(bytes12 left, bytes4 right) internal pure returns (bytes16 result) { assembly ("memory-safe") { left := and(left, shl(160, not(0))) @@ -212,6 +332,14 @@ library Packing { } } + function pack_12_10(bytes12 left, bytes10 right) internal pure returns (bytes22 result) { + assembly ("memory-safe") { + left := and(left, shl(160, not(0))) + right := and(right, shl(176, not(0))) + result := or(left, shr(96, right)) + } + } + function pack_12_12(bytes12 left, bytes12 right) internal pure returns (bytes24 result) { assembly ("memory-safe") { left := and(left, shl(160, not(0))) @@ -244,6 +372,14 @@ library Packing { } } + function pack_16_6(bytes16 left, bytes6 right) internal pure returns (bytes22 result) { + assembly ("memory-safe") { + left := and(left, shl(128, not(0))) + right := and(right, shl(208, not(0))) + result := or(left, shr(128, right)) + } + } + function pack_16_8(bytes16 left, bytes8 right) internal pure returns (bytes24 result) { assembly ("memory-safe") { left := and(left, shl(128, not(0))) @@ -268,6 +404,14 @@ library Packing { } } + function pack_20_2(bytes20 left, bytes2 right) internal pure returns (bytes22 result) { + assembly ("memory-safe") { + left := and(left, shl(96, not(0))) + right := and(right, shl(240, not(0))) + result := or(left, shr(160, right)) + } + } + function pack_20_4(bytes20 left, bytes4 right) internal pure returns (bytes24 result) { assembly ("memory-safe") { left := and(left, shl(96, not(0))) @@ -292,6 +436,30 @@ library Packing { } } + function pack_22_2(bytes22 left, bytes2 right) internal pure returns (bytes24 result) { + assembly ("memory-safe") { + left := and(left, shl(80, not(0))) + right := and(right, shl(240, not(0))) + result := or(left, shr(176, right)) + } + } + + function pack_22_6(bytes22 left, bytes6 right) internal pure returns (bytes28 result) { + assembly ("memory-safe") { + left := and(left, shl(80, not(0))) + right := and(right, shl(208, not(0))) + result := or(left, shr(176, right)) + } + } + + function pack_22_10(bytes22 left, bytes10 right) internal pure returns (bytes32 result) { + assembly ("memory-safe") { + left := and(left, shl(80, not(0))) + right := and(right, shl(176, not(0))) + result := or(left, shr(176, right)) + } + } + function pack_24_4(bytes24 left, bytes4 right) internal pure returns (bytes28 result) { assembly ("memory-safe") { left := and(left, shl(64, not(0))) @@ -466,6 +634,81 @@ library Packing { } } + function extract_10_1(bytes10 self, uint8 offset) internal pure returns (bytes1 result) { + if (offset > 9) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(248, not(0))) + } + } + + function replace_10_1(bytes10 self, bytes1 value, uint8 offset) internal pure returns (bytes10 result) { + bytes1 oldValue = extract_10_1(self, offset); + assembly ("memory-safe") { + value := and(value, shl(248, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + + function extract_10_2(bytes10 self, uint8 offset) internal pure returns (bytes2 result) { + if (offset > 8) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(240, not(0))) + } + } + + function replace_10_2(bytes10 self, bytes2 value, uint8 offset) internal pure returns (bytes10 result) { + bytes2 oldValue = extract_10_2(self, offset); + assembly ("memory-safe") { + value := and(value, shl(240, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + + function extract_10_4(bytes10 self, uint8 offset) internal pure returns (bytes4 result) { + if (offset > 6) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(224, not(0))) + } + } + + function replace_10_4(bytes10 self, bytes4 value, uint8 offset) internal pure returns (bytes10 result) { + bytes4 oldValue = extract_10_4(self, offset); + assembly ("memory-safe") { + value := and(value, shl(224, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + + function extract_10_6(bytes10 self, uint8 offset) internal pure returns (bytes6 result) { + if (offset > 4) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(208, not(0))) + } + } + + function replace_10_6(bytes10 self, bytes6 value, uint8 offset) internal pure returns (bytes10 result) { + bytes6 oldValue = extract_10_6(self, offset); + assembly ("memory-safe") { + value := and(value, shl(208, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + + function extract_10_8(bytes10 self, uint8 offset) internal pure returns (bytes8 result) { + if (offset > 2) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(192, not(0))) + } + } + + function replace_10_8(bytes10 self, bytes8 value, uint8 offset) internal pure returns (bytes10 result) { + bytes8 oldValue = extract_10_8(self, offset); + assembly ("memory-safe") { + value := and(value, shl(192, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + function extract_12_1(bytes12 self, uint8 offset) internal pure returns (bytes1 result) { if (offset > 11) revert OutOfRangeAccess(); assembly ("memory-safe") { @@ -541,6 +784,21 @@ library Packing { } } + function extract_12_10(bytes12 self, uint8 offset) internal pure returns (bytes10 result) { + if (offset > 2) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(176, not(0))) + } + } + + function replace_12_10(bytes12 self, bytes10 value, uint8 offset) internal pure returns (bytes12 result) { + bytes10 oldValue = extract_12_10(self, offset); + assembly ("memory-safe") { + value := and(value, shl(176, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + function extract_16_1(bytes16 self, uint8 offset) internal pure returns (bytes1 result) { if (offset > 15) revert OutOfRangeAccess(); assembly ("memory-safe") { @@ -616,6 +874,21 @@ library Packing { } } + function extract_16_10(bytes16 self, uint8 offset) internal pure returns (bytes10 result) { + if (offset > 6) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(176, not(0))) + } + } + + function replace_16_10(bytes16 self, bytes10 value, uint8 offset) internal pure returns (bytes16 result) { + bytes10 oldValue = extract_16_10(self, offset); + assembly ("memory-safe") { + value := and(value, shl(176, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + function extract_16_12(bytes16 self, uint8 offset) internal pure returns (bytes12 result) { if (offset > 4) revert OutOfRangeAccess(); assembly ("memory-safe") { @@ -706,6 +979,21 @@ library Packing { } } + function extract_20_10(bytes20 self, uint8 offset) internal pure returns (bytes10 result) { + if (offset > 10) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(176, not(0))) + } + } + + function replace_20_10(bytes20 self, bytes10 value, uint8 offset) internal pure returns (bytes20 result) { + bytes10 oldValue = extract_20_10(self, offset); + assembly ("memory-safe") { + value := and(value, shl(176, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + function extract_20_12(bytes20 self, uint8 offset) internal pure returns (bytes12 result) { if (offset > 8) revert OutOfRangeAccess(); assembly ("memory-safe") { @@ -736,6 +1024,141 @@ library Packing { } } + function extract_22_1(bytes22 self, uint8 offset) internal pure returns (bytes1 result) { + if (offset > 21) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(248, not(0))) + } + } + + function replace_22_1(bytes22 self, bytes1 value, uint8 offset) internal pure returns (bytes22 result) { + bytes1 oldValue = extract_22_1(self, offset); + assembly ("memory-safe") { + value := and(value, shl(248, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + + function extract_22_2(bytes22 self, uint8 offset) internal pure returns (bytes2 result) { + if (offset > 20) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(240, not(0))) + } + } + + function replace_22_2(bytes22 self, bytes2 value, uint8 offset) internal pure returns (bytes22 result) { + bytes2 oldValue = extract_22_2(self, offset); + assembly ("memory-safe") { + value := and(value, shl(240, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + + function extract_22_4(bytes22 self, uint8 offset) internal pure returns (bytes4 result) { + if (offset > 18) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(224, not(0))) + } + } + + function replace_22_4(bytes22 self, bytes4 value, uint8 offset) internal pure returns (bytes22 result) { + bytes4 oldValue = extract_22_4(self, offset); + assembly ("memory-safe") { + value := and(value, shl(224, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + + function extract_22_6(bytes22 self, uint8 offset) internal pure returns (bytes6 result) { + if (offset > 16) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(208, not(0))) + } + } + + function replace_22_6(bytes22 self, bytes6 value, uint8 offset) internal pure returns (bytes22 result) { + bytes6 oldValue = extract_22_6(self, offset); + assembly ("memory-safe") { + value := and(value, shl(208, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + + function extract_22_8(bytes22 self, uint8 offset) internal pure returns (bytes8 result) { + if (offset > 14) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(192, not(0))) + } + } + + function replace_22_8(bytes22 self, bytes8 value, uint8 offset) internal pure returns (bytes22 result) { + bytes8 oldValue = extract_22_8(self, offset); + assembly ("memory-safe") { + value := and(value, shl(192, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + + function extract_22_10(bytes22 self, uint8 offset) internal pure returns (bytes10 result) { + if (offset > 12) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(176, not(0))) + } + } + + function replace_22_10(bytes22 self, bytes10 value, uint8 offset) internal pure returns (bytes22 result) { + bytes10 oldValue = extract_22_10(self, offset); + assembly ("memory-safe") { + value := and(value, shl(176, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + + function extract_22_12(bytes22 self, uint8 offset) internal pure returns (bytes12 result) { + if (offset > 10) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(160, not(0))) + } + } + + function replace_22_12(bytes22 self, bytes12 value, uint8 offset) internal pure returns (bytes22 result) { + bytes12 oldValue = extract_22_12(self, offset); + assembly ("memory-safe") { + value := and(value, shl(160, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + + function extract_22_16(bytes22 self, uint8 offset) internal pure returns (bytes16 result) { + if (offset > 6) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(128, not(0))) + } + } + + function replace_22_16(bytes22 self, bytes16 value, uint8 offset) internal pure returns (bytes22 result) { + bytes16 oldValue = extract_22_16(self, offset); + assembly ("memory-safe") { + value := and(value, shl(128, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + + function extract_22_20(bytes22 self, uint8 offset) internal pure returns (bytes20 result) { + if (offset > 2) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(96, not(0))) + } + } + + function replace_22_20(bytes22 self, bytes20 value, uint8 offset) internal pure returns (bytes22 result) { + bytes20 oldValue = extract_22_20(self, offset); + assembly ("memory-safe") { + value := and(value, shl(96, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + function extract_24_1(bytes24 self, uint8 offset) internal pure returns (bytes1 result) { if (offset > 23) revert OutOfRangeAccess(); assembly ("memory-safe") { @@ -811,6 +1234,21 @@ library Packing { } } + function extract_24_10(bytes24 self, uint8 offset) internal pure returns (bytes10 result) { + if (offset > 14) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(176, not(0))) + } + } + + function replace_24_10(bytes24 self, bytes10 value, uint8 offset) internal pure returns (bytes24 result) { + bytes10 oldValue = extract_24_10(self, offset); + assembly ("memory-safe") { + value := and(value, shl(176, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + function extract_24_12(bytes24 self, uint8 offset) internal pure returns (bytes12 result) { if (offset > 12) revert OutOfRangeAccess(); assembly ("memory-safe") { @@ -856,6 +1294,21 @@ library Packing { } } + function extract_24_22(bytes24 self, uint8 offset) internal pure returns (bytes22 result) { + if (offset > 2) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(80, not(0))) + } + } + + function replace_24_22(bytes24 self, bytes22 value, uint8 offset) internal pure returns (bytes24 result) { + bytes22 oldValue = extract_24_22(self, offset); + assembly ("memory-safe") { + value := and(value, shl(80, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + function extract_28_1(bytes28 self, uint8 offset) internal pure returns (bytes1 result) { if (offset > 27) revert OutOfRangeAccess(); assembly ("memory-safe") { @@ -931,6 +1384,21 @@ library Packing { } } + function extract_28_10(bytes28 self, uint8 offset) internal pure returns (bytes10 result) { + if (offset > 18) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(176, not(0))) + } + } + + function replace_28_10(bytes28 self, bytes10 value, uint8 offset) internal pure returns (bytes28 result) { + bytes10 oldValue = extract_28_10(self, offset); + assembly ("memory-safe") { + value := and(value, shl(176, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + function extract_28_12(bytes28 self, uint8 offset) internal pure returns (bytes12 result) { if (offset > 16) revert OutOfRangeAccess(); assembly ("memory-safe") { @@ -976,6 +1444,21 @@ library Packing { } } + function extract_28_22(bytes28 self, uint8 offset) internal pure returns (bytes22 result) { + if (offset > 6) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(80, not(0))) + } + } + + function replace_28_22(bytes28 self, bytes22 value, uint8 offset) internal pure returns (bytes28 result) { + bytes22 oldValue = extract_28_22(self, offset); + assembly ("memory-safe") { + value := and(value, shl(80, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + function extract_28_24(bytes28 self, uint8 offset) internal pure returns (bytes24 result) { if (offset > 4) revert OutOfRangeAccess(); assembly ("memory-safe") { @@ -1066,6 +1549,21 @@ library Packing { } } + function extract_32_10(bytes32 self, uint8 offset) internal pure returns (bytes10 result) { + if (offset > 22) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(176, not(0))) + } + } + + function replace_32_10(bytes32 self, bytes10 value, uint8 offset) internal pure returns (bytes32 result) { + bytes10 oldValue = extract_32_10(self, offset); + assembly ("memory-safe") { + value := and(value, shl(176, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + function extract_32_12(bytes32 self, uint8 offset) internal pure returns (bytes12 result) { if (offset > 20) revert OutOfRangeAccess(); assembly ("memory-safe") { @@ -1111,6 +1609,21 @@ library Packing { } } + function extract_32_22(bytes32 self, uint8 offset) internal pure returns (bytes22 result) { + if (offset > 10) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(80, not(0))) + } + } + + function replace_32_22(bytes32 self, bytes22 value, uint8 offset) internal pure returns (bytes32 result) { + bytes22 oldValue = extract_32_22(self, offset); + assembly ("memory-safe") { + value := and(value, shl(80, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + function extract_32_24(bytes32 self, uint8 offset) internal pure returns (bytes24 result) { if (offset > 8) revert OutOfRangeAccess(); assembly ("memory-safe") { diff --git a/contracts/utils/README.adoc b/contracts/utils/README.adoc index 24b95b4e6..432b806e3 100644 --- a/contracts/utils/README.adoc +++ b/contracts/utils/README.adoc @@ -18,6 +18,7 @@ Miscellaneous contracts and libraries containing utility functions you can use t * {ReentrancyGuardTransient}: Variant of {ReentrancyGuard} that uses transient storage (https://eips.ethereum.org/EIPS/eip-1153[EIP-1153]). * {Pausable}: A common emergency response mechanism that can pause functionality while a remediation is pending. * {Nonces}: Utility for tracking and verifying address nonces that only increment. + * {NoncesKeyed}: Alternative to {Nonces}, that support key-ed nonces following https://eips.ethereum.org/EIPS/eip-4337#semi-abstracted-nonce-support[ERC-4337 speciciations]. * {ERC165}, {ERC165Checker}: Utilities for inspecting interfaces supported by contracts. * {BitMaps}: A simple library to manage boolean value mapped to a numerical index in an efficient way. * {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`). @@ -31,16 +32,18 @@ Miscellaneous contracts and libraries containing utility functions you can use t * {Address}: Collection of functions for overloading Solidity's https://docs.soliditylang.org/en/latest/types.html#address[`address`] type. * {Arrays}: Collection of functions that operate on https://docs.soliditylang.org/en/latest/types.html#arrays[`arrays`]. * {Base64}: On-chain base64 and base64URL encoding according to https://datatracker.ietf.org/doc/html/rfc4648[RFC-4648]. + * {Bytes}: Common operations on bytes objects. * {Strings}: Common operations for strings formatting. * {ShortString}: Library to encode (and decode) short strings into (or from) a single bytes32 slot for optimizing costs. Short strings are limited to 31 characters. * {SlotDerivation}: Methods for deriving storage slot from ERC-7201 namespaces as well as from constructions such as mapping and arrays. - * {StorageSlot}: Methods for accessing specific storage slots formatted as common primitive types. + * {StorageSlot}: Methods for accessing specific storage slots formatted as common primitive types. * {TransientSlot}: Primitives for reading from and writing to transient storage (only value types are currently supported). * {Multicall}: Abstract contract with a utility to allow batching together multiple calls in a single transaction. Useful for allowing EOAs to perform multiple operations at once. * {Context}: A utility for abstracting the sender and calldata in the current execution context. * {Packing}: A library for packing and unpacking multiple values into bytes32 * {Panic}: A library to revert with https://docs.soliditylang.org/en/v0.8.20/control-structures.html#panic-via-assert-and-error-via-require[Solidity panic codes]. * {Comparators}: A library that contains comparator functions to use with with the {Heap} library. + * {CAIP2}, {CAIP10}: Libraries for formatting and parsing CAIP-2 and CAIP-10 identifiers. [NOTE] ==== @@ -83,6 +86,8 @@ Because Solidity does not support generic types, {EnumerableMap} and {Enumerable {{Nonces}} +{{NoncesKeyed}} + == 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_. diff --git a/contracts/utils/Strings.sol b/contracts/utils/Strings.sol index 35cdf5baa..0360b4cab 100644 --- a/contracts/utils/Strings.sol +++ b/contracts/utils/Strings.sol @@ -1,15 +1,18 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.1.0) (utils/Strings.sol) +// OpenZeppelin Contracts (last updated v5.2.0) (utils/Strings.sol) pragma solidity ^0.8.20; import {Math} from "./math/Math.sol"; +import {SafeCast} from "./math/SafeCast.sol"; import {SignedMath} from "./math/SignedMath.sol"; /** * @dev String operations. */ library Strings { + using SafeCast for *; + bytes16 private constant HEX_DIGITS = "0123456789abcdef"; uint8 private constant ADDRESS_LENGTH = 20; @@ -18,6 +21,16 @@ library Strings { */ error StringsInsufficientHexLength(uint256 value, uint256 length); + /** + * @dev The string being parsed contains characters that are not in scope of the given base. + */ + error StringsInvalidChar(); + + /** + * @dev The string being parsed is not a properly formatted address. + */ + error StringsInvalidAddressFormat(); + /** * @dev Converts a `uint256` to its ASCII `string` decimal representation. */ @@ -113,4 +126,316 @@ library Strings { 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)); } + + /** + * @dev Parse a decimal string and returns the value as a `uint256`. + * + * Requirements: + * - The string must be formatted as `[0-9]*` + * - The result must fit into an `uint256` type + */ + function parseUint(string memory input) internal pure returns (uint256) { + return parseUint(input, 0, bytes(input).length); + } + + /** + * @dev Variant of {parseUint} that parses a substring of `input` located between position `begin` (included) and + * `end` (excluded). + * + * Requirements: + * - The substring must be formatted as `[0-9]*` + * - The result must fit into an `uint256` type + */ + function parseUint(string memory input, uint256 begin, uint256 end) internal pure returns (uint256) { + (bool success, uint256 value) = tryParseUint(input, begin, end); + if (!success) revert StringsInvalidChar(); + return value; + } + + /** + * @dev Variant of {parseUint-string} that returns false if the parsing fails because of an invalid character. + * + * NOTE: This function will revert if the result does not fit in a `uint256`. + */ + function tryParseUint(string memory input) internal pure returns (bool success, uint256 value) { + return _tryParseUintUncheckedBounds(input, 0, bytes(input).length); + } + + /** + * @dev Variant of {parseUint-string-uint256-uint256} that returns false if the parsing fails because of an invalid + * character. + * + * NOTE: This function will revert if the result does not fit in a `uint256`. + */ + function tryParseUint( + string memory input, + uint256 begin, + uint256 end + ) internal pure returns (bool success, uint256 value) { + if (end > bytes(input).length || begin > end) return (false, 0); + return _tryParseUintUncheckedBounds(input, begin, end); + } + + /** + * @dev Implementation of {tryParseUint} that does not check bounds. Caller should make sure that + * `begin <= end <= input.length`. Other inputs would result in undefined behavior. + */ + function _tryParseUintUncheckedBounds( + string memory input, + uint256 begin, + uint256 end + ) private pure returns (bool success, uint256 value) { + bytes memory buffer = bytes(input); + + uint256 result = 0; + for (uint256 i = begin; i < end; ++i) { + uint8 chr = _tryParseChr(bytes1(_unsafeReadBytesOffset(buffer, i))); + if (chr > 9) return (false, 0); + result *= 10; + result += chr; + } + return (true, result); + } + + /** + * @dev Parse a decimal string and returns the value as a `int256`. + * + * Requirements: + * - The string must be formatted as `[-+]?[0-9]*` + * - The result must fit in an `int256` type. + */ + function parseInt(string memory input) internal pure returns (int256) { + return parseInt(input, 0, bytes(input).length); + } + + /** + * @dev Variant of {parseInt-string} that parses a substring of `input` located between position `begin` (included) and + * `end` (excluded). + * + * Requirements: + * - The substring must be formatted as `[-+]?[0-9]*` + * - The result must fit in an `int256` type. + */ + function parseInt(string memory input, uint256 begin, uint256 end) internal pure returns (int256) { + (bool success, int256 value) = tryParseInt(input, begin, end); + if (!success) revert StringsInvalidChar(); + return value; + } + + /** + * @dev Variant of {parseInt-string} that returns false if the parsing fails because of an invalid character or if + * the result does not fit in a `int256`. + * + * NOTE: This function will revert if the absolute value of the result does not fit in a `uint256`. + */ + function tryParseInt(string memory input) internal pure returns (bool success, int256 value) { + return _tryParseIntUncheckedBounds(input, 0, bytes(input).length); + } + + uint256 private constant ABS_MIN_INT256 = 2 ** 255; + + /** + * @dev Variant of {parseInt-string-uint256-uint256} that returns false if the parsing fails because of an invalid + * character or if the result does not fit in a `int256`. + * + * NOTE: This function will revert if the absolute value of the result does not fit in a `uint256`. + */ + function tryParseInt( + string memory input, + uint256 begin, + uint256 end + ) internal pure returns (bool success, int256 value) { + if (end > bytes(input).length || begin > end) return (false, 0); + return _tryParseIntUncheckedBounds(input, begin, end); + } + + /** + * @dev Implementation of {tryParseInt} that does not check bounds. Caller should make sure that + * `begin <= end <= input.length`. Other inputs would result in undefined behavior. + */ + function _tryParseIntUncheckedBounds( + string memory input, + uint256 begin, + uint256 end + ) private pure returns (bool success, int256 value) { + bytes memory buffer = bytes(input); + + // Check presence of a negative sign. + bytes1 sign = begin == end ? bytes1(0) : bytes1(_unsafeReadBytesOffset(buffer, begin)); // don't do out-of-bound (possibly unsafe) read if sub-string is empty + bool positiveSign = sign == bytes1("+"); + bool negativeSign = sign == bytes1("-"); + uint256 offset = (positiveSign || negativeSign).toUint(); + + (bool absSuccess, uint256 absValue) = tryParseUint(input, begin + offset, end); + + if (absSuccess && absValue < ABS_MIN_INT256) { + return (true, negativeSign ? -int256(absValue) : int256(absValue)); + } else if (absSuccess && negativeSign && absValue == ABS_MIN_INT256) { + return (true, type(int256).min); + } else return (false, 0); + } + + /** + * @dev Parse a hexadecimal string (with or without "0x" prefix), and returns the value as a `uint256`. + * + * Requirements: + * - The string must be formatted as `(0x)?[0-9a-fA-F]*` + * - The result must fit in an `uint256` type. + */ + function parseHexUint(string memory input) internal pure returns (uint256) { + return parseHexUint(input, 0, bytes(input).length); + } + + /** + * @dev Variant of {parseHexUint} that parses a substring of `input` located between position `begin` (included) and + * `end` (excluded). + * + * Requirements: + * - The substring must be formatted as `(0x)?[0-9a-fA-F]*` + * - The result must fit in an `uint256` type. + */ + function parseHexUint(string memory input, uint256 begin, uint256 end) internal pure returns (uint256) { + (bool success, uint256 value) = tryParseHexUint(input, begin, end); + if (!success) revert StringsInvalidChar(); + return value; + } + + /** + * @dev Variant of {parseHexUint-string} that returns false if the parsing fails because of an invalid character. + * + * NOTE: This function will revert if the result does not fit in a `uint256`. + */ + function tryParseHexUint(string memory input) internal pure returns (bool success, uint256 value) { + return _tryParseHexUintUncheckedBounds(input, 0, bytes(input).length); + } + + /** + * @dev Variant of {parseHexUint-string-uint256-uint256} that returns false if the parsing fails because of an + * invalid character. + * + * NOTE: This function will revert if the result does not fit in a `uint256`. + */ + function tryParseHexUint( + string memory input, + uint256 begin, + uint256 end + ) internal pure returns (bool success, uint256 value) { + if (end > bytes(input).length || begin > end) return (false, 0); + return _tryParseHexUintUncheckedBounds(input, begin, end); + } + + /** + * @dev Implementation of {tryParseHexUint} that does not check bounds. Caller should make sure that + * `begin <= end <= input.length`. Other inputs would result in undefined behavior. + */ + function _tryParseHexUintUncheckedBounds( + string memory input, + uint256 begin, + uint256 end + ) private pure returns (bool success, uint256 value) { + bytes memory buffer = bytes(input); + + // skip 0x prefix if present + bool hasPrefix = (end > begin + 1) && bytes2(_unsafeReadBytesOffset(buffer, begin)) == bytes2("0x"); // don't do out-of-bound (possibly unsafe) read if sub-string is empty + uint256 offset = hasPrefix.toUint() * 2; + + uint256 result = 0; + for (uint256 i = begin + offset; i < end; ++i) { + uint8 chr = _tryParseChr(bytes1(_unsafeReadBytesOffset(buffer, i))); + if (chr > 15) return (false, 0); + result *= 16; + unchecked { + // Multiplying by 16 is equivalent to a shift of 4 bits (with additional overflow check). + // This guaratees that adding a value < 16 will not cause an overflow, hence the unchecked. + result += chr; + } + } + return (true, result); + } + + /** + * @dev Parse a hexadecimal string (with or without "0x" prefix), and returns the value as an `address`. + * + * Requirements: + * - The string must be formatted as `(0x)?[0-9a-fA-F]{40}` + */ + function parseAddress(string memory input) internal pure returns (address) { + return parseAddress(input, 0, bytes(input).length); + } + + /** + * @dev Variant of {parseAddress} that parses a substring of `input` located between position `begin` (included) and + * `end` (excluded). + * + * Requirements: + * - The substring must be formatted as `(0x)?[0-9a-fA-F]{40}` + */ + function parseAddress(string memory input, uint256 begin, uint256 end) internal pure returns (address) { + (bool success, address value) = tryParseAddress(input, begin, end); + if (!success) revert StringsInvalidAddressFormat(); + return value; + } + + /** + * @dev Variant of {parseAddress-string} that returns false if the parsing fails because the input is not a properly + * formatted address. See {parseAddress} requirements. + */ + function tryParseAddress(string memory input) internal pure returns (bool success, address value) { + return tryParseAddress(input, 0, bytes(input).length); + } + + /** + * @dev Variant of {parseAddress-string-uint256-uint256} that returns false if the parsing fails because input is not a properly + * formatted address. See {parseAddress} requirements. + */ + function tryParseAddress( + string memory input, + uint256 begin, + uint256 end + ) internal pure returns (bool success, address value) { + if (end > bytes(input).length || begin > end) return (false, address(0)); + + bool hasPrefix = (end > begin + 1) && bytes2(_unsafeReadBytesOffset(bytes(input), begin)) == bytes2("0x"); // don't do out-of-bound (possibly unsafe) read if sub-string is empty + uint256 expectedLength = 40 + hasPrefix.toUint() * 2; + + // check that input is the correct length + if (end - begin == expectedLength) { + // length guarantees that this does not overflow, and value is at most type(uint160).max + (bool s, uint256 v) = _tryParseHexUintUncheckedBounds(input, begin, end); + return (s, address(uint160(v))); + } else { + return (false, address(0)); + } + } + + function _tryParseChr(bytes1 chr) private pure returns (uint8) { + uint8 value = uint8(chr); + + // Try to parse `chr`: + // - Case 1: [0-9] + // - Case 2: [a-f] + // - Case 3: [A-F] + // - otherwise not supported + unchecked { + if (value > 47 && value < 58) value -= 48; + else if (value > 96 && value < 103) value -= 87; + else if (value > 64 && value < 71) value -= 55; + else return type(uint8).max; + } + + return value; + } + + /** + * @dev Reads a bytes32 from a bytes array without bounds checking. + * + * NOTE: making this function internal would mean it could be used with memory unsafe offset, and marking the + * assembly block as such would prevent some optimizations. + */ + function _unsafeReadBytesOffset(bytes memory buffer, uint256 offset) private pure returns (bytes32 value) { + // This is not memory safe in the general case, but all calls to this private function are within bounds. + assembly ("memory-safe") { + value := mload(add(buffer, add(0x20, offset))) + } + } } diff --git a/docs/modules/ROOT/pages/erc1155.adoc b/docs/modules/ROOT/pages/erc1155.adoc index 5bfb49acc..7f00f3ea4 100644 --- a/docs/modules/ROOT/pages/erc1155.adoc +++ b/docs/modules/ROOT/pages/erc1155.adoc @@ -108,7 +108,7 @@ ERC1155InvalidReceiver("
") This is a good thing! It means that the recipient contract has not registered itself as aware of the ERC-1155 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 ERC-1155 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: +In order for our contract to receive ERC-1155 tokens we can inherit from the convenience contract xref:api:token/ERC1155.adoc#ERC1155Holder[`ERC1155Holder`] which handles the registering for us. However, we need to remember to implement functionality to allow tokens to be transferred out of our contract: [source,solidity] ---- diff --git a/docs/modules/ROOT/pages/erc4626.adoc b/docs/modules/ROOT/pages/erc4626.adoc index 79388c0a2..c219595dd 100644 --- a/docs/modules/ROOT/pages/erc4626.adoc +++ b/docs/modules/ROOT/pages/erc4626.adoc @@ -29,7 +29,7 @@ image::erc4626-rate-loglogext.png[More exchange rates in logarithmic scale] === The attack -When depositing tokens, the number of shares a user gets is rounded towards zero. This rounding takes away value from the user in favor of the vault (i.e. in favor of all the current share holders). This rounding is often negligible because of the amount at stake. If you deposit 1e9 shares worth of tokens, the rounding will have you lose at most 0.0000001% of your deposit. However if you deposit 10 shares worth of tokens, you could lose 10% of your deposit. Even worse, if you deposit <1 share worth of tokens, then you get 0 shares, and you basically made a donation. +When depositing tokens, the number of shares a user gets is rounded towards zero. This rounding takes away value from the user in favor of the vault (i.e. in favor of all the current shareholders). 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. @@ -47,7 +47,7 @@ The idea of an inflation attack is that an attacker can donate assets to the vau 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. +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 shareholder (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. diff --git a/docs/modules/ROOT/pages/extending-contracts.adoc b/docs/modules/ROOT/pages/extending-contracts.adoc index 1cdc0d781..8ff4101f2 100644 --- a/docs/modules/ROOT/pages/extending-contracts.adoc +++ b/docs/modules/ROOT/pages/extending-contracts.adoc @@ -46,6 +46,6 @@ NOTE: The same rule is implemented and extended in xref:api:access.adoc#AccessCo 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. +Custom overrides, especially to hooks, can disrupt important assumptions and may introduce security risks in the code that was previously secure. 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 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/governance.adoc b/docs/modules/ROOT/pages/governance.adoc index 19f23d78d..5f8e77555 100644 --- a/docs/modules/ROOT/pages/governance.adoc +++ b/docs/modules/ROOT/pages/governance.adoc @@ -74,7 +74,7 @@ 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. +We can optionally set a proposal threshold as well. This restricts proposal creation to accounts that have enough voting power. ```solidity include::api:example$governance/MyGovernor.sol[] diff --git a/docs/modules/api/examples/access-control/AccessControlUnrevokableAdmin.sol b/docs/modules/api/examples/access-control/AccessControlNonRevokableAdmin.sol similarity index 100% rename from docs/modules/api/examples/access-control/AccessControlUnrevokableAdmin.sol rename to docs/modules/api/examples/access-control/AccessControlNonRevokableAdmin.sol diff --git a/docs/modules/api/nav.adoc b/docs/modules/api/nav.adoc index ac2ab659c..d9f67fd74 100644 --- a/docs/modules/api/nav.adoc +++ b/docs/modules/api/nav.adoc @@ -1,5 +1,6 @@ .API * xref:access.adoc[Access] +* xref:account.adoc[Account] * xref:token/common.adoc[Common (Tokens)] * xref:token/ERC20.adoc[ERC 20] * xref:token/ERC721.adoc[ERC 721] diff --git a/docs/modules/api/pages/access.adoc b/docs/modules/api/pages/access.adoc index 6d4d4e373..234edc012 100644 --- a/docs/modules/api/pages/access.adoc +++ b/docs/modules/api/pages/access.adoc @@ -358,7 +358,7 @@ This directory provides ways to restrict who can access the functions of a contr [.contract] [[Ownable]] -=== `++Ownable++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/access/Ownable.sol[{github-icon},role=heading-link] +=== `++Ownable++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/access/Ownable.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -481,7 +481,7 @@ The owner is not a valid owner account. (eg. `address(0)`) [.contract] [[Ownable2Step]] -=== `++Ownable2Step++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/access/Ownable2Step.sol[{github-icon},role=heading-link] +=== `++Ownable2Step++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/access/Ownable2Step.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -587,7 +587,7 @@ The new owner accepts the ownership transfer. [.contract] [[IAccessControl]] -=== `++IAccessControl++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/access/IAccessControl.sol[{github-icon},role=heading-link] +=== `++IAccessControl++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/access/IAccessControl.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -740,7 +740,7 @@ NOTE: Don't confuse with {AccessControlUnauthorizedAccount}. [.contract] [[AccessControl]] -=== `++AccessControl++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/access/AccessControl.sol[{github-icon},role=heading-link] +=== `++AccessControl++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/access/AccessControl.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -982,7 +982,7 @@ May emit a {RoleRevoked} event. [.contract] [[IAccessControlEnumerable]] -=== `++IAccessControlEnumerable++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/access/extensions/IAccessControlEnumerable.sol[{github-icon},role=heading-link] +=== `++IAccessControlEnumerable++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/access/extensions/IAccessControlEnumerable.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -1061,7 +1061,7 @@ together with {getRoleMember} to enumerate all bearers of a role. [.contract] [[AccessControlEnumerable]] -=== `++AccessControlEnumerable++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/access/extensions/AccessControlEnumerable.sol[{github-icon},role=heading-link] +=== `++AccessControlEnumerable++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/access/extensions/AccessControlEnumerable.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -1224,7 +1224,7 @@ Overload {AccessControl-_revokeRole} to track enumerable memberships [.contract] [[IAccessControlDefaultAdminRules]] -=== `++IAccessControlDefaultAdminRules++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/access/extensions/IAccessControlDefaultAdminRules.sol[{github-icon},role=heading-link] +=== `++IAccessControlDefaultAdminRules++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/access/extensions/IAccessControlDefaultAdminRules.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -1513,7 +1513,7 @@ NOTE: `schedule` can be 0 indicating there's no transfer scheduled. [.contract] [[AccessControlDefaultAdminRules]] -=== `++AccessControlDefaultAdminRules++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/access/extensions/AccessControlDefaultAdminRules.sol[{github-icon},role=heading-link] +=== `++AccessControlDefaultAdminRules++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/access/extensions/AccessControlDefaultAdminRules.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -1934,7 +1934,7 @@ See {defaultAdminDelayIncreaseWait}. [.contract] [[IAuthority]] -=== `++IAuthority++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/access/manager/IAuthority.sol[{github-icon},role=heading-link] +=== `++IAuthority++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/access/manager/IAuthority.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -2011,7 +2011,7 @@ Returns true if the caller can invoke on a target the function identified by a f [.contract] [[IAccessManager]] -=== `++IAccessManager++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/access/manager/IAccessManager.sol[{github-icon},role=heading-link] +=== `++IAccessManager++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/access/manager/IAccessManager.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -2585,7 +2585,7 @@ Admin delay for a given `target` will be updated to `delay` when `since` is reac [.contract] [[AccessManager]] -=== `++AccessManager++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/access/manager/AccessManager.sol[{github-icon},role=heading-link] +=== `++AccessManager++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/access/manager/AccessManager.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -3186,7 +3186,7 @@ The identifier of the public role. Automatically granted to all addresses with n [.contract] [[IAccessManaged]] -=== `++IAccessManaged++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/access/manager/IAccessManaged.sol[{github-icon},role=heading-link] +=== `++IAccessManaged++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/access/manager/IAccessManaged.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -3266,7 +3266,7 @@ Authority that manages this contract was updated. [.contract] [[AccessManaged]] -=== `++AccessManaged++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/access/manager/AccessManaged.sol[{github-icon},role=heading-link] +=== `++AccessManaged++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/access/manager/AccessManaged.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -3394,7 +3394,7 @@ is less than 4 bytes long. [.contract] [[AuthorityUtils]] -=== `++AuthorityUtils++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/access/manager/AuthorityUtils.sol[{github-icon},role=heading-link] +=== `++AuthorityUtils++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/access/manager/AuthorityUtils.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity diff --git a/docs/modules/api/pages/account.adoc b/docs/modules/api/pages/account.adoc new file mode 100644 index 000000000..c5c7dcf65 --- /dev/null +++ b/docs/modules/api/pages/account.adoc @@ -0,0 +1,479 @@ +:github-icon: pass:[] +:xref-ERC4337Utils-parseValidationData-uint256-: xref:account.adoc#ERC4337Utils-parseValidationData-uint256- +:xref-ERC4337Utils-packValidationData-address-uint48-uint48-: xref:account.adoc#ERC4337Utils-packValidationData-address-uint48-uint48- +:xref-ERC4337Utils-packValidationData-bool-uint48-uint48-: xref:account.adoc#ERC4337Utils-packValidationData-bool-uint48-uint48- +:xref-ERC4337Utils-combineValidationData-uint256-uint256-: xref:account.adoc#ERC4337Utils-combineValidationData-uint256-uint256- +:xref-ERC4337Utils-getValidationData-uint256-: xref:account.adoc#ERC4337Utils-getValidationData-uint256- +:xref-ERC4337Utils-hash-struct-PackedUserOperation-address-uint256-: xref:account.adoc#ERC4337Utils-hash-struct-PackedUserOperation-address-uint256- +:xref-ERC4337Utils-factory-struct-PackedUserOperation-: xref:account.adoc#ERC4337Utils-factory-struct-PackedUserOperation- +:xref-ERC4337Utils-factoryData-struct-PackedUserOperation-: xref:account.adoc#ERC4337Utils-factoryData-struct-PackedUserOperation- +:xref-ERC4337Utils-verificationGasLimit-struct-PackedUserOperation-: xref:account.adoc#ERC4337Utils-verificationGasLimit-struct-PackedUserOperation- +:xref-ERC4337Utils-callGasLimit-struct-PackedUserOperation-: xref:account.adoc#ERC4337Utils-callGasLimit-struct-PackedUserOperation- +:xref-ERC4337Utils-maxPriorityFeePerGas-struct-PackedUserOperation-: xref:account.adoc#ERC4337Utils-maxPriorityFeePerGas-struct-PackedUserOperation- +:xref-ERC4337Utils-maxFeePerGas-struct-PackedUserOperation-: xref:account.adoc#ERC4337Utils-maxFeePerGas-struct-PackedUserOperation- +:xref-ERC4337Utils-gasPrice-struct-PackedUserOperation-: xref:account.adoc#ERC4337Utils-gasPrice-struct-PackedUserOperation- +:xref-ERC4337Utils-paymaster-struct-PackedUserOperation-: xref:account.adoc#ERC4337Utils-paymaster-struct-PackedUserOperation- +:xref-ERC4337Utils-paymasterVerificationGasLimit-struct-PackedUserOperation-: xref:account.adoc#ERC4337Utils-paymasterVerificationGasLimit-struct-PackedUserOperation- +:xref-ERC4337Utils-paymasterPostOpGasLimit-struct-PackedUserOperation-: xref:account.adoc#ERC4337Utils-paymasterPostOpGasLimit-struct-PackedUserOperation- +:xref-ERC4337Utils-paymasterData-struct-PackedUserOperation-: xref:account.adoc#ERC4337Utils-paymasterData-struct-PackedUserOperation- +:xref-ERC4337Utils-SIG_VALIDATION_SUCCESS-uint256: xref:account.adoc#ERC4337Utils-SIG_VALIDATION_SUCCESS-uint256 +:xref-ERC4337Utils-SIG_VALIDATION_FAILED-uint256: xref:account.adoc#ERC4337Utils-SIG_VALIDATION_FAILED-uint256 +:PackedUserOperation: pass:normal[xref:interfaces.adoc#PackedUserOperation[`PackedUserOperation`]] +:PackedUserOperation: pass:normal[xref:interfaces.adoc#PackedUserOperation[`PackedUserOperation`]] +:PackedUserOperation: pass:normal[xref:interfaces.adoc#PackedUserOperation[`PackedUserOperation`]] +:PackedUserOperation: pass:normal[xref:interfaces.adoc#PackedUserOperation[`PackedUserOperation`]] +:PackedUserOperation: pass:normal[xref:interfaces.adoc#PackedUserOperation[`PackedUserOperation`]] +:PackedUserOperation: pass:normal[xref:interfaces.adoc#PackedUserOperation[`PackedUserOperation`]] +:PackedUserOperation: pass:normal[xref:interfaces.adoc#PackedUserOperation[`PackedUserOperation`]] +:PackedUserOperation: pass:normal[xref:interfaces.adoc#PackedUserOperation[`PackedUserOperation`]] +:PackedUserOperation: pass:normal[xref:interfaces.adoc#PackedUserOperation[`PackedUserOperation`]] +:PackedUserOperation: pass:normal[xref:interfaces.adoc#PackedUserOperation[`PackedUserOperation`]] +:PackedUserOperation: pass:normal[xref:interfaces.adoc#PackedUserOperation[`PackedUserOperation`]] +:xref-ERC7579Utils-execSingle-bytes-ExecType-: xref:account.adoc#ERC7579Utils-execSingle-bytes-ExecType- +:xref-ERC7579Utils-execBatch-bytes-ExecType-: xref:account.adoc#ERC7579Utils-execBatch-bytes-ExecType- +:xref-ERC7579Utils-execDelegateCall-bytes-ExecType-: xref:account.adoc#ERC7579Utils-execDelegateCall-bytes-ExecType- +:xref-ERC7579Utils-encodeMode-CallType-ExecType-ModeSelector-ModePayload-: xref:account.adoc#ERC7579Utils-encodeMode-CallType-ExecType-ModeSelector-ModePayload- +:xref-ERC7579Utils-decodeMode-Mode-: xref:account.adoc#ERC7579Utils-decodeMode-Mode- +:xref-ERC7579Utils-encodeSingle-address-uint256-bytes-: xref:account.adoc#ERC7579Utils-encodeSingle-address-uint256-bytes- +:xref-ERC7579Utils-decodeSingle-bytes-: xref:account.adoc#ERC7579Utils-decodeSingle-bytes- +:xref-ERC7579Utils-encodeDelegate-address-bytes-: xref:account.adoc#ERC7579Utils-encodeDelegate-address-bytes- +:xref-ERC7579Utils-decodeDelegate-bytes-: xref:account.adoc#ERC7579Utils-decodeDelegate-bytes- +:xref-ERC7579Utils-encodeBatch-struct-Execution---: xref:account.adoc#ERC7579Utils-encodeBatch-struct-Execution--- +:xref-ERC7579Utils-decodeBatch-bytes-: xref:account.adoc#ERC7579Utils-decodeBatch-bytes- +:xref-ERC7579Utils-ERC7579TryExecuteFail-uint256-bytes-: xref:account.adoc#ERC7579Utils-ERC7579TryExecuteFail-uint256-bytes- +:xref-ERC7579Utils-ERC7579UnsupportedCallType-CallType-: xref:account.adoc#ERC7579Utils-ERC7579UnsupportedCallType-CallType- +:xref-ERC7579Utils-ERC7579UnsupportedExecType-ExecType-: xref:account.adoc#ERC7579Utils-ERC7579UnsupportedExecType-ExecType- +:xref-ERC7579Utils-ERC7579MismatchedModuleTypeId-uint256-address-: xref:account.adoc#ERC7579Utils-ERC7579MismatchedModuleTypeId-uint256-address- +:xref-ERC7579Utils-ERC7579UninstalledModule-uint256-address-: xref:account.adoc#ERC7579Utils-ERC7579UninstalledModule-uint256-address- +:xref-ERC7579Utils-ERC7579AlreadyInstalledModule-uint256-address-: xref:account.adoc#ERC7579Utils-ERC7579AlreadyInstalledModule-uint256-address- +:xref-ERC7579Utils-ERC7579UnsupportedModuleType-uint256-: xref:account.adoc#ERC7579Utils-ERC7579UnsupportedModuleType-uint256- +:xref-ERC7579Utils-ERC7579DecodingError--: xref:account.adoc#ERC7579Utils-ERC7579DecodingError-- +:xref-ERC7579Utils-CALLTYPE_SINGLE-CallType: xref:account.adoc#ERC7579Utils-CALLTYPE_SINGLE-CallType +:xref-ERC7579Utils-CALLTYPE_BATCH-CallType: xref:account.adoc#ERC7579Utils-CALLTYPE_BATCH-CallType +:xref-ERC7579Utils-CALLTYPE_DELEGATECALL-CallType: xref:account.adoc#ERC7579Utils-CALLTYPE_DELEGATECALL-CallType +:xref-ERC7579Utils-EXECTYPE_DEFAULT-ExecType: xref:account.adoc#ERC7579Utils-EXECTYPE_DEFAULT-ExecType +:xref-ERC7579Utils-EXECTYPE_TRY-ExecType: xref:account.adoc#ERC7579Utils-EXECTYPE_TRY-ExecType +:CallType: pass:normal[xref:account.adoc#CallType[`CallType`]] +:ExecType: pass:normal[xref:account.adoc#ExecType[`ExecType`]] += Account + +[.readme-notice] +NOTE: This document is better viewed at https://docs.openzeppelin.com/contracts/api/account + +This directory includes contracts to build accounts for ERC-4337. + +== Utilities + +:SIG_VALIDATION_SUCCESS: pass:normal[xref:#ERC4337Utils-SIG_VALIDATION_SUCCESS-uint256[`++SIG_VALIDATION_SUCCESS++`]] +:SIG_VALIDATION_FAILED: pass:normal[xref:#ERC4337Utils-SIG_VALIDATION_FAILED-uint256[`++SIG_VALIDATION_FAILED++`]] +:parseValidationData: pass:normal[xref:#ERC4337Utils-parseValidationData-uint256-[`++parseValidationData++`]] +:packValidationData: pass:normal[xref:#ERC4337Utils-packValidationData-address-uint48-uint48-[`++packValidationData++`]] +:packValidationData: pass:normal[xref:#ERC4337Utils-packValidationData-bool-uint48-uint48-[`++packValidationData++`]] +:combineValidationData: pass:normal[xref:#ERC4337Utils-combineValidationData-uint256-uint256-[`++combineValidationData++`]] +:getValidationData: pass:normal[xref:#ERC4337Utils-getValidationData-uint256-[`++getValidationData++`]] +:hash: pass:normal[xref:#ERC4337Utils-hash-struct-PackedUserOperation-address-uint256-[`++hash++`]] +:factory: pass:normal[xref:#ERC4337Utils-factory-struct-PackedUserOperation-[`++factory++`]] +:factoryData: pass:normal[xref:#ERC4337Utils-factoryData-struct-PackedUserOperation-[`++factoryData++`]] +:verificationGasLimit: pass:normal[xref:#ERC4337Utils-verificationGasLimit-struct-PackedUserOperation-[`++verificationGasLimit++`]] +:callGasLimit: pass:normal[xref:#ERC4337Utils-callGasLimit-struct-PackedUserOperation-[`++callGasLimit++`]] +:maxPriorityFeePerGas: pass:normal[xref:#ERC4337Utils-maxPriorityFeePerGas-struct-PackedUserOperation-[`++maxPriorityFeePerGas++`]] +:maxFeePerGas: pass:normal[xref:#ERC4337Utils-maxFeePerGas-struct-PackedUserOperation-[`++maxFeePerGas++`]] +:gasPrice: pass:normal[xref:#ERC4337Utils-gasPrice-struct-PackedUserOperation-[`++gasPrice++`]] +:paymaster: pass:normal[xref:#ERC4337Utils-paymaster-struct-PackedUserOperation-[`++paymaster++`]] +:paymasterVerificationGasLimit: pass:normal[xref:#ERC4337Utils-paymasterVerificationGasLimit-struct-PackedUserOperation-[`++paymasterVerificationGasLimit++`]] +:paymasterPostOpGasLimit: pass:normal[xref:#ERC4337Utils-paymasterPostOpGasLimit-struct-PackedUserOperation-[`++paymasterPostOpGasLimit++`]] +:paymasterData: pass:normal[xref:#ERC4337Utils-paymasterData-struct-PackedUserOperation-[`++paymasterData++`]] + +[.contract] +[[ERC4337Utils]] +=== `++ERC4337Utils++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/account/utils/draft-ERC4337Utils.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/account/utils/draft-ERC4337Utils.sol"; +``` + +Library with common ERC-4337 utility functions. + +See https://eips.ethereum.org/EIPS/eip-4337[ERC-4337]. + +[.contract-index] +.Functions +-- +* {xref-ERC4337Utils-parseValidationData-uint256-}[`++parseValidationData(validationData)++`] +* {xref-ERC4337Utils-packValidationData-address-uint48-uint48-}[`++packValidationData(aggregator, validAfter, validUntil)++`] +* {xref-ERC4337Utils-packValidationData-bool-uint48-uint48-}[`++packValidationData(sigSuccess, validAfter, validUntil)++`] +* {xref-ERC4337Utils-combineValidationData-uint256-uint256-}[`++combineValidationData(validationData1, validationData2)++`] +* {xref-ERC4337Utils-getValidationData-uint256-}[`++getValidationData(validationData)++`] +* {xref-ERC4337Utils-hash-struct-PackedUserOperation-address-uint256-}[`++hash(self, entrypoint, chainid)++`] +* {xref-ERC4337Utils-factory-struct-PackedUserOperation-}[`++factory(self)++`] +* {xref-ERC4337Utils-factoryData-struct-PackedUserOperation-}[`++factoryData(self)++`] +* {xref-ERC4337Utils-verificationGasLimit-struct-PackedUserOperation-}[`++verificationGasLimit(self)++`] +* {xref-ERC4337Utils-callGasLimit-struct-PackedUserOperation-}[`++callGasLimit(self)++`] +* {xref-ERC4337Utils-maxPriorityFeePerGas-struct-PackedUserOperation-}[`++maxPriorityFeePerGas(self)++`] +* {xref-ERC4337Utils-maxFeePerGas-struct-PackedUserOperation-}[`++maxFeePerGas(self)++`] +* {xref-ERC4337Utils-gasPrice-struct-PackedUserOperation-}[`++gasPrice(self)++`] +* {xref-ERC4337Utils-paymaster-struct-PackedUserOperation-}[`++paymaster(self)++`] +* {xref-ERC4337Utils-paymasterVerificationGasLimit-struct-PackedUserOperation-}[`++paymasterVerificationGasLimit(self)++`] +* {xref-ERC4337Utils-paymasterPostOpGasLimit-struct-PackedUserOperation-}[`++paymasterPostOpGasLimit(self)++`] +* {xref-ERC4337Utils-paymasterData-struct-PackedUserOperation-}[`++paymasterData(self)++`] + +-- + +[.contract-index] +.Internal Variables +-- +* {xref-ERC4337Utils-SIG_VALIDATION_SUCCESS-uint256}[`++uint256 constant SIG_VALIDATION_SUCCESS++`] +* {xref-ERC4337Utils-SIG_VALIDATION_FAILED-uint256}[`++uint256 constant SIG_VALIDATION_FAILED++`] + +-- + +[.contract-item] +[[ERC4337Utils-parseValidationData-uint256-]] +==== `[.contract-item-name]#++parseValidationData++#++(uint256 validationData) → address aggregator, uint48 validAfter, uint48 validUntil++` [.item-kind]#internal# + +Parses the validation data into its components. See {packValidationData}. + +[.contract-item] +[[ERC4337Utils-packValidationData-address-uint48-uint48-]] +==== `[.contract-item-name]#++packValidationData++#++(address aggregator, uint48 validAfter, uint48 validUntil) → uint256++` [.item-kind]#internal# + +Packs the validation data into a single uint256. See {parseValidationData}. + +[.contract-item] +[[ERC4337Utils-packValidationData-bool-uint48-uint48-]] +==== `[.contract-item-name]#++packValidationData++#++(bool sigSuccess, uint48 validAfter, uint48 validUntil) → uint256++` [.item-kind]#internal# + +Same as {packValidationData}, but with a boolean signature success flag. + +[.contract-item] +[[ERC4337Utils-combineValidationData-uint256-uint256-]] +==== `[.contract-item-name]#++combineValidationData++#++(uint256 validationData1, uint256 validationData2) → uint256++` [.item-kind]#internal# + +Combines two validation data into a single one. + +The `aggregator` is set to {SIG_VALIDATION_SUCCESS} if both are successful, while +the `validAfter` is the maximum and the `validUntil` is the minimum of both. + +[.contract-item] +[[ERC4337Utils-getValidationData-uint256-]] +==== `[.contract-item-name]#++getValidationData++#++(uint256 validationData) → address aggregator, bool outOfTimeRange++` [.item-kind]#internal# + +Returns the aggregator of the `validationData` and whether it is out of time range. + +[.contract-item] +[[ERC4337Utils-hash-struct-PackedUserOperation-address-uint256-]] +==== `[.contract-item-name]#++hash++#++(struct PackedUserOperation self, address entrypoint, uint256 chainid) → bytes32++` [.item-kind]#internal# + +Computes the hash of a user operation for a given entrypoint and chainid. + +[.contract-item] +[[ERC4337Utils-factory-struct-PackedUserOperation-]] +==== `[.contract-item-name]#++factory++#++(struct PackedUserOperation self) → address++` [.item-kind]#internal# + +Returns `factory` from the {PackedUserOperation}, or address(0) if the initCode is empty or not properly formatted. + +[.contract-item] +[[ERC4337Utils-factoryData-struct-PackedUserOperation-]] +==== `[.contract-item-name]#++factoryData++#++(struct PackedUserOperation self) → bytes++` [.item-kind]#internal# + +Returns `factoryData` from the {PackedUserOperation}, or empty bytes if the initCode is empty or not properly formatted. + +[.contract-item] +[[ERC4337Utils-verificationGasLimit-struct-PackedUserOperation-]] +==== `[.contract-item-name]#++verificationGasLimit++#++(struct PackedUserOperation self) → uint256++` [.item-kind]#internal# + +Returns `verificationGasLimit` from the {PackedUserOperation}. + +[.contract-item] +[[ERC4337Utils-callGasLimit-struct-PackedUserOperation-]] +==== `[.contract-item-name]#++callGasLimit++#++(struct PackedUserOperation self) → uint256++` [.item-kind]#internal# + +Returns `callGasLimit` from the {PackedUserOperation}. + +[.contract-item] +[[ERC4337Utils-maxPriorityFeePerGas-struct-PackedUserOperation-]] +==== `[.contract-item-name]#++maxPriorityFeePerGas++#++(struct PackedUserOperation self) → uint256++` [.item-kind]#internal# + +Returns the first section of `gasFees` from the {PackedUserOperation}. + +[.contract-item] +[[ERC4337Utils-maxFeePerGas-struct-PackedUserOperation-]] +==== `[.contract-item-name]#++maxFeePerGas++#++(struct PackedUserOperation self) → uint256++` [.item-kind]#internal# + +Returns the second section of `gasFees` from the {PackedUserOperation}. + +[.contract-item] +[[ERC4337Utils-gasPrice-struct-PackedUserOperation-]] +==== `[.contract-item-name]#++gasPrice++#++(struct PackedUserOperation self) → uint256++` [.item-kind]#internal# + +Returns the total gas price for the {PackedUserOperation} (ie. `maxFeePerGas` or `maxPriorityFeePerGas + basefee`). + +[.contract-item] +[[ERC4337Utils-paymaster-struct-PackedUserOperation-]] +==== `[.contract-item-name]#++paymaster++#++(struct PackedUserOperation self) → address++` [.item-kind]#internal# + +Returns the first section of `paymasterAndData` from the {PackedUserOperation}. + +[.contract-item] +[[ERC4337Utils-paymasterVerificationGasLimit-struct-PackedUserOperation-]] +==== `[.contract-item-name]#++paymasterVerificationGasLimit++#++(struct PackedUserOperation self) → uint256++` [.item-kind]#internal# + +Returns the second section of `paymasterAndData` from the {PackedUserOperation}. + +[.contract-item] +[[ERC4337Utils-paymasterPostOpGasLimit-struct-PackedUserOperation-]] +==== `[.contract-item-name]#++paymasterPostOpGasLimit++#++(struct PackedUserOperation self) → uint256++` [.item-kind]#internal# + +Returns the third section of `paymasterAndData` from the {PackedUserOperation}. + +[.contract-item] +[[ERC4337Utils-paymasterData-struct-PackedUserOperation-]] +==== `[.contract-item-name]#++paymasterData++#++(struct PackedUserOperation self) → bytes++` [.item-kind]#internal# + +Returns the fourth section of `paymasterAndData` from the {PackedUserOperation}. + +[.contract-item] +[[ERC4337Utils-SIG_VALIDATION_SUCCESS-uint256]] +==== `uint256 [.contract-item-name]#++SIG_VALIDATION_SUCCESS++#` [.item-kind]#internal constant# + +For simulation purposes, validateUserOp (and validatePaymasterUserOp) return this value on success. + +[.contract-item] +[[ERC4337Utils-SIG_VALIDATION_FAILED-uint256]] +==== `uint256 [.contract-item-name]#++SIG_VALIDATION_FAILED++#` [.item-kind]#internal constant# + +For simulation purposes, validateUserOp (and validatePaymasterUserOp) must return this value in case of signature failure, instead of revert. + +:CALLTYPE_SINGLE: pass:normal[xref:#ERC7579Utils-CALLTYPE_SINGLE-CallType[`++CALLTYPE_SINGLE++`]] +:CALLTYPE_BATCH: pass:normal[xref:#ERC7579Utils-CALLTYPE_BATCH-CallType[`++CALLTYPE_BATCH++`]] +:CALLTYPE_DELEGATECALL: pass:normal[xref:#ERC7579Utils-CALLTYPE_DELEGATECALL-CallType[`++CALLTYPE_DELEGATECALL++`]] +:EXECTYPE_DEFAULT: pass:normal[xref:#ERC7579Utils-EXECTYPE_DEFAULT-ExecType[`++EXECTYPE_DEFAULT++`]] +:EXECTYPE_TRY: pass:normal[xref:#ERC7579Utils-EXECTYPE_TRY-ExecType[`++EXECTYPE_TRY++`]] +:ERC7579TryExecuteFail: pass:normal[xref:#ERC7579Utils-ERC7579TryExecuteFail-uint256-bytes-[`++ERC7579TryExecuteFail++`]] +:ERC7579UnsupportedCallType: pass:normal[xref:#ERC7579Utils-ERC7579UnsupportedCallType-CallType-[`++ERC7579UnsupportedCallType++`]] +:ERC7579UnsupportedExecType: pass:normal[xref:#ERC7579Utils-ERC7579UnsupportedExecType-ExecType-[`++ERC7579UnsupportedExecType++`]] +:ERC7579MismatchedModuleTypeId: pass:normal[xref:#ERC7579Utils-ERC7579MismatchedModuleTypeId-uint256-address-[`++ERC7579MismatchedModuleTypeId++`]] +:ERC7579UninstalledModule: pass:normal[xref:#ERC7579Utils-ERC7579UninstalledModule-uint256-address-[`++ERC7579UninstalledModule++`]] +:ERC7579AlreadyInstalledModule: pass:normal[xref:#ERC7579Utils-ERC7579AlreadyInstalledModule-uint256-address-[`++ERC7579AlreadyInstalledModule++`]] +:ERC7579UnsupportedModuleType: pass:normal[xref:#ERC7579Utils-ERC7579UnsupportedModuleType-uint256-[`++ERC7579UnsupportedModuleType++`]] +:ERC7579DecodingError: pass:normal[xref:#ERC7579Utils-ERC7579DecodingError--[`++ERC7579DecodingError++`]] +:execSingle: pass:normal[xref:#ERC7579Utils-execSingle-bytes-ExecType-[`++execSingle++`]] +:execBatch: pass:normal[xref:#ERC7579Utils-execBatch-bytes-ExecType-[`++execBatch++`]] +:execDelegateCall: pass:normal[xref:#ERC7579Utils-execDelegateCall-bytes-ExecType-[`++execDelegateCall++`]] +:encodeMode: pass:normal[xref:#ERC7579Utils-encodeMode-CallType-ExecType-ModeSelector-ModePayload-[`++encodeMode++`]] +:decodeMode: pass:normal[xref:#ERC7579Utils-decodeMode-Mode-[`++decodeMode++`]] +:encodeSingle: pass:normal[xref:#ERC7579Utils-encodeSingle-address-uint256-bytes-[`++encodeSingle++`]] +:decodeSingle: pass:normal[xref:#ERC7579Utils-decodeSingle-bytes-[`++decodeSingle++`]] +:encodeDelegate: pass:normal[xref:#ERC7579Utils-encodeDelegate-address-bytes-[`++encodeDelegate++`]] +:decodeDelegate: pass:normal[xref:#ERC7579Utils-decodeDelegate-bytes-[`++decodeDelegate++`]] +:encodeBatch: pass:normal[xref:#ERC7579Utils-encodeBatch-struct-Execution---[`++encodeBatch++`]] +:decodeBatch: pass:normal[xref:#ERC7579Utils-decodeBatch-bytes-[`++decodeBatch++`]] + +[.contract] +[[ERC7579Utils]] +=== `++ERC7579Utils++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/account/utils/draft-ERC7579Utils.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/account/utils/draft-ERC7579Utils.sol"; +``` + +Library with common ERC-7579 utility functions. + +See https://eips.ethereum.org/EIPS/eip-7579[ERC-7579]. + +[.contract-index] +.Functions +-- +* {xref-ERC7579Utils-execSingle-bytes-ExecType-}[`++execSingle(executionCalldata, execType)++`] +* {xref-ERC7579Utils-execBatch-bytes-ExecType-}[`++execBatch(executionCalldata, execType)++`] +* {xref-ERC7579Utils-execDelegateCall-bytes-ExecType-}[`++execDelegateCall(executionCalldata, execType)++`] +* {xref-ERC7579Utils-encodeMode-CallType-ExecType-ModeSelector-ModePayload-}[`++encodeMode(callType, execType, selector, payload)++`] +* {xref-ERC7579Utils-decodeMode-Mode-}[`++decodeMode(mode)++`] +* {xref-ERC7579Utils-encodeSingle-address-uint256-bytes-}[`++encodeSingle(target, value, callData)++`] +* {xref-ERC7579Utils-decodeSingle-bytes-}[`++decodeSingle(executionCalldata)++`] +* {xref-ERC7579Utils-encodeDelegate-address-bytes-}[`++encodeDelegate(target, callData)++`] +* {xref-ERC7579Utils-decodeDelegate-bytes-}[`++decodeDelegate(executionCalldata)++`] +* {xref-ERC7579Utils-encodeBatch-struct-Execution---}[`++encodeBatch(executionBatch)++`] +* {xref-ERC7579Utils-decodeBatch-bytes-}[`++decodeBatch(executionCalldata)++`] + +-- + +[.contract-index] +.Events +-- +* {xref-ERC7579Utils-ERC7579TryExecuteFail-uint256-bytes-}[`++ERC7579TryExecuteFail(batchExecutionIndex, returndata)++`] + +-- + +[.contract-index] +.Errors +-- +* {xref-ERC7579Utils-ERC7579UnsupportedCallType-CallType-}[`++ERC7579UnsupportedCallType(callType)++`] +* {xref-ERC7579Utils-ERC7579UnsupportedExecType-ExecType-}[`++ERC7579UnsupportedExecType(execType)++`] +* {xref-ERC7579Utils-ERC7579MismatchedModuleTypeId-uint256-address-}[`++ERC7579MismatchedModuleTypeId(moduleTypeId, module)++`] +* {xref-ERC7579Utils-ERC7579UninstalledModule-uint256-address-}[`++ERC7579UninstalledModule(moduleTypeId, module)++`] +* {xref-ERC7579Utils-ERC7579AlreadyInstalledModule-uint256-address-}[`++ERC7579AlreadyInstalledModule(moduleTypeId, module)++`] +* {xref-ERC7579Utils-ERC7579UnsupportedModuleType-uint256-}[`++ERC7579UnsupportedModuleType(moduleTypeId)++`] +* {xref-ERC7579Utils-ERC7579DecodingError--}[`++ERC7579DecodingError()++`] + +-- + +[.contract-index] +.Internal Variables +-- +* {xref-ERC7579Utils-CALLTYPE_SINGLE-CallType}[`++CallType constant CALLTYPE_SINGLE++`] +* {xref-ERC7579Utils-CALLTYPE_BATCH-CallType}[`++CallType constant CALLTYPE_BATCH++`] +* {xref-ERC7579Utils-CALLTYPE_DELEGATECALL-CallType}[`++CallType constant CALLTYPE_DELEGATECALL++`] +* {xref-ERC7579Utils-EXECTYPE_DEFAULT-ExecType}[`++ExecType constant EXECTYPE_DEFAULT++`] +* {xref-ERC7579Utils-EXECTYPE_TRY-ExecType}[`++ExecType constant EXECTYPE_TRY++`] + +-- + +[.contract-item] +[[ERC7579Utils-execSingle-bytes-ExecType-]] +==== `[.contract-item-name]#++execSingle++#++(bytes executionCalldata, ExecType execType) → bytes[] returnData++` [.item-kind]#internal# + +Executes a single call. + +[.contract-item] +[[ERC7579Utils-execBatch-bytes-ExecType-]] +==== `[.contract-item-name]#++execBatch++#++(bytes executionCalldata, ExecType execType) → bytes[] returnData++` [.item-kind]#internal# + +Executes a batch of calls. + +[.contract-item] +[[ERC7579Utils-execDelegateCall-bytes-ExecType-]] +==== `[.contract-item-name]#++execDelegateCall++#++(bytes executionCalldata, ExecType execType) → bytes[] returnData++` [.item-kind]#internal# + +Executes a delegate call. + +[.contract-item] +[[ERC7579Utils-encodeMode-CallType-ExecType-ModeSelector-ModePayload-]] +==== `[.contract-item-name]#++encodeMode++#++(CallType callType, ExecType execType, ModeSelector selector, ModePayload payload) → Mode mode++` [.item-kind]#internal# + +Encodes the mode with the provided parameters. See {decodeMode}. + +[.contract-item] +[[ERC7579Utils-decodeMode-Mode-]] +==== `[.contract-item-name]#++decodeMode++#++(Mode mode) → CallType callType, ExecType execType, ModeSelector selector, ModePayload payload++` [.item-kind]#internal# + +Decodes the mode into its parameters. See {encodeMode}. + +[.contract-item] +[[ERC7579Utils-encodeSingle-address-uint256-bytes-]] +==== `[.contract-item-name]#++encodeSingle++#++(address target, uint256 value, bytes callData) → bytes executionCalldata++` [.item-kind]#internal# + +Encodes a single call execution. See {decodeSingle}. + +[.contract-item] +[[ERC7579Utils-decodeSingle-bytes-]] +==== `[.contract-item-name]#++decodeSingle++#++(bytes executionCalldata) → address target, uint256 value, bytes callData++` [.item-kind]#internal# + +Decodes a single call execution. See {encodeSingle}. + +[.contract-item] +[[ERC7579Utils-encodeDelegate-address-bytes-]] +==== `[.contract-item-name]#++encodeDelegate++#++(address target, bytes callData) → bytes executionCalldata++` [.item-kind]#internal# + +Encodes a delegate call execution. See {decodeDelegate}. + +[.contract-item] +[[ERC7579Utils-decodeDelegate-bytes-]] +==== `[.contract-item-name]#++decodeDelegate++#++(bytes executionCalldata) → address target, bytes callData++` [.item-kind]#internal# + +Decodes a delegate call execution. See {encodeDelegate}. + +[.contract-item] +[[ERC7579Utils-encodeBatch-struct-Execution---]] +==== `[.contract-item-name]#++encodeBatch++#++(struct Execution[] executionBatch) → bytes executionCalldata++` [.item-kind]#internal# + +Encodes a batch of executions. See {decodeBatch}. + +[.contract-item] +[[ERC7579Utils-decodeBatch-bytes-]] +==== `[.contract-item-name]#++decodeBatch++#++(bytes executionCalldata) → struct Execution[] executionBatch++` [.item-kind]#internal# + +Decodes a batch of executions. See {encodeBatch}. + +NOTE: This function runs some checks and will throw a {ERC7579DecodingError} if the input is not properly formatted. + +[.contract-item] +[[ERC7579Utils-ERC7579TryExecuteFail-uint256-bytes-]] +==== `[.contract-item-name]#++ERC7579TryExecuteFail++#++(uint256 batchExecutionIndex, bytes returndata)++` [.item-kind]#event# + +Emits when an {EXECTYPE_TRY} execution fails. + +[.contract-item] +[[ERC7579Utils-ERC7579UnsupportedCallType-CallType-]] +==== `[.contract-item-name]#++ERC7579UnsupportedCallType++#++(CallType callType)++` [.item-kind]#error# + +The provided {CallType} is not supported. + +[.contract-item] +[[ERC7579Utils-ERC7579UnsupportedExecType-ExecType-]] +==== `[.contract-item-name]#++ERC7579UnsupportedExecType++#++(ExecType execType)++` [.item-kind]#error# + +The provided {ExecType} is not supported. + +[.contract-item] +[[ERC7579Utils-ERC7579MismatchedModuleTypeId-uint256-address-]] +==== `[.contract-item-name]#++ERC7579MismatchedModuleTypeId++#++(uint256 moduleTypeId, address module)++` [.item-kind]#error# + +The provided module doesn't match the provided module type. + +[.contract-item] +[[ERC7579Utils-ERC7579UninstalledModule-uint256-address-]] +==== `[.contract-item-name]#++ERC7579UninstalledModule++#++(uint256 moduleTypeId, address module)++` [.item-kind]#error# + +The module is not installed. + +[.contract-item] +[[ERC7579Utils-ERC7579AlreadyInstalledModule-uint256-address-]] +==== `[.contract-item-name]#++ERC7579AlreadyInstalledModule++#++(uint256 moduleTypeId, address module)++` [.item-kind]#error# + +The module is already installed. + +[.contract-item] +[[ERC7579Utils-ERC7579UnsupportedModuleType-uint256-]] +==== `[.contract-item-name]#++ERC7579UnsupportedModuleType++#++(uint256 moduleTypeId)++` [.item-kind]#error# + +The module type is not supported. + +[.contract-item] +[[ERC7579Utils-ERC7579DecodingError--]] +==== `[.contract-item-name]#++ERC7579DecodingError++#++()++` [.item-kind]#error# + +Input calldata not properly formatted and possibly malicious. + +[.contract-item] +[[ERC7579Utils-CALLTYPE_SINGLE-CallType]] +==== `CallType [.contract-item-name]#++CALLTYPE_SINGLE++#` [.item-kind]#internal constant# + +A single `call` execution. + +[.contract-item] +[[ERC7579Utils-CALLTYPE_BATCH-CallType]] +==== `CallType [.contract-item-name]#++CALLTYPE_BATCH++#` [.item-kind]#internal constant# + +A batch of `call` executions. + +[.contract-item] +[[ERC7579Utils-CALLTYPE_DELEGATECALL-CallType]] +==== `CallType [.contract-item-name]#++CALLTYPE_DELEGATECALL++#` [.item-kind]#internal constant# + +A `delegatecall` execution. + +[.contract-item] +[[ERC7579Utils-EXECTYPE_DEFAULT-ExecType]] +==== `ExecType [.contract-item-name]#++EXECTYPE_DEFAULT++#` [.item-kind]#internal constant# + +Default execution type that reverts on failure. + +[.contract-item] +[[ERC7579Utils-EXECTYPE_TRY-ExecType]] +==== `ExecType [.contract-item-name]#++EXECTYPE_TRY++#` [.item-kind]#internal constant# + +Execution type that does not revert on failure. + diff --git a/docs/modules/api/pages/finance.adoc b/docs/modules/api/pages/finance.adoc index 253b1edad..0e3861eb8 100644 --- a/docs/modules/api/pages/finance.adoc +++ b/docs/modules/api/pages/finance.adoc @@ -58,7 +58,7 @@ This directory includes primitives for financial systems: [.contract] [[VestingWallet]] -=== `++VestingWallet++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/finance/VestingWallet.sol[{github-icon},role=heading-link] +=== `++VestingWallet++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/finance/VestingWallet.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -83,6 +83,11 @@ 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. +NOTE: Chains with support for native ERC20s may allow the vesting wallet to withdraw the underlying asset as both an +ERC20 and as native currency. For example, if chain C supports token A and the wallet gets deposited 100 A, then +at 50% of the vesting period, the beneficiary can withdraw 50 A as ERC20 and 25 A as native currency (totaling 75 A). +Consider disabling one of the withdrawal methods. + [.contract-index] .Functions -- diff --git a/docs/modules/api/pages/governance.adoc b/docs/modules/api/pages/governance.adoc index b578dd022..9cddbf4de 100644 --- a/docs/modules/api/pages/governance.adoc +++ b/docs/modules/api/pages/governance.adoc @@ -6,6 +6,7 @@ :GovernorVotesQuorumFraction: pass:normal[xref:governance.adoc#GovernorVotesQuorumFraction[`GovernorVotesQuorumFraction`]] :GovernorCountingSimple: pass:normal[xref:governance.adoc#GovernorCountingSimple[`GovernorCountingSimple`]] :GovernorCountingFractional: pass:normal[xref:governance.adoc#GovernorCountingFractional[`GovernorCountingFractional`]] +:GovernorCountingOverridable: pass:normal[xref:governance.adoc#GovernorCountingOverridable[`GovernorCountingOverridable`]] :GovernorTimelockAccess: pass:normal[xref:governance.adoc#GovernorTimelockAccess[`GovernorTimelockAccess`]] :AccessManager: pass:normal[xref:access.adoc#AccessManager[`AccessManager`]] :GovernorTimelockControl: pass:normal[xref:governance.adoc#GovernorTimelockControl[`GovernorTimelockControl`]] @@ -93,6 +94,7 @@ :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-_tallyUpdated-uint256-: xref:governance.adoc#Governor-_tallyUpdated-uint256- :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- @@ -117,6 +119,7 @@ :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-_validateStateBitmap-uint256-bytes32-: xref:governance.adoc#Governor-_validateStateBitmap-uint256-bytes32- :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-- @@ -214,6 +217,7 @@ :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-_tallyUpdated-uint256-: xref:governance.adoc#Governor-_tallyUpdated-uint256- :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- @@ -238,6 +242,7 @@ :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-_validateStateBitmap-uint256-bytes32-: xref:governance.adoc#Governor-_validateStateBitmap-uint256-bytes32- :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-- @@ -307,6 +312,7 @@ :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-_tallyUpdated-uint256-: xref:governance.adoc#Governor-_tallyUpdated-uint256- :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- @@ -331,6 +337,7 @@ :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-_validateStateBitmap-uint256-bytes32-: xref:governance.adoc#Governor-_validateStateBitmap-uint256-bytes32- :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-- @@ -403,6 +410,7 @@ :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-_tallyUpdated-uint256-: xref:governance.adoc#Governor-_tallyUpdated-uint256- :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- @@ -427,6 +435,7 @@ :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-_validateStateBitmap-uint256-bytes32-: xref:governance.adoc#Governor-_validateStateBitmap-uint256-bytes32- :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-- @@ -496,6 +505,7 @@ :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-_tallyUpdated-uint256-: xref:governance.adoc#Governor-_tallyUpdated-uint256- :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- @@ -520,6 +530,7 @@ :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-_validateStateBitmap-uint256-bytes32-: xref:governance.adoc#Governor-_validateStateBitmap-uint256-bytes32- :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-- @@ -597,6 +608,7 @@ :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-_tallyUpdated-uint256-: xref:governance.adoc#Governor-_tallyUpdated-uint256- :xref-Governor-_defaultParams--: xref:governance.adoc#Governor-_defaultParams-- :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- @@ -617,6 +629,7 @@ :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-_validateStateBitmap-uint256-bytes32-: xref:governance.adoc#Governor-_validateStateBitmap-uint256-bytes32- :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-- @@ -706,6 +719,7 @@ :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-_tallyUpdated-uint256-: xref:governance.adoc#Governor-_tallyUpdated-uint256- :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- @@ -726,6 +740,7 @@ :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-_validateStateBitmap-uint256-bytes32-: xref:governance.adoc#Governor-_validateStateBitmap-uint256-bytes32- :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-- @@ -806,6 +821,7 @@ :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-_tallyUpdated-uint256-: xref:governance.adoc#Governor-_tallyUpdated-uint256- :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- @@ -826,6 +842,7 @@ :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-_validateStateBitmap-uint256-bytes32-: xref:governance.adoc#Governor-_validateStateBitmap-uint256-bytes32- :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-- @@ -900,6 +917,7 @@ :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-_tallyUpdated-uint256-: xref:governance.adoc#Governor-_tallyUpdated-uint256- :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- @@ -924,6 +942,7 @@ :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-_validateStateBitmap-uint256-bytes32-: xref:governance.adoc#Governor-_validateStateBitmap-uint256-bytes32- :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-- @@ -972,7 +991,7 @@ :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-_tallyUpdated-uint256-: xref:governance.adoc#GovernorPreventLateQuorum-_tallyUpdated-uint256- :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- @@ -1009,12 +1028,14 @@ :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-_validateStateBitmap-uint256-bytes32-: xref:governance.adoc#Governor-_validateStateBitmap-uint256-bytes32- :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-- @@ -1060,7 +1081,6 @@ :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`]] @@ -1088,6 +1108,7 @@ :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-_tallyUpdated-uint256-: xref:governance.adoc#Governor-_tallyUpdated-uint256- :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- @@ -1111,6 +1132,7 @@ :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-_validateStateBitmap-uint256-bytes32-: xref:governance.adoc#Governor-_validateStateBitmap-uint256-bytes32- :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-- @@ -1160,6 +1182,7 @@ :ERC721-_update: pass:normal[xref:token/ERC721.adoc#ERC721-_update-address-uint256-address-[`ERC721._update`]] :xref-Votes-clock--: xref:governance.adoc#Votes-clock-- :xref-Votes-CLOCK_MODE--: xref:governance.adoc#Votes-CLOCK_MODE-- +:xref-Votes-_validateTimepoint-uint256-: xref:governance.adoc#Votes-_validateTimepoint-uint256- :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- @@ -1190,6 +1213,49 @@ :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`]] +:Votes: pass:normal[xref:governance.adoc#Votes[`Votes`]] +:Votes: pass:normal[xref:governance.adoc#Votes[`Votes`]] +:Votes: pass:normal[xref:governance.adoc#Votes[`Votes`]] +:VotesExtended: pass:normal[xref:governance.adoc#VotesExtended[`VotesExtended`]] +:VotesExtended: pass:normal[xref:governance.adoc#VotesExtended[`VotesExtended`]] +:ERC20Votes: pass:normal[xref:token/ERC20.adoc#ERC20Votes[`ERC20Votes`]] +:ERC721Votes: pass:normal[xref:token/ERC721.adoc#ERC721Votes[`ERC721Votes`]] +:VotesExtended: pass:normal[xref:governance.adoc#VotesExtended[`VotesExtended`]] +:xref-VotesExtended-getPastDelegate-address-uint256-: xref:governance.adoc#VotesExtended-getPastDelegate-address-uint256- +:xref-VotesExtended-getPastBalanceOf-address-uint256-: xref:governance.adoc#VotesExtended-getPastBalanceOf-address-uint256- +:xref-VotesExtended-_delegate-address-address-: xref:governance.adoc#VotesExtended-_delegate-address-address- +:xref-VotesExtended-_transferVotingUnits-address-address-uint256-: xref:governance.adoc#VotesExtended-_transferVotingUnits-address-address-uint256- +:xref-Votes-clock--: xref:governance.adoc#Votes-clock-- +:xref-Votes-CLOCK_MODE--: xref:governance.adoc#Votes-CLOCK_MODE-- +:xref-Votes-_validateTimepoint-uint256-: xref:governance.adoc#Votes-_validateTimepoint-uint256- +: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-_moveDelegateVotes-address-address-uint256-: xref:governance.adoc#Votes-_moveDelegateVotes-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`]] @@ -1281,6 +1347,8 @@ Counting modules determine valid voting options. * {GovernorCountingFractional}: A more modular voting system that allows a user to vote with only part of its voting power, and to split that weight arbitrarily between the 3 different options (Against, For and Abstain). +* {GovernorCountingOverridable}: An extended version of `GovernorCountingSimple` which allows delegatees to override their delegates while the vote is live. + 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. * {GovernorTimelockAccess}: Connects with an instance of an {AccessManager}. This allows restrictions (and delays) enforced by the manager to be considered by the Governor and integrated into the AccessManager's "schedule + execute" workflow. @@ -1359,7 +1427,7 @@ NOTE: Functions of the `Governor` contract do not include access control. If you [.contract] [[IGovernor]] -=== `++IGovernor++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/governance/IGovernor.sol[{github-icon},role=heading-link] +=== `++IGovernor++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/governance/IGovernor.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -1858,6 +1926,7 @@ If the `voter` is a contract, the signature is not valid using {IERC1271-isValid :_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++`]] +:_tallyUpdated: pass:normal[xref:#Governor-_tallyUpdated-uint256-[`++_tallyUpdated++`]] :_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++`]] @@ -1882,6 +1951,7 @@ If the `voter` is a contract, the signature is not valid using {IERC1271-isValid :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++`]] +:_validateStateBitmap: pass:normal[xref:#Governor-_validateStateBitmap-uint256-bytes32-[`++_validateStateBitmap++`]] :_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++`]] @@ -1891,7 +1961,7 @@ If the `voter` is a contract, the signature is not valid using {IERC1271-isValid [.contract] [[Governor]] -=== `++Governor++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/governance/Governor.sol[{github-icon},role=heading-link] +=== `++Governor++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/governance/Governor.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -1933,6 +2003,7 @@ This contract is abstract and requires several functions to be implemented in va * {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, totalWeight, params)++`] +* {xref-Governor-_tallyUpdated-uint256-}[`++_tallyUpdated(proposalId)++`] * {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)++`] @@ -1957,6 +2028,7 @@ This contract is abstract and requires several functions to be implemented in va * {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-_validateStateBitmap-uint256-bytes32-}[`++_validateStateBitmap(proposalId, allowedStates)++`] * {xref-Governor-_isValidDescriptionForProposer-address-string-}[`++_isValidDescriptionForProposer(proposer, description)++`] * {xref-Governor-clock--}[`++clock()++`] * {xref-Governor-CLOCK_MODE--}[`++CLOCK_MODE()++`] @@ -2230,6 +2302,14 @@ Register a vote for `proposalId` by `account` with a given `support`, voting `we Note: Support is generic and can represent various things depending on the voting system used. +[.contract-item] +[[Governor-_tallyUpdated-uint256-]] +==== `[.contract-item-name]#++_tallyUpdated++#++(uint256 proposalId)++` [.item-kind]#internal# + +Hook that should be called every time the tally for a proposal is updated. + +Note: This function must run successfully. Reverts will result in the bricking of governance + [.contract-item] [[Governor-_defaultParams--]] ==== `[.contract-item-name]#++_defaultParams++#++() → bytes++` [.item-kind]#internal# @@ -2418,6 +2498,15 @@ the underlying position in the `ProposalState` enum. For example: ^-- Active ^- Pending +[.contract-item] +[[Governor-_validateStateBitmap-uint256-bytes32-]] +==== `[.contract-item-name]#++_validateStateBitmap++#++(uint256 proposalId, bytes32 allowedStates) → enum IGovernor.ProposalState++` [.item-kind]#internal# + +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. + [.contract-item] [[Governor-_isValidDescriptionForProposer-address-string-]] ==== `[.contract-item-name]#++_isValidDescriptionForProposer++#++(address proposer, string description) → bool++` [.item-kind]#internal# @@ -2491,7 +2580,7 @@ quorum depending on values such as the totalSupply of a token at this timepoint [.contract] [[GovernorCountingSimple]] -=== `++GovernorCountingSimple++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/governance/extensions/GovernorCountingSimple.sol[{github-icon},role=heading-link] +=== `++GovernorCountingSimple++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/governance/extensions/GovernorCountingSimple.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -2526,6 +2615,7 @@ Extension of {Governor} for simple, 3 options, vote counting. * {xref-Governor-proposalNeedsQueuing-uint256-}[`++proposalNeedsQueuing()++`] * {xref-Governor-_checkGovernance--}[`++_checkGovernance()++`] * {xref-Governor-_getVotes-address-uint256-bytes-}[`++_getVotes(account, timepoint, params)++`] +* {xref-Governor-_tallyUpdated-uint256-}[`++_tallyUpdated(proposalId)++`] * {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)++`] @@ -2550,6 +2640,7 @@ Extension of {Governor} for simple, 3 options, vote counting. * {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-_validateStateBitmap-uint256-bytes32-}[`++_validateStateBitmap(proposalId, allowedStates)++`] * {xref-Governor-_isValidDescriptionForProposer-address-string-}[`++_isValidDescriptionForProposer(proposer, description)++`] * {xref-Governor-clock--}[`++clock()++`] * {xref-Governor-CLOCK_MODE--}[`++CLOCK_MODE()++`] @@ -2741,7 +2832,7 @@ See {Governor-_countVote}. In this module, the support follows the `VoteType` en [.contract] [[GovernorCountingFractional]] -=== `++GovernorCountingFractional++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/governance/extensions/GovernorCountingFractional.sol[{github-icon},role=heading-link] +=== `++GovernorCountingFractional++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/governance/extensions/GovernorCountingFractional.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -2798,6 +2889,7 @@ _Available since v5.1._ * {xref-Governor-proposalNeedsQueuing-uint256-}[`++proposalNeedsQueuing()++`] * {xref-Governor-_checkGovernance--}[`++_checkGovernance()++`] * {xref-Governor-_getVotes-address-uint256-bytes-}[`++_getVotes(account, timepoint, params)++`] +* {xref-Governor-_tallyUpdated-uint256-}[`++_tallyUpdated(proposalId)++`] * {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)++`] @@ -2822,6 +2914,7 @@ _Available since v5.1._ * {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-_validateStateBitmap-uint256-bytes32-}[`++_validateStateBitmap(proposalId, allowedStates)++`] * {xref-Governor-_isValidDescriptionForProposer-address-string-}[`++_isValidDescriptionForProposer(proposer, description)++`] * {xref-Governor-clock--}[`++clock()++`] * {xref-Governor-CLOCK_MODE--}[`++CLOCK_MODE()++`] @@ -3089,7 +3182,7 @@ A fractional vote params uses more votes than are available for that user. [.contract] [[GovernorVotes]] -=== `++GovernorVotes++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/governance/extensions/GovernorVotes.sol[{github-icon},role=heading-link] +=== `++GovernorVotes++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/governance/extensions/GovernorVotes.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -3126,6 +3219,7 @@ token. * {xref-Governor-_quorumReached-uint256-}[`++_quorumReached(proposalId)++`] * {xref-Governor-_voteSucceeded-uint256-}[`++_voteSucceeded(proposalId)++`] * {xref-Governor-_countVote-uint256-address-uint8-uint256-bytes-}[`++_countVote(proposalId, account, support, totalWeight, params)++`] +* {xref-Governor-_tallyUpdated-uint256-}[`++_tallyUpdated(proposalId)++`] * {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)++`] @@ -3150,6 +3244,7 @@ token. * {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-_validateStateBitmap-uint256-bytes32-}[`++_validateStateBitmap(proposalId, allowedStates)++`] * {xref-Governor-_isValidDescriptionForProposer-address-string-}[`++_isValidDescriptionForProposer(proposer, description)++`] * {xref-Governor-votingDelay--}[`++votingDelay()++`] * {xref-Governor-votingPeriod--}[`++votingPeriod()++`] @@ -3331,7 +3426,7 @@ Machine-readable description of the clock as specified in ERC-6372. [.contract] [[GovernorVotesQuorumFraction]] -=== `++GovernorVotesQuorumFraction++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/governance/extensions/GovernorVotesQuorumFraction.sol[{github-icon},role=heading-link] +=== `++GovernorVotesQuorumFraction++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/governance/extensions/GovernorVotesQuorumFraction.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -3377,6 +3472,7 @@ fraction of the total supply. * {xref-Governor-_quorumReached-uint256-}[`++_quorumReached(proposalId)++`] * {xref-Governor-_voteSucceeded-uint256-}[`++_voteSucceeded(proposalId)++`] * {xref-Governor-_countVote-uint256-address-uint8-uint256-bytes-}[`++_countVote(proposalId, account, support, totalWeight, params)++`] +* {xref-Governor-_tallyUpdated-uint256-}[`++_tallyUpdated(proposalId)++`] * {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)++`] @@ -3401,6 +3497,7 @@ fraction of the total supply. * {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-_validateStateBitmap-uint256-bytes32-}[`++_validateStateBitmap(proposalId, allowedStates)++`] * {xref-Governor-_isValidDescriptionForProposer-address-string-}[`++_isValidDescriptionForProposer(proposer, description)++`] * {xref-Governor-votingDelay--}[`++votingDelay()++`] * {xref-Governor-votingPeriod--}[`++votingPeriod()++`] @@ -3644,7 +3741,7 @@ The quorum set is not a valid fraction. [.contract] [[GovernorTimelockAccess]] -=== `++GovernorTimelockAccess++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/governance/extensions/GovernorTimelockAccess.sol[{github-icon},role=heading-link] +=== `++GovernorTimelockAccess++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/governance/extensions/GovernorTimelockAccess.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -3714,6 +3811,7 @@ the same time. See {AccessManager-schedule} for a workaround. * {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, totalWeight, params)++`] +* {xref-Governor-_tallyUpdated-uint256-}[`++_tallyUpdated(proposalId)++`] * {xref-Governor-_defaultParams--}[`++_defaultParams()++`] * {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)++`] @@ -3734,6 +3832,7 @@ the same time. See {AccessManager-schedule} for a workaround. * {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-_validateStateBitmap-uint256-bytes32-}[`++_validateStateBitmap(proposalId, allowedStates)++`] * {xref-Governor-_isValidDescriptionForProposer-address-string-}[`++_isValidDescriptionForProposer(proposer, description)++`] * {xref-Governor-clock--}[`++clock()++`] * {xref-Governor-CLOCK_MODE--}[`++CLOCK_MODE()++`] @@ -4014,7 +4113,7 @@ See {IGovernor-_cancel} [.contract] [[GovernorTimelockControl]] -=== `++GovernorTimelockControl++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/governance/extensions/GovernorTimelockControl.sol[{github-icon},role=heading-link] +=== `++GovernorTimelockControl++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/governance/extensions/GovernorTimelockControl.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -4064,6 +4163,7 @@ proposals that have been approved by the voters, effectively executing a Denial * {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, totalWeight, params)++`] +* {xref-Governor-_tallyUpdated-uint256-}[`++_tallyUpdated(proposalId)++`] * {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)++`] @@ -4084,6 +4184,7 @@ proposals that have been approved by the voters, effectively executing a Denial * {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-_validateStateBitmap-uint256-bytes32-}[`++_validateStateBitmap(proposalId, allowedStates)++`] * {xref-Governor-_isValidDescriptionForProposer-address-string-}[`++_isValidDescriptionForProposer(proposer, description)++`] * {xref-Governor-clock--}[`++clock()++`] * {xref-Governor-CLOCK_MODE--}[`++CLOCK_MODE()++`] @@ -4308,7 +4409,7 @@ Emitted when the timelock controller used for proposal execution is modified. [.contract] [[GovernorTimelockCompound]] -=== `++GovernorTimelockCompound++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/governance/extensions/GovernorTimelockCompound.sol[{github-icon},role=heading-link] +=== `++GovernorTimelockCompound++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/governance/extensions/GovernorTimelockCompound.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -4355,6 +4456,7 @@ inaccessible from a proposal, unless executed via {Governor-relay}. * {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, totalWeight, params)++`] +* {xref-Governor-_tallyUpdated-uint256-}[`++_tallyUpdated(proposalId)++`] * {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)++`] @@ -4375,6 +4477,7 @@ inaccessible from a proposal, unless executed via {Governor-relay}. * {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-_validateStateBitmap-uint256-bytes32-}[`++_validateStateBitmap(proposalId, allowedStates)++`] * {xref-Governor-_isValidDescriptionForProposer-address-string-}[`++_isValidDescriptionForProposer(proposer, description)++`] * {xref-Governor-clock--}[`++clock()++`] * {xref-Governor-CLOCK_MODE--}[`++CLOCK_MODE()++`] @@ -4614,7 +4717,7 @@ Emitted when the timelock controller used for proposal execution is modified. [.contract] [[GovernorSettings]] -=== `++GovernorSettings++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/governance/extensions/GovernorSettings.sol[{github-icon},role=heading-link] +=== `++GovernorSettings++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/governance/extensions/GovernorSettings.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -4655,6 +4758,7 @@ Extension of {Governor} for settings updatable through governance. * {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, totalWeight, params)++`] +* {xref-Governor-_tallyUpdated-uint256-}[`++_tallyUpdated(proposalId)++`] * {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)++`] @@ -4679,6 +4783,7 @@ Extension of {Governor} for settings updatable through governance. * {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-_validateStateBitmap-uint256-bytes32-}[`++_validateStateBitmap(proposalId, allowedStates)++`] * {xref-Governor-_isValidDescriptionForProposer-address-string-}[`++_isValidDescriptionForProposer(proposer, description)++`] * {xref-Governor-clock--}[`++clock()++`] * {xref-Governor-CLOCK_MODE--}[`++CLOCK_MODE()++`] @@ -4912,14 +5017,14 @@ Emits a {ProposalThresholdSet} event. :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++`]] +:_tallyUpdated: pass:normal[xref:#GovernorPreventLateQuorum-_tallyUpdated-uint256-[`++_tallyUpdated++`]] :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.1.0/contracts/governance/extensions/GovernorPreventLateQuorum.sol[{github-icon},role=heading-link] +=== `++GovernorPreventLateQuorum++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/governance/extensions/GovernorPreventLateQuorum.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -4939,7 +5044,7 @@ proposal. -- * {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-_tallyUpdated-uint256-}[`++_tallyUpdated(proposalId)++`] * {xref-GovernorPreventLateQuorum-lateQuorumVoteExtension--}[`++lateQuorumVoteExtension()++`] * {xref-GovernorPreventLateQuorum-setLateQuorumVoteExtension-uint48-}[`++setLateQuorumVoteExtension(newVoteExtension)++`] * {xref-GovernorPreventLateQuorum-_setLateQuorumVoteExtension-uint48-}[`++_setLateQuorumVoteExtension(newVoteExtension)++`] @@ -4979,12 +5084,14 @@ proposal. * {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-_validateStateBitmap-uint256-bytes32-}[`++_validateStateBitmap(proposalId, allowedStates)++`] * {xref-Governor-_isValidDescriptionForProposer-address-string-}[`++_isValidDescriptionForProposer(proposer, description)++`] * {xref-Governor-clock--}[`++clock()++`] * {xref-Governor-CLOCK_MODE--}[`++CLOCK_MODE()++`] @@ -5147,11 +5254,10 @@ Returns the proposal deadline, which may have been extended beyond that set at p 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# +[[GovernorPreventLateQuorum-_tallyUpdated-uint256-]] +==== `[.contract-item-name]#++_tallyUpdated++#++(uint256 proposalId)++` [.item-kind]#internal# -Casts a vote and detects if it caused quorum to be reached, potentially extending the voting period. See -{Governor-_castVote}. +Vote tally updated and detects if it caused quorum to be reached, potentially extending the voting period. May emit a {ProposalExtended} event. @@ -5203,7 +5309,7 @@ Emitted when the {lateQuorumVoteExtension} parameter is changed. [.contract] [[GovernorStorage]] -=== `++GovernorStorage++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/governance/extensions/GovernorStorage.sol[{github-icon},role=heading-link] +=== `++GovernorStorage++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/governance/extensions/GovernorStorage.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -5248,6 +5354,7 @@ Use cases for this module include: * {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, totalWeight, params)++`] +* {xref-Governor-_tallyUpdated-uint256-}[`++_tallyUpdated(proposalId)++`] * {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)++`] @@ -5271,6 +5378,7 @@ Use cases for this module include: * {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-_validateStateBitmap-uint256-bytes32-}[`++_validateStateBitmap(proposalId, allowedStates)++`] * {xref-Governor-_isValidDescriptionForProposer-address-string-}[`++_isValidDescriptionForProposer(proposer, description)++`] * {xref-Governor-clock--}[`++clock()++`] * {xref-Governor-CLOCK_MODE--}[`++CLOCK_MODE()++`] @@ -5463,6 +5571,7 @@ Returns the details (including the proposalId) of a proposal given its sequentia :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++`]] +:_validateTimepoint: pass:normal[xref:#Votes-_validateTimepoint-uint256-[`++_validateTimepoint++`]] :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++`]] @@ -5479,7 +5588,7 @@ Returns the details (including the proposalId) of a proposal given its sequentia [.contract] [[Votes]] -=== `++Votes++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/governance/utils/Votes.sol[{github-icon},role=heading-link] +=== `++Votes++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/governance/utils/Votes.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -5508,6 +5617,7 @@ previous example, it would be included in {ERC721-_update}). -- * {xref-Votes-clock--}[`++clock()++`] * {xref-Votes-CLOCK_MODE--}[`++CLOCK_MODE()++`] +* {xref-Votes-_validateTimepoint-uint256-}[`++_validateTimepoint(timepoint)++`] * {xref-Votes-getVotes-address-}[`++getVotes(account)++`] * {xref-Votes-getPastVotes-address-uint256-}[`++getPastVotes(account, timepoint)++`] * {xref-Votes-getPastTotalSupply-uint256-}[`++getPastTotalSupply(timepoint)++`] @@ -5618,6 +5728,12 @@ checkpoints (and voting), in which case {CLOCK_MODE} should be overridden as wel Machine-readable description of the clock as specified in ERC-6372. +[.contract-item] +[[Votes-_validateTimepoint-uint256-]] +==== `[.contract-item-name]#++_validateTimepoint++#++(uint256 timepoint) → uint48++` [.item-kind]#internal# + +Validate that a timepoint is in the past, and return it as a uint48. + [.contract-item] [[Votes-getVotes-address-]] ==== `[.contract-item-name]#++getVotes++#++(address account) → uint256++` [.item-kind]#public# @@ -5725,6 +5841,195 @@ The clock was incorrectly modified. Lookup to future votes is not available. +:getPastDelegate: pass:normal[xref:#VotesExtended-getPastDelegate-address-uint256-[`++getPastDelegate++`]] +:getPastBalanceOf: pass:normal[xref:#VotesExtended-getPastBalanceOf-address-uint256-[`++getPastBalanceOf++`]] +:_delegate: pass:normal[xref:#VotesExtended-_delegate-address-address-[`++_delegate++`]] +:_transferVotingUnits: pass:normal[xref:#VotesExtended-_transferVotingUnits-address-address-uint256-[`++_transferVotingUnits++`]] + +[.contract] +[[VotesExtended]] +=== `++VotesExtended++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/governance/utils/VotesExtended.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/governance/utils/VotesExtended.sol"; +``` + +Extension of {Votes} that adds checkpoints for delegations and balances. + +WARNING: While this contract extends {Votes}, valid uses of {Votes} may not be compatible with +{VotesExtended} without additional considerations. This implementation of {_transferVotingUnits} must +run AFTER the voting weight movement is registered, such that it is reflected on {_getVotingUnits}. + +Said differently, {VotesExtended} MUST be integrated in a way that calls {_transferVotingUnits} AFTER the +asset transfer is registered and balances are updated: + +```solidity +contract VotingToken is Token, VotesExtended { + function transfer(address from, address to, uint256 tokenId) public override { + super.transfer(from, to, tokenId); // <- Perform the transfer first ... + _transferVotingUnits(from, to, 1); // <- ... then call _transferVotingUnits. + } + + function _getVotingUnits(address account) internal view override returns (uint256) { + return balanceOf(account); + } +} +``` + +{ERC20Votes} and {ERC721Votes} follow this pattern and are thus safe to use with {VotesExtended}. + +[.contract-index] +.Functions +-- +* {xref-VotesExtended-getPastDelegate-address-uint256-}[`++getPastDelegate(account, timepoint)++`] +* {xref-VotesExtended-getPastBalanceOf-address-uint256-}[`++getPastBalanceOf(account, timepoint)++`] +* {xref-VotesExtended-_delegate-address-address-}[`++_delegate(account, delegatee)++`] +* {xref-VotesExtended-_transferVotingUnits-address-address-uint256-}[`++_transferVotingUnits(from, to, amount)++`] + +[.contract-subindex-inherited] +.Votes +* {xref-Votes-clock--}[`++clock()++`] +* {xref-Votes-CLOCK_MODE--}[`++CLOCK_MODE()++`] +* {xref-Votes-_validateTimepoint-uint256-}[`++_validateTimepoint(timepoint)++`] +* {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-_moveDelegateVotes-address-address-uint256-}[`++_moveDelegateVotes(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] +.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-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-item] +[[VotesExtended-getPastDelegate-address-uint256-]] +==== `[.contract-item-name]#++getPastDelegate++#++(address account, uint256 timepoint) → address++` [.item-kind]#public# + +Returns the delegate of an `account` 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] +[[VotesExtended-getPastBalanceOf-address-uint256-]] +==== `[.contract-item-name]#++getPastBalanceOf++#++(address account, uint256 timepoint) → uint256++` [.item-kind]#public# + +Returns the `balanceOf` of an `account` 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] +[[VotesExtended-_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] +[[VotesExtended-_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. + == 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}. @@ -5768,7 +6073,7 @@ In a governance system, the {TimelockController} contract is in charge of introd [.contract] [[TimelockController]] -=== `++TimelockController++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/governance/TimelockController.sol[{github-icon},role=heading-link] +=== `++TimelockController++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/governance/TimelockController.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity diff --git a/docs/modules/api/pages/interfaces.adoc b/docs/modules/api/pages/interfaces.adoc index 2c0e6ee0d..4760ff5bb 100644 --- a/docs/modules/api/pages/interfaces.adoc +++ b/docs/modules/api/pages/interfaces.adoc @@ -215,7 +215,7 @@ are useful to interact with third party contracts that implement them. [.contract] [[IERC20Errors]] -=== `++IERC20Errors++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/interfaces/draft-IERC6093.sol[{github-icon},role=heading-link] +=== `++IERC20Errors++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/interfaces/draft-IERC6093.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -284,7 +284,7 @@ Indicates a failure with the `spender` to be approved. Used in approvals. [.contract] [[IERC721Errors]] -=== `++IERC721Errors++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/interfaces/draft-IERC6093.sol[{github-icon},role=heading-link] +=== `++IERC721Errors++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/interfaces/draft-IERC6093.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -367,7 +367,7 @@ Indicates a failure with the `operator` to be approved. Used in approvals. [.contract] [[IERC1155Errors]] -=== `++IERC1155Errors++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/interfaces/draft-IERC6093.sol[{github-icon},role=heading-link] +=== `++IERC1155Errors++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/interfaces/draft-IERC6093.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -437,7 +437,7 @@ Used in batch transfers. [.contract] [[IERC1271]] -=== `++IERC1271++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/interfaces/IERC1271.sol[{github-icon},role=heading-link] +=== `++IERC1271++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/interfaces/IERC1271.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -469,7 +469,7 @@ Should return whether the signature provided is valid for the provided data [.contract] [[IERC1363]] -=== `++IERC1363++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/interfaces/IERC1363.sol[{github-icon},role=heading-link] +=== `++IERC1363++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/interfaces/IERC1363.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -566,7 +566,7 @@ caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender` [.contract] [[IERC1363Receiver]] -=== `++IERC1363Receiver++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/interfaces/IERC1363Receiver.sol[{github-icon},role=heading-link] +=== `++IERC1363Receiver++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/interfaces/IERC1363Receiver.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -598,7 +598,7 @@ NOTE: To accept the transfer, this must return [.contract] [[IERC1363Spender]] -=== `++IERC1363Spender++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/interfaces/IERC1363Spender.sol[{github-icon},role=heading-link] +=== `++IERC1363Spender++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/interfaces/IERC1363Spender.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -630,7 +630,7 @@ NOTE: To accept the approval, this must return [.contract] [[IERC1820Implementer]] -=== `++IERC1820Implementer++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/interfaces/IERC1820Implementer.sol[{github-icon},role=heading-link] +=== `++IERC1820Implementer++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/interfaces/IERC1820Implementer.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -671,7 +671,7 @@ See {IERC1820Registry-setInterfaceImplementer}. [.contract] [[IERC1820Registry]] -=== `++IERC1820Registry++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/interfaces/IERC1820Registry.sol[{github-icon},role=heading-link] +=== `++IERC1820Registry++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/interfaces/IERC1820Registry.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -804,7 +804,7 @@ https://eips.ethereum.org/EIPS/eip-1820#interface-name[section of the ERC]. [.contract] [[IERC1822Proxiable]] -=== `++IERC1822Proxiable++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/interfaces/draft-IERC1822.sol[{github-icon},role=heading-link] +=== `++IERC1822Proxiable++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/interfaces/draft-IERC1822.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -834,7 +834,7 @@ function revert if invoked through a proxy. [.contract] [[IERC2612]] -=== `++IERC2612++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/interfaces/IERC2612.sol[{github-icon},role=heading-link] +=== `++IERC2612++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/interfaces/IERC2612.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -857,7 +857,7 @@ import "@openzeppelin/contracts/interfaces/IERC2612.sol"; [.contract] [[IERC2981]] -=== `++IERC2981++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/interfaces/IERC2981.sol[{github-icon},role=heading-link] +=== `++IERC2981++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/interfaces/IERC2981.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -896,7 +896,7 @@ royalty receiver and 0 tokens to the seller. Contracts dealing with royalty shou [.contract] [[IERC3156FlashLender]] -=== `++IERC3156FlashLender++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/interfaces/IERC3156FlashLender.sol[{github-icon},role=heading-link] +=== `++IERC3156FlashLender++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/interfaces/IERC3156FlashLender.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -937,7 +937,7 @@ Initiate a flash loan. [.contract] [[IERC3156FlashBorrower]] -=== `++IERC3156FlashBorrower++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/interfaces/IERC3156FlashBorrower.sol[{github-icon},role=heading-link] +=== `++IERC3156FlashBorrower++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/interfaces/IERC3156FlashBorrower.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -981,7 +981,7 @@ Receive a flash loan. [.contract] [[IERC4626]] -=== `++IERC4626++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/interfaces/IERC4626.sol[{github-icon},role=heading-link] +=== `++IERC4626++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/interfaces/IERC4626.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -1279,7 +1279,7 @@ Those methods should be performed separately. [.contract] [[IERC5313]] -=== `++IERC5313++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/interfaces/IERC5313.sol[{github-icon},role=heading-link] +=== `++IERC5313++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/interfaces/IERC5313.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -1308,7 +1308,7 @@ Gets the address of the owner. [.contract] [[IERC5267]] -=== `++IERC5267++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/interfaces/IERC5267.sol[{github-icon},role=heading-link] +=== `++IERC5267++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/interfaces/IERC5267.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -1344,7 +1344,7 @@ MAY be emitted to signal that the domain could have changed. [.contract] [[IERC5805]] -=== `++IERC5805++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/interfaces/IERC5805.sol[{github-icon},role=heading-link] +=== `++IERC5805++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/interfaces/IERC5805.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -1403,7 +1403,7 @@ import "@openzeppelin/contracts/interfaces/IERC5805.sol"; [.contract] [[IERC6372]] -=== `++IERC6372++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/interfaces/IERC6372.sol[{github-icon},role=heading-link] +=== `++IERC6372++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/interfaces/IERC6372.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -1434,7 +1434,7 @@ Description of the clock [.contract] [[IERC7674]] -=== `++IERC7674++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/interfaces/draft-IERC7674.sol[{github-icon},role=heading-link] +=== `++IERC7674++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/interfaces/draft-IERC7674.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity diff --git a/docs/modules/api/pages/metatx.adoc b/docs/modules/api/pages/metatx.adoc index 35a96dde3..fa8310d65 100644 --- a/docs/modules/api/pages/metatx.adoc +++ b/docs/modules/api/pages/metatx.adoc @@ -54,7 +54,7 @@ This directory includes contracts for adding meta-transaction capabilities (i.e. [.contract] [[ERC2771Context]] -=== `++ERC2771Context++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/metatx/ERC2771Context.sol[{github-icon},role=heading-link] +=== `++ERC2771Context++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/metatx/ERC2771Context.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -142,7 +142,7 @@ ERC-2771 specifies the context as being a single address (20 bytes). [.contract] [[ERC2771Forwarder]] -=== `++ERC2771Forwarder++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/metatx/ERC2771Forwarder.sol[{github-icon},role=heading-link] +=== `++ERC2771Forwarder++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/metatx/ERC2771Forwarder.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity diff --git a/docs/modules/api/pages/proxy.adoc b/docs/modules/api/pages/proxy.adoc index b09aebb3d..57cf4bd7a 100644 --- a/docs/modules/api/pages/proxy.adoc +++ b/docs/modules/api/pages/proxy.adoc @@ -100,10 +100,22 @@ :xref-Clones-cloneDeterministic-address-bytes32-uint256-: xref:proxy.adoc#Clones-cloneDeterministic-address-bytes32-uint256- :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-cloneWithImmutableArgs-address-bytes-: xref:proxy.adoc#Clones-cloneWithImmutableArgs-address-bytes- +:xref-Clones-cloneWithImmutableArgs-address-bytes-uint256-: xref:proxy.adoc#Clones-cloneWithImmutableArgs-address-bytes-uint256- +:xref-Clones-cloneDeterministicWithImmutableArgs-address-bytes-bytes32-: xref:proxy.adoc#Clones-cloneDeterministicWithImmutableArgs-address-bytes-bytes32- +:xref-Clones-cloneDeterministicWithImmutableArgs-address-bytes-bytes32-uint256-: xref:proxy.adoc#Clones-cloneDeterministicWithImmutableArgs-address-bytes-bytes32-uint256- +:xref-Clones-predictDeterministicAddressWithImmutableArgs-address-bytes-bytes32-address-: xref:proxy.adoc#Clones-predictDeterministicAddressWithImmutableArgs-address-bytes-bytes32-address- +:xref-Clones-predictDeterministicAddressWithImmutableArgs-address-bytes-bytes32-: xref:proxy.adoc#Clones-predictDeterministicAddressWithImmutableArgs-address-bytes-bytes32- +:xref-Clones-fetchCloneArgs-address-: xref:proxy.adoc#Clones-fetchCloneArgs-address- +:xref-Clones-CloneArgumentsTooLong--: xref:proxy.adoc#Clones-CloneArgumentsTooLong-- :xref-Clones-clone-address-: xref:proxy.adoc#Clones-clone-address- :xref-Clones-cloneDeterministic-address-bytes32-: xref:proxy.adoc#Clones-cloneDeterministic-address-bytes32- :Clones-cloneDeterministic: pass:normal[xref:proxy.adoc#Clones-cloneDeterministic-address-bytes32-uint256-[`Clones.cloneDeterministic`]] :Clones-cloneDeterministic: pass:normal[xref:proxy.adoc#Clones-cloneDeterministic-address-bytes32-uint256-[`Clones.cloneDeterministic`]] +:xref-Clones-cloneWithImmutableArgs-address-bytes-: xref:proxy.adoc#Clones-cloneWithImmutableArgs-address-bytes- +:xref-Clones-cloneDeterministicWithImmutableArgs-address-bytes-bytes32-: xref:proxy.adoc#Clones-cloneDeterministicWithImmutableArgs-address-bytes-bytes32- +:Clones-cloneDeterministicWithImmutableArgs: pass:normal[xref:proxy.adoc#Clones-cloneDeterministicWithImmutableArgs-address-bytes-bytes32-uint256-[`Clones.cloneDeterministicWithImmutableArgs`]] +:Clones-cloneDeterministicWithImmutableArgs: pass:normal[xref:proxy.adoc#Clones-cloneDeterministicWithImmutableArgs-address-bytes-bytes32-uint256-[`Clones.cloneDeterministicWithImmutableArgs`]] :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- @@ -188,7 +200,7 @@ The current implementation of this security mechanism uses https://eips.ethereum [.contract] [[Proxy]] -=== `++Proxy++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/proxy/Proxy.sol[{github-icon},role=heading-link] +=== `++Proxy++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/proxy/Proxy.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -251,7 +263,7 @@ function in the contract matches the call data. [.contract] [[ERC1967Proxy]] -=== `++ERC1967Proxy++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/proxy/ERC1967/ERC1967Proxy.sol[{github-icon},role=heading-link] +=== `++ERC1967Proxy++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/proxy/ERC1967/ERC1967Proxy.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -316,7 +328,7 @@ the https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call. [.contract] [[ERC1967Utils]] -=== `++ERC1967Utils++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/proxy/ERC1967/ERC1967Utils.sol[{github-icon},role=heading-link] +=== `++ERC1967Utils++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/proxy/ERC1967/ERC1967Utils.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -465,7 +477,7 @@ This is the keccak-256 hash of "eip1967.proxy.beacon" subtracted by 1. [.contract] [[TransparentUpgradeableProxy]] -=== `++TransparentUpgradeableProxy++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/proxy/transparent/TransparentUpgradeableProxy.sol[{github-icon},role=heading-link] +=== `++TransparentUpgradeableProxy++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/proxy/transparent/TransparentUpgradeableProxy.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -574,7 +586,7 @@ The proxy caller is the current admin, and can't fallback to the proxy target. [.contract] [[ProxyAdmin]] -=== `++ProxyAdmin++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/proxy/transparent/ProxyAdmin.sol[{github-icon},role=heading-link] +=== `++ProxyAdmin++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/proxy/transparent/ProxyAdmin.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -659,7 +671,7 @@ during an upgrade. [.contract] [[BeaconProxy]] -=== `++BeaconProxy++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/proxy/beacon/BeaconProxy.sol[{github-icon},role=heading-link] +=== `++BeaconProxy++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/proxy/beacon/BeaconProxy.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -724,7 +736,7 @@ Returns the beacon. [.contract] [[IBeacon]] -=== `++IBeacon++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/proxy/beacon/IBeacon.sol[{github-icon},role=heading-link] +=== `++IBeacon++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/proxy/beacon/IBeacon.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -756,7 +768,7 @@ Must return an address that can be used as a delegate call target. [.contract] [[UpgradeableBeacon]] -=== `++UpgradeableBeacon++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/proxy/beacon/UpgradeableBeacon.sol[{github-icon},role=heading-link] +=== `++UpgradeableBeacon++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/proxy/beacon/UpgradeableBeacon.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -856,16 +868,24 @@ The `implementation` of the beacon is invalid. == Minimal Clones +:CloneArgumentsTooLong: pass:normal[xref:#Clones-CloneArgumentsTooLong--[`++CloneArgumentsTooLong++`]] :clone: pass:normal[xref:#Clones-clone-address-[`++clone++`]] :clone: pass:normal[xref:#Clones-clone-address-uint256-[`++clone++`]] :cloneDeterministic: pass:normal[xref:#Clones-cloneDeterministic-address-bytes32-[`++cloneDeterministic++`]] :cloneDeterministic: pass:normal[xref:#Clones-cloneDeterministic-address-bytes32-uint256-[`++cloneDeterministic++`]] :predictDeterministicAddress: pass:normal[xref:#Clones-predictDeterministicAddress-address-bytes32-address-[`++predictDeterministicAddress++`]] :predictDeterministicAddress: pass:normal[xref:#Clones-predictDeterministicAddress-address-bytes32-[`++predictDeterministicAddress++`]] +:cloneWithImmutableArgs: pass:normal[xref:#Clones-cloneWithImmutableArgs-address-bytes-[`++cloneWithImmutableArgs++`]] +:cloneWithImmutableArgs: pass:normal[xref:#Clones-cloneWithImmutableArgs-address-bytes-uint256-[`++cloneWithImmutableArgs++`]] +:cloneDeterministicWithImmutableArgs: pass:normal[xref:#Clones-cloneDeterministicWithImmutableArgs-address-bytes-bytes32-[`++cloneDeterministicWithImmutableArgs++`]] +:cloneDeterministicWithImmutableArgs: pass:normal[xref:#Clones-cloneDeterministicWithImmutableArgs-address-bytes-bytes32-uint256-[`++cloneDeterministicWithImmutableArgs++`]] +:predictDeterministicAddressWithImmutableArgs: pass:normal[xref:#Clones-predictDeterministicAddressWithImmutableArgs-address-bytes-bytes32-address-[`++predictDeterministicAddressWithImmutableArgs++`]] +:predictDeterministicAddressWithImmutableArgs: pass:normal[xref:#Clones-predictDeterministicAddressWithImmutableArgs-address-bytes-bytes32-[`++predictDeterministicAddressWithImmutableArgs++`]] +:fetchCloneArgs: pass:normal[xref:#Clones-fetchCloneArgs-address-[`++fetchCloneArgs++`]] [.contract] [[Clones]] -=== `++Clones++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/proxy/Clones.sol[{github-icon},role=heading-link] +=== `++Clones++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/proxy/Clones.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -891,6 +911,20 @@ deterministic method. * {xref-Clones-cloneDeterministic-address-bytes32-uint256-}[`++cloneDeterministic(implementation, salt, value)++`] * {xref-Clones-predictDeterministicAddress-address-bytes32-address-}[`++predictDeterministicAddress(implementation, salt, deployer)++`] * {xref-Clones-predictDeterministicAddress-address-bytes32-}[`++predictDeterministicAddress(implementation, salt)++`] +* {xref-Clones-cloneWithImmutableArgs-address-bytes-}[`++cloneWithImmutableArgs(implementation, args)++`] +* {xref-Clones-cloneWithImmutableArgs-address-bytes-uint256-}[`++cloneWithImmutableArgs(implementation, args, value)++`] +* {xref-Clones-cloneDeterministicWithImmutableArgs-address-bytes-bytes32-}[`++cloneDeterministicWithImmutableArgs(implementation, args, salt)++`] +* {xref-Clones-cloneDeterministicWithImmutableArgs-address-bytes-bytes32-uint256-}[`++cloneDeterministicWithImmutableArgs(implementation, args, salt, value)++`] +* {xref-Clones-predictDeterministicAddressWithImmutableArgs-address-bytes-bytes32-address-}[`++predictDeterministicAddressWithImmutableArgs(implementation, args, salt, deployer)++`] +* {xref-Clones-predictDeterministicAddressWithImmutableArgs-address-bytes-bytes32-}[`++predictDeterministicAddressWithImmutableArgs(implementation, args, salt)++`] +* {xref-Clones-fetchCloneArgs-address-}[`++fetchCloneArgs(instance)++`] + +-- + +[.contract-index] +.Errors +-- +* {xref-Clones-CloneArgumentsTooLong--}[`++CloneArgumentsTooLong()++`] -- @@ -919,7 +953,7 @@ to always have enough balance for new deployments. Consider exposing this functi 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 clone. Using the same `implementation` and `salt` multiple times will revert, since the clones cannot be deployed twice at the same address. [.contract-item] @@ -944,6 +978,78 @@ Computes the address of a clone deployed using {Clones-cloneDeterministic}. Computes the address of a clone deployed using {Clones-cloneDeterministic}. +[.contract-item] +[[Clones-cloneWithImmutableArgs-address-bytes-]] +==== `[.contract-item-name]#++cloneWithImmutableArgs++#++(address implementation, bytes args) → address instance++` [.item-kind]#internal# + +Deploys and returns the address of a clone that mimics the behavior of `implementation` with custom +immutable arguments. These are provided through `args` and cannot be changed after deployment. To +access the arguments within the implementation, use {fetchCloneArgs}. + +This function uses the create opcode, which should never revert. + +[.contract-item] +[[Clones-cloneWithImmutableArgs-address-bytes-uint256-]] +==== `[.contract-item-name]#++cloneWithImmutableArgs++#++(address implementation, bytes args, uint256 value) → address instance++` [.item-kind]#internal# + +Same as {xref-Clones-cloneWithImmutableArgs-address-bytes-}[cloneWithImmutableArgs], but with a `value` +parameter to send native currency to the new contract. + +NOTE: Using a non-zero value at creation will require the contract using this function (e.g. a factory) +to always have enough balance for new deployments. Consider exposing this function under a payable method. + +[.contract-item] +[[Clones-cloneDeterministicWithImmutableArgs-address-bytes-bytes32-]] +==== `[.contract-item-name]#++cloneDeterministicWithImmutableArgs++#++(address implementation, bytes args, bytes32 salt) → address instance++` [.item-kind]#internal# + +Deploys and returns the address of a clone that mimics the behaviour of `implementation` with custom +immutable arguments. These are provided through `args` and cannot be changed after deployment. To +access the arguments within the implementation, use {fetchCloneArgs}. + +This function uses the create2 opcode and a `salt` to deterministically deploy the clone. Using the same +`implementation`, `args` and `salt` multiple times will revert, since the clones cannot be deployed twice +at the same address. + +[.contract-item] +[[Clones-cloneDeterministicWithImmutableArgs-address-bytes-bytes32-uint256-]] +==== `[.contract-item-name]#++cloneDeterministicWithImmutableArgs++#++(address implementation, bytes args, bytes32 salt, uint256 value) → address instance++` [.item-kind]#internal# + +Same as {xref-Clones-cloneDeterministicWithImmutableArgs-address-bytes-bytes32-}[cloneDeterministicWithImmutableArgs], +but with a `value` parameter to send native currency to the new contract. + +NOTE: Using a non-zero value at creation will require the contract using this function (e.g. a factory) +to always have enough balance for new deployments. Consider exposing this function under a payable method. + +[.contract-item] +[[Clones-predictDeterministicAddressWithImmutableArgs-address-bytes-bytes32-address-]] +==== `[.contract-item-name]#++predictDeterministicAddressWithImmutableArgs++#++(address implementation, bytes args, bytes32 salt, address deployer) → address predicted++` [.item-kind]#internal# + +Computes the address of a clone deployed using {Clones-cloneDeterministicWithImmutableArgs}. + +[.contract-item] +[[Clones-predictDeterministicAddressWithImmutableArgs-address-bytes-bytes32-]] +==== `[.contract-item-name]#++predictDeterministicAddressWithImmutableArgs++#++(address implementation, bytes args, bytes32 salt) → address predicted++` [.item-kind]#internal# + +Computes the address of a clone deployed using {Clones-cloneDeterministicWithImmutableArgs}. + +[.contract-item] +[[Clones-fetchCloneArgs-address-]] +==== `[.contract-item-name]#++fetchCloneArgs++#++(address instance) → bytes++` [.item-kind]#internal# + +Get the immutable args attached to a clone. + +- If `instance` is a clone that was deployed using `clone` or `cloneDeterministic`, this + function will return an empty array. +- If `instance` is a clone that was deployed using `cloneWithImmutableArgs` or + `cloneDeterministicWithImmutableArgs`, this function will return the args array used at + creation. +- If `instance` is NOT a clone deployed using this library, the behavior is undefined. This + function should only be used to check addresses that are known to be clones. + +[.contract-item] +[[Clones-CloneArgumentsTooLong--]] +==== `[.contract-item-name]#++CloneArgumentsTooLong++#++()++` [.item-kind]#error# + == Utils :InitializableStorage: pass:normal[xref:#Initializable-InitializableStorage[`++InitializableStorage++`]] @@ -960,7 +1066,7 @@ Computes the address of a clone deployed using {Clones-cloneDeterministic}. [.contract] [[Initializable]] -=== `++Initializable++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/proxy/utils/Initializable.sol[{github-icon},role=heading-link] +=== `++Initializable++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/proxy/utils/Initializable.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -1150,7 +1256,7 @@ The contract is not initializing. [.contract] [[UUPSUpgradeable]] -=== `++UUPSUpgradeable++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/proxy/utils/UUPSUpgradeable.sol[{github-icon},role=heading-link] +=== `++UUPSUpgradeable++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/proxy/utils/UUPSUpgradeable.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity diff --git a/docs/modules/api/pages/token/ERC1155.adoc b/docs/modules/api/pages/token/ERC1155.adoc index d56fe4470..8840497d0 100644 --- a/docs/modules/api/pages/token/ERC1155.adoc +++ b/docs/modules/api/pages/token/ERC1155.adoc @@ -264,7 +264,7 @@ NOTE: This core set of contracts is designed to be unopinionated, allowing devel [.contract] [[IERC1155]] -=== `++IERC1155++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/token/ERC1155/IERC1155.sol[{github-icon},role=heading-link] +=== `++IERC1155++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/token/ERC1155/IERC1155.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -413,7 +413,7 @@ returned by {IERC1155MetadataURI-uri}. [.contract] [[IERC1155MetadataURI]] -=== `++IERC1155MetadataURI++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol[{github-icon},role=heading-link] +=== `++IERC1155MetadataURI++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -490,7 +490,7 @@ clients with the actual token type ID. [.contract] [[ERC1155]] -=== `++ERC1155++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/token/ERC1155/ERC1155.sol[{github-icon},role=heading-link] +=== `++ERC1155++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/token/ERC1155/ERC1155.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -812,7 +812,7 @@ Requirements: [.contract] [[IERC1155Receiver]] -=== `++IERC1155Receiver++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/token/ERC1155/IERC1155Receiver.sol[{github-icon},role=heading-link] +=== `++IERC1155Receiver++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/token/ERC1155/IERC1155Receiver.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -863,7 +863,7 @@ NOTE: To accept the transfer(s), this must return [.contract] [[ERC1155Pausable]] -=== `++ERC1155Pausable++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/token/ERC1155/extensions/ERC1155Pausable.sol[{github-icon},role=heading-link] +=== `++ERC1155Pausable++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/token/ERC1155/extensions/ERC1155Pausable.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -1016,7 +1016,7 @@ Requirements: [.contract] [[ERC1155Burnable]] -=== `++ERC1155Burnable++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/token/ERC1155/extensions/ERC1155Burnable.sol[{github-icon},role=heading-link] +=== `++ERC1155Burnable++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/token/ERC1155/extensions/ERC1155Burnable.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -1144,7 +1144,7 @@ own tokens and those that they have been approved to use. [.contract] [[ERC1155Supply]] -=== `++ERC1155Supply++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/token/ERC1155/extensions/ERC1155Supply.sol[{github-icon},role=heading-link] +=== `++ERC1155Supply++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/token/ERC1155/extensions/ERC1155Supply.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -1297,7 +1297,7 @@ See {ERC1155-_update}. [.contract] [[ERC1155URIStorage]] -=== `++ERC1155URIStorage++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/token/ERC1155/extensions/ERC1155URIStorage.sol[{github-icon},role=heading-link] +=== `++ERC1155URIStorage++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/token/ERC1155/extensions/ERC1155URIStorage.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -1451,7 +1451,7 @@ Sets `baseURI` as the `_baseURI` for all tokens [.contract] [[ERC1155Holder]] -=== `++ERC1155Holder++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/token/ERC1155/utils/ERC1155Holder.sol[{github-icon},role=heading-link] +=== `++ERC1155Holder++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/token/ERC1155/utils/ERC1155Holder.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -1500,7 +1500,7 @@ See {IERC165-supportsInterface}. [.contract] [[ERC1155Utils]] -=== `++ERC1155Utils++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/token/ERC1155/utils/ERC1155Utils.sol[{github-icon},role=heading-link] +=== `++ERC1155Utils++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/token/ERC1155/utils/ERC1155Utils.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity diff --git a/docs/modules/api/pages/token/ERC20.adoc b/docs/modules/api/pages/token/ERC20.adoc index 62f54d52b..932633bf3 100644 --- a/docs/modules/api/pages/token/ERC20.adoc +++ b/docs/modules/api/pages/token/ERC20.adoc @@ -230,6 +230,7 @@ :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-_validateTimepoint-uint256-: xref:governance.adoc#Votes-_validateTimepoint-uint256- :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- @@ -580,7 +581,7 @@ NOTE: This core set of contracts is designed to be unopinionated, allowing devel [.contract] [[IERC20]] -=== `++IERC20++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/token/ERC20/IERC20.sol[{github-icon},role=heading-link] +=== `++IERC20++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/token/ERC20/IERC20.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -693,7 +694,7 @@ a call to {approve}. `value` is the new allowance. [.contract] [[IERC20Metadata]] -=== `++IERC20Metadata++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/token/ERC20/extensions/IERC20Metadata.sol[{github-icon},role=heading-link] +=== `++IERC20Metadata++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/token/ERC20/extensions/IERC20Metadata.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -769,7 +770,7 @@ Returns the decimals places of the token. [.contract] [[ERC20]] -=== `++ERC20++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/token/ERC20/ERC20.sol[{github-icon},role=heading-link] +=== `++ERC20++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/token/ERC20/ERC20.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -1065,7 +1066,7 @@ Does not emit an {Approval} event. [.contract] [[IERC20Permit]] -=== `++IERC20Permit++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/token/ERC20/extensions/IERC20Permit.sol[{github-icon},role=heading-link] +=== `++IERC20Permit++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/token/ERC20/extensions/IERC20Permit.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -1167,7 +1168,7 @@ Returns the domain separator used in the encoding of the signature for {permit}, [.contract] [[ERC20Permit]] -=== `++ERC20Permit++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/token/ERC20/extensions/ERC20Permit.sol[{github-icon},role=heading-link] +=== `++ERC20Permit++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/token/ERC20/extensions/ERC20Permit.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -1377,7 +1378,7 @@ Mismatched signature. [.contract] [[ERC20Burnable]] -=== `++ERC20Burnable++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/token/ERC20/extensions/ERC20Burnable.sol[{github-icon},role=heading-link] +=== `++ERC20Burnable++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/token/ERC20/extensions/ERC20Burnable.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -1498,7 +1499,7 @@ Requirements: [.contract] [[ERC20Capped]] -=== `++ERC20Capped++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/token/ERC20/extensions/ERC20Capped.sol[{github-icon},role=heading-link] +=== `++ERC20Capped++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/token/ERC20/extensions/ERC20Capped.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -1624,7 +1625,7 @@ The supplied cap is not a valid cap. [.contract] [[ERC20Pausable]] -=== `++ERC20Pausable++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/token/ERC20/extensions/ERC20Pausable.sol[{github-icon},role=heading-link] +=== `++ERC20Pausable++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/token/ERC20/extensions/ERC20Pausable.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -1758,7 +1759,7 @@ Requirements: [.contract] [[ERC20Votes]] -=== `++ERC20Votes++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/token/ERC20/extensions/ERC20Votes.sol[{github-icon},role=heading-link] +=== `++ERC20Votes++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/token/ERC20/extensions/ERC20Votes.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -1790,6 +1791,7 @@ requires users to delegate to themselves in order to activate checkpoints and ha .Votes * {xref-Votes-clock--}[`++clock()++`] * {xref-Votes-CLOCK_MODE--}[`++CLOCK_MODE()++`] +* {xref-Votes-_validateTimepoint-uint256-}[`++_validateTimepoint(timepoint)++`] * {xref-Votes-getVotes-address-}[`++getVotes(account)++`] * {xref-Votes-getPastVotes-address-uint256-}[`++getPastVotes(account, timepoint)++`] * {xref-Votes-getPastTotalSupply-uint256-}[`++getPastTotalSupply(timepoint)++`] @@ -2010,7 +2012,7 @@ Total supply cap has been exceeded, introducing a risk of votes overflowing. [.contract] [[ERC20Wrapper]] -=== `++ERC20Wrapper++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/token/ERC20/extensions/ERC20Wrapper.sol[{github-icon},role=heading-link] +=== `++ERC20Wrapper++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/token/ERC20/extensions/ERC20Wrapper.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -2164,7 +2166,7 @@ The underlying token couldn't be wrapped. [.contract] [[ERC20FlashMint]] -=== `++ERC20FlashMint++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/token/ERC20/extensions/ERC20FlashMint.sol[{github-icon},role=heading-link] +=== `++ERC20FlashMint++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/token/ERC20/extensions/ERC20FlashMint.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -2342,7 +2344,7 @@ The receiver of a flashloan is not a valid {IERC3156FlashBorrower-onFlashLoan} i [.contract] [[ERC20TemporaryApproval]] -=== `++ERC20TemporaryApproval++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/token/ERC20/extensions/draft-ERC20TemporaryApproval.sol[{github-icon},role=heading-link] +=== `++ERC20TemporaryApproval++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/token/ERC20/extensions/draft-ERC20TemporaryApproval.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -2509,7 +2511,7 @@ is enough to cover the spending. [.contract] [[ERC1363]] -=== `++ERC1363++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/token/ERC20/extensions/ERC1363.sol[{github-icon},role=heading-link] +=== `++ERC1363++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/token/ERC20/extensions/ERC1363.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -2653,7 +2655,8 @@ This function call must use less than 30 000 gas. ==== `[.contract-item-name]#++transferAndCall++#++(address to, uint256 value) → bool++` [.item-kind]#public# Moves a `value` amount of tokens from the caller's account to `to` -and then calls {IERC1363Receiver-onTransferReceived} on `to`. +and then calls {IERC1363Receiver-onTransferReceived} on `to`. Returns a flag that indicates +if the call succeeded. Requirements: @@ -2674,7 +2677,8 @@ no specified format. ==== `[.contract-item-name]#++transferFromAndCall++#++(address from, address to, uint256 value) → bool++` [.item-kind]#public# Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism -and then calls {IERC1363Receiver-onTransferReceived} on `to`. +and then calls {IERC1363Receiver-onTransferReceived} on `to`. Returns a flag that indicates +if the call succeeded. Requirements: @@ -2696,6 +2700,7 @@ no specified format. Sets a `value` amount of tokens as the allowance of `spender` over the caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`. +Returns a flag that indicates if the call succeeded. Requirements: @@ -2759,7 +2764,7 @@ Indicates a failure within the {approve} part of a approveAndCall operation. [.contract] [[ERC4626]] -=== `++ERC4626++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/token/ERC20/extensions/ERC4626.sol[{github-icon},role=heading-link] +=== `++ERC4626++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/token/ERC20/extensions/ERC4626.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -3096,7 +3101,7 @@ Attempted to redeem more shares than the max amount for `receiver`. [.contract] [[SafeERC20]] -=== `++SafeERC20++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/token/ERC20/utils/SafeERC20.sol[{github-icon},role=heading-link] +=== `++SafeERC20++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/token/ERC20/utils/SafeERC20.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -3235,7 +3240,7 @@ Indicates a failed `decreaseAllowance` request. [.contract] [[ERC1363Utils]] -=== `++ERC1363Utils++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/token/ERC20/utils/ERC1363Utils.sol[{github-icon},role=heading-link] +=== `++ERC1363Utils++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/token/ERC20/utils/ERC1363Utils.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity diff --git a/docs/modules/api/pages/token/ERC721.adoc b/docs/modules/api/pages/token/ERC721.adoc index 6a7997f35..10cf72827 100644 --- a/docs/modules/api/pages/token/ERC721.adoc +++ b/docs/modules/api/pages/token/ERC721.adoc @@ -392,6 +392,7 @@ :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-_validateTimepoint-uint256-: xref:governance.adoc#Votes-_validateTimepoint-uint256- :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- @@ -629,7 +630,7 @@ NOTE: This core set of contracts is designed to be unopinionated, allowing devel [.contract] [[IERC721]] -=== `++IERC721++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/token/ERC721/IERC721.sol[{github-icon},role=heading-link] +=== `++IERC721++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/token/ERC721/IERC721.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -811,7 +812,7 @@ Emitted when `owner` enables or disables (`approved`) `operator` to manage all o [.contract] [[IERC721Metadata]] -=== `++IERC721Metadata++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/token/ERC721/extensions/IERC721Metadata.sol[{github-icon},role=heading-link] +=== `++IERC721Metadata++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/token/ERC721/extensions/IERC721Metadata.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -884,7 +885,7 @@ Returns the Uniform Resource Identifier (URI) for `tokenId` token. [.contract] [[IERC721Enumerable]] -=== `++IERC721Enumerable++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/token/ERC721/extensions/IERC721Enumerable.sol[{github-icon},role=heading-link] +=== `++IERC721Enumerable++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/token/ERC721/extensions/IERC721Enumerable.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -988,7 +989,7 @@ Use along with {totalSupply} to enumerate all tokens. [.contract] [[ERC721]] -=== `++ERC721++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/token/ERC721/ERC721.sol[{github-icon},role=heading-link] +=== `++ERC721++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/token/ERC721/ERC721.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -1406,7 +1407,7 @@ Overrides to ownership logic should be done to {_ownerOf}. [.contract] [[ERC721Enumerable]] -=== `++ERC721Enumerable++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/token/ERC721/extensions/ERC721Enumerable.sol[{github-icon},role=heading-link] +=== `++ERC721Enumerable++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/token/ERC721/extensions/ERC721Enumerable.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -1599,7 +1600,7 @@ Batch mint is not allowed. [.contract] [[IERC721Receiver]] -=== `++IERC721Receiver++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/token/ERC721/IERC721Receiver.sol[{github-icon},role=heading-link] +=== `++IERC721Receiver++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/token/ERC721/IERC721Receiver.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -1635,7 +1636,7 @@ The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received. [.contract] [[ERC721Pausable]] -=== `++ERC721Pausable++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/token/ERC721/extensions/ERC721Pausable.sol[{github-icon},role=heading-link] +=== `++ERC721Pausable++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/token/ERC721/extensions/ERC721Pausable.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -1800,7 +1801,7 @@ Requirements: [.contract] [[ERC721Burnable]] -=== `++ERC721Burnable++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/token/ERC721/extensions/ERC721Burnable.sol[{github-icon},role=heading-link] +=== `++ERC721Burnable++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/token/ERC721/extensions/ERC721Burnable.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -1946,7 +1947,7 @@ Requirements: [.contract] [[ERC721Consecutive]] -=== `++ERC721Consecutive++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/token/ERC721/extensions/ERC721Consecutive.sol[{github-icon},role=heading-link] +=== `++ERC721Consecutive++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/token/ERC721/extensions/ERC721Consecutive.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -2186,7 +2187,7 @@ Batch burn is not supported. [.contract] [[ERC721URIStorage]] -=== `++ERC721URIStorage++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/token/ERC721/extensions/ERC721URIStorage.sol[{github-icon},role=heading-link] +=== `++ERC721URIStorage++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/token/ERC721/extensions/ERC721URIStorage.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -2347,7 +2348,7 @@ Emits {MetadataUpdate}. [.contract] [[ERC721Votes]] -=== `++ERC721Votes++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/token/ERC721/extensions/ERC721Votes.sol[{github-icon},role=heading-link] +=== `++ERC721Votes++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/token/ERC721/extensions/ERC721Votes.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -2372,6 +2373,7 @@ the votes in governance decisions, or they can delegate to themselves to be thei .Votes * {xref-Votes-clock--}[`++clock()++`] * {xref-Votes-CLOCK_MODE--}[`++CLOCK_MODE()++`] +* {xref-Votes-_validateTimepoint-uint256-}[`++_validateTimepoint(timepoint)++`] * {xref-Votes-getVotes-address-}[`++getVotes(account)++`] * {xref-Votes-getPastVotes-address-uint256-}[`++getPastVotes(account, timepoint)++`] * {xref-Votes-getPastTotalSupply-uint256-}[`++getPastTotalSupply(timepoint)++`] @@ -2594,7 +2596,7 @@ See {ERC721-_increaseBalance}. We need that to account tokens that were minted i [.contract] [[ERC721Royalty]] -=== `++ERC721Royalty++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/token/ERC721/extensions/ERC721Royalty.sol[{github-icon},role=heading-link] +=== `++ERC721Royalty++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/token/ERC721/extensions/ERC721Royalty.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -2769,7 +2771,7 @@ See {IERC165-supportsInterface}. [.contract] [[ERC721Wrapper]] -=== `++ERC721Wrapper++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/token/ERC721/extensions/ERC721Wrapper.sol[{github-icon},role=heading-link] +=== `++ERC721Wrapper++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/token/ERC721/extensions/ERC721Wrapper.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -2966,7 +2968,7 @@ The received ERC-721 token couldn't be wrapped. [.contract] [[ERC721Holder]] -=== `++ERC721Holder++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/token/ERC721/utils/ERC721Holder.sol[{github-icon},role=heading-link] +=== `++ERC721Holder++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/token/ERC721/utils/ERC721Holder.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -3001,7 +3003,7 @@ Always returns `IERC721Receiver.onERC721Received.selector`. [.contract] [[ERC721Utils]] -=== `++ERC721Utils++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/token/ERC721/utils/ERC721Utils.sol[{github-icon},role=heading-link] +=== `++ERC721Utils++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/token/ERC721/utils/ERC721Utils.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity diff --git a/docs/modules/api/pages/token/common.adoc b/docs/modules/api/pages/token/common.adoc index c82234dca..d1622d1ae 100644 --- a/docs/modules/api/pages/token/common.adoc +++ b/docs/modules/api/pages/token/common.adoc @@ -37,7 +37,7 @@ Functionality that is common to multiple token standards. [.contract] [[ERC2981]] -=== `++ERC2981++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/token/common/ERC2981.sol[{github-icon},role=heading-link] +=== `++ERC2981++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/token/common/ERC2981.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity diff --git a/docs/modules/api/pages/utils.adoc b/docs/modules/api/pages/utils.adoc index f4603bf4f..9414289da 100644 --- a/docs/modules/api/pages/utils.adoc +++ b/docs/modules/api/pages/utils.adoc @@ -15,6 +15,8 @@ :ReentrancyGuard: pass:normal[xref:utils.adoc#ReentrancyGuard[`ReentrancyGuard`]] :Pausable: pass:normal[xref:utils.adoc#Pausable[`Pausable`]] :Nonces: pass:normal[xref:utils.adoc#Nonces[`Nonces`]] +:NoncesKeyed: pass:normal[xref:utils.adoc#NoncesKeyed[`NoncesKeyed`]] +:Nonces: pass:normal[xref:utils.adoc#Nonces[`Nonces`]] :ERC165: pass:normal[xref:utils.adoc#ERC165[`ERC165`]] :ERC165Checker: pass:normal[xref:utils.adoc#ERC165Checker[`ERC165Checker`]] :BitMaps: pass:normal[xref:utils.adoc#BitMaps[`BitMaps`]] @@ -30,6 +32,7 @@ :Address: pass:normal[xref:utils.adoc#Address[`Address`]] :Arrays: pass:normal[xref:utils.adoc#Arrays[`Arrays`]] :Base64: pass:normal[xref:utils.adoc#Base64[`Base64`]] +:Bytes: pass:normal[xref:utils.adoc#Bytes[`Bytes`]] :Strings: pass:normal[xref:utils.adoc#Strings[`Strings`]] :ShortString: pass:normal[xref:utils.adoc#ShortString[`ShortString`]] :SlotDerivation: pass:normal[xref:utils.adoc#SlotDerivation[`SlotDerivation`]] @@ -41,6 +44,8 @@ :Panic: pass:normal[xref:utils.adoc#Panic[`Panic`]] :Comparators: pass:normal[xref:utils.adoc#Comparators[`Comparators`]] :Heap: pass:normal[xref:utils.adoc#Heap[`Heap`]] +:CAIP2: pass:normal[xref:utils.adoc#CAIP2[`CAIP2`]] +:CAIP10: pass:normal[xref:utils.adoc#CAIP10[`CAIP10`]] :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- @@ -234,6 +239,17 @@ :xref-Nonces-_useNonce-address-: xref:utils.adoc#Nonces-_useNonce-address- :xref-Nonces-_useCheckedNonce-address-uint256-: xref:utils.adoc#Nonces-_useCheckedNonce-address-uint256- :xref-Nonces-InvalidAccountNonce-address-uint256-: xref:utils.adoc#Nonces-InvalidAccountNonce-address-uint256- +:Nonces: pass:normal[xref:utils.adoc#Nonces[`Nonces`]] +:Nonces: pass:normal[xref:utils.adoc#Nonces[`Nonces`]] +:Nonces: pass:normal[xref:utils.adoc#Nonces[`Nonces`]] +:NoncesKeyed: pass:normal[xref:utils.adoc#NoncesKeyed[`NoncesKeyed`]] +:xref-NoncesKeyed-nonces-address-uint192-: xref:utils.adoc#NoncesKeyed-nonces-address-uint192- +:xref-NoncesKeyed-_useNonce-address-uint192-: xref:utils.adoc#NoncesKeyed-_useNonce-address-uint192- +:xref-NoncesKeyed-_useCheckedNonce-address-uint256-: xref:utils.adoc#NoncesKeyed-_useCheckedNonce-address-uint256- +:xref-NoncesKeyed-_useCheckedNonce-address-uint192-uint64-: xref:utils.adoc#NoncesKeyed-_useCheckedNonce-address-uint192-uint64- +:xref-Nonces-nonces-address-: xref:utils.adoc#Nonces-nonces-address- +:xref-Nonces-_useNonce-address-: xref:utils.adoc#Nonces-_useNonce-address- +:xref-Nonces-InvalidAccountNonce-address-uint256-: xref:utils.adoc#Nonces-InvalidAccountNonce-address-uint256- :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- @@ -469,7 +485,25 @@ :xref-Strings-toHexString-address-: xref:utils.adoc#Strings-toHexString-address- :xref-Strings-toChecksumHexString-address-: xref:utils.adoc#Strings-toChecksumHexString-address- :xref-Strings-equal-string-string-: xref:utils.adoc#Strings-equal-string-string- +:xref-Strings-parseUint-string-: xref:utils.adoc#Strings-parseUint-string- +:xref-Strings-parseUint-string-uint256-uint256-: xref:utils.adoc#Strings-parseUint-string-uint256-uint256- +:xref-Strings-tryParseUint-string-: xref:utils.adoc#Strings-tryParseUint-string- +:xref-Strings-tryParseUint-string-uint256-uint256-: xref:utils.adoc#Strings-tryParseUint-string-uint256-uint256- +:xref-Strings-parseInt-string-: xref:utils.adoc#Strings-parseInt-string- +:xref-Strings-parseInt-string-uint256-uint256-: xref:utils.adoc#Strings-parseInt-string-uint256-uint256- +:xref-Strings-tryParseInt-string-: xref:utils.adoc#Strings-tryParseInt-string- +:xref-Strings-tryParseInt-string-uint256-uint256-: xref:utils.adoc#Strings-tryParseInt-string-uint256-uint256- +:xref-Strings-parseHexUint-string-: xref:utils.adoc#Strings-parseHexUint-string- +:xref-Strings-parseHexUint-string-uint256-uint256-: xref:utils.adoc#Strings-parseHexUint-string-uint256-uint256- +:xref-Strings-tryParseHexUint-string-: xref:utils.adoc#Strings-tryParseHexUint-string- +:xref-Strings-tryParseHexUint-string-uint256-uint256-: xref:utils.adoc#Strings-tryParseHexUint-string-uint256-uint256- +:xref-Strings-parseAddress-string-: xref:utils.adoc#Strings-parseAddress-string- +:xref-Strings-parseAddress-string-uint256-uint256-: xref:utils.adoc#Strings-parseAddress-string-uint256-uint256- +:xref-Strings-tryParseAddress-string-: xref:utils.adoc#Strings-tryParseAddress-string- +:xref-Strings-tryParseAddress-string-uint256-uint256-: xref:utils.adoc#Strings-tryParseAddress-string-uint256-uint256- :xref-Strings-StringsInsufficientHexLength-uint256-uint256-: xref:utils.adoc#Strings-StringsInsufficientHexLength-uint256-uint256- +:xref-Strings-StringsInvalidChar--: xref:utils.adoc#Strings-StringsInvalidChar-- +:xref-Strings-StringsInvalidAddressFormat--: xref:utils.adoc#Strings-StringsInvalidAddressFormat-- :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- @@ -524,8 +558,13 @@ :xref-Packing-pack_2_2-bytes2-bytes2-: xref:utils.adoc#Packing-pack_2_2-bytes2-bytes2- :xref-Packing-pack_2_4-bytes2-bytes4-: xref:utils.adoc#Packing-pack_2_4-bytes2-bytes4- :xref-Packing-pack_2_6-bytes2-bytes6-: xref:utils.adoc#Packing-pack_2_6-bytes2-bytes6- +:xref-Packing-pack_2_8-bytes2-bytes8-: xref:utils.adoc#Packing-pack_2_8-bytes2-bytes8- +:xref-Packing-pack_2_10-bytes2-bytes10-: xref:utils.adoc#Packing-pack_2_10-bytes2-bytes10- +:xref-Packing-pack_2_20-bytes2-bytes20-: xref:utils.adoc#Packing-pack_2_20-bytes2-bytes20- +:xref-Packing-pack_2_22-bytes2-bytes22-: xref:utils.adoc#Packing-pack_2_22-bytes2-bytes22- :xref-Packing-pack_4_2-bytes4-bytes2-: xref:utils.adoc#Packing-pack_4_2-bytes4-bytes2- :xref-Packing-pack_4_4-bytes4-bytes4-: xref:utils.adoc#Packing-pack_4_4-bytes4-bytes4- +:xref-Packing-pack_4_6-bytes4-bytes6-: xref:utils.adoc#Packing-pack_4_6-bytes4-bytes6- :xref-Packing-pack_4_8-bytes4-bytes8-: xref:utils.adoc#Packing-pack_4_8-bytes4-bytes8- :xref-Packing-pack_4_12-bytes4-bytes12-: xref:utils.adoc#Packing-pack_4_12-bytes4-bytes12- :xref-Packing-pack_4_16-bytes4-bytes16-: xref:utils.adoc#Packing-pack_4_16-bytes4-bytes16- @@ -533,25 +572,41 @@ :xref-Packing-pack_4_24-bytes4-bytes24-: xref:utils.adoc#Packing-pack_4_24-bytes4-bytes24- :xref-Packing-pack_4_28-bytes4-bytes28-: xref:utils.adoc#Packing-pack_4_28-bytes4-bytes28- :xref-Packing-pack_6_2-bytes6-bytes2-: xref:utils.adoc#Packing-pack_6_2-bytes6-bytes2- +:xref-Packing-pack_6_4-bytes6-bytes4-: xref:utils.adoc#Packing-pack_6_4-bytes6-bytes4- :xref-Packing-pack_6_6-bytes6-bytes6-: xref:utils.adoc#Packing-pack_6_6-bytes6-bytes6- +:xref-Packing-pack_6_10-bytes6-bytes10-: xref:utils.adoc#Packing-pack_6_10-bytes6-bytes10- +:xref-Packing-pack_6_16-bytes6-bytes16-: xref:utils.adoc#Packing-pack_6_16-bytes6-bytes16- +:xref-Packing-pack_6_22-bytes6-bytes22-: xref:utils.adoc#Packing-pack_6_22-bytes6-bytes22- +:xref-Packing-pack_8_2-bytes8-bytes2-: xref:utils.adoc#Packing-pack_8_2-bytes8-bytes2- :xref-Packing-pack_8_4-bytes8-bytes4-: xref:utils.adoc#Packing-pack_8_4-bytes8-bytes4- :xref-Packing-pack_8_8-bytes8-bytes8-: xref:utils.adoc#Packing-pack_8_8-bytes8-bytes8- :xref-Packing-pack_8_12-bytes8-bytes12-: xref:utils.adoc#Packing-pack_8_12-bytes8-bytes12- :xref-Packing-pack_8_16-bytes8-bytes16-: xref:utils.adoc#Packing-pack_8_16-bytes8-bytes16- :xref-Packing-pack_8_20-bytes8-bytes20-: xref:utils.adoc#Packing-pack_8_20-bytes8-bytes20- :xref-Packing-pack_8_24-bytes8-bytes24-: xref:utils.adoc#Packing-pack_8_24-bytes8-bytes24- +:xref-Packing-pack_10_2-bytes10-bytes2-: xref:utils.adoc#Packing-pack_10_2-bytes10-bytes2- +:xref-Packing-pack_10_6-bytes10-bytes6-: xref:utils.adoc#Packing-pack_10_6-bytes10-bytes6- +:xref-Packing-pack_10_10-bytes10-bytes10-: xref:utils.adoc#Packing-pack_10_10-bytes10-bytes10- +:xref-Packing-pack_10_12-bytes10-bytes12-: xref:utils.adoc#Packing-pack_10_12-bytes10-bytes12- +:xref-Packing-pack_10_22-bytes10-bytes22-: xref:utils.adoc#Packing-pack_10_22-bytes10-bytes22- :xref-Packing-pack_12_4-bytes12-bytes4-: xref:utils.adoc#Packing-pack_12_4-bytes12-bytes4- :xref-Packing-pack_12_8-bytes12-bytes8-: xref:utils.adoc#Packing-pack_12_8-bytes12-bytes8- +:xref-Packing-pack_12_10-bytes12-bytes10-: xref:utils.adoc#Packing-pack_12_10-bytes12-bytes10- :xref-Packing-pack_12_12-bytes12-bytes12-: xref:utils.adoc#Packing-pack_12_12-bytes12-bytes12- :xref-Packing-pack_12_16-bytes12-bytes16-: xref:utils.adoc#Packing-pack_12_16-bytes12-bytes16- :xref-Packing-pack_12_20-bytes12-bytes20-: xref:utils.adoc#Packing-pack_12_20-bytes12-bytes20- :xref-Packing-pack_16_4-bytes16-bytes4-: xref:utils.adoc#Packing-pack_16_4-bytes16-bytes4- +:xref-Packing-pack_16_6-bytes16-bytes6-: xref:utils.adoc#Packing-pack_16_6-bytes16-bytes6- :xref-Packing-pack_16_8-bytes16-bytes8-: xref:utils.adoc#Packing-pack_16_8-bytes16-bytes8- :xref-Packing-pack_16_12-bytes16-bytes12-: xref:utils.adoc#Packing-pack_16_12-bytes16-bytes12- :xref-Packing-pack_16_16-bytes16-bytes16-: xref:utils.adoc#Packing-pack_16_16-bytes16-bytes16- +:xref-Packing-pack_20_2-bytes20-bytes2-: xref:utils.adoc#Packing-pack_20_2-bytes20-bytes2- :xref-Packing-pack_20_4-bytes20-bytes4-: xref:utils.adoc#Packing-pack_20_4-bytes20-bytes4- :xref-Packing-pack_20_8-bytes20-bytes8-: xref:utils.adoc#Packing-pack_20_8-bytes20-bytes8- :xref-Packing-pack_20_12-bytes20-bytes12-: xref:utils.adoc#Packing-pack_20_12-bytes20-bytes12- +:xref-Packing-pack_22_2-bytes22-bytes2-: xref:utils.adoc#Packing-pack_22_2-bytes22-bytes2- +:xref-Packing-pack_22_6-bytes22-bytes6-: xref:utils.adoc#Packing-pack_22_6-bytes22-bytes6- +:xref-Packing-pack_22_10-bytes22-bytes10-: xref:utils.adoc#Packing-pack_22_10-bytes22-bytes10- :xref-Packing-pack_24_4-bytes24-bytes4-: xref:utils.adoc#Packing-pack_24_4-bytes24-bytes4- :xref-Packing-pack_24_8-bytes24-bytes8-: xref:utils.adoc#Packing-pack_24_8-bytes24-bytes8- :xref-Packing-pack_28_4-bytes28-bytes4-: xref:utils.adoc#Packing-pack_28_4-bytes28-bytes4- @@ -575,6 +630,16 @@ :xref-Packing-replace_8_4-bytes8-bytes4-uint8-: xref:utils.adoc#Packing-replace_8_4-bytes8-bytes4-uint8- :xref-Packing-extract_8_6-bytes8-uint8-: xref:utils.adoc#Packing-extract_8_6-bytes8-uint8- :xref-Packing-replace_8_6-bytes8-bytes6-uint8-: xref:utils.adoc#Packing-replace_8_6-bytes8-bytes6-uint8- +:xref-Packing-extract_10_1-bytes10-uint8-: xref:utils.adoc#Packing-extract_10_1-bytes10-uint8- +:xref-Packing-replace_10_1-bytes10-bytes1-uint8-: xref:utils.adoc#Packing-replace_10_1-bytes10-bytes1-uint8- +:xref-Packing-extract_10_2-bytes10-uint8-: xref:utils.adoc#Packing-extract_10_2-bytes10-uint8- +:xref-Packing-replace_10_2-bytes10-bytes2-uint8-: xref:utils.adoc#Packing-replace_10_2-bytes10-bytes2-uint8- +:xref-Packing-extract_10_4-bytes10-uint8-: xref:utils.adoc#Packing-extract_10_4-bytes10-uint8- +:xref-Packing-replace_10_4-bytes10-bytes4-uint8-: xref:utils.adoc#Packing-replace_10_4-bytes10-bytes4-uint8- +:xref-Packing-extract_10_6-bytes10-uint8-: xref:utils.adoc#Packing-extract_10_6-bytes10-uint8- +:xref-Packing-replace_10_6-bytes10-bytes6-uint8-: xref:utils.adoc#Packing-replace_10_6-bytes10-bytes6-uint8- +:xref-Packing-extract_10_8-bytes10-uint8-: xref:utils.adoc#Packing-extract_10_8-bytes10-uint8- +:xref-Packing-replace_10_8-bytes10-bytes8-uint8-: xref:utils.adoc#Packing-replace_10_8-bytes10-bytes8-uint8- :xref-Packing-extract_12_1-bytes12-uint8-: xref:utils.adoc#Packing-extract_12_1-bytes12-uint8- :xref-Packing-replace_12_1-bytes12-bytes1-uint8-: xref:utils.adoc#Packing-replace_12_1-bytes12-bytes1-uint8- :xref-Packing-extract_12_2-bytes12-uint8-: xref:utils.adoc#Packing-extract_12_2-bytes12-uint8- @@ -585,6 +650,8 @@ :xref-Packing-replace_12_6-bytes12-bytes6-uint8-: xref:utils.adoc#Packing-replace_12_6-bytes12-bytes6-uint8- :xref-Packing-extract_12_8-bytes12-uint8-: xref:utils.adoc#Packing-extract_12_8-bytes12-uint8- :xref-Packing-replace_12_8-bytes12-bytes8-uint8-: xref:utils.adoc#Packing-replace_12_8-bytes12-bytes8-uint8- +:xref-Packing-extract_12_10-bytes12-uint8-: xref:utils.adoc#Packing-extract_12_10-bytes12-uint8- +:xref-Packing-replace_12_10-bytes12-bytes10-uint8-: xref:utils.adoc#Packing-replace_12_10-bytes12-bytes10-uint8- :xref-Packing-extract_16_1-bytes16-uint8-: xref:utils.adoc#Packing-extract_16_1-bytes16-uint8- :xref-Packing-replace_16_1-bytes16-bytes1-uint8-: xref:utils.adoc#Packing-replace_16_1-bytes16-bytes1-uint8- :xref-Packing-extract_16_2-bytes16-uint8-: xref:utils.adoc#Packing-extract_16_2-bytes16-uint8- @@ -595,6 +662,8 @@ :xref-Packing-replace_16_6-bytes16-bytes6-uint8-: xref:utils.adoc#Packing-replace_16_6-bytes16-bytes6-uint8- :xref-Packing-extract_16_8-bytes16-uint8-: xref:utils.adoc#Packing-extract_16_8-bytes16-uint8- :xref-Packing-replace_16_8-bytes16-bytes8-uint8-: xref:utils.adoc#Packing-replace_16_8-bytes16-bytes8-uint8- +:xref-Packing-extract_16_10-bytes16-uint8-: xref:utils.adoc#Packing-extract_16_10-bytes16-uint8- +:xref-Packing-replace_16_10-bytes16-bytes10-uint8-: xref:utils.adoc#Packing-replace_16_10-bytes16-bytes10-uint8- :xref-Packing-extract_16_12-bytes16-uint8-: xref:utils.adoc#Packing-extract_16_12-bytes16-uint8- :xref-Packing-replace_16_12-bytes16-bytes12-uint8-: xref:utils.adoc#Packing-replace_16_12-bytes16-bytes12-uint8- :xref-Packing-extract_20_1-bytes20-uint8-: xref:utils.adoc#Packing-extract_20_1-bytes20-uint8- @@ -607,10 +676,30 @@ :xref-Packing-replace_20_6-bytes20-bytes6-uint8-: xref:utils.adoc#Packing-replace_20_6-bytes20-bytes6-uint8- :xref-Packing-extract_20_8-bytes20-uint8-: xref:utils.adoc#Packing-extract_20_8-bytes20-uint8- :xref-Packing-replace_20_8-bytes20-bytes8-uint8-: xref:utils.adoc#Packing-replace_20_8-bytes20-bytes8-uint8- +:xref-Packing-extract_20_10-bytes20-uint8-: xref:utils.adoc#Packing-extract_20_10-bytes20-uint8- +:xref-Packing-replace_20_10-bytes20-bytes10-uint8-: xref:utils.adoc#Packing-replace_20_10-bytes20-bytes10-uint8- :xref-Packing-extract_20_12-bytes20-uint8-: xref:utils.adoc#Packing-extract_20_12-bytes20-uint8- :xref-Packing-replace_20_12-bytes20-bytes12-uint8-: xref:utils.adoc#Packing-replace_20_12-bytes20-bytes12-uint8- :xref-Packing-extract_20_16-bytes20-uint8-: xref:utils.adoc#Packing-extract_20_16-bytes20-uint8- :xref-Packing-replace_20_16-bytes20-bytes16-uint8-: xref:utils.adoc#Packing-replace_20_16-bytes20-bytes16-uint8- +:xref-Packing-extract_22_1-bytes22-uint8-: xref:utils.adoc#Packing-extract_22_1-bytes22-uint8- +:xref-Packing-replace_22_1-bytes22-bytes1-uint8-: xref:utils.adoc#Packing-replace_22_1-bytes22-bytes1-uint8- +:xref-Packing-extract_22_2-bytes22-uint8-: xref:utils.adoc#Packing-extract_22_2-bytes22-uint8- +:xref-Packing-replace_22_2-bytes22-bytes2-uint8-: xref:utils.adoc#Packing-replace_22_2-bytes22-bytes2-uint8- +:xref-Packing-extract_22_4-bytes22-uint8-: xref:utils.adoc#Packing-extract_22_4-bytes22-uint8- +:xref-Packing-replace_22_4-bytes22-bytes4-uint8-: xref:utils.adoc#Packing-replace_22_4-bytes22-bytes4-uint8- +:xref-Packing-extract_22_6-bytes22-uint8-: xref:utils.adoc#Packing-extract_22_6-bytes22-uint8- +:xref-Packing-replace_22_6-bytes22-bytes6-uint8-: xref:utils.adoc#Packing-replace_22_6-bytes22-bytes6-uint8- +:xref-Packing-extract_22_8-bytes22-uint8-: xref:utils.adoc#Packing-extract_22_8-bytes22-uint8- +:xref-Packing-replace_22_8-bytes22-bytes8-uint8-: xref:utils.adoc#Packing-replace_22_8-bytes22-bytes8-uint8- +:xref-Packing-extract_22_10-bytes22-uint8-: xref:utils.adoc#Packing-extract_22_10-bytes22-uint8- +:xref-Packing-replace_22_10-bytes22-bytes10-uint8-: xref:utils.adoc#Packing-replace_22_10-bytes22-bytes10-uint8- +:xref-Packing-extract_22_12-bytes22-uint8-: xref:utils.adoc#Packing-extract_22_12-bytes22-uint8- +:xref-Packing-replace_22_12-bytes22-bytes12-uint8-: xref:utils.adoc#Packing-replace_22_12-bytes22-bytes12-uint8- +:xref-Packing-extract_22_16-bytes22-uint8-: xref:utils.adoc#Packing-extract_22_16-bytes22-uint8- +:xref-Packing-replace_22_16-bytes22-bytes16-uint8-: xref:utils.adoc#Packing-replace_22_16-bytes22-bytes16-uint8- +:xref-Packing-extract_22_20-bytes22-uint8-: xref:utils.adoc#Packing-extract_22_20-bytes22-uint8- +:xref-Packing-replace_22_20-bytes22-bytes20-uint8-: xref:utils.adoc#Packing-replace_22_20-bytes22-bytes20-uint8- :xref-Packing-extract_24_1-bytes24-uint8-: xref:utils.adoc#Packing-extract_24_1-bytes24-uint8- :xref-Packing-replace_24_1-bytes24-bytes1-uint8-: xref:utils.adoc#Packing-replace_24_1-bytes24-bytes1-uint8- :xref-Packing-extract_24_2-bytes24-uint8-: xref:utils.adoc#Packing-extract_24_2-bytes24-uint8- @@ -621,12 +710,16 @@ :xref-Packing-replace_24_6-bytes24-bytes6-uint8-: xref:utils.adoc#Packing-replace_24_6-bytes24-bytes6-uint8- :xref-Packing-extract_24_8-bytes24-uint8-: xref:utils.adoc#Packing-extract_24_8-bytes24-uint8- :xref-Packing-replace_24_8-bytes24-bytes8-uint8-: xref:utils.adoc#Packing-replace_24_8-bytes24-bytes8-uint8- +:xref-Packing-extract_24_10-bytes24-uint8-: xref:utils.adoc#Packing-extract_24_10-bytes24-uint8- +:xref-Packing-replace_24_10-bytes24-bytes10-uint8-: xref:utils.adoc#Packing-replace_24_10-bytes24-bytes10-uint8- :xref-Packing-extract_24_12-bytes24-uint8-: xref:utils.adoc#Packing-extract_24_12-bytes24-uint8- :xref-Packing-replace_24_12-bytes24-bytes12-uint8-: xref:utils.adoc#Packing-replace_24_12-bytes24-bytes12-uint8- :xref-Packing-extract_24_16-bytes24-uint8-: xref:utils.adoc#Packing-extract_24_16-bytes24-uint8- :xref-Packing-replace_24_16-bytes24-bytes16-uint8-: xref:utils.adoc#Packing-replace_24_16-bytes24-bytes16-uint8- :xref-Packing-extract_24_20-bytes24-uint8-: xref:utils.adoc#Packing-extract_24_20-bytes24-uint8- :xref-Packing-replace_24_20-bytes24-bytes20-uint8-: xref:utils.adoc#Packing-replace_24_20-bytes24-bytes20-uint8- +:xref-Packing-extract_24_22-bytes24-uint8-: xref:utils.adoc#Packing-extract_24_22-bytes24-uint8- +:xref-Packing-replace_24_22-bytes24-bytes22-uint8-: xref:utils.adoc#Packing-replace_24_22-bytes24-bytes22-uint8- :xref-Packing-extract_28_1-bytes28-uint8-: xref:utils.adoc#Packing-extract_28_1-bytes28-uint8- :xref-Packing-replace_28_1-bytes28-bytes1-uint8-: xref:utils.adoc#Packing-replace_28_1-bytes28-bytes1-uint8- :xref-Packing-extract_28_2-bytes28-uint8-: xref:utils.adoc#Packing-extract_28_2-bytes28-uint8- @@ -637,12 +730,16 @@ :xref-Packing-replace_28_6-bytes28-bytes6-uint8-: xref:utils.adoc#Packing-replace_28_6-bytes28-bytes6-uint8- :xref-Packing-extract_28_8-bytes28-uint8-: xref:utils.adoc#Packing-extract_28_8-bytes28-uint8- :xref-Packing-replace_28_8-bytes28-bytes8-uint8-: xref:utils.adoc#Packing-replace_28_8-bytes28-bytes8-uint8- +:xref-Packing-extract_28_10-bytes28-uint8-: xref:utils.adoc#Packing-extract_28_10-bytes28-uint8- +:xref-Packing-replace_28_10-bytes28-bytes10-uint8-: xref:utils.adoc#Packing-replace_28_10-bytes28-bytes10-uint8- :xref-Packing-extract_28_12-bytes28-uint8-: xref:utils.adoc#Packing-extract_28_12-bytes28-uint8- :xref-Packing-replace_28_12-bytes28-bytes12-uint8-: xref:utils.adoc#Packing-replace_28_12-bytes28-bytes12-uint8- :xref-Packing-extract_28_16-bytes28-uint8-: xref:utils.adoc#Packing-extract_28_16-bytes28-uint8- :xref-Packing-replace_28_16-bytes28-bytes16-uint8-: xref:utils.adoc#Packing-replace_28_16-bytes28-bytes16-uint8- :xref-Packing-extract_28_20-bytes28-uint8-: xref:utils.adoc#Packing-extract_28_20-bytes28-uint8- :xref-Packing-replace_28_20-bytes28-bytes20-uint8-: xref:utils.adoc#Packing-replace_28_20-bytes28-bytes20-uint8- +:xref-Packing-extract_28_22-bytes28-uint8-: xref:utils.adoc#Packing-extract_28_22-bytes28-uint8- +:xref-Packing-replace_28_22-bytes28-bytes22-uint8-: xref:utils.adoc#Packing-replace_28_22-bytes28-bytes22-uint8- :xref-Packing-extract_28_24-bytes28-uint8-: xref:utils.adoc#Packing-extract_28_24-bytes28-uint8- :xref-Packing-replace_28_24-bytes28-bytes24-uint8-: xref:utils.adoc#Packing-replace_28_24-bytes28-bytes24-uint8- :xref-Packing-extract_32_1-bytes32-uint8-: xref:utils.adoc#Packing-extract_32_1-bytes32-uint8- @@ -655,12 +752,16 @@ :xref-Packing-replace_32_6-bytes32-bytes6-uint8-: xref:utils.adoc#Packing-replace_32_6-bytes32-bytes6-uint8- :xref-Packing-extract_32_8-bytes32-uint8-: xref:utils.adoc#Packing-extract_32_8-bytes32-uint8- :xref-Packing-replace_32_8-bytes32-bytes8-uint8-: xref:utils.adoc#Packing-replace_32_8-bytes32-bytes8-uint8- +:xref-Packing-extract_32_10-bytes32-uint8-: xref:utils.adoc#Packing-extract_32_10-bytes32-uint8- +:xref-Packing-replace_32_10-bytes32-bytes10-uint8-: xref:utils.adoc#Packing-replace_32_10-bytes32-bytes10-uint8- :xref-Packing-extract_32_12-bytes32-uint8-: xref:utils.adoc#Packing-extract_32_12-bytes32-uint8- :xref-Packing-replace_32_12-bytes32-bytes12-uint8-: xref:utils.adoc#Packing-replace_32_12-bytes32-bytes12-uint8- :xref-Packing-extract_32_16-bytes32-uint8-: xref:utils.adoc#Packing-extract_32_16-bytes32-uint8- :xref-Packing-replace_32_16-bytes32-bytes16-uint8-: xref:utils.adoc#Packing-replace_32_16-bytes32-bytes16-uint8- :xref-Packing-extract_32_20-bytes32-uint8-: xref:utils.adoc#Packing-extract_32_20-bytes32-uint8- :xref-Packing-replace_32_20-bytes32-bytes20-uint8-: xref:utils.adoc#Packing-replace_32_20-bytes32-bytes20-uint8- +:xref-Packing-extract_32_22-bytes32-uint8-: xref:utils.adoc#Packing-extract_32_22-bytes32-uint8- +:xref-Packing-replace_32_22-bytes32-bytes22-uint8-: xref:utils.adoc#Packing-replace_32_22-bytes32-bytes22-uint8- :xref-Packing-extract_32_24-bytes32-uint8-: xref:utils.adoc#Packing-extract_32_24-bytes32-uint8- :xref-Packing-replace_32_24-bytes32-bytes24-uint8-: xref:utils.adoc#Packing-replace_32_24-bytes32-bytes24-uint8- :xref-Packing-extract_32_28-bytes32-uint8-: xref:utils.adoc#Packing-extract_32_28-bytes32-uint8- @@ -699,6 +800,7 @@ Miscellaneous contracts and libraries containing utility functions you can use t * {ReentrancyGuardTransient}: Variant of {ReentrancyGuard} that uses transient storage (https://eips.ethereum.org/EIPS/eip-1153[EIP-1153]). * {Pausable}: A common emergency response mechanism that can pause functionality while a remediation is pending. * {Nonces}: Utility for tracking and verifying address nonces that only increment. + * {NoncesKeyed}: Alternative to {Nonces}, that support key-ed nonces following https://eips.ethereum.org/EIPS/eip-4337#semi-abstracted-nonce-support[ERC-4337 speciciations]. * {ERC165}, {ERC165Checker}: Utilities for inspecting interfaces supported by contracts. * {BitMaps}: A simple library to manage boolean value mapped to a numerical index in an efficient way. * {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`). @@ -712,16 +814,18 @@ Miscellaneous contracts and libraries containing utility functions you can use t * {Address}: Collection of functions for overloading Solidity's https://docs.soliditylang.org/en/latest/types.html#address[`address`] type. * {Arrays}: Collection of functions that operate on https://docs.soliditylang.org/en/latest/types.html#arrays[`arrays`]. * {Base64}: On-chain base64 and base64URL encoding according to https://datatracker.ietf.org/doc/html/rfc4648[RFC-4648]. + * {Bytes}: Common operations on bytes objects. * {Strings}: Common operations for strings formatting. * {ShortString}: Library to encode (and decode) short strings into (or from) a single bytes32 slot for optimizing costs. Short strings are limited to 31 characters. * {SlotDerivation}: Methods for deriving storage slot from ERC-7201 namespaces as well as from constructions such as mapping and arrays. - * {StorageSlot}: Methods for accessing specific storage slots formatted as common primitive types. + * {StorageSlot}: Methods for accessing specific storage slots formatted as common primitive types. * {TransientSlot}: Primitives for reading from and writing to transient storage (only value types are currently supported). * {Multicall}: Abstract contract with a utility to allow batching together multiple calls in a single transaction. Useful for allowing EOAs to perform multiple operations at once. * {Context}: A utility for abstracting the sender and calldata in the current execution context. * {Packing}: A library for packing and unpacking multiple values into bytes32 * {Panic}: A library to revert with https://docs.soliditylang.org/en/v0.8.20/control-structures.html#panic-via-assert-and-error-via-require[Solidity panic codes]. * {Comparators}: A library that contains comparator functions to use with with the {Heap} library. + * {CAIP2}, {CAIP10}: Libraries for formatting and parsing CAIP-2 and CAIP-10 identifiers. [NOTE] ==== @@ -761,7 +865,7 @@ Because Solidity does not support generic types, {EnumerableMap} and {Enumerable [.contract] [[Math]] -=== `++Math++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/utils/math/Math.sol[{github-icon},role=heading-link] +=== `++Math++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/utils/math/Math.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -1028,7 +1132,7 @@ Returns whether a provided rounding mode is considered rounding up for unsigned [.contract] [[SignedMath]] -=== `++SignedMath++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/utils/math/SignedMath.sol[{github-icon},role=heading-link] +=== `++SignedMath++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/utils/math/SignedMath.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -1155,7 +1259,7 @@ Returns the absolute unsigned value of a signed value. [.contract] [[SafeCast]] -=== `++SafeCast++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/utils/math/SafeCast.sol[{github-icon},role=heading-link] +=== `++SafeCast++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/utils/math/SafeCast.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -2156,7 +2260,7 @@ An uint value doesn't fit in an int of `bits` size. [.contract] [[ECDSA]] -=== `++ECDSA++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/utils/cryptography/ECDSA.sol[{github-icon},role=heading-link] +=== `++ECDSA++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/utils/cryptography/ECDSA.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -2291,7 +2395,7 @@ The signature has an S value that is in the upper half order. [.contract] [[P256]] -=== `++P256++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/utils/cryptography/P256.sol[{github-icon},role=heading-link] +=== `++P256++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/utils/cryptography/P256.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -2410,7 +2514,7 @@ B parameter of the weierstrass equation [.contract] [[RSA]] -=== `++RSA++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/utils/cryptography/RSA.sol[{github-icon},role=heading-link] +=== `++RSA++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/utils/cryptography/RSA.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -2469,7 +2573,7 @@ using a low exponent out of security concerns. [.contract] [[EIP712]] -=== `++EIP712++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/utils/cryptography/EIP712.sol[{github-icon},role=heading-link] +=== `++EIP712++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/utils/cryptography/EIP712.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -2592,7 +2696,7 @@ It only reads from storage if necessary (in case the value is too large to fit i [.contract] [[MessageHashUtils]] -=== `++MessageHashUtils++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/utils/cryptography/MessageHashUtils.sol[{github-icon},role=heading-link] +=== `++MessageHashUtils++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/utils/cryptography/MessageHashUtils.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -2674,7 +2778,7 @@ See {ECDSA-recover}. [.contract] [[SignatureChecker]] -=== `++SignatureChecker++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/utils/cryptography/SignatureChecker.sol[{github-icon},role=heading-link] +=== `++SignatureChecker++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/utils/cryptography/SignatureChecker.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -2717,7 +2821,7 @@ change through time. It could return true at block N and false at block N+1 (or [.contract] [[Hashes]] -=== `++Hashes++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/utils/cryptography/Hashes.sol[{github-icon},role=heading-link] +=== `++Hashes++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/utils/cryptography/Hashes.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -2763,7 +2867,7 @@ NOTE: Equivalent to the `standardNodeHash` in our https://github.com/OpenZeppeli [.contract] [[MerkleProof]] -=== `++MerkleProof++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/utils/cryptography/MerkleProof.sol[{github-icon},role=heading-link] +=== `++MerkleProof++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/utils/cryptography/MerkleProof.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -3055,7 +3159,7 @@ The multiproof provided is not valid. [.contract] [[ReentrancyGuard]] -=== `++ReentrancyGuard++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/utils/ReentrancyGuard.sol[{github-icon},role=heading-link] +=== `++ReentrancyGuard++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/utils/ReentrancyGuard.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -3134,7 +3238,7 @@ Unauthorized reentrant call. [.contract] [[ReentrancyGuardTransient]] -=== `++ReentrancyGuardTransient++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/utils/ReentrancyGuardTransient.sol[{github-icon},role=heading-link] +=== `++ReentrancyGuardTransient++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/utils/ReentrancyGuardTransient.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -3205,7 +3309,7 @@ Unauthorized reentrant call. [.contract] [[Pausable]] -=== `++Pausable++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/utils/Pausable.sol[{github-icon},role=heading-link] +=== `++Pausable++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/utils/Pausable.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -3350,7 +3454,7 @@ The operation failed because the contract is not paused. [.contract] [[Nonces]] -=== `++Nonces++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/utils/Nonces.sol[{github-icon},role=heading-link] +=== `++Nonces++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/utils/Nonces.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -3401,6 +3505,86 @@ Same as {_useNonce} but checking that `nonce` is the next valid for `owner`. The nonce used for an `account` is not the expected current nonce. +:nonces: pass:normal[xref:#NoncesKeyed-nonces-address-uint192-[`++nonces++`]] +:_useNonce: pass:normal[xref:#NoncesKeyed-_useNonce-address-uint192-[`++_useNonce++`]] +:_useCheckedNonce: pass:normal[xref:#NoncesKeyed-_useCheckedNonce-address-uint256-[`++_useCheckedNonce++`]] +:_useCheckedNonce: pass:normal[xref:#NoncesKeyed-_useCheckedNonce-address-uint192-uint64-[`++_useCheckedNonce++`]] + +[.contract] +[[NoncesKeyed]] +=== `++NoncesKeyed++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/utils/NoncesKeyed.sol[{github-icon},role=heading-link] + +[.hljs-theme-light.nopadding] +```solidity +import "@openzeppelin/contracts/utils/NoncesKeyed.sol"; +``` + +Alternative to {Nonces}, that supports key-ed nonces. + +Follows the https://eips.ethereum.org/EIPS/eip-4337#semi-abstracted-nonce-support[ERC-4337's semi-abstracted nonce system]. + +NOTE: This contract inherits from {Nonces} and reuses its storage for the first nonce key (i.e. `0`). This +makes upgrading from {Nonces} to {NoncesKeyed} safe when using their upgradeable versions (e.g. `NoncesKeyedUpgradeable`). +Doing so will NOT reset the current state of nonces, avoiding replay attacks where a nonce is reused after the upgrade. + +[.contract-index] +.Functions +-- +* {xref-NoncesKeyed-nonces-address-uint192-}[`++nonces(owner, key)++`] +* {xref-NoncesKeyed-_useNonce-address-uint192-}[`++_useNonce(owner, key)++`] +* {xref-NoncesKeyed-_useCheckedNonce-address-uint256-}[`++_useCheckedNonce(owner, keyNonce)++`] +* {xref-NoncesKeyed-_useCheckedNonce-address-uint192-uint64-}[`++_useCheckedNonce(owner, key, nonce)++`] + +[.contract-subindex-inherited] +.Nonces +* {xref-Nonces-nonces-address-}[`++nonces(owner)++`] +* {xref-Nonces-_useNonce-address-}[`++_useNonce(owner)++`] + +-- + +[.contract-index] +.Errors +-- + +[.contract-subindex-inherited] +.Nonces +* {xref-Nonces-InvalidAccountNonce-address-uint256-}[`++InvalidAccountNonce(account, currentNonce)++`] + +-- + +[.contract-item] +[[NoncesKeyed-nonces-address-uint192-]] +==== `[.contract-item-name]#++nonces++#++(address owner, uint192 key) → uint256++` [.item-kind]#public# + +Returns the next unused nonce for an address and key. Result contains the key prefix. + +[.contract-item] +[[NoncesKeyed-_useNonce-address-uint192-]] +==== `[.contract-item-name]#++_useNonce++#++(address owner, uint192 key) → uint256++` [.item-kind]#internal# + +Consumes the next unused nonce for an address and key. + +Returns the current value without the key prefix. Consumed nonce is increased, so calling this function twice +with the same arguments will return different (sequential) results. + +[.contract-item] +[[NoncesKeyed-_useCheckedNonce-address-uint256-]] +==== `[.contract-item-name]#++_useCheckedNonce++#++(address owner, uint256 keyNonce)++` [.item-kind]#internal# + +Same as {_useNonce} but checking that `nonce` is the next valid for `owner`. + +This version takes the key and the nonce in a single uint256 parameter: +- use the first 24 bytes for the key +- use the last 8 bytes for the nonce + +[.contract-item] +[[NoncesKeyed-_useCheckedNonce-address-uint192-uint64-]] +==== `[.contract-item-name]#++_useCheckedNonce++#++(address owner, uint192 key, uint64 nonce)++` [.item-kind]#internal# + +Same as {_useNonce} but checking that `nonce` is the next valid for `owner`. + +This version takes the key and the nonce as two different parameters. + == 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_. @@ -3411,7 +3595,7 @@ Ethereum contracts have no native concept of an interface, so applications must [.contract] [[IERC165]] -=== `++IERC165++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/utils/introspection/IERC165.sol[{github-icon},role=heading-link] +=== `++IERC165++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/utils/introspection/IERC165.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -3448,7 +3632,7 @@ This function call must use less than 30 000 gas. [.contract] [[ERC165]] -=== `++ERC165++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/utils/introspection/ERC165.sol[{github-icon},role=heading-link] +=== `++ERC165++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/utils/introspection/ERC165.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -3490,7 +3674,7 @@ See {IERC165-supportsInterface}. [.contract] [[ERC165Checker]] -=== `++ERC165Checker++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/utils/introspection/ERC165Checker.sol[{github-icon},role=heading-link] +=== `++ERC165Checker++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/utils/introspection/ERC165Checker.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -3575,7 +3759,7 @@ Interface identification is specified in ERC-165. [.contract] [[BitMaps]] -=== `++BitMaps++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/utils/structs/BitMaps.sol[{github-icon},role=heading-link] +=== `++BitMaps++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/utils/structs/BitMaps.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -3713,7 +3897,7 @@ Unsets the bit at `index`. [.contract] [[EnumerableMap]] -=== `++EnumerableMap++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/utils/structs/EnumerableMap.sol[{github-icon},role=heading-link] +=== `++EnumerableMap++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/utils/structs/EnumerableMap.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -4509,7 +4693,7 @@ Query for a nonexistent map key. [.contract] [[EnumerableSet]] -=== `++EnumerableSet++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/utils/structs/EnumerableSet.sol[{github-icon},role=heading-link] +=== `++EnumerableSet++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/utils/structs/EnumerableSet.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -4749,7 +4933,7 @@ uncallable if the set grows to a point where copying to memory consumes too much [.contract] [[DoubleEndedQueue]] -=== `++DoubleEndedQueue++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/utils/structs/DoubleEndedQueue.sol[{github-icon},role=heading-link] +=== `++DoubleEndedQueue++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/utils/structs/DoubleEndedQueue.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -4873,7 +5057,7 @@ Returns true if the queue is empty. [.contract] [[CircularBuffer]] -=== `++CircularBuffer++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/utils/structs/CircularBuffer.sol[{github-icon},role=heading-link] +=== `++CircularBuffer++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/utils/structs/CircularBuffer.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -5023,7 +5207,7 @@ Error emitted when trying to setup a buffer with a size of 0. [.contract] [[Checkpoints]] -=== `++Checkpoints++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/utils/structs/Checkpoints.sol[{github-icon},role=heading-link] +=== `++Checkpoints++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/utils/structs/Checkpoints.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -5272,7 +5456,7 @@ A value was attempted to be inserted on a past checkpoint. [.contract] [[Heap]] -=== `++Heap++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/utils/structs/Heap.sol[{github-icon},role=heading-link] +=== `++Heap++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/utils/structs/Heap.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -5403,7 +5587,7 @@ Removes all elements in the heap. [.contract] [[MerkleTree]] -=== `++MerkleTree++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/utils/structs/MerkleTree.sol[{github-icon},role=heading-link] +=== `++MerkleTree++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/utils/structs/MerkleTree.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -5511,7 +5695,7 @@ Tree's depth (set at initialization) [.contract] [[Create2]] -=== `++Create2++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/utils/Create2.sol[{github-icon},role=heading-link] +=== `++Create2++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/utils/Create2.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -5590,7 +5774,7 @@ There's no code to deploy. [.contract] [[Address]] -=== `++Address++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/utils/Address.sol[{github-icon},role=heading-link] +=== `++Address++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/utils/Address.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -5729,7 +5913,7 @@ There's no code at `target` (it is not a contract). [.contract] [[Arrays]] -=== `++Arrays++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/utils/Arrays.sol[{github-icon},role=heading-link] +=== `++Arrays++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/utils/Arrays.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -5959,7 +6143,7 @@ WARNING: this does not clear elements if length is reduced, of initialize elemen [.contract] [[Base64]] -=== `++Base64++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/utils/Base64.sol[{github-icon},role=heading-link] +=== `++Base64++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/utils/Base64.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -6009,6 +6193,8 @@ See sections 4 and 5 of https://datatracker.ietf.org/doc/html/rfc4648 ==== `string [.contract-item-name]#++_TABLE_URL++#` [.item-kind]#internal constant# :StringsInsufficientHexLength: pass:normal[xref:#Strings-StringsInsufficientHexLength-uint256-uint256-[`++StringsInsufficientHexLength++`]] +:StringsInvalidChar: pass:normal[xref:#Strings-StringsInvalidChar--[`++StringsInvalidChar++`]] +:StringsInvalidAddressFormat: pass:normal[xref:#Strings-StringsInvalidAddressFormat--[`++StringsInvalidAddressFormat++`]] :toString: pass:normal[xref:#Strings-toString-uint256-[`++toString++`]] :toStringSigned: pass:normal[xref:#Strings-toStringSigned-int256-[`++toStringSigned++`]] :toHexString: pass:normal[xref:#Strings-toHexString-uint256-[`++toHexString++`]] @@ -6016,10 +6202,26 @@ See sections 4 and 5 of https://datatracker.ietf.org/doc/html/rfc4648 :toHexString: pass:normal[xref:#Strings-toHexString-address-[`++toHexString++`]] :toChecksumHexString: pass:normal[xref:#Strings-toChecksumHexString-address-[`++toChecksumHexString++`]] :equal: pass:normal[xref:#Strings-equal-string-string-[`++equal++`]] +:parseUint: pass:normal[xref:#Strings-parseUint-string-[`++parseUint++`]] +:parseUint: pass:normal[xref:#Strings-parseUint-string-uint256-uint256-[`++parseUint++`]] +:tryParseUint: pass:normal[xref:#Strings-tryParseUint-string-[`++tryParseUint++`]] +:tryParseUint: pass:normal[xref:#Strings-tryParseUint-string-uint256-uint256-[`++tryParseUint++`]] +:parseInt: pass:normal[xref:#Strings-parseInt-string-[`++parseInt++`]] +:parseInt: pass:normal[xref:#Strings-parseInt-string-uint256-uint256-[`++parseInt++`]] +:tryParseInt: pass:normal[xref:#Strings-tryParseInt-string-[`++tryParseInt++`]] +:tryParseInt: pass:normal[xref:#Strings-tryParseInt-string-uint256-uint256-[`++tryParseInt++`]] +:parseHexUint: pass:normal[xref:#Strings-parseHexUint-string-[`++parseHexUint++`]] +:parseHexUint: pass:normal[xref:#Strings-parseHexUint-string-uint256-uint256-[`++parseHexUint++`]] +:tryParseHexUint: pass:normal[xref:#Strings-tryParseHexUint-string-[`++tryParseHexUint++`]] +:tryParseHexUint: pass:normal[xref:#Strings-tryParseHexUint-string-uint256-uint256-[`++tryParseHexUint++`]] +:parseAddress: pass:normal[xref:#Strings-parseAddress-string-[`++parseAddress++`]] +:parseAddress: pass:normal[xref:#Strings-parseAddress-string-uint256-uint256-[`++parseAddress++`]] +:tryParseAddress: pass:normal[xref:#Strings-tryParseAddress-string-[`++tryParseAddress++`]] +:tryParseAddress: pass:normal[xref:#Strings-tryParseAddress-string-uint256-uint256-[`++tryParseAddress++`]] [.contract] [[Strings]] -=== `++Strings++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/utils/Strings.sol[{github-icon},role=heading-link] +=== `++Strings++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/utils/Strings.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -6038,6 +6240,22 @@ String operations. * {xref-Strings-toHexString-address-}[`++toHexString(addr)++`] * {xref-Strings-toChecksumHexString-address-}[`++toChecksumHexString(addr)++`] * {xref-Strings-equal-string-string-}[`++equal(a, b)++`] +* {xref-Strings-parseUint-string-}[`++parseUint(input)++`] +* {xref-Strings-parseUint-string-uint256-uint256-}[`++parseUint(input, begin, end)++`] +* {xref-Strings-tryParseUint-string-}[`++tryParseUint(input)++`] +* {xref-Strings-tryParseUint-string-uint256-uint256-}[`++tryParseUint(input, begin, end)++`] +* {xref-Strings-parseInt-string-}[`++parseInt(input)++`] +* {xref-Strings-parseInt-string-uint256-uint256-}[`++parseInt(input, begin, end)++`] +* {xref-Strings-tryParseInt-string-}[`++tryParseInt(input)++`] +* {xref-Strings-tryParseInt-string-uint256-uint256-}[`++tryParseInt(input, begin, end)++`] +* {xref-Strings-parseHexUint-string-}[`++parseHexUint(input)++`] +* {xref-Strings-parseHexUint-string-uint256-uint256-}[`++parseHexUint(input, begin, end)++`] +* {xref-Strings-tryParseHexUint-string-}[`++tryParseHexUint(input)++`] +* {xref-Strings-tryParseHexUint-string-uint256-uint256-}[`++tryParseHexUint(input, begin, end)++`] +* {xref-Strings-parseAddress-string-}[`++parseAddress(input)++`] +* {xref-Strings-parseAddress-string-uint256-uint256-}[`++parseAddress(input, begin, end)++`] +* {xref-Strings-tryParseAddress-string-}[`++tryParseAddress(input)++`] +* {xref-Strings-tryParseAddress-string-uint256-uint256-}[`++tryParseAddress(input, begin, end)++`] -- @@ -6045,6 +6263,8 @@ String operations. .Errors -- * {xref-Strings-StringsInsufficientHexLength-uint256-uint256-}[`++StringsInsufficientHexLength(value, length)++`] +* {xref-Strings-StringsInvalidChar--}[`++StringsInvalidChar()++`] +* {xref-Strings-StringsInvalidAddressFormat--}[`++StringsInvalidAddressFormat()++`] -- @@ -6092,12 +6312,172 @@ representation, according to EIP-55. Returns true if the two strings are equal. +[.contract-item] +[[Strings-parseUint-string-]] +==== `[.contract-item-name]#++parseUint++#++(string input) → uint256++` [.item-kind]#internal# + +Parse a decimal string and returns the value as a `uint256`. + +Requirements: +- The string must be formatted as `[0-9]*` +- The result must fit into an `uint256` type + +[.contract-item] +[[Strings-parseUint-string-uint256-uint256-]] +==== `[.contract-item-name]#++parseUint++#++(string input, uint256 begin, uint256 end) → uint256++` [.item-kind]#internal# + +Variant of {parseUint} that parses a substring of `input` located between position `begin` (included) and +`end` (excluded). + +Requirements: +- The substring must be formatted as `[0-9]*` +- The result must fit into an `uint256` type + +[.contract-item] +[[Strings-tryParseUint-string-]] +==== `[.contract-item-name]#++tryParseUint++#++(string input) → bool success, uint256 value++` [.item-kind]#internal# + +Variant of {parseUint-string} that returns false if the parsing fails because of an invalid character. + +NOTE: This function will revert if the result does not fit in a `uint256`. + +[.contract-item] +[[Strings-tryParseUint-string-uint256-uint256-]] +==== `[.contract-item-name]#++tryParseUint++#++(string input, uint256 begin, uint256 end) → bool success, uint256 value++` [.item-kind]#internal# + +Variant of {parseUint-string-uint256-uint256} that returns false if the parsing fails because of an invalid +character. + +NOTE: This function will revert if the result does not fit in a `uint256`. + +[.contract-item] +[[Strings-parseInt-string-]] +==== `[.contract-item-name]#++parseInt++#++(string input) → int256++` [.item-kind]#internal# + +Parse a decimal string and returns the value as a `int256`. + +Requirements: +- The string must be formatted as `[-+]?[0-9]*` +- The result must fit in an `int256` type. + +[.contract-item] +[[Strings-parseInt-string-uint256-uint256-]] +==== `[.contract-item-name]#++parseInt++#++(string input, uint256 begin, uint256 end) → int256++` [.item-kind]#internal# + +Variant of {parseInt-string} that parses a substring of `input` located between position `begin` (included) and +`end` (excluded). + +Requirements: +- The substring must be formatted as `[-+]?[0-9]*` +- The result must fit in an `int256` type. + +[.contract-item] +[[Strings-tryParseInt-string-]] +==== `[.contract-item-name]#++tryParseInt++#++(string input) → bool success, int256 value++` [.item-kind]#internal# + +Variant of {parseInt-string} that returns false if the parsing fails because of an invalid character or if +the result does not fit in a `int256`. + +NOTE: This function will revert if the absolute value of the result does not fit in a `uint256`. + +[.contract-item] +[[Strings-tryParseInt-string-uint256-uint256-]] +==== `[.contract-item-name]#++tryParseInt++#++(string input, uint256 begin, uint256 end) → bool success, int256 value++` [.item-kind]#internal# + +Variant of {parseInt-string-uint256-uint256} that returns false if the parsing fails because of an invalid +character or if the result does not fit in a `int256`. + +NOTE: This function will revert if the absolute value of the result does not fit in a `uint256`. + +[.contract-item] +[[Strings-parseHexUint-string-]] +==== `[.contract-item-name]#++parseHexUint++#++(string input) → uint256++` [.item-kind]#internal# + +Parse a hexadecimal string (with or without "0x" prefix), and returns the value as a `uint256`. + +Requirements: +- The string must be formatted as `(0x)?[0-9a-fA-F]*` +- The result must fit in an `uint256` type. + +[.contract-item] +[[Strings-parseHexUint-string-uint256-uint256-]] +==== `[.contract-item-name]#++parseHexUint++#++(string input, uint256 begin, uint256 end) → uint256++` [.item-kind]#internal# + +Variant of {parseHexUint} that parses a substring of `input` located between position `begin` (included) and +`end` (excluded). + +Requirements: +- The substring must be formatted as `(0x)?[0-9a-fA-F]*` +- The result must fit in an `uint256` type. + +[.contract-item] +[[Strings-tryParseHexUint-string-]] +==== `[.contract-item-name]#++tryParseHexUint++#++(string input) → bool success, uint256 value++` [.item-kind]#internal# + +Variant of {parseHexUint-string} that returns false if the parsing fails because of an invalid character. + +NOTE: This function will revert if the result does not fit in a `uint256`. + +[.contract-item] +[[Strings-tryParseHexUint-string-uint256-uint256-]] +==== `[.contract-item-name]#++tryParseHexUint++#++(string input, uint256 begin, uint256 end) → bool success, uint256 value++` [.item-kind]#internal# + +Variant of {parseHexUint-string-uint256-uint256} that returns false if the parsing fails because of an +invalid character. + +NOTE: This function will revert if the result does not fit in a `uint256`. + +[.contract-item] +[[Strings-parseAddress-string-]] +==== `[.contract-item-name]#++parseAddress++#++(string input) → address++` [.item-kind]#internal# + +Parse a hexadecimal string (with or without "0x" prefix), and returns the value as an `address`. + +Requirements: +- The string must be formatted as `(0x)?[0-9a-fA-F]{40}` + +[.contract-item] +[[Strings-parseAddress-string-uint256-uint256-]] +==== `[.contract-item-name]#++parseAddress++#++(string input, uint256 begin, uint256 end) → address++` [.item-kind]#internal# + +Variant of {parseAddress} that parses a substring of `input` located between position `begin` (included) and +`end` (excluded). + +Requirements: +- The substring must be formatted as `(0x)?[0-9a-fA-F]{40}` + +[.contract-item] +[[Strings-tryParseAddress-string-]] +==== `[.contract-item-name]#++tryParseAddress++#++(string input) → bool success, address value++` [.item-kind]#internal# + +Variant of {parseAddress-string} that returns false if the parsing fails because the input is not a properly +formatted address. See {parseAddress} requirements. + +[.contract-item] +[[Strings-tryParseAddress-string-uint256-uint256-]] +==== `[.contract-item-name]#++tryParseAddress++#++(string input, uint256 begin, uint256 end) → bool success, address value++` [.item-kind]#internal# + +Variant of {parseAddress-string-uint256-uint256} that returns false if the parsing fails because input is not a properly +formatted address. See {parseAddress} requirements. + [.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`. +[.contract-item] +[[Strings-StringsInvalidChar--]] +==== `[.contract-item-name]#++StringsInvalidChar++#++()++` [.item-kind]#error# + +The string being parsed contains characters that are not in scope of the given base. + +[.contract-item] +[[Strings-StringsInvalidAddressFormat--]] +==== `[.contract-item-name]#++StringsInvalidAddressFormat++#++()++` [.item-kind]#error# + +The string being parsed is not a properly formatted address. + :StringTooLong: pass:normal[xref:#ShortStrings-StringTooLong-string-[`++StringTooLong++`]] :InvalidShortString: pass:normal[xref:#ShortStrings-InvalidShortString--[`++InvalidShortString++`]] :toShortString: pass:normal[xref:#ShortStrings-toShortString-string-[`++toShortString++`]] @@ -6109,7 +6489,7 @@ The `value` string doesn't fit in the specified `length`. [.contract] [[ShortStrings]] -=== `++ShortStrings++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/utils/ShortStrings.sol[{github-icon},role=heading-link] +=== `++ShortStrings++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/utils/ShortStrings.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -6226,7 +6606,7 @@ actual characters as the UTF-8 encoding of a single character can span over mult [.contract] [[SlotDerivation]] -=== `++SlotDerivation++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/utils/SlotDerivation.sol[{github-icon},role=heading-link] +=== `++SlotDerivation++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/utils/SlotDerivation.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -6361,7 +6741,7 @@ Derive the location of a mapping element from the key. [.contract] [[StorageSlot]] -=== `++StorageSlot++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/utils/StorageSlot.sol[{github-icon},role=heading-link] +=== `++StorageSlot++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/utils/StorageSlot.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -6486,7 +6866,7 @@ Returns an `BytesSlot` representation of the bytes storage pointer `store`. [.contract] [[TransientSlot]] -=== `++TransientSlot++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/utils/TransientSlot.sol[{github-icon},role=heading-link] +=== `++TransientSlot++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/utils/TransientSlot.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -6633,7 +7013,7 @@ Store `value` at location `slot` in transient storage. [.contract] [[Multicall]] -=== `++Multicall++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/utils/Multicall.sol[{github-icon},role=heading-link] +=== `++Multicall++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/utils/Multicall.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -6670,7 +7050,7 @@ Receives and executes a batch of function calls on this contract. [.contract] [[Context]] -=== `++Context++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/utils/Context.sol[{github-icon},role=heading-link] +=== `++Context++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/utils/Context.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -6712,8 +7092,13 @@ This contract is only required for intermediate, library-like contracts. :pack_2_2: pass:normal[xref:#Packing-pack_2_2-bytes2-bytes2-[`++pack_2_2++`]] :pack_2_4: pass:normal[xref:#Packing-pack_2_4-bytes2-bytes4-[`++pack_2_4++`]] :pack_2_6: pass:normal[xref:#Packing-pack_2_6-bytes2-bytes6-[`++pack_2_6++`]] +:pack_2_8: pass:normal[xref:#Packing-pack_2_8-bytes2-bytes8-[`++pack_2_8++`]] +:pack_2_10: pass:normal[xref:#Packing-pack_2_10-bytes2-bytes10-[`++pack_2_10++`]] +:pack_2_20: pass:normal[xref:#Packing-pack_2_20-bytes2-bytes20-[`++pack_2_20++`]] +:pack_2_22: pass:normal[xref:#Packing-pack_2_22-bytes2-bytes22-[`++pack_2_22++`]] :pack_4_2: pass:normal[xref:#Packing-pack_4_2-bytes4-bytes2-[`++pack_4_2++`]] :pack_4_4: pass:normal[xref:#Packing-pack_4_4-bytes4-bytes4-[`++pack_4_4++`]] +:pack_4_6: pass:normal[xref:#Packing-pack_4_6-bytes4-bytes6-[`++pack_4_6++`]] :pack_4_8: pass:normal[xref:#Packing-pack_4_8-bytes4-bytes8-[`++pack_4_8++`]] :pack_4_12: pass:normal[xref:#Packing-pack_4_12-bytes4-bytes12-[`++pack_4_12++`]] :pack_4_16: pass:normal[xref:#Packing-pack_4_16-bytes4-bytes16-[`++pack_4_16++`]] @@ -6721,25 +7106,41 @@ This contract is only required for intermediate, library-like contracts. :pack_4_24: pass:normal[xref:#Packing-pack_4_24-bytes4-bytes24-[`++pack_4_24++`]] :pack_4_28: pass:normal[xref:#Packing-pack_4_28-bytes4-bytes28-[`++pack_4_28++`]] :pack_6_2: pass:normal[xref:#Packing-pack_6_2-bytes6-bytes2-[`++pack_6_2++`]] +:pack_6_4: pass:normal[xref:#Packing-pack_6_4-bytes6-bytes4-[`++pack_6_4++`]] :pack_6_6: pass:normal[xref:#Packing-pack_6_6-bytes6-bytes6-[`++pack_6_6++`]] +:pack_6_10: pass:normal[xref:#Packing-pack_6_10-bytes6-bytes10-[`++pack_6_10++`]] +:pack_6_16: pass:normal[xref:#Packing-pack_6_16-bytes6-bytes16-[`++pack_6_16++`]] +:pack_6_22: pass:normal[xref:#Packing-pack_6_22-bytes6-bytes22-[`++pack_6_22++`]] +:pack_8_2: pass:normal[xref:#Packing-pack_8_2-bytes8-bytes2-[`++pack_8_2++`]] :pack_8_4: pass:normal[xref:#Packing-pack_8_4-bytes8-bytes4-[`++pack_8_4++`]] :pack_8_8: pass:normal[xref:#Packing-pack_8_8-bytes8-bytes8-[`++pack_8_8++`]] :pack_8_12: pass:normal[xref:#Packing-pack_8_12-bytes8-bytes12-[`++pack_8_12++`]] :pack_8_16: pass:normal[xref:#Packing-pack_8_16-bytes8-bytes16-[`++pack_8_16++`]] :pack_8_20: pass:normal[xref:#Packing-pack_8_20-bytes8-bytes20-[`++pack_8_20++`]] :pack_8_24: pass:normal[xref:#Packing-pack_8_24-bytes8-bytes24-[`++pack_8_24++`]] +:pack_10_2: pass:normal[xref:#Packing-pack_10_2-bytes10-bytes2-[`++pack_10_2++`]] +:pack_10_6: pass:normal[xref:#Packing-pack_10_6-bytes10-bytes6-[`++pack_10_6++`]] +:pack_10_10: pass:normal[xref:#Packing-pack_10_10-bytes10-bytes10-[`++pack_10_10++`]] +:pack_10_12: pass:normal[xref:#Packing-pack_10_12-bytes10-bytes12-[`++pack_10_12++`]] +:pack_10_22: pass:normal[xref:#Packing-pack_10_22-bytes10-bytes22-[`++pack_10_22++`]] :pack_12_4: pass:normal[xref:#Packing-pack_12_4-bytes12-bytes4-[`++pack_12_4++`]] :pack_12_8: pass:normal[xref:#Packing-pack_12_8-bytes12-bytes8-[`++pack_12_8++`]] +:pack_12_10: pass:normal[xref:#Packing-pack_12_10-bytes12-bytes10-[`++pack_12_10++`]] :pack_12_12: pass:normal[xref:#Packing-pack_12_12-bytes12-bytes12-[`++pack_12_12++`]] :pack_12_16: pass:normal[xref:#Packing-pack_12_16-bytes12-bytes16-[`++pack_12_16++`]] :pack_12_20: pass:normal[xref:#Packing-pack_12_20-bytes12-bytes20-[`++pack_12_20++`]] :pack_16_4: pass:normal[xref:#Packing-pack_16_4-bytes16-bytes4-[`++pack_16_4++`]] +:pack_16_6: pass:normal[xref:#Packing-pack_16_6-bytes16-bytes6-[`++pack_16_6++`]] :pack_16_8: pass:normal[xref:#Packing-pack_16_8-bytes16-bytes8-[`++pack_16_8++`]] :pack_16_12: pass:normal[xref:#Packing-pack_16_12-bytes16-bytes12-[`++pack_16_12++`]] :pack_16_16: pass:normal[xref:#Packing-pack_16_16-bytes16-bytes16-[`++pack_16_16++`]] +:pack_20_2: pass:normal[xref:#Packing-pack_20_2-bytes20-bytes2-[`++pack_20_2++`]] :pack_20_4: pass:normal[xref:#Packing-pack_20_4-bytes20-bytes4-[`++pack_20_4++`]] :pack_20_8: pass:normal[xref:#Packing-pack_20_8-bytes20-bytes8-[`++pack_20_8++`]] :pack_20_12: pass:normal[xref:#Packing-pack_20_12-bytes20-bytes12-[`++pack_20_12++`]] +:pack_22_2: pass:normal[xref:#Packing-pack_22_2-bytes22-bytes2-[`++pack_22_2++`]] +:pack_22_6: pass:normal[xref:#Packing-pack_22_6-bytes22-bytes6-[`++pack_22_6++`]] +:pack_22_10: pass:normal[xref:#Packing-pack_22_10-bytes22-bytes10-[`++pack_22_10++`]] :pack_24_4: pass:normal[xref:#Packing-pack_24_4-bytes24-bytes4-[`++pack_24_4++`]] :pack_24_8: pass:normal[xref:#Packing-pack_24_8-bytes24-bytes8-[`++pack_24_8++`]] :pack_28_4: pass:normal[xref:#Packing-pack_28_4-bytes28-bytes4-[`++pack_28_4++`]] @@ -6763,6 +7164,16 @@ This contract is only required for intermediate, library-like contracts. :replace_8_4: pass:normal[xref:#Packing-replace_8_4-bytes8-bytes4-uint8-[`++replace_8_4++`]] :extract_8_6: pass:normal[xref:#Packing-extract_8_6-bytes8-uint8-[`++extract_8_6++`]] :replace_8_6: pass:normal[xref:#Packing-replace_8_6-bytes8-bytes6-uint8-[`++replace_8_6++`]] +:extract_10_1: pass:normal[xref:#Packing-extract_10_1-bytes10-uint8-[`++extract_10_1++`]] +:replace_10_1: pass:normal[xref:#Packing-replace_10_1-bytes10-bytes1-uint8-[`++replace_10_1++`]] +:extract_10_2: pass:normal[xref:#Packing-extract_10_2-bytes10-uint8-[`++extract_10_2++`]] +:replace_10_2: pass:normal[xref:#Packing-replace_10_2-bytes10-bytes2-uint8-[`++replace_10_2++`]] +:extract_10_4: pass:normal[xref:#Packing-extract_10_4-bytes10-uint8-[`++extract_10_4++`]] +:replace_10_4: pass:normal[xref:#Packing-replace_10_4-bytes10-bytes4-uint8-[`++replace_10_4++`]] +:extract_10_6: pass:normal[xref:#Packing-extract_10_6-bytes10-uint8-[`++extract_10_6++`]] +:replace_10_6: pass:normal[xref:#Packing-replace_10_6-bytes10-bytes6-uint8-[`++replace_10_6++`]] +:extract_10_8: pass:normal[xref:#Packing-extract_10_8-bytes10-uint8-[`++extract_10_8++`]] +:replace_10_8: pass:normal[xref:#Packing-replace_10_8-bytes10-bytes8-uint8-[`++replace_10_8++`]] :extract_12_1: pass:normal[xref:#Packing-extract_12_1-bytes12-uint8-[`++extract_12_1++`]] :replace_12_1: pass:normal[xref:#Packing-replace_12_1-bytes12-bytes1-uint8-[`++replace_12_1++`]] :extract_12_2: pass:normal[xref:#Packing-extract_12_2-bytes12-uint8-[`++extract_12_2++`]] @@ -6773,6 +7184,8 @@ This contract is only required for intermediate, library-like contracts. :replace_12_6: pass:normal[xref:#Packing-replace_12_6-bytes12-bytes6-uint8-[`++replace_12_6++`]] :extract_12_8: pass:normal[xref:#Packing-extract_12_8-bytes12-uint8-[`++extract_12_8++`]] :replace_12_8: pass:normal[xref:#Packing-replace_12_8-bytes12-bytes8-uint8-[`++replace_12_8++`]] +:extract_12_10: pass:normal[xref:#Packing-extract_12_10-bytes12-uint8-[`++extract_12_10++`]] +:replace_12_10: pass:normal[xref:#Packing-replace_12_10-bytes12-bytes10-uint8-[`++replace_12_10++`]] :extract_16_1: pass:normal[xref:#Packing-extract_16_1-bytes16-uint8-[`++extract_16_1++`]] :replace_16_1: pass:normal[xref:#Packing-replace_16_1-bytes16-bytes1-uint8-[`++replace_16_1++`]] :extract_16_2: pass:normal[xref:#Packing-extract_16_2-bytes16-uint8-[`++extract_16_2++`]] @@ -6783,6 +7196,8 @@ This contract is only required for intermediate, library-like contracts. :replace_16_6: pass:normal[xref:#Packing-replace_16_6-bytes16-bytes6-uint8-[`++replace_16_6++`]] :extract_16_8: pass:normal[xref:#Packing-extract_16_8-bytes16-uint8-[`++extract_16_8++`]] :replace_16_8: pass:normal[xref:#Packing-replace_16_8-bytes16-bytes8-uint8-[`++replace_16_8++`]] +:extract_16_10: pass:normal[xref:#Packing-extract_16_10-bytes16-uint8-[`++extract_16_10++`]] +:replace_16_10: pass:normal[xref:#Packing-replace_16_10-bytes16-bytes10-uint8-[`++replace_16_10++`]] :extract_16_12: pass:normal[xref:#Packing-extract_16_12-bytes16-uint8-[`++extract_16_12++`]] :replace_16_12: pass:normal[xref:#Packing-replace_16_12-bytes16-bytes12-uint8-[`++replace_16_12++`]] :extract_20_1: pass:normal[xref:#Packing-extract_20_1-bytes20-uint8-[`++extract_20_1++`]] @@ -6795,10 +7210,30 @@ This contract is only required for intermediate, library-like contracts. :replace_20_6: pass:normal[xref:#Packing-replace_20_6-bytes20-bytes6-uint8-[`++replace_20_6++`]] :extract_20_8: pass:normal[xref:#Packing-extract_20_8-bytes20-uint8-[`++extract_20_8++`]] :replace_20_8: pass:normal[xref:#Packing-replace_20_8-bytes20-bytes8-uint8-[`++replace_20_8++`]] +:extract_20_10: pass:normal[xref:#Packing-extract_20_10-bytes20-uint8-[`++extract_20_10++`]] +:replace_20_10: pass:normal[xref:#Packing-replace_20_10-bytes20-bytes10-uint8-[`++replace_20_10++`]] :extract_20_12: pass:normal[xref:#Packing-extract_20_12-bytes20-uint8-[`++extract_20_12++`]] :replace_20_12: pass:normal[xref:#Packing-replace_20_12-bytes20-bytes12-uint8-[`++replace_20_12++`]] :extract_20_16: pass:normal[xref:#Packing-extract_20_16-bytes20-uint8-[`++extract_20_16++`]] :replace_20_16: pass:normal[xref:#Packing-replace_20_16-bytes20-bytes16-uint8-[`++replace_20_16++`]] +:extract_22_1: pass:normal[xref:#Packing-extract_22_1-bytes22-uint8-[`++extract_22_1++`]] +:replace_22_1: pass:normal[xref:#Packing-replace_22_1-bytes22-bytes1-uint8-[`++replace_22_1++`]] +:extract_22_2: pass:normal[xref:#Packing-extract_22_2-bytes22-uint8-[`++extract_22_2++`]] +:replace_22_2: pass:normal[xref:#Packing-replace_22_2-bytes22-bytes2-uint8-[`++replace_22_2++`]] +:extract_22_4: pass:normal[xref:#Packing-extract_22_4-bytes22-uint8-[`++extract_22_4++`]] +:replace_22_4: pass:normal[xref:#Packing-replace_22_4-bytes22-bytes4-uint8-[`++replace_22_4++`]] +:extract_22_6: pass:normal[xref:#Packing-extract_22_6-bytes22-uint8-[`++extract_22_6++`]] +:replace_22_6: pass:normal[xref:#Packing-replace_22_6-bytes22-bytes6-uint8-[`++replace_22_6++`]] +:extract_22_8: pass:normal[xref:#Packing-extract_22_8-bytes22-uint8-[`++extract_22_8++`]] +:replace_22_8: pass:normal[xref:#Packing-replace_22_8-bytes22-bytes8-uint8-[`++replace_22_8++`]] +:extract_22_10: pass:normal[xref:#Packing-extract_22_10-bytes22-uint8-[`++extract_22_10++`]] +:replace_22_10: pass:normal[xref:#Packing-replace_22_10-bytes22-bytes10-uint8-[`++replace_22_10++`]] +:extract_22_12: pass:normal[xref:#Packing-extract_22_12-bytes22-uint8-[`++extract_22_12++`]] +:replace_22_12: pass:normal[xref:#Packing-replace_22_12-bytes22-bytes12-uint8-[`++replace_22_12++`]] +:extract_22_16: pass:normal[xref:#Packing-extract_22_16-bytes22-uint8-[`++extract_22_16++`]] +:replace_22_16: pass:normal[xref:#Packing-replace_22_16-bytes22-bytes16-uint8-[`++replace_22_16++`]] +:extract_22_20: pass:normal[xref:#Packing-extract_22_20-bytes22-uint8-[`++extract_22_20++`]] +:replace_22_20: pass:normal[xref:#Packing-replace_22_20-bytes22-bytes20-uint8-[`++replace_22_20++`]] :extract_24_1: pass:normal[xref:#Packing-extract_24_1-bytes24-uint8-[`++extract_24_1++`]] :replace_24_1: pass:normal[xref:#Packing-replace_24_1-bytes24-bytes1-uint8-[`++replace_24_1++`]] :extract_24_2: pass:normal[xref:#Packing-extract_24_2-bytes24-uint8-[`++extract_24_2++`]] @@ -6809,12 +7244,16 @@ This contract is only required for intermediate, library-like contracts. :replace_24_6: pass:normal[xref:#Packing-replace_24_6-bytes24-bytes6-uint8-[`++replace_24_6++`]] :extract_24_8: pass:normal[xref:#Packing-extract_24_8-bytes24-uint8-[`++extract_24_8++`]] :replace_24_8: pass:normal[xref:#Packing-replace_24_8-bytes24-bytes8-uint8-[`++replace_24_8++`]] +:extract_24_10: pass:normal[xref:#Packing-extract_24_10-bytes24-uint8-[`++extract_24_10++`]] +:replace_24_10: pass:normal[xref:#Packing-replace_24_10-bytes24-bytes10-uint8-[`++replace_24_10++`]] :extract_24_12: pass:normal[xref:#Packing-extract_24_12-bytes24-uint8-[`++extract_24_12++`]] :replace_24_12: pass:normal[xref:#Packing-replace_24_12-bytes24-bytes12-uint8-[`++replace_24_12++`]] :extract_24_16: pass:normal[xref:#Packing-extract_24_16-bytes24-uint8-[`++extract_24_16++`]] :replace_24_16: pass:normal[xref:#Packing-replace_24_16-bytes24-bytes16-uint8-[`++replace_24_16++`]] :extract_24_20: pass:normal[xref:#Packing-extract_24_20-bytes24-uint8-[`++extract_24_20++`]] :replace_24_20: pass:normal[xref:#Packing-replace_24_20-bytes24-bytes20-uint8-[`++replace_24_20++`]] +:extract_24_22: pass:normal[xref:#Packing-extract_24_22-bytes24-uint8-[`++extract_24_22++`]] +:replace_24_22: pass:normal[xref:#Packing-replace_24_22-bytes24-bytes22-uint8-[`++replace_24_22++`]] :extract_28_1: pass:normal[xref:#Packing-extract_28_1-bytes28-uint8-[`++extract_28_1++`]] :replace_28_1: pass:normal[xref:#Packing-replace_28_1-bytes28-bytes1-uint8-[`++replace_28_1++`]] :extract_28_2: pass:normal[xref:#Packing-extract_28_2-bytes28-uint8-[`++extract_28_2++`]] @@ -6825,12 +7264,16 @@ This contract is only required for intermediate, library-like contracts. :replace_28_6: pass:normal[xref:#Packing-replace_28_6-bytes28-bytes6-uint8-[`++replace_28_6++`]] :extract_28_8: pass:normal[xref:#Packing-extract_28_8-bytes28-uint8-[`++extract_28_8++`]] :replace_28_8: pass:normal[xref:#Packing-replace_28_8-bytes28-bytes8-uint8-[`++replace_28_8++`]] +:extract_28_10: pass:normal[xref:#Packing-extract_28_10-bytes28-uint8-[`++extract_28_10++`]] +:replace_28_10: pass:normal[xref:#Packing-replace_28_10-bytes28-bytes10-uint8-[`++replace_28_10++`]] :extract_28_12: pass:normal[xref:#Packing-extract_28_12-bytes28-uint8-[`++extract_28_12++`]] :replace_28_12: pass:normal[xref:#Packing-replace_28_12-bytes28-bytes12-uint8-[`++replace_28_12++`]] :extract_28_16: pass:normal[xref:#Packing-extract_28_16-bytes28-uint8-[`++extract_28_16++`]] :replace_28_16: pass:normal[xref:#Packing-replace_28_16-bytes28-bytes16-uint8-[`++replace_28_16++`]] :extract_28_20: pass:normal[xref:#Packing-extract_28_20-bytes28-uint8-[`++extract_28_20++`]] :replace_28_20: pass:normal[xref:#Packing-replace_28_20-bytes28-bytes20-uint8-[`++replace_28_20++`]] +:extract_28_22: pass:normal[xref:#Packing-extract_28_22-bytes28-uint8-[`++extract_28_22++`]] +:replace_28_22: pass:normal[xref:#Packing-replace_28_22-bytes28-bytes22-uint8-[`++replace_28_22++`]] :extract_28_24: pass:normal[xref:#Packing-extract_28_24-bytes28-uint8-[`++extract_28_24++`]] :replace_28_24: pass:normal[xref:#Packing-replace_28_24-bytes28-bytes24-uint8-[`++replace_28_24++`]] :extract_32_1: pass:normal[xref:#Packing-extract_32_1-bytes32-uint8-[`++extract_32_1++`]] @@ -6843,12 +7286,16 @@ This contract is only required for intermediate, library-like contracts. :replace_32_6: pass:normal[xref:#Packing-replace_32_6-bytes32-bytes6-uint8-[`++replace_32_6++`]] :extract_32_8: pass:normal[xref:#Packing-extract_32_8-bytes32-uint8-[`++extract_32_8++`]] :replace_32_8: pass:normal[xref:#Packing-replace_32_8-bytes32-bytes8-uint8-[`++replace_32_8++`]] +:extract_32_10: pass:normal[xref:#Packing-extract_32_10-bytes32-uint8-[`++extract_32_10++`]] +:replace_32_10: pass:normal[xref:#Packing-replace_32_10-bytes32-bytes10-uint8-[`++replace_32_10++`]] :extract_32_12: pass:normal[xref:#Packing-extract_32_12-bytes32-uint8-[`++extract_32_12++`]] :replace_32_12: pass:normal[xref:#Packing-replace_32_12-bytes32-bytes12-uint8-[`++replace_32_12++`]] :extract_32_16: pass:normal[xref:#Packing-extract_32_16-bytes32-uint8-[`++extract_32_16++`]] :replace_32_16: pass:normal[xref:#Packing-replace_32_16-bytes32-bytes16-uint8-[`++replace_32_16++`]] :extract_32_20: pass:normal[xref:#Packing-extract_32_20-bytes32-uint8-[`++extract_32_20++`]] :replace_32_20: pass:normal[xref:#Packing-replace_32_20-bytes32-bytes20-uint8-[`++replace_32_20++`]] +:extract_32_22: pass:normal[xref:#Packing-extract_32_22-bytes32-uint8-[`++extract_32_22++`]] +:replace_32_22: pass:normal[xref:#Packing-replace_32_22-bytes32-bytes22-uint8-[`++replace_32_22++`]] :extract_32_24: pass:normal[xref:#Packing-extract_32_24-bytes32-uint8-[`++extract_32_24++`]] :replace_32_24: pass:normal[xref:#Packing-replace_32_24-bytes32-bytes24-uint8-[`++replace_32_24++`]] :extract_32_28: pass:normal[xref:#Packing-extract_32_28-bytes32-uint8-[`++extract_32_28++`]] @@ -6856,7 +7303,7 @@ This contract is only required for intermediate, library-like contracts. [.contract] [[Packing]] -=== `++Packing++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/utils/Packing.sol[{github-icon},role=heading-link] +=== `++Packing++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/utils/Packing.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -6897,8 +7344,13 @@ _Available since v5.1._ * {xref-Packing-pack_2_2-bytes2-bytes2-}[`++pack_2_2(left, right)++`] * {xref-Packing-pack_2_4-bytes2-bytes4-}[`++pack_2_4(left, right)++`] * {xref-Packing-pack_2_6-bytes2-bytes6-}[`++pack_2_6(left, right)++`] +* {xref-Packing-pack_2_8-bytes2-bytes8-}[`++pack_2_8(left, right)++`] +* {xref-Packing-pack_2_10-bytes2-bytes10-}[`++pack_2_10(left, right)++`] +* {xref-Packing-pack_2_20-bytes2-bytes20-}[`++pack_2_20(left, right)++`] +* {xref-Packing-pack_2_22-bytes2-bytes22-}[`++pack_2_22(left, right)++`] * {xref-Packing-pack_4_2-bytes4-bytes2-}[`++pack_4_2(left, right)++`] * {xref-Packing-pack_4_4-bytes4-bytes4-}[`++pack_4_4(left, right)++`] +* {xref-Packing-pack_4_6-bytes4-bytes6-}[`++pack_4_6(left, right)++`] * {xref-Packing-pack_4_8-bytes4-bytes8-}[`++pack_4_8(left, right)++`] * {xref-Packing-pack_4_12-bytes4-bytes12-}[`++pack_4_12(left, right)++`] * {xref-Packing-pack_4_16-bytes4-bytes16-}[`++pack_4_16(left, right)++`] @@ -6906,25 +7358,41 @@ _Available since v5.1._ * {xref-Packing-pack_4_24-bytes4-bytes24-}[`++pack_4_24(left, right)++`] * {xref-Packing-pack_4_28-bytes4-bytes28-}[`++pack_4_28(left, right)++`] * {xref-Packing-pack_6_2-bytes6-bytes2-}[`++pack_6_2(left, right)++`] +* {xref-Packing-pack_6_4-bytes6-bytes4-}[`++pack_6_4(left, right)++`] * {xref-Packing-pack_6_6-bytes6-bytes6-}[`++pack_6_6(left, right)++`] +* {xref-Packing-pack_6_10-bytes6-bytes10-}[`++pack_6_10(left, right)++`] +* {xref-Packing-pack_6_16-bytes6-bytes16-}[`++pack_6_16(left, right)++`] +* {xref-Packing-pack_6_22-bytes6-bytes22-}[`++pack_6_22(left, right)++`] +* {xref-Packing-pack_8_2-bytes8-bytes2-}[`++pack_8_2(left, right)++`] * {xref-Packing-pack_8_4-bytes8-bytes4-}[`++pack_8_4(left, right)++`] * {xref-Packing-pack_8_8-bytes8-bytes8-}[`++pack_8_8(left, right)++`] * {xref-Packing-pack_8_12-bytes8-bytes12-}[`++pack_8_12(left, right)++`] * {xref-Packing-pack_8_16-bytes8-bytes16-}[`++pack_8_16(left, right)++`] * {xref-Packing-pack_8_20-bytes8-bytes20-}[`++pack_8_20(left, right)++`] * {xref-Packing-pack_8_24-bytes8-bytes24-}[`++pack_8_24(left, right)++`] +* {xref-Packing-pack_10_2-bytes10-bytes2-}[`++pack_10_2(left, right)++`] +* {xref-Packing-pack_10_6-bytes10-bytes6-}[`++pack_10_6(left, right)++`] +* {xref-Packing-pack_10_10-bytes10-bytes10-}[`++pack_10_10(left, right)++`] +* {xref-Packing-pack_10_12-bytes10-bytes12-}[`++pack_10_12(left, right)++`] +* {xref-Packing-pack_10_22-bytes10-bytes22-}[`++pack_10_22(left, right)++`] * {xref-Packing-pack_12_4-bytes12-bytes4-}[`++pack_12_4(left, right)++`] * {xref-Packing-pack_12_8-bytes12-bytes8-}[`++pack_12_8(left, right)++`] +* {xref-Packing-pack_12_10-bytes12-bytes10-}[`++pack_12_10(left, right)++`] * {xref-Packing-pack_12_12-bytes12-bytes12-}[`++pack_12_12(left, right)++`] * {xref-Packing-pack_12_16-bytes12-bytes16-}[`++pack_12_16(left, right)++`] * {xref-Packing-pack_12_20-bytes12-bytes20-}[`++pack_12_20(left, right)++`] * {xref-Packing-pack_16_4-bytes16-bytes4-}[`++pack_16_4(left, right)++`] +* {xref-Packing-pack_16_6-bytes16-bytes6-}[`++pack_16_6(left, right)++`] * {xref-Packing-pack_16_8-bytes16-bytes8-}[`++pack_16_8(left, right)++`] * {xref-Packing-pack_16_12-bytes16-bytes12-}[`++pack_16_12(left, right)++`] * {xref-Packing-pack_16_16-bytes16-bytes16-}[`++pack_16_16(left, right)++`] +* {xref-Packing-pack_20_2-bytes20-bytes2-}[`++pack_20_2(left, right)++`] * {xref-Packing-pack_20_4-bytes20-bytes4-}[`++pack_20_4(left, right)++`] * {xref-Packing-pack_20_8-bytes20-bytes8-}[`++pack_20_8(left, right)++`] * {xref-Packing-pack_20_12-bytes20-bytes12-}[`++pack_20_12(left, right)++`] +* {xref-Packing-pack_22_2-bytes22-bytes2-}[`++pack_22_2(left, right)++`] +* {xref-Packing-pack_22_6-bytes22-bytes6-}[`++pack_22_6(left, right)++`] +* {xref-Packing-pack_22_10-bytes22-bytes10-}[`++pack_22_10(left, right)++`] * {xref-Packing-pack_24_4-bytes24-bytes4-}[`++pack_24_4(left, right)++`] * {xref-Packing-pack_24_8-bytes24-bytes8-}[`++pack_24_8(left, right)++`] * {xref-Packing-pack_28_4-bytes28-bytes4-}[`++pack_28_4(left, right)++`] @@ -6948,6 +7416,16 @@ _Available since v5.1._ * {xref-Packing-replace_8_4-bytes8-bytes4-uint8-}[`++replace_8_4(self, value, offset)++`] * {xref-Packing-extract_8_6-bytes8-uint8-}[`++extract_8_6(self, offset)++`] * {xref-Packing-replace_8_6-bytes8-bytes6-uint8-}[`++replace_8_6(self, value, offset)++`] +* {xref-Packing-extract_10_1-bytes10-uint8-}[`++extract_10_1(self, offset)++`] +* {xref-Packing-replace_10_1-bytes10-bytes1-uint8-}[`++replace_10_1(self, value, offset)++`] +* {xref-Packing-extract_10_2-bytes10-uint8-}[`++extract_10_2(self, offset)++`] +* {xref-Packing-replace_10_2-bytes10-bytes2-uint8-}[`++replace_10_2(self, value, offset)++`] +* {xref-Packing-extract_10_4-bytes10-uint8-}[`++extract_10_4(self, offset)++`] +* {xref-Packing-replace_10_4-bytes10-bytes4-uint8-}[`++replace_10_4(self, value, offset)++`] +* {xref-Packing-extract_10_6-bytes10-uint8-}[`++extract_10_6(self, offset)++`] +* {xref-Packing-replace_10_6-bytes10-bytes6-uint8-}[`++replace_10_6(self, value, offset)++`] +* {xref-Packing-extract_10_8-bytes10-uint8-}[`++extract_10_8(self, offset)++`] +* {xref-Packing-replace_10_8-bytes10-bytes8-uint8-}[`++replace_10_8(self, value, offset)++`] * {xref-Packing-extract_12_1-bytes12-uint8-}[`++extract_12_1(self, offset)++`] * {xref-Packing-replace_12_1-bytes12-bytes1-uint8-}[`++replace_12_1(self, value, offset)++`] * {xref-Packing-extract_12_2-bytes12-uint8-}[`++extract_12_2(self, offset)++`] @@ -6958,6 +7436,8 @@ _Available since v5.1._ * {xref-Packing-replace_12_6-bytes12-bytes6-uint8-}[`++replace_12_6(self, value, offset)++`] * {xref-Packing-extract_12_8-bytes12-uint8-}[`++extract_12_8(self, offset)++`] * {xref-Packing-replace_12_8-bytes12-bytes8-uint8-}[`++replace_12_8(self, value, offset)++`] +* {xref-Packing-extract_12_10-bytes12-uint8-}[`++extract_12_10(self, offset)++`] +* {xref-Packing-replace_12_10-bytes12-bytes10-uint8-}[`++replace_12_10(self, value, offset)++`] * {xref-Packing-extract_16_1-bytes16-uint8-}[`++extract_16_1(self, offset)++`] * {xref-Packing-replace_16_1-bytes16-bytes1-uint8-}[`++replace_16_1(self, value, offset)++`] * {xref-Packing-extract_16_2-bytes16-uint8-}[`++extract_16_2(self, offset)++`] @@ -6968,6 +7448,8 @@ _Available since v5.1._ * {xref-Packing-replace_16_6-bytes16-bytes6-uint8-}[`++replace_16_6(self, value, offset)++`] * {xref-Packing-extract_16_8-bytes16-uint8-}[`++extract_16_8(self, offset)++`] * {xref-Packing-replace_16_8-bytes16-bytes8-uint8-}[`++replace_16_8(self, value, offset)++`] +* {xref-Packing-extract_16_10-bytes16-uint8-}[`++extract_16_10(self, offset)++`] +* {xref-Packing-replace_16_10-bytes16-bytes10-uint8-}[`++replace_16_10(self, value, offset)++`] * {xref-Packing-extract_16_12-bytes16-uint8-}[`++extract_16_12(self, offset)++`] * {xref-Packing-replace_16_12-bytes16-bytes12-uint8-}[`++replace_16_12(self, value, offset)++`] * {xref-Packing-extract_20_1-bytes20-uint8-}[`++extract_20_1(self, offset)++`] @@ -6980,10 +7462,30 @@ _Available since v5.1._ * {xref-Packing-replace_20_6-bytes20-bytes6-uint8-}[`++replace_20_6(self, value, offset)++`] * {xref-Packing-extract_20_8-bytes20-uint8-}[`++extract_20_8(self, offset)++`] * {xref-Packing-replace_20_8-bytes20-bytes8-uint8-}[`++replace_20_8(self, value, offset)++`] +* {xref-Packing-extract_20_10-bytes20-uint8-}[`++extract_20_10(self, offset)++`] +* {xref-Packing-replace_20_10-bytes20-bytes10-uint8-}[`++replace_20_10(self, value, offset)++`] * {xref-Packing-extract_20_12-bytes20-uint8-}[`++extract_20_12(self, offset)++`] * {xref-Packing-replace_20_12-bytes20-bytes12-uint8-}[`++replace_20_12(self, value, offset)++`] * {xref-Packing-extract_20_16-bytes20-uint8-}[`++extract_20_16(self, offset)++`] * {xref-Packing-replace_20_16-bytes20-bytes16-uint8-}[`++replace_20_16(self, value, offset)++`] +* {xref-Packing-extract_22_1-bytes22-uint8-}[`++extract_22_1(self, offset)++`] +* {xref-Packing-replace_22_1-bytes22-bytes1-uint8-}[`++replace_22_1(self, value, offset)++`] +* {xref-Packing-extract_22_2-bytes22-uint8-}[`++extract_22_2(self, offset)++`] +* {xref-Packing-replace_22_2-bytes22-bytes2-uint8-}[`++replace_22_2(self, value, offset)++`] +* {xref-Packing-extract_22_4-bytes22-uint8-}[`++extract_22_4(self, offset)++`] +* {xref-Packing-replace_22_4-bytes22-bytes4-uint8-}[`++replace_22_4(self, value, offset)++`] +* {xref-Packing-extract_22_6-bytes22-uint8-}[`++extract_22_6(self, offset)++`] +* {xref-Packing-replace_22_6-bytes22-bytes6-uint8-}[`++replace_22_6(self, value, offset)++`] +* {xref-Packing-extract_22_8-bytes22-uint8-}[`++extract_22_8(self, offset)++`] +* {xref-Packing-replace_22_8-bytes22-bytes8-uint8-}[`++replace_22_8(self, value, offset)++`] +* {xref-Packing-extract_22_10-bytes22-uint8-}[`++extract_22_10(self, offset)++`] +* {xref-Packing-replace_22_10-bytes22-bytes10-uint8-}[`++replace_22_10(self, value, offset)++`] +* {xref-Packing-extract_22_12-bytes22-uint8-}[`++extract_22_12(self, offset)++`] +* {xref-Packing-replace_22_12-bytes22-bytes12-uint8-}[`++replace_22_12(self, value, offset)++`] +* {xref-Packing-extract_22_16-bytes22-uint8-}[`++extract_22_16(self, offset)++`] +* {xref-Packing-replace_22_16-bytes22-bytes16-uint8-}[`++replace_22_16(self, value, offset)++`] +* {xref-Packing-extract_22_20-bytes22-uint8-}[`++extract_22_20(self, offset)++`] +* {xref-Packing-replace_22_20-bytes22-bytes20-uint8-}[`++replace_22_20(self, value, offset)++`] * {xref-Packing-extract_24_1-bytes24-uint8-}[`++extract_24_1(self, offset)++`] * {xref-Packing-replace_24_1-bytes24-bytes1-uint8-}[`++replace_24_1(self, value, offset)++`] * {xref-Packing-extract_24_2-bytes24-uint8-}[`++extract_24_2(self, offset)++`] @@ -6994,12 +7496,16 @@ _Available since v5.1._ * {xref-Packing-replace_24_6-bytes24-bytes6-uint8-}[`++replace_24_6(self, value, offset)++`] * {xref-Packing-extract_24_8-bytes24-uint8-}[`++extract_24_8(self, offset)++`] * {xref-Packing-replace_24_8-bytes24-bytes8-uint8-}[`++replace_24_8(self, value, offset)++`] +* {xref-Packing-extract_24_10-bytes24-uint8-}[`++extract_24_10(self, offset)++`] +* {xref-Packing-replace_24_10-bytes24-bytes10-uint8-}[`++replace_24_10(self, value, offset)++`] * {xref-Packing-extract_24_12-bytes24-uint8-}[`++extract_24_12(self, offset)++`] * {xref-Packing-replace_24_12-bytes24-bytes12-uint8-}[`++replace_24_12(self, value, offset)++`] * {xref-Packing-extract_24_16-bytes24-uint8-}[`++extract_24_16(self, offset)++`] * {xref-Packing-replace_24_16-bytes24-bytes16-uint8-}[`++replace_24_16(self, value, offset)++`] * {xref-Packing-extract_24_20-bytes24-uint8-}[`++extract_24_20(self, offset)++`] * {xref-Packing-replace_24_20-bytes24-bytes20-uint8-}[`++replace_24_20(self, value, offset)++`] +* {xref-Packing-extract_24_22-bytes24-uint8-}[`++extract_24_22(self, offset)++`] +* {xref-Packing-replace_24_22-bytes24-bytes22-uint8-}[`++replace_24_22(self, value, offset)++`] * {xref-Packing-extract_28_1-bytes28-uint8-}[`++extract_28_1(self, offset)++`] * {xref-Packing-replace_28_1-bytes28-bytes1-uint8-}[`++replace_28_1(self, value, offset)++`] * {xref-Packing-extract_28_2-bytes28-uint8-}[`++extract_28_2(self, offset)++`] @@ -7010,12 +7516,16 @@ _Available since v5.1._ * {xref-Packing-replace_28_6-bytes28-bytes6-uint8-}[`++replace_28_6(self, value, offset)++`] * {xref-Packing-extract_28_8-bytes28-uint8-}[`++extract_28_8(self, offset)++`] * {xref-Packing-replace_28_8-bytes28-bytes8-uint8-}[`++replace_28_8(self, value, offset)++`] +* {xref-Packing-extract_28_10-bytes28-uint8-}[`++extract_28_10(self, offset)++`] +* {xref-Packing-replace_28_10-bytes28-bytes10-uint8-}[`++replace_28_10(self, value, offset)++`] * {xref-Packing-extract_28_12-bytes28-uint8-}[`++extract_28_12(self, offset)++`] * {xref-Packing-replace_28_12-bytes28-bytes12-uint8-}[`++replace_28_12(self, value, offset)++`] * {xref-Packing-extract_28_16-bytes28-uint8-}[`++extract_28_16(self, offset)++`] * {xref-Packing-replace_28_16-bytes28-bytes16-uint8-}[`++replace_28_16(self, value, offset)++`] * {xref-Packing-extract_28_20-bytes28-uint8-}[`++extract_28_20(self, offset)++`] * {xref-Packing-replace_28_20-bytes28-bytes20-uint8-}[`++replace_28_20(self, value, offset)++`] +* {xref-Packing-extract_28_22-bytes28-uint8-}[`++extract_28_22(self, offset)++`] +* {xref-Packing-replace_28_22-bytes28-bytes22-uint8-}[`++replace_28_22(self, value, offset)++`] * {xref-Packing-extract_28_24-bytes28-uint8-}[`++extract_28_24(self, offset)++`] * {xref-Packing-replace_28_24-bytes28-bytes24-uint8-}[`++replace_28_24(self, value, offset)++`] * {xref-Packing-extract_32_1-bytes32-uint8-}[`++extract_32_1(self, offset)++`] @@ -7028,12 +7538,16 @@ _Available since v5.1._ * {xref-Packing-replace_32_6-bytes32-bytes6-uint8-}[`++replace_32_6(self, value, offset)++`] * {xref-Packing-extract_32_8-bytes32-uint8-}[`++extract_32_8(self, offset)++`] * {xref-Packing-replace_32_8-bytes32-bytes8-uint8-}[`++replace_32_8(self, value, offset)++`] +* {xref-Packing-extract_32_10-bytes32-uint8-}[`++extract_32_10(self, offset)++`] +* {xref-Packing-replace_32_10-bytes32-bytes10-uint8-}[`++replace_32_10(self, value, offset)++`] * {xref-Packing-extract_32_12-bytes32-uint8-}[`++extract_32_12(self, offset)++`] * {xref-Packing-replace_32_12-bytes32-bytes12-uint8-}[`++replace_32_12(self, value, offset)++`] * {xref-Packing-extract_32_16-bytes32-uint8-}[`++extract_32_16(self, offset)++`] * {xref-Packing-replace_32_16-bytes32-bytes16-uint8-}[`++replace_32_16(self, value, offset)++`] * {xref-Packing-extract_32_20-bytes32-uint8-}[`++extract_32_20(self, offset)++`] * {xref-Packing-replace_32_20-bytes32-bytes20-uint8-}[`++replace_32_20(self, value, offset)++`] +* {xref-Packing-extract_32_22-bytes32-uint8-}[`++extract_32_22(self, offset)++`] +* {xref-Packing-replace_32_22-bytes32-bytes22-uint8-}[`++replace_32_22(self, value, offset)++`] * {xref-Packing-extract_32_24-bytes32-uint8-}[`++extract_32_24(self, offset)++`] * {xref-Packing-replace_32_24-bytes32-bytes24-uint8-}[`++replace_32_24(self, value, offset)++`] * {xref-Packing-extract_32_28-bytes32-uint8-}[`++extract_32_28(self, offset)++`] @@ -7064,6 +7578,22 @@ _Available since v5.1._ [[Packing-pack_2_6-bytes2-bytes6-]] ==== `[.contract-item-name]#++pack_2_6++#++(bytes2 left, bytes6 right) → bytes8 result++` [.item-kind]#internal# +[.contract-item] +[[Packing-pack_2_8-bytes2-bytes8-]] +==== `[.contract-item-name]#++pack_2_8++#++(bytes2 left, bytes8 right) → bytes10 result++` [.item-kind]#internal# + +[.contract-item] +[[Packing-pack_2_10-bytes2-bytes10-]] +==== `[.contract-item-name]#++pack_2_10++#++(bytes2 left, bytes10 right) → bytes12 result++` [.item-kind]#internal# + +[.contract-item] +[[Packing-pack_2_20-bytes2-bytes20-]] +==== `[.contract-item-name]#++pack_2_20++#++(bytes2 left, bytes20 right) → bytes22 result++` [.item-kind]#internal# + +[.contract-item] +[[Packing-pack_2_22-bytes2-bytes22-]] +==== `[.contract-item-name]#++pack_2_22++#++(bytes2 left, bytes22 right) → bytes24 result++` [.item-kind]#internal# + [.contract-item] [[Packing-pack_4_2-bytes4-bytes2-]] ==== `[.contract-item-name]#++pack_4_2++#++(bytes4 left, bytes2 right) → bytes6 result++` [.item-kind]#internal# @@ -7072,6 +7602,10 @@ _Available since v5.1._ [[Packing-pack_4_4-bytes4-bytes4-]] ==== `[.contract-item-name]#++pack_4_4++#++(bytes4 left, bytes4 right) → bytes8 result++` [.item-kind]#internal# +[.contract-item] +[[Packing-pack_4_6-bytes4-bytes6-]] +==== `[.contract-item-name]#++pack_4_6++#++(bytes4 left, bytes6 right) → bytes10 result++` [.item-kind]#internal# + [.contract-item] [[Packing-pack_4_8-bytes4-bytes8-]] ==== `[.contract-item-name]#++pack_4_8++#++(bytes4 left, bytes8 right) → bytes12 result++` [.item-kind]#internal# @@ -7100,10 +7634,30 @@ _Available since v5.1._ [[Packing-pack_6_2-bytes6-bytes2-]] ==== `[.contract-item-name]#++pack_6_2++#++(bytes6 left, bytes2 right) → bytes8 result++` [.item-kind]#internal# +[.contract-item] +[[Packing-pack_6_4-bytes6-bytes4-]] +==== `[.contract-item-name]#++pack_6_4++#++(bytes6 left, bytes4 right) → bytes10 result++` [.item-kind]#internal# + [.contract-item] [[Packing-pack_6_6-bytes6-bytes6-]] ==== `[.contract-item-name]#++pack_6_6++#++(bytes6 left, bytes6 right) → bytes12 result++` [.item-kind]#internal# +[.contract-item] +[[Packing-pack_6_10-bytes6-bytes10-]] +==== `[.contract-item-name]#++pack_6_10++#++(bytes6 left, bytes10 right) → bytes16 result++` [.item-kind]#internal# + +[.contract-item] +[[Packing-pack_6_16-bytes6-bytes16-]] +==== `[.contract-item-name]#++pack_6_16++#++(bytes6 left, bytes16 right) → bytes22 result++` [.item-kind]#internal# + +[.contract-item] +[[Packing-pack_6_22-bytes6-bytes22-]] +==== `[.contract-item-name]#++pack_6_22++#++(bytes6 left, bytes22 right) → bytes28 result++` [.item-kind]#internal# + +[.contract-item] +[[Packing-pack_8_2-bytes8-bytes2-]] +==== `[.contract-item-name]#++pack_8_2++#++(bytes8 left, bytes2 right) → bytes10 result++` [.item-kind]#internal# + [.contract-item] [[Packing-pack_8_4-bytes8-bytes4-]] ==== `[.contract-item-name]#++pack_8_4++#++(bytes8 left, bytes4 right) → bytes12 result++` [.item-kind]#internal# @@ -7128,6 +7682,26 @@ _Available since v5.1._ [[Packing-pack_8_24-bytes8-bytes24-]] ==== `[.contract-item-name]#++pack_8_24++#++(bytes8 left, bytes24 right) → bytes32 result++` [.item-kind]#internal# +[.contract-item] +[[Packing-pack_10_2-bytes10-bytes2-]] +==== `[.contract-item-name]#++pack_10_2++#++(bytes10 left, bytes2 right) → bytes12 result++` [.item-kind]#internal# + +[.contract-item] +[[Packing-pack_10_6-bytes10-bytes6-]] +==== `[.contract-item-name]#++pack_10_6++#++(bytes10 left, bytes6 right) → bytes16 result++` [.item-kind]#internal# + +[.contract-item] +[[Packing-pack_10_10-bytes10-bytes10-]] +==== `[.contract-item-name]#++pack_10_10++#++(bytes10 left, bytes10 right) → bytes20 result++` [.item-kind]#internal# + +[.contract-item] +[[Packing-pack_10_12-bytes10-bytes12-]] +==== `[.contract-item-name]#++pack_10_12++#++(bytes10 left, bytes12 right) → bytes22 result++` [.item-kind]#internal# + +[.contract-item] +[[Packing-pack_10_22-bytes10-bytes22-]] +==== `[.contract-item-name]#++pack_10_22++#++(bytes10 left, bytes22 right) → bytes32 result++` [.item-kind]#internal# + [.contract-item] [[Packing-pack_12_4-bytes12-bytes4-]] ==== `[.contract-item-name]#++pack_12_4++#++(bytes12 left, bytes4 right) → bytes16 result++` [.item-kind]#internal# @@ -7136,6 +7710,10 @@ _Available since v5.1._ [[Packing-pack_12_8-bytes12-bytes8-]] ==== `[.contract-item-name]#++pack_12_8++#++(bytes12 left, bytes8 right) → bytes20 result++` [.item-kind]#internal# +[.contract-item] +[[Packing-pack_12_10-bytes12-bytes10-]] +==== `[.contract-item-name]#++pack_12_10++#++(bytes12 left, bytes10 right) → bytes22 result++` [.item-kind]#internal# + [.contract-item] [[Packing-pack_12_12-bytes12-bytes12-]] ==== `[.contract-item-name]#++pack_12_12++#++(bytes12 left, bytes12 right) → bytes24 result++` [.item-kind]#internal# @@ -7152,6 +7730,10 @@ _Available since v5.1._ [[Packing-pack_16_4-bytes16-bytes4-]] ==== `[.contract-item-name]#++pack_16_4++#++(bytes16 left, bytes4 right) → bytes20 result++` [.item-kind]#internal# +[.contract-item] +[[Packing-pack_16_6-bytes16-bytes6-]] +==== `[.contract-item-name]#++pack_16_6++#++(bytes16 left, bytes6 right) → bytes22 result++` [.item-kind]#internal# + [.contract-item] [[Packing-pack_16_8-bytes16-bytes8-]] ==== `[.contract-item-name]#++pack_16_8++#++(bytes16 left, bytes8 right) → bytes24 result++` [.item-kind]#internal# @@ -7164,6 +7746,10 @@ _Available since v5.1._ [[Packing-pack_16_16-bytes16-bytes16-]] ==== `[.contract-item-name]#++pack_16_16++#++(bytes16 left, bytes16 right) → bytes32 result++` [.item-kind]#internal# +[.contract-item] +[[Packing-pack_20_2-bytes20-bytes2-]] +==== `[.contract-item-name]#++pack_20_2++#++(bytes20 left, bytes2 right) → bytes22 result++` [.item-kind]#internal# + [.contract-item] [[Packing-pack_20_4-bytes20-bytes4-]] ==== `[.contract-item-name]#++pack_20_4++#++(bytes20 left, bytes4 right) → bytes24 result++` [.item-kind]#internal# @@ -7176,6 +7762,18 @@ _Available since v5.1._ [[Packing-pack_20_12-bytes20-bytes12-]] ==== `[.contract-item-name]#++pack_20_12++#++(bytes20 left, bytes12 right) → bytes32 result++` [.item-kind]#internal# +[.contract-item] +[[Packing-pack_22_2-bytes22-bytes2-]] +==== `[.contract-item-name]#++pack_22_2++#++(bytes22 left, bytes2 right) → bytes24 result++` [.item-kind]#internal# + +[.contract-item] +[[Packing-pack_22_6-bytes22-bytes6-]] +==== `[.contract-item-name]#++pack_22_6++#++(bytes22 left, bytes6 right) → bytes28 result++` [.item-kind]#internal# + +[.contract-item] +[[Packing-pack_22_10-bytes22-bytes10-]] +==== `[.contract-item-name]#++pack_22_10++#++(bytes22 left, bytes10 right) → bytes32 result++` [.item-kind]#internal# + [.contract-item] [[Packing-pack_24_4-bytes24-bytes4-]] ==== `[.contract-item-name]#++pack_24_4++#++(bytes24 left, bytes4 right) → bytes28 result++` [.item-kind]#internal# @@ -7268,6 +7866,46 @@ _Available since v5.1._ [[Packing-replace_8_6-bytes8-bytes6-uint8-]] ==== `[.contract-item-name]#++replace_8_6++#++(bytes8 self, bytes6 value, uint8 offset) → bytes8 result++` [.item-kind]#internal# +[.contract-item] +[[Packing-extract_10_1-bytes10-uint8-]] +==== `[.contract-item-name]#++extract_10_1++#++(bytes10 self, uint8 offset) → bytes1 result++` [.item-kind]#internal# + +[.contract-item] +[[Packing-replace_10_1-bytes10-bytes1-uint8-]] +==== `[.contract-item-name]#++replace_10_1++#++(bytes10 self, bytes1 value, uint8 offset) → bytes10 result++` [.item-kind]#internal# + +[.contract-item] +[[Packing-extract_10_2-bytes10-uint8-]] +==== `[.contract-item-name]#++extract_10_2++#++(bytes10 self, uint8 offset) → bytes2 result++` [.item-kind]#internal# + +[.contract-item] +[[Packing-replace_10_2-bytes10-bytes2-uint8-]] +==== `[.contract-item-name]#++replace_10_2++#++(bytes10 self, bytes2 value, uint8 offset) → bytes10 result++` [.item-kind]#internal# + +[.contract-item] +[[Packing-extract_10_4-bytes10-uint8-]] +==== `[.contract-item-name]#++extract_10_4++#++(bytes10 self, uint8 offset) → bytes4 result++` [.item-kind]#internal# + +[.contract-item] +[[Packing-replace_10_4-bytes10-bytes4-uint8-]] +==== `[.contract-item-name]#++replace_10_4++#++(bytes10 self, bytes4 value, uint8 offset) → bytes10 result++` [.item-kind]#internal# + +[.contract-item] +[[Packing-extract_10_6-bytes10-uint8-]] +==== `[.contract-item-name]#++extract_10_6++#++(bytes10 self, uint8 offset) → bytes6 result++` [.item-kind]#internal# + +[.contract-item] +[[Packing-replace_10_6-bytes10-bytes6-uint8-]] +==== `[.contract-item-name]#++replace_10_6++#++(bytes10 self, bytes6 value, uint8 offset) → bytes10 result++` [.item-kind]#internal# + +[.contract-item] +[[Packing-extract_10_8-bytes10-uint8-]] +==== `[.contract-item-name]#++extract_10_8++#++(bytes10 self, uint8 offset) → bytes8 result++` [.item-kind]#internal# + +[.contract-item] +[[Packing-replace_10_8-bytes10-bytes8-uint8-]] +==== `[.contract-item-name]#++replace_10_8++#++(bytes10 self, bytes8 value, uint8 offset) → bytes10 result++` [.item-kind]#internal# + [.contract-item] [[Packing-extract_12_1-bytes12-uint8-]] ==== `[.contract-item-name]#++extract_12_1++#++(bytes12 self, uint8 offset) → bytes1 result++` [.item-kind]#internal# @@ -7308,6 +7946,14 @@ _Available since v5.1._ [[Packing-replace_12_8-bytes12-bytes8-uint8-]] ==== `[.contract-item-name]#++replace_12_8++#++(bytes12 self, bytes8 value, uint8 offset) → bytes12 result++` [.item-kind]#internal# +[.contract-item] +[[Packing-extract_12_10-bytes12-uint8-]] +==== `[.contract-item-name]#++extract_12_10++#++(bytes12 self, uint8 offset) → bytes10 result++` [.item-kind]#internal# + +[.contract-item] +[[Packing-replace_12_10-bytes12-bytes10-uint8-]] +==== `[.contract-item-name]#++replace_12_10++#++(bytes12 self, bytes10 value, uint8 offset) → bytes12 result++` [.item-kind]#internal# + [.contract-item] [[Packing-extract_16_1-bytes16-uint8-]] ==== `[.contract-item-name]#++extract_16_1++#++(bytes16 self, uint8 offset) → bytes1 result++` [.item-kind]#internal# @@ -7348,6 +7994,14 @@ _Available since v5.1._ [[Packing-replace_16_8-bytes16-bytes8-uint8-]] ==== `[.contract-item-name]#++replace_16_8++#++(bytes16 self, bytes8 value, uint8 offset) → bytes16 result++` [.item-kind]#internal# +[.contract-item] +[[Packing-extract_16_10-bytes16-uint8-]] +==== `[.contract-item-name]#++extract_16_10++#++(bytes16 self, uint8 offset) → bytes10 result++` [.item-kind]#internal# + +[.contract-item] +[[Packing-replace_16_10-bytes16-bytes10-uint8-]] +==== `[.contract-item-name]#++replace_16_10++#++(bytes16 self, bytes10 value, uint8 offset) → bytes16 result++` [.item-kind]#internal# + [.contract-item] [[Packing-extract_16_12-bytes16-uint8-]] ==== `[.contract-item-name]#++extract_16_12++#++(bytes16 self, uint8 offset) → bytes12 result++` [.item-kind]#internal# @@ -7396,6 +8050,14 @@ _Available since v5.1._ [[Packing-replace_20_8-bytes20-bytes8-uint8-]] ==== `[.contract-item-name]#++replace_20_8++#++(bytes20 self, bytes8 value, uint8 offset) → bytes20 result++` [.item-kind]#internal# +[.contract-item] +[[Packing-extract_20_10-bytes20-uint8-]] +==== `[.contract-item-name]#++extract_20_10++#++(bytes20 self, uint8 offset) → bytes10 result++` [.item-kind]#internal# + +[.contract-item] +[[Packing-replace_20_10-bytes20-bytes10-uint8-]] +==== `[.contract-item-name]#++replace_20_10++#++(bytes20 self, bytes10 value, uint8 offset) → bytes20 result++` [.item-kind]#internal# + [.contract-item] [[Packing-extract_20_12-bytes20-uint8-]] ==== `[.contract-item-name]#++extract_20_12++#++(bytes20 self, uint8 offset) → bytes12 result++` [.item-kind]#internal# @@ -7412,6 +8074,78 @@ _Available since v5.1._ [[Packing-replace_20_16-bytes20-bytes16-uint8-]] ==== `[.contract-item-name]#++replace_20_16++#++(bytes20 self, bytes16 value, uint8 offset) → bytes20 result++` [.item-kind]#internal# +[.contract-item] +[[Packing-extract_22_1-bytes22-uint8-]] +==== `[.contract-item-name]#++extract_22_1++#++(bytes22 self, uint8 offset) → bytes1 result++` [.item-kind]#internal# + +[.contract-item] +[[Packing-replace_22_1-bytes22-bytes1-uint8-]] +==== `[.contract-item-name]#++replace_22_1++#++(bytes22 self, bytes1 value, uint8 offset) → bytes22 result++` [.item-kind]#internal# + +[.contract-item] +[[Packing-extract_22_2-bytes22-uint8-]] +==== `[.contract-item-name]#++extract_22_2++#++(bytes22 self, uint8 offset) → bytes2 result++` [.item-kind]#internal# + +[.contract-item] +[[Packing-replace_22_2-bytes22-bytes2-uint8-]] +==== `[.contract-item-name]#++replace_22_2++#++(bytes22 self, bytes2 value, uint8 offset) → bytes22 result++` [.item-kind]#internal# + +[.contract-item] +[[Packing-extract_22_4-bytes22-uint8-]] +==== `[.contract-item-name]#++extract_22_4++#++(bytes22 self, uint8 offset) → bytes4 result++` [.item-kind]#internal# + +[.contract-item] +[[Packing-replace_22_4-bytes22-bytes4-uint8-]] +==== `[.contract-item-name]#++replace_22_4++#++(bytes22 self, bytes4 value, uint8 offset) → bytes22 result++` [.item-kind]#internal# + +[.contract-item] +[[Packing-extract_22_6-bytes22-uint8-]] +==== `[.contract-item-name]#++extract_22_6++#++(bytes22 self, uint8 offset) → bytes6 result++` [.item-kind]#internal# + +[.contract-item] +[[Packing-replace_22_6-bytes22-bytes6-uint8-]] +==== `[.contract-item-name]#++replace_22_6++#++(bytes22 self, bytes6 value, uint8 offset) → bytes22 result++` [.item-kind]#internal# + +[.contract-item] +[[Packing-extract_22_8-bytes22-uint8-]] +==== `[.contract-item-name]#++extract_22_8++#++(bytes22 self, uint8 offset) → bytes8 result++` [.item-kind]#internal# + +[.contract-item] +[[Packing-replace_22_8-bytes22-bytes8-uint8-]] +==== `[.contract-item-name]#++replace_22_8++#++(bytes22 self, bytes8 value, uint8 offset) → bytes22 result++` [.item-kind]#internal# + +[.contract-item] +[[Packing-extract_22_10-bytes22-uint8-]] +==== `[.contract-item-name]#++extract_22_10++#++(bytes22 self, uint8 offset) → bytes10 result++` [.item-kind]#internal# + +[.contract-item] +[[Packing-replace_22_10-bytes22-bytes10-uint8-]] +==== `[.contract-item-name]#++replace_22_10++#++(bytes22 self, bytes10 value, uint8 offset) → bytes22 result++` [.item-kind]#internal# + +[.contract-item] +[[Packing-extract_22_12-bytes22-uint8-]] +==== `[.contract-item-name]#++extract_22_12++#++(bytes22 self, uint8 offset) → bytes12 result++` [.item-kind]#internal# + +[.contract-item] +[[Packing-replace_22_12-bytes22-bytes12-uint8-]] +==== `[.contract-item-name]#++replace_22_12++#++(bytes22 self, bytes12 value, uint8 offset) → bytes22 result++` [.item-kind]#internal# + +[.contract-item] +[[Packing-extract_22_16-bytes22-uint8-]] +==== `[.contract-item-name]#++extract_22_16++#++(bytes22 self, uint8 offset) → bytes16 result++` [.item-kind]#internal# + +[.contract-item] +[[Packing-replace_22_16-bytes22-bytes16-uint8-]] +==== `[.contract-item-name]#++replace_22_16++#++(bytes22 self, bytes16 value, uint8 offset) → bytes22 result++` [.item-kind]#internal# + +[.contract-item] +[[Packing-extract_22_20-bytes22-uint8-]] +==== `[.contract-item-name]#++extract_22_20++#++(bytes22 self, uint8 offset) → bytes20 result++` [.item-kind]#internal# + +[.contract-item] +[[Packing-replace_22_20-bytes22-bytes20-uint8-]] +==== `[.contract-item-name]#++replace_22_20++#++(bytes22 self, bytes20 value, uint8 offset) → bytes22 result++` [.item-kind]#internal# + [.contract-item] [[Packing-extract_24_1-bytes24-uint8-]] ==== `[.contract-item-name]#++extract_24_1++#++(bytes24 self, uint8 offset) → bytes1 result++` [.item-kind]#internal# @@ -7452,6 +8186,14 @@ _Available since v5.1._ [[Packing-replace_24_8-bytes24-bytes8-uint8-]] ==== `[.contract-item-name]#++replace_24_8++#++(bytes24 self, bytes8 value, uint8 offset) → bytes24 result++` [.item-kind]#internal# +[.contract-item] +[[Packing-extract_24_10-bytes24-uint8-]] +==== `[.contract-item-name]#++extract_24_10++#++(bytes24 self, uint8 offset) → bytes10 result++` [.item-kind]#internal# + +[.contract-item] +[[Packing-replace_24_10-bytes24-bytes10-uint8-]] +==== `[.contract-item-name]#++replace_24_10++#++(bytes24 self, bytes10 value, uint8 offset) → bytes24 result++` [.item-kind]#internal# + [.contract-item] [[Packing-extract_24_12-bytes24-uint8-]] ==== `[.contract-item-name]#++extract_24_12++#++(bytes24 self, uint8 offset) → bytes12 result++` [.item-kind]#internal# @@ -7476,6 +8218,14 @@ _Available since v5.1._ [[Packing-replace_24_20-bytes24-bytes20-uint8-]] ==== `[.contract-item-name]#++replace_24_20++#++(bytes24 self, bytes20 value, uint8 offset) → bytes24 result++` [.item-kind]#internal# +[.contract-item] +[[Packing-extract_24_22-bytes24-uint8-]] +==== `[.contract-item-name]#++extract_24_22++#++(bytes24 self, uint8 offset) → bytes22 result++` [.item-kind]#internal# + +[.contract-item] +[[Packing-replace_24_22-bytes24-bytes22-uint8-]] +==== `[.contract-item-name]#++replace_24_22++#++(bytes24 self, bytes22 value, uint8 offset) → bytes24 result++` [.item-kind]#internal# + [.contract-item] [[Packing-extract_28_1-bytes28-uint8-]] ==== `[.contract-item-name]#++extract_28_1++#++(bytes28 self, uint8 offset) → bytes1 result++` [.item-kind]#internal# @@ -7516,6 +8266,14 @@ _Available since v5.1._ [[Packing-replace_28_8-bytes28-bytes8-uint8-]] ==== `[.contract-item-name]#++replace_28_8++#++(bytes28 self, bytes8 value, uint8 offset) → bytes28 result++` [.item-kind]#internal# +[.contract-item] +[[Packing-extract_28_10-bytes28-uint8-]] +==== `[.contract-item-name]#++extract_28_10++#++(bytes28 self, uint8 offset) → bytes10 result++` [.item-kind]#internal# + +[.contract-item] +[[Packing-replace_28_10-bytes28-bytes10-uint8-]] +==== `[.contract-item-name]#++replace_28_10++#++(bytes28 self, bytes10 value, uint8 offset) → bytes28 result++` [.item-kind]#internal# + [.contract-item] [[Packing-extract_28_12-bytes28-uint8-]] ==== `[.contract-item-name]#++extract_28_12++#++(bytes28 self, uint8 offset) → bytes12 result++` [.item-kind]#internal# @@ -7540,6 +8298,14 @@ _Available since v5.1._ [[Packing-replace_28_20-bytes28-bytes20-uint8-]] ==== `[.contract-item-name]#++replace_28_20++#++(bytes28 self, bytes20 value, uint8 offset) → bytes28 result++` [.item-kind]#internal# +[.contract-item] +[[Packing-extract_28_22-bytes28-uint8-]] +==== `[.contract-item-name]#++extract_28_22++#++(bytes28 self, uint8 offset) → bytes22 result++` [.item-kind]#internal# + +[.contract-item] +[[Packing-replace_28_22-bytes28-bytes22-uint8-]] +==== `[.contract-item-name]#++replace_28_22++#++(bytes28 self, bytes22 value, uint8 offset) → bytes28 result++` [.item-kind]#internal# + [.contract-item] [[Packing-extract_28_24-bytes28-uint8-]] ==== `[.contract-item-name]#++extract_28_24++#++(bytes28 self, uint8 offset) → bytes24 result++` [.item-kind]#internal# @@ -7588,6 +8354,14 @@ _Available since v5.1._ [[Packing-replace_32_8-bytes32-bytes8-uint8-]] ==== `[.contract-item-name]#++replace_32_8++#++(bytes32 self, bytes8 value, uint8 offset) → bytes32 result++` [.item-kind]#internal# +[.contract-item] +[[Packing-extract_32_10-bytes32-uint8-]] +==== `[.contract-item-name]#++extract_32_10++#++(bytes32 self, uint8 offset) → bytes10 result++` [.item-kind]#internal# + +[.contract-item] +[[Packing-replace_32_10-bytes32-bytes10-uint8-]] +==== `[.contract-item-name]#++replace_32_10++#++(bytes32 self, bytes10 value, uint8 offset) → bytes32 result++` [.item-kind]#internal# + [.contract-item] [[Packing-extract_32_12-bytes32-uint8-]] ==== `[.contract-item-name]#++extract_32_12++#++(bytes32 self, uint8 offset) → bytes12 result++` [.item-kind]#internal# @@ -7612,6 +8386,14 @@ _Available since v5.1._ [[Packing-replace_32_20-bytes32-bytes20-uint8-]] ==== `[.contract-item-name]#++replace_32_20++#++(bytes32 self, bytes20 value, uint8 offset) → bytes32 result++` [.item-kind]#internal# +[.contract-item] +[[Packing-extract_32_22-bytes32-uint8-]] +==== `[.contract-item-name]#++extract_32_22++#++(bytes32 self, uint8 offset) → bytes22 result++` [.item-kind]#internal# + +[.contract-item] +[[Packing-replace_32_22-bytes32-bytes22-uint8-]] +==== `[.contract-item-name]#++replace_32_22++#++(bytes32 self, bytes22 value, uint8 offset) → bytes32 result++` [.item-kind]#internal# + [.contract-item] [[Packing-extract_32_24-bytes32-uint8-]] ==== `[.contract-item-name]#++extract_32_24++#++(bytes32 self, uint8 offset) → bytes24 result++` [.item-kind]#internal# @@ -7646,7 +8428,7 @@ _Available since v5.1._ [.contract] [[Panic]] -=== `++Panic++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/utils/Panic.sol[{github-icon},role=heading-link] +=== `++Panic++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/utils/Panic.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity @@ -7766,7 +8548,7 @@ calling invalid internal function [.contract] [[Comparators]] -=== `++Comparators++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/utils/Comparators.sol[{github-icon},role=heading-link] +=== `++Comparators++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/utils/Comparators.sol[{github-icon},role=heading-link] [.hljs-theme-light.nopadding] ```solidity diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 000000000..00fcc95bb --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,26 @@ +import js from '@eslint/js'; +import { includeIgnoreFile } from '@eslint/compat'; +import prettier from 'eslint-config-prettier'; +import globals from 'globals'; +import path from 'path'; + +export default [ + js.configs.recommended, + prettier, + { + languageOptions: { + ecmaVersion: 2022, + globals: { + ...globals.browser, + ...globals.mocha, + ...globals.node, + artifacts: 'readonly', + contract: 'readonly', + web3: 'readonly', + extendEnvironment: 'readonly', + expect: 'readonly', + }, + }, + }, + includeIgnoreFile(path.resolve(import.meta.dirname, '.gitignore')), +]; diff --git a/foundry.toml b/foundry.toml index 3f60b7cbb..78dd07812 100644 --- a/foundry.toml +++ b/foundry.toml @@ -8,6 +8,7 @@ out = 'out' libs = ['node_modules', 'lib'] test = 'test' cache_path = 'cache_forge' +fs_permissions = [{ access = "read", path = "./test/bin" }] [fuzz] runs = 5000 diff --git a/fv-requirements.txt b/fv-requirements.txt index 920662b02..608b4de24 100644 --- a/fv-requirements.txt +++ b/fv-requirements.txt @@ -1,4 +1,4 @@ certora-cli==4.13.1 # File uses a custom name (fv-requirements.txt) so that it isn't picked by Netlify's build # whose latest Python version is 0.3.8, incompatible with most recent versions of Halmos -halmos==0.1.13 +halmos==0.2.0 diff --git a/lib/forge-std b/lib/forge-std index ae570fec0..8f24d6b04 160000 --- a/lib/forge-std +++ b/lib/forge-std @@ -1 +1 @@ -Subproject commit ae570fec082bfe1c1f45b0acca4a2b4f84d345ce +Subproject commit 8f24d6b04c92975e0795b5868aa0d783251cdeaa diff --git a/package-lock.json b/package-lock.json index 7326aebea..6066b617f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,18 +1,19 @@ { "name": "openzeppelin-solidity", - "version": "5.0.2", + "version": "5.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "openzeppelin-solidity", - "version": "5.0.2", + "version": "5.1.0", "license": "MIT", "devDependencies": { "@changesets/changelog-github": "^0.5.0", "@changesets/cli": "^2.26.0", "@changesets/pre": "^2.0.0", "@changesets/read": "^0.6.0", + "@eslint/compat": "^1.2.1", "@nomicfoundation/hardhat-chai-matchers": "^2.0.6", "@nomicfoundation/hardhat-ethers": "^3.0.4", "@nomicfoundation/hardhat-network-helpers": "^1.0.3", @@ -21,10 +22,11 @@ "@openzeppelin/upgrade-safe-transpiler": "^0.3.32", "@openzeppelin/upgrades-core": "^1.20.6", "chai": "^4.2.0", - "eslint": "^8.30.0", + "eslint": "^9.0.0", "eslint-config-prettier": "^9.0.0", "ethers": "^6.7.1", - "glob": "^10.3.5", + "glob": "^11.0.0", + "globals": "^15.3.0", "graphlib": "^2.1.8", "hardhat": "^2.22.2", "hardhat-exposed": "^0.3.15", @@ -571,24 +573,69 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.8.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.8.1.tgz", - "integrity": "sha512-PWiOzLIUAjN/w5K17PoF4n6sKBw0gqLHPhywmYHP4t1VFQQVYeb1yWsJwnMVEMl3tUHME7X/SJPZLmtG7XBDxQ==", + "version": "4.11.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.1.tgz", + "integrity": "sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q==", "dev": true, + "license": "MIT", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, - "node_modules/@eslint/eslintrc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", - "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", + "node_modules/@eslint/compat": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-1.2.1.tgz", + "integrity": "sha512-JbHG2TWuCeNzh87fXo+/46Z1LEo9DBA9T188d0fZgGxAD+cNyS6sx9fdiyxjGPBMyQVRlCutTByZ6a5+YMkF7g==", "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "eslint": "^9.10.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/@eslint/config-array": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.18.0.tgz", + "integrity": "sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.4", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.7.0.tgz", + "integrity": "sha512-xp5Jirz5DyPYlPiKat8jaq0EmYvDXKKpzTbxXMpT9eqlRJkRKIz9AGMdlvYjih+im+QlhWrpvVjl8IPC/lHlUw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz", + "integrity": "sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==", + "dev": true, + "license": "MIT", "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", + "espree": "^10.0.1", + "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", @@ -596,7 +643,7 @@ "strip-json-comments": "^3.1.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -608,6 +655,18 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "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", @@ -621,12 +680,36 @@ } }, "node_modules/@eslint/js": { - "version": "8.49.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.49.0.tgz", - "integrity": "sha512-1S8uAY/MTJqVx0SC4epBq+N2yhuwtNwLbJYNZyhL2pO1ZVKn5HFXav5T41Ryzy9K9V7ZId2JB2oy/W4aCd9/2w==", + "version": "9.13.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.13.0.tgz", + "integrity": "sha512-IFLyoY4d72Z5y/6o/BazFBezupzI/taV8sGumxTAVw3lXG9A6md1Dc34T9s1FoD/an9pJH8RHbAxsaEbBed9lA==", "dev": true, + "license": "MIT", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", + "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.1.tgz", + "integrity": "sha512-HFZ4Mp26nbWk9d/BpvP0YNL6W4UoZF0VFcTw/aPPA8RpOxeFQgK+ClABGgAUXs9Y/RGX/l1vOmrqz1MQt9MNuw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@ethereumjs/rlp": { @@ -1111,18 +1194,28 @@ "pnpm": "7.5.1" } }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.11.11", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", - "integrity": "sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==", + "node_modules/@humanfs/core": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.0.tgz", + "integrity": "sha512-2cbWIHbZVEweE853g8jymffCA+NCMiuqeECeBBLm8dg2oFdjuGJhgN4UAbI+6v0CKbbhvtXA4qV8YR5Ji86nmw==", "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.5", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.5.tgz", + "integrity": "sha512-KSPA4umqSG4LHYRodq31VDwKAvaTF4xmVlzM8Aeh4PlU1JQ3IG0wiA8C25d3RQ9nJyM3mBHyI53K06VVL/oFFg==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", - "minimatch": "^3.0.5" + "@humanfs/core": "^0.19.0", + "@humanwhocodes/retry": "^0.3.0" }, "engines": { - "node": ">=10.10.0" + "node": ">=18.18.0" } }, "node_modules/@humanwhocodes/module-importer": { @@ -1138,11 +1231,19 @@ "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/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } }, "node_modules/@isaacs/cliui": { "version": "8.0.2", @@ -2450,6 +2551,13 @@ "@types/chai": "*" } }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/glob": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", @@ -2475,6 +2583,13 @@ "ci-info": "^3.1.0" } }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/@types/lru-cache/-/lru-cache-5.1.1.tgz", @@ -2560,10 +2675,11 @@ } }, "node_modules/acorn": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", - "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.13.0.tgz", + "integrity": "sha512-8zSiw54Oxrdym50NlZ9sUusyO1Z1ZchgRLWRaK6c86XJFClyCgFKetdowBg5bKxyp/u+CDBJG4Mpp0m3HLZl9w==", "dev": true, + "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -2576,6 +2692,7 @@ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, + "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } @@ -2932,9 +3049,9 @@ } }, "node_modules/axios": { - "version": "1.6.8", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz", - "integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==", + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz", + "integrity": "sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==", "dev": true, "dependencies": { "follow-redirects": "^1.15.6", @@ -3970,18 +4087,6 @@ "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/dotenv": { "version": "8.6.0", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz", @@ -4277,57 +4382,64 @@ } }, "node_modules/eslint": { - "version": "8.49.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.49.0.tgz", - "integrity": "sha512-jw03ENfm6VJI0jA9U+8H5zfl5b+FvuU3YYvZRdZHOlU2ggJkxrlkJH4HcDrZpj6YwD8kuYqvQM8LyesoazrSOQ==", + "version": "9.13.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.13.0.tgz", + "integrity": "sha512-EYZK6SX6zjFHST/HRytOdA/zE72Cq/bfw45LSyuwrdvcclb/gqV8RRQxywOBEWO2+WDpva6UZa4CcDeJKzUCFA==", "dev": true, + "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.2", - "@eslint/js": "8.49.0", - "@humanwhocodes/config-array": "^0.11.11", + "@eslint-community/regexpp": "^4.11.0", + "@eslint/config-array": "^0.18.0", + "@eslint/core": "^0.7.0", + "@eslint/eslintrc": "^3.1.0", + "@eslint/js": "9.13.0", + "@eslint/plugin-kit": "^0.2.0", + "@humanfs/node": "^0.16.5", "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", + "@humanwhocodes/retry": "^0.3.1", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "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.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", + "eslint-scope": "^8.1.0", + "eslint-visitor-keys": "^4.1.0", + "espree": "^10.2.0", + "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", + "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", "ignore": "^5.2.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", "text-table": "^0.2.0" }, "bin": { "eslint": "bin/eslint.js" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } } }, "node_modules/eslint-config-prettier": { @@ -4343,16 +4455,17 @@ } }, "node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.1.0.tgz", + "integrity": "sha512-14dSvlhaVhKKsa9Fx1l8A17s7ah7Ef7wCakJ10LYk6+GYmP9yDti2oq2SEwcyndt6knfcZyhyxwY3i9yL78EQw==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -4375,6 +4488,7 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, + "license": "MIT", "dependencies": { "color-convert": "^2.0.1" }, @@ -4385,17 +4499,12 @@ "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, + "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -4412,6 +4521,7 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, + "license": "MIT", "dependencies": { "color-name": "~1.1.4" }, @@ -4423,13 +4533,15 @@ "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 + "dev": true, + "license": "MIT" }, "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, + "license": "MIT", "engines": { "node": ">=10" }, @@ -4437,11 +4549,25 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.1.0.tgz", + "integrity": "sha512-Q7lok0mqMUSf5a/AdAZkA5a/gHcO6snwQClVNNvFKCAVlxXucdU8pKydU5ZVZjBx5xr37vGbFFWtLQYreLzrZg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "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, + "license": "MIT", "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" @@ -4458,6 +4584,7 @@ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, + "license": "ISC", "dependencies": { "is-glob": "^4.0.3" }, @@ -4470,27 +4597,17 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, + "license": "MIT", "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, + "license": "MIT", "dependencies": { "p-locate": "^5.0.0" }, @@ -4506,6 +4623,7 @@ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, + "license": "MIT", "dependencies": { "p-limit": "^3.0.2" }, @@ -4521,6 +4639,7 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, + "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -4529,17 +4648,31 @@ } }, "node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.2.0.tgz", + "integrity": "sha512-upbkBJbckcCNBDBDXEbuhjbP68n+scUd3k/U2EkyM9nw+I/jPiL4cLF/Al06CF96wRltFda16sxDFrxsI1v0/g==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.9.0", + "acorn": "^8.12.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" + "eslint-visitor-keys": "^4.1.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.1.0.tgz", + "integrity": "sha512-Q7lok0mqMUSf5a/AdAZkA5a/gHcO6snwQClVNNvFKCAVlxXucdU8pKydU5ZVZjBx5xr37vGbFFWtLQYreLzrZg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -4575,6 +4708,7 @@ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" }, @@ -4881,15 +5015,16 @@ } }, "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==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, + "license": "MIT", "dependencies": { - "flat-cache": "^3.0.4" + "flat-cache": "^4.0.0" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=16.0.0" } }, "node_modules/fill-range": { @@ -4937,59 +5072,25 @@ } }, "node_modules/flat-cache": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.1.0.tgz", - "integrity": "sha512-OHx4Qwrrt0E4jEIcI5/Xb+f+QmJYNj2rrK8wiIdQOIrB9WrrJL8cjZvXdXuBTkkEwEqLycb5BeZDV1o2i9bTew==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "dev": true, + "license": "MIT", "dependencies": { - "flatted": "^3.2.7", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" + "flatted": "^3.2.9", + "keyv": "^4.5.4" }, "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/flat-cache/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/flat-cache/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": ">=16" } }, "node_modules/flatted": { - "version": "3.2.9", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", - "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", - "dev": true + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "dev": true, + "license": "ISC" }, "node_modules/follow-redirects": { "version": "1.15.6", @@ -5226,22 +5327,24 @@ } }, "node_modules/glob": { - "version": "10.3.10", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", - "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.0.tgz", + "integrity": "sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==", "dev": true, + "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", - "jackspeak": "^2.3.5", - "minimatch": "^9.0.1", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", - "path-scurry": "^1.10.1" + "jackspeak": "^4.0.1", + "minimatch": "^10.0.0", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" }, "bin": { "glob": "dist/esm/bin.mjs" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -5264,20 +5367,22 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } }, "node_modules/glob/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", + "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -5322,15 +5427,13 @@ } }, "node_modules/globals": { - "version": "13.22.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.22.0.tgz", - "integrity": "sha512-H1Ddc/PbZHTDVJSnj8kWptIRSD6AM3pK+mKytuIVF4uoBV7rshFlhhvA58ceJ5wp3Er58w6zj7bykMpYXt3ETw==", + "version": "15.11.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.11.0.tgz", + "integrity": "sha512-yeyNSjdbyVaWurlwCpcA6XNBrHTMIeDdj0/hnvX/OLJ9ekOXYbLsLinH/MucQyGvNnXhidTdNhTtJaffL2sMfw==", "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -5420,12 +5523,6 @@ "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", @@ -5645,6 +5742,16 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/hardhat-gas-reporter/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, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, "node_modules/hardhat-gas-reporter/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -5706,6 +5813,27 @@ "@scure/bip39": "1.2.2" } }, + "node_modules/hardhat-gas-reporter/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/hardhat-gas-reporter/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -5724,6 +5852,29 @@ "node": ">=8" } }, + "node_modules/hardhat-gas-reporter/node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/hardhat-gas-reporter/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, "node_modules/hardhat-gas-reporter/node_modules/markdown-table": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-2.0.0.tgz", @@ -5737,6 +5888,39 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/hardhat-gas-reporter/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/hardhat-gas-reporter/node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/hardhat-gas-reporter/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -6552,15 +6736,6 @@ "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", @@ -6725,15 +6900,16 @@ } }, "node_modules/jackspeak": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", - "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.1.tgz", + "integrity": "sha512-cub8rahkh0Q/bw1+GxP7aeSe29hHHn2V4m29nnDlvCdlgU+3UGxkZp7Z53jLUdpX3jdTO0nJZUDl3xvbWc2Xog==", "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/cliui": "^8.0.2" }, "engines": { - "node": ">=14" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -6825,10 +7001,11 @@ } }, "node_modules/keyv": { - "version": "4.5.3", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.3.tgz", - "integrity": "sha512-QCiSav9WaX1PgETJ+SpNnx2PRRapJ/oRSXM4VO5OGYGSjrxbKPVFVhB3l2OCbLCk329N8qyAtsJjSjvVBWzEug==", + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, + "license": "MIT", "dependencies": { "json-buffer": "3.0.1" } @@ -8022,30 +8199,22 @@ "dev": true }, "node_modules/path-scurry": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", - "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", + "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { - "lru-cache": "^9.1.1 || ^10.0.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.0.1.tgz", - "integrity": "sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g==", - "dev": true, - "engines": { - "node": "14 || >=16.14" - } - }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -8642,105 +8811,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/rimraf/node_modules/balanced-match": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-3.0.1.tgz", - "integrity": "sha512-vjtV3hiLqYDNRoiAv0zC4QaGAMPomEoq83PRmYIofPswwZurCeWR5LByXm7SyoL0Zh5+2z0+HC7jG8gSZJUh0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 16" - } - }, - "node_modules/rimraf/node_modules/brace-expansion": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-4.0.0.tgz", - "integrity": "sha512-l/mOwLWs7BQIgOKrL46dIAbyCKvPV7YJPDspkuc88rHsZRlg3hptUGdU7Trv0VFP4d3xnSGBQrKu5ZvGB7UeIw==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^3.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/rimraf/node_modules/glob": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.0.tgz", - "integrity": "sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^4.0.1", - "minimatch": "^10.0.0", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^2.0.0" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/jackspeak": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.1.tgz", - "integrity": "sha512-cub8rahkh0Q/bw1+GxP7aeSe29hHHn2V4m29nnDlvCdlgU+3UGxkZp7Z53jLUdpX3jdTO0nJZUDl3xvbWc2Xog==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, - "node_modules/rimraf/node_modules/minimatch": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.0.tgz", - "integrity": "sha512-S4phymWe5NHWbTV8sAlyNQfkmdhvaoHX43x4yLtJBjw2zJtEuzkihDjV5uKq+D/EoMkjbG6msw3ubbSd1pGkyg==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^4.0.0" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/path-scurry": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", - "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^11.0.0", - "minipass": "^7.1.2" - }, - "engines": { - "node": "20 || >=22" - }, - "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", @@ -8950,20 +9020,41 @@ "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==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-4.0.4.tgz", + "integrity": "sha512-6JfvwvjUOn8F/jUoBY2Q1v5WY5XS+rj8qSe0v8Y4ezH4InLgTEeOOPQsRll9OV429Pvo6BCHGavIyJfr3TAhsw==", "dev": true, "hasInstallScript": true, "dependencies": { - "elliptic": "^6.5.4", - "node-addon-api": "^2.0.0", + "elliptic": "^6.5.7", + "node-addon-api": "^5.0.0", "node-gyp-build": "^4.2.0" }, "engines": { - "node": ">=10.0.0" + "node": ">=18.0.0" } }, + "node_modules/secp256k1/node_modules/elliptic": { + "version": "6.5.7", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.7.tgz", + "integrity": "sha512-ESVCtTwiA+XhY3wyh24QqRGBoP3rEdDUl3EDUUo9tft074fi19IrdpH7hLCMMP3CIj7jb3W96rn8lt/BqIlt5Q==", + "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/secp256k1/node_modules/node-addon-api": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", + "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==", + "dev": true + }, "node_modules/semver": { "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", diff --git a/package.json b/package.json index 94b7c2a6a..4db5ac0a1 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "openzeppelin-solidity", "description": "Secure Smart Contract library for Solidity", - "version": "5.1.0", + "version": "5.2.0", "private": true, "files": [ "/contracts/**/*.sol", @@ -17,8 +17,8 @@ "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:js": "prettier --log-level warn --ignore-path .gitignore '**/*.{js,ts}' --check && eslint .", + "lint:js:fix": "prettier --log-level warn --ignore-path .gitignore '**/*.{js,ts}' --write && eslint . --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", @@ -26,8 +26,9 @@ "generate": "scripts/generate/run.js", "version": "scripts/release/version.sh", "test": "hardhat test", - "test:inheritance": "scripts/checks/inheritance-ordering.js artifacts/build-info/*", "test:generation": "scripts/checks/generation.sh", + "test:inheritance": "scripts/checks/inheritance-ordering.js artifacts/build-info/*", + "test:pragma": "scripts/checks/pragma-consistency.js artifacts/build-info/*", "gas-report": "env ENABLE_GAS_REPORT=true npm run test", "slither": "npm run clean && slither ." }, @@ -54,6 +55,7 @@ "@changesets/cli": "^2.26.0", "@changesets/pre": "^2.0.0", "@changesets/read": "^0.6.0", + "@eslint/compat": "^1.2.1", "@nomicfoundation/hardhat-chai-matchers": "^2.0.6", "@nomicfoundation/hardhat-ethers": "^3.0.4", "@nomicfoundation/hardhat-network-helpers": "^1.0.3", @@ -62,10 +64,11 @@ "@openzeppelin/upgrade-safe-transpiler": "^0.3.32", "@openzeppelin/upgrades-core": "^1.20.6", "chai": "^4.2.0", - "eslint": "^8.30.0", + "eslint": "^9.0.0", "eslint-config-prettier": "^9.0.0", "ethers": "^6.7.1", - "glob": "^10.3.5", + "globals": "^15.3.0", + "glob": "^11.0.0", "graphlib": "^2.1.8", "hardhat": "^2.22.2", "hardhat-exposed": "^0.3.15", diff --git a/scripts/checks/inheritance-ordering.js b/scripts/checks/inheritance-ordering.js index 72aa37ef7..4ed2deec4 100755 --- a/scripts/checks/inheritance-ordering.js +++ b/scripts/checks/inheritance-ordering.js @@ -2,9 +2,13 @@ const path = require('path'); const graphlib = require('graphlib'); +const match = require('micromatch'); const { findAll } = require('solidity-ast/utils'); const { _: artifacts } = require('yargs').argv; +// files to skip +const skipPatterns = ['contracts-exposed/**', 'contracts/mocks/**']; + for (const artifact of artifacts) { const { output: solcOutput } = require(path.resolve(__dirname, '../..', artifact)); @@ -13,10 +17,7 @@ for (const artifact of artifacts) { const linearized = []; for (const source in solcOutput.contracts) { - if (['contracts-exposed/', 'contracts/mocks/'].some(pattern => source.startsWith(pattern))) { - continue; - } - + if (match.any(source, skipPatterns)) continue; for (const contractDef of findAll('ContractDefinition', solcOutput.sources[source].ast)) { names[contractDef.id] = contractDef.name; linearized.push(contractDef.linearizedBaseContracts); diff --git a/scripts/checks/pragma-consistency.js b/scripts/checks/pragma-consistency.js new file mode 100755 index 000000000..f2f3c548f --- /dev/null +++ b/scripts/checks/pragma-consistency.js @@ -0,0 +1,49 @@ +#!/usr/bin/env node + +const path = require('path'); +const semver = require('semver'); +const match = require('micromatch'); +const { findAll } = require('solidity-ast/utils'); +const { _: artifacts } = require('yargs').argv; + +// files to skip +const skipPatterns = ['contracts-exposed/**', 'contracts/mocks/WithInit.sol']; + +for (const artifact of artifacts) { + const { output: solcOutput } = require(path.resolve(__dirname, '../..', artifact)); + + const pragma = {}; + + // Extract pragma directive for all files + for (const source in solcOutput.contracts) { + if (match.any(source, skipPatterns)) continue; + for (const { literals } of findAll('PragmaDirective', solcOutput.sources[source].ast)) { + // There should only be one. + const [first, ...rest] = literals; + if (first === 'solidity') pragma[source] = rest.join(''); + } + } + + // Compare the pragma directive of the file, to that of the files it imports + for (const source in solcOutput.contracts) { + if (match.any(source, skipPatterns)) continue; + // minimum version of the compiler that matches source's pragma + const minVersion = semver.minVersion(pragma[source]); + // loop over all imports in source + for (const { absolutePath } of findAll('ImportDirective', solcOutput.sources[source].ast)) { + // So files that only import without declaring anything cause issues, because they don't shop in in "pragma" + if (!pragma[absolutePath]) continue; + // Check that the minVersion for source satisfies the requirements of the imported files + if (!semver.satisfies(minVersion, pragma[absolutePath])) { + console.log( + `- ${source} uses ${pragma[source]} but depends on ${absolutePath} that requires ${pragma[absolutePath]}`, + ); + process.exitCode = 1; + } + } + } +} + +if (!process.exitCode) { + console.log('Pragma directives are consistent.'); +} diff --git a/scripts/generate/run.js b/scripts/generate/run.js index e4947eb12..6779c93f4 100755 --- a/scripts/generate/run.js +++ b/scripts/generate/run.js @@ -8,7 +8,7 @@ const format = require('./format-lines'); function getVersion(path) { try { return fs.readFileSync(path, 'utf8').match(/\/\/ OpenZeppelin Contracts \(last updated v[^)]+\)/)[0]; - } catch (err) { + } catch { return null; } } diff --git a/scripts/generate/templates/Checkpoints.js b/scripts/generate/templates/Checkpoints.js index d418b1177..7ec4a7253 100644 --- a/scripts/generate/templates/Checkpoints.js +++ b/scripts/generate/templates/Checkpoints.js @@ -227,7 +227,6 @@ function _unsafeAccess( } } `; -/* eslint-enable max-len */ // GENERATE module.exports = format( diff --git a/scripts/generate/templates/Checkpoints.t.js b/scripts/generate/templates/Checkpoints.t.js index dd564e59b..edd2e9f98 100644 --- a/scripts/generate/templates/Checkpoints.t.js +++ b/scripts/generate/templates/Checkpoints.t.js @@ -11,7 +11,6 @@ import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; import {Checkpoints} from "@openzeppelin/contracts/utils/structs/Checkpoints.sol"; `; -/* eslint-disable max-len */ const template = opts => `\ using Checkpoints for Checkpoints.${opts.historyTypeName}; diff --git a/scripts/generate/templates/EnumerableMap.js b/scripts/generate/templates/EnumerableMap.js index fc896f8fb..c9cad6c1b 100644 --- a/scripts/generate/templates/EnumerableMap.js +++ b/scripts/generate/templates/EnumerableMap.js @@ -2,7 +2,6 @@ const format = require('../format-lines'); const { fromBytes32, toBytes32 } = require('./conversion'); const { TYPES } = require('./EnumerableMap.opts'); -/* eslint-disable max-len */ const header = `\ pragma solidity ^0.8.20; @@ -52,7 +51,6 @@ import {EnumerableSet} from "./EnumerableSet.sol"; * ==== */ `; -/* eslint-enable max-len */ const defaultMap = `\ // To implement this library for multiple types with as little code repetition as possible, we write it in diff --git a/scripts/generate/templates/EnumerableSet.js b/scripts/generate/templates/EnumerableSet.js index 351466b13..02eccd0df 100644 --- a/scripts/generate/templates/EnumerableSet.js +++ b/scripts/generate/templates/EnumerableSet.js @@ -2,7 +2,6 @@ const format = require('../format-lines'); const { fromBytes32, toBytes32 } = require('./conversion'); const { TYPES } = require('./EnumerableSet.opts'); -/* eslint-disable max-len */ const header = `\ pragma solidity ^0.8.20; @@ -41,7 +40,6 @@ pragma solidity ^0.8.20; * ==== */ `; -/* eslint-enable max-len */ const defaultSet = `\ // To implement this library for multiple types with as little code diff --git a/scripts/generate/templates/MerkleProof.js b/scripts/generate/templates/MerkleProof.js index 6711a87e2..890b2feba 100644 --- a/scripts/generate/templates/MerkleProof.js +++ b/scripts/generate/templates/MerkleProof.js @@ -43,7 +43,6 @@ const errors = `\ error MerkleProofInvalidMultiproof(); `; -/* eslint-disable max-len */ const templateProof = ({ suffix, location, visibility, hash }) => `\ /** * @dev Returns true if a \`leaf\` can be proved to be a part of a Merkle tree @@ -172,7 +171,6 @@ function processMultiProof${suffix}(${formatArgsMultiline( } } `; -/* eslint-enable max-len */ // GENERATE module.exports = format( diff --git a/scripts/generate/templates/Packing.opts.js b/scripts/generate/templates/Packing.opts.js index de9ab77ff..893ad6297 100644 --- a/scripts/generate/templates/Packing.opts.js +++ b/scripts/generate/templates/Packing.opts.js @@ -1,3 +1,3 @@ module.exports = { - SIZES: [1, 2, 4, 6, 8, 12, 16, 20, 24, 28, 32], + SIZES: [1, 2, 4, 6, 8, 10, 12, 16, 20, 22, 24, 28, 32], }; diff --git a/scripts/generate/templates/Packing.t.js b/scripts/generate/templates/Packing.t.js index 56e9c0cc7..1feec28f5 100644 --- a/scripts/generate/templates/Packing.t.js +++ b/scripts/generate/templates/Packing.t.js @@ -11,14 +11,14 @@ import {Packing} from "@openzeppelin/contracts/utils/Packing.sol"; `; const testPack = (left, right) => `\ -function testPack(bytes${left} left, bytes${right} right) external { +function testPack(bytes${left} left, bytes${right} right) external pure { assertEq(left, Packing.pack_${left}_${right}(left, right).extract_${left + right}_${left}(0)); assertEq(right, Packing.pack_${left}_${right}(left, right).extract_${left + right}_${right}(${left})); } `; const testReplace = (outer, inner) => `\ -function testReplace(bytes${outer} container, bytes${inner} newValue, uint8 offset) external { +function testReplace(bytes${outer} container, bytes${inner} newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, ${outer - inner})); bytes${inner} oldValue = container.extract_${outer}_${inner}(offset); diff --git a/scripts/generate/templates/SafeCast.js b/scripts/generate/templates/SafeCast.js index a3b32e3f0..21000cf4a 100644 --- a/scripts/generate/templates/SafeCast.js +++ b/scripts/generate/templates/SafeCast.js @@ -61,7 +61,6 @@ function toUint${length}(uint256 value) internal pure returns (uint${length}) { } `; -/* eslint-disable max-len */ const toIntDownCast = length => `\ /** * @dev Returns the downcasted int${length} from int256, reverting on @@ -81,7 +80,6 @@ function toInt${length}(int256 value) internal pure returns (int${length} downca } } `; -/* eslint-enable max-len */ const toInt = length => `\ /** diff --git a/scripts/release/workflow/state.js b/scripts/release/workflow/state.js index 914e8de02..002f7774d 100644 --- a/scripts/release/workflow/state.js +++ b/scripts/release/workflow/state.js @@ -106,7 +106,7 @@ async function readChangesetState(cwd = process.cwd()) { }; } -async function isPublishedOnNpm(package, version) { - const res = await fetch(`https://registry.npmjs.com/${package}/${version}`); +async function isPublishedOnNpm(packageName, version) { + const res = await fetch(`https://registry.npmjs.com/${packageName}/${version}`); return res.ok; } diff --git a/scripts/update-docs-branch.js b/scripts/update-docs-branch.js index 324ba0c67..cf61daad8 100644 --- a/scripts/update-docs-branch.js +++ b/scripts/update-docs-branch.js @@ -6,7 +6,7 @@ const run = cmd => { const tryRead = cmd => { try { return read(cmd); - } catch (e) { + } catch { return undefined; } }; diff --git a/slither.config.json b/slither.config.json index 069da1f3a..fa52f4dd1 100644 --- a/slither.config.json +++ b/slither.config.json @@ -1,5 +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", + "filter_paths": "contracts/mocks,contracts/vendor,contracts-exposed", "compile_force_framework": "hardhat" } diff --git a/test/account/utils/draft-ERC4337Utils.test.js b/test/account/utils/draft-ERC4337Utils.test.js new file mode 100644 index 000000000..7c292910d --- /dev/null +++ b/test/account/utils/draft-ERC4337Utils.test.js @@ -0,0 +1,288 @@ +const { ethers } = require('hardhat'); +const { expect } = require('chai'); +const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); + +const { packValidationData, UserOperation } = require('../../helpers/erc4337'); +const { deployEntrypoint } = require('../../helpers/erc4337-entrypoint'); +const { MAX_UINT48 } = require('../../helpers/constants'); +const ADDRESS_ONE = '0x0000000000000000000000000000000000000001'; + +const fixture = async () => { + const { entrypoint } = await deployEntrypoint(); + const [authorizer, sender, factory, paymaster] = await ethers.getSigners(); + const utils = await ethers.deployContract('$ERC4337Utils'); + const SIG_VALIDATION_SUCCESS = await utils.$SIG_VALIDATION_SUCCESS(); + const SIG_VALIDATION_FAILED = await utils.$SIG_VALIDATION_FAILED(); + return { utils, authorizer, sender, entrypoint, factory, paymaster, SIG_VALIDATION_SUCCESS, SIG_VALIDATION_FAILED }; +}; + +describe('ERC4337Utils', function () { + beforeEach(async function () { + Object.assign(this, await loadFixture(fixture)); + }); + + describe('parseValidationData', function () { + it('parses the validation data', async function () { + const authorizer = this.authorizer; + const validUntil = 0x12345678n; + const validAfter = 0x9abcdef0n; + const validationData = packValidationData(validAfter, validUntil, authorizer); + + expect(this.utils.$parseValidationData(validationData)).to.eventually.deep.equal([ + authorizer.address, + validAfter, + validUntil, + ]); + }); + + it('returns an type(uint48).max if until is 0', async function () { + const authorizer = this.authorizer; + const validAfter = 0x12345678n; + const validationData = packValidationData(validAfter, 0, authorizer); + + expect(this.utils.$parseValidationData(validationData)).to.eventually.deep.equal([ + authorizer.address, + validAfter, + MAX_UINT48, + ]); + }); + + it('parse canonical values', async function () { + expect(this.utils.$parseValidationData(this.SIG_VALIDATION_SUCCESS)).to.eventually.deep.equal([ + ethers.ZeroAddress, + 0n, + MAX_UINT48, + ]); + + expect(this.utils.$parseValidationData(this.SIG_VALIDATION_FAILED)).to.eventually.deep.equal([ + ADDRESS_ONE, + 0n, + MAX_UINT48, + ]); + }); + }); + + describe('packValidationData', function () { + it('packs the validation data', async function () { + const authorizer = this.authorizer; + const validUntil = 0x12345678n; + const validAfter = 0x9abcdef0n; + const validationData = packValidationData(validAfter, validUntil, authorizer); + + expect( + this.utils.$packValidationData(ethers.Typed.address(authorizer), validAfter, validUntil), + ).to.eventually.equal(validationData); + }); + + it('packs the validation data (bool)', async function () { + const success = false; + const validUntil = 0x12345678n; + const validAfter = 0x9abcdef0n; + const validationData = packValidationData(validAfter, validUntil, false); + + expect(this.utils.$packValidationData(ethers.Typed.bool(success), validAfter, validUntil)).to.eventually.equal( + validationData, + ); + }); + + it('packing reproduced canonical values', async function () { + expect(this.utils.$packValidationData(ethers.Typed.address(ethers.ZeroAddress), 0n, 0n)).to.eventually.equal( + this.SIG_VALIDATION_SUCCESS, + ); + expect(this.utils.$packValidationData(ethers.Typed.bool(true), 0n, 0n)).to.eventually.equal( + this.SIG_VALIDATION_SUCCESS, + ); + expect(this.utils.$packValidationData(ethers.Typed.address(ADDRESS_ONE), 0n, 0n)).to.eventually.equal( + this.SIG_VALIDATION_FAILED, + ); + expect(this.utils.$packValidationData(ethers.Typed.bool(false), 0n, 0n)).to.eventually.equal( + this.SIG_VALIDATION_FAILED, + ); + }); + }); + + describe('combineValidationData', function () { + const validUntil1 = 0x12345678n; + const validAfter1 = 0x9abcdef0n; + const validUntil2 = 0x87654321n; + const validAfter2 = 0xabcdef90n; + + it('combines the validation data', async function () { + const validationData1 = packValidationData(validAfter1, validUntil1, ethers.ZeroAddress); + const validationData2 = packValidationData(validAfter2, validUntil2, ethers.ZeroAddress); + const expected = packValidationData(validAfter2, validUntil1, true); + + // check symmetry + expect(this.utils.$combineValidationData(validationData1, validationData2)).to.eventually.equal(expected); + expect(this.utils.$combineValidationData(validationData2, validationData1)).to.eventually.equal(expected); + }); + + for (const [authorizer1, authorizer2] of [ + [ethers.ZeroAddress, '0xbf023313b891fd6000544b79e353323aa94a4f29'], + ['0xbf023313b891fd6000544b79e353323aa94a4f29', ethers.ZeroAddress], + ]) { + it('returns SIG_VALIDATION_FAILURE if one of the authorizers is not address(0)', async function () { + const validationData1 = packValidationData(validAfter1, validUntil1, authorizer1); + const validationData2 = packValidationData(validAfter2, validUntil2, authorizer2); + const expected = packValidationData(validAfter2, validUntil1, false); + + // check symmetry + expect(this.utils.$combineValidationData(validationData1, validationData2)).to.eventually.equal(expected); + expect(this.utils.$combineValidationData(validationData2, validationData1)).to.eventually.equal(expected); + }); + } + }); + + describe('getValidationData', function () { + it('returns the validation data with valid validity range', async function () { + const aggregator = this.authorizer; + const validAfter = 0; + const validUntil = MAX_UINT48; + const validationData = packValidationData(validAfter, validUntil, aggregator); + + expect(this.utils.$getValidationData(validationData)).to.eventually.deep.equal([aggregator.address, false]); + }); + + it('returns the validation data with invalid validity range (expired)', async function () { + const aggregator = this.authorizer; + const validAfter = 0; + const validUntil = 1; + const validationData = packValidationData(validAfter, validUntil, aggregator); + + expect(this.utils.$getValidationData(validationData)).to.eventually.deep.equal([aggregator.address, true]); + }); + + it('returns the validation data with invalid validity range (not yet valid)', async function () { + const aggregator = this.authorizer; + const validAfter = MAX_UINT48; + const validUntil = MAX_UINT48; + const validationData = packValidationData(validAfter, validUntil, aggregator); + + expect(this.utils.$getValidationData(validationData)).to.eventually.deep.equal([aggregator.address, true]); + }); + + it('returns address(0) and false for validationData = 0', function () { + expect(this.utils.$getValidationData(0n)).to.eventually.deep.equal([ethers.ZeroAddress, false]); + }); + }); + + describe('hash', function () { + it('returns the operation hash with specified entrypoint and chainId', async function () { + const userOp = new UserOperation({ sender: this.sender, nonce: 1 }); + const chainId = await ethers.provider.getNetwork().then(({ chainId }) => chainId); + const otherChainId = 0xdeadbeef; + + // check that helper matches entrypoint logic + expect(this.entrypoint.getUserOpHash(userOp.packed)).to.eventually.equal(userOp.hash(this.entrypoint, chainId)); + + // check library against helper + expect(this.utils.$hash(userOp.packed, this.entrypoint, chainId)).to.eventually.equal( + userOp.hash(this.entrypoint, chainId), + ); + expect(this.utils.$hash(userOp.packed, this.entrypoint, otherChainId)).to.eventually.equal( + userOp.hash(this.entrypoint, otherChainId), + ); + }); + }); + + describe('userOp values', function () { + describe('intiCode', function () { + beforeEach(async function () { + this.userOp = new UserOperation({ + sender: this.sender, + nonce: 1, + verificationGas: 0x12345678n, + factory: this.factory, + factoryData: '0x123456', + }); + + this.emptyUserOp = new UserOperation({ + sender: this.sender, + nonce: 1, + }); + }); + + it('returns factory', async function () { + expect(this.utils.$factory(this.userOp.packed)).to.eventually.equal(this.factory); + expect(this.utils.$factory(this.emptyUserOp.packed)).to.eventually.equal(ethers.ZeroAddress); + }); + + it('returns factoryData', async function () { + expect(this.utils.$factoryData(this.userOp.packed)).to.eventually.equal('0x123456'); + expect(this.utils.$factoryData(this.emptyUserOp.packed)).to.eventually.equal('0x'); + }); + }); + + it('returns verificationGasLimit', async function () { + const userOp = new UserOperation({ sender: this.sender, nonce: 1, verificationGas: 0x12345678n }); + expect(this.utils.$verificationGasLimit(userOp.packed)).to.eventually.equal(userOp.verificationGas); + }); + + it('returns callGasLimit', async function () { + const userOp = new UserOperation({ sender: this.sender, nonce: 1, callGas: 0x12345678n }); + expect(this.utils.$callGasLimit(userOp.packed)).to.eventually.equal(userOp.callGas); + }); + + it('returns maxPriorityFeePerGas', async function () { + const userOp = new UserOperation({ sender: this.sender, nonce: 1, maxPriorityFee: 0x12345678n }); + expect(this.utils.$maxPriorityFeePerGas(userOp.packed)).to.eventually.equal(userOp.maxPriorityFee); + }); + + it('returns maxFeePerGas', async function () { + const userOp = new UserOperation({ sender: this.sender, nonce: 1, maxFeePerGas: 0x12345678n }); + expect(this.utils.$maxFeePerGas(userOp.packed)).to.eventually.equal(userOp.maxFeePerGas); + }); + + it('returns gasPrice', async function () { + const userOp = new UserOperation({ + sender: this.sender, + nonce: 1, + maxPriorityFee: 0x12345678n, + maxFeePerGas: 0x87654321n, + }); + expect(this.utils.$gasPrice(userOp.packed)).to.eventually.equal(userOp.maxPriorityFee); + }); + + describe('paymasterAndData', function () { + beforeEach(async function () { + this.userOp = new UserOperation({ + sender: this.sender, + nonce: 1, + paymaster: this.paymaster, + paymasterVerificationGasLimit: 0x12345678n, + paymasterPostOpGasLimit: 0x87654321n, + paymasterData: '0xbeefcafe', + }); + + this.emptyUserOp = new UserOperation({ + sender: this.sender, + nonce: 1, + }); + }); + + it('returns paymaster', async function () { + expect(this.utils.$paymaster(this.userOp.packed)).to.eventually.equal(this.userOp.paymaster); + expect(this.utils.$paymaster(this.emptyUserOp.packed)).to.eventually.equal(ethers.ZeroAddress); + }); + + it('returns verificationGasLimit', async function () { + expect(this.utils.$paymasterVerificationGasLimit(this.userOp.packed)).to.eventually.equal( + this.userOp.paymasterVerificationGasLimit, + ); + expect(this.utils.$paymasterVerificationGasLimit(this.emptyUserOp.packed)).to.eventually.equal(0n); + }); + + it('returns postOpGasLimit', async function () { + expect(this.utils.$paymasterPostOpGasLimit(this.userOp.packed)).to.eventually.equal( + this.userOp.paymasterPostOpGasLimit, + ); + expect(this.utils.$paymasterPostOpGasLimit(this.emptyUserOp.packed)).to.eventually.equal(0n); + }); + + it('returns data', async function () { + expect(this.utils.$paymasterData(this.userOp.packed)).to.eventually.equal(this.userOp.paymasterData); + expect(this.utils.$paymasterData(this.emptyUserOp.packed)).to.eventually.equal('0x'); + }); + }); + }); +}); diff --git a/test/account/utils/draft-ERC7579Utils.t.sol b/test/account/utils/draft-ERC7579Utils.t.sol new file mode 100644 index 000000000..fdd4edf59 --- /dev/null +++ b/test/account/utils/draft-ERC7579Utils.t.sol @@ -0,0 +1,421 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.24; + +// Parts of this test file are adapted from Adam Egyed (@adamegyed) proof of concept available at: +// https://github.com/adamegyed/erc7579-execute-vulnerability/tree/4589a30ff139e143d6c57183ac62b5c029217a90 +// +// solhint-disable no-console + +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; +import {PackedUserOperation, IAccount, IEntryPoint} from "@openzeppelin/contracts/interfaces/draft-IERC4337.sol"; +import {ERC4337Utils} from "@openzeppelin/contracts/account/utils/draft-ERC4337Utils.sol"; +import {ERC7579Utils, Mode, CallType, ExecType, ModeSelector, ModePayload, Execution} from "@openzeppelin/contracts/account/utils/draft-ERC7579Utils.sol"; +import {Test, Vm, console} from "forge-std/Test.sol"; + +contract SampleAccount is IAccount, Ownable { + using ECDSA for *; + using MessageHashUtils for *; + using ERC4337Utils for *; + using ERC7579Utils for *; + + IEntryPoint internal constant ENTRY_POINT = IEntryPoint(payable(0x0000000071727De22E5E9d8BAf0edAc6f37da032)); + + event Log(bool duringValidation, Execution[] calls); + + error UnsupportedCallType(CallType callType); + + constructor(address initialOwner) Ownable(initialOwner) {} + + function validateUserOp( + PackedUserOperation calldata userOp, + bytes32 userOpHash, + uint256 missingAccountFunds + ) external override returns (uint256 validationData) { + require(msg.sender == address(ENTRY_POINT), "only from EP"); + // Check signature + if (userOpHash.toEthSignedMessageHash().recover(userOp.signature) != owner()) { + revert OwnableUnauthorizedAccount(_msgSender()); + } + + // If this is an execute call with a batch operation, log the call details from the calldata + if (bytes4(userOp.callData[0x00:0x04]) == this.execute.selector) { + (CallType callType, , , ) = Mode.wrap(bytes32(userOp.callData[0x04:0x24])).decodeMode(); + + if (callType == ERC7579Utils.CALLTYPE_BATCH) { + // Remove the selector + bytes calldata params = userOp.callData[0x04:]; + + // Use the same vulnerable assignment technique here, but assert afterwards that the checks aren't + // broken here by comparing to the result of `abi.decode(...)`. + bytes calldata executionCalldata; + assembly ("memory-safe") { + let dataptr := add(params.offset, calldataload(add(params.offset, 0x20))) + executionCalldata.offset := add(dataptr, 32) + executionCalldata.length := calldataload(dataptr) + } + // Check that this decoding step is done correctly. + (, bytes memory executionCalldataMemory) = abi.decode(params, (bytes32, bytes)); + + require( + keccak256(executionCalldata) == keccak256(executionCalldataMemory), + "decoding during validation failed" + ); + // Now, we know that we have `bytes calldata executionCalldata` as would be decoded by the solidity + // builtin decoder for the `execute` function. + + // This is where the vulnerability from ExecutionLib results in a different result between validation + // andexecution. + + emit Log(true, executionCalldata.decodeBatch()); + } + } + + if (missingAccountFunds > 0) { + (bool success, ) = payable(msg.sender).call{value: missingAccountFunds}(""); + success; // Silence warning. The entrypoint should validate the result. + } + + return ERC4337Utils.SIG_VALIDATION_SUCCESS; + } + + function execute(Mode mode, bytes calldata executionCalldata) external payable { + require(msg.sender == address(this) || msg.sender == address(ENTRY_POINT), "not auth"); + + (CallType callType, ExecType execType, , ) = mode.decodeMode(); + + // check if calltype is batch or single + if (callType == ERC7579Utils.CALLTYPE_SINGLE) { + executionCalldata.execSingle(execType); + } else if (callType == ERC7579Utils.CALLTYPE_BATCH) { + executionCalldata.execBatch(execType); + + emit Log(false, executionCalldata.decodeBatch()); + } else if (callType == ERC7579Utils.CALLTYPE_DELEGATECALL) { + executionCalldata.execDelegateCall(execType); + } else { + revert UnsupportedCallType(callType); + } + } +} + +contract ERC7579UtilsTest is Test { + using MessageHashUtils for *; + using ERC4337Utils for *; + using ERC7579Utils for *; + + IEntryPoint private constant ENTRYPOINT = IEntryPoint(payable(0x0000000071727De22E5E9d8BAf0edAc6f37da032)); + address private _owner; + uint256 private _ownerKey; + address private _account; + address private _beneficiary; + address private _recipient1; + address private _recipient2; + + constructor() { + vm.etch(0x0000000071727De22E5E9d8BAf0edAc6f37da032, vm.readFileBinary("test/bin/EntryPoint070.bytecode")); + vm.etch(0xEFC2c1444eBCC4Db75e7613d20C6a62fF67A167C, vm.readFileBinary("test/bin/SenderCreator070.bytecode")); + + // signing key + (_owner, _ownerKey) = makeAddrAndKey("owner"); + + // ERC-4337 account + _account = address(new SampleAccount(_owner)); + vm.deal(_account, 1 ether); + + // other + _beneficiary = makeAddr("beneficiary"); + _recipient1 = makeAddr("recipient1"); + _recipient2 = makeAddr("recipient2"); + } + + function testExecuteBatchDecodeCorrectly() public { + Execution[] memory calls = new Execution[](2); + calls[0] = Execution({target: _recipient1, value: 1 wei, callData: ""}); + calls[1] = Execution({target: _recipient2, value: 1 wei, callData: ""}); + + PackedUserOperation[] memory userOps = new PackedUserOperation[](1); + userOps[0] = PackedUserOperation({ + sender: _account, + nonce: 0, + initCode: "", + callData: abi.encodeCall( + SampleAccount.execute, + ( + ERC7579Utils.encodeMode( + ERC7579Utils.CALLTYPE_BATCH, + ERC7579Utils.EXECTYPE_DEFAULT, + ModeSelector.wrap(0x00), + ModePayload.wrap(0x00) + ), + ERC7579Utils.encodeBatch(calls) + ) + ), + accountGasLimits: _packGas(500_000, 500_000), + preVerificationGas: 0, + gasFees: _packGas(1, 1), + paymasterAndData: "", + signature: "" + }); + + (uint8 v, bytes32 r, bytes32 s) = vm.sign( + _ownerKey, + this.hashUserOperation(userOps[0]).toEthSignedMessageHash() + ); + userOps[0].signature = abi.encodePacked(r, s, v); + + vm.recordLogs(); + ENTRYPOINT.handleOps(userOps, payable(_beneficiary)); + + assertEq(_recipient1.balance, 1 wei); + assertEq(_recipient2.balance, 1 wei); + + _collectAndPrintLogs(false); + } + + function testExecuteBatchDecodeEmpty() public { + bytes memory fakeCalls = abi.encodePacked( + uint256(1), // Length of execution[] + uint256(0x20), // offset + uint256(uint160(_recipient1)), // target + uint256(1), // value: 1 wei + uint256(0x60), // offset of data + uint256(0) // length of + ); + + PackedUserOperation[] memory userOps = new PackedUserOperation[](1); + userOps[0] = PackedUserOperation({ + sender: _account, + nonce: 0, + initCode: "", + callData: abi.encodeCall( + SampleAccount.execute, + ( + ERC7579Utils.encodeMode( + ERC7579Utils.CALLTYPE_BATCH, + ERC7579Utils.EXECTYPE_DEFAULT, + ModeSelector.wrap(0x00), + ModePayload.wrap(0x00) + ), + abi.encodePacked( + uint256(0x70) // fake offset pointing to paymasterAndData + ) + ) + ), + accountGasLimits: _packGas(500_000, 500_000), + preVerificationGas: 0, + gasFees: _packGas(1, 1), + paymasterAndData: abi.encodePacked(address(0), fakeCalls), + signature: "" + }); + + (uint8 v, bytes32 r, bytes32 s) = vm.sign( + _ownerKey, + this.hashUserOperation(userOps[0]).toEthSignedMessageHash() + ); + userOps[0].signature = abi.encodePacked(r, s, v); + + vm.expectRevert( + abi.encodeWithSelector( + IEntryPoint.FailedOpWithRevert.selector, + 0, + "AA23 reverted", + abi.encodeWithSelector(ERC7579Utils.ERC7579DecodingError.selector) + ) + ); + ENTRYPOINT.handleOps(userOps, payable(_beneficiary)); + + _collectAndPrintLogs(false); + } + + function testExecuteBatchDecodeDifferent() public { + bytes memory execCallData = abi.encodePacked( + uint256(0x20), // offset pointing to the next segment + uint256(5), // Length of execution[] + uint256(0), // offset of calls[0], and target (!!) + uint256(0x20), // offset of calls[1], and value (!!) + uint256(0), // offset of calls[2], and rel offset of data (!!) + uint256(0) // offset of calls[3]. + // There is one more to read by the array length, but it's not present here. This will be + // paymasterAndData.length during validation, pointing to an all-zero call. + // During execution, the offset will be 0, pointing to a call with value. + ); + + PackedUserOperation[] memory userOps = new PackedUserOperation[](1); + userOps[0] = PackedUserOperation({ + sender: _account, + nonce: 0, + initCode: "", + callData: abi.encodePacked( + SampleAccount.execute.selector, + ERC7579Utils.encodeMode( + ERC7579Utils.CALLTYPE_BATCH, + ERC7579Utils.EXECTYPE_DEFAULT, + ModeSelector.wrap(0x00), + ModePayload.wrap(0x00) + ), + uint256(0x5c), // offset pointing to the next segment + uint224(type(uint224).max), // Padding to align the `bytes` types + // type(uint256).max, // unknown padding + uint256(execCallData.length), // Length of the data + execCallData + ), + accountGasLimits: _packGas(500_000, 500_000), + preVerificationGas: 0, + gasFees: _packGas(1, 1), + paymasterAndData: abi.encodePacked(uint256(0), uint256(0)), // padding length to create an offset + signature: "" + }); + + (uint8 v, bytes32 r, bytes32 s) = vm.sign( + _ownerKey, + this.hashUserOperation(userOps[0]).toEthSignedMessageHash() + ); + userOps[0].signature = abi.encodePacked(r, s, v); + + vm.expectRevert( + abi.encodeWithSelector( + IEntryPoint.FailedOpWithRevert.selector, + 0, + "AA23 reverted", + abi.encodeWithSelector(ERC7579Utils.ERC7579DecodingError.selector) + ) + ); + ENTRYPOINT.handleOps(userOps, payable(_beneficiary)); + + _collectAndPrintLogs(true); + } + + function testDecodeBatch() public { + // BAD: buffer empty + vm.expectRevert(ERC7579Utils.ERC7579DecodingError.selector); + this.callDecodeBatch(""); + + // BAD: buffer too short + vm.expectRevert(ERC7579Utils.ERC7579DecodingError.selector); + this.callDecodeBatch(abi.encodePacked(uint128(0))); + + // GOOD + this.callDecodeBatch(abi.encode(0)); + // Note: Solidity also supports this even though it's odd. Offset 0 means array is at the same location, which + // is interpreted as an array of length 0, which doesn't require any more data + // solhint-disable-next-line var-name-mixedcase + uint256[] memory _1 = abi.decode(abi.encode(0), (uint256[])); + _1; + + // BAD: offset is out of bounds + vm.expectRevert(ERC7579Utils.ERC7579DecodingError.selector); + this.callDecodeBatch(abi.encode(1)); + + // GOOD + this.callDecodeBatch(abi.encode(32, 0)); + + // BAD: reported array length extends beyond bounds + vm.expectRevert(ERC7579Utils.ERC7579DecodingError.selector); + this.callDecodeBatch(abi.encode(32, 1)); + + // GOOD + this.callDecodeBatch(abi.encode(32, 1, 0)); + + // GOOD + // + // 0000000000000000000000000000000000000000000000000000000000000020 (32) offset + // 0000000000000000000000000000000000000000000000000000000000000001 ( 1) array length + // 0000000000000000000000000000000000000000000000000000000000000020 (32) element 0 offset + // 000000000000000000000000xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx (recipient) target for element #0 + // 000000000000000000000000000000000000000000000000000000000000002a (42) value for element #0 + // 0000000000000000000000000000000000000000000000000000000000000060 (96) offset to calldata for element #0 + // 000000000000000000000000000000000000000000000000000000000000000c (12) length of the calldata for element #0 + // 48656c6c6f20576f726c64210000000000000000000000000000000000000000 (..) buffer for the calldata for element #0 + assertEq( + bytes("Hello World!"), + this.callDecodeBatchAndGetFirstBytes( + abi.encode(32, 1, 32, _recipient1, 42, 96, 12, bytes12("Hello World!")) + ) + ); + + // This is invalid, the first element of the array points is out of bounds + // but we allow it past initial validation, because solidity will validate later when the bytes field is accessed + // + // 0000000000000000000000000000000000000000000000000000000000000020 (32) offset + // 0000000000000000000000000000000000000000000000000000000000000001 ( 1) array length + // 0000000000000000000000000000000000000000000000000000000000000020 (32) element 0 offset + // + bytes memory invalid = abi.encode(32, 1, 32); + this.callDecodeBatch(invalid); + vm.expectRevert(); + this.callDecodeBatchAndGetFirst(invalid); + + // this is invalid: the bytes field of the first element of the array is out of bounds + // but we allow it past initial validation, because solidity will validate later when the bytes field is accessed + // + // 0000000000000000000000000000000000000000000000000000000000000020 (32) offset + // 0000000000000000000000000000000000000000000000000000000000000001 ( 1) array length + // 0000000000000000000000000000000000000000000000000000000000000020 (32) element 0 offset + // 000000000000000000000000xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx (recipient) target for element #0 + // 000000000000000000000000000000000000000000000000000000000000002a (42) value for element #0 + // 0000000000000000000000000000000000000000000000000000000000000060 (96) offset to calldata for element #0 + // + bytes memory invalidDeeply = abi.encode(32, 1, 32, _recipient1, 42, 96); + this.callDecodeBatch(invalidDeeply); + // Note that this is ok because we don't return the value. Returning it would introduce a check that would fails. + this.callDecodeBatchAndGetFirst(invalidDeeply); + vm.expectRevert(); + this.callDecodeBatchAndGetFirstBytes(invalidDeeply); + } + + function callDecodeBatch(bytes calldata executionCalldata) public pure { + ERC7579Utils.decodeBatch(executionCalldata); + } + + function callDecodeBatchAndGetFirst(bytes calldata executionCalldata) public pure { + ERC7579Utils.decodeBatch(executionCalldata)[0]; + } + + function callDecodeBatchAndGetFirstBytes(bytes calldata executionCalldata) public pure returns (bytes calldata) { + return ERC7579Utils.decodeBatch(executionCalldata)[0].callData; + } + + function hashUserOperation(PackedUserOperation calldata useroperation) public view returns (bytes32) { + return useroperation.hash(address(ENTRYPOINT), block.chainid); + } + + function _collectAndPrintLogs(bool includeTotalValue) internal { + Vm.Log[] memory logs = vm.getRecordedLogs(); + for (uint256 i = 0; i < logs.length; i++) { + if (logs[i].emitter == _account) { + _printDecodedCalls(logs[i].data, includeTotalValue); + } + } + } + + function _printDecodedCalls(bytes memory logData, bool includeTotalValue) internal pure { + (bool duringValidation, Execution[] memory calls) = abi.decode(logData, (bool, Execution[])); + + console.log( + string.concat( + "Batch execute contents, as read during ", + duringValidation ? "validation" : "execution", + ": " + ) + ); + console.log(" Execution[] length: %s", calls.length); + + uint256 totalValue = 0; + for (uint256 i = 0; i < calls.length; ++i) { + console.log(string.concat(" calls[", vm.toString(i), "].target = ", vm.toString(calls[i].target))); + console.log(string.concat(" calls[", vm.toString(i), "].value = ", vm.toString(calls[i].value))); + console.log(string.concat(" calls[", vm.toString(i), "].data = ", vm.toString(calls[i].callData))); + totalValue += calls[i].value; + } + + if (includeTotalValue) { + console.log(" Total value: %s", totalValue); + } + } + + function _packGas(uint256 upper, uint256 lower) internal pure returns (bytes32) { + return bytes32(uint256((upper << 128) | uint128(lower))); + } +} diff --git a/test/account/utils/draft-ERC7579Utils.test.js b/test/account/utils/draft-ERC7579Utils.test.js new file mode 100644 index 000000000..7419c667b --- /dev/null +++ b/test/account/utils/draft-ERC7579Utils.test.js @@ -0,0 +1,353 @@ +const { ethers } = require('hardhat'); +const { expect } = require('chai'); +const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); +const { + EXEC_TYPE_DEFAULT, + EXEC_TYPE_TRY, + encodeSingle, + encodeBatch, + encodeDelegate, + CALL_TYPE_CALL, + CALL_TYPE_BATCH, + encodeMode, +} = require('../../helpers/erc7579'); +const { selector } = require('../../helpers/methods'); + +const coder = ethers.AbiCoder.defaultAbiCoder(); + +const fixture = async () => { + const [sender] = await ethers.getSigners(); + const utils = await ethers.deployContract('$ERC7579Utils', { value: ethers.parseEther('1') }); + const utilsGlobal = await ethers.deployContract('$ERC7579UtilsGlobalMock'); + const target = await ethers.deployContract('CallReceiverMock'); + const anotherTarget = await ethers.deployContract('CallReceiverMock'); + return { utils, utilsGlobal, target, anotherTarget, sender }; +}; + +describe('ERC7579Utils', function () { + beforeEach(async function () { + Object.assign(this, await loadFixture(fixture)); + }); + + describe('execSingle', function () { + it('calls the target with value', async function () { + const value = 0x012; + const data = encodeSingle(this.target, value, this.target.interface.encodeFunctionData('mockFunction')); + + await expect(this.utils.$execSingle(data, EXEC_TYPE_DEFAULT)).to.emit(this.target, 'MockFunctionCalled'); + + expect(ethers.provider.getBalance(this.target)).to.eventually.equal(value); + }); + + it('calls the target with value and args', async function () { + const value = 0x432; + const data = encodeSingle( + this.target, + value, + this.target.interface.encodeFunctionData('mockFunctionWithArgs', [42, '0x1234']), + ); + + await expect(this.utils.$execSingle(data, EXEC_TYPE_DEFAULT)) + .to.emit(this.target, 'MockFunctionCalledWithArgs') + .withArgs(42, '0x1234'); + + expect(ethers.provider.getBalance(this.target)).to.eventually.equal(value); + }); + + it('reverts when target reverts in default ExecType', async function () { + const value = 0x012; + const data = encodeSingle( + this.target, + value, + this.target.interface.encodeFunctionData('mockFunctionRevertsReason'), + ); + + await expect(this.utils.$execSingle(data, EXEC_TYPE_DEFAULT)).to.be.revertedWith('CallReceiverMock: reverting'); + }); + + it('emits ERC7579TryExecuteFail event when target reverts in try ExecType', async function () { + const value = 0x012; + const data = encodeSingle( + this.target, + value, + this.target.interface.encodeFunctionData('mockFunctionRevertsReason'), + ); + + await expect(this.utils.$execSingle(data, EXEC_TYPE_TRY)) + .to.emit(this.utils, 'ERC7579TryExecuteFail') + .withArgs( + CALL_TYPE_CALL, + ethers.solidityPacked( + ['bytes4', 'bytes'], + [selector('Error(string)'), coder.encode(['string'], ['CallReceiverMock: reverting'])], + ), + ); + }); + + it('reverts with an invalid exec type', async function () { + const value = 0x012; + const data = encodeSingle(this.target, value, this.target.interface.encodeFunctionData('mockFunction')); + + await expect(this.utils.$execSingle(data, '0x03')) + .to.be.revertedWithCustomError(this.utils, 'ERC7579UnsupportedExecType') + .withArgs('0x03'); + }); + }); + + describe('execBatch', function () { + it('calls the targets with value', async function () { + const value1 = 0x012; + const value2 = 0x234; + const data = encodeBatch( + [this.target, value1, this.target.interface.encodeFunctionData('mockFunction')], + [this.anotherTarget, value2, this.anotherTarget.interface.encodeFunctionData('mockFunction')], + ); + + await expect(this.utils.$execBatch(data, EXEC_TYPE_DEFAULT)) + .to.emit(this.target, 'MockFunctionCalled') + .to.emit(this.anotherTarget, 'MockFunctionCalled'); + + expect(ethers.provider.getBalance(this.target)).to.eventually.equal(value1); + expect(ethers.provider.getBalance(this.anotherTarget)).to.eventually.equal(value2); + }); + + it('calls the targets with value and args', async function () { + const value1 = 0x012; + const value2 = 0x234; + const data = encodeBatch( + [this.target, value1, this.target.interface.encodeFunctionData('mockFunctionWithArgs', [42, '0x1234'])], + [ + this.anotherTarget, + value2, + this.anotherTarget.interface.encodeFunctionData('mockFunctionWithArgs', [42, '0x1234']), + ], + ); + + await expect(this.utils.$execBatch(data, EXEC_TYPE_DEFAULT)) + .to.emit(this.target, 'MockFunctionCalledWithArgs') + .to.emit(this.anotherTarget, 'MockFunctionCalledWithArgs'); + + expect(ethers.provider.getBalance(this.target)).to.eventually.equal(value1); + expect(ethers.provider.getBalance(this.anotherTarget)).to.eventually.equal(value2); + }); + + it('reverts when any target reverts in default ExecType', async function () { + const value1 = 0x012; + const value2 = 0x234; + const data = encodeBatch( + [this.target, value1, this.target.interface.encodeFunctionData('mockFunction')], + [this.anotherTarget, value2, this.anotherTarget.interface.encodeFunctionData('mockFunctionRevertsReason')], + ); + + await expect(this.utils.$execBatch(data, EXEC_TYPE_DEFAULT)).to.be.revertedWith('CallReceiverMock: reverting'); + }); + + it('emits ERC7579TryExecuteFail event when any target reverts in try ExecType', async function () { + const value1 = 0x012; + const value2 = 0x234; + const data = encodeBatch( + [this.target, value1, this.target.interface.encodeFunctionData('mockFunction')], + [this.anotherTarget, value2, this.anotherTarget.interface.encodeFunctionData('mockFunctionRevertsReason')], + ); + + await expect(this.utils.$execBatch(data, EXEC_TYPE_TRY)) + .to.emit(this.utils, 'ERC7579TryExecuteFail') + .withArgs( + CALL_TYPE_BATCH, + ethers.solidityPacked( + ['bytes4', 'bytes'], + [selector('Error(string)'), coder.encode(['string'], ['CallReceiverMock: reverting'])], + ), + ); + + // Check balances + expect(ethers.provider.getBalance(this.target)).to.eventually.equal(value1); + expect(ethers.provider.getBalance(this.anotherTarget)).to.eventually.equal(0); + }); + + it('reverts with an invalid exec type', async function () { + const value1 = 0x012; + const value2 = 0x234; + const data = encodeBatch( + [this.target, value1, this.target.interface.encodeFunctionData('mockFunction')], + [this.anotherTarget, value2, this.anotherTarget.interface.encodeFunctionData('mockFunction')], + ); + + await expect(this.utils.$execBatch(data, '0x03')) + .to.be.revertedWithCustomError(this.utils, 'ERC7579UnsupportedExecType') + .withArgs('0x03'); + }); + }); + + describe('execDelegateCall', function () { + it('delegate calls the target', async function () { + const slot = ethers.hexlify(ethers.randomBytes(32)); + const value = ethers.hexlify(ethers.randomBytes(32)); + const data = encodeDelegate( + this.target, + this.target.interface.encodeFunctionData('mockFunctionWritesStorage', [slot, value]), + ); + + expect(ethers.provider.getStorage(this.utils.target, slot)).to.eventually.equal(ethers.ZeroHash); + await this.utils.$execDelegateCall(data, EXEC_TYPE_DEFAULT); + expect(ethers.provider.getStorage(this.utils.target, slot)).to.eventually.equal(value); + }); + + it('reverts when target reverts in default ExecType', async function () { + const data = encodeDelegate(this.target, this.target.interface.encodeFunctionData('mockFunctionRevertsReason')); + await expect(this.utils.$execDelegateCall(data, EXEC_TYPE_DEFAULT)).to.be.revertedWith( + 'CallReceiverMock: reverting', + ); + }); + + it('emits ERC7579TryExecuteFail event when target reverts in try ExecType', async function () { + const data = encodeDelegate(this.target, this.target.interface.encodeFunctionData('mockFunctionRevertsReason')); + await expect(this.utils.$execDelegateCall(data, EXEC_TYPE_TRY)) + .to.emit(this.utils, 'ERC7579TryExecuteFail') + .withArgs( + CALL_TYPE_CALL, + ethers.solidityPacked( + ['bytes4', 'bytes'], + [selector('Error(string)'), coder.encode(['string'], ['CallReceiverMock: reverting'])], + ), + ); + }); + + it('reverts with an invalid exec type', async function () { + const data = encodeDelegate(this.target, this.target.interface.encodeFunctionData('mockFunction')); + await expect(this.utils.$execDelegateCall(data, '0x03')) + .to.be.revertedWithCustomError(this.utils, 'ERC7579UnsupportedExecType') + .withArgs('0x03'); + }); + }); + + it('encodes Mode', async function () { + const callType = CALL_TYPE_BATCH; + const execType = EXEC_TYPE_TRY; + const selector = '0x12345678'; + const payload = ethers.toBeHex(0, 22); + + expect(this.utils.$encodeMode(callType, execType, selector, payload)).to.eventually.equal( + encodeMode({ + callType, + execType, + selector, + payload, + }), + ); + }); + + it('decodes Mode', async function () { + const callType = CALL_TYPE_BATCH; + const execType = EXEC_TYPE_TRY; + const selector = '0x12345678'; + const payload = ethers.toBeHex(0, 22); + + expect( + this.utils.$decodeMode( + encodeMode({ + callType, + execType, + selector, + payload, + }), + ), + ).to.eventually.deep.equal([callType, execType, selector, payload]); + }); + + it('encodes single', async function () { + const target = this.target; + const value = 0x123; + const data = '0x12345678'; + + expect(this.utils.$encodeSingle(target, value, data)).to.eventually.equal(encodeSingle(target, value, data)); + }); + + it('decodes single', async function () { + const target = this.target; + const value = 0x123; + const data = '0x12345678'; + + expect(this.utils.$decodeSingle(encodeSingle(target, value, data))).to.eventually.deep.equal([ + target.target, + value, + data, + ]); + }); + + it('encodes batch', async function () { + const entries = [ + [this.target, 0x123, '0x12345678'], + [this.anotherTarget, 0x456, '0x12345678'], + ]; + + expect(this.utils.$encodeBatch(entries)).to.eventually.equal(encodeBatch(...entries)); + }); + + it('decodes batch', async function () { + const entries = [ + [this.target.target, 0x123, '0x12345678'], + [this.anotherTarget.target, 0x456, '0x12345678'], + ]; + + expect(this.utils.$decodeBatch(encodeBatch(...entries))).to.eventually.deep.equal(entries); + }); + + it('encodes delegate', async function () { + const target = this.target; + const data = '0x12345678'; + + expect(this.utils.$encodeDelegate(target, data)).to.eventually.equal(encodeDelegate(target, data)); + }); + + it('decodes delegate', async function () { + const target = this.target; + const data = '0x12345678'; + + expect(this.utils.$decodeDelegate(encodeDelegate(target, data))).to.eventually.deep.equal([target.target, data]); + }); + + describe('global', function () { + describe('eqCallTypeGlobal', function () { + it('returns true if both call types are equal', async function () { + expect(this.utilsGlobal.$eqCallTypeGlobal(CALL_TYPE_BATCH, CALL_TYPE_BATCH)).to.eventually.be.true; + }); + + it('returns false if both call types are different', async function () { + expect(this.utilsGlobal.$eqCallTypeGlobal(CALL_TYPE_CALL, CALL_TYPE_BATCH)).to.eventually.be.false; + }); + }); + + describe('eqExecTypeGlobal', function () { + it('returns true if both exec types are equal', async function () { + expect(this.utilsGlobal.$eqExecTypeGlobal(EXEC_TYPE_TRY, EXEC_TYPE_TRY)).to.eventually.be.true; + }); + + it('returns false if both exec types are different', async function () { + expect(this.utilsGlobal.$eqExecTypeGlobal(EXEC_TYPE_DEFAULT, EXEC_TYPE_TRY)).to.eventually.be.false; + }); + }); + + describe('eqModeSelectorGlobal', function () { + it('returns true if both selectors are equal', async function () { + expect(this.utilsGlobal.$eqModeSelectorGlobal('0x12345678', '0x12345678')).to.eventually.be.true; + }); + + it('returns false if both selectors are different', async function () { + expect(this.utilsGlobal.$eqModeSelectorGlobal('0x12345678', '0x87654321')).to.eventually.be.false; + }); + }); + + describe('eqModePayloadGlobal', function () { + it('returns true if both payloads are equal', async function () { + expect(this.utilsGlobal.$eqModePayloadGlobal(ethers.toBeHex(0, 22), ethers.toBeHex(0, 22))).to.eventually.be + .true; + }); + + it('returns false if both payloads are different', async function () { + expect(this.utilsGlobal.$eqModePayloadGlobal(ethers.toBeHex(0, 22), ethers.toBeHex(1, 22))).to.eventually.be + .false; + }); + }); + }); +}); diff --git a/test/bin/EntryPoint070.abi b/test/bin/EntryPoint070.abi new file mode 100644 index 000000000..3f3b1d6e5 --- /dev/null +++ b/test/bin/EntryPoint070.abi @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"bool","name":"success","type":"bool"},{"internalType":"bytes","name":"ret","type":"bytes"}],"name":"DelegateAndRevert","type":"error"},{"inputs":[{"internalType":"uint256","name":"opIndex","type":"uint256"},{"internalType":"string","name":"reason","type":"string"}],"name":"FailedOp","type":"error"},{"inputs":[{"internalType":"uint256","name":"opIndex","type":"uint256"},{"internalType":"string","name":"reason","type":"string"},{"internalType":"bytes","name":"inner","type":"bytes"}],"name":"FailedOpWithRevert","type":"error"},{"inputs":[{"internalType":"bytes","name":"returnData","type":"bytes"}],"name":"PostOpReverted","type":"error"},{"inputs":[],"name":"ReentrancyGuardReentrantCall","type":"error"},{"inputs":[{"internalType":"address","name":"sender","type":"address"}],"name":"SenderAddressResult","type":"error"},{"inputs":[{"internalType":"address","name":"aggregator","type":"address"}],"name":"SignatureValidationFailed","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"userOpHash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"address","name":"factory","type":"address"},{"indexed":false,"internalType":"address","name":"paymaster","type":"address"}],"name":"AccountDeployed","type":"event"},{"anonymous":false,"inputs":[],"name":"BeforeExecution","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint256","name":"totalDeposit","type":"uint256"}],"name":"Deposited","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"userOpHash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"uint256","name":"nonce","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"revertReason","type":"bytes"}],"name":"PostOpRevertReason","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"aggregator","type":"address"}],"name":"SignatureAggregatorChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint256","name":"totalStaked","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"unstakeDelaySec","type":"uint256"}],"name":"StakeLocked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint256","name":"withdrawTime","type":"uint256"}],"name":"StakeUnlocked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"address","name":"withdrawAddress","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"StakeWithdrawn","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"userOpHash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"address","name":"paymaster","type":"address"},{"indexed":false,"internalType":"uint256","name":"nonce","type":"uint256"},{"indexed":false,"internalType":"bool","name":"success","type":"bool"},{"indexed":false,"internalType":"uint256","name":"actualGasCost","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"actualGasUsed","type":"uint256"}],"name":"UserOperationEvent","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"userOpHash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"uint256","name":"nonce","type":"uint256"}],"name":"UserOperationPrefundTooLow","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"userOpHash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"uint256","name":"nonce","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"revertReason","type":"bytes"}],"name":"UserOperationRevertReason","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"address","name":"withdrawAddress","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Withdrawn","type":"event"},{"inputs":[{"internalType":"uint32","name":"unstakeDelaySec","type":"uint32"}],"name":"addStake","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"target","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"delegateAndRevert","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"depositTo","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"deposits","outputs":[{"internalType":"uint256","name":"deposit","type":"uint256"},{"internalType":"bool","name":"staked","type":"bool"},{"internalType":"uint112","name":"stake","type":"uint112"},{"internalType":"uint32","name":"unstakeDelaySec","type":"uint32"},{"internalType":"uint48","name":"withdrawTime","type":"uint48"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"getDepositInfo","outputs":[{"components":[{"internalType":"uint256","name":"deposit","type":"uint256"},{"internalType":"bool","name":"staked","type":"bool"},{"internalType":"uint112","name":"stake","type":"uint112"},{"internalType":"uint32","name":"unstakeDelaySec","type":"uint32"},{"internalType":"uint48","name":"withdrawTime","type":"uint48"}],"internalType":"struct IStakeManager.DepositInfo","name":"info","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint192","name":"key","type":"uint192"}],"name":"getNonce","outputs":[{"internalType":"uint256","name":"nonce","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"initCode","type":"bytes"}],"name":"getSenderAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"bytes","name":"initCode","type":"bytes"},{"internalType":"bytes","name":"callData","type":"bytes"},{"internalType":"bytes32","name":"accountGasLimits","type":"bytes32"},{"internalType":"uint256","name":"preVerificationGas","type":"uint256"},{"internalType":"bytes32","name":"gasFees","type":"bytes32"},{"internalType":"bytes","name":"paymasterAndData","type":"bytes"},{"internalType":"bytes","name":"signature","type":"bytes"}],"internalType":"struct PackedUserOperation","name":"userOp","type":"tuple"}],"name":"getUserOpHash","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"components":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"bytes","name":"initCode","type":"bytes"},{"internalType":"bytes","name":"callData","type":"bytes"},{"internalType":"bytes32","name":"accountGasLimits","type":"bytes32"},{"internalType":"uint256","name":"preVerificationGas","type":"uint256"},{"internalType":"bytes32","name":"gasFees","type":"bytes32"},{"internalType":"bytes","name":"paymasterAndData","type":"bytes"},{"internalType":"bytes","name":"signature","type":"bytes"}],"internalType":"struct PackedUserOperation[]","name":"userOps","type":"tuple[]"},{"internalType":"contract IAggregator","name":"aggregator","type":"address"},{"internalType":"bytes","name":"signature","type":"bytes"}],"internalType":"struct IEntryPoint.UserOpsPerAggregator[]","name":"opsPerAggregator","type":"tuple[]"},{"internalType":"address payable","name":"beneficiary","type":"address"}],"name":"handleAggregatedOps","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"bytes","name":"initCode","type":"bytes"},{"internalType":"bytes","name":"callData","type":"bytes"},{"internalType":"bytes32","name":"accountGasLimits","type":"bytes32"},{"internalType":"uint256","name":"preVerificationGas","type":"uint256"},{"internalType":"bytes32","name":"gasFees","type":"bytes32"},{"internalType":"bytes","name":"paymasterAndData","type":"bytes"},{"internalType":"bytes","name":"signature","type":"bytes"}],"internalType":"struct PackedUserOperation[]","name":"ops","type":"tuple[]"},{"internalType":"address payable","name":"beneficiary","type":"address"}],"name":"handleOps","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint192","name":"key","type":"uint192"}],"name":"incrementNonce","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"callData","type":"bytes"},{"components":[{"components":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"verificationGasLimit","type":"uint256"},{"internalType":"uint256","name":"callGasLimit","type":"uint256"},{"internalType":"uint256","name":"paymasterVerificationGasLimit","type":"uint256"},{"internalType":"uint256","name":"paymasterPostOpGasLimit","type":"uint256"},{"internalType":"uint256","name":"preVerificationGas","type":"uint256"},{"internalType":"address","name":"paymaster","type":"address"},{"internalType":"uint256","name":"maxFeePerGas","type":"uint256"},{"internalType":"uint256","name":"maxPriorityFeePerGas","type":"uint256"}],"internalType":"struct EntryPoint.MemoryUserOp","name":"mUserOp","type":"tuple"},{"internalType":"bytes32","name":"userOpHash","type":"bytes32"},{"internalType":"uint256","name":"prefund","type":"uint256"},{"internalType":"uint256","name":"contextOffset","type":"uint256"},{"internalType":"uint256","name":"preOpGas","type":"uint256"}],"internalType":"struct EntryPoint.UserOpInfo","name":"opInfo","type":"tuple"},{"internalType":"bytes","name":"context","type":"bytes"}],"name":"innerHandleOp","outputs":[{"internalType":"uint256","name":"actualGasCost","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint192","name":"","type":"uint192"}],"name":"nonceSequenceNumber","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"unlockStake","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address payable","name":"withdrawAddress","type":"address"}],"name":"withdrawStake","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address payable","name":"withdrawAddress","type":"address"},{"internalType":"uint256","name":"withdrawAmount","type":"uint256"}],"name":"withdrawTo","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}] \ No newline at end of file diff --git a/test/bin/EntryPoint070.bytecode b/test/bin/EntryPoint070.bytecode new file mode 100644 index 000000000..fce261af5 Binary files /dev/null and b/test/bin/EntryPoint070.bytecode differ diff --git a/test/bin/SenderCreator070.abi b/test/bin/SenderCreator070.abi new file mode 100644 index 000000000..0a1f0e4fb --- /dev/null +++ b/test/bin/SenderCreator070.abi @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"bytes","name":"initCode","type":"bytes"}],"name":"createSender","outputs":[{"internalType":"address","name":"sender","type":"address"}],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/test/bin/SenderCreator070.bytecode b/test/bin/SenderCreator070.bytecode new file mode 100644 index 000000000..8344c2028 Binary files /dev/null and b/test/bin/SenderCreator070.bytecode differ diff --git a/test/governance/extensions/GovernorCountingOverridable.test.js b/test/governance/extensions/GovernorCountingOverridable.test.js new file mode 100644 index 000000000..32ee47439 --- /dev/null +++ b/test/governance/extensions/GovernorCountingOverridable.test.js @@ -0,0 +1,346 @@ +const { ethers } = require('hardhat'); +const { expect } = require('chai'); +const { loadFixture, mine } = require('@nomicfoundation/hardhat-network-helpers'); + +const { GovernorHelper } = require('../../helpers/governance'); +const { getDomain, OverrideBallot } = require('../../helpers/eip712'); +const { VoteType } = require('../../helpers/enums'); + +const TOKENS = [ + { Token: '$ERC20VotesExtendedMock', mode: 'blocknumber' }, + { Token: '$ERC20VotesExtendedTimestampMock', mode: 'timestamp' }, +]; + +const name = 'Override Governor'; +const version = '1'; +const tokenName = 'MockToken'; +const tokenSymbol = 'MTKN'; +const tokenSupply = ethers.parseEther('100'); +const votingDelay = 4n; +const votingPeriod = 16n; +const value = ethers.parseEther('1'); + +const signBallot = account => (contract, message) => + getDomain(contract).then(domain => account.signTypedData(domain, { OverrideBallot }, message)); + +describe('GovernorCountingOverridable', function () { + for (const { Token, mode } of TOKENS) { + const fixture = async () => { + const [owner, proposer, voter1, voter2, voter3, voter4, other] = await ethers.getSigners(); + const receiver = await ethers.deployContract('CallReceiverMock'); + + const token = await ethers.deployContract(Token, [tokenName, tokenSymbol, tokenName, version]); + const mock = await ethers.deployContract('$GovernorCountingOverridableMock', [ + name, // name + votingDelay, // initialVotingDelay + votingPeriod, // initialVotingPeriod + 0n, // initialProposalThreshold + token, // tokenAddress + 10n, // quorumNumeratorValue + ]); + + await owner.sendTransaction({ to: mock, value }); + await token.$_mint(owner, tokenSupply); + + const helper = new GovernorHelper(mock, mode); + await helper.connect(owner).delegate({ token, to: voter1, value: ethers.parseEther('10') }); + await helper.connect(owner).delegate({ token, to: voter2, value: ethers.parseEther('7') }); + await helper.connect(owner).delegate({ token, to: voter3, value: ethers.parseEther('5') }); + await helper.connect(owner).delegate({ token, to: voter4, value: ethers.parseEther('2') }); + + return { owner, proposer, voter1, voter2, voter3, voter4, other, receiver, token, mock, helper }; + }; + + describe(`using ${Token}`, function () { + beforeEach(async function () { + Object.assign(this, await loadFixture(fixture)); + + // default proposal + this.proposal = this.helper.setProposal( + [ + { + target: this.receiver.target, + value, + data: this.receiver.interface.encodeFunctionData('mockFunction'), + }, + ], + '', + ); + }); + + it('deployment check', async function () { + expect(await this.mock.name()).to.equal(name); + expect(await this.mock.token()).to.equal(this.token); + expect(await this.mock.votingDelay()).to.equal(votingDelay); + expect(await this.mock.votingPeriod()).to.equal(votingPeriod); + expect(await this.mock.COUNTING_MODE()).to.equal('support=bravo,override&quorum=for,abstain&overridable=true'); + }); + + it('nominal is unaffected', async function () { + await this.helper.connect(this.proposer).propose(); + await this.helper.waitForSnapshot(); + await this.helper.connect(this.voter1).vote({ support: VoteType.For, reason: 'This is nice' }); + await this.helper.connect(this.voter2).vote({ support: VoteType.For }); + await this.helper.connect(this.voter3).vote({ support: VoteType.Against }); + await this.helper.connect(this.voter4).vote({ support: VoteType.Abstain }); + await this.helper.waitForDeadline(); + await this.helper.execute(); + + expect(await this.mock.hasVoted(this.proposal.id, this.owner)).to.be.false; + expect(await this.mock.hasVoted(this.proposal.id, this.voter1)).to.be.true; + expect(await this.mock.hasVoted(this.proposal.id, this.voter2)).to.be.true; + expect(await ethers.provider.getBalance(this.mock)).to.equal(0n); + expect(await ethers.provider.getBalance(this.receiver)).to.equal(value); + }); + + describe('cast override vote', async function () { + beforeEach(async function () { + // user 1 -(delegate 10 tokens)-> user 2 + // user 2 -(delegate 7 tokens)-> user 2 + // user 3 -(delegate 5 tokens)-> user 1 + // user 4 -(delegate 2 tokens)-> user 2 + await this.token.connect(this.voter1).delegate(this.voter2); + await this.token.connect(this.voter3).delegate(this.voter1); + await this.token.connect(this.voter4).delegate(this.voter2); + await mine(); + + await this.helper.connect(this.proposer).propose(); + await this.helper.waitForSnapshot(); + }); + + it('override after delegate vote', async function () { + expect(await this.mock.hasVoted(this.helper.id, this.voter1)).to.be.false; + expect(await this.mock.hasVoted(this.helper.id, this.voter2)).to.be.false; + expect(await this.mock.hasVoted(this.helper.id, this.voter3)).to.be.false; + expect(await this.mock.hasVoted(this.helper.id, this.voter4)).to.be.false; + expect(await this.mock.hasVotedOverride(this.helper.id, this.voter1)).to.be.false; + expect(await this.mock.hasVotedOverride(this.helper.id, this.voter2)).to.be.false; + expect(await this.mock.hasVotedOverride(this.helper.id, this.voter3)).to.be.false; + expect(await this.mock.hasVotedOverride(this.helper.id, this.voter4)).to.be.false; + + // user 2 votes + + await expect(this.helper.connect(this.voter2).vote({ support: VoteType.For })) + .to.emit(this.mock, 'VoteCast') + .withArgs(this.voter2, this.helper.id, VoteType.For, ethers.parseEther('19'), ''); // 10 + 7 + 2 + + expect(await this.mock.proposalVotes(this.helper.id)).to.deep.eq( + [0, 19, 0].map(x => ethers.parseEther(x.toString())), + ); + expect(await this.mock.hasVoted(this.helper.id, this.voter2)).to.be.true; + expect(await this.mock.hasVotedOverride(this.helper.id, this.voter2)).to.be.false; + + // user 1 overrides after user 2 votes + + const reason = "disagree with user 2's decision"; + await expect(this.mock.connect(this.voter1).castOverrideVote(this.helper.id, VoteType.Against, reason)) + .to.emit(this.mock, 'OverrideVoteCast') + .withArgs(this.voter1, this.helper.id, VoteType.Against, ethers.parseEther('10'), reason) + .to.emit(this.mock, 'VoteReduced') + .withArgs(this.voter2, this.helper.id, VoteType.For, ethers.parseEther('10')); + + expect(await this.mock.proposalVotes(this.helper.id)).to.deep.eq( + [10, 9, 0].map(x => ethers.parseEther(x.toString())), + ); + expect(await this.mock.hasVoted(this.helper.id, this.voter1)).to.be.false; + expect(await this.mock.hasVotedOverride(this.helper.id, this.voter1)).to.be.true; + }); + + it('override before delegate vote', async function () { + expect(await this.mock.hasVoted(this.helper.id, this.voter1)).to.be.false; + expect(await this.mock.hasVoted(this.helper.id, this.voter2)).to.be.false; + expect(await this.mock.hasVoted(this.helper.id, this.voter3)).to.be.false; + expect(await this.mock.hasVoted(this.helper.id, this.voter4)).to.be.false; + expect(await this.mock.hasVotedOverride(this.helper.id, this.voter1)).to.be.false; + expect(await this.mock.hasVotedOverride(this.helper.id, this.voter2)).to.be.false; + expect(await this.mock.hasVotedOverride(this.helper.id, this.voter3)).to.be.false; + expect(await this.mock.hasVotedOverride(this.helper.id, this.voter4)).to.be.false; + + // user 1 overrides before user 2 votes + + const reason = 'voter 2 is not voting'; + await expect(this.mock.connect(this.voter1).castOverrideVote(this.helper.id, VoteType.Against, reason)) + .to.emit(this.mock, 'OverrideVoteCast') + .withArgs(this.voter1, this.helper.id, VoteType.Against, ethers.parseEther('10'), reason) + .to.not.emit(this.mock, 'VoteReduced'); + + expect(await this.mock.proposalVotes(this.helper.id)).to.deep.eq( + [10, 0, 0].map(x => ethers.parseEther(x.toString())), + ); + expect(await this.mock.hasVoted(this.helper.id, this.voter1)).to.be.false; + expect(await this.mock.hasVotedOverride(this.helper.id, this.voter1)).to.be.true; + + // user 2 votes + + await expect(this.helper.connect(this.voter2).vote({ support: VoteType.For })) + .to.emit(this.mock, 'VoteCast') + .withArgs(this.voter2, this.helper.id, VoteType.For, ethers.parseEther('9'), ''); // 7 + 2 + + expect(await this.mock.proposalVotes(this.helper.id)).to.deep.eq( + [10, 9, 0].map(x => ethers.parseEther(x.toString())), + ); + expect(await this.mock.hasVoted(this.helper.id, this.voter2)).to.be.true; + expect(await this.mock.hasVotedOverride(this.helper.id, this.voter2)).to.be.false; + }); + + it('override before and after delegate vote', async function () { + expect(await this.mock.hasVoted(this.helper.id, this.voter1)).to.be.false; + expect(await this.mock.hasVoted(this.helper.id, this.voter2)).to.be.false; + expect(await this.mock.hasVoted(this.helper.id, this.voter3)).to.be.false; + expect(await this.mock.hasVoted(this.helper.id, this.voter4)).to.be.false; + expect(await this.mock.hasVotedOverride(this.helper.id, this.voter1)).to.be.false; + expect(await this.mock.hasVotedOverride(this.helper.id, this.voter2)).to.be.false; + expect(await this.mock.hasVotedOverride(this.helper.id, this.voter3)).to.be.false; + expect(await this.mock.hasVotedOverride(this.helper.id, this.voter4)).to.be.false; + + // user 1 overrides before user 2 votes + + const reason = 'voter 2 is not voting'; + await expect(this.mock.connect(this.voter1).castOverrideVote(this.helper.id, VoteType.Against, reason)) + .to.emit(this.mock, 'OverrideVoteCast') + .withArgs(this.voter1, this.helper.id, VoteType.Against, ethers.parseEther('10'), reason) + .to.not.emit(this.mock, 'VoteReduced'); + + expect(await this.mock.proposalVotes(this.helper.id)).to.deep.eq( + [10, 0, 0].map(x => ethers.parseEther(x.toString())), + ); + expect(await this.mock.hasVoted(this.helper.id, this.voter1)).to.be.false; + expect(await this.mock.hasVotedOverride(this.helper.id, this.voter1)).to.be.true; + + // user 2 votes + + await expect(this.helper.connect(this.voter2).vote({ support: VoteType.For })) + .to.emit(this.mock, 'VoteCast') + .withArgs(this.voter2, this.helper.id, VoteType.For, ethers.parseEther('9'), ''); // 7 + 2 + + expect(await this.mock.proposalVotes(this.helper.id)).to.deep.eq( + [10, 9, 0].map(x => ethers.parseEther(x.toString())), + ); + expect(await this.mock.hasVoted(this.helper.id, this.voter2)).to.be.true; + expect(await this.mock.hasVotedOverride(this.helper.id, this.voter2)).to.be.false; + + // User 4 overrides after user 2 votes + + const reason2 = "disagree with user 2's decision"; + await expect(this.mock.connect(this.voter4).castOverrideVote(this.helper.id, VoteType.Abstain, reason2)) + .to.emit(this.mock, 'OverrideVoteCast') + .withArgs(this.voter4, this.helper.id, VoteType.Abstain, ethers.parseEther('2'), reason2) + .to.emit(this.mock, 'VoteReduced') + .withArgs(this.voter2, this.helper.id, VoteType.For, ethers.parseEther('2')); + + expect(await this.mock.proposalVotes(this.helper.id)).to.deep.eq( + [10, 7, 2].map(x => ethers.parseEther(x.toString())), + ); + expect(await this.mock.hasVoted(this.helper.id, this.voter4)).to.be.false; + expect(await this.mock.hasVotedOverride(this.helper.id, this.voter4)).to.be.true; + }); + + it('vote (with delegated balance) and override (with self balance) are independent', async function () { + expect(await this.mock.proposalVotes(this.helper.id)).to.deep.eq( + [0, 0, 0].map(x => ethers.parseEther(x.toString())), + ); + expect(await this.mock.hasVoted(this.helper.id, this.voter1)).to.be.false; + expect(await this.mock.hasVotedOverride(this.helper.id, this.voter1)).to.be.false; + + // user 1 votes with delegated weight from user 3 + await expect(this.mock.connect(this.voter1).castVote(this.helper.id, VoteType.For)) + .to.emit(this.mock, 'VoteCast') + .withArgs(this.voter1, this.helper.id, VoteType.For, ethers.parseEther('5'), ''); + + // user 1 cast an override vote with its own balance (delegated to user 2) + await expect(this.mock.connect(this.voter1).castOverrideVote(this.helper.id, VoteType.Against, '')) + .to.emit(this.mock, 'OverrideVoteCast') + .withArgs(this.voter1, this.helper.id, VoteType.Against, ethers.parseEther('10'), ''); + + expect(await this.mock.proposalVotes(this.helper.id)).to.deep.eq( + [10, 5, 0].map(x => ethers.parseEther(x.toString())), + ); + expect(await this.mock.hasVoted(this.helper.id, this.voter1)).to.be.true; + expect(await this.mock.hasVotedOverride(this.helper.id, this.voter1)).to.be.true; + }); + + it('can not override vote twice', async function () { + await expect(this.mock.connect(this.voter1).castOverrideVote(this.helper.id, VoteType.Against, '')) + .to.emit(this.mock, 'OverrideVoteCast') + .withArgs(this.voter1, this.helper.id, VoteType.Against, ethers.parseEther('10'), ''); + await expect(this.mock.connect(this.voter1).castOverrideVote(this.helper.id, VoteType.Abstain, '')) + .to.be.revertedWithCustomError(this.mock, 'GovernorAlreadyOverridenVote') + .withArgs(this.voter1.address); + }); + + it('can not vote twice', async function () { + await expect(this.mock.connect(this.voter1).castVote(this.helper.id, VoteType.Against)) + .to.emit(this.mock, 'VoteCast') + .withArgs(this.voter1, this.helper.id, VoteType.Against, ethers.parseEther('5'), ''); + await expect(this.mock.connect(this.voter1).castVote(this.helper.id, VoteType.Abstain)) + .to.be.revertedWithCustomError(this.mock, 'GovernorAlreadyCastVote') + .withArgs(this.voter1.address); + }); + + describe('invalid vote type', function () { + it('override vote', async function () { + await expect( + this.mock.connect(this.voter1).castOverrideVote(this.helper.id, 3, ''), + ).to.be.revertedWithCustomError(this.mock, 'GovernorInvalidVoteType'); + }); + + it('traditional vote', async function () { + await expect(this.mock.connect(this.voter1).castVote(this.helper.id, 3)).to.be.revertedWithCustomError( + this.mock, + 'GovernorInvalidVoteType', + ); + }); + }); + + describe('by signature', function () { + it('EOA signature', async function () { + const nonce = await this.mock.nonces(this.voter1); + + await expect( + this.helper.overrideVote({ + support: VoteType.For, + voter: this.voter1.address, + nonce, + signature: signBallot(this.voter1), + }), + ) + .to.emit(this.mock, 'OverrideVoteCast') + .withArgs(this.voter1, this.helper.id, VoteType.For, ethers.parseEther('10'), ''); + + expect(await this.mock.hasVotedOverride(this.proposal.id, this.voter1)).to.be.true; + }); + + it('revert if signature does not match signer', async function () { + const nonce = await this.mock.nonces(this.voter1); + + const voteParams = { + support: VoteType.For, + voter: this.voter2.address, + nonce, + signature: signBallot(this.voter1), + }; + + await expect(this.helper.overrideVote(voteParams)) + .to.be.revertedWithCustomError(this.mock, 'GovernorInvalidSignature') + .withArgs(voteParams.voter); + }); + + it('revert if vote nonce is incorrect', async function () { + const nonce = await this.mock.nonces(this.voter1); + + const voteParams = { + support: VoteType.For, + voter: this.voter1.address, + nonce: nonce + 1n, + signature: signBallot(this.voter1), + }; + + await expect(this.helper.overrideVote(voteParams)) + .to.be.revertedWithCustomError(this.mock, 'GovernorInvalidSignature') + .withArgs(voteParams.voter); + }); + }); + }); + }); + } +}); diff --git a/test/governance/utils/ERC6372.behavior.js b/test/governance/utils/ERC6372.behavior.js index abcae43c7..32f27b59a 100644 --- a/test/governance/utils/ERC6372.behavior.js +++ b/test/governance/utils/ERC6372.behavior.js @@ -1,21 +1,24 @@ const { expect } = require('chai'); - const time = require('../../helpers/time'); function shouldBehaveLikeERC6372(mode = 'blocknumber') { - describe('should implement ERC-6372', function () { + describe(`ERC-6372 behavior in ${mode} mode`, function () { beforeEach(async function () { this.mock = this.mock ?? this.token ?? this.votes; }); - it('clock is correct', async function () { - expect(await this.mock.clock()).to.equal(await time.clock[mode]()); + it('should have a correct clock value', async function () { + const currentClock = await this.mock.clock(); + const expectedClock = await time.clock[mode](); + expect(currentClock).to.equal(expectedClock, `Clock mismatch in ${mode} mode`); }); - it('CLOCK_MODE is correct', async function () { - const params = new URLSearchParams(await this.mock.CLOCK_MODE()); - expect(params.get('mode')).to.equal(mode); - expect(params.get('from')).to.equal(mode == 'blocknumber' ? 'default' : null); + it('should have the correct CLOCK_MODE parameters', async function () { + const clockModeParams = new URLSearchParams(await this.mock.CLOCK_MODE()); + const expectedFromValue = mode === 'blocknumber' ? 'default' : null; + + expect(clockModeParams.get('mode')).to.equal(mode, `Expected mode to be ${mode}`); + expect(clockModeParams.get('from')).to.equal(expectedFromValue, `Expected 'from' to be ${expectedFromValue}`); }); }); } diff --git a/test/governance/utils/VotesExtended.test.js b/test/governance/utils/VotesExtended.test.js new file mode 100644 index 000000000..4a66ef274 --- /dev/null +++ b/test/governance/utils/VotesExtended.test.js @@ -0,0 +1,152 @@ +const { ethers } = require('hardhat'); +const { expect } = require('chai'); +const { loadFixture, mine } = require('@nomicfoundation/hardhat-network-helpers'); + +const { sum } = require('../../helpers/math'); +const { zip } = require('../../helpers/iterate'); +const time = require('../../helpers/time'); + +const { shouldBehaveLikeVotes } = require('./Votes.behavior'); + +const MODES = { + blocknumber: '$VotesExtendedMock', + timestamp: '$VotesExtendedTimestampMock', +}; + +const AMOUNTS = [ethers.parseEther('10000000'), 10n, 20n]; + +describe('VotesExtended', function () { + for (const [mode, artifact] of Object.entries(MODES)) { + const fixture = async () => { + const accounts = await ethers.getSigners(); + + const amounts = Object.fromEntries( + zip( + accounts.slice(0, AMOUNTS.length).map(({ address }) => address), + AMOUNTS, + ), + ); + + const name = 'Override Votes'; + const version = '1'; + const votes = await ethers.deployContract(artifact, [name, version]); + + return { accounts, amounts, votes, name, version }; + }; + + describe(`vote with ${mode}`, function () { + beforeEach(async function () { + Object.assign(this, await loadFixture(fixture)); + }); + + shouldBehaveLikeVotes(AMOUNTS, { mode, fungible: true }); + + it('starts with zero votes', async function () { + expect(await this.votes.getTotalSupply()).to.equal(0n); + }); + + describe('performs voting operations', function () { + beforeEach(async function () { + this.txs = []; + for (const [account, amount] of Object.entries(this.amounts)) { + this.txs.push(await this.votes.$_mint(account, amount)); + } + }); + + it('reverts if block number >= current block', async function () { + const lastTxTimepoint = await time.clockFromReceipt[mode](this.txs.at(-1)); + const clock = await this.votes.clock(); + await expect(this.votes.getPastTotalSupply(lastTxTimepoint)) + .to.be.revertedWithCustomError(this.votes, 'ERC5805FutureLookup') + .withArgs(lastTxTimepoint, clock); + }); + + it('delegates', async function () { + expect(await this.votes.getVotes(this.accounts[0])).to.equal(0n); + expect(await this.votes.getVotes(this.accounts[1])).to.equal(0n); + expect(await this.votes.delegates(this.accounts[0])).to.equal(ethers.ZeroAddress); + expect(await this.votes.delegates(this.accounts[1])).to.equal(ethers.ZeroAddress); + + await this.votes.delegate(this.accounts[0], ethers.Typed.address(this.accounts[0])); + + expect(await this.votes.getVotes(this.accounts[0])).to.equal(this.amounts[this.accounts[0].address]); + expect(await this.votes.getVotes(this.accounts[1])).to.equal(0n); + expect(await this.votes.delegates(this.accounts[0])).to.equal(this.accounts[0]); + expect(await this.votes.delegates(this.accounts[1])).to.equal(ethers.ZeroAddress); + + await this.votes.delegate(this.accounts[1], ethers.Typed.address(this.accounts[0])); + + expect(await this.votes.getVotes(this.accounts[0])).to.equal( + this.amounts[this.accounts[0].address] + this.amounts[this.accounts[1].address], + ); + expect(await this.votes.getVotes(this.accounts[1])).to.equal(0n); + expect(await this.votes.delegates(this.accounts[0])).to.equal(this.accounts[0]); + expect(await this.votes.delegates(this.accounts[1])).to.equal(this.accounts[0]); + }); + + it('cross delegates', async function () { + await this.votes.delegate(this.accounts[0], ethers.Typed.address(this.accounts[1])); + await this.votes.delegate(this.accounts[1], ethers.Typed.address(this.accounts[0])); + + expect(await this.votes.getVotes(this.accounts[0])).to.equal(this.amounts[this.accounts[1].address]); + expect(await this.votes.getVotes(this.accounts[1])).to.equal(this.amounts[this.accounts[0].address]); + }); + + it('returns total amount of votes', async function () { + const totalSupply = sum(...Object.values(this.amounts)); + expect(await this.votes.getTotalSupply()).to.equal(totalSupply); + }); + }); + }); + + describe(`checkpoint delegates with ${mode}`, function () { + beforeEach(async function () { + Object.assign(this, await loadFixture(fixture)); + }); + + it('checkpoint delegates', async function () { + const tx = await this.votes.delegate(this.accounts[0], ethers.Typed.address(this.accounts[1])); + const timepoint = await time.clockFromReceipt[mode](tx); + await mine(2); + + expect(await this.votes.getPastDelegate(this.accounts[0], timepoint - 1n)).to.equal(ethers.ZeroAddress); + expect(await this.votes.getPastDelegate(this.accounts[0], timepoint)).to.equal(this.accounts[1].address); + expect(await this.votes.getPastDelegate(this.accounts[0], timepoint + 1n)).to.equal(this.accounts[1].address); + }); + + it('reverts if current timepoint <= timepoint', async function () { + const tx = await this.votes.delegate(this.accounts[0], ethers.Typed.address(this.accounts[1])); + const timepoint = await time.clockFromReceipt[mode](tx); + + await expect(this.votes.getPastDelegate(this.accounts[0], timepoint + 1n)) + .to.be.revertedWithCustomError(this.votes, 'ERC5805FutureLookup') + .withArgs(timepoint + 1n, timepoint); + }); + }); + + describe(`checkpoint balances with ${mode}`, function () { + beforeEach(async function () { + Object.assign(this, await loadFixture(fixture)); + }); + + it('checkpoint balances', async function () { + const tx = await this.votes.$_mint(this.accounts[0].address, 100n); + const timepoint = await time.clockFromReceipt[mode](tx); + await mine(2); + + expect(await this.votes.getPastBalanceOf(this.accounts[0].address, timepoint - 1n)).to.equal(0n); + expect(await this.votes.getPastBalanceOf(this.accounts[0].address, timepoint)).to.equal(100n); + expect(await this.votes.getPastBalanceOf(this.accounts[0].address, timepoint + 1n)).to.equal(100n); + }); + + it('reverts if current timepoint <= timepoint', async function () { + const tx = await this.votes.$_mint(this.accounts[0].address, 100n); + const timepoint = await time.clockFromReceipt[mode](tx); + + await expect(this.votes.getPastBalanceOf(this.accounts[0], timepoint + 1n)) + .to.be.revertedWithCustomError(this.votes, 'ERC5805FutureLookup') + .withArgs(timepoint + 1n, timepoint); + }); + }); + } +}); diff --git a/test/helpers/chains.js b/test/helpers/chains.js new file mode 100644 index 000000000..3711a8125 --- /dev/null +++ b/test/helpers/chains.js @@ -0,0 +1,109 @@ +// NOTE: this file defines some examples of CAIP-2 and CAIP-10 identifiers. +// The following listing does not pretend to be exhaustive or even accurate. It SHOULD NOT be used in production. + +const { ethers } = require('hardhat'); +const { mapValues } = require('./iterate'); + +// EVM (https://axelarscan.io/resources/chains?type=evm) +const ethereum = { + Ethereum: '1', + optimism: '10', + binance: '56', + Polygon: '137', + Fantom: '250', + fraxtal: '252', + filecoin: '314', + Moonbeam: '1284', + centrifuge: '2031', + kava: '2222', + mantle: '5000', + base: '8453', + immutable: '13371', + arbitrum: '42161', + celo: '42220', + Avalanche: '43114', + linea: '59144', + blast: '81457', + scroll: '534352', + aurora: '1313161554', +}; + +// Cosmos (https://axelarscan.io/resources/chains?type=cosmos) +const cosmos = { + Axelarnet: 'axelar-dojo-1', + osmosis: 'osmosis-1', + cosmoshub: 'cosmoshub-4', + juno: 'juno-1', + 'e-money': 'emoney-3', + injective: 'injective-1', + crescent: 'crescent-1', + kujira: 'kaiyo-1', + 'secret-snip': 'secret-4', + secret: 'secret-4', + sei: 'pacific-1', + stargaze: 'stargaze-1', + assetmantle: 'mantle-1', + fetch: 'fetchhub-4', + ki: 'kichain-2', + evmos: 'evmos_9001-2', + aura: 'xstaxy-1', + comdex: 'comdex-1', + persistence: 'core-1', + regen: 'regen-1', + umee: 'umee-1', + agoric: 'agoric-3', + xpla: 'dimension_37-1', + acre: 'acre_9052-1', + stride: 'stride-1', + carbon: 'carbon-1', + sommelier: 'sommelier-3', + neutron: 'neutron-1', + rebus: 'reb_1111-1', + archway: 'archway-1', + provenance: 'pio-mainnet-1', + ixo: 'ixo-5', + migaloo: 'migaloo-1', + teritori: 'teritori-1', + haqq: 'haqq_11235-1', + celestia: 'celestia', + ojo: 'agamotto', + chihuahua: 'chihuahua-1', + saga: 'ssc-1', + dymension: 'dymension_1100-1', + fxcore: 'fxcore', + c4e: 'perun-1', + bitsong: 'bitsong-2b', + nolus: 'pirin-1', + lava: 'lava-mainnet-1', + 'terra-2': 'phoenix-1', + terra: 'columbus-5', +}; + +const makeCAIP = ({ namespace, reference, account }) => ({ + namespace, + reference, + account, + caip2: `${namespace}:${reference}`, + caip10: `${namespace}:${reference}:${account}`, + toCaip10: other => `${namespace}:${reference}:${ethers.getAddress(other.target ?? other.address ?? other)}`, +}); + +module.exports = { + CHAINS: mapValues( + Object.assign( + mapValues(ethereum, reference => ({ + namespace: 'eip155', + reference, + account: ethers.Wallet.createRandom().address, + })), + mapValues(cosmos, reference => ({ + namespace: 'cosmos', + reference, + account: ethers.encodeBase58(ethers.randomBytes(32)), + })), + ), + makeCAIP, + ), + getLocalCAIP: account => + ethers.provider.getNetwork().then(({ chainId }) => makeCAIP({ namespace: 'eip155', reference: chainId, account })), +}; diff --git a/test/helpers/eip712-types.js b/test/helpers/eip712-types.js index b2b6ccf83..d04969e44 100644 --- a/test/helpers/eip712-types.js +++ b/test/helpers/eip712-types.js @@ -32,6 +32,13 @@ module.exports = mapValues( reason: 'string', params: 'bytes', }, + OverrideBallot: { + proposalId: 'uint256', + support: 'uint8', + voter: 'address', + nonce: 'uint256', + reason: 'string', + }, Delegation: { delegatee: 'address', nonce: 'uint256', diff --git a/test/helpers/erc4337-entrypoint.js b/test/helpers/erc4337-entrypoint.js new file mode 100644 index 000000000..aba49f4c4 --- /dev/null +++ b/test/helpers/erc4337-entrypoint.js @@ -0,0 +1,31 @@ +const { ethers } = require('hardhat'); +const { setCode } = require('@nomicfoundation/hardhat-network-helpers'); +const fs = require('fs'); +const path = require('path'); + +const INSTANCES = { + entrypoint: { + address: '0x0000000071727De22E5E9d8BAf0edAc6f37da032', + abi: JSON.parse(fs.readFileSync(path.resolve(__dirname, '../bin/EntryPoint070.abi'), 'utf-8')), + bytecode: fs.readFileSync(path.resolve(__dirname, '../bin/EntryPoint070.bytecode'), 'hex'), + }, + sendercreator: { + address: '0xEFC2c1444eBCC4Db75e7613d20C6a62fF67A167C', + abi: JSON.parse(fs.readFileSync(path.resolve(__dirname, '../bin/SenderCreator070.abi'), 'utf-8')), + bytecode: fs.readFileSync(path.resolve(__dirname, '../bin/SenderCreator070.bytecode'), 'hex'), + }, +}; + +function deployEntrypoint() { + return Promise.all( + Object.entries(INSTANCES).map(([name, { address, abi, bytecode }]) => + setCode(address, '0x' + bytecode.replace(/0x/, '')) + .then(() => ethers.getContractAt(abi, address)) + .then(instance => ({ [name]: instance })), + ), + ).then(namedInstances => Object.assign(...namedInstances)); +} + +module.exports = { + deployEntrypoint, +}; diff --git a/test/helpers/erc4337.js b/test/helpers/erc4337.js new file mode 100644 index 000000000..50a3b4a04 --- /dev/null +++ b/test/helpers/erc4337.js @@ -0,0 +1,111 @@ +const { ethers } = require('hardhat'); + +const SIG_VALIDATION_SUCCESS = '0x0000000000000000000000000000000000000000'; +const SIG_VALIDATION_FAILURE = '0x0000000000000000000000000000000000000001'; + +function getAddress(account) { + return account.target ?? account.address ?? account; +} + +function pack(left, right) { + return ethers.solidityPacked(['uint128', 'uint128'], [left, right]); +} + +function packValidationData(validAfter, validUntil, authorizer) { + return ethers.solidityPacked( + ['uint48', 'uint48', 'address'], + [ + validAfter, + validUntil, + typeof authorizer == 'boolean' + ? authorizer + ? SIG_VALIDATION_SUCCESS + : SIG_VALIDATION_FAILURE + : getAddress(authorizer), + ], + ); +} + +function packInitCode(factory, factoryData) { + return ethers.solidityPacked(['address', 'bytes'], [getAddress(factory), factoryData]); +} + +function packPaymasterAndData(paymaster, paymasterVerificationGasLimit, paymasterPostOpGasLimit, paymasterData) { + return ethers.solidityPacked( + ['address', 'uint128', 'uint128', 'bytes'], + [getAddress(paymaster), paymasterVerificationGasLimit, paymasterPostOpGasLimit, paymasterData], + ); +} + +/// Represent one user operation +class UserOperation { + constructor(params) { + this.sender = getAddress(params.sender); + this.nonce = params.nonce; + this.factory = params.factory ?? undefined; + this.factoryData = params.factoryData ?? '0x'; + this.callData = params.callData ?? '0x'; + this.verificationGas = params.verificationGas ?? 10_000_000n; + this.callGas = params.callGas ?? 100_000n; + this.preVerificationGas = params.preVerificationGas ?? 100_000n; + this.maxPriorityFee = params.maxPriorityFee ?? 100_000n; + this.maxFeePerGas = params.maxFeePerGas ?? 100_000n; + this.paymaster = params.paymaster ?? undefined; + this.paymasterVerificationGasLimit = params.paymasterVerificationGasLimit ?? 0n; + this.paymasterPostOpGasLimit = params.paymasterPostOpGasLimit ?? 0n; + this.paymasterData = params.paymasterData ?? '0x'; + this.signature = params.signature ?? '0x'; + } + + get packed() { + return { + sender: this.sender, + nonce: this.nonce, + initCode: this.factory ? packInitCode(this.factory, this.factoryData) : '0x', + callData: this.callData, + accountGasLimits: pack(this.verificationGas, this.callGas), + preVerificationGas: this.preVerificationGas, + gasFees: pack(this.maxPriorityFee, this.maxFeePerGas), + paymasterAndData: this.paymaster + ? packPaymasterAndData( + this.paymaster, + this.paymasterVerificationGasLimit, + this.paymasterPostOpGasLimit, + this.paymasterData, + ) + : '0x', + signature: this.signature, + }; + } + + hash(entrypoint, chainId) { + const p = this.packed; + const h = ethers.keccak256( + ethers.AbiCoder.defaultAbiCoder().encode( + ['address', 'uint256', 'bytes32', 'bytes32', 'uint256', 'uint256', 'uint256', 'uint256'], + [ + p.sender, + p.nonce, + ethers.keccak256(p.initCode), + ethers.keccak256(p.callData), + p.accountGasLimits, + p.preVerificationGas, + p.gasFees, + ethers.keccak256(p.paymasterAndData), + ], + ), + ); + return ethers.keccak256( + ethers.AbiCoder.defaultAbiCoder().encode(['bytes32', 'address', 'uint256'], [h, getAddress(entrypoint), chainId]), + ); + } +} + +module.exports = { + SIG_VALIDATION_SUCCESS, + SIG_VALIDATION_FAILURE, + packValidationData, + packInitCode, + packPaymasterAndData, + UserOperation, +}; diff --git a/test/helpers/erc7579.js b/test/helpers/erc7579.js new file mode 100644 index 000000000..6c3b4759b --- /dev/null +++ b/test/helpers/erc7579.js @@ -0,0 +1,58 @@ +const { ethers } = require('hardhat'); + +const MODULE_TYPE_VALIDATOR = 1; +const MODULE_TYPE_EXECUTOR = 2; +const MODULE_TYPE_FALLBACK = 3; +const MODULE_TYPE_HOOK = 4; + +const EXEC_TYPE_DEFAULT = '0x00'; +const EXEC_TYPE_TRY = '0x01'; + +const CALL_TYPE_CALL = '0x00'; +const CALL_TYPE_BATCH = '0x01'; +const CALL_TYPE_DELEGATE = '0xff'; + +const encodeMode = ({ + callType = '0x00', + execType = '0x00', + selector = '0x00000000', + payload = '0x00000000000000000000000000000000000000000000', +} = {}) => + ethers.solidityPacked( + ['bytes1', 'bytes1', 'bytes4', 'bytes4', 'bytes22'], + [callType, execType, '0x00000000', selector, payload], + ); + +const encodeSingle = (target, value = 0n, data = '0x') => + ethers.solidityPacked(['address', 'uint256', 'bytes'], [target.target ?? target.address ?? target, value, data]); + +const encodeBatch = (...entries) => + ethers.AbiCoder.defaultAbiCoder().encode( + ['(address,uint256,bytes)[]'], + [ + entries.map(entry => + Array.isArray(entry) + ? [entry[0].target ?? entry[0].address ?? entry[0], entry[1] ?? 0n, entry[2] ?? '0x'] + : [entry.target.target ?? entry.target.address ?? entry.target, entry.value ?? 0n, entry.data ?? '0x'], + ), + ], + ); + +const encodeDelegate = (target, data = '0x') => + ethers.solidityPacked(['address', 'bytes'], [target.target ?? target.address ?? target, data]); + +module.exports = { + MODULE_TYPE_VALIDATOR, + MODULE_TYPE_EXECUTOR, + MODULE_TYPE_FALLBACK, + MODULE_TYPE_HOOK, + EXEC_TYPE_DEFAULT, + EXEC_TYPE_TRY, + CALL_TYPE_CALL, + CALL_TYPE_BATCH, + CALL_TYPE_DELEGATE, + encodeMode, + encodeSingle, + encodeBatch, + encodeDelegate, +}; diff --git a/test/helpers/governance.js b/test/helpers/governance.js index dce5927b7..540967af4 100644 --- a/test/helpers/governance.js +++ b/test/helpers/governance.js @@ -128,6 +128,23 @@ class GovernorHelper { return await this.governor[method](...args); } + async overrideVote(vote = {}) { + let method = 'castOverrideVote'; + let args = [this.id, vote.support]; + + vote.reason = vote.reason ?? ''; + + if (vote.signature) { + let message = this.forgeMessage(vote); + message.reason = message.reason ?? ''; + const sign = await vote.signature(this.governor, message); + method = 'castOverrideVoteBySig'; + args.push(vote.voter, vote.reason ?? '', sign); + } + + return await this.governor[method](...args); + } + /// Clock helpers async waitForSnapshot(offset = 0n) { const timepoint = await this.governor.proposalSnapshot(this.id); diff --git a/test/helpers/storage.js b/test/helpers/storage.js index a75a3060d..466cbb10c 100644 --- a/test/helpers/storage.js +++ b/test/helpers/storage.js @@ -26,7 +26,7 @@ const upgradeableSlot = (contractName, offset) => { // Try to get the artifact paths, will throw if it doesn't exist artifacts._getArtifactPathSync(`${contractName}Upgradeable`); return offset + ethers.toBigInt(erc7201Slot(erc7201format(contractName))); - } catch (_) { + } catch { return offset; } }; diff --git a/test/metatx/ERC2771Forwarder.t.sol b/test/metatx/ERC2771Forwarder.t.sol index d69b4750a..e6baac6f0 100644 --- a/test/metatx/ERC2771Forwarder.t.sol +++ b/test/metatx/ERC2771Forwarder.t.sol @@ -5,21 +5,24 @@ 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"; +import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; -struct ForwardRequest { - address from; - address to; - uint256 value; - uint256 gas; - uint256 nonce; - uint48 deadline; - bytes data; +enum TamperType { + FROM, + TO, + VALUE, + DATA, + SIGNATURE } contract ERC2771ForwarderMock is ERC2771Forwarder { constructor(string memory name) ERC2771Forwarder(name) {} - function structHash(ForwardRequest calldata request) external view returns (bytes32) { + function forwardRequestStructHash( + ERC2771Forwarder.ForwardRequestData calldata request, + uint256 nonce + ) external view returns (bytes32) { return _hashTypedDataV4( keccak256( @@ -29,7 +32,7 @@ contract ERC2771ForwarderMock is ERC2771Forwarder { request.to, request.value, request.gas, - request.nonce, + nonce, request.deadline, keccak256(request.data) ) @@ -39,127 +42,238 @@ contract ERC2771ForwarderMock is ERC2771Forwarder { } contract ERC2771ForwarderTest is Test { + using ECDSA for bytes32; + ERC2771ForwarderMock internal _erc2771Forwarder; CallReceiverMockTrustingForwarder internal _receiver; - uint256 internal _signerPrivateKey; - uint256 internal _relayerPrivateKey; - - address internal _signer; - address internal _relayer; + uint256 internal _signerPrivateKey = 0xA11CE; + address internal _signer = vm.addr(_signerPrivateKey); 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); + // Forge a new ForwardRequestData + function _forgeRequestData() private view returns (ERC2771Forwarder.ForwardRequestData memory) { + return + _forgeRequestData({ + value: 0, + deadline: uint48(block.timestamp + 1), + data: abi.encodeCall(CallReceiverMock.mockFunction, ()) + }); } 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 + from: _signer, + to: address(_receiver), + value: value, + gas: 30000, + deadline: deadline, + data: data, + signature: "" }); } + // Sign a ForwardRequestData (in place) for a given nonce. Also returns it for convenience. + function _signRequestData( + ERC2771Forwarder.ForwardRequestData memory request, + uint256 nonce + ) private view returns (ERC2771Forwarder.ForwardRequestData memory) { + bytes32 digest = _erc2771Forwarder.forwardRequestStructHash(request, nonce); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(_signerPrivateKey, digest); + request.signature = abi.encodePacked(r, s, v); + return request; + } + + // Tamper a ForwardRequestData (in place). Also returns it for convenience. + function _tamperRequestData( + ERC2771Forwarder.ForwardRequestData memory request, + TamperType tamper + ) private returns (ERC2771Forwarder.ForwardRequestData memory) { + if (tamper == TamperType.FROM) request.from = vm.randomAddress(); + else if (tamper == TamperType.TO) request.to = vm.randomAddress(); + else if (tamper == TamperType.VALUE) request.value = vm.randomUint(); + else if (tamper == TamperType.DATA) request.data = vm.randomBytes(4); + else if (tamper == TamperType.SIGNATURE) request.signature = vm.randomBytes(65); + + return request; + } + + // Predict the revert error for a tampered request, and expect it is emitted. + function _tamperedExpectRevert( + ERC2771Forwarder.ForwardRequestData memory request, + TamperType tamper, + uint256 nonce + ) private returns (ERC2771Forwarder.ForwardRequestData memory) { + if (tamper == TamperType.FROM) nonce = _erc2771Forwarder.nonces(request.from); + + // predict revert + if (tamper == TamperType.TO) { + vm.expectRevert( + abi.encodeWithSelector( + ERC2771Forwarder.ERC2771UntrustfulTarget.selector, + request.to, + address(_erc2771Forwarder) + ) + ); + } else { + (address recovered, , ) = _erc2771Forwarder.forwardRequestStructHash(request, nonce).tryRecover( + request.signature + ); + vm.expectRevert( + abi.encodeWithSelector(ERC2771Forwarder.ERC2771ForwarderInvalidSigner.selector, recovered, request.from) + ); + } + return request; + } + 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({ + // create and sign request + ERC2771Forwarder.ForwardRequestData memory request = _forgeRequestData({ value: value, - nonce: nonce, deadline: uint48(block.timestamp + 1), data: targetReverts ? abi.encodeCall(CallReceiverMock.mockFunctionRevertsNoReason, ()) : abi.encodeCall(CallReceiverMock.mockFunction, ()) }); + _signRequestData(request, _erc2771Forwarder.nonces(_signer)); - if (targetReverts) { - vm.expectRevert(); - } + vm.deal(address(_erc2771Forwarder), initialBalance); + vm.deal(address(this), request.value); + + if (targetReverts) vm.expectRevert(); + _erc2771Forwarder.execute{value: value}(request); - _erc2771Forwarder.execute{value: value}(requestData); assertEq(address(_erc2771Forwarder).balance, initialBalance); } function testExecuteBatchAvoidsETHStuck(uint256 initialBalance, uint256 batchSize, uint256 value) public { + uint256 seed = uint256(keccak256(abi.encodePacked(initialBalance, batchSize, value))); + batchSize = bound(batchSize, 1, 10); initialBalance = bound(initialBalance, 0, _MAX_ETHER); value = bound(value, 0, _MAX_ETHER); - vm.deal(address(_erc2771Forwarder), initialBalance); + address refundReceiver = address(0xebe); + uint256 refundExpected = 0; uint256 nonce = _erc2771Forwarder.nonces(_signer); - ERC2771Forwarder.ForwardRequestData[] memory batchRequestDatas = new ERC2771Forwarder.ForwardRequestData[]( - batchSize - ); - - uint256 expectedRefund; - + // create an sign array or requests (that may fail) + ERC2771Forwarder.ForwardRequestData[] memory requests = new ERC2771Forwarder.ForwardRequestData[](batchSize); for (uint256 i = 0; i < batchSize; ++i) { - bytes memory data; - bool succeed = uint256(keccak256(abi.encodePacked(initialBalance, i))) % 2 == 0; + bool failure = (seed >> i) & 0x1 == 0x1; - if (succeed) { - data = abi.encodeCall(CallReceiverMock.mockFunction, ()); - } else { - expectedRefund += value; - data = abi.encodeCall(CallReceiverMock.mockFunctionRevertsNoReason, ()); - } - - batchRequestDatas[i] = _forgeRequestData({ + requests[i] = _forgeRequestData({ value: value, - nonce: nonce + i, deadline: uint48(block.timestamp + 1), - data: data + data: failure + ? abi.encodeCall(CallReceiverMock.mockFunctionRevertsNoReason, ()) + : abi.encodeCall(CallReceiverMock.mockFunction, ()) }); + _signRequestData(requests[i], nonce + i); + + refundExpected += SafeCast.toUint(failure) * value; } - address payable refundReceiver = payable(address(0xebe)); - uint256 totalValue = value * batchSize; + // distribute ether + vm.deal(address(_erc2771Forwarder), initialBalance); + vm.deal(address(this), value * batchSize); - vm.deal(address(this), totalValue); - _erc2771Forwarder.executeBatch{value: totalValue}(batchRequestDatas, refundReceiver); + // execute batch + _erc2771Forwarder.executeBatch{value: value * batchSize}(requests, payable(refundReceiver)); + // check balances assertEq(address(_erc2771Forwarder).balance, initialBalance); - assertEq(refundReceiver.balance, expectedRefund); + assertEq(refundReceiver.balance, refundExpected); + } + + function testVerifyTamperedValues(uint8 _tamper) public { + TamperType tamper = _asTamper(_tamper); + + // create request, sign, tamper + ERC2771Forwarder.ForwardRequestData memory request = _forgeRequestData(); + _signRequestData(request, 0); + _tamperRequestData(request, tamper); + + // should not pass verification + assertFalse(_erc2771Forwarder.verify(request)); + } + + function testExecuteTamperedValues(uint8 _tamper) public { + TamperType tamper = _asTamper(_tamper); + + // create request, sign, tamper, expect execution revert + ERC2771Forwarder.ForwardRequestData memory request = _forgeRequestData(); + _signRequestData(request, 0); + _tamperRequestData(request, tamper); + _tamperedExpectRevert(request, tamper, 0); + + vm.deal(address(this), request.value); + _erc2771Forwarder.execute{value: request.value}(request); + } + + function testExecuteBatchTamperedValuesZeroReceiver(uint8 _tamper) public { + TamperType tamper = _asTamper(_tamper); + uint256 nonce = _erc2771Forwarder.nonces(_signer); + + // create an sign array or requests + ERC2771Forwarder.ForwardRequestData[] memory requests = new ERC2771Forwarder.ForwardRequestData[](3); + for (uint256 i = 0; i < requests.length; ++i) { + requests[i] = _forgeRequestData({ + value: 0, + deadline: uint48(block.timestamp + 1), + data: abi.encodeCall(CallReceiverMock.mockFunction, ()) + }); + _signRequestData(requests[i], nonce + i); + } + + // tamper with request[1] and expect execution revert + _tamperRequestData(requests[1], tamper); + _tamperedExpectRevert(requests[1], tamper, nonce + 1); + + vm.deal(address(this), requests[1].value); + _erc2771Forwarder.executeBatch{value: requests[1].value}(requests, payable(address(0))); + } + + function testExecuteBatchTamperedValues(uint8 _tamper) public { + TamperType tamper = _asTamper(_tamper); + uint256 nonce = _erc2771Forwarder.nonces(_signer); + + // create an sign array or requests + ERC2771Forwarder.ForwardRequestData[] memory requests = new ERC2771Forwarder.ForwardRequestData[](3); + for (uint256 i = 0; i < requests.length; ++i) { + requests[i] = _forgeRequestData({ + value: 0, + deadline: uint48(block.timestamp + 1), + data: abi.encodeCall(CallReceiverMock.mockFunction, ()) + }); + _signRequestData(requests[i], nonce + i); + } + + // tamper with request[1] + _tamperRequestData(requests[1], tamper); + + // should not revert + vm.expectCall(address(_receiver), abi.encodeCall(CallReceiverMock.mockFunction, ()), 1); + + vm.deal(address(this), requests[1].value); + _erc2771Forwarder.executeBatch{value: requests[1].value}(requests, payable(address(0xebe))); + } + + function _asTamper(uint8 _tamper) private pure returns (TamperType) { + return TamperType(bound(_tamper, uint8(TamperType.FROM), uint8(TamperType.SIGNATURE))); } } diff --git a/test/metatx/ERC2771Forwarder.test.js b/test/metatx/ERC2771Forwarder.test.js index 8653ad708..bf6cfd10c 100644 --- a/test/metatx/ERC2771Forwarder.test.js +++ b/test/metatx/ERC2771Forwarder.test.js @@ -52,19 +52,6 @@ async function fixture() { }; } -// values or function to tamper with a signed request. -const tamperedValues = { - from: ethers.Wallet.createRandom().address, - to: ethers.Wallet.createRandom().address, - value: ethers.parseEther('0.5'), - data: '0x1742', - signature: s => { - const t = ethers.toBeArray(s); - t[42] ^= 0xff; - return ethers.hexlify(t); - }, -}; - describe('ERC2771Forwarder', function () { beforeEach(async function () { Object.assign(this, await loadFixture(fixture)); @@ -81,15 +68,6 @@ describe('ERC2771Forwarder', function () { }); describe('with tampered values', function () { - for (const [key, value] of Object.entries(tamperedValues)) { - it(`returns false with tampered ${key}`, async function () { - const request = await this.forgeRequest(); - request[key] = typeof value == 'function' ? value(request[key]) : value; - - expect(await this.forwarder.verify(request)).to.be.false; - }); - } - it('returns false with valid signature for non-current nonce', async function () { const request = await this.forgeRequest({ nonce: 1337n }); expect(await this.forwarder.verify(request)).to.be.false; @@ -127,24 +105,6 @@ describe('ERC2771Forwarder', function () { }); describe('with tampered request', function () { - for (const [key, value] of Object.entries(tamperedValues)) { - it(`reverts with tampered ${key}`, async function () { - const request = await this.forgeRequest(); - request[key] = typeof value == 'function' ? value(request[key]) : value; - - const promise = this.forwarder.execute(request, { value: key == 'value' ? value : 0 }); - if (key != 'to') { - await expect(promise) - .to.be.revertedWithCustomError(this.forwarder, 'ERC2771ForwarderInvalidSigner') - .withArgs(ethers.verifyTypedData(this.domain, this.types, request, request.signature), request.from); - } else { - await expect(promise) - .to.be.revertedWithCustomError(this.forwarder, 'ERC2771UntrustfulTarget') - .withArgs(request.to, this.forwarder); - } - }); - } - it('reverts with valid signature for non-current nonce', async function () { const request = await this.forgeRequest(); @@ -284,26 +244,6 @@ describe('ERC2771Forwarder', function () { this.refundReceiver = ethers.ZeroAddress; }); - for (const [key, value] of Object.entries(tamperedValues)) { - it(`reverts with at least one tampered request ${key}`, async function () { - this.requests[idx][key] = typeof value == 'function' ? value(this.requests[idx][key]) : value; - - const promise = this.forwarder.executeBatch(this.requests, this.refundReceiver, { value: this.value }); - if (key != 'to') { - await expect(promise) - .to.be.revertedWithCustomError(this.forwarder, 'ERC2771ForwarderInvalidSigner') - .withArgs( - ethers.verifyTypedData(this.domain, this.types, this.requests[idx], this.requests[idx].signature), - this.requests[idx].from, - ); - } else { - await expect(promise) - .to.be.revertedWithCustomError(this.forwarder, 'ERC2771UntrustfulTarget') - .withArgs(this.requests[idx].to, this.forwarder); - } - }); - } - it('reverts with at least one valid signature for non-current nonce', async function () { // Execute first a request await this.forwarder.execute(this.requests[idx], { value: this.requests[idx].value }); @@ -340,23 +280,6 @@ describe('ERC2771Forwarder', function () { this.initialTamperedRequestNonce = await this.forwarder.nonces(this.requests[idx].from); }); - for (const [key, value] of Object.entries(tamperedValues)) { - it(`ignores a request with tampered ${key} and refunds its value`, async function () { - this.requests[idx][key] = typeof value == 'function' ? value(this.requests[idx][key]) : value; - - const events = await this.forwarder - .executeBatch(this.requests, this.refundReceiver, { value: requestsValue(this.requests) }) - .then(tx => tx.wait()) - .then(receipt => - receipt.logs.filter( - log => log?.fragment?.type == 'event' && log?.fragment?.name == 'ExecutedForwardRequest', - ), - ); - - expect(events).to.have.lengthOf(this.requests.length - 1); - }); - } - it('ignores a request with a valid signature for non-current nonce', async function () { // Execute first a request await this.forwarder.execute(this.requests[idx], { value: this.requests[idx].value }); diff --git a/test/proxy/Clones.t.sol b/test/proxy/Clones.t.sol index 31b072b98..e589ba906 100644 --- a/test/proxy/Clones.t.sol +++ b/test/proxy/Clones.t.sol @@ -19,12 +19,28 @@ contract ClonesTest is Test { assertEq(spillage, bytes32(0)); } + function testSymbolicPredictDeterministicAddressWithImmutableArgsSpillage( + address implementation, + bytes32 salt, + bytes memory args + ) public { + vm.assume(args.length < 0xbfd3); + + address predicted = Clones.predictDeterministicAddressWithImmutableArgs(implementation, args, salt); + bytes32 spillage; + /// @solidity memory-safe-assembly + assembly { + spillage := and(predicted, 0xffffffffffffffffffffffff0000000000000000000000000000000000000000) + } + assertEq(spillage, bytes32(0)); + } + function testCloneDirty() external { address cloneClean = Clones.clone(address(this)); address cloneDirty = Clones.clone(_dirty(address(this))); // both clones have the same code - assertEq(keccak256(cloneClean.code), keccak256(cloneDirty.code)); + assertEq(cloneClean.code, cloneDirty.code); // both clones behave as expected assertEq(ClonesTest(cloneClean).getNumber(), this.getNumber()); @@ -36,7 +52,7 @@ contract ClonesTest is Test { address cloneDirty = Clones.cloneDeterministic(_dirty(address(this)), ~salt); // both clones have the same code - assertEq(keccak256(cloneClean.code), keccak256(cloneDirty.code)); + assertEq(cloneClean.code, cloneDirty.code); // both clones behave as expected assertEq(ClonesTest(cloneClean).getNumber(), this.getNumber()); @@ -51,6 +67,22 @@ contract ClonesTest is Test { assertEq(predictClean, predictDirty); } + function testFetchCloneArgs(bytes memory args, bytes32 salt) external { + vm.assume(args.length < 0xbfd3); + + address instance1 = Clones.cloneWithImmutableArgs(address(this), args); + address instance2 = Clones.cloneDeterministicWithImmutableArgs(address(this), args, salt); + + // both clones have the same code + assertEq(instance1.code, instance2.code); + + // both clones behave as expected and args can be fetched + assertEq(ClonesTest(instance1).getNumber(), this.getNumber()); + assertEq(ClonesTest(instance2).getNumber(), this.getNumber()); + assertEq(Clones.fetchCloneArgs(instance1), args); + assertEq(Clones.fetchCloneArgs(instance2), args); + } + function _dirty(address input) private pure returns (address output) { assembly ("memory-safe") { output := or(input, shl(160, not(0))) diff --git a/test/proxy/Clones.test.js b/test/proxy/Clones.test.js index 70220fbf7..0706c6f83 100644 --- a/test/proxy/Clones.test.js +++ b/test/proxy/Clones.test.js @@ -2,38 +2,77 @@ const { ethers } = require('hardhat'); const { expect } = require('chai'); const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); +const { generators } = require('../helpers/random'); + const shouldBehaveLikeClone = require('./Clones.behaviour'); +const cloneInitCode = (instance, args = undefined) => + args + ? ethers.concat([ + '0x61', + ethers.toBeHex(0x2d + ethers.getBytes(args).length, 2), + '0x3d81600a3d39f3363d3d373d3d3d363d73', + instance.target ?? instance.address ?? instance, + '0x5af43d82803e903d91602b57fd5bf3', + args, + ]) + : ethers.concat([ + '0x3d602d80600a3d3981f3363d3d373d3d3d363d73', + instance.target ?? instance.address ?? instance, + '0x5af43d82803e903d91602b57fd5bf3', + ]); + async function fixture() { const [deployer] = await ethers.getSigners(); const factory = await ethers.deployContract('$Clones'); const implementation = await ethers.deployContract('DummyImplementation'); - const newClone = async (opts = {}) => { - const clone = await factory.$clone.staticCall(implementation).then(address => implementation.attach(address)); - const tx = await (opts.deployValue - ? factory.$clone(implementation, ethers.Typed.uint256(opts.deployValue)) - : factory.$clone(implementation)); - if (opts.initData || opts.initValue) { - await deployer.sendTransaction({ to: clone, value: opts.initValue ?? 0n, data: opts.initData ?? '0x' }); - } - return Object.assign(clone, { deploymentTransaction: () => tx }); - }; + const newClone = + args => + async (opts = {}) => { + const clone = await (args + ? factory.$cloneWithImmutableArgs.staticCall(implementation, args) + : factory.$clone.staticCall(implementation) + ).then(address => implementation.attach(address)); + const tx = await (args + ? opts.deployValue + ? factory.$cloneWithImmutableArgs(implementation, args, ethers.Typed.uint256(opts.deployValue)) + : factory.$cloneWithImmutableArgs(implementation, args) + : opts.deployValue + ? factory.$clone(implementation, ethers.Typed.uint256(opts.deployValue)) + : factory.$clone(implementation)); + if (opts.initData || opts.initValue) { + await deployer.sendTransaction({ to: clone, value: opts.initValue ?? 0n, data: opts.initData ?? '0x' }); + } + return Object.assign(clone, { deploymentTransaction: () => tx }); + }; - const newCloneDeterministic = async (opts = {}) => { - const salt = opts.salt ?? ethers.randomBytes(32); - const clone = await factory.$cloneDeterministic - .staticCall(implementation, salt) - .then(address => implementation.attach(address)); - const tx = await (opts.deployValue - ? factory.$cloneDeterministic(implementation, salt, ethers.Typed.uint256(opts.deployValue)) - : factory.$cloneDeterministic(implementation, salt)); - if (opts.initData || opts.initValue) { - await deployer.sendTransaction({ to: clone, value: opts.initValue ?? 0n, data: opts.initData ?? '0x' }); - } - return Object.assign(clone, { deploymentTransaction: () => tx }); - }; + const newCloneDeterministic = + args => + async (opts = {}) => { + const salt = opts.salt ?? ethers.randomBytes(32); + const clone = await (args + ? factory.$cloneDeterministicWithImmutableArgs.staticCall(implementation, args, salt) + : factory.$cloneDeterministic.staticCall(implementation, salt) + ).then(address => implementation.attach(address)); + const tx = await (args + ? opts.deployValue + ? factory.$cloneDeterministicWithImmutableArgs( + implementation, + args, + salt, + ethers.Typed.uint256(opts.deployValue), + ) + : factory.$cloneDeterministicWithImmutableArgs(implementation, args, salt) + : opts.deployValue + ? factory.$cloneDeterministic(implementation, salt, ethers.Typed.uint256(opts.deployValue)) + : factory.$cloneDeterministic(implementation, salt)); + if (opts.initData || opts.initValue) { + await deployer.sendTransaction({ to: clone, value: opts.initValue ?? 0n, data: opts.initData ?? '0x' }); + } + return Object.assign(clone, { deploymentTransaction: () => tx }); + }; return { deployer, factory, implementation, newClone, newCloneDeterministic }; } @@ -43,53 +82,94 @@ describe('Clones', function () { Object.assign(this, await loadFixture(fixture)); }); - describe('clone', function () { - beforeEach(async function () { - this.createClone = this.newClone; + for (const args of [undefined, '0x', '0x11223344']) { + describe(args ? `with immutable args: ${args}` : 'without immutable args', function () { + describe('clone', function () { + beforeEach(async function () { + this.createClone = this.newClone(args); + }); + + shouldBehaveLikeClone(); + + it('get immutable arguments', async function () { + const instance = await this.createClone(); + expect(await this.factory.$fetchCloneArgs(instance)).to.equal(args ?? '0x'); + }); + }); + + describe('cloneDeterministic', function () { + beforeEach(async function () { + this.createClone = this.newCloneDeterministic(args); + }); + + shouldBehaveLikeClone(); + + it('get immutable arguments', async function () { + const instance = await this.createClone(); + expect(await this.factory.$fetchCloneArgs(instance)).to.equal(args ?? '0x'); + }); + + it('revert if address already used', async function () { + const salt = ethers.randomBytes(32); + + const deployClone = () => + args + ? this.factory.$cloneDeterministicWithImmutableArgs(this.implementation, args, salt) + : this.factory.$cloneDeterministic(this.implementation, salt); + + // deploy once + await expect(deployClone()).to.not.be.reverted; + + // deploy twice + await expect(deployClone()).to.be.revertedWithCustomError(this.factory, 'FailedDeployment'); + }); + + it('address prediction', async function () { + const salt = ethers.randomBytes(32); + + const expected = ethers.getCreate2Address( + this.factory.target, + salt, + ethers.keccak256(cloneInitCode(this.implementation, args)), + ); + + if (args) { + const predicted = await this.factory.$predictDeterministicAddressWithImmutableArgs( + this.implementation, + args, + salt, + ); + expect(predicted).to.equal(expected); + + await expect(this.factory.$cloneDeterministicWithImmutableArgs(this.implementation, args, salt)) + .to.emit(this.factory, 'return$cloneDeterministicWithImmutableArgs_address_bytes_bytes32') + .withArgs(predicted); + } else { + const predicted = await this.factory.$predictDeterministicAddress(this.implementation, salt); + expect(predicted).to.equal(expected); + + await expect(this.factory.$cloneDeterministic(this.implementation, salt)) + .to.emit(this.factory, 'return$cloneDeterministic_address_bytes32') + .withArgs(predicted); + } + }); + }); }); + } - shouldBehaveLikeClone(); - }); + it('EIP-170 limit on immutable args', async function () { + // EIP-170 limits the contract code size to 0x6000 + // This limits the length of immutable args to 0x5fd3 + const args = generators.hexBytes(0x5fd4); + const salt = ethers.randomBytes(32); - describe('cloneDeterministic', function () { - beforeEach(async function () { - this.createClone = this.newCloneDeterministic; - }); + await expect( + this.factory.$predictDeterministicAddressWithImmutableArgs(this.implementation, args, salt), + ).to.be.revertedWithCustomError(this.factory, 'CloneArgumentsTooLong'); - shouldBehaveLikeClone(); - - it('revert if address already used', async function () { - const salt = ethers.randomBytes(32); - - // deploy once - await expect(this.factory.$cloneDeterministic(this.implementation, salt)).to.emit( - this.factory, - 'return$cloneDeterministic_address_bytes32', - ); - - // deploy twice - await expect(this.factory.$cloneDeterministic(this.implementation, salt)).to.be.revertedWithCustomError( - this.factory, - 'FailedDeployment', - ); - }); - - it('address prediction', async function () { - const salt = ethers.randomBytes(32); - - const creationCode = ethers.concat([ - '0x3d602d80600a3d3981f3363d3d373d3d3d363d73', - this.implementation.target, - '0x5af43d82803e903d91602b57fd5bf3', - ]); - - const predicted = await this.factory.$predictDeterministicAddress(this.implementation, salt); - const expected = ethers.getCreate2Address(this.factory.target, salt, ethers.keccak256(creationCode)); - expect(predicted).to.equal(expected); - - await expect(this.factory.$cloneDeterministic(this.implementation, salt)) - .to.emit(this.factory, 'return$cloneDeterministic_address_bytes32') - .withArgs(predicted); - }); + await expect(this.factory.$cloneWithImmutableArgs(this.implementation, args)).to.be.revertedWithCustomError( + this.factory, + 'CloneArgumentsTooLong', + ); }); }); diff --git a/test/proxy/transparent/TransparentUpgradeableProxy.behaviour.js b/test/proxy/transparent/TransparentUpgradeableProxy.behaviour.js index d90bd56e2..8e1d62eaa 100644 --- a/test/proxy/transparent/TransparentUpgradeableProxy.behaviour.js +++ b/test/proxy/transparent/TransparentUpgradeableProxy.behaviour.js @@ -243,10 +243,10 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy() { }); it('proxy admin cannot call delegated functions', async function () { - const interface = await ethers.getContractFactory('TransparentUpgradeableProxy'); + const factory = await ethers.getContractFactory('TransparentUpgradeableProxy'); await expect(this.instance.connect(this.proxyAdminAsSigner).delegatedFunction()).to.be.revertedWithCustomError( - interface, + factory, 'ProxyDeniedAdminAccess', ); }); diff --git a/test/token/ERC721/extensions/ERC721Consecutive.test.js b/test/token/ERC721/extensions/ERC721Consecutive.test.js index d2eda944c..f62d6dc5b 100644 --- a/test/token/ERC721/extensions/ERC721Consecutive.test.js +++ b/test/token/ERC721/extensions/ERC721Consecutive.test.js @@ -202,35 +202,35 @@ describe('ERC721Consecutive', function () { const receiver = ethers.Wallet.createRandom(); it('cannot mint a batch larger than 5000', async function () { - const { interface } = await ethers.getContractFactory('$ERC721ConsecutiveMock'); + const factory = await ethers.getContractFactory('$ERC721ConsecutiveMock'); await expect(ethers.deployContract('$ERC721ConsecutiveMock', [name, symbol, 0, [], [receiver], [5001n]])) - .to.be.revertedWithCustomError({ interface }, 'ERC721ExceededMaxBatchMint') + .to.be.revertedWithCustomError(factory, 'ERC721ExceededMaxBatchMint') .withArgs(5001n, 5000n); }); it('cannot use single minting during construction', async function () { - const { interface } = await ethers.getContractFactory('$ERC721ConsecutiveNoConstructorMintMock'); + const factory = await ethers.getContractFactory('$ERC721ConsecutiveNoConstructorMintMock'); await expect( ethers.deployContract('$ERC721ConsecutiveNoConstructorMintMock', [name, symbol]), - ).to.be.revertedWithCustomError({ interface }, 'ERC721ForbiddenMint'); + ).to.be.revertedWithCustomError(factory, 'ERC721ForbiddenMint'); }); it('cannot use single minting during construction', async function () { - const { interface } = await ethers.getContractFactory('$ERC721ConsecutiveNoConstructorMintMock'); + const factory = await ethers.getContractFactory('$ERC721ConsecutiveNoConstructorMintMock'); await expect( ethers.deployContract('$ERC721ConsecutiveNoConstructorMintMock', [name, symbol]), - ).to.be.revertedWithCustomError({ interface }, 'ERC721ForbiddenMint'); + ).to.be.revertedWithCustomError(factory, 'ERC721ForbiddenMint'); }); it('consecutive mint not compatible with enumerability', async function () { - const { interface } = await ethers.getContractFactory('$ERC721ConsecutiveEnumerableMock'); + const factory = await ethers.getContractFactory('$ERC721ConsecutiveEnumerableMock'); await expect( ethers.deployContract('$ERC721ConsecutiveEnumerableMock', [name, symbol, [receiver], [100n]]), - ).to.be.revertedWithCustomError({ interface }, 'ERC721EnumerableForbiddenBatchMint'); + ).to.be.revertedWithCustomError(factory, 'ERC721EnumerableForbiddenBatchMint'); }); }); }); diff --git a/test/utils/Address.test.js b/test/utils/Address.test.js index 21775397a..8307a923e 100644 --- a/test/utils/Address.test.js +++ b/test/utils/Address.test.js @@ -126,8 +126,9 @@ describe('Address', function () { }); it('reverts when function does not exist', async function () { - const interface = new ethers.Interface(['function mockFunctionDoesNotExist()']); - const call = interface.encodeFunctionData('mockFunctionDoesNotExist'); + const call = new ethers.Interface(['function mockFunctionDoesNotExist()']).encodeFunctionData( + 'mockFunctionDoesNotExist', + ); await expect(this.mock.$functionCall(this.target, call)).to.be.revertedWithCustomError(this.mock, 'FailedCall'); }); diff --git a/test/utils/Bytes.test.js b/test/utils/Bytes.test.js new file mode 100644 index 000000000..52a1ae95e --- /dev/null +++ b/test/utils/Bytes.test.js @@ -0,0 +1,88 @@ +const { ethers } = require('hardhat'); +const { expect } = require('chai'); +const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); + +async function fixture() { + const mock = await ethers.deployContract('$Bytes'); + return { mock }; +} + +const lorem = ethers.toUtf8Bytes( + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', +); +const present = lorem.at(1); +const absent = 255; + +describe('Bytes', function () { + before(async function () { + Object.assign(this, await loadFixture(fixture)); + }); + + describe('indexOf', function () { + it('first', async function () { + expect(await this.mock.$indexOf(lorem, ethers.toBeHex(present))).to.equal(lorem.indexOf(present)); + }); + + it('from index', async function () { + for (const start in Array(lorem.length + 10).fill()) { + const index = lorem.indexOf(present, start); + const result = index === -1 ? ethers.MaxUint256 : index; + expect(await this.mock.$indexOf(lorem, ethers.toBeHex(present), ethers.Typed.uint256(start))).to.equal(result); + } + }); + + it('absent', async function () { + expect(await this.mock.$indexOf(lorem, ethers.toBeHex(absent))).to.equal(ethers.MaxUint256); + }); + }); + + describe('lastIndexOf', function () { + it('first', async function () { + expect(await this.mock.$lastIndexOf(lorem, ethers.toBeHex(present))).to.equal(lorem.lastIndexOf(present)); + }); + + it('from index', async function () { + for (const start in Array(lorem.length + 10).fill()) { + const index = lorem.lastIndexOf(present, start); + const result = index === -1 ? ethers.MaxUint256 : index; + expect(await this.mock.$lastIndexOf(lorem, ethers.toBeHex(present), ethers.Typed.uint256(start))).to.equal( + result, + ); + } + }); + + it('absent', async function () { + expect(await this.mock.$lastIndexOf(lorem, ethers.toBeHex(absent))).to.equal(ethers.MaxUint256); + }); + }); + + describe('slice', function () { + describe('slice(bytes, uint256)', function () { + for (const [descr, start] of Object.entries({ + 'start = 0': 0, + 'start within bound': 10, + 'start out of bound': 1000, + })) { + it(descr, async function () { + const result = ethers.hexlify(lorem.slice(start)); + expect(await this.mock.$slice(lorem, start)).to.equal(result); + }); + } + }); + + describe('slice(bytes, uint256, uint256)', function () { + for (const [descr, [start, end]] of Object.entries({ + 'start = 0': [0, 42], + 'start and end within bound': [17, 42], + 'end out of bound': [42, 1000], + 'start = end': [17, 17], + 'start > end': [42, 17], + })) { + it(descr, async function () { + const result = ethers.hexlify(lorem.slice(start, end)); + expect(await this.mock.$slice(lorem, start, ethers.Typed.uint256(end))).to.equal(result); + }); + } + }); + }); +}); diff --git a/test/utils/CAIP.test.js b/test/utils/CAIP.test.js new file mode 100644 index 000000000..cd5995cad --- /dev/null +++ b/test/utils/CAIP.test.js @@ -0,0 +1,53 @@ +const { ethers } = require('hardhat'); +const { expect } = require('chai'); +const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); + +const { CHAINS, getLocalCAIP } = require('../helpers/chains'); + +async function fixture() { + const caip2 = await ethers.deployContract('$CAIP2'); + const caip10 = await ethers.deployContract('$CAIP10'); + return { caip2, caip10 }; +} + +describe('CAIP utilities', function () { + beforeEach(async function () { + Object.assign(this, await loadFixture(fixture)); + }); + + describe('CAIP-2', function () { + it('local()', async function () { + const { caip2 } = await getLocalCAIP(); + expect(await this.caip2.$local()).to.equal(caip2); + }); + + for (const { namespace, reference, caip2 } of Object.values(CHAINS)) + it(`format(${namespace}, ${reference})`, async function () { + expect(await this.caip2.$format(namespace, reference)).to.equal(caip2); + }); + + for (const { namespace, reference, caip2 } of Object.values(CHAINS)) + it(`parse(${caip2})`, async function () { + expect(await this.caip2.$parse(caip2)).to.deep.equal([namespace, reference]); + }); + }); + + describe('CAIP-10', function () { + const { address: account } = ethers.Wallet.createRandom(); + + it(`local(${account})`, async function () { + const { caip10 } = await getLocalCAIP(account); + expect(await this.caip10.$local(ethers.Typed.address(account))).to.equal(caip10); + }); + + for (const { account, caip2, caip10 } of Object.values(CHAINS)) + it(`format(${caip2}, ${account})`, async function () { + expect(await this.caip10.$format(ethers.Typed.string(caip2), ethers.Typed.string(account))).to.equal(caip10); + }); + + for (const { account, caip2, caip10 } of Object.values(CHAINS)) + it(`parse(${caip10})`, async function () { + expect(await this.caip10.$parse(caip10)).to.deep.equal([caip2, account]); + }); + }); +}); diff --git a/test/utils/Create2.test.js b/test/utils/Create2.test.js index 152fdbdf4..99c47a0e3 100644 --- a/test/utils/Create2.test.js +++ b/test/utils/Create2.test.js @@ -14,13 +14,13 @@ async function fixture() { // We use a vesting wallet, with 3 constructor arguments. const constructorByteCode = await ethers .getContractFactory('VestingWallet') - .then(({ bytecode, interface }) => ethers.concat([bytecode, interface.encodeDeploy([other.address, 0n, 0n])])); + .then(factory => ethers.concat([factory.bytecode, factory.interface.encodeDeploy([other.address, 0n, 0n])])); // Bytecode for deploying a contract that has no constructor log. // Here we use the Create2 helper factory. const constructorLessBytecode = await ethers .getContractFactory('$Create2') - .then(({ bytecode, interface }) => ethers.concat([bytecode, interface.encodeDeploy([])])); + .then(factory => ethers.concat([factory.bytecode, factory.interface.encodeDeploy([])])); const mockFactory = await ethers.getContractFactory('ConstructorMock'); diff --git a/test/utils/Nonces.behavior.js b/test/utils/Nonces.behavior.js new file mode 100644 index 000000000..32201f690 --- /dev/null +++ b/test/utils/Nonces.behavior.js @@ -0,0 +1,189 @@ +const { ethers } = require('hardhat'); +const { expect } = require('chai'); + +function shouldBehaveLikeNonces() { + describe('should behave like Nonces', function () { + const sender = ethers.Wallet.createRandom(); + const other = ethers.Wallet.createRandom(); + + it('gets a nonce', async function () { + expect(this.mock.nonces(sender)).to.eventually.equal(0n); + }); + + describe('_useNonce', function () { + it('increments a nonce', async function () { + expect(this.mock.nonces(sender)).to.eventually.equal(0n); + + const eventName = ['return$_useNonce', 'return$_useNonce_address'].find(name => + this.mock.interface.getEvent(name), + ); + + await expect(this.mock.$_useNonce(sender)).to.emit(this.mock, eventName).withArgs(0n); + + expect(this.mock.nonces(sender)).to.eventually.equal(1n); + }); + + it("increments only sender's nonce", async function () { + expect(this.mock.nonces(sender)).to.eventually.equal(0n); + expect(this.mock.nonces(other)).to.eventually.equal(0n); + + await this.mock.$_useNonce(sender); + + expect(this.mock.nonces(sender)).to.eventually.equal(1n); + expect(this.mock.nonces(other)).to.eventually.equal(0n); + }); + }); + + describe('_useCheckedNonce', function () { + it('increments a nonce', async function () { + // current nonce is 0n + expect(this.mock.nonces(sender)).to.eventually.equal(0n); + + await this.mock.$_useCheckedNonce(sender, 0n); + + expect(this.mock.nonces(sender)).to.eventually.equal(1n); + }); + + it("increments only sender's nonce", async function () { + // current nonce is 0n + expect(this.mock.nonces(sender)).to.eventually.equal(0n); + expect(this.mock.nonces(other)).to.eventually.equal(0n); + + await this.mock.$_useCheckedNonce(sender, 0n); + + expect(this.mock.nonces(sender)).to.eventually.equal(1n); + expect(this.mock.nonces(other)).to.eventually.equal(0n); + }); + + it('reverts when nonce is not the expected', async function () { + const currentNonce = await this.mock.nonces(sender); + + await expect(this.mock.$_useCheckedNonce(sender, currentNonce + 1n)) + .to.be.revertedWithCustomError(this.mock, 'InvalidAccountNonce') + .withArgs(sender, currentNonce); + }); + }); + }); +} + +function shouldBehaveLikeNoncesKeyed() { + describe('should support nonces with keys', function () { + const sender = ethers.Wallet.createRandom(); + + const keyOffset = key => key << 64n; + + it('gets a nonce', async function () { + expect(this.mock.nonces(sender, ethers.Typed.uint192(0n))).to.eventually.equal(keyOffset(0n) + 0n); + expect(this.mock.nonces(sender, ethers.Typed.uint192(17n))).to.eventually.equal(keyOffset(17n) + 0n); + }); + + describe('_useNonce', function () { + it('default variant uses key 0', async function () { + expect(this.mock.nonces(sender, ethers.Typed.uint192(0n))).to.eventually.equal(keyOffset(0n) + 0n); + expect(this.mock.nonces(sender, ethers.Typed.uint192(17n))).to.eventually.equal(keyOffset(17n) + 0n); + + await expect(this.mock.$_useNonce(sender)).to.emit(this.mock, 'return$_useNonce_address').withArgs(0n); + + await expect(this.mock.$_useNonce(sender, ethers.Typed.uint192(0n))) + .to.emit(this.mock, 'return$_useNonce_address_uint192') + .withArgs(keyOffset(0n) + 1n); + + expect(this.mock.nonces(sender, ethers.Typed.uint192(0n))).to.eventually.equal(keyOffset(0n) + 2n); + expect(this.mock.nonces(sender, ethers.Typed.uint192(17n))).to.eventually.equal(keyOffset(17n) + 0n); + }); + + it('use nonce at another key', async function () { + expect(this.mock.nonces(sender, ethers.Typed.uint192(0n))).to.eventually.equal(keyOffset(0n) + 0n); + expect(this.mock.nonces(sender, ethers.Typed.uint192(17n))).to.eventually.equal(keyOffset(17n) + 0n); + + await expect(this.mock.$_useNonce(sender, ethers.Typed.uint192(17n))) + .to.emit(this.mock, 'return$_useNonce_address_uint192') + .withArgs(keyOffset(17n) + 0n); + + await expect(this.mock.$_useNonce(sender, ethers.Typed.uint192(17n))) + .to.emit(this.mock, 'return$_useNonce_address_uint192') + .withArgs(keyOffset(17n) + 1n); + + expect(this.mock.nonces(sender, ethers.Typed.uint192(0n))).to.eventually.equal(keyOffset(0n) + 0n); + expect(this.mock.nonces(sender, ethers.Typed.uint192(17n))).to.eventually.equal(keyOffset(17n) + 2n); + }); + }); + + describe('_useCheckedNonce(address, uint256)', function () { + it('default variant uses key 0', async function () { + const currentNonce = await this.mock.nonces(sender, ethers.Typed.uint192(0n)); + + await this.mock.$_useCheckedNonce(sender, currentNonce); + + expect(this.mock.nonces(sender, ethers.Typed.uint192(0n))).to.eventually.equal(currentNonce + 1n); + }); + + it('use nonce at another key', async function () { + const currentNonce = await this.mock.nonces(sender, ethers.Typed.uint192(17n)); + + await this.mock.$_useCheckedNonce(sender, currentNonce); + + expect(this.mock.nonces(sender, ethers.Typed.uint192(17n))).to.eventually.equal(currentNonce + 1n); + }); + + it('reverts when nonce is not the expected', async function () { + const currentNonce = await this.mock.nonces(sender, ethers.Typed.uint192(42n)); + + // use and increment + await this.mock.$_useCheckedNonce(sender, currentNonce); + + // reuse same nonce + await expect(this.mock.$_useCheckedNonce(sender, currentNonce)) + .to.be.revertedWithCustomError(this.mock, 'InvalidAccountNonce') + .withArgs(sender, currentNonce + 1n); + + // use "future" nonce too early + await expect(this.mock.$_useCheckedNonce(sender, currentNonce + 10n)) + .to.be.revertedWithCustomError(this.mock, 'InvalidAccountNonce') + .withArgs(sender, currentNonce + 1n); + }); + }); + + describe('_useCheckedNonce(address, uint192, uint64)', function () { + const MASK = 0xffffffffffffffffn; + + it('default variant uses key 0', async function () { + const currentNonce = await this.mock.nonces(sender, ethers.Typed.uint192(0n)); + + await this.mock.$_useCheckedNonce(sender, ethers.Typed.uint192(0n), currentNonce); + + expect(this.mock.nonces(sender, ethers.Typed.uint192(0n))).to.eventually.equal(currentNonce + 1n); + }); + + it('use nonce at another key', async function () { + const currentNonce = await this.mock.nonces(sender, ethers.Typed.uint192(17n)); + + await this.mock.$_useCheckedNonce(sender, ethers.Typed.uint192(17n), currentNonce & MASK); + + expect(this.mock.nonces(sender, ethers.Typed.uint192(17n))).to.eventually.equal(currentNonce + 1n); + }); + + it('reverts when nonce is not the expected', async function () { + const currentNonce = await this.mock.nonces(sender, ethers.Typed.uint192(42n)); + + // use and increment + await this.mock.$_useCheckedNonce(sender, ethers.Typed.uint192(42n), currentNonce & MASK); + + // reuse same nonce + await expect(this.mock.$_useCheckedNonce(sender, ethers.Typed.uint192(42n), currentNonce & MASK)) + .to.be.revertedWithCustomError(this.mock, 'InvalidAccountNonce') + .withArgs(sender, currentNonce + 1n); + + // use "future" nonce too early + await expect(this.mock.$_useCheckedNonce(sender, ethers.Typed.uint192(42n), (currentNonce & MASK) + 10n)) + .to.be.revertedWithCustomError(this.mock, 'InvalidAccountNonce') + .withArgs(sender, currentNonce + 1n); + }); + }); + }); +} + +module.exports = { + shouldBehaveLikeNonces, + shouldBehaveLikeNoncesKeyed, +}; diff --git a/test/utils/Nonces.test.js b/test/utils/Nonces.test.js index 2cb4798de..85aa7358a 100644 --- a/test/utils/Nonces.test.js +++ b/test/utils/Nonces.test.js @@ -1,13 +1,10 @@ const { ethers } = require('hardhat'); -const { expect } = require('chai'); const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); +const { shouldBehaveLikeNonces } = require('./Nonces.behavior'); async function fixture() { - const [sender, other] = await ethers.getSigners(); - const mock = await ethers.deployContract('$Nonces'); - - return { sender, other, mock }; + return { mock }; } describe('Nonces', function () { @@ -15,61 +12,5 @@ describe('Nonces', function () { Object.assign(this, await loadFixture(fixture)); }); - it('gets a nonce', async function () { - expect(await this.mock.nonces(this.sender)).to.equal(0n); - }); - - describe('_useNonce', function () { - it('increments a nonce', async function () { - expect(await this.mock.nonces(this.sender)).to.equal(0n); - - await expect(await this.mock.$_useNonce(this.sender)) - .to.emit(this.mock, 'return$_useNonce') - .withArgs(0n); - - expect(await this.mock.nonces(this.sender)).to.equal(1n); - }); - - it("increments only sender's nonce", async function () { - expect(await this.mock.nonces(this.sender)).to.equal(0n); - expect(await this.mock.nonces(this.other)).to.equal(0n); - - await this.mock.$_useNonce(this.sender); - - expect(await this.mock.nonces(this.sender)).to.equal(1n); - expect(await this.mock.nonces(this.other)).to.equal(0n); - }); - }); - - describe('_useCheckedNonce', function () { - it('increments a nonce', async function () { - const currentNonce = await this.mock.nonces(this.sender); - - expect(currentNonce).to.equal(0n); - - await this.mock.$_useCheckedNonce(this.sender, currentNonce); - - expect(await this.mock.nonces(this.sender)).to.equal(1n); - }); - - it("increments only sender's nonce", async function () { - const currentNonce = await this.mock.nonces(this.sender); - - expect(currentNonce).to.equal(0n); - expect(await this.mock.nonces(this.other)).to.equal(0n); - - await this.mock.$_useCheckedNonce(this.sender, currentNonce); - - expect(await this.mock.nonces(this.sender)).to.equal(1n); - expect(await this.mock.nonces(this.other)).to.equal(0n); - }); - - it('reverts when nonce is not the expected', async function () { - const currentNonce = await this.mock.nonces(this.sender); - - await expect(this.mock.$_useCheckedNonce(this.sender, currentNonce + 1n)) - .to.be.revertedWithCustomError(this.mock, 'InvalidAccountNonce') - .withArgs(this.sender, currentNonce); - }); - }); + shouldBehaveLikeNonces(); }); diff --git a/test/utils/NoncesKeyed.test.js b/test/utils/NoncesKeyed.test.js new file mode 100644 index 000000000..c46948ee4 --- /dev/null +++ b/test/utils/NoncesKeyed.test.js @@ -0,0 +1,17 @@ +const { ethers } = require('hardhat'); +const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); +const { shouldBehaveLikeNonces, shouldBehaveLikeNoncesKeyed } = require('./Nonces.behavior'); + +async function fixture() { + const mock = await ethers.deployContract('$NoncesKeyed'); + return { mock }; +} + +describe('NoncesKeyed', function () { + beforeEach(async function () { + Object.assign(this, await loadFixture(fixture)); + }); + + shouldBehaveLikeNonces(); + shouldBehaveLikeNoncesKeyed(); +}); diff --git a/test/utils/Packing.t.sol b/test/utils/Packing.t.sol index 9531f1bff..40f052c80 100644 --- a/test/utils/Packing.t.sol +++ b/test/utils/Packing.t.sol @@ -9,182 +9,287 @@ import {Packing} from "@openzeppelin/contracts/utils/Packing.sol"; contract PackingTest is Test { using Packing for *; - function testPack(bytes1 left, bytes1 right) external { + function testPack(bytes1 left, bytes1 right) external pure { assertEq(left, Packing.pack_1_1(left, right).extract_2_1(0)); assertEq(right, Packing.pack_1_1(left, right).extract_2_1(1)); } - function testPack(bytes2 left, bytes2 right) external { + function testPack(bytes2 left, bytes2 right) external pure { assertEq(left, Packing.pack_2_2(left, right).extract_4_2(0)); assertEq(right, Packing.pack_2_2(left, right).extract_4_2(2)); } - function testPack(bytes2 left, bytes4 right) external { + function testPack(bytes2 left, bytes4 right) external pure { assertEq(left, Packing.pack_2_4(left, right).extract_6_2(0)); assertEq(right, Packing.pack_2_4(left, right).extract_6_4(2)); } - function testPack(bytes2 left, bytes6 right) external { + function testPack(bytes2 left, bytes6 right) external pure { assertEq(left, Packing.pack_2_6(left, right).extract_8_2(0)); assertEq(right, Packing.pack_2_6(left, right).extract_8_6(2)); } - function testPack(bytes4 left, bytes2 right) external { + function testPack(bytes2 left, bytes8 right) external pure { + assertEq(left, Packing.pack_2_8(left, right).extract_10_2(0)); + assertEq(right, Packing.pack_2_8(left, right).extract_10_8(2)); + } + + function testPack(bytes2 left, bytes10 right) external pure { + assertEq(left, Packing.pack_2_10(left, right).extract_12_2(0)); + assertEq(right, Packing.pack_2_10(left, right).extract_12_10(2)); + } + + function testPack(bytes2 left, bytes20 right) external pure { + assertEq(left, Packing.pack_2_20(left, right).extract_22_2(0)); + assertEq(right, Packing.pack_2_20(left, right).extract_22_20(2)); + } + + function testPack(bytes2 left, bytes22 right) external pure { + assertEq(left, Packing.pack_2_22(left, right).extract_24_2(0)); + assertEq(right, Packing.pack_2_22(left, right).extract_24_22(2)); + } + + function testPack(bytes4 left, bytes2 right) external pure { assertEq(left, Packing.pack_4_2(left, right).extract_6_4(0)); assertEq(right, Packing.pack_4_2(left, right).extract_6_2(4)); } - function testPack(bytes4 left, bytes4 right) external { + function testPack(bytes4 left, bytes4 right) external pure { assertEq(left, Packing.pack_4_4(left, right).extract_8_4(0)); assertEq(right, Packing.pack_4_4(left, right).extract_8_4(4)); } - function testPack(bytes4 left, bytes8 right) external { + function testPack(bytes4 left, bytes6 right) external pure { + assertEq(left, Packing.pack_4_6(left, right).extract_10_4(0)); + assertEq(right, Packing.pack_4_6(left, right).extract_10_6(4)); + } + + function testPack(bytes4 left, bytes8 right) external pure { assertEq(left, Packing.pack_4_8(left, right).extract_12_4(0)); assertEq(right, Packing.pack_4_8(left, right).extract_12_8(4)); } - function testPack(bytes4 left, bytes12 right) external { + function testPack(bytes4 left, bytes12 right) external pure { assertEq(left, Packing.pack_4_12(left, right).extract_16_4(0)); assertEq(right, Packing.pack_4_12(left, right).extract_16_12(4)); } - function testPack(bytes4 left, bytes16 right) external { + function testPack(bytes4 left, bytes16 right) external pure { assertEq(left, Packing.pack_4_16(left, right).extract_20_4(0)); assertEq(right, Packing.pack_4_16(left, right).extract_20_16(4)); } - function testPack(bytes4 left, bytes20 right) external { + function testPack(bytes4 left, bytes20 right) external pure { assertEq(left, Packing.pack_4_20(left, right).extract_24_4(0)); assertEq(right, Packing.pack_4_20(left, right).extract_24_20(4)); } - function testPack(bytes4 left, bytes24 right) external { + function testPack(bytes4 left, bytes24 right) external pure { assertEq(left, Packing.pack_4_24(left, right).extract_28_4(0)); assertEq(right, Packing.pack_4_24(left, right).extract_28_24(4)); } - function testPack(bytes4 left, bytes28 right) external { + function testPack(bytes4 left, bytes28 right) external pure { assertEq(left, Packing.pack_4_28(left, right).extract_32_4(0)); assertEq(right, Packing.pack_4_28(left, right).extract_32_28(4)); } - function testPack(bytes6 left, bytes2 right) external { + function testPack(bytes6 left, bytes2 right) external pure { assertEq(left, Packing.pack_6_2(left, right).extract_8_6(0)); assertEq(right, Packing.pack_6_2(left, right).extract_8_2(6)); } - function testPack(bytes6 left, bytes6 right) external { + function testPack(bytes6 left, bytes4 right) external pure { + assertEq(left, Packing.pack_6_4(left, right).extract_10_6(0)); + assertEq(right, Packing.pack_6_4(left, right).extract_10_4(6)); + } + + function testPack(bytes6 left, bytes6 right) external pure { assertEq(left, Packing.pack_6_6(left, right).extract_12_6(0)); assertEq(right, Packing.pack_6_6(left, right).extract_12_6(6)); } - function testPack(bytes8 left, bytes4 right) external { + function testPack(bytes6 left, bytes10 right) external pure { + assertEq(left, Packing.pack_6_10(left, right).extract_16_6(0)); + assertEq(right, Packing.pack_6_10(left, right).extract_16_10(6)); + } + + function testPack(bytes6 left, bytes16 right) external pure { + assertEq(left, Packing.pack_6_16(left, right).extract_22_6(0)); + assertEq(right, Packing.pack_6_16(left, right).extract_22_16(6)); + } + + function testPack(bytes6 left, bytes22 right) external pure { + assertEq(left, Packing.pack_6_22(left, right).extract_28_6(0)); + assertEq(right, Packing.pack_6_22(left, right).extract_28_22(6)); + } + + function testPack(bytes8 left, bytes2 right) external pure { + assertEq(left, Packing.pack_8_2(left, right).extract_10_8(0)); + assertEq(right, Packing.pack_8_2(left, right).extract_10_2(8)); + } + + function testPack(bytes8 left, bytes4 right) external pure { assertEq(left, Packing.pack_8_4(left, right).extract_12_8(0)); assertEq(right, Packing.pack_8_4(left, right).extract_12_4(8)); } - function testPack(bytes8 left, bytes8 right) external { + function testPack(bytes8 left, bytes8 right) external pure { assertEq(left, Packing.pack_8_8(left, right).extract_16_8(0)); assertEq(right, Packing.pack_8_8(left, right).extract_16_8(8)); } - function testPack(bytes8 left, bytes12 right) external { + function testPack(bytes8 left, bytes12 right) external pure { assertEq(left, Packing.pack_8_12(left, right).extract_20_8(0)); assertEq(right, Packing.pack_8_12(left, right).extract_20_12(8)); } - function testPack(bytes8 left, bytes16 right) external { + function testPack(bytes8 left, bytes16 right) external pure { assertEq(left, Packing.pack_8_16(left, right).extract_24_8(0)); assertEq(right, Packing.pack_8_16(left, right).extract_24_16(8)); } - function testPack(bytes8 left, bytes20 right) external { + function testPack(bytes8 left, bytes20 right) external pure { assertEq(left, Packing.pack_8_20(left, right).extract_28_8(0)); assertEq(right, Packing.pack_8_20(left, right).extract_28_20(8)); } - function testPack(bytes8 left, bytes24 right) external { + function testPack(bytes8 left, bytes24 right) external pure { assertEq(left, Packing.pack_8_24(left, right).extract_32_8(0)); assertEq(right, Packing.pack_8_24(left, right).extract_32_24(8)); } - function testPack(bytes12 left, bytes4 right) external { + function testPack(bytes10 left, bytes2 right) external pure { + assertEq(left, Packing.pack_10_2(left, right).extract_12_10(0)); + assertEq(right, Packing.pack_10_2(left, right).extract_12_2(10)); + } + + function testPack(bytes10 left, bytes6 right) external pure { + assertEq(left, Packing.pack_10_6(left, right).extract_16_10(0)); + assertEq(right, Packing.pack_10_6(left, right).extract_16_6(10)); + } + + function testPack(bytes10 left, bytes10 right) external pure { + assertEq(left, Packing.pack_10_10(left, right).extract_20_10(0)); + assertEq(right, Packing.pack_10_10(left, right).extract_20_10(10)); + } + + function testPack(bytes10 left, bytes12 right) external pure { + assertEq(left, Packing.pack_10_12(left, right).extract_22_10(0)); + assertEq(right, Packing.pack_10_12(left, right).extract_22_12(10)); + } + + function testPack(bytes10 left, bytes22 right) external pure { + assertEq(left, Packing.pack_10_22(left, right).extract_32_10(0)); + assertEq(right, Packing.pack_10_22(left, right).extract_32_22(10)); + } + + function testPack(bytes12 left, bytes4 right) external pure { assertEq(left, Packing.pack_12_4(left, right).extract_16_12(0)); assertEq(right, Packing.pack_12_4(left, right).extract_16_4(12)); } - function testPack(bytes12 left, bytes8 right) external { + function testPack(bytes12 left, bytes8 right) external pure { assertEq(left, Packing.pack_12_8(left, right).extract_20_12(0)); assertEq(right, Packing.pack_12_8(left, right).extract_20_8(12)); } - function testPack(bytes12 left, bytes12 right) external { + function testPack(bytes12 left, bytes10 right) external pure { + assertEq(left, Packing.pack_12_10(left, right).extract_22_12(0)); + assertEq(right, Packing.pack_12_10(left, right).extract_22_10(12)); + } + + function testPack(bytes12 left, bytes12 right) external pure { assertEq(left, Packing.pack_12_12(left, right).extract_24_12(0)); assertEq(right, Packing.pack_12_12(left, right).extract_24_12(12)); } - function testPack(bytes12 left, bytes16 right) external { + function testPack(bytes12 left, bytes16 right) external pure { assertEq(left, Packing.pack_12_16(left, right).extract_28_12(0)); assertEq(right, Packing.pack_12_16(left, right).extract_28_16(12)); } - function testPack(bytes12 left, bytes20 right) external { + function testPack(bytes12 left, bytes20 right) external pure { assertEq(left, Packing.pack_12_20(left, right).extract_32_12(0)); assertEq(right, Packing.pack_12_20(left, right).extract_32_20(12)); } - function testPack(bytes16 left, bytes4 right) external { + function testPack(bytes16 left, bytes4 right) external pure { assertEq(left, Packing.pack_16_4(left, right).extract_20_16(0)); assertEq(right, Packing.pack_16_4(left, right).extract_20_4(16)); } - function testPack(bytes16 left, bytes8 right) external { + function testPack(bytes16 left, bytes6 right) external pure { + assertEq(left, Packing.pack_16_6(left, right).extract_22_16(0)); + assertEq(right, Packing.pack_16_6(left, right).extract_22_6(16)); + } + + function testPack(bytes16 left, bytes8 right) external pure { assertEq(left, Packing.pack_16_8(left, right).extract_24_16(0)); assertEq(right, Packing.pack_16_8(left, right).extract_24_8(16)); } - function testPack(bytes16 left, bytes12 right) external { + function testPack(bytes16 left, bytes12 right) external pure { assertEq(left, Packing.pack_16_12(left, right).extract_28_16(0)); assertEq(right, Packing.pack_16_12(left, right).extract_28_12(16)); } - function testPack(bytes16 left, bytes16 right) external { + function testPack(bytes16 left, bytes16 right) external pure { assertEq(left, Packing.pack_16_16(left, right).extract_32_16(0)); assertEq(right, Packing.pack_16_16(left, right).extract_32_16(16)); } - function testPack(bytes20 left, bytes4 right) external { + function testPack(bytes20 left, bytes2 right) external pure { + assertEq(left, Packing.pack_20_2(left, right).extract_22_20(0)); + assertEq(right, Packing.pack_20_2(left, right).extract_22_2(20)); + } + + function testPack(bytes20 left, bytes4 right) external pure { assertEq(left, Packing.pack_20_4(left, right).extract_24_20(0)); assertEq(right, Packing.pack_20_4(left, right).extract_24_4(20)); } - function testPack(bytes20 left, bytes8 right) external { + function testPack(bytes20 left, bytes8 right) external pure { assertEq(left, Packing.pack_20_8(left, right).extract_28_20(0)); assertEq(right, Packing.pack_20_8(left, right).extract_28_8(20)); } - function testPack(bytes20 left, bytes12 right) external { + function testPack(bytes20 left, bytes12 right) external pure { assertEq(left, Packing.pack_20_12(left, right).extract_32_20(0)); assertEq(right, Packing.pack_20_12(left, right).extract_32_12(20)); } - function testPack(bytes24 left, bytes4 right) external { + function testPack(bytes22 left, bytes2 right) external pure { + assertEq(left, Packing.pack_22_2(left, right).extract_24_22(0)); + assertEq(right, Packing.pack_22_2(left, right).extract_24_2(22)); + } + + function testPack(bytes22 left, bytes6 right) external pure { + assertEq(left, Packing.pack_22_6(left, right).extract_28_22(0)); + assertEq(right, Packing.pack_22_6(left, right).extract_28_6(22)); + } + + function testPack(bytes22 left, bytes10 right) external pure { + assertEq(left, Packing.pack_22_10(left, right).extract_32_22(0)); + assertEq(right, Packing.pack_22_10(left, right).extract_32_10(22)); + } + + function testPack(bytes24 left, bytes4 right) external pure { assertEq(left, Packing.pack_24_4(left, right).extract_28_24(0)); assertEq(right, Packing.pack_24_4(left, right).extract_28_4(24)); } - function testPack(bytes24 left, bytes8 right) external { + function testPack(bytes24 left, bytes8 right) external pure { assertEq(left, Packing.pack_24_8(left, right).extract_32_24(0)); assertEq(right, Packing.pack_24_8(left, right).extract_32_8(24)); } - function testPack(bytes28 left, bytes4 right) external { + function testPack(bytes28 left, bytes4 right) external pure { assertEq(left, Packing.pack_28_4(left, right).extract_32_28(0)); assertEq(right, Packing.pack_28_4(left, right).extract_32_4(28)); } - function testReplace(bytes2 container, bytes1 newValue, uint8 offset) external { + function testReplace(bytes2 container, bytes1 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 1)); bytes1 oldValue = container.extract_2_1(offset); @@ -193,7 +298,7 @@ contract PackingTest is Test { assertEq(container, container.replace_2_1(newValue, offset).replace_2_1(oldValue, offset)); } - function testReplace(bytes4 container, bytes1 newValue, uint8 offset) external { + function testReplace(bytes4 container, bytes1 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 3)); bytes1 oldValue = container.extract_4_1(offset); @@ -202,7 +307,7 @@ contract PackingTest is Test { assertEq(container, container.replace_4_1(newValue, offset).replace_4_1(oldValue, offset)); } - function testReplace(bytes4 container, bytes2 newValue, uint8 offset) external { + function testReplace(bytes4 container, bytes2 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 2)); bytes2 oldValue = container.extract_4_2(offset); @@ -211,7 +316,7 @@ contract PackingTest is Test { assertEq(container, container.replace_4_2(newValue, offset).replace_4_2(oldValue, offset)); } - function testReplace(bytes6 container, bytes1 newValue, uint8 offset) external { + function testReplace(bytes6 container, bytes1 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 5)); bytes1 oldValue = container.extract_6_1(offset); @@ -220,7 +325,7 @@ contract PackingTest is Test { assertEq(container, container.replace_6_1(newValue, offset).replace_6_1(oldValue, offset)); } - function testReplace(bytes6 container, bytes2 newValue, uint8 offset) external { + function testReplace(bytes6 container, bytes2 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 4)); bytes2 oldValue = container.extract_6_2(offset); @@ -229,7 +334,7 @@ contract PackingTest is Test { assertEq(container, container.replace_6_2(newValue, offset).replace_6_2(oldValue, offset)); } - function testReplace(bytes6 container, bytes4 newValue, uint8 offset) external { + function testReplace(bytes6 container, bytes4 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 2)); bytes4 oldValue = container.extract_6_4(offset); @@ -238,7 +343,7 @@ contract PackingTest is Test { assertEq(container, container.replace_6_4(newValue, offset).replace_6_4(oldValue, offset)); } - function testReplace(bytes8 container, bytes1 newValue, uint8 offset) external { + function testReplace(bytes8 container, bytes1 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 7)); bytes1 oldValue = container.extract_8_1(offset); @@ -247,7 +352,7 @@ contract PackingTest is Test { assertEq(container, container.replace_8_1(newValue, offset).replace_8_1(oldValue, offset)); } - function testReplace(bytes8 container, bytes2 newValue, uint8 offset) external { + function testReplace(bytes8 container, bytes2 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 6)); bytes2 oldValue = container.extract_8_2(offset); @@ -256,7 +361,7 @@ contract PackingTest is Test { assertEq(container, container.replace_8_2(newValue, offset).replace_8_2(oldValue, offset)); } - function testReplace(bytes8 container, bytes4 newValue, uint8 offset) external { + function testReplace(bytes8 container, bytes4 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 4)); bytes4 oldValue = container.extract_8_4(offset); @@ -265,7 +370,7 @@ contract PackingTest is Test { assertEq(container, container.replace_8_4(newValue, offset).replace_8_4(oldValue, offset)); } - function testReplace(bytes8 container, bytes6 newValue, uint8 offset) external { + function testReplace(bytes8 container, bytes6 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 2)); bytes6 oldValue = container.extract_8_6(offset); @@ -274,7 +379,52 @@ contract PackingTest is Test { assertEq(container, container.replace_8_6(newValue, offset).replace_8_6(oldValue, offset)); } - function testReplace(bytes12 container, bytes1 newValue, uint8 offset) external { + function testReplace(bytes10 container, bytes1 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 9)); + + bytes1 oldValue = container.extract_10_1(offset); + + assertEq(newValue, container.replace_10_1(newValue, offset).extract_10_1(offset)); + assertEq(container, container.replace_10_1(newValue, offset).replace_10_1(oldValue, offset)); + } + + function testReplace(bytes10 container, bytes2 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 8)); + + bytes2 oldValue = container.extract_10_2(offset); + + assertEq(newValue, container.replace_10_2(newValue, offset).extract_10_2(offset)); + assertEq(container, container.replace_10_2(newValue, offset).replace_10_2(oldValue, offset)); + } + + function testReplace(bytes10 container, bytes4 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 6)); + + bytes4 oldValue = container.extract_10_4(offset); + + assertEq(newValue, container.replace_10_4(newValue, offset).extract_10_4(offset)); + assertEq(container, container.replace_10_4(newValue, offset).replace_10_4(oldValue, offset)); + } + + function testReplace(bytes10 container, bytes6 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 4)); + + bytes6 oldValue = container.extract_10_6(offset); + + assertEq(newValue, container.replace_10_6(newValue, offset).extract_10_6(offset)); + assertEq(container, container.replace_10_6(newValue, offset).replace_10_6(oldValue, offset)); + } + + function testReplace(bytes10 container, bytes8 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 2)); + + bytes8 oldValue = container.extract_10_8(offset); + + assertEq(newValue, container.replace_10_8(newValue, offset).extract_10_8(offset)); + assertEq(container, container.replace_10_8(newValue, offset).replace_10_8(oldValue, offset)); + } + + function testReplace(bytes12 container, bytes1 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 11)); bytes1 oldValue = container.extract_12_1(offset); @@ -283,7 +433,7 @@ contract PackingTest is Test { assertEq(container, container.replace_12_1(newValue, offset).replace_12_1(oldValue, offset)); } - function testReplace(bytes12 container, bytes2 newValue, uint8 offset) external { + function testReplace(bytes12 container, bytes2 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 10)); bytes2 oldValue = container.extract_12_2(offset); @@ -292,7 +442,7 @@ contract PackingTest is Test { assertEq(container, container.replace_12_2(newValue, offset).replace_12_2(oldValue, offset)); } - function testReplace(bytes12 container, bytes4 newValue, uint8 offset) external { + function testReplace(bytes12 container, bytes4 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 8)); bytes4 oldValue = container.extract_12_4(offset); @@ -301,7 +451,7 @@ contract PackingTest is Test { assertEq(container, container.replace_12_4(newValue, offset).replace_12_4(oldValue, offset)); } - function testReplace(bytes12 container, bytes6 newValue, uint8 offset) external { + function testReplace(bytes12 container, bytes6 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 6)); bytes6 oldValue = container.extract_12_6(offset); @@ -310,7 +460,7 @@ contract PackingTest is Test { assertEq(container, container.replace_12_6(newValue, offset).replace_12_6(oldValue, offset)); } - function testReplace(bytes12 container, bytes8 newValue, uint8 offset) external { + function testReplace(bytes12 container, bytes8 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 4)); bytes8 oldValue = container.extract_12_8(offset); @@ -319,7 +469,16 @@ contract PackingTest is Test { assertEq(container, container.replace_12_8(newValue, offset).replace_12_8(oldValue, offset)); } - function testReplace(bytes16 container, bytes1 newValue, uint8 offset) external { + function testReplace(bytes12 container, bytes10 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 2)); + + bytes10 oldValue = container.extract_12_10(offset); + + assertEq(newValue, container.replace_12_10(newValue, offset).extract_12_10(offset)); + assertEq(container, container.replace_12_10(newValue, offset).replace_12_10(oldValue, offset)); + } + + function testReplace(bytes16 container, bytes1 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 15)); bytes1 oldValue = container.extract_16_1(offset); @@ -328,7 +487,7 @@ contract PackingTest is Test { assertEq(container, container.replace_16_1(newValue, offset).replace_16_1(oldValue, offset)); } - function testReplace(bytes16 container, bytes2 newValue, uint8 offset) external { + function testReplace(bytes16 container, bytes2 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 14)); bytes2 oldValue = container.extract_16_2(offset); @@ -337,7 +496,7 @@ contract PackingTest is Test { assertEq(container, container.replace_16_2(newValue, offset).replace_16_2(oldValue, offset)); } - function testReplace(bytes16 container, bytes4 newValue, uint8 offset) external { + function testReplace(bytes16 container, bytes4 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 12)); bytes4 oldValue = container.extract_16_4(offset); @@ -346,7 +505,7 @@ contract PackingTest is Test { assertEq(container, container.replace_16_4(newValue, offset).replace_16_4(oldValue, offset)); } - function testReplace(bytes16 container, bytes6 newValue, uint8 offset) external { + function testReplace(bytes16 container, bytes6 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 10)); bytes6 oldValue = container.extract_16_6(offset); @@ -355,7 +514,7 @@ contract PackingTest is Test { assertEq(container, container.replace_16_6(newValue, offset).replace_16_6(oldValue, offset)); } - function testReplace(bytes16 container, bytes8 newValue, uint8 offset) external { + function testReplace(bytes16 container, bytes8 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 8)); bytes8 oldValue = container.extract_16_8(offset); @@ -364,7 +523,16 @@ contract PackingTest is Test { assertEq(container, container.replace_16_8(newValue, offset).replace_16_8(oldValue, offset)); } - function testReplace(bytes16 container, bytes12 newValue, uint8 offset) external { + function testReplace(bytes16 container, bytes10 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 6)); + + bytes10 oldValue = container.extract_16_10(offset); + + assertEq(newValue, container.replace_16_10(newValue, offset).extract_16_10(offset)); + assertEq(container, container.replace_16_10(newValue, offset).replace_16_10(oldValue, offset)); + } + + function testReplace(bytes16 container, bytes12 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 4)); bytes12 oldValue = container.extract_16_12(offset); @@ -373,7 +541,7 @@ contract PackingTest is Test { assertEq(container, container.replace_16_12(newValue, offset).replace_16_12(oldValue, offset)); } - function testReplace(bytes20 container, bytes1 newValue, uint8 offset) external { + function testReplace(bytes20 container, bytes1 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 19)); bytes1 oldValue = container.extract_20_1(offset); @@ -382,7 +550,7 @@ contract PackingTest is Test { assertEq(container, container.replace_20_1(newValue, offset).replace_20_1(oldValue, offset)); } - function testReplace(bytes20 container, bytes2 newValue, uint8 offset) external { + function testReplace(bytes20 container, bytes2 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 18)); bytes2 oldValue = container.extract_20_2(offset); @@ -391,7 +559,7 @@ contract PackingTest is Test { assertEq(container, container.replace_20_2(newValue, offset).replace_20_2(oldValue, offset)); } - function testReplace(bytes20 container, bytes4 newValue, uint8 offset) external { + function testReplace(bytes20 container, bytes4 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 16)); bytes4 oldValue = container.extract_20_4(offset); @@ -400,7 +568,7 @@ contract PackingTest is Test { assertEq(container, container.replace_20_4(newValue, offset).replace_20_4(oldValue, offset)); } - function testReplace(bytes20 container, bytes6 newValue, uint8 offset) external { + function testReplace(bytes20 container, bytes6 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 14)); bytes6 oldValue = container.extract_20_6(offset); @@ -409,7 +577,7 @@ contract PackingTest is Test { assertEq(container, container.replace_20_6(newValue, offset).replace_20_6(oldValue, offset)); } - function testReplace(bytes20 container, bytes8 newValue, uint8 offset) external { + function testReplace(bytes20 container, bytes8 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 12)); bytes8 oldValue = container.extract_20_8(offset); @@ -418,7 +586,16 @@ contract PackingTest is Test { assertEq(container, container.replace_20_8(newValue, offset).replace_20_8(oldValue, offset)); } - function testReplace(bytes20 container, bytes12 newValue, uint8 offset) external { + function testReplace(bytes20 container, bytes10 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 10)); + + bytes10 oldValue = container.extract_20_10(offset); + + assertEq(newValue, container.replace_20_10(newValue, offset).extract_20_10(offset)); + assertEq(container, container.replace_20_10(newValue, offset).replace_20_10(oldValue, offset)); + } + + function testReplace(bytes20 container, bytes12 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 8)); bytes12 oldValue = container.extract_20_12(offset); @@ -427,7 +604,7 @@ contract PackingTest is Test { assertEq(container, container.replace_20_12(newValue, offset).replace_20_12(oldValue, offset)); } - function testReplace(bytes20 container, bytes16 newValue, uint8 offset) external { + function testReplace(bytes20 container, bytes16 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 4)); bytes16 oldValue = container.extract_20_16(offset); @@ -436,7 +613,88 @@ contract PackingTest is Test { assertEq(container, container.replace_20_16(newValue, offset).replace_20_16(oldValue, offset)); } - function testReplace(bytes24 container, bytes1 newValue, uint8 offset) external { + function testReplace(bytes22 container, bytes1 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 21)); + + bytes1 oldValue = container.extract_22_1(offset); + + assertEq(newValue, container.replace_22_1(newValue, offset).extract_22_1(offset)); + assertEq(container, container.replace_22_1(newValue, offset).replace_22_1(oldValue, offset)); + } + + function testReplace(bytes22 container, bytes2 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 20)); + + bytes2 oldValue = container.extract_22_2(offset); + + assertEq(newValue, container.replace_22_2(newValue, offset).extract_22_2(offset)); + assertEq(container, container.replace_22_2(newValue, offset).replace_22_2(oldValue, offset)); + } + + function testReplace(bytes22 container, bytes4 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 18)); + + bytes4 oldValue = container.extract_22_4(offset); + + assertEq(newValue, container.replace_22_4(newValue, offset).extract_22_4(offset)); + assertEq(container, container.replace_22_4(newValue, offset).replace_22_4(oldValue, offset)); + } + + function testReplace(bytes22 container, bytes6 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 16)); + + bytes6 oldValue = container.extract_22_6(offset); + + assertEq(newValue, container.replace_22_6(newValue, offset).extract_22_6(offset)); + assertEq(container, container.replace_22_6(newValue, offset).replace_22_6(oldValue, offset)); + } + + function testReplace(bytes22 container, bytes8 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 14)); + + bytes8 oldValue = container.extract_22_8(offset); + + assertEq(newValue, container.replace_22_8(newValue, offset).extract_22_8(offset)); + assertEq(container, container.replace_22_8(newValue, offset).replace_22_8(oldValue, offset)); + } + + function testReplace(bytes22 container, bytes10 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 12)); + + bytes10 oldValue = container.extract_22_10(offset); + + assertEq(newValue, container.replace_22_10(newValue, offset).extract_22_10(offset)); + assertEq(container, container.replace_22_10(newValue, offset).replace_22_10(oldValue, offset)); + } + + function testReplace(bytes22 container, bytes12 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 10)); + + bytes12 oldValue = container.extract_22_12(offset); + + assertEq(newValue, container.replace_22_12(newValue, offset).extract_22_12(offset)); + assertEq(container, container.replace_22_12(newValue, offset).replace_22_12(oldValue, offset)); + } + + function testReplace(bytes22 container, bytes16 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 6)); + + bytes16 oldValue = container.extract_22_16(offset); + + assertEq(newValue, container.replace_22_16(newValue, offset).extract_22_16(offset)); + assertEq(container, container.replace_22_16(newValue, offset).replace_22_16(oldValue, offset)); + } + + function testReplace(bytes22 container, bytes20 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 2)); + + bytes20 oldValue = container.extract_22_20(offset); + + assertEq(newValue, container.replace_22_20(newValue, offset).extract_22_20(offset)); + assertEq(container, container.replace_22_20(newValue, offset).replace_22_20(oldValue, offset)); + } + + function testReplace(bytes24 container, bytes1 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 23)); bytes1 oldValue = container.extract_24_1(offset); @@ -445,7 +703,7 @@ contract PackingTest is Test { assertEq(container, container.replace_24_1(newValue, offset).replace_24_1(oldValue, offset)); } - function testReplace(bytes24 container, bytes2 newValue, uint8 offset) external { + function testReplace(bytes24 container, bytes2 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 22)); bytes2 oldValue = container.extract_24_2(offset); @@ -454,7 +712,7 @@ contract PackingTest is Test { assertEq(container, container.replace_24_2(newValue, offset).replace_24_2(oldValue, offset)); } - function testReplace(bytes24 container, bytes4 newValue, uint8 offset) external { + function testReplace(bytes24 container, bytes4 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 20)); bytes4 oldValue = container.extract_24_4(offset); @@ -463,7 +721,7 @@ contract PackingTest is Test { assertEq(container, container.replace_24_4(newValue, offset).replace_24_4(oldValue, offset)); } - function testReplace(bytes24 container, bytes6 newValue, uint8 offset) external { + function testReplace(bytes24 container, bytes6 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 18)); bytes6 oldValue = container.extract_24_6(offset); @@ -472,7 +730,7 @@ contract PackingTest is Test { assertEq(container, container.replace_24_6(newValue, offset).replace_24_6(oldValue, offset)); } - function testReplace(bytes24 container, bytes8 newValue, uint8 offset) external { + function testReplace(bytes24 container, bytes8 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 16)); bytes8 oldValue = container.extract_24_8(offset); @@ -481,7 +739,16 @@ contract PackingTest is Test { assertEq(container, container.replace_24_8(newValue, offset).replace_24_8(oldValue, offset)); } - function testReplace(bytes24 container, bytes12 newValue, uint8 offset) external { + function testReplace(bytes24 container, bytes10 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 14)); + + bytes10 oldValue = container.extract_24_10(offset); + + assertEq(newValue, container.replace_24_10(newValue, offset).extract_24_10(offset)); + assertEq(container, container.replace_24_10(newValue, offset).replace_24_10(oldValue, offset)); + } + + function testReplace(bytes24 container, bytes12 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 12)); bytes12 oldValue = container.extract_24_12(offset); @@ -490,7 +757,7 @@ contract PackingTest is Test { assertEq(container, container.replace_24_12(newValue, offset).replace_24_12(oldValue, offset)); } - function testReplace(bytes24 container, bytes16 newValue, uint8 offset) external { + function testReplace(bytes24 container, bytes16 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 8)); bytes16 oldValue = container.extract_24_16(offset); @@ -499,7 +766,7 @@ contract PackingTest is Test { assertEq(container, container.replace_24_16(newValue, offset).replace_24_16(oldValue, offset)); } - function testReplace(bytes24 container, bytes20 newValue, uint8 offset) external { + function testReplace(bytes24 container, bytes20 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 4)); bytes20 oldValue = container.extract_24_20(offset); @@ -508,7 +775,16 @@ contract PackingTest is Test { assertEq(container, container.replace_24_20(newValue, offset).replace_24_20(oldValue, offset)); } - function testReplace(bytes28 container, bytes1 newValue, uint8 offset) external { + function testReplace(bytes24 container, bytes22 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 2)); + + bytes22 oldValue = container.extract_24_22(offset); + + assertEq(newValue, container.replace_24_22(newValue, offset).extract_24_22(offset)); + assertEq(container, container.replace_24_22(newValue, offset).replace_24_22(oldValue, offset)); + } + + function testReplace(bytes28 container, bytes1 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 27)); bytes1 oldValue = container.extract_28_1(offset); @@ -517,7 +793,7 @@ contract PackingTest is Test { assertEq(container, container.replace_28_1(newValue, offset).replace_28_1(oldValue, offset)); } - function testReplace(bytes28 container, bytes2 newValue, uint8 offset) external { + function testReplace(bytes28 container, bytes2 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 26)); bytes2 oldValue = container.extract_28_2(offset); @@ -526,7 +802,7 @@ contract PackingTest is Test { assertEq(container, container.replace_28_2(newValue, offset).replace_28_2(oldValue, offset)); } - function testReplace(bytes28 container, bytes4 newValue, uint8 offset) external { + function testReplace(bytes28 container, bytes4 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 24)); bytes4 oldValue = container.extract_28_4(offset); @@ -535,7 +811,7 @@ contract PackingTest is Test { assertEq(container, container.replace_28_4(newValue, offset).replace_28_4(oldValue, offset)); } - function testReplace(bytes28 container, bytes6 newValue, uint8 offset) external { + function testReplace(bytes28 container, bytes6 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 22)); bytes6 oldValue = container.extract_28_6(offset); @@ -544,7 +820,7 @@ contract PackingTest is Test { assertEq(container, container.replace_28_6(newValue, offset).replace_28_6(oldValue, offset)); } - function testReplace(bytes28 container, bytes8 newValue, uint8 offset) external { + function testReplace(bytes28 container, bytes8 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 20)); bytes8 oldValue = container.extract_28_8(offset); @@ -553,7 +829,16 @@ contract PackingTest is Test { assertEq(container, container.replace_28_8(newValue, offset).replace_28_8(oldValue, offset)); } - function testReplace(bytes28 container, bytes12 newValue, uint8 offset) external { + function testReplace(bytes28 container, bytes10 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 18)); + + bytes10 oldValue = container.extract_28_10(offset); + + assertEq(newValue, container.replace_28_10(newValue, offset).extract_28_10(offset)); + assertEq(container, container.replace_28_10(newValue, offset).replace_28_10(oldValue, offset)); + } + + function testReplace(bytes28 container, bytes12 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 16)); bytes12 oldValue = container.extract_28_12(offset); @@ -562,7 +847,7 @@ contract PackingTest is Test { assertEq(container, container.replace_28_12(newValue, offset).replace_28_12(oldValue, offset)); } - function testReplace(bytes28 container, bytes16 newValue, uint8 offset) external { + function testReplace(bytes28 container, bytes16 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 12)); bytes16 oldValue = container.extract_28_16(offset); @@ -571,7 +856,7 @@ contract PackingTest is Test { assertEq(container, container.replace_28_16(newValue, offset).replace_28_16(oldValue, offset)); } - function testReplace(bytes28 container, bytes20 newValue, uint8 offset) external { + function testReplace(bytes28 container, bytes20 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 8)); bytes20 oldValue = container.extract_28_20(offset); @@ -580,7 +865,16 @@ contract PackingTest is Test { assertEq(container, container.replace_28_20(newValue, offset).replace_28_20(oldValue, offset)); } - function testReplace(bytes28 container, bytes24 newValue, uint8 offset) external { + function testReplace(bytes28 container, bytes22 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 6)); + + bytes22 oldValue = container.extract_28_22(offset); + + assertEq(newValue, container.replace_28_22(newValue, offset).extract_28_22(offset)); + assertEq(container, container.replace_28_22(newValue, offset).replace_28_22(oldValue, offset)); + } + + function testReplace(bytes28 container, bytes24 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 4)); bytes24 oldValue = container.extract_28_24(offset); @@ -589,7 +883,7 @@ contract PackingTest is Test { assertEq(container, container.replace_28_24(newValue, offset).replace_28_24(oldValue, offset)); } - function testReplace(bytes32 container, bytes1 newValue, uint8 offset) external { + function testReplace(bytes32 container, bytes1 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 31)); bytes1 oldValue = container.extract_32_1(offset); @@ -598,7 +892,7 @@ contract PackingTest is Test { assertEq(container, container.replace_32_1(newValue, offset).replace_32_1(oldValue, offset)); } - function testReplace(bytes32 container, bytes2 newValue, uint8 offset) external { + function testReplace(bytes32 container, bytes2 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 30)); bytes2 oldValue = container.extract_32_2(offset); @@ -607,7 +901,7 @@ contract PackingTest is Test { assertEq(container, container.replace_32_2(newValue, offset).replace_32_2(oldValue, offset)); } - function testReplace(bytes32 container, bytes4 newValue, uint8 offset) external { + function testReplace(bytes32 container, bytes4 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 28)); bytes4 oldValue = container.extract_32_4(offset); @@ -616,7 +910,7 @@ contract PackingTest is Test { assertEq(container, container.replace_32_4(newValue, offset).replace_32_4(oldValue, offset)); } - function testReplace(bytes32 container, bytes6 newValue, uint8 offset) external { + function testReplace(bytes32 container, bytes6 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 26)); bytes6 oldValue = container.extract_32_6(offset); @@ -625,7 +919,7 @@ contract PackingTest is Test { assertEq(container, container.replace_32_6(newValue, offset).replace_32_6(oldValue, offset)); } - function testReplace(bytes32 container, bytes8 newValue, uint8 offset) external { + function testReplace(bytes32 container, bytes8 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 24)); bytes8 oldValue = container.extract_32_8(offset); @@ -634,7 +928,16 @@ contract PackingTest is Test { assertEq(container, container.replace_32_8(newValue, offset).replace_32_8(oldValue, offset)); } - function testReplace(bytes32 container, bytes12 newValue, uint8 offset) external { + function testReplace(bytes32 container, bytes10 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 22)); + + bytes10 oldValue = container.extract_32_10(offset); + + assertEq(newValue, container.replace_32_10(newValue, offset).extract_32_10(offset)); + assertEq(container, container.replace_32_10(newValue, offset).replace_32_10(oldValue, offset)); + } + + function testReplace(bytes32 container, bytes12 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 20)); bytes12 oldValue = container.extract_32_12(offset); @@ -643,7 +946,7 @@ contract PackingTest is Test { assertEq(container, container.replace_32_12(newValue, offset).replace_32_12(oldValue, offset)); } - function testReplace(bytes32 container, bytes16 newValue, uint8 offset) external { + function testReplace(bytes32 container, bytes16 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 16)); bytes16 oldValue = container.extract_32_16(offset); @@ -652,7 +955,7 @@ contract PackingTest is Test { assertEq(container, container.replace_32_16(newValue, offset).replace_32_16(oldValue, offset)); } - function testReplace(bytes32 container, bytes20 newValue, uint8 offset) external { + function testReplace(bytes32 container, bytes20 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 12)); bytes20 oldValue = container.extract_32_20(offset); @@ -661,7 +964,16 @@ contract PackingTest is Test { assertEq(container, container.replace_32_20(newValue, offset).replace_32_20(oldValue, offset)); } - function testReplace(bytes32 container, bytes24 newValue, uint8 offset) external { + function testReplace(bytes32 container, bytes22 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 10)); + + bytes22 oldValue = container.extract_32_22(offset); + + assertEq(newValue, container.replace_32_22(newValue, offset).extract_32_22(offset)); + assertEq(container, container.replace_32_22(newValue, offset).replace_32_22(oldValue, offset)); + } + + function testReplace(bytes32 container, bytes24 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 8)); bytes24 oldValue = container.extract_32_24(offset); @@ -670,7 +982,7 @@ contract PackingTest is Test { assertEq(container, container.replace_32_24(newValue, offset).replace_32_24(oldValue, offset)); } - function testReplace(bytes32 container, bytes28 newValue, uint8 offset) external { + function testReplace(bytes32 container, bytes28 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 4)); bytes28 oldValue = container.extract_32_28(offset); diff --git a/test/utils/Strings.t.sol b/test/utils/Strings.t.sol new file mode 100644 index 000000000..f59e675cd --- /dev/null +++ b/test/utils/Strings.t.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {Test} from "forge-std/Test.sol"; + +import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; + +contract StringsTest is Test { + using Strings for *; + + function testParse(uint256 value) external { + assertEq(value, value.toString().parseUint()); + } + + function testParseSigned(int256 value) external { + assertEq(value, value.toStringSigned().parseInt()); + } + + function testParseHex(uint256 value) external { + assertEq(value, value.toHexString().parseHexUint()); + } + + function testParseChecksumHex(address value) external { + assertEq(value, value.toChecksumHexString().parseAddress()); + } + + function testTryParseHexUintExtendedEnd(string memory random) external pure { + uint256 length = bytes(random).length; + assembly ("memory-safe") { + mstore(add(add(random, 0x20), length), 0x3030303030303030303030303030303030303030303030303030303030303030) + } + + (bool success, ) = random.tryParseHexUint(1, length + 1); + assertFalse(success); + } + + function testTryParseAddressExtendedEnd(address random, uint256 begin) external pure { + begin = bound(begin, 3, 43); + string memory input = random.toHexString(); + uint256 length = bytes(input).length; + + assembly ("memory-safe") { + mstore(add(add(input, 0x20), length), 0x3030303030303030303030303030303030303030303030303030303030303030) + } + + (bool success, ) = input.tryParseAddress(begin, begin + 40); + assertFalse(success); + } +} diff --git a/test/utils/Strings.test.js b/test/utils/Strings.test.js index 6353fd886..0b2d87190 100644 --- a/test/utils/Strings.test.js +++ b/test/utils/Strings.test.js @@ -1,6 +1,7 @@ const { ethers } = require('hardhat'); const { expect } = require('chai'); const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); +const { PANIC_CODES } = require('@nomicfoundation/hardhat-chai-matchers/panic'); async function fixture() { const mock = await ethers.deployContract('$Strings'); @@ -38,11 +39,15 @@ describe('Strings', function () { it('converts MAX_UINT256', async function () { const value = ethers.MaxUint256; expect(await this.mock.$toString(value)).to.equal(value.toString(10)); + expect(await this.mock.$parseUint(value.toString(10))).to.equal(value); + expect(await this.mock.$tryParseUint(value.toString(10))).to.deep.equal([true, value]); }); for (const value of values) { it(`converts ${value}`, async function () { - expect(await this.mock.$toString(value)).to.equal(value); + expect(await this.mock.$toString(value)).to.equal(value.toString(10)); + expect(await this.mock.$parseUint(value.toString(10))).to.equal(value); + expect(await this.mock.$tryParseUint(value.toString(10))).to.deep.equal([true, value]); }); } }); @@ -51,21 +56,29 @@ describe('Strings', function () { it('converts MAX_INT256', async function () { const value = ethers.MaxInt256; expect(await this.mock.$toStringSigned(value)).to.equal(value.toString(10)); + expect(await this.mock.$parseInt(value.toString(10))).to.equal(value); + expect(await this.mock.$tryParseInt(value.toString(10))).to.deep.equal([true, value]); }); it('converts MIN_INT256', async function () { const value = ethers.MinInt256; expect(await this.mock.$toStringSigned(value)).to.equal(value.toString(10)); + expect(await this.mock.$parseInt(value.toString(10))).to.equal(value); + expect(await this.mock.$tryParseInt(value.toString(10))).to.deep.equal([true, value]); }); for (const value of values) { it(`convert ${value}`, async function () { - expect(await this.mock.$toStringSigned(value)).to.equal(value); + expect(await this.mock.$toStringSigned(value)).to.equal(value.toString(10)); + expect(await this.mock.$parseInt(value.toString(10))).to.equal(value); + expect(await this.mock.$tryParseInt(value.toString(10))).to.deep.equal([true, value]); }); it(`convert negative ${value}`, async function () { const negated = -value; expect(await this.mock.$toStringSigned(negated)).to.equal(negated.toString(10)); + expect(await this.mock.$parseInt(negated.toString(10))).to.equal(negated); + expect(await this.mock.$tryParseInt(negated.toString(10))).to.deep.equal([true, negated]); }); } }); @@ -73,17 +86,36 @@ describe('Strings', function () { describe('toHexString', function () { it('converts 0', async function () { - expect(await this.mock.getFunction('$toHexString(uint256)')(0n)).to.equal('0x00'); + const value = 0n; + const string = ethers.toBeHex(value); // 0x00 + + expect(await this.mock.getFunction('$toHexString(uint256)')(value)).to.equal(string); + expect(await this.mock.$parseHexUint(string)).to.equal(value); + expect(await this.mock.$parseHexUint(string.replace(/0x/, ''))).to.equal(value); + expect(await this.mock.$tryParseHexUint(string)).to.deep.equal([true, value]); + expect(await this.mock.$tryParseHexUint(string.replace(/0x/, ''))).to.deep.equal([true, value]); }); it('converts a positive number', async function () { - expect(await this.mock.getFunction('$toHexString(uint256)')(0x4132n)).to.equal('0x4132'); + const value = 0x4132n; + const string = ethers.toBeHex(value); + + expect(await this.mock.getFunction('$toHexString(uint256)')(value)).to.equal(string); + expect(await this.mock.$parseHexUint(string)).to.equal(value); + expect(await this.mock.$parseHexUint(string.replace(/0x/, ''))).to.equal(value); + expect(await this.mock.$tryParseHexUint(string)).to.deep.equal([true, value]); + expect(await this.mock.$tryParseHexUint(string.replace(/0x/, ''))).to.deep.equal([true, value]); }); it('converts MAX_UINT256', async function () { - expect(await this.mock.getFunction('$toHexString(uint256)')(ethers.MaxUint256)).to.equal( - `0x${ethers.MaxUint256.toString(16)}`, - ); + const value = ethers.MaxUint256; + const string = ethers.toBeHex(value); + + expect(await this.mock.getFunction('$toHexString(uint256)')(value)).to.equal(string); + expect(await this.mock.$parseHexUint(string)).to.equal(value); + expect(await this.mock.$parseHexUint(string.replace(/0x/, ''))).to.equal(value); + expect(await this.mock.$tryParseHexUint(string)).to.deep.equal([true, value]); + expect(await this.mock.$tryParseHexUint(string.replace(/0x/, ''))).to.deep.equal([true, value]); }); }); @@ -97,13 +129,13 @@ describe('Strings', function () { it('converts a positive number (short)', async function () { const length = 1n; await expect(this.mock.getFunction('$toHexString(uint256,uint256)')(0x4132n, length)) - .to.be.revertedWithCustomError(this.mock, `StringsInsufficientHexLength`) + .to.be.revertedWithCustomError(this.mock, 'StringsInsufficientHexLength') .withArgs(0x4132, length); }); it('converts MAX_UINT256', async function () { expect(await this.mock.getFunction('$toHexString(uint256,uint256)')(ethers.MaxUint256, 32n)).to.equal( - `0x${ethers.MaxUint256.toString(16)}`, + ethers.toBeHex(ethers.MaxUint256), ); }); }); @@ -139,9 +171,16 @@ describe('Strings', function () { describe('toChecksumHexString', function () { for (const addr of addresses) { it(`converts ${addr}`, async function () { - expect(await this.mock.getFunction('$toChecksumHexString(address)')(addr)).to.equal( - ethers.getAddress(addr.toLowerCase()), - ); + expect(await this.mock.$toChecksumHexString(addr)).to.equal(ethers.getAddress(addr)); + }); + } + }); + + describe('parseAddress', function () { + for (const addr of addresses) { + it(`converts ${addr}`, async function () { + expect(await this.mock.$parseAddress(addr)).to.equal(ethers.getAddress(addr)); + expect(await this.mock.$tryParseAddress(addr)).to.deep.equal([true, ethers.getAddress(addr)]); }); } }); @@ -177,4 +216,127 @@ describe('Strings', function () { expect(await this.mock.$equal(str1, str2)).to.be.true; }); }); + + describe('Edge cases: invalid parsing', function () { + it('parseUint overflow', async function () { + await expect(this.mock.$parseUint((ethers.MaxUint256 + 1n).toString(10))).to.be.revertedWithPanic( + PANIC_CODES.ARITHMETIC_OVERFLOW, + ); + await expect(this.mock.$tryParseUint((ethers.MaxUint256 + 1n).toString(10))).to.be.revertedWithPanic( + PANIC_CODES.ARITHMETIC_OVERFLOW, + ); + }); + + it('parseUint invalid character', async function () { + await expect(this.mock.$parseUint('0x1')).to.be.revertedWithCustomError(this.mock, 'StringsInvalidChar'); + await expect(this.mock.$parseUint('1f')).to.be.revertedWithCustomError(this.mock, 'StringsInvalidChar'); + await expect(this.mock.$parseUint('-10')).to.be.revertedWithCustomError(this.mock, 'StringsInvalidChar'); + await expect(this.mock.$parseUint('1.0')).to.be.revertedWithCustomError(this.mock, 'StringsInvalidChar'); + await expect(this.mock.$parseUint('1 000')).to.be.revertedWithCustomError(this.mock, 'StringsInvalidChar'); + expect(await this.mock.$tryParseUint('0x1')).to.deep.equal([false, 0n]); + expect(await this.mock.$tryParseUint('1f')).to.deep.equal([false, 0n]); + expect(await this.mock.$tryParseUint('-10')).to.deep.equal([false, 0n]); + expect(await this.mock.$tryParseUint('1.0')).deep.equal([false, 0n]); + expect(await this.mock.$tryParseUint('1 000')).deep.equal([false, 0n]); + }); + + it('parseUint invalid range', async function () { + expect(this.mock.$parseUint('12', 3, 2)).to.be.revertedWithCustomError(this.mock, 'StringsInvalidChar'); + expect(await this.mock.$tryParseUint('12', 3, 2)).to.deep.equal([false, 0n]); + }); + + it('parseInt overflow', async function () { + await expect(this.mock.$parseInt((ethers.MaxUint256 + 1n).toString(10))).to.be.revertedWithPanic( + PANIC_CODES.ARITHMETIC_OVERFLOW, + ); + await expect(this.mock.$parseInt((-ethers.MaxUint256 - 1n).toString(10))).to.be.revertedWithPanic( + PANIC_CODES.ARITHMETIC_OVERFLOW, + ); + await expect(this.mock.$tryParseInt((ethers.MaxUint256 + 1n).toString(10))).to.be.revertedWithPanic( + PANIC_CODES.ARITHMETIC_OVERFLOW, + ); + await expect(this.mock.$tryParseInt((-ethers.MaxUint256 - 1n).toString(10))).to.be.revertedWithPanic( + PANIC_CODES.ARITHMETIC_OVERFLOW, + ); + await expect(this.mock.$parseInt((ethers.MaxInt256 + 1n).toString(10))).to.be.revertedWithCustomError( + this.mock, + 'StringsInvalidChar', + ); + await expect(this.mock.$parseInt((ethers.MinInt256 - 1n).toString(10))).to.be.revertedWithCustomError( + this.mock, + 'StringsInvalidChar', + ); + expect(await this.mock.$tryParseInt((ethers.MaxInt256 + 1n).toString(10))).to.deep.equal([false, 0n]); + expect(await this.mock.$tryParseInt((ethers.MinInt256 - 1n).toString(10))).to.deep.equal([false, 0n]); + }); + + it('parseInt invalid character', async function () { + await expect(this.mock.$parseInt('0x1')).to.be.revertedWithCustomError(this.mock, 'StringsInvalidChar'); + await expect(this.mock.$parseInt('1f')).to.be.revertedWithCustomError(this.mock, 'StringsInvalidChar'); + await expect(this.mock.$parseInt('1.0')).to.be.revertedWithCustomError(this.mock, 'StringsInvalidChar'); + await expect(this.mock.$parseInt('1 000')).to.be.revertedWithCustomError(this.mock, 'StringsInvalidChar'); + expect(await this.mock.$tryParseInt('0x1')).to.deep.equal([false, 0n]); + expect(await this.mock.$tryParseInt('1f')).to.deep.equal([false, 0n]); + expect(await this.mock.$tryParseInt('1.0')).to.deep.equal([false, 0n]); + expect(await this.mock.$tryParseInt('1 000')).to.deep.equal([false, 0n]); + }); + + it('parseInt invalid range', async function () { + expect(this.mock.$parseInt('-12', 3, 2)).to.be.revertedWithCustomError(this.mock, 'StringsInvalidChar'); + expect(await this.mock.$tryParseInt('-12', 3, 2)).to.deep.equal([false, 0n]); + }); + + it('parseHexUint overflow', async function () { + await expect(this.mock.$parseHexUint((ethers.MaxUint256 + 1n).toString(16))).to.be.revertedWithPanic( + PANIC_CODES.ARITHMETIC_OVERFLOW, + ); + await expect(this.mock.$tryParseHexUint((ethers.MaxUint256 + 1n).toString(16))).to.be.revertedWithPanic( + PANIC_CODES.ARITHMETIC_OVERFLOW, + ); + }); + + it('parseHexUint invalid character', async function () { + await expect(this.mock.$parseHexUint('0123456789abcdefg')).to.be.revertedWithCustomError( + this.mock, + 'StringsInvalidChar', + ); + await expect(this.mock.$parseHexUint('-1')).to.be.revertedWithCustomError(this.mock, 'StringsInvalidChar'); + await expect(this.mock.$parseHexUint('-f')).to.be.revertedWithCustomError(this.mock, 'StringsInvalidChar'); + await expect(this.mock.$parseHexUint('-0xf')).to.be.revertedWithCustomError(this.mock, 'StringsInvalidChar'); + await expect(this.mock.$parseHexUint('1.0')).to.be.revertedWithCustomError(this.mock, 'StringsInvalidChar'); + await expect(this.mock.$parseHexUint('1 000')).to.be.revertedWithCustomError(this.mock, 'StringsInvalidChar'); + expect(await this.mock.$tryParseHexUint('0123456789abcdefg')).to.deep.equal([false, 0n]); + expect(await this.mock.$tryParseHexUint('-1')).to.deep.equal([false, 0n]); + expect(await this.mock.$tryParseHexUint('-f')).to.deep.equal([false, 0n]); + expect(await this.mock.$tryParseHexUint('-0xf')).to.deep.equal([false, 0n]); + expect(await this.mock.$tryParseHexUint('1.0')).to.deep.equal([false, 0n]); + expect(await this.mock.$tryParseHexUint('1 000')).to.deep.equal([false, 0n]); + }); + + it('parseHexUint invalid begin and end', async function () { + expect(this.mock.$parseHexUint('0x', 3, 2)).to.be.revertedWithCustomError(this.mock, 'StringsInvalidChar'); + expect(await this.mock.$tryParseHexUint('0x', 3, 2)).to.deep.equal([false, 0n]); + }); + + it('parseAddress invalid format', async function () { + for (const addr of [ + '0x736a507fB2881d6bB62dcA54673CF5295dC07833', // valid + '0x736a507fB2881d6-B62dcA54673CF5295dC07833', // invalid char + '0x0736a507fB2881d6bB62dcA54673CF5295dC07833', // tooLong + '0x36a507fB2881d6bB62dcA54673CF5295dC07833', // tooShort + '736a507fB2881d6bB62dcA54673CF5295dC07833', // missingPrefix - supported + ]) { + if (ethers.isAddress(addr)) { + expect(await this.mock.$parseAddress(addr)).to.equal(ethers.getAddress(addr)); + expect(await this.mock.$tryParseAddress(addr)).to.deep.equal([true, ethers.getAddress(addr)]); + } else { + await expect(this.mock.$parseAddress(addr)).to.be.revertedWithCustomError( + this.mock, + 'StringsInvalidAddressFormat', + ); + expect(await this.mock.$tryParseAddress(addr)).to.deep.equal([false, ethers.ZeroAddress]); + } + } + }); + }); }); diff --git a/test/utils/cryptography/ECDSA.test.js b/test/utils/cryptography/ECDSA.test.js index 6b24bdbce..0f2879a86 100644 --- a/test/utils/cryptography/ECDSA.test.js +++ b/test/utils/cryptography/ECDSA.test.js @@ -26,7 +26,6 @@ describe('ECDSA', function () { it('with long signature', async function () { await expect( - // eslint-disable-next-line max-len this.mock.$recover( TEST_MESSAGE, '0x01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789', @@ -61,7 +60,6 @@ describe('ECDSA', function () { }); it('reverts with invalid signature', async function () { - // eslint-disable-next-line max-len const signature = '0x332ce75a821c982f9127538858900d87d3ec1f9f737338ad67cad133fa48feff48e6fa0c18abc62e42820f05943e47af3e9fbe306ce74d64094bdf1691ee53e01c'; await expect(this.mock.$recover(TEST_MESSAGE, signature)).to.be.revertedWithCustomError( @@ -73,7 +71,7 @@ describe('ECDSA', function () { describe('with v=27 signature', function () { const signer = '0x2cc1166f6212628A0deEf2B33BEFB2187D35b86c'; - // eslint-disable-next-line max-len + const signatureWithoutV = '0x5d99b6f7f6d1f73d1a26497f2b1c89b24c0993913f86e9a2d02cd69887d9c94f3c880358579d811b21dd1b7fd9bb01c1d81d10e69f0384e675c32b39643be892'; @@ -133,7 +131,7 @@ describe('ECDSA', function () { describe('with v=28 signature', function () { const signer = '0x1E318623aB09Fe6de3C9b8672098464Aeda9100E'; - // eslint-disable-next-line max-len + const signatureWithoutV = '0x331fe75a821c982f9127538858900d87d3ec1f9f737338ad67cad133fa48feff48e6fa0c18abc62e42820f05943e47af3e9fbe306ce74d64094bdf1691ee53e0'; @@ -193,7 +191,7 @@ describe('ECDSA', function () { it('reverts with high-s value signature', async function () { const message = '0xb94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9'; - // eslint-disable-next-line max-len + const highSSignature = '0xe742ff452d41413616a5bf43fe15dd88294e983d3d36206c2712f39083d638bde0a0fc89be718fbc1033e1d30d78be1c68081562ed2e97af876f286f3453231d1b'; diff --git a/test/utils/cryptography/P256.t.sol b/test/utils/cryptography/P256.t.sol index 8b95ff225..0c9b2c78a 100644 --- a/test/utils/cryptography/P256.t.sol +++ b/test/utils/cryptography/P256.t.sol @@ -12,23 +12,23 @@ contract P256Test is Test { function testVerify(bytes32 digest, uint256 seed) public { uint256 privateKey = _asPrivateKey(seed); - (bytes32 x, bytes32 y) = P256PublicKey.getPublicKey(privateKey); + (uint256 x, uint256 y) = vm.publicKeyP256(privateKey); (bytes32 r, bytes32 s) = vm.signP256(privateKey, digest); s = _ensureLowerS(s); - assertTrue(P256.verify(digest, r, s, x, y)); - assertTrue(P256.verifySolidity(digest, r, s, x, y)); + assertTrue(P256.verify(digest, r, s, bytes32(x), bytes32(y))); + assertTrue(P256.verifySolidity(digest, r, s, bytes32(x), bytes32(y))); } /// forge-config: default.fuzz.runs = 512 function testRecover(bytes32 digest, uint256 seed) public { uint256 privateKey = _asPrivateKey(seed); - (bytes32 x, bytes32 y) = P256PublicKey.getPublicKey(privateKey); + (uint256 x, uint256 y) = vm.publicKeyP256(privateKey); (bytes32 r, bytes32 s) = vm.signP256(privateKey, digest); s = _ensureLowerS(s); (bytes32 qx0, bytes32 qy0) = P256.recovery(digest, 0, r, s); (bytes32 qx1, bytes32 qy1) = P256.recovery(digest, 1, r, s); - assertTrue((qx0 == x && qy0 == y) || (qx1 == x && qy1 == y)); + assertTrue((qx0 == bytes32(x) && qy0 == bytes32(y)) || (qx1 == bytes32(x) && qy1 == bytes32(y))); } function _asPrivateKey(uint256 seed) private pure returns (uint256) { @@ -42,98 +42,3 @@ contract P256Test is Test { } } } - -/** - * @dev Library to derive P256 public key from private key - * Should be removed if Foundry adds this functionality - * See https://github.com/foundry-rs/foundry/issues/7908 - */ -library P256PublicKey { - function getPublicKey(uint256 privateKey) internal view returns (bytes32, bytes32) { - (uint256 x, uint256 y, uint256 z) = _jMult(P256.GX, P256.GY, 1, privateKey); - return _affineFromJacobian(x, y, z); - } - - function _jMult( - uint256 x, - uint256 y, - uint256 z, - uint256 k - ) private pure returns (uint256 rx, uint256 ry, uint256 rz) { - unchecked { - for (uint256 i = 0; i < 256; ++i) { - if (rz > 0) { - (rx, ry, rz) = _jDouble(rx, ry, rz); - } - if (k >> 255 > 0) { - if (rz == 0) { - (rx, ry, rz) = (x, y, z); - } else { - (rx, ry, rz) = _jAdd(rx, ry, rz, x, y, z); - } - } - k <<= 1; - } - } - } - - /// From P256.sol - - function _affineFromJacobian(uint256 jx, uint256 jy, uint256 jz) private view returns (bytes32 ax, bytes32 ay) { - if (jz == 0) return (0, 0); - uint256 zinv = Math.invModPrime(jz, P256.P); - uint256 zzinv = mulmod(zinv, zinv, P256.P); - uint256 zzzinv = mulmod(zzinv, zinv, P256.P); - ax = bytes32(mulmod(jx, zzinv, P256.P)); - ay = bytes32(mulmod(jy, zzzinv, P256.P)); - } - - function _jDouble(uint256 x, uint256 y, uint256 z) private pure returns (uint256 rx, uint256 ry, uint256 rz) { - uint256 p = P256.P; - uint256 a = P256.A; - assembly ("memory-safe") { - let yy := mulmod(y, y, p) - let zz := mulmod(z, z, p) - let s := mulmod(4, mulmod(x, yy, p), p) // s = 4*x*y² - let m := addmod(mulmod(3, mulmod(x, x, p), p), mulmod(a, mulmod(zz, zz, p), p), p) // m = 3*x²+a*z⁴ - let t := addmod(mulmod(m, m, p), sub(p, mulmod(2, s, p)), p) // t = m²-2*s - - // x' = t - rx := t - // y' = m*(s-t)-8*y⁴ - ry := addmod(mulmod(m, addmod(s, sub(p, t), p), p), sub(p, mulmod(8, mulmod(yy, yy, p), p)), p) - // z' = 2*y*z - rz := mulmod(2, mulmod(y, z, p), p) - } - } - - function _jAdd( - uint256 x1, - uint256 y1, - uint256 z1, - uint256 x2, - uint256 y2, - uint256 z2 - ) private pure returns (uint256 rx, uint256 ry, uint256 rz) { - uint256 p = P256.P; - assembly ("memory-safe") { - let zz1 := mulmod(z1, z1, p) // zz1 = z1² - let zz2 := mulmod(z2, z2, p) // zz2 = z2² - let u1 := mulmod(x1, zz2, p) // u1 = x1*z2² - let u2 := mulmod(x2, zz1, p) // u2 = x2*z1² - let s1 := mulmod(y1, mulmod(zz2, z2, p), p) // s1 = y1*z2³ - let s2 := mulmod(y2, mulmod(zz1, z1, p), p) // s2 = y2*z1³ - let h := addmod(u2, sub(p, u1), p) // h = u2-u1 - let hh := mulmod(h, h, p) // h² - let hhh := mulmod(h, hh, p) // h³ - let r := addmod(s2, sub(p, s1), p) // r = s2-s1 - - // x' = r²-h³-2*u1*h² - rx := addmod(addmod(mulmod(r, r, p), sub(p, hhh), p), sub(p, mulmod(2, mulmod(u1, hh, p), p)), p) - // y' = r*(u1*h²-x')-s1*h³ - ry := addmod(mulmod(r, addmod(mulmod(u1, hh, p), sub(p, rx), p), p), sub(p, mulmod(s1, hhh, p)), p) - // z' = h*z1*z2 - rz := mulmod(h, mulmod(z1, z2, p), p) - } - } -} diff --git a/test/utils/introspection/SupportsInterface.behavior.js b/test/utils/introspection/SupportsInterface.behavior.js index c2bd1a479..8a7bc4b5e 100644 --- a/test/utils/introspection/SupportsInterface.behavior.js +++ b/test/utils/introspection/SupportsInterface.behavior.js @@ -103,8 +103,8 @@ function shouldSupportInterfaces(interfaces = []) { describe('when the interfaceId is supported', function () { it('uses less than 30k gas', async function () { for (const k of interfaces) { - const interface = INTERFACE_IDS[k] ?? k; - expect(await this.contractUnderTest.supportsInterface.estimateGas(interface)).to.lte(30_000n); + const interfaceId = INTERFACE_IDS[k] ?? k; + expect(await this.contractUnderTest.supportsInterface.estimateGas(interfaceId)).to.lte(30_000n); } });