Browse Source

Merge branch 'master' into typo-fixes

Hadrien Croubois 3 months ago
parent
commit
337211c43b
42 changed files with 1665 additions and 41 deletions
  1. 5 0
      .changeset/dull-students-eat.md
  2. 5 0
      .changeset/major-feet-write.md
  3. 5 0
      .changeset/new-days-tease.md
  4. 5 0
      .changeset/violet-turtles-like.md
  5. 5 0
      .changeset/whole-plums-speak.md
  6. 5 0
      .changeset/wild-baths-buy.md
  7. 1 1
      contracts/access/manager/IAccessManager.sol
  8. 5 5
      contracts/account/extensions/draft-AccountERC7579.sol
  9. 1 1
      contracts/governance/extensions/GovernorNoncesKeyed.sol
  10. 3 0
      contracts/interfaces/README.adoc
  11. 64 0
      contracts/interfaces/draft-IERC7786.sol
  12. 1 0
      contracts/mocks/Stateless.sol
  13. 20 0
      contracts/mocks/docs/account/MyAccountERC7702.sol
  14. 37 0
      contracts/mocks/docs/account/MyFactoryAccount.sol
  15. 52 0
      contracts/utils/Bytes.sol
  16. 44 0
      contracts/utils/Memory.sol
  17. 3 0
      contracts/utils/README.adoc
  18. 17 0
      contracts/utils/Strings.sol
  19. 33 0
      contracts/utils/cryptography/ECDSA.sol
  20. 36 7
      contracts/utils/cryptography/SignatureChecker.sol
  21. 3 2
      contracts/utils/cryptography/signers/MultiSignerERC7913.sol
  22. 14 0
      contracts/utils/cryptography/signers/MultiSignerERC7913Weighted.sol
  23. 4 0
      contracts/utils/cryptography/signers/SignerERC7913.sol
  24. 24 16
      contracts/utils/structs/EnumerableMap.sol
  25. 3 2
      contracts/utils/structs/EnumerableSet.sol
  26. 5 0
      docs/modules/ROOT/nav.adoc
  27. 100 0
      docs/modules/ROOT/pages/account-abstraction.adoc
  28. 354 0
      docs/modules/ROOT/pages/accounts.adoc
  29. 143 0
      docs/modules/ROOT/pages/eoa-delegation.adoc
  30. 306 0
      docs/modules/ROOT/pages/multisig.adoc
  31. 34 2
      docs/modules/ROOT/pages/utilities.adoc
  32. 3 2
      scripts/generate/templates/EnumerableMap.js
  33. 3 2
      scripts/generate/templates/EnumerableSet.js
  34. 16 0
      test/account/AccountMultiSignerWeighted.test.js
  35. 2 0
      test/helpers/constants.js
  36. 74 0
      test/utils/Bytes.t.sol
  37. 124 0
      test/utils/Bytes.test.js
  38. 21 0
      test/utils/Memory.t.sol
  39. 39 0
      test/utils/Memory.test.js
  40. 11 0
      test/utils/Strings.test.js
  41. 28 0
      test/utils/cryptography/ECDSA.test.js
  42. 7 1
      test/utils/cryptography/SignatureChecker.test.js

+ 5 - 0
.changeset/dull-students-eat.md

@@ -0,0 +1,5 @@
+---
+'openzeppelin-solidity': minor
+---
+
+`Memory`: Add library with utilities to manipulate memory

+ 5 - 0
.changeset/major-feet-write.md

@@ -0,0 +1,5 @@
+---
+'openzeppelin-solidity': minor
+---
+
+`Bytes`: Add `reverseBytes32`, `reverseBytes16`, `reverseBytes8`, `reverseBytes4`, and `reverseBytes2` functions to reverse byte order for converting between little-endian and big-endian representations.

+ 5 - 0
.changeset/new-days-tease.md

@@ -0,0 +1,5 @@
+---
+'openzeppelin-solidity': minor
+---
+
+`Strings`: Add `toHexString(bytes)`.

+ 5 - 0
.changeset/violet-turtles-like.md

@@ -0,0 +1,5 @@
+---
+'openzeppelin-solidity': minor
+---
+
+`ECDSA`: Add `recoverCalldata` and `tryRecoverCalldata`, variants of `recover` and `tryRecover` that are more efficient when signatures are in calldata.

+ 5 - 0
.changeset/whole-plums-speak.md

@@ -0,0 +1,5 @@
+---
+'openzeppelin-solidity': minor
+---
+
+`SignatureChecker`: Add `isValidSignatureNowCalldata(address,bytes32,bytes calldata)` for efficient processing of calldata signatures.

+ 5 - 0
.changeset/wild-baths-buy.md

@@ -0,0 +1,5 @@
+---
+'openzeppelin-solidity': minor
+---
+
+`IERC7786`: Add the (draft) interface for ERC-7786 "Cross-Chain Messaging Gateway"

+ 1 - 1
contracts/access/manager/IAccessManager.sol

@@ -97,7 +97,7 @@ interface IAccessManager {
      * previously set delay (not zero), then the function should return false and the caller should schedule the operation
      * for future execution.
      *
-     * If `immediate` is true, the delay can be disregarded and the operation can be immediately executed, otherwise
+     * If `allowed` is true, the delay can be disregarded and the operation can be immediately executed, otherwise
      * the operation can be executed if and only if delay is greater than 0.
      *
      * NOTE: The IAuthority interface does not include the `uint32` delay. This is an extension of that interface that

+ 5 - 5
contracts/account/extensions/draft-AccountERC7579.sol

@@ -237,11 +237,11 @@ abstract contract AccountERC7579 is Account, IERC1271, IERC7579Execution, IERC75
      *
      * Requirements:
      *
-     * * Module type must be supported. See {supportsModule}. Reverts with {ERC7579UnsupportedModuleType}.
-     * * Module must be of the given type. Reverts with {ERC7579MismatchedModuleTypeId}.
-     * * Module must not be already installed. Reverts with {ERC7579AlreadyInstalledModule}.
+     * * Module type must be supported. See {supportsModule}. Reverts with {ERC7579Utils-ERC7579UnsupportedModuleType}.
+     * * Module must be of the given type. Reverts with {ERC7579Utils-ERC7579MismatchedModuleTypeId}.
+     * * Module must not be already installed. Reverts with {ERC7579Utils-ERC7579AlreadyInstalledModule}.
      *
-     * Emits a {ModuleInstalled} event.
+     * Emits a {IERC7579ModuleConfig-ModuleInstalled} event.
      */
     function _installModule(uint256 moduleTypeId, address module, bytes memory initData) internal virtual {
         require(supportsModule(moduleTypeId), ERC7579Utils.ERC7579UnsupportedModuleType(moduleTypeId));
@@ -276,7 +276,7 @@ abstract contract AccountERC7579 is Account, IERC1271, IERC7579Execution, IERC75
      *
      * Requirements:
      *
-     * * Module must be already installed. Reverts with {ERC7579UninstalledModule} otherwise.
+     * * Module must be already installed. Reverts with {ERC7579Utils-ERC7579UninstalledModule} otherwise.
      */
     function _uninstallModule(uint256 moduleTypeId, address module, bytes memory deInitData) internal virtual {
         require(supportsModule(moduleTypeId), ERC7579Utils.ERC7579UnsupportedModuleType(moduleTypeId));

+ 1 - 1
contracts/governance/extensions/GovernorNoncesKeyed.sol

@@ -8,7 +8,7 @@ import {NoncesKeyed} from "../../utils/NoncesKeyed.sol";
 import {SignatureChecker} from "../../utils/cryptography/SignatureChecker.sol";
 
 /**
- * @dev An extension of {Governor} that extends existing nonce management to use {NoncesKeyed}, where the key is the first 192 bits of the `proposalId`.
+ * @dev An extension of {Governor} that extends existing nonce management to use {NoncesKeyed}, where the key is the low-order 192 bits of the `proposalId`.
  * This is useful for voting by signature while maintaining separate sequences of nonces for each proposal.
  *
  * NOTE: Traditional (un-keyed) nonces are still supported and can continue to be used as if this extension was not present.

+ 3 - 0
contracts/interfaces/README.adoc

@@ -45,6 +45,7 @@ are useful to interact with third party contracts that implement them.
 - {IERC6909Metadata}
 - {IERC6909TokenSupply}
 - {IERC7674}
+- {IERC7786}
 - {IERC7802}
 
 == Detailed ABI
@@ -99,4 +100,6 @@ are useful to interact with third party contracts that implement them.
 
 {{IERC7674}}
 
+{{IERC7786}}
+
 {{IERC7802}}

+ 64 - 0
contracts/interfaces/draft-IERC7786.sol

@@ -0,0 +1,64 @@
+// SPDX-License-Identifier: MIT
+
+pragma solidity >=0.8.4;
+
+/**
+ * @dev Interface for ERC-7786 source gateways.
+ *
+ * See ERC-7786 for more details
+ */
+interface IERC7786GatewaySource {
+    /**
+     * @dev Event emitted when a message is created. If `outboxId` is zero, no further processing is necessary. If
+     * `outboxId` is not zero, then further (gateway specific, and non-standardized) action is required.
+     */
+    event MessageSent(
+        bytes32 indexed sendId,
+        bytes sender, // Binary Interoperable Address
+        bytes receiver, // Binary Interoperable Address
+        bytes payload,
+        uint256 value,
+        bytes[] attributes
+    );
+
+    /// @dev This error is thrown when a message creation fails because of an unsupported attribute being specified.
+    error UnsupportedAttribute(bytes4 selector);
+
+    /// @dev Getter to check whether an attribute is supported or not.
+    function supportsAttribute(bytes4 selector) external view returns (bool);
+
+    /**
+     * @dev Endpoint for creating a new message. If the message requires further (gateway specific) processing before
+     * it can be sent to the destination chain, then a non-zero `outboxId` must be returned. Otherwise, the
+     * message MUST be sent and this function must return 0.
+     *
+     * * MUST emit a {MessageSent} event.
+     *
+     * If any of the `attributes` is not supported, this function SHOULD revert with an {UnsupportedAttribute} error.
+     * Other errors SHOULD revert with errors not specified in ERC-7786.
+     */
+    function sendMessage(
+        bytes calldata recipient, // Binary Interoperable Address
+        bytes calldata payload,
+        bytes[] calldata attributes
+    ) external payable returns (bytes32 sendId);
+}
+
+/**
+ * @dev Interface for the ERC-7786 client contract (receiver).
+ *
+ * See ERC-7786 for more details
+ */
+interface IERC7786Receiver {
+    /**
+     * @dev Endpoint for receiving cross-chain message.
+     *
+     * This function may be called directly by the gateway.
+     */
+    function executeMessage(
+        bytes32 receiveId,
+        bytes calldata sender, // Binary Interoperable Address
+        bytes calldata payload,
+        bytes[] calldata attributes
+    ) external payable returns (bytes4);
+}

+ 1 - 0
contracts/mocks/Stateless.sol

@@ -49,6 +49,7 @@ import {SignatureChecker} from "../utils/cryptography/SignatureChecker.sol";
 import {SignedMath} from "../utils/math/SignedMath.sol";
 import {StorageSlot} from "../utils/StorageSlot.sol";
 import {Strings} from "../utils/Strings.sol";
+import {Memory} from "../utils/Memory.sol";
 import {Time} from "../utils/types/Time.sol";
 
 contract Dummy1234 {}

+ 20 - 0
contracts/mocks/docs/account/MyAccountERC7702.sol

@@ -0,0 +1,20 @@
+// contracts/MyAccountERC7702.sol
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.20;
+
+import {Account} from "../../../account/Account.sol";
+import {ERC721Holder} from "../../../token/ERC721/utils/ERC721Holder.sol";
+import {ERC1155Holder} from "../../../token/ERC1155/utils/ERC1155Holder.sol";
+import {ERC7821} from "../../../account/extensions/draft-ERC7821.sol";
+import {SignerERC7702} from "../../../utils/cryptography/signers/SignerERC7702.sol";
+
+contract MyAccountERC7702 is Account, SignerERC7702, ERC7821, ERC721Holder, ERC1155Holder {
+    /// @dev Allows the entry point as an authorized executor.
+    function _erc7821AuthorizedExecutor(
+        address caller,
+        bytes32 mode,
+        bytes calldata executionData
+    ) internal view virtual override returns (bool) {
+        return caller == address(entryPoint()) || super._erc7821AuthorizedExecutor(caller, mode, executionData);
+    }
+}

+ 37 - 0
contracts/mocks/docs/account/MyFactoryAccount.sol

@@ -0,0 +1,37 @@
+// contracts/MyFactoryAccount.sol
+// SPDX-License-Identifier: MIT
+
+pragma solidity ^0.8.20;
+
+import {Clones} from "../../../proxy/Clones.sol";
+import {Address} from "../../../utils/Address.sol";
+
+/**
+ * @dev A factory contract to create accounts on demand.
+ */
+contract MyFactoryAccount {
+    using Clones for address;
+    using Address for address;
+
+    address private immutable _impl;
+
+    constructor(address impl_) {
+        require(impl_.code.length > 0);
+        _impl = impl_;
+    }
+
+    /// @dev Predict the address of the account
+    function predictAddress(bytes calldata callData) public view returns (address) {
+        return _impl.predictDeterministicAddress(keccak256(callData), address(this));
+    }
+
+    /// @dev Create clone accounts on demand
+    function cloneAndInitialize(bytes calldata callData) public returns (address) {
+        address predicted = predictAddress(callData);
+        if (predicted.code.length == 0) {
+            _impl.cloneDeterministic(keccak256(callData));
+            predicted.functionCall(callData);
+        }
+        return predicted;
+    }
+}

+ 52 - 0
contracts/utils/Bytes.sol

@@ -99,6 +99,58 @@ library Bytes {
         return result;
     }
 
+    /**
+     * @dev Reverses the byte order of a bytes32 value, converting between little-endian and big-endian.
+     * Inspired in https://graphics.stanford.edu/~seander/bithacks.html#ReverseParallel[Reverse Parallel]
+     */
+    function reverseBytes32(bytes32 value) internal pure returns (bytes32) {
+        value = // swap bytes
+            ((value >> 8) & 0x00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF) |
+            ((value & 0x00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF) << 8);
+        value = // swap 2-byte long pairs
+            ((value >> 16) & 0x0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF) |
+            ((value & 0x0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF) << 16);
+        value = // swap 4-byte long pairs
+            ((value >> 32) & 0x00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF) |
+            ((value & 0x00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF) << 32);
+        value = // swap 8-byte long pairs
+            ((value >> 64) & 0x0000000000000000FFFFFFFFFFFFFFFF0000000000000000FFFFFFFFFFFFFFFF) |
+            ((value & 0x0000000000000000FFFFFFFFFFFFFFFF0000000000000000FFFFFFFFFFFFFFFF) << 64);
+        return (value >> 128) | (value << 128); // swap 16-byte long pairs
+    }
+
+    /// @dev Same as {reverseBytes32} but optimized for 128-bit values.
+    function reverseBytes16(bytes16 value) internal pure returns (bytes16) {
+        value = // swap bytes
+            ((value & 0xFF00FF00FF00FF00FF00FF00FF00FF00) >> 8) |
+            ((value & 0x00FF00FF00FF00FF00FF00FF00FF00FF) << 8);
+        value = // swap 2-byte long pairs
+            ((value & 0xFFFF0000FFFF0000FFFF0000FFFF0000) >> 16) |
+            ((value & 0x0000FFFF0000FFFF0000FFFF0000FFFF) << 16);
+        value = // swap 4-byte long pairs
+            ((value & 0xFFFFFFFF00000000FFFFFFFF00000000) >> 32) |
+            ((value & 0x00000000FFFFFFFF00000000FFFFFFFF) << 32);
+        return (value >> 64) | (value << 64); // swap 8-byte long pairs
+    }
+
+    /// @dev Same as {reverseBytes32} but optimized for 64-bit values.
+    function reverseBytes8(bytes8 value) internal pure returns (bytes8) {
+        value = ((value & 0xFF00FF00FF00FF00) >> 8) | ((value & 0x00FF00FF00FF00FF) << 8); // swap bytes
+        value = ((value & 0xFFFF0000FFFF0000) >> 16) | ((value & 0x0000FFFF0000FFFF) << 16); // swap 2-byte long pairs
+        return (value >> 32) | (value << 32); // swap 4-byte long pairs
+    }
+
+    /// @dev Same as {reverseBytes32} but optimized for 32-bit values.
+    function reverseBytes4(bytes4 value) internal pure returns (bytes4) {
+        value = ((value & 0xFF00FF00) >> 8) | ((value & 0x00FF00FF) << 8); // swap bytes
+        return (value >> 16) | (value << 16); // swap 2-byte long pairs
+    }
+
+    /// @dev Same as {reverseBytes32} but optimized for 16-bit values.
+    function reverseBytes2(bytes2 value) internal pure returns (bytes2) {
+        return (value >> 8) | (value << 8);
+    }
+
     /**
      * @dev Reads a bytes32 from a bytes array without bounds checking.
      *

+ 44 - 0
contracts/utils/Memory.sol

@@ -0,0 +1,44 @@
+// SPDX-License-Identifier: MIT
+
+pragma solidity ^0.8.20;
+
+/**
+ * @dev Utilities to manipulate memory.
+ *
+ * Memory is a contiguous and dynamic byte array in which Solidity stores non-primitive types.
+ * This library provides functions to manipulate pointers to this dynamic array.
+ *
+ * WARNING: When manipulating memory, make sure to follow the Solidity documentation
+ * guidelines for https://docs.soliditylang.org/en/v0.8.20/assembly.html#memory-safety[Memory Safety].
+ */
+library Memory {
+    type Pointer is bytes32;
+
+    /// @dev Returns a `Pointer` to the current free `Pointer`.
+    function getFreeMemoryPointer() internal pure returns (Pointer ptr) {
+        assembly ("memory-safe") {
+            ptr := mload(0x40)
+        }
+    }
+
+    /**
+     * @dev Sets the free `Pointer` to a specific value.
+     *
+     * WARNING: Everything after the pointer may be overwritten.
+     **/
+    function setFreeMemoryPointer(Pointer ptr) internal pure {
+        assembly ("memory-safe") {
+            mstore(0x40, ptr)
+        }
+    }
+
+    /// @dev `Pointer` to `bytes32`. Expects a pointer to a properly ABI-encoded `bytes` object.
+    function asBytes32(Pointer ptr) internal pure returns (bytes32) {
+        return Pointer.unwrap(ptr);
+    }
+
+    /// @dev `bytes32` to `Pointer`. Expects a pointer to a properly ABI-encoded `bytes` object.
+    function asPointer(bytes32 value) internal pure returns (Pointer) {
+        return Pointer.wrap(value);
+    }
+}

+ 3 - 0
contracts/utils/README.adoc

@@ -38,6 +38,7 @@ Miscellaneous contracts and libraries containing utility functions you can use t
  * {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 the {Heap} library.
  * {CAIP2}, {CAIP10}: Libraries for formatting and parsing CAIP-2 and CAIP-10 identifiers.
+ * {Memory}: A utility library to manipulate memory.
  * {InteroperableAddress}: Library for formatting and parsing ERC-7930 interoperable addresses.
  * {Blockhash}: A library for accessing historical block hashes beyond the standard 256 block limit utilizing EIP-2935's historical blockhash functionality.
  * {Time}: A library that provides helpers for manipulating time-related objects, including a `Delay` type.
@@ -135,6 +136,8 @@ Ethereum contracts have no native concept of an interface, so applications must
 
 {{CAIP10}}
 
+{{Memory}}
+
 {{InteroperableAddress}}
 
 {{Blockhash}}

+ 17 - 0
contracts/utils/Strings.sol

@@ -128,6 +128,23 @@ library Strings {
         return string(buffer);
     }
 
+    /**
+     * @dev Converts a `bytes` buffer to its ASCII `string` hexadecimal representation.
+     */
+    function toHexString(bytes memory input) internal pure returns (string memory) {
+        unchecked {
+            bytes memory buffer = new bytes(2 * input.length + 2);
+            buffer[0] = "0";
+            buffer[1] = "x";
+            for (uint256 i = 0; i < input.length; ++i) {
+                uint8 v = uint8(input[i]);
+                buffer[2 * i + 2] = HEX_DIGITS[v >> 4];
+                buffer[2 * i + 3] = HEX_DIGITS[v & 0xf];
+            }
+            return string(buffer);
+        }
+    }
+
     /**
      * @dev Returns true if the two strings are equal.
      */

+ 33 - 0
contracts/utils/cryptography/ECDSA.sol

@@ -75,6 +75,30 @@ library ECDSA {
         }
     }
 
+    /**
+     * @dev Variant of {tryRecover} that takes a signature in calldata
+     */
+    function tryRecoverCalldata(
+        bytes32 hash,
+        bytes calldata signature
+    ) internal pure returns (address recovered, RecoverError err, bytes32 errArg) {
+        if (signature.length == 65) {
+            bytes32 r;
+            bytes32 s;
+            uint8 v;
+            // ecrecover takes the signature parameters, calldata slices would work here, but are
+            // significantly more expensive (length check) than using calldataload in assembly.
+            assembly ("memory-safe") {
+                r := calldataload(signature.offset)
+                s := calldataload(add(signature.offset, 0x20))
+                v := byte(0, calldataload(add(signature.offset, 0x40)))
+            }
+            return tryRecover(hash, v, r, s);
+        } else {
+            return (address(0), RecoverError.InvalidSignatureLength, bytes32(signature.length));
+        }
+    }
+
     /**
      * @dev Returns the address that signed a hashed message (`hash`) with
      * `signature`. This address can then be used for verification purposes.
@@ -95,6 +119,15 @@ library ECDSA {
         return recovered;
     }
 
+    /**
+     * @dev Variant of {recover} that takes a signature in calldata
+     */
+    function recoverCalldata(bytes32 hash, bytes calldata signature) internal pure returns (address) {
+        (address recovered, RecoverError error, bytes32 errorArg) = tryRecoverCalldata(hash, signature);
+        _throwError(error, errorArg);
+        return recovered;
+    }
+
     /**
      * @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.
      *

+ 36 - 7
contracts/utils/cryptography/SignatureChecker.sol

@@ -38,6 +38,22 @@ library SignatureChecker {
         }
     }
 
+    /**
+     * @dev Variant of {isValidSignatureNow} that takes a signature in calldata
+     */
+    function isValidSignatureNowCalldata(
+        address signer,
+        bytes32 hash,
+        bytes calldata signature
+    ) internal view returns (bool) {
+        if (signer.code.length == 0) {
+            (address recovered, ECDSA.RecoverError err, ) = ECDSA.tryRecoverCalldata(hash, signature);
+            return err == ECDSA.RecoverError.NoError && recovered == signer;
+        } else {
+            return isValidERC1271SignatureNow(signer, hash, signature);
+        }
+    }
+
     /**
      * @dev Checks if a signature is valid for a given signer and data hash. The signature is validated
      * against the signer smart contract using ERC-1271.
@@ -49,13 +65,26 @@ library SignatureChecker {
         address signer,
         bytes32 hash,
         bytes memory signature
-    ) internal view returns (bool) {
-        (bool success, bytes memory result) = signer.staticcall(
-            abi.encodeCall(IERC1271.isValidSignature, (hash, signature))
-        );
-        return (success &&
-            result.length >= 32 &&
-            abi.decode(result, (bytes32)) == bytes32(IERC1271.isValidSignature.selector));
+    ) internal view returns (bool result) {
+        bytes4 selector = IERC1271.isValidSignature.selector;
+        uint256 length = signature.length;
+
+        assembly ("memory-safe") {
+            // Encoded calldata is :
+            // [ 0x00 - 0x03 ] <selector>
+            // [ 0x04 - 0x23 ] <hash>
+            // [ 0x24 - 0x44 ] <signature offset> (0x40)
+            // [ 0x44 - 0x64 ] <signature length>
+            // [ 0x64 - ...  ] <signature data>
+            let ptr := mload(0x40)
+            mstore(ptr, selector)
+            mstore(add(ptr, 0x04), hash)
+            mstore(add(ptr, 0x24), 0x40)
+            mcopy(add(ptr, 0x44), signature, add(length, 0x20))
+
+            let success := staticcall(gas(), signer, ptr, add(length, 0x64), 0, 0x20)
+            result := and(success, and(gt(returndatasize(), 0x19), eq(mload(0x00), selector)))
+        }
     }
 
     /**

+ 3 - 2
contracts/utils/cryptography/signers/MultiSignerERC7913.sol

@@ -160,7 +160,8 @@ abstract contract MultiSignerERC7913 is AbstractSigner {
      *
      * Requirements:
      *
-     * * The {signers}'s length must be `>=` to the {threshold}. Throws {MultiSignerERC7913UnreachableThreshold} if not.
+     * * The {getSignerCount} must be greater or equal than to the {threshold}. Throws
+     * {MultiSignerERC7913UnreachableThreshold} if not.
      */
     function _validateReachableThreshold() internal view virtual {
         uint256 signersLength = _signers.length();
@@ -225,7 +226,7 @@ abstract contract MultiSignerERC7913 is AbstractSigner {
      *
      * Requirements:
      *
-     * * The `signatures` arrays must be at least as large as the `signers` arrays. Panics otherwise.
+     * * The `signatures` and `signers` arrays must be equal in length. Returns false otherwise.
      */
     function _validateSignatures(
         bytes32 hash,

+ 14 - 0
contracts/utils/cryptography/signers/MultiSignerERC7913Weighted.sol

@@ -129,6 +129,20 @@ abstract contract MultiSignerERC7913Weighted is MultiSignerERC7913 {
         _validateReachableThreshold();
     }
 
+    /**
+     * @dev See {MultiSignerERC7913-_addSigners}.
+     *
+     * In cases where {totalWeight} is almost `type(uint64).max` (due to a large `_totalExtraWeight`), adding new
+     * signers could cause the {totalWeight} computation to overflow. Adding a {totalWeight} calls after the new
+     * signers are added ensures no such overflow happens.
+     */
+    function _addSigners(bytes[] memory newSigners) internal virtual override {
+        super._addSigners(newSigners);
+
+        // This will revert if the new signers cause an overflow
+        _validateReachableThreshold();
+    }
+
     /**
      * @dev See {MultiSignerERC7913-_removeSigners}.
      *

+ 4 - 0
contracts/utils/cryptography/signers/SignerERC7913.sol

@@ -21,6 +21,10 @@ import {SignatureChecker} from "../SignatureChecker.sol";
  *     function initialize(bytes memory signer_) public initializer {
  *       _setSigner(signer_);
  *     }
+ *
+ *     function setSigner(bytes memory signer_) public onlyEntryPointOrSelf {
+ *       _setSigner(signer_);
+ *     }
  * }
  * ```
  *

+ 24 - 16
contracts/utils/structs/EnumerableMap.sol

@@ -220,8 +220,9 @@ library EnumerableMap {
     /**
      * @dev Removes all the entries from a map. O(n).
      *
-     * WARNING: Developers should keep in mind that this function has an unbounded cost and using it may render the
-     * function uncallable if the map grows to the point where clearing it consumes too much gas to fit in a block.
+     * WARNING: This function has an unbounded cost that scales with map size. Developers should keep in mind that
+     * using it may render the function uncallable if the map grows to the point where clearing it consumes too much
+     * gas to fit in a block.
      */
     function clear(UintToUintMap storage map) internal {
         clear(map._inner);
@@ -342,8 +343,9 @@ library EnumerableMap {
     /**
      * @dev Removes all the entries from a map. O(n).
      *
-     * WARNING: Developers should keep in mind that this function has an unbounded cost and using it may render the
-     * function uncallable if the map grows to the point where clearing it consumes too much gas to fit in a block.
+     * WARNING: This function has an unbounded cost that scales with map size. Developers should keep in mind that
+     * using it may render the function uncallable if the map grows to the point where clearing it consumes too much
+     * gas to fit in a block.
      */
     function clear(UintToAddressMap storage map) internal {
         clear(map._inner);
@@ -464,8 +466,9 @@ library EnumerableMap {
     /**
      * @dev Removes all the entries from a map. O(n).
      *
-     * WARNING: Developers should keep in mind that this function has an unbounded cost and using it may render the
-     * function uncallable if the map grows to the point where clearing it consumes too much gas to fit in a block.
+     * WARNING: This function has an unbounded cost that scales with map size. Developers should keep in mind that
+     * using it may render the function uncallable if the map grows to the point where clearing it consumes too much
+     * gas to fit in a block.
      */
     function clear(UintToBytes32Map storage map) internal {
         clear(map._inner);
@@ -586,8 +589,9 @@ library EnumerableMap {
     /**
      * @dev Removes all the entries from a map. O(n).
      *
-     * WARNING: Developers should keep in mind that this function has an unbounded cost and using it may render the
-     * function uncallable if the map grows to the point where clearing it consumes too much gas to fit in a block.
+     * WARNING: This function has an unbounded cost that scales with map size. Developers should keep in mind that
+     * using it may render the function uncallable if the map grows to the point where clearing it consumes too much
+     * gas to fit in a block.
      */
     function clear(AddressToUintMap storage map) internal {
         clear(map._inner);
@@ -708,8 +712,9 @@ library EnumerableMap {
     /**
      * @dev Removes all the entries from a map. O(n).
      *
-     * WARNING: Developers should keep in mind that this function has an unbounded cost and using it may render the
-     * function uncallable if the map grows to the point where clearing it consumes too much gas to fit in a block.
+     * WARNING: This function has an unbounded cost that scales with map size. Developers should keep in mind that
+     * using it may render the function uncallable if the map grows to the point where clearing it consumes too much
+     * gas to fit in a block.
      */
     function clear(AddressToAddressMap storage map) internal {
         clear(map._inner);
@@ -834,8 +839,9 @@ library EnumerableMap {
     /**
      * @dev Removes all the entries from a map. O(n).
      *
-     * WARNING: Developers should keep in mind that this function has an unbounded cost and using it may render the
-     * function uncallable if the map grows to the point where clearing it consumes too much gas to fit in a block.
+     * WARNING: This function has an unbounded cost that scales with map size. Developers should keep in mind that
+     * using it may render the function uncallable if the map grows to the point where clearing it consumes too much
+     * gas to fit in a block.
      */
     function clear(AddressToBytes32Map storage map) internal {
         clear(map._inner);
@@ -960,8 +966,9 @@ library EnumerableMap {
     /**
      * @dev Removes all the entries from a map. O(n).
      *
-     * WARNING: Developers should keep in mind that this function has an unbounded cost and using it may render the
-     * function uncallable if the map grows to the point where clearing it consumes too much gas to fit in a block.
+     * WARNING: This function has an unbounded cost that scales with map size. Developers should keep in mind that
+     * using it may render the function uncallable if the map grows to the point where clearing it consumes too much
+     * gas to fit in a block.
      */
     function clear(Bytes32ToUintMap storage map) internal {
         clear(map._inner);
@@ -1082,8 +1089,9 @@ library EnumerableMap {
     /**
      * @dev Removes all the entries from a map. O(n).
      *
-     * WARNING: Developers should keep in mind that this function has an unbounded cost and using it may render the
-     * function uncallable if the map grows to the point where clearing it consumes too much gas to fit in a block.
+     * WARNING: This function has an unbounded cost that scales with map size. Developers should keep in mind that
+     * using it may render the function uncallable if the map grows to the point where clearing it consumes too much
+     * gas to fit in a block.
      */
     function clear(Bytes32ToAddressMap storage map) internal {
         clear(map._inner);

+ 3 - 2
contracts/utils/structs/EnumerableSet.sol

@@ -126,8 +126,9 @@ library EnumerableSet {
     /**
      * @dev Removes all the values from a set. O(n).
      *
-     * WARNING: Developers should keep in mind that this function has an unbounded cost and using it may render the
-     * function uncallable if the set grows to the point where clearing it consumes too much gas to fit in a block.
+     * WARNING: This function has an unbounded cost that scales with set size. Developers should keep in mind that
+     * using it may render the function uncallable if the set grows to the point where clearing it consumes too much
+     * gas to fit in a block.
      */
     function _clear(Set storage set) private {
         uint256 len = _length(set);

+ 5 - 0
docs/modules/ROOT/nav.adoc

@@ -7,6 +7,11 @@
 
 * xref:access-control.adoc[Access Control]
 
+* xref:account-abstraction.adoc[Account Abstraction]
+** xref:accounts.adoc[Accounts]
+*** xref:eoa-delegation.adoc[EOA Delegation]
+*** xref:multisig.adoc[Multisig]
+
 * xref:tokens.adoc[Tokens]
 ** xref:erc20.adoc[ERC-20]
 *** xref:erc20-supply.adoc[Creating Supply]

+ 100 - 0
docs/modules/ROOT/pages/account-abstraction.adoc

@@ -0,0 +1,100 @@
+= Account Abstraction
+
+Unlike Externally Owned Accounts (EOAs), smart contracts may contain arbitrary verification logic based on authentication mechanisms different to Ethereum's native xref:api:utils.adoc#ECDSA[ECDSA] and have execution advantages such as batching or gas sponsorship. To leverage these properties of smart contracts, the community has widely adopted https://eips.ethereum.org/EIPS/eip-4337[ERC-4337], a standard to process user operations through an alternative mempool.
+
+The library provides multiple contracts for Account Abstraction following this standard as it enables more flexible and user-friendly interactions with applications. Account Abstraction use cases include wallets in novel contexts (e.g. embedded wallets), more granular configuration of accounts, and recovery mechanisms. 
+
+== ERC-4337 Overview
+
+The ERC-4337 is a detailed specification of how to implement the necessary logic to handle operations without making changes to the protocol level (i.e. the rules of the blockchain itself). This specification defines the following components:
+
+=== UserOperation
+
+A `UserOperation` is a higher-layer pseudo-transaction object that represents the intent of the account. This shares some similarities with regular EVM transactions like the concept of `gasFees` or `callData` but includes fields that enable new capabilities.
+
+```solidity
+struct PackedUserOperation {
+    address sender;
+    uint256 nonce;
+    bytes initCode; // concatenation of factory address and factoryData (or empty)
+    bytes callData;
+    bytes32 accountGasLimits; // concatenation of verificationGas (16 bytes) and callGas (16 bytes)
+    uint256 preVerificationGas;
+    bytes32 gasFees; // concatenation of maxPriorityFee (16 bytes) and maxFeePerGas (16 bytes)
+    bytes paymasterAndData; // concatenation of paymaster fields (or empty)
+    bytes signature;
+}
+```
+
+This process of bundling user operations involves several costs that the bundler must cover, including base transaction fees, calldata serialization, entrypoint execution, and paymaster context costs. To compensate for these expenses, bundlers use the `preVerificationGas` and `gasFees` fields to charge users appropriately.
+
+NOTE: Estimating `preVerificationGas` is not standardized as it varies based on network conditions such as gas prices and the size of the operation bundle.
+
+TIP: Use xref:api:account.adoc#ERC4337Utils[`ERC4337Utils`] to manipulate the `UserOperation` struct and other ERC-4337 related values.
+
+=== Entrypoint
+
+Each `UserOperation` is executed through a contract known as the https://etherscan.io/address/0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108#code[`EntryPoint`]. This contract is a singleton deployed across multiple networks at the same address although other custom implementations may be used.
+
+The Entrypoint contracts is considered a trusted entity by the account.
+
+=== Bundlers
+
+The bundler is a piece of _offchain_ infrastructure that is in charge of processing an alternative mempool of user operations. Bundlers themselves call the Entrypoint contract's `handleOps` function with an array of UserOperations that are executed and included in a block.
+
+During the process, the bundler pays for the gas of executing the transaction and gets refunded during the execution phase of the Entrypoint contract.
+
+```solidity
+/// @dev Process `userOps` and `beneficiary` receives all
+/// the gas fees collected during the bundle execution.
+function handleOps(
+    PackedUserOperation[] calldata ops,
+    address payable beneficiary
+) external { ... }
+```
+
+=== Account Contract
+
+The Account Contract is a smart contract that implements the logic required to validate a `UserOperation` in the context of ERC-4337. Any smart contract account should conform with the `IAccount` interface to validate operations.
+
+```solidity
+interface IAccount {
+    function validateUserOp(PackedUserOperation calldata, bytes32, uint256) external returns (uint256 validationData);
+}
+```
+
+Similarly, an Account should have a way to execute these operations by either handling arbitrary calldata on its `fallback` or implementing the `IAccountExecute` interface:
+
+```solidity
+interface IAccountExecute {
+    function executeUserOp(PackedUserOperation calldata userOp, bytes32 userOpHash) external;
+}
+```
+
+NOTE: The `IAccountExecute` interface is optional. Developers might want to use xref:api:account.adoc#ERC7821[`ERC-7821`] for a minimal batched execution interface or rely on ERC-7579 or any other execution logic.
+
+To build your own account, see xref:accounts.adoc[accounts].
+
+=== Factory Contract
+
+The smart contract accounts are created by a Factory contract defined by the Account developer. This factory receives arbitrary bytes as `initData` and returns an `address` where the logic of the account is deployed.
+
+To build your own factory, see xref:accounts.adoc#accounts_factory[account factories].
+
+=== Paymaster Contract
+
+A Paymaster is an optional entity that can sponsor gas fees for Accounts, or allow them to pay for those fees in ERC-20 instead of native currency. This abstracts gas away of the user experience in the same way that computational costs of cloud servers are abstracted away from end-users.
+
+To build your own paymaster, see https://docs.openzeppelin.com/community-contracts/0.0.1/paymasters[paymasters].
+
+== Further notes
+
+=== ERC-7562 Validation Rules
+
+To process a bundle of `UserOperations`, bundlers call xref:api:account.adoc#Account-validateUserOp-struct-PackedUserOperation-bytes32-uint256-[`validateUserOp`] on each operation sender to check whether the operation can be executed. However, the bundler has no guarantee that the state of the blockchain will remain the same after the validation phase. To overcome this problem, https://eips.ethereum.org/EIPS/eip-7562[ERC-7562] proposes a set of limitations to EVM code so that bundlers (or node operators) are protected from unexpected state changes.
+
+These rules outline the requirements for operations to be processed by the canonical mempool.
+
+Accounts can access its own storage during the validation phase, they might easily violate ERC-7562 storage access rules in undirect ways. For example, most accounts access their public keys from storage when validating a signature, limiting the ability of having accounts that validate operations for other accounts (e.g. via ERC-1271)
+
+TIP: Although any Account that breaks such rules may still be processed by a private bundler, developers should keep in mind the centralization tradeoffs of relying on private infrastructure instead of _permissionless_ execution.

+ 354 - 0
docs/modules/ROOT/pages/accounts.adoc

@@ -0,0 +1,354 @@
+= Smart Accounts
+
+OpenZeppelin provides a simple xref:api:account.adoc#Account[`Account`] implementation including only the basic logic to handle user operations in compliance with ERC-4337. Developers who want to build their own account can leverage it to bootstrap custom implementations.
+
+User operations are validated using an xref:api:utils.adoc#AbstractSigner[`AbstractSigner`], which requires to implement the internal xref:api:utils.adoc#AbstractSigner-_rawSignatureValidation-bytes32-bytes-[`_rawSignatureValidation`] function, of which we offer a set of implementations to cover a wide customization range. This is the lowest-level signature validation layer and is used to wrap other validation methods like the Account's xref:api:account.adoc#Account-validateUserOp-struct-PackedUserOperation-bytes32-uint256-[`validateUserOp`].
+
+== Setting up an account
+
+To setup an account, you can either start configuring it using our Wizard and selecting a predefined validation scheme, or bring your own logic and start by inheriting xref:api:account.adoc#Account[`Account`] from scratch. 
+
+++++
+<script async src="https://wizard.openzeppelin.com/build/embed.js"></script>
+
+<oz-wizard data-tab="Account" style="display: block; min-height: 40rem;"></oz-wizard>
+++++
+
+NOTE: Accounts don't support xref:erc721.adoc[ERC-721] and xref:erc1155.adoc[ERC-1155] tokens natively since these require the receiving address to implement an acceptance check. It is recommended to inherit xref:api:token/ERC721.adoc#ERC721Holder[ERC721Holder], xref:api:token/ERC1155.adoc#ERC1155Holder[ERC1155Holder] to include these checks in your account.
+
+=== Selecting a signer
+
+Since the minimum requirement of xref:api:account.adoc#Account[`Account`] is to provide an implementation of xref:api:utils/cryptography.adoc#AbstractSigner-_rawSignatureValidation-bytes32-bytes-[`_rawSignatureValidation`], the library includes specializations of the `AbstractSigner` contract that use custom digital signature verification algorithms. Some examples that you can select from include:
+
+* xref:api:utils/cryptography.adoc#SignerECDSA[`SignerECDSA`]: Verifies signatures produced by regular EVM Externally Owned Accounts (EOAs).
+* xref:api:utils/cryptography.adoc#SignerP256[`SignerP256`]: Validates signatures using the secp256r1 curve, common for World Wide Web Consortium (W3C) standards such as FIDO keys, passkeys or secure enclaves.
+* xref:api:utils/cryptography.adoc#SignerRSA[`SignerRSA`]: Verifies signatures of traditional PKI systems and X.509 certificates.
+* xref:api:utils/cryptography.adoc#SignerERC7702[`SignerERC7702`]: Checks EOA signatures delegated to this signer using https://eips.ethereum.org/EIPS/eip-7702#set-code-transaction[EIP-7702 authorizations]
+* xref:api:utils/cryptography.adoc#SignerERC7913[`SignerERC7913`]: Verifies generalized signatures following https://eips.ethereum.org/EIPS/eip-7913[ERC-7913].
+* https://docs.openzeppelin.com/community-contracts/0.0.1/api/utils#SignerZKEmail[`SignerZKEmail`]: Enables email-based authentication for smart contracts using zero knowledge proofs of email authority signatures.
+* xref:api:utils/cryptography.adoc#MultiSignerERC7913[`MultiSignerERC7913`]: Allows using multiple ERC-7913 signers with a threshold-based signature verification system.
+* xref:api:utils/cryptography.adoc#MultiSignerERC7913Weighted[`MultiSignerERC7913Weighted`]: Overrides the threshold mechanism of xref:api:utils/cryptography.adoc#MultiSignerERC7913[`MultiSignerERC7913`], offering different weights per signer.
+
+TIP: Given xref:api:utils/cryptography.adoc#SignerERC7913[`SignerERC7913`] provides a generalized standard for signature validation, you don't need to implement your own xref:api:utils/cryptography.adoc#AbstractSigner[`AbstractSigner`] for different signature schemes, consider bringing your own ERC-7913 verifier instead.
+
+==== Accounts factory
+
+The first time you send an user operation, your account will be created deterministically (i.e. its code and address can be predicted) using the the `initCode` field in the UserOperation. This field contains both the address of a smart contract (the factory) and the data required to call it and create your smart account.
+
+Suggestively, you can create your own account factory using the xref:api:proxy.adoc#Clones[Clones library], taking advantage of decreased deployment costs and account address predictability.
+
+[source,solidity]
+----
+include::api:example$account/MyFactoryAccount.sol[]
+----
+
+Account factories should be carefully implemented to ensure the account address is deterministically tied to the initial owners. This prevents frontrunning attacks where a malicious actor could deploy the account with their own owners before the intended owner does. The factory should include the owner's address in the salt used for address calculation.
+
+==== Handling initialization
+
+Most smart accounts are deployed by a factory, the best practice is to create xref:api:proxy.adoc#minimal_clones[minimal clones] of initializable contracts. These signer implementations provide an initializable design by default so that the factory can interact with the account to set it up right after deployment in a single transaction.
+
+[source,solidity]
+----
+import {Account} from "@openzeppelin/community-contracts/account/Account.sol";
+import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
+import {SignerECDSA} from "@openzeppelin/community-contracts/utils/cryptography/SignerECDSA.sol";
+
+contract MyAccount is Initializable, Account, SignerECDSA, ... {
+    // ...
+
+    function initializeECDSA(address signer) public initializer {
+        _setSigner(signer);
+    }
+}
+----
+
+Note that some account implementations may be deployed directly and therefore, won't require a factory.
+
+WARNING: Leaving an account uninitialized may leave it unusable since no public key was associated with it.
+
+=== Signature validation
+
+Regularly, accounts implement https://eips.ethereum.org/EIPS/eip-1271[ERC-1271] to enable smart contract signature verification given its wide adoption. To be compliant means that smart contract exposes an xref:api:interfaces.adoc#IERC1271-isValidSignature-bytes32-bytes-[`isValidSignature(bytes32 hash, bytes memory signature)`] method that returns `0x1626ba7e` to identify whether the signature is valid.
+
+The benefit of this standard is that it allows to receive any format of `signature` for a given `hash`. This generalized mechanism fits very well with the account abstraction principle of _bringing your own validation mechanism_.
+
+This is how you enable ERC-1271 using an xref:api:utils/cryptography.adoc#AbstractSigner[`AbstractSigner`]:
+
+[source,solidity]
+----
+function isValidSignature(bytes32 hash, bytes calldata signature) public view override returns (bytes4) {
+    return _rawSignatureValidation(hash, signature) ? IERC1271.isValidSignature.selector : bytes4(0xffffffff);
+}
+----
+
+IMPORTANT: We recommend using xref:api:utils/cryptography.adoc#ERC7739[ERC7739] to avoid replayability across accounts. This defensive rehashing mechanism that prevents signatures for this account to be replayed in another account controlled by the same signer. See xref:accounts.adoc#erc_7739_signatures[ERC-7739 signatures].
+
+=== Batched execution
+
+Batched execution allows accounts to execute multiple calls in a single transaction, which is particularly useful for bundling operations that need to be atomic. This is especially valuable in the context of account abstraction where you want to minimize the number of user operations and associated gas costs. xref:api:account.adoc#ERC7821[`ERC-7821`] standard provides a minimal interface for batched execution. 
+
+The library implementation supports a single batch mode (`0x01000000000000000000`) and allows accounts to execute multiple calls atomically. The standard includes access control through the xref:api:account.adoc#ERC7821-_erc7821AuthorizedExecutor-address-bytes32-bytes-[`_erc7821AuthorizedExecutor`] function, which by default only allows the contract itself to execute batches.
+
+Here's an example of how to use batched execution using EIP-7702:
+
+[source,solidity]
+----
+import {Account} from "@openzeppelin/community-contracts/account/Account.sol";
+import {ERC7821} from "@openzeppelin/community-contracts/account/extensions/draft-ERC7821.sol";
+import {SignerERC7702} from "@openzeppelin/community-contracts/utils/cryptography/SignerERC7702.sol";
+
+contract MyAccount is Account, SignerERC7702, ERC7821 {
+    // Override to allow the entrypoint to execute batches
+    function _erc7821AuthorizedExecutor(
+        address caller,
+        bytes32 mode,
+        bytes calldata executionData
+    ) internal view virtual override returns (bool) {
+        return caller == address(entryPoint()) || super._erc7821AuthorizedExecutor(caller, mode, executionData);
+    }
+}
+----
+
+The batched execution data follows a specific format that includes the calls to be executed. This format follows the same format as https://eips.ethereum.org/EIPS/eip-7579#execution-behavior[ERC-7579 execution] but only supports `0x01` call type (i.e. batched `call`) and default execution type (i.e. reverts if at least one subcall does).
+
+To encode an ERC-7821 batch, you can use https://viem.sh/[viem]'s utilities:
+
+[source,typescript]
+----
+// CALL_TYPE_BATCH, EXEC_TYPE_DEFAULT, ..., selector, payload 
+const mode = encodePacked(
+  ["bytes1", "bytes1", "bytes4", "bytes4", "bytes22"],
+  ["0x01", "0x00", "0x00000000", "0x00000000", "0x00000000000000000000000000000000000000000000"]
+);
+
+const entries = [
+  {
+    target: "0x000...0001",
+    value: 0n,
+    data: "0x000...000",
+  },
+  {
+    target: "0x000...0002",
+    value: 0n,
+    data: "0x000...000",
+  }
+];
+
+const batch = encodeAbiParameters(
+  [parseAbiParameter("(address,uint256,bytes)[]")],
+  [
+    entries.map<[Address, bigint, Hex]>((entry) =>
+      [entry.target, entry.value ?? 0n, entry.data ?? "0x"]
+    ),
+  ]
+);
+
+const userOpData = encodeFunctionData({
+    abi: account.abi,
+    functionName: "execute",
+    args: [mode, batch]
+});
+----
+
+== Bundle a `UserOperation`
+
+xref:account-abstraction.adoc#useroperation[UserOperations] are a powerful abstraction layer that enable more sophisticated transaction capabilities compared to traditional Ethereum transactions. To get started, you'll need to an account, which you can get by xref:accounts.adoc#accounts_factory[deploying a factory] for your implementation.
+
+=== Preparing a UserOp
+
+A UserOperation is a struct that contains all the necessary information for the EntryPoint to execute your transaction. You'll need the `sender`, `nonce`, `accountGasLimits` and `callData` fields to construct a `PackedUserOperation` that can be signed later (to populate the `signature` field).
+
+TIP: Specify `paymasterAndData` with the address of a paymaster contract concatenated to `data` that will be passed to the paymaster's validatePaymasterUserOp function to support sponsorship as part of your user operation.
+
+Here's how to prepare one using https://viem.sh/[viem]:
+
+[source,typescript]
+----
+import { getContract, createWalletClient, http, Hex } from 'viem';
+
+const walletClient = createWalletClient({
+  account, // See Viem's `privateKeyToAccount`
+  chain, // import { ... } from 'viem/chains';
+  transport: http(),
+})
+
+const entrypoint = getContract({
+  abi: [/* ENTRYPOINT ABI */],
+  address: '0x<ENTRYPOINT_ADDRESS>',
+  client: walletClient,
+});
+
+const userOp = {
+  sender: '0x<YOUR_ACCOUNT_ADDRESS>',
+  nonce: await entrypoint.read.getNonce([sender, 0n]),
+  initCode: "0x" as Hex,
+  callData: '0x<CALLDATA_TO_EXECUTE_IN_THE_ACCOUNT>',
+  accountGasLimits: encodePacked(
+    ["uint128", "uint128"],
+    [
+      100_000n, // verificationGasLimit
+      300_000n, // callGasLimit
+    ]
+  ),
+  preVerificationGas: 50_000n,
+  gasFees: encodePacked(
+    ["uint128", "uint128"],
+    [
+      0n, // maxPriorityFeePerGas
+      0n, // maxFeePerGas
+    ]
+  ),
+  paymasterAndData: "0x" as Hex,
+  signature: "0x" as Hex,
+};
+----
+
+In case your account hasn't been deployed yet, make sure to provide the `initCode` field as `abi.encodePacked(factory, factoryData)` to deploy the account within the same UserOp:
+
+[source,typescript]
+----
+const deployed = await publicClient.getCode({ address: predictedAddress });
+
+if (!deployed) {
+  userOp.initCode = encodePacked(
+    ["address", "bytes"],
+    [
+      '0x<ACCOUNT_FACTORY_ADDRESS>',
+      encodeFunctionData({
+        abi: [/* ACCOUNT ABI */],
+        functionName: "<FUNCTION NAME>",
+        args: [...],
+      }),
+    ]
+  );
+}
+----
+
+==== Estimating gas
+
+To calculate gas parameters of a `UserOperation`, developers should carefully consider the following fields:
+
+* `verificationGasLimit`: This covers the gas costs for signature verification, paymaster validation (if used), and account validation logic. While a typical value is around 100,000 gas units, this can vary significantly based on the complexity of your signature validation scheme in both the account and paymaster contracts.
+
+* `callGasLimit`: This parameter accounts for the actual execution of your account's logic. It's recommended to use `eth_estimateGas` for each subcall and add additional buffer for computational overhead.
+
+* `preVerificationGas`: This compensates for the EntryPoint's execution overhead. While 50,000 gas is a reasonable starting point, you may need to increase this value based on your UserOperation's size and specific bundler requirements.
+
+NOTE: The `maxFeePerGas` and `maxPriorityFeePerGas` values are typically provided by your bundler service, either through their SDK or a custom RPC method.
+
+IMPORTANT: A penalty of 10% (`UNUSED_GAS_PENALTY_PERCENT`) is applied on the amounts of `callGasLimit` and `paymasterPostOpGasLimit` gas that remains unused if the amount of remaining unused gas is greater than or equal to 40,000 (`PENALTY_GAS_THRESHOLD`).
+
+=== Signing the UserOp
+
+To sign a UserOperation, you'll need to first calculate its hash as an EIP-712 typed data structure using the EntryPoint's domain, then sign this hash using your account's signature scheme, and finally encode the resulting signature in the format that your account contract expects for verification.
+
+[source,typescript]
+----
+import { signTypedData } from 'viem/actions';
+
+// EntryPoint v0.8 EIP-712 domain
+const domain = {
+  name: 'ERC4337',
+  version: '1',
+  chainId: 1, // Your target chain ID
+  verifyingContract: '0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108', // v08
+};
+
+// EIP-712 types for PackedUserOperation
+const types = {
+  PackedUserOperation: [
+    { name: 'sender', type: 'address' },
+    { name: 'nonce', type: 'uint256' },
+    { name: 'initCode', type: 'bytes' },
+    { name: 'callData', type: 'bytes' },
+    { name: 'accountGasLimits', type: 'bytes32' },
+    { name: 'preVerificationGas', type: 'uint256' },
+    { name: 'gasFees', type: 'bytes32' },
+    { name: 'paymasterAndData', type: 'bytes' },
+  ],
+} as const;
+
+// Sign the UserOperation using EIP-712
+userOp.signature = await eoa.signTypedData({
+  domain,
+  types,
+  primaryType: 'PackedUserOperation',
+  message: {
+    sender: userOp.sender,
+    nonce: userOp.nonce,
+    initCode: userOp.initCode,
+    callData: userOp.callData,
+    accountGasLimits: userOp.accountGasLimits,
+    preVerificationGas: userOp.preVerificationGas,
+    gasFees: userOp.gasFees,
+    paymasterAndData: userOp.paymasterAndData,
+  },
+});
+----
+
+Alternatively, developers can get the raw user operation hash by using the Entrypoint's `getUserOpHash` function:
+
+[source,typescript]
+----
+const userOpHash = await entrypoint.read.getUserOpHash([userOp]);
+userOp.signature = await eoa.sign({ hash: userOpHash });
+----
+
+IMPORTANT: Using `getUserOpHash` directly may provide a poorer user experience as users see an opaque hash rather than structured transaction data. In many cases, offchain signers won't have an option to sign a raw hash.
+
+=== Sending the UserOp
+
+Finally, to send the user operation you can call `handleOps` on the Entrypoint contract and set yourself as the `beneficiary`. 
+
+[source,typescript]
+----
+// Send the UserOperation
+const userOpReceipt = await walletClient
+  .writeContract({
+    abi: [/* ENTRYPOINT ABI */],
+    address: '0x<ENTRYPOINT_ADDRESS>',
+    functionName: "handleOps",
+    args: [[userOp], eoa.address],
+  })
+  .then((txHash) =>
+    publicClient.waitForTransactionReceipt({
+      hash: txHash,
+    })
+  );
+
+// Print receipt
+console.log(userOpReceipt);
+----
+
+TIP: Since you're bundling your user operations yourself, you can safely specify `preVerificationGas` and `maxFeePerGas` in 0.
+
+=== Using a Bundler
+
+For better reliability, consider using a bundler service. Bundlers provide several key benefits: they automatically handle gas estimation, manage transaction ordering, support bundling multiple operations together, and generally offer higher transaction success rates compared to self-bundling.
+
+== Further notes
+
+=== ERC-7739 Signatures
+
+A common security practice to prevent user operation https://mirror.xyz/curiousapple.eth/pFqAdW2LiJ-6S4sg_u1z08k4vK6BCJ33LcyXpnNb8yU[replayability across smart contract accounts controlled by the same private key] (i.e. multiple accounts for the same signer) is to link the signature to the `address` and `chainId` of the account. This can be done by asking the user to sign a hash that includes these values.
+
+The problem with this approach is that the user might be prompted by the wallet provider to sign an https://x.com/howydev/status/1780353754333634738[obfuscated message], which is a phishing vector that may lead to a user losing its assets.
+
+To prevent this, developers may use xref:api:account#ERC7739Signer[`ERC7739Signer`], a utility that implements xref:api:interfaces#IERC1271[`IERC1271`] for smart contract signatures with a defensive rehashing mechanism based on a https://github.com/frangio/eip712-wrapper-for-eip1271[nested EIP-712 approach] to wrap the signature request in a context where there's clearer information for the end user.
+
+=== EIP-7702 Delegation
+
+https://eips.ethereum.org/EIPS/eip-7702[EIP-7702] lets EOAs delegate to smart contracts while keeping their original signing key. This creates a hybrid account that works like an EOA for signing but has smart contract features. Protocols don't need major changes to support EIP-7702 since they already handle both EOAs and smart contracts (see xref:api:utils/cryptography.adoc#SignatureChecker[SignatureChecker]).
+
+The signature verification stays compatible: delegated EOAs are treated as contracts using ERC-1271, making it easy to redelegate to a contract with ERC-1271 support with little overhead by reusing the validation mechanism of the account. 
+
+TIP: Learn more about delegating to an ERC-7702 account in our xref:eoa-delegation.adoc[EOA Delegation] section.
+
+=== ERC-7579 Modules
+
+Smart accounts have evolved to embrace modularity as a design principle, with popular implementations like https://erc7579.com/#supporters[Safe, Pimlico, Rhinestone, Etherspot and many others] agreeing on ERC-7579 as the standard for module interoperability. This standardization enables accounts to extend their functionality through external contracts while maintaining compatibility across different implementations.
+
+OpenZeppelin Contracts provides both the building blocks for creating ERC-7579-compliant modules and an xref:api:account.adoc#AccountERC7579[`AccountERC7579`] implementation that supports installing and managing these modules. 
+
+TIP: Learn more in our https://docs.openzeppelin.com/community-contracts/0.0.1/account-modules[account modules] section.

+ 143 - 0
docs/modules/ROOT/pages/eoa-delegation.adoc

@@ -0,0 +1,143 @@
+= EOA Delegation
+
+https://eips.ethereum.org/EIPS/eip-7702[EIP-7702] introduces a new transaction type (`0x4`) that grants https://ethereum.org/en/developers/docs/accounts/[Externally Owned Accounts (EOAs)] the ability to delegate execution to an smart contract. This is particularly useful to enable traditional EVM accounts to:
+
+* Batch multiple operations in a single transaction (e.g. xref:api:token/ERC20.adoc#IERC20-approve-address-uint256-[`approve`] + xref:api:token/ERC20.adoc#IERC20-transfer-address-uint256-[`transfer`], yey!)
+* Sponsoring transactions for other users.
+* Implementing privilege de-escalation (e.g., sub-keys with limited permissions)
+
+This section walks you through the process of delegating an EOA to a contract following https://eips.ethereum.org/EIPS/eip-7702[ERC-7702]. This allows you to use your EOA's private key to sign and execute operations with custom execution logic. Combined with https://eips.ethereum.org/EIPS/eip-4337[ERC-4337] infrastructure, users can achieve gas sponsoring through https://docs.openzeppelin.com/community-contracts/paymasters[paymasters].
+
+== Delegating execution
+
+EIP-7702 enables EOAs to delegate their execution capabilities to smart contracts, effectively bridging the gap between traditional and xref:accounts.adoc[Smart Accounts]. The xref:api:utils/cryptography.adoc#[`SignerERC7702`] utility facilitates this delegation by verifying signatures against the EOA's address (`address(this)`), making it easier to implement EIP-7702 in smart contract accounts.
+
+[source,solidity]
+----
+include::api:example$account/MyAccountERC7702.sol[]
+----
+
+TIP: Users can delegate to an instance of xref:api:account.adoc#ERC7821[`ERC-7821`] for a minimal batch executor that does not use ERC-4337 related code.
+
+=== Signing Authorization
+
+To authorize delegation, the EOA owner signs a message containing the chain ID, nonce, delegation address, and signature components (i.e. `[chain_id, address, nonce, y_parity, r, s]`). This signed authorization serves two purposes: it restricts execution to only the delegate contract and prevents replay attacks.
+
+The EOA maintains a delegation designator for each authorized address on each chain, which points to the contract whose code will be executed in the EOA's context to handle delegated operations.
+
+Here's how to construct an authorization with https://viem.sh/[viem]:
+
+[source, typescript]
+----
+// Remember not to hardcode your private key! 
+const eoa = privateKeyToAccount('<YOUR_PRIVATE_KEY>');
+const eoaClient = createWalletClient({
+  account: eoa,
+  chain: publicClient.chain,
+  transport: http(),
+});
+
+const walletClient = createWalletClient({
+  account, // See Viem's `privateKeyToAccount`
+  chain, // import { ... } from 'viem/chains';
+  transport: http(),
+})
+
+const authorization = await eoaClient.signAuthorization({
+  account: walletClient.account.address,
+  contractAddress: '0x<YOUR_DELEGATE_CONTRACT_ADDRESS>',
+  // Use instead of `account` if your 
+  // walletClient's account is the one sending the transaction
+  // executor: "self",
+});
+----
+
+NOTE: When implementing delegate contracts, ensure they require signatures that avoid replayability (e.g. a domain separator, nonce).
+A poorly implemented delegate can allow a malicious actor to take near complete control over a signer's EOA.
+
+=== Send a Set Code Transaction
+
+After signing the authorization, you need to send a `SET_CODE_TX_TYPE` (0x04) transaction to write the delegation designator (i.e. `0xef0100 || address`) to your EOA's code, which tells the EVM to load and execute code from the specified address when operations are performed on your EOA.
+
+[source, typescript]
+----
+// Send the `authorization` along with `data`
+const receipt = await walletClient
+  .sendTransaction({
+    authorizationList: [authorization],
+    data: '0x<CALLDATA_TO_EXECUTE_IN_THE_ACCOUNT>',
+    to: eoa.address,
+  })
+  .then((txHash) =>
+    publicClient.waitForTransactionReceipt({
+      hash: txHash,
+    })
+  );
+
+// Print receipt
+console.log(userOpReceipt);
+----
+
+To remove the delegation and restore your EOA to its original state, you can send a `SET_CODE_TX_TYPE` transaction with an authorization tuple that points to the zero address (`0x0000000000000000000000000000000000000000`). This will clear the account's code and reset its code hash to the empty hash, however, be aware that it will not automatically clean the EOA storage.
+
+When changing an account's delegation, ensure the newly delegated code is purposely designed and tested as an upgrade to the old one. To ensure safe migration between delegate contracts, namespaced storage that avoid accidental collisions following ERC-7201. 
+
+WARNING: Updating the delegation designator may render your EOA unusable due to potential storage collisions. We recommend following similar practices to those of https://docs.openzeppelin.com/upgrades-plugins/writing-upgradeable[writing upgradeable smart contracts].
+
+== Using with ERC-4337
+
+The ability to set code to execute logic on an EOA allows users to leverage ERC-4337 infrastructure to process user operations. Developers only need to combine an xref:api:account.adoc#Account[`Account`] contract with an xref:api:utils/cryptography.adoc#SignerERC7702[`SignerERC7702`] to accomplish ERC-4337 compliance out of the box.
+
+=== Sending a UserOp
+
+Once your EOA is delegated to an ERC-4337 compatible account, you can send user operations through the entry point contract. The user operation includes all the necessary fields for execution, including gas limits, fees, and the actual call data to execute. The entry point will validate the operation and execute it in the context of your delegated account.
+
+Similar to how xref:accounts.adoc#bundle_a_useroperation[sending a UserOp] is achieved for factory accounts, here's how you can construct a UserOp for an EOA who's delegated to an xref:api:account.adoc#Account[`Account`] contract.
+
+[source, typescript]
+----
+const userOp = {
+  sender: eoa.address,
+  nonce: await entrypoint.read.getNonce([eoa.address, 0n]),
+  initCode: "0x" as Hex,
+  callData: '0x<CALLDATA_TO_EXECUTE_IN_THE_ACCOUNT>',
+  accountGasLimits: encodePacked(
+    ["uint128", "uint128"],
+    [
+      100_000n, // verificationGas
+      300_000n, // callGas
+    ]
+  ),
+  preVerificationGas: 50_000n,
+  gasFees: encodePacked(
+    ["uint128", "uint128"],
+    [
+      0n, // maxPriorityFeePerGas
+      0n, // maxFeePerGas
+    ]
+  ),
+  paymasterAndData: "0x" as Hex,
+  signature: "0x" as Hex,
+};
+
+const userOpHash = await entrypoint.read.getUserOpHash([userOp]);
+userOp.signature = await eoa.sign({ hash: userOpHash });
+
+const userOpReceipt = await eoaClient
+  .writeContract({
+    abi: EntrypointV08Abi,
+    address: entrypoint.address,
+    authorizationList: [authorization],
+    functionName: "handleOps",
+    args: [[userOp], eoa.address],
+  })
+  .then((txHash) =>
+    publicClient.waitForTransactionReceipt({
+      hash: txHash,
+    })
+  );
+
+console.log(userOpReceipt);
+----
+
+NOTE: When using sponsored transaction relayers, be aware that the authorized account can cause relayers to spend gas without being reimbursed by either invalidating the authorization (increasing the account's nonce) or by sweeping the relevant assets out of the account. Relayers may implement safeguards like requiring a bond or using a reputation system.

+ 306 - 0
docs/modules/ROOT/pages/multisig.adoc

@@ -0,0 +1,306 @@
+= Multisig Account
+
+A multi-signature (multisig) account is a smart account that requires multiple authorized signers to approve operations before execution. Unlike traditional accounts controlled by a single private key, multisigs distribute control among multiple parties, eliminating single points of failure. For example, a 2-of-3 multisig requires signatures from at least 2 out of 3 possible signers.
+
+Popular implementations like https://safe.global/[Safe] (formerly Gnosis Safe) have become the standard for securing valuable assets. Multisigs provide enhanced security through collective authorization, customizable controls for ownership and thresholds, and the ability to rotate signers without changing the account address.
+
+== Beyond Standard Signature Verification
+
+As discussed in the xref:accounts.adoc#signature_validation[accounts section], the standard approach for smart contracts to verify signatures is https://eips.ethereum.org/EIPS/eip-1271[ERC-1271], which defines an `isValidSignature(hash, signature)`. However, it is limited in two important ways:
+
+1. It assumes the signer has an EVM address
+2. It treats the signer as a single identity
+
+This becomes problematic when implementing multisig accounts where:
+
+* You may want to use signers that don't have EVM addresses (like keys from hardware devices)
+* Each signer needs to be individually verified rather than treated as a collective identity
+* You need a threshold system to determine when enough valid signatures are present
+
+The https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/SignatureChecker.sol[SignatureChecker] library is useful for verifying EOA and ERC-1271 signatures, but it's not designed for more complex arrangements like threshold-based multisigs.
+
+== ERC-7913 Signers
+
+https://eips.ethereum.org/EIPS/eip-7913[ERC-7913] extends the concept of signer representation to include keys that don't have EVM addresses, addressing this limitation. OpenZeppelin implements this standard through three contracts:
+
+=== SignerERC7913
+
+The xref:api:utils.adoc#SignerERC7913[`SignerERC7913`] contract allows a single ERC-7913 formatted signer to control an account. The signer is represented as a `bytes` object that concatenates a verifier address and a key: `verifier || key`.
+
+[source,solidity]
+----
+// contracts/MyAccountERC7913.sol
+// SPDX-License-Identifier: MIT
+
+pragma solidity ^0.8.24;
+
+import {Account} from "@openzeppelin/community-contracts/account/Account.sol";
+import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
+import {ERC721Holder} from "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol";
+import {ERC1155Holder} from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol";
+import {ERC7739} from "@openzeppelin/community-contracts/utils/cryptography/signers/ERC7739.sol";
+import {ERC7821} from "@openzeppelin/community-contracts/account/extensions/ERC7821.sol";
+import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
+import {SignerERC7913} from "@openzeppelin/community-contracts/utils/cryptography/signers/SignerERC7913.sol";
+
+contract MyAccountERC7913 is Account, SignerERC7913, ERC7739, ERC7821, ERC721Holder, ERC1155Holder, Initializable {
+    constructor() EIP712("MyAccount7913", "1") {}
+
+    function initialize(bytes memory signer) public initializer {
+        _setSigner(signer);
+    }
+
+    function setSigner(bytes memory signer) public onlyEntryPointOrSelf {
+        _setSigner(signer);
+    }
+
+    /// @dev Allows the entry point as an authorized executor.
+    function _erc7821AuthorizedExecutor(
+        address caller,
+        bytes32 mode,
+        bytes calldata executionData
+    ) internal view virtual override returns (bool) {
+        return caller == address(entryPoint()) || super._erc7821AuthorizedExecutor(caller, mode, executionData);
+    }
+}
+----
+
+WARNING: Leaving an account uninitialized may leave it unusable since no public key was associated with it.
+
+=== MultiSignerERC7913
+
+The xref:api:utils/cryptography.adoc#MultiSignerERC7913[`MultiSignerERC7913`] contract extends this concept to support multiple signers with a threshold-based signature verification system.
+
+[source,solidity]
+----
+// contracts/MyAccountMultiSigner.sol
+// SPDX-License-Identifier: MIT
+
+pragma solidity ^0.8.27;
+
+import {Account} from "@openzeppelin/community-contracts/account/Account.sol";
+import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
+import {ERC721Holder} from "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol";
+import {ERC1155Holder} from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol";
+import {ERC7739} from "@openzeppelin/community-contracts/utils/cryptography/signers/ERC7739.sol";
+import {ERC7821} from "@openzeppelin/community-contracts/account/extensions/ERC7821.sol";
+import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
+import {MultiSignerERC7913} from "@openzeppelin/community-contracts/utils/cryptography/signers/MultiSignerERC7913.sol";
+
+contract MyAccountMultiSigner is
+    Account,
+    MultiSignerERC7913,
+    ERC7739,
+    ERC7821,
+    ERC721Holder,
+    ERC1155Holder,
+    Initializable
+{
+    constructor() EIP712("MyAccountMultiSigner", "1") {}
+
+    function initialize(bytes[] memory signers, uint256 threshold) public initializer {
+        _addSigners(signers);
+        _setThreshold(threshold);
+    }
+
+    function addSigners(bytes[] memory signers) public onlyEntryPointOrSelf {
+        _addSigners(signers);
+    }
+
+    function removeSigners(bytes[] memory signers) public onlyEntryPointOrSelf {
+        _removeSigners(signers);
+    }
+
+    function setThreshold(uint256 threshold) public onlyEntryPointOrSelf {
+        _setThreshold(threshold);
+    }
+
+    /// @dev Allows the entry point as an authorized executor.
+    function _erc7821AuthorizedExecutor(
+        address caller,
+        bytes32 mode,
+        bytes calldata executionData
+    ) internal view virtual override returns (bool) {
+        return caller == address(entryPoint()) || super._erc7821AuthorizedExecutor(caller, mode, executionData);
+    }
+}
+----
+
+This implementation is ideal for standard multisig setups where each signer has equal authority, and a fixed number of approvals is required.
+
+The `MultiSignerERC7913` contract provides several key features for managing multi-signature accounts. It maintains a set of authorized signers and implements a threshold-based system that requires a minimum number of signatures to approve operations. The contract includes an internal interface for managing signers, allowing for the addition and removal of authorized parties.
+
+NOTE: `MultiSignerERC7913` safeguards to ensure that the threshold remains achievable based on the current number of active signers, preventing situations where operations could become impossible to execute.
+
+The contract also provides public functions for querying signer information: xref:api:utils/cryptography.adoc#MultiSignerERC7913-isSigner-bytes-[`isSigner(bytes memory signer)`] to check if a given signer is authorized, xref:api:utils/cryptography.adoc#MultiSignerERC7913-getSigners-uint64-uint64-[`getSigners(uint64 start, uint64 end)`] to retrieve a paginated list of authorized signers, and xref:api:utils/cryptography.adoc#MultiSignerERC7913-getSignerCount[`getSignerCount()`] to get the total number of signers. These functions are useful when validating signatures, implementing customized access control logic, or building user interfaces that need to display signer information.
+
+=== MultiSignerERC7913Weighted
+
+For more sophisticated governance structures, the xref:api:utils/cryptography.adoc#MultiSignerERC7913Weighted[`MultiSignerERC7913Weighted`] contract extends `MultiSignerERC7913` by assigning different weights to each signer.
+
+[source,solidity]
+----
+// contracts/MyAccountMultiSignerWeighted.sol
+// SPDX-License-Identifier: MIT
+
+pragma solidity ^0.8.27;
+
+import {Account} from "@openzeppelin/community-contracts/account/Account.sol";
+import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
+import {ERC721Holder} from "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol";
+import {ERC1155Holder} from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol";
+import {ERC7739} from "@openzeppelin/community-contracts/utils/cryptography/signers/ERC7739.sol";
+import {ERC7821} from "@openzeppelin/community-contracts/account/extensions/ERC7821.sol";
+import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
+import {MultiSignerERC7913Weighted} from "@openzeppelin/community-contracts/utils/cryptography/signers/MultiSignerERC7913Weighted.sol";
+
+contract MyAccountMultiSignerWeighted is
+    Account,
+    MultiSignerERC7913Weighted,
+    ERC7739,
+    ERC7821,
+    ERC721Holder,
+    ERC1155Holder,
+    Initializable
+{
+    constructor() EIP712("MyAccountMultiSignerWeighted", "1") {}
+
+    function initialize(bytes[] memory signers, uint256[] memory weights, uint256 threshold) public initializer {
+        _addSigners(signers);
+        _setSignerWeights(signers, weights);
+        _setThreshold(threshold);
+    }
+
+    function addSigners(bytes[] memory signers) public onlyEntryPointOrSelf {
+        _addSigners(signers);
+    }
+
+    function removeSigners(bytes[] memory signers) public onlyEntryPointOrSelf {
+        _removeSigners(signers);
+    }
+
+    function setThreshold(uint256 threshold) public onlyEntryPointOrSelf {
+        _setThreshold(threshold);
+    }
+
+    function setSignerWeights(bytes[] memory signers, uint256[] memory weights) public onlyEntryPointOrSelf {
+        _setSignerWeights(signers, weights);
+    }
+
+    /// @dev Allows the entry point as an authorized executor.
+    function _erc7821AuthorizedExecutor(
+        address caller,
+        bytes32 mode,
+        bytes calldata executionData
+    ) internal view virtual override returns (bool) {
+        return caller == address(entryPoint()) || super._erc7821AuthorizedExecutor(caller, mode, executionData);
+    }
+}
+----
+
+This implementation is perfect for scenarios where different signers should have varying levels of authority, such as:
+
+* Board members with different voting powers
+* Organizational structures with hierarchical decision-making
+* Hybrid governance systems combining core team and community members
+* Execution setups like "social recovery" where you trust particular guardians more than others
+
+The `MultiSignerERC7913Weighted` contract extends `MultiSignerERC7913` with a weighting system. Each signer can have a custom weight, and operations require the total weight of signing participants to meet or exceed the threshold. Signers without explicit weights default to a weight of 1.
+
+NOTE: When setting up a weighted multisig, ensure the threshold value matches the scale used for signer weights. For example, if signers have weights like 1, 2, or 3, then a threshold of 4 would require at least two signers (e.g., one with weight 1 and one with weight 3).
+
+== Setting Up a Multisig Account
+
+To create a multisig account, you need to:
+
+1. Define your signers
+2. Determine your threshold
+3. Initialize your account with these parameters
+
+The example below demonstrates setting up a 2-of-3 multisig account with different types of signers:
+
+[source,solidity]
+----
+// Example setup code
+function setupMultisigAccount() external {
+    // Create signers using different types of keys
+    bytes memory ecdsaSigner = alice; // EOA address (20 bytes)
+    
+    // P256 signer with format: verifier || pubKey
+    bytes memory p256Signer = abi.encodePacked(
+        p256Verifier,
+        bobP256PublicKeyX,
+        bobP256PublicKeyY
+    );
+    
+    // RSA signer with format: verifier || pubKey
+    bytes memory rsaSigner = abi.encodePacked(
+        rsaVerifier,
+        abi.encode(charlieRSAPublicKeyE, charlieRSAPublicKeyN)
+    );
+    
+    // Create array of signers
+    bytes[] memory signers = new bytes[](3);
+    signers[0] = ecdsaSigner;
+    signers[1] = p256Signer;
+    signers[2] = rsaSigner;
+    
+    // Set threshold to 2 (2-of-3 multisig)
+    uint256 threshold = 2;
+    
+    // Initialize the account
+    myMultisigAccount.initialize(signers, threshold);
+}
+----
+
+For a weighted multisig, you would also specify weights:
+
+[source,solidity]
+----
+// Example setup for weighted multisig
+function setupWeightedMultisigAccount() external {
+    // Create array of signers (same as above)
+    bytes[] memory signers = new bytes[](3);
+    signers[0] = ecdsaSigner;
+    signers[1] = p256Signer;
+    signers[2] = rsaSigner;
+    
+    // Assign weights to signers (Alice:1, Bob:2, Charlie:3)
+    uint256[] memory weights = new uint256[](3);
+    weights[0] = 1;
+    weights[1] = 2;
+    weights[2] = 3;
+    
+    // Set threshold to 4 (requires at least Bob+Charlie or all three)
+    uint256 threshold = 4;
+    
+    // Initialize the weighted account
+    myWeightedMultisigAccount.initialize(signers, weights, threshold);
+}
+----
+
+IMPORTANT: The xref:api:utils/cryptography.adoc#MultiSignerERC7913-_validateReachableThreshold--[`_validateReachableThreshold`] function ensures that the sum of weights for all active signers meets or exceeds the threshold. Any customization built on top of the multisigner contracts must ensure the threshold is always reachable.
+
+For multisig accounts, the signature is a complex structure that contains both the signers and their individual signatures. The format follows ERC-7913's specification and must be properly encoded.
+
+=== Signature Format
+
+The multisig signature is encoded as:
+
+[source,solidity]
+----
+abi.encode(
+    bytes[] signers,   // Array of signers sorted by `keccak256`
+    bytes[] signatures // Array of signatures corresponding to each signer
+)
+----
+
+Where:
+
+* `signers` is an array of the signers participating in this particular signature
+* `signatures` is an array of the individual signatures corresponding to each signer
+
+[NOTE]
+====
+To avoid duplicate signers, the contract uses `keccak256` to generate a unique id for each signer. When providing a multisignature, the `signers` array should be sorted in ascending order by `keccak256`, and the `signatures` array must match the order of their corresponding signers.
+====

+ 34 - 2
docs/modules/ROOT/pages/utilities.adoc

@@ -99,7 +99,7 @@ IMPORTANT: Always use keys of at least 2048 bits. Additionally, be aware that PK
 
 === Signature Verification
 
-The xref:api:utils.adoc#SignatureChecker[`SignatureChecker`] library provides a unified interface for verifying signatures from different sources. It seamlessly supports:
+The xref:api:utils/cryptography.adoc#SignatureChecker[`SignatureChecker`] library provides a unified interface for verifying signatures from different sources. It seamlessly supports:
 
 * ECDSA signatures from externally owned accounts (EOAs)
 * ERC-1271 signatures from smart contract wallets like Argent and Safe Wallet
@@ -263,7 +263,7 @@ Some use cases require more powerful data structures than arrays and mappings of
 - xref:api:utils.adoc#EnumerableSet[`EnumerableSet`]: A https://en.wikipedia.org/wiki/Set_(abstract_data_type)[set] with enumeration capabilities.
 - xref:api:utils.adoc#EnumerableMap[`EnumerableMap`]: A `mapping` variant with enumeration capabilities.
 - xref:api:utils.adoc#MerkleTree[`MerkleTree`]: An on-chain https://wikipedia.org/wiki/Merkle_Tree[Merkle Tree] with helper functions.
-- xref:api:utils.adoc#Heap.sol[`Heap`]: A 
+- xref:api:utils.adoc#Heap.sol[`Heap`]: A https://en.wikipedia.org/wiki/Binary_heap[binary heap] to store elements with priority defined by a compartor function.
 
 The `Enumerable*` structures are similar to mappings in that they store and remove elements in constant time and don't allow for repeated entries, but they also support _enumeration_, which means you can easily query all stored entries both on and off-chain.
 
@@ -461,6 +461,38 @@ await instance.multicall([
 ]);
 ----
 
+=== Memory
+
+The xref:api:utils.adoc#Memory[`Memory`] library provides functions for advanced use cases that require granular memory management. A common use case is to avoid unnecessary memory expansion costs when performing repeated operations that allocate memory in a loop. Consider the following example:
+
+[source,solidity]
+----
+function processMultipleItems(uint256[] memory items) internal {
+  for (uint256 i = 0; i < items.length; i++) {
+    bytes memory tempData = abi.encode(items[i], block.timestamp);
+    // Process tempData...
+  }
+}
+----
+
+Note that each iteration allocates new memory for `tempData`, causing the memory to expand continuously. This can be optimized by resetting the memory pointer between iterations:
+
+[source,solidity]
+----
+function processMultipleItems(uint256[] memory items) internal {
+  Memory.Pointer ptr = Memory.getFreeMemoryPointer(); // Cache pointer
+  for (uint256 i = 0; i < items.length; i++) {
+    bytes memory tempData = abi.encode(items[i], block.timestamp);
+    // Process tempData...
+    Memory.setFreeMemoryPointer(ptr); // Reset pointer for reuse
+  }
+}
+----
+
+This way, memory allocated for `tempData` in each iteration is reused, significantly reducing memory expansion costs when processing many items.
+
+IMPORTANT: Only use these functions after carefully confirming they're necessary. By default, Solidity handles memory safely. Using this library without understanding memory layout and safety may be dangerous. See the https://docs.soliditylang.org/en/v0.8.20/internals/layout_in_memory.html[memory layout] and https://docs.soliditylang.org/en/v0.8.20/assembly.html#memory-safety[memory safety] documentation for details.
+
 === Historical Block Hashes
 
 xref:api:utils.adoc#Blockhash[`Blockhash`] provides L2 protocol developers with extended access to historical block hashes beyond Ethereum's native 256-block limit. By leveraging https://eips.ethereum.org/EIPS/eip-2935[EIP-2935]'s history storage contract, the library enables access to block hashes up to 8,191 blocks in the past, making it invaluable for L2 fraud proofs and state verification systems.

+ 3 - 2
scripts/generate/templates/EnumerableMap.js

@@ -219,8 +219,9 @@ function remove(${name} storage map, ${key.type} key) internal returns (bool) {
 /**
  * @dev Removes all the entries from a map. O(n).
  *
- * WARNING: Developers should keep in mind that this function has an unbounded cost and using it may render the
- * function uncallable if the map grows to the point where clearing it consumes too much gas to fit in a block.
+ * WARNING: This function has an unbounded cost that scales with map size. Developers should keep in mind that
+ * using it may render the function uncallable if the map grows to the point where clearing it consumes too much
+ * gas to fit in a block.
  */
 function clear(${name} storage map) internal {
     clear(map._inner);

+ 3 - 2
scripts/generate/templates/EnumerableSet.js

@@ -130,8 +130,9 @@ function _remove(Set storage set, bytes32 value) private returns (bool) {
 /**
  * @dev Removes all the values from a set. O(n).
  *
- * WARNING: Developers should keep in mind that this function has an unbounded cost and using it may render the
- * function uncallable if the set grows to the point where clearing it consumes too much gas to fit in a block.
+ * WARNING: This function has an unbounded cost that scales with set size. Developers should keep in mind that
+ * using it may render the function uncallable if the set grows to the point where clearing it consumes too much
+ * gas to fit in a block.
  */
 function _clear(Set storage set) private {
     uint256 len = _length(set);

+ 16 - 0
test/account/AccountMultiSignerWeighted.test.js

@@ -6,6 +6,7 @@ const { getDomain } = require('../helpers/eip712');
 const { ERC4337Helper } = require('../helpers/erc4337');
 const { NonNativeSigner, P256SigningKey, RSASHA256SigningKey, MultiERC7913SigningKey } = require('../helpers/signers');
 const { PackedUserOperation } = require('../helpers/eip712-types');
+const { MAX_UINT64 } = require('../helpers/constants');
 
 const { shouldBehaveLikeAccountCore, shouldBehaveLikeAccountHolder } = require('./Account.behavior');
 const { shouldBehaveLikeERC1271 } = require('../utils/cryptography/ERC1271.behavior');
@@ -292,5 +293,20 @@ describe('AccountMultiSignerWeighted', function () {
         .withArgs(signer1)
         .to.not.emit(this.mock, 'ERC7913SignerWeightChanged');
     });
+
+    it('should revert if total weight to overflow (_setSignerWeights)', async function () {
+      await expect(this.mock.$_setSignerWeights([signer1, signer2, signer3], [1n, 1n, MAX_UINT64 - 1n]))
+        .to.be.revertedWithCustomError(this.mock, 'SafeCastOverflowedUintDowncast')
+        .withArgs(64, MAX_UINT64 + 1n);
+    });
+
+    it('should revert if total weight to overflow (_addSigner)', async function () {
+      await this.mock.$_setSignerWeights([signer1, signer2, signer3], [1n, 1n, MAX_UINT64 - 2n]);
+      await expect(this.mock.totalWeight()).to.eventually.equal(MAX_UINT64);
+
+      await expect(this.mock.$_addSigners([signer4]))
+        .to.be.revertedWithCustomError(this.mock, 'SafeCastOverflowedUintDowncast')
+        .withArgs(64, MAX_UINT64 + 1n);
+    });
   });
 });

+ 2 - 0
test/helpers/constants.js

@@ -1,5 +1,7 @@
 module.exports = {
+  MAX_UINT16: 2n ** 16n - 1n,
   MAX_UINT32: 2n ** 32n - 1n,
   MAX_UINT48: 2n ** 48n - 1n,
   MAX_UINT64: 2n ** 64n - 1n,
+  MAX_UINT128: 2n ** 128n - 1n,
 };

+ 74 - 0
test/utils/Bytes.t.sol

@@ -0,0 +1,74 @@
+// SPDX-License-Identifier: MIT
+
+pragma solidity ^0.8.20;
+
+import {Test} from "forge-std/Test.sol";
+import {Bytes} from "@openzeppelin/contracts/utils/Bytes.sol";
+
+contract BytesTest is Test {
+    // REVERSE BITS
+    function testSymbolicReverseBytes32(bytes32 value) public pure {
+        assertEq(Bytes.reverseBytes32(Bytes.reverseBytes32(value)), value);
+    }
+
+    function testSymbolicReverseBytes16(bytes16 value) public pure {
+        assertEq(Bytes.reverseBytes16(Bytes.reverseBytes16(value)), value);
+    }
+
+    function testSymbolicReverseBytes16Dirty(bytes16 value) public pure {
+        assertEq(Bytes.reverseBytes16(Bytes.reverseBytes16(_dirtyBytes16(value))), value);
+        assertEq(Bytes.reverseBytes16(_dirtyBytes16(Bytes.reverseBytes16(value))), value);
+    }
+
+    function testSymbolicReverseBytes8(bytes8 value) public pure {
+        assertEq(Bytes.reverseBytes8(Bytes.reverseBytes8(value)), value);
+    }
+
+    function testSymbolicReverseBytes8Dirty(bytes8 value) public pure {
+        assertEq(Bytes.reverseBytes8(Bytes.reverseBytes8(_dirtyBytes8(value))), value);
+        assertEq(Bytes.reverseBytes8(_dirtyBytes8(Bytes.reverseBytes8(value))), value);
+    }
+
+    function testSymbolicReverseBytes4(bytes4 value) public pure {
+        assertEq(Bytes.reverseBytes4(Bytes.reverseBytes4(value)), value);
+    }
+
+    function testSymbolicReverseBytes4Dirty(bytes4 value) public pure {
+        assertEq(Bytes.reverseBytes4(Bytes.reverseBytes4(_dirtyBytes4(value))), value);
+        assertEq(Bytes.reverseBytes4(_dirtyBytes4(Bytes.reverseBytes4(value))), value);
+    }
+
+    function testSymbolicReverseBytes2(bytes2 value) public pure {
+        assertEq(Bytes.reverseBytes2(Bytes.reverseBytes2(value)), value);
+    }
+
+    function testSymbolicReverseBytes2Dirty(bytes2 value) public pure {
+        assertEq(Bytes.reverseBytes2(Bytes.reverseBytes2(_dirtyBytes2(value))), value);
+        assertEq(Bytes.reverseBytes2(_dirtyBytes2(Bytes.reverseBytes2(value))), value);
+    }
+
+    // Helpers
+    function _dirtyBytes16(bytes16 value) private pure returns (bytes16 dirty) {
+        assembly ("memory-safe") {
+            dirty := or(value, shr(128, not(0)))
+        }
+    }
+
+    function _dirtyBytes8(bytes8 value) private pure returns (bytes8 dirty) {
+        assembly ("memory-safe") {
+            dirty := or(value, shr(192, not(0)))
+        }
+    }
+
+    function _dirtyBytes4(bytes4 value) private pure returns (bytes4 dirty) {
+        assembly ("memory-safe") {
+            dirty := or(value, shr(224, not(0)))
+        }
+    }
+
+    function _dirtyBytes2(bytes2 value) private pure returns (bytes2 dirty) {
+        assembly ("memory-safe") {
+            dirty := or(value, shr(240, not(0)))
+        }
+    }
+}

+ 124 - 0
test/utils/Bytes.test.js

@@ -1,6 +1,14 @@
 const { ethers } = require('hardhat');
 const { expect } = require('chai');
 const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
+const { MAX_UINT128, MAX_UINT64, MAX_UINT32, MAX_UINT16 } = require('../helpers/constants');
+
+// Helper functions for fixed bytes types
+const bytes32 = value => ethers.toBeHex(value, 32);
+const bytes16 = value => ethers.toBeHex(value, 16);
+const bytes8 = value => ethers.toBeHex(value, 8);
+const bytes4 = value => ethers.toBeHex(value, 4);
+const bytes2 = value => ethers.toBeHex(value, 2);
 
 async function fixture() {
   const mock = await ethers.deployContract('$Bytes');
@@ -85,4 +93,120 @@ describe('Bytes', function () {
       }
     });
   });
+
+  describe('reverseBits', function () {
+    describe('reverseBytes32', function () {
+      it('reverses bytes correctly', async function () {
+        await expect(this.mock.$reverseBytes32(bytes32(0))).to.eventually.equal(bytes32(0));
+        await expect(this.mock.$reverseBytes32(bytes32(ethers.MaxUint256))).to.eventually.equal(
+          bytes32(ethers.MaxUint256),
+        );
+
+        // Test complex pattern that clearly shows byte reversal
+        await expect(
+          this.mock.$reverseBytes32('0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'),
+        ).to.eventually.equal('0xefcdab8967452301efcdab8967452301efcdab8967452301efcdab8967452301');
+      });
+
+      it('double reverse returns original', async function () {
+        const values = [0n, 1n, 0x12345678n, ethers.MaxUint256];
+        for (const value of values) {
+          const reversed = await this.mock.$reverseBytes32(bytes32(value));
+          await expect(this.mock.$reverseBytes32(reversed)).to.eventually.equal(bytes32(value));
+        }
+      });
+    });
+
+    describe('reverseBytes16', function () {
+      it('reverses bytes correctly', async function () {
+        await expect(this.mock.$reverseBytes16(bytes16(0))).to.eventually.equal(bytes16(0));
+        await expect(this.mock.$reverseBytes16(bytes16(MAX_UINT128))).to.eventually.equal(bytes16(MAX_UINT128));
+
+        // Test complex pattern that clearly shows byte reversal
+        await expect(this.mock.$reverseBytes16('0x0123456789abcdef0123456789abcdef')).to.eventually.equal(
+          '0xefcdab8967452301efcdab8967452301',
+        );
+      });
+
+      it('double reverse returns original', async function () {
+        const values = [0n, 1n, 0x12345678n, MAX_UINT128];
+        for (const value of values) {
+          const reversed = await this.mock.$reverseBytes16(bytes16(value));
+          // Cast back to uint128 for comparison since function returns uint256
+          await expect(this.mock.$reverseBytes16(reversed)).to.eventually.equal(bytes16(value & MAX_UINT128));
+        }
+      });
+    });
+
+    describe('reverseBytes8', function () {
+      it('reverses bytes correctly', async function () {
+        await expect(this.mock.$reverseBytes8(bytes8(0))).to.eventually.equal(bytes8(0));
+        await expect(this.mock.$reverseBytes8(bytes8(MAX_UINT64))).to.eventually.equal(bytes8(MAX_UINT64));
+
+        // Test known pattern: 0x123456789ABCDEF0 -> 0xF0DEBC9A78563412
+        await expect(this.mock.$reverseBytes8('0x123456789abcdef0')).to.eventually.equal('0xf0debc9a78563412');
+      });
+
+      it('double reverse returns original', async function () {
+        const values = [0n, 1n, 0x12345678n, MAX_UINT64];
+        for (const value of values) {
+          const reversed = await this.mock.$reverseBytes8(bytes8(value));
+          // Cast back to uint64 for comparison since function returns uint256
+          await expect(this.mock.$reverseBytes8(reversed)).to.eventually.equal(bytes8(value & MAX_UINT64));
+        }
+      });
+    });
+
+    describe('reverseBytes4', function () {
+      it('reverses bytes correctly', async function () {
+        await expect(this.mock.$reverseBytes4(bytes4(0))).to.eventually.equal(bytes4(0));
+        await expect(this.mock.$reverseBytes4(bytes4(MAX_UINT32))).to.eventually.equal(bytes4(MAX_UINT32));
+
+        // Test known pattern: 0x12345678 -> 0x78563412
+        await expect(this.mock.$reverseBytes4(bytes4(0x12345678))).to.eventually.equal(bytes4(0x78563412));
+      });
+
+      it('double reverse returns original', async function () {
+        const values = [0n, 1n, 0x12345678n, MAX_UINT32];
+        for (const value of values) {
+          const reversed = await this.mock.$reverseBytes4(bytes4(value));
+          // Cast back to uint32 for comparison since function returns uint256
+          await expect(this.mock.$reverseBytes4(reversed)).to.eventually.equal(bytes4(value & MAX_UINT32));
+        }
+      });
+    });
+
+    describe('reverseBytes2', function () {
+      it('reverses bytes correctly', async function () {
+        await expect(this.mock.$reverseBytes2(bytes2(0))).to.eventually.equal(bytes2(0));
+        await expect(this.mock.$reverseBytes2(bytes2(MAX_UINT16))).to.eventually.equal(bytes2(MAX_UINT16));
+
+        // Test known pattern: 0x1234 -> 0x3412
+        await expect(this.mock.$reverseBytes2(bytes2(0x1234))).to.eventually.equal(bytes2(0x3412));
+      });
+
+      it('double reverse returns original', async function () {
+        const values = [0n, 1n, 0x1234n, MAX_UINT16];
+        for (const value of values) {
+          const reversed = await this.mock.$reverseBytes2(bytes2(value));
+          // Cast back to uint16 for comparison since function returns uint256
+          await expect(this.mock.$reverseBytes2(reversed)).to.eventually.equal(bytes2(value & MAX_UINT16));
+        }
+      });
+    });
+
+    describe('edge cases', function () {
+      it('handles single byte values', async function () {
+        await expect(this.mock.$reverseBytes2(bytes2(0x00ff))).to.eventually.equal(bytes2(0xff00));
+        await expect(this.mock.$reverseBytes4(bytes4(0x000000ff))).to.eventually.equal(bytes4(0xff000000));
+      });
+
+      it('handles alternating patterns', async function () {
+        await expect(this.mock.$reverseBytes2(bytes2(0xaaaa))).to.eventually.equal(bytes2(0xaaaa));
+        await expect(this.mock.$reverseBytes2(bytes2(0x5555))).to.eventually.equal(bytes2(0x5555));
+        await expect(this.mock.$reverseBytes4(bytes4(0xaaaaaaaa))).to.eventually.equal(bytes4(0xaaaaaaaa));
+        await expect(this.mock.$reverseBytes4(bytes4(0x55555555))).to.eventually.equal(bytes4(0x55555555));
+      });
+    });
+  });
 });

+ 21 - 0
test/utils/Memory.t.sol

@@ -0,0 +1,21 @@
+// SPDX-License-Identifier: MIT
+
+pragma solidity ^0.8.20;
+
+import {Test} from "forge-std/Test.sol";
+import {Memory} from "@openzeppelin/contracts/utils/Memory.sol";
+
+contract MemoryTest is Test {
+    using Memory for *;
+
+    // - first 0x80 bytes are reserved (scratch + FMP + zero)
+    uint256 constant START_PTR = 0x80;
+    // - moving the free memory pointer to far causes OOG errors
+    uint256 constant END_PTR = type(uint24).max;
+
+    function testGetsetFreeMemoryPointer(uint256 seed) public pure {
+        bytes32 ptr = bytes32(bound(seed, START_PTR, END_PTR));
+        ptr.asPointer().setFreeMemoryPointer();
+        assertEq(Memory.getFreeMemoryPointer().asBytes32(), ptr);
+    }
+}

+ 39 - 0
test/utils/Memory.test.js

@@ -0,0 +1,39 @@
+const { ethers } = require('hardhat');
+const { expect } = require('chai');
+const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
+
+async function fixture() {
+  const mock = await ethers.deployContract('$Memory');
+  return { mock };
+}
+
+describe('Memory', function () {
+  beforeEach(async function () {
+    Object.assign(this, await loadFixture(fixture));
+  });
+
+  describe('free pointer', function () {
+    it('sets free memory pointer', async function () {
+      const ptr = ethers.toBeHex(0xa0, 32);
+      await expect(this.mock.$setFreeMemoryPointer(ptr)).to.not.be.reverted;
+    });
+
+    it('gets free memory pointer', async function () {
+      await expect(this.mock.$getFreeMemoryPointer()).to.eventually.equal(
+        ethers.toBeHex(0x80, 32), // Default pointer
+      );
+    });
+  });
+
+  describe('pointer conversions', function () {
+    it('asBytes32', async function () {
+      const ptr = ethers.toBeHex('0x1234', 32);
+      await expect(this.mock.$asBytes32(ptr)).to.eventually.equal(ptr);
+    });
+
+    it('asPointer', async function () {
+      const ptr = ethers.toBeHex('0x1234', 32);
+      await expect(this.mock.$asPointer(ptr)).to.eventually.equal(ptr);
+    });
+  });
+});

+ 11 - 0
test/utils/Strings.test.js

@@ -186,6 +186,17 @@ describe('Strings', function () {
     });
   });
 
+  describe('bytes', function () {
+    describe('toHexString', function () {
+      for (const length of [0, 17, 20, 32, 42, 64, 512]) {
+        const input = ethers.hexlify(ethers.randomBytes(length));
+        it(`hexlify buffer of length ${length}`, async function () {
+          expect(await this.mock.getFunction('$toHexString(bytes)')(input)).to.equal(input);
+        });
+      }
+    });
+  });
+
   describe('equal', function () {
     it('compares two empty strings', async function () {
       expect(await this.mock.$equal('', '')).to.be.true;

+ 28 - 0
test/utils/cryptography/ECDSA.test.js

@@ -44,6 +44,7 @@ describe('ECDSA', function () {
 
         // Recover the signer address from the generated message and signature.
         expect(await this.mock.$recover(ethers.hashMessage(TEST_MESSAGE), signature)).to.equal(this.signer);
+        expect(await this.mock.$recoverCalldata(ethers.hashMessage(TEST_MESSAGE), signature)).to.equal(this.signer);
       });
 
       it('returns signer address with correct signature for arbitrary length message', async function () {
@@ -52,11 +53,13 @@ describe('ECDSA', function () {
 
         // Recover the signer address from the generated message and signature.
         expect(await this.mock.$recover(ethers.hashMessage(NON_HASH_MESSAGE), signature)).to.equal(this.signer);
+        expect(await this.mock.$recoverCalldata(ethers.hashMessage(NON_HASH_MESSAGE), signature)).to.equal(this.signer);
       });
 
       it('returns a different address', async function () {
         const signature = await this.signer.signMessage(TEST_MESSAGE);
         expect(await this.mock.$recover(WRONG_MESSAGE, signature)).to.not.be.equal(this.signer);
+        expect(await this.mock.$recoverCalldata(WRONG_MESSAGE, signature)).to.not.be.equal(this.signer);
       });
 
       it('reverts with invalid signature', async function () {
@@ -66,6 +69,10 @@ describe('ECDSA', function () {
           this.mock,
           'ECDSAInvalidSignature',
         );
+        await expect(this.mock.$recoverCalldata(TEST_MESSAGE, signature)).to.be.revertedWithCustomError(
+          this.mock,
+          'ECDSAInvalidSignature',
+        );
       });
     });
 
@@ -79,6 +86,7 @@ describe('ECDSA', function () {
         const v = '0x1b'; // 27 = 1b.
         const signature = ethers.concat([signatureWithoutV, v]);
         expect(await this.mock.$recover(TEST_MESSAGE, signature)).to.equal(signer);
+        expect(await this.mock.$recoverCalldata(TEST_MESSAGE, signature)).to.equal(signer);
 
         const { r, s, yParityAndS: vs } = ethers.Signature.from(signature);
         expect(await this.mock.getFunction('$recover(bytes32,uint8,bytes32,bytes32)')(TEST_MESSAGE, v, r, s)).to.equal(
@@ -92,6 +100,7 @@ describe('ECDSA', function () {
         const v = '0x1c'; // 28 = 1c.
         const signature = ethers.concat([signatureWithoutV, v]);
         expect(await this.mock.$recover(TEST_MESSAGE, signature)).to.not.equal(signer);
+        expect(await this.mock.$recoverCalldata(TEST_MESSAGE, signature)).to.not.equal(signer);
 
         const { r, s, yParityAndS: vs } = ethers.Signature.from(signature);
         expect(
@@ -110,6 +119,10 @@ describe('ECDSA', function () {
             this.mock,
             'ECDSAInvalidSignature',
           );
+          await expect(this.mock.$recoverCalldata(TEST_MESSAGE, signature)).to.be.revertedWithCustomError(
+            this.mock,
+            'ECDSAInvalidSignature',
+          );
 
           const { r, s } = ethers.Signature.from(signature);
           await expect(
@@ -126,6 +139,9 @@ describe('ECDSA', function () {
         await expect(this.mock.$recover(TEST_MESSAGE, compactSerialized))
           .to.be.revertedWithCustomError(this.mock, 'ECDSAInvalidSignatureLength')
           .withArgs(64);
+        await expect(this.mock.$recoverCalldata(TEST_MESSAGE, compactSerialized))
+          .to.be.revertedWithCustomError(this.mock, 'ECDSAInvalidSignatureLength')
+          .withArgs(64);
       });
     });
 
@@ -139,6 +155,7 @@ describe('ECDSA', function () {
         const v = '0x1c'; // 28 = 1c.
         const signature = ethers.concat([signatureWithoutV, v]);
         expect(await this.mock.$recover(TEST_MESSAGE, signature)).to.equal(signer);
+        expect(await this.mock.$recoverCalldata(TEST_MESSAGE, signature)).to.equal(signer);
 
         const { r, s, yParityAndS: vs } = ethers.Signature.from(signature);
         expect(await this.mock.getFunction('$recover(bytes32,uint8,bytes32,bytes32)')(TEST_MESSAGE, v, r, s)).to.equal(
@@ -152,6 +169,7 @@ describe('ECDSA', function () {
         const v = '0x1b'; // 27 = 1b.
         const signature = ethers.concat([signatureWithoutV, v]);
         expect(await this.mock.$recover(TEST_MESSAGE, signature)).to.not.equal(signer);
+        expect(await this.mock.$recoverCalldata(TEST_MESSAGE, signature)).to.not.equal(signer);
 
         const { r, s, yParityAndS: vs } = ethers.Signature.from(signature);
         expect(
@@ -170,6 +188,10 @@ describe('ECDSA', function () {
             this.mock,
             'ECDSAInvalidSignature',
           );
+          await expect(this.mock.$recoverCalldata(TEST_MESSAGE, signature)).to.be.revertedWithCustomError(
+            this.mock,
+            'ECDSAInvalidSignature',
+          );
 
           const { r, s } = ethers.Signature.from(signature);
           await expect(
@@ -186,6 +208,9 @@ describe('ECDSA', function () {
         await expect(this.mock.$recover(TEST_MESSAGE, compactSerialized))
           .to.be.revertedWithCustomError(this.mock, 'ECDSAInvalidSignatureLength')
           .withArgs(64);
+        await expect(this.mock.$recoverCalldata(TEST_MESSAGE, compactSerialized))
+          .to.be.revertedWithCustomError(this.mock, 'ECDSAInvalidSignatureLength')
+          .withArgs(64);
       });
     });
 
@@ -202,6 +227,9 @@ describe('ECDSA', function () {
       await expect(this.mock.$recover(message, highSSignature))
         .to.be.revertedWithCustomError(this.mock, 'ECDSAInvalidSignatureS')
         .withArgs(s);
+      await expect(this.mock.$recoverCalldata(message, highSSignature))
+        .to.be.revertedWithCustomError(this.mock, 'ECDSAInvalidSignatureS')
+        .withArgs(s);
       await expect(this.mock.getFunction('$recover(bytes32,uint8,bytes32,bytes32)')(TEST_MESSAGE, v, r, s))
         .to.be.revertedWithCustomError(this.mock, 'ECDSAInvalidSignatureS')
         .withArgs(s);

+ 7 - 1
test/utils/cryptography/SignatureChecker.test.js

@@ -36,23 +36,29 @@ describe('SignatureChecker (ERC1271)', function () {
       await expect(
         this.mock.$isValidSignatureNow(ethers.Typed.address(this.signer.address), TEST_MESSAGE_HASH, this.signature),
       ).to.eventually.be.true;
+      await expect(this.mock.$isValidSignatureNowCalldata(this.signer.address, TEST_MESSAGE_HASH, this.signature)).to
+        .eventually.be.true;
     });
 
     it('with invalid signer', async function () {
       await expect(
         this.mock.$isValidSignatureNow(ethers.Typed.address(this.other.address), TEST_MESSAGE_HASH, this.signature),
       ).to.eventually.be.false;
+      await expect(this.mock.$isValidSignatureNowCalldata(this.other.address, TEST_MESSAGE_HASH, this.signature)).to
+        .eventually.be.false;
     });
 
     it('with invalid signature', async function () {
       await expect(
         this.mock.$isValidSignatureNow(ethers.Typed.address(this.signer.address), WRONG_MESSAGE_HASH, this.signature),
       ).to.eventually.be.false;
+      await expect(this.mock.$isValidSignatureNowCalldata(this.signer.address, WRONG_MESSAGE_HASH, this.signature)).to
+        .eventually.be.false;
     });
   });
 
   describe('ERC1271 wallet', function () {
-    for (const fn of ['isValidERC1271SignatureNow', 'isValidSignatureNow']) {
+    for (const fn of ['isValidERC1271SignatureNow', 'isValidSignatureNow', 'isValidSignatureNowCalldata']) {
       describe(fn, function () {
         it('with matching signer and signature', async function () {
           await expect(