github-actions 8 hónapja
szülő
commit
0dda004024
100 módosított fájl, 6446 hozzáadás és 551 törlés
  1. 1 0
      .codecov.yml
  2. 0 20
      .eslintrc
  3. 6 2
      .github/workflows/checks.yml
  4. 1 1
      .github/workflows/formal-verification.yml
  5. 43 1
      CHANGELOG.md
  6. 8 1
      GUIDELINES.md
  7. BIN
      audits/2024-10-v5.1.pdf
  8. 1 0
      audits/README.md
  9. 12 0
      contracts/account/README.adoc
  10. 170 0
      contracts/account/utils/draft-ERC4337Utils.sol
  11. 278 0
      contracts/account/utils/draft-ERC7579Utils.sol
  12. 6 1
      contracts/finance/VestingWallet.sol
  13. 38 57
      contracts/governance/Governor.sol
  14. 6 0
      contracts/governance/README.adoc
  15. 225 0
      contracts/governance/extensions/GovernorCountingOverridable.sol
  16. 4 14
      contracts/governance/extensions/GovernorPreventLateQuorum.sol
  17. 12 11
      contracts/governance/utils/Votes.sol
  18. 84 0
      contracts/governance/utils/VotesExtended.sol
  19. 253 0
      contracts/interfaces/draft-IERC4337.sol
  20. 226 0
      contracts/interfaces/draft-IERC7579.sol
  21. 1 1
      contracts/mocks/DummyImplementation.sol
  22. 1 1
      contracts/mocks/MerkleTreeMock.sol
  23. 8 1
      contracts/mocks/Stateless.sol
  24. 42 0
      contracts/mocks/VotesExtendedMock.sol
  25. 23 0
      contracts/mocks/account/utils/ERC7579UtilsMock.sol
  26. 0 0
      contracts/mocks/docs/access-control/AccessControlNonRevokableAdmin.sol
  27. 18 0
      contracts/mocks/governance/GovernorCountingOverridableMock.sol
  28. 2 8
      contracts/mocks/governance/GovernorPreventLateQuorumMock.sol
  29. 1 1
      contracts/mocks/governance/GovernorStorageMock.sol
  30. 1 1
      contracts/mocks/proxy/UUPSUpgradeableMock.sol
  31. 31 0
      contracts/mocks/token/ERC20VotesAdditionalCheckpointsMock.sol
  32. 1 1
      contracts/package.json
  33. 143 2
      contracts/proxy/Clones.sol
  34. 2 2
      contracts/proxy/ERC1967/ERC1967Proxy.sol
  35. 2 2
      contracts/proxy/ERC1967/ERC1967Utils.sol
  36. 2 2
      contracts/proxy/beacon/BeaconProxy.sol
  37. 2 2
      contracts/proxy/transparent/ProxyAdmin.sol
  38. 2 2
      contracts/proxy/transparent/TransparentUpgradeableProxy.sol
  39. 2 2
      contracts/proxy/utils/UUPSUpgradeable.sol
  40. 2 2
      contracts/token/ERC20/ERC20.sol
  41. 6 3
      contracts/token/ERC20/extensions/ERC1363.sol
  42. 1 2
      contracts/token/ERC20/utils/SafeERC20.sol
  43. 3 3
      contracts/utils/Address.sol
  44. 114 0
      contracts/utils/Bytes.sol
  45. 54 0
      contracts/utils/CAIP10.sol
  46. 51 0
      contracts/utils/CAIP2.sol
  47. 74 0
      contracts/utils/NoncesKeyed.sol
  48. 514 1
      contracts/utils/Packing.sol
  49. 6 1
      contracts/utils/README.adoc
  50. 326 1
      contracts/utils/Strings.sol
  51. 1 1
      docs/modules/ROOT/pages/erc1155.adoc
  52. 2 2
      docs/modules/ROOT/pages/erc4626.adoc
  53. 1 1
      docs/modules/ROOT/pages/extending-contracts.adoc
  54. 1 1
      docs/modules/ROOT/pages/governance.adoc
  55. 0 0
      docs/modules/api/examples/access-control/AccessControlNonRevokableAdmin.sol
  56. 1 0
      docs/modules/api/nav.adoc
  57. 14 14
      docs/modules/api/pages/access.adoc
  58. 479 0
      docs/modules/api/pages/account.adoc
  59. 6 1
      docs/modules/api/pages/finance.adoc
  60. 327 22
      docs/modules/api/pages/governance.adoc
  61. 20 20
      docs/modules/api/pages/interfaces.adoc
  62. 2 2
      docs/modules/api/pages/metatx.adoc
  63. 118 12
      docs/modules/api/pages/proxy.adoc
  64. 10 10
      docs/modules/api/pages/token/ERC1155.adoc
  65. 23 18
      docs/modules/api/pages/token/ERC20.adoc
  66. 17 15
      docs/modules/api/pages/token/ERC721.adoc
  67. 1 1
      docs/modules/api/pages/token/common.adoc
  68. 451 38
      docs/modules/api/pages/utils.adoc
  69. 26 0
      eslint.config.mjs
  70. 1 0
      foundry.toml
  71. 1 1
      fv-requirements.txt
  72. 1 1
      lib/forge-std
  73. 375 206
      package-lock.json
  74. 9 6
      package.json
  75. 5 4
      scripts/checks/inheritance-ordering.js
  76. 49 0
      scripts/checks/pragma-consistency.js
  77. 1 1
      scripts/generate/run.js
  78. 0 1
      scripts/generate/templates/Checkpoints.js
  79. 0 1
      scripts/generate/templates/Checkpoints.t.js
  80. 0 2
      scripts/generate/templates/EnumerableMap.js
  81. 0 2
      scripts/generate/templates/EnumerableSet.js
  82. 0 2
      scripts/generate/templates/MerkleProof.js
  83. 1 1
      scripts/generate/templates/Packing.opts.js
  84. 2 2
      scripts/generate/templates/Packing.t.js
  85. 0 2
      scripts/generate/templates/SafeCast.js
  86. 2 2
      scripts/release/workflow/state.js
  87. 1 1
      scripts/update-docs-branch.js
  88. 1 1
      slither.config.json
  89. 288 0
      test/account/utils/draft-ERC4337Utils.test.js
  90. 421 0
      test/account/utils/draft-ERC7579Utils.t.sol
  91. 353 0
      test/account/utils/draft-ERC7579Utils.test.js
  92. 0 0
      test/bin/EntryPoint070.abi
  93. BIN
      test/bin/EntryPoint070.bytecode
  94. 1 0
      test/bin/SenderCreator070.abi
  95. BIN
      test/bin/SenderCreator070.bytecode
  96. 346 0
      test/governance/extensions/GovernorCountingOverridable.test.js
  97. 11 8
      test/governance/utils/ERC6372.behavior.js
  98. 152 0
      test/governance/utils/VotesExtended.test.js
  99. 109 0
      test/helpers/chains.js
  100. 7 0
      test/helpers/eip712-types.js

+ 1 - 0
.codecov.yml

@@ -13,3 +13,4 @@ coverage:
 ignore:
   - "test"
   - "contracts/mocks"
+  - "contracts/vendor"

+ 0 - 20
.eslintrc

@@ -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",
-  }
-}

+ 6 - 2
.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

+ 1 - 1
.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'
 

+ 43 - 1
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))
 

+ 8 - 1
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:

BIN
audits/2024-10-v5.1.pdf


+ 1 - 0
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) |

+ 12 - 0
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}}

+ 170 - 0
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
+        }
+    }
+}

+ 278 - 0
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);
+}

+ 6 - 1
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);

+ 38 - 57
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;
-
-        // 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;
-        }
+        unchecked {
+            uint256 length = bytes(description).length;
 
-        // 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)))
+        }
+    }
 }

+ 6 - 0
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}.

+ 225 - 0
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);
+    }
+}

+ 4 - 14
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;
     }
 
     /**

+ 12 - 11
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));
     }
 
     /**

+ 84 - 0
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)));
+            }
+        }
+    }
+}

+ 253 - 0
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;
+}

+ 226 - 0
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);
+}

+ 1 - 1
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";

+ 1 - 1
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";
 

+ 8 - 1
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";

+ 42 - 0
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";
+    }
+}

+ 23 - 0
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;
+    }
+}

+ 0 - 0
contracts/mocks/docs/access-control/AccessControlUnrevokableAdmin.sol → contracts/mocks/docs/access-control/AccessControlNonRevokableAdmin.sol


+ 18 - 0
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();
+    }
+}

+ 2 - 8
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);
     }
 }

+ 1 - 1
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";

+ 1 - 1
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";

+ 31 - 0
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";
+    }
+}

+ 1 - 1
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",

+ 143 - 2
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
+            );
+    }
 }

+ 2 - 2
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";

+ 2 - 2
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";

+ 2 - 2
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";

+ 2 - 2
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";

+ 2 - 2
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";

+ 2 - 2
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";

+ 2 - 2
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);
             }

+ 6 - 3
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:
      *

+ 1 - 2
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

+ 3 - 3
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);
         }
     }
 

+ 114 - 0
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)))
+        }
+    }
+}

+ 54 - 0
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)));
+    }
+}

+ 51 - 0
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)));
+    }
+}

+ 74 - 0
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));
+    }
+}

+ 514 - 1
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") {

+ 6 - 1
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_.

+ 326 - 1
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)))
+        }
+    }
 }

+ 1 - 1
docs/modules/ROOT/pages/erc1155.adoc

@@ -108,7 +108,7 @@ ERC1155InvalidReceiver("<ADDRESS>")
 
 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]
 ----

+ 2 - 2
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.
 

+ 1 - 1
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.

+ 1 - 1
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[]

+ 0 - 0
docs/modules/api/examples/access-control/AccessControlUnrevokableAdmin.sol → docs/modules/api/examples/access-control/AccessControlNonRevokableAdmin.sol


+ 1 - 0
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]

+ 14 - 14
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

+ 479 - 0
docs/modules/api/pages/account.adoc

@@ -0,0 +1,479 @@
+:github-icon: pass:[<svg class="icon"><use href="#github-icon"/></svg>]
+: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.
+

+ 6 - 1
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
 --

+ 327 - 22
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

+ 20 - 20
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

+ 2 - 2
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

+ 118 - 12
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

+ 10 - 10
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

+ 23 - 18
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

+ 17 - 15
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

+ 1 - 1
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

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 451 - 38
docs/modules/api/pages/utils.adoc


+ 26 - 0
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')),
+];

+ 1 - 0
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

+ 1 - 1
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

+ 1 - 1
lib/forge-std

@@ -1 +1 @@
-Subproject commit ae570fec082bfe1c1f45b0acca4a2b4f84d345ce
+Subproject commit 8f24d6b04c92975e0795b5868aa0d783251cdeaa

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 375 - 206
package-lock.json


+ 9 - 6
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",

+ 5 - 4
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);

+ 49 - 0
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.');
+}

+ 1 - 1
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;
   }
 }

+ 0 - 1
scripts/generate/templates/Checkpoints.js

@@ -227,7 +227,6 @@ function _unsafeAccess(
     }
 }
 `;
-/* eslint-enable max-len */
 
 // GENERATE
 module.exports = format(

+ 0 - 1
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};
 

+ 0 - 2
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

+ 0 - 2
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

+ 0 - 2
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(

+ 1 - 1
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],
 };

+ 2 - 2
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);

+ 0 - 2
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 => `\
 /**

+ 2 - 2
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;
 }

+ 1 - 1
scripts/update-docs-branch.js

@@ -6,7 +6,7 @@ const run = cmd => {
 const tryRead = cmd => {
   try {
     return read(cmd);
-  } catch (e) {
+  } catch {
     return undefined;
   }
 };

+ 1 - 1
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"
 }

+ 288 - 0
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');
+      });
+    });
+  });
+});

+ 421 - 0
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
+        // <missing element>
+        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
+        // <missing data>
+        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)));
+    }
+}

+ 353 - 0
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;
+      });
+    });
+  });
+});

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 0 - 0
test/bin/EntryPoint070.abi


BIN
test/bin/EntryPoint070.bytecode


+ 1 - 0
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"}]

BIN
test/bin/SenderCreator070.bytecode


+ 346 - 0
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'),
+            },
+          ],
+          '<proposal description>',
+        );
+      });
+
+      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);
+          });
+        });
+      });
+    });
+  }
+});

+ 11 - 8
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}`);
     });
   });
 }

+ 152 - 0
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);
+      });
+    });
+  }
+});

+ 109 - 0
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 })),
+};

+ 7 - 0
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',

Nem az összes módosított fájl került megjelenítésre, mert túl sok fájl változott