Browse Source

Release v5.2 audit fixes (#5330)

Signed-off-by: Hadrien Croubois <hadrien.croubois@gmail.com>
Co-authored-by: Sam Bugs <101145325+0xsambugs@users.noreply.github.com>
Co-authored-by: Ernesto García <ernestognw@gmail.com>
Co-authored-by: Arr00 <13561405+arr00@users.noreply.github.com>
Co-authored-by: wizard <112275929+famouswizard@users.noreply.github.com>
Co-authored-by: leopardracer <136604165+leopardracer@users.noreply.github.com>
Co-authored-by: cairo <cairoeth@protonmail.com>
Hadrien Croubois 10 months ago
parent
commit
e5e9ff72f0

+ 1 - 1
.changeset/proud-planes-arrive.md

@@ -2,4 +2,4 @@
 'openzeppelin-solidity': minor
 'openzeppelin-solidity': minor
 ---
 ---
 
 
-`Bytes`: Add a library of common operation that operate on `bytes` objects.
+`Bytes`: Add a library of common operations that operate on `bytes` objects.

+ 1 - 1
GUIDELINES.md

@@ -55,7 +55,7 @@ External contributions must be reviewed separately by multiple maintainers.
 
 
 Automation should be used as much as possible to reduce the possibility of human error and forgetfulness.
 Automation should be used as much as possible to reduce the possibility of human error and forgetfulness.
 
 
-Automations that make use of sensitive credentials must use secure secret management, and must be strengthened against attacks such as [those on GitHub Actions worklows](https://github.com/nikitastupin/pwnhub).
+Automations that make use of sensitive credentials must use secure secret management, and must be strengthened against attacks such as [those on GitHub Actions workflows](https://github.com/nikitastupin/pwnhub).
 
 
 Some other examples of automation are:
 Some other examples of automation are:
 
 

+ 38 - 19
contracts/account/utils/draft-ERC4337Utils.sol

@@ -2,7 +2,7 @@
 
 
 pragma solidity ^0.8.20;
 pragma solidity ^0.8.20;
 
 
-import {IEntryPoint, PackedUserOperation} from "../../interfaces/draft-IERC4337.sol";
+import {PackedUserOperation} from "../../interfaces/draft-IERC4337.sol";
 import {Math} from "../../utils/math/Math.sol";
 import {Math} from "../../utils/math/Math.sol";
 import {Packing} from "../../utils/Packing.sol";
 import {Packing} from "../../utils/Packing.sol";
 
 
@@ -24,9 +24,9 @@ library ERC4337Utils {
     function parseValidationData(
     function parseValidationData(
         uint256 validationData
         uint256 validationData
     ) internal pure returns (address aggregator, uint48 validAfter, uint48 validUntil) {
     ) internal pure returns (address aggregator, uint48 validAfter, uint48 validUntil) {
-        validAfter = uint48(bytes32(validationData).extract_32_6(0x00));
-        validUntil = uint48(bytes32(validationData).extract_32_6(0x06));
-        aggregator = address(bytes32(validationData).extract_32_20(0x0c));
+        validAfter = uint48(bytes32(validationData).extract_32_6(0));
+        validUntil = uint48(bytes32(validationData).extract_32_6(6));
+        aggregator = address(bytes32(validationData).extract_32_20(12));
         if (validUntil == 0) validUntil = type(uint48).max;
         if (validUntil == 0) validUntil = type(uint48).max;
     }
     }
 
 
@@ -59,7 +59,8 @@ library ERC4337Utils {
         (address aggregator1, uint48 validAfter1, uint48 validUntil1) = parseValidationData(validationData1);
         (address aggregator1, uint48 validAfter1, uint48 validUntil1) = parseValidationData(validationData1);
         (address aggregator2, uint48 validAfter2, uint48 validUntil2) = parseValidationData(validationData2);
         (address aggregator2, uint48 validAfter2, uint48 validUntil2) = parseValidationData(validationData2);
 
 
-        bool success = aggregator1 == address(0) && aggregator2 == address(0);
+        bool success = aggregator1 == address(uint160(SIG_VALIDATION_SUCCESS)) &&
+            aggregator2 == address(uint160(SIG_VALIDATION_SUCCESS));
         uint48 validAfter = uint48(Math.max(validAfter1, validAfter2));
         uint48 validAfter = uint48(Math.max(validAfter1, validAfter2));
         uint48 validUntil = uint48(Math.min(validUntil1, validUntil2));
         uint48 validUntil = uint48(Math.min(validUntil1, validUntil2));
         return packValidationData(success, validAfter, validUntil);
         return packValidationData(success, validAfter, validUntil);
@@ -71,12 +72,7 @@ library ERC4337Utils {
         return (aggregator_, block.timestamp < validAfter || validUntil < block.timestamp);
         return (aggregator_, block.timestamp < validAfter || validUntil < block.timestamp);
     }
     }
 
 
-    /// @dev Computes the hash of a user operation with the current entrypoint and chainid.
-    function hash(PackedUserOperation calldata self) internal view returns (bytes32) {
-        return hash(self, address(this), block.chainid);
-    }
-
-    /// @dev Sames as {hash}, but with a custom entrypoint and chainid.
+    /// @dev Computes the hash of a user operation for a given entrypoint and chainid.
     function hash(
     function hash(
         PackedUserOperation calldata self,
         PackedUserOperation calldata self,
         address entrypoint,
         address entrypoint,
@@ -103,24 +99,34 @@ library ERC4337Utils {
         return result;
         return result;
     }
     }
 
 
+    /// @dev Returns `factory` from the {PackedUserOperation}, or address(0) if the initCode is empty or not properly formatted.
+    function factory(PackedUserOperation calldata self) internal pure returns (address) {
+        return self.initCode.length < 20 ? address(0) : address(bytes20(self.initCode[0:20]));
+    }
+
+    /// @dev Returns `factoryData` from the {PackedUserOperation}, or empty bytes if the initCode is empty or not properly formatted.
+    function factoryData(PackedUserOperation calldata self) internal pure returns (bytes calldata) {
+        return self.initCode.length < 20 ? _emptyCalldataBytes() : self.initCode[20:];
+    }
+
     /// @dev Returns `verificationGasLimit` from the {PackedUserOperation}.
     /// @dev Returns `verificationGasLimit` from the {PackedUserOperation}.
     function verificationGasLimit(PackedUserOperation calldata self) internal pure returns (uint256) {
     function verificationGasLimit(PackedUserOperation calldata self) internal pure returns (uint256) {
-        return uint128(self.accountGasLimits.extract_32_16(0x00));
+        return uint128(self.accountGasLimits.extract_32_16(0));
     }
     }
 
 
     /// @dev Returns `accountGasLimits` from the {PackedUserOperation}.
     /// @dev Returns `accountGasLimits` from the {PackedUserOperation}.
     function callGasLimit(PackedUserOperation calldata self) internal pure returns (uint256) {
     function callGasLimit(PackedUserOperation calldata self) internal pure returns (uint256) {
-        return uint128(self.accountGasLimits.extract_32_16(0x10));
+        return uint128(self.accountGasLimits.extract_32_16(16));
     }
     }
 
 
     /// @dev Returns the first section of `gasFees` from the {PackedUserOperation}.
     /// @dev Returns the first section of `gasFees` from the {PackedUserOperation}.
     function maxPriorityFeePerGas(PackedUserOperation calldata self) internal pure returns (uint256) {
     function maxPriorityFeePerGas(PackedUserOperation calldata self) internal pure returns (uint256) {
-        return uint128(self.gasFees.extract_32_16(0x00));
+        return uint128(self.gasFees.extract_32_16(0));
     }
     }
 
 
     /// @dev Returns the second section of `gasFees` from the {PackedUserOperation}.
     /// @dev Returns the second section of `gasFees` from the {PackedUserOperation}.
     function maxFeePerGas(PackedUserOperation calldata self) internal pure returns (uint256) {
     function maxFeePerGas(PackedUserOperation calldata self) internal pure returns (uint256) {
-        return uint128(self.gasFees.extract_32_16(0x10));
+        return uint128(self.gasFees.extract_32_16(16));
     }
     }
 
 
     /// @dev Returns the total gas price for the {PackedUserOperation} (ie. `maxFeePerGas` or `maxPriorityFeePerGas + basefee`).
     /// @dev Returns the total gas price for the {PackedUserOperation} (ie. `maxFeePerGas` or `maxPriorityFeePerGas + basefee`).
@@ -129,22 +135,35 @@ library ERC4337Utils {
             // Following values are "per gas"
             // Following values are "per gas"
             uint256 maxPriorityFee = maxPriorityFeePerGas(self);
             uint256 maxPriorityFee = maxPriorityFeePerGas(self);
             uint256 maxFee = maxFeePerGas(self);
             uint256 maxFee = maxFeePerGas(self);
-            return Math.ternary(maxFee == maxPriorityFee, maxFee, Math.min(maxFee, maxPriorityFee + block.basefee));
+            return Math.min(maxFee, maxPriorityFee + block.basefee);
         }
         }
     }
     }
 
 
     /// @dev Returns the first section of `paymasterAndData` from the {PackedUserOperation}.
     /// @dev Returns the first section of `paymasterAndData` from the {PackedUserOperation}.
     function paymaster(PackedUserOperation calldata self) internal pure returns (address) {
     function paymaster(PackedUserOperation calldata self) internal pure returns (address) {
-        return address(bytes20(self.paymasterAndData[0:20]));
+        return self.paymasterAndData.length < 52 ? address(0) : address(bytes20(self.paymasterAndData[0:20]));
     }
     }
 
 
     /// @dev Returns the second section of `paymasterAndData` from the {PackedUserOperation}.
     /// @dev Returns the second section of `paymasterAndData` from the {PackedUserOperation}.
     function paymasterVerificationGasLimit(PackedUserOperation calldata self) internal pure returns (uint256) {
     function paymasterVerificationGasLimit(PackedUserOperation calldata self) internal pure returns (uint256) {
-        return uint128(bytes16(self.paymasterAndData[20:36]));
+        return self.paymasterAndData.length < 52 ? 0 : uint128(bytes16(self.paymasterAndData[20:36]));
     }
     }
 
 
     /// @dev Returns the third section of `paymasterAndData` from the {PackedUserOperation}.
     /// @dev Returns the third section of `paymasterAndData` from the {PackedUserOperation}.
     function paymasterPostOpGasLimit(PackedUserOperation calldata self) internal pure returns (uint256) {
     function paymasterPostOpGasLimit(PackedUserOperation calldata self) internal pure returns (uint256) {
-        return uint128(bytes16(self.paymasterAndData[36:52]));
+        return self.paymasterAndData.length < 52 ? 0 : uint128(bytes16(self.paymasterAndData[36:52]));
+    }
+
+    /// @dev Returns the forth section of `paymasterAndData` from the {PackedUserOperation}.
+    function paymasterData(PackedUserOperation calldata self) internal pure returns (bytes calldata) {
+        return self.paymasterAndData.length < 52 ? _emptyCalldataBytes() : self.paymasterAndData[52:];
+    }
+
+    // slither-disable-next-line write-after-write
+    function _emptyCalldataBytes() private pure returns (bytes calldata result) {
+        assembly ("memory-safe") {
+            result.offset := 0
+            result.length := 0
+        }
     }
     }
 }
 }

+ 12 - 8
contracts/account/utils/draft-ERC7579Utils.sol

@@ -22,22 +22,26 @@ library ERC7579Utils {
     using Packing for *;
     using Packing for *;
 
 
     /// @dev A single `call` execution.
     /// @dev A single `call` execution.
-    CallType constant CALLTYPE_SINGLE = CallType.wrap(0x00);
+    CallType internal constant CALLTYPE_SINGLE = CallType.wrap(0x00);
 
 
     /// @dev A batch of `call` executions.
     /// @dev A batch of `call` executions.
-    CallType constant CALLTYPE_BATCH = CallType.wrap(0x01);
+    CallType internal constant CALLTYPE_BATCH = CallType.wrap(0x01);
 
 
     /// @dev A `delegatecall` execution.
     /// @dev A `delegatecall` execution.
-    CallType constant CALLTYPE_DELEGATECALL = CallType.wrap(0xFF);
+    CallType internal constant CALLTYPE_DELEGATECALL = CallType.wrap(0xFF);
 
 
     /// @dev Default execution type that reverts on failure.
     /// @dev Default execution type that reverts on failure.
-    ExecType constant EXECTYPE_DEFAULT = ExecType.wrap(0x00);
+    ExecType internal constant EXECTYPE_DEFAULT = ExecType.wrap(0x00);
 
 
     /// @dev Execution type that does not revert on failure.
     /// @dev Execution type that does not revert on failure.
-    ExecType constant EXECTYPE_TRY = ExecType.wrap(0x01);
-
-    /// @dev Emits when an {EXECTYPE_TRY} execution fails.
-    event ERC7579TryExecuteFail(uint256 batchExecutionIndex, bytes result);
+    ExecType internal constant EXECTYPE_TRY = ExecType.wrap(0x01);
+
+    /**
+     * @dev Emits when an {EXECTYPE_TRY} execution fails.
+     * @param batchExecutionIndex The index of the failed call in the execution batch.
+     * @param returndata The returned data from the failed call.
+     */
+    event ERC7579TryExecuteFail(uint256 batchExecutionIndex, bytes returndata);
 
 
     /// @dev The provided {CallType} is not supported.
     /// @dev The provided {CallType} is not supported.
     error ERC7579UnsupportedCallType(CallType callType);
     error ERC7579UnsupportedCallType(CallType callType);

+ 5 - 0
contracts/finance/VestingWallet.sol

@@ -26,6 +26,11 @@ import {Ownable} from "../access/Ownable.sol";
  *
  *
  * NOTE: When using this contract with any token whose balance is adjusted automatically (i.e. a rebase token), make
  * NOTE: When using this contract with any token whose balance is adjusted automatically (i.e. a rebase token), make
  * sure to account the supply/balance adjustment in the vesting schedule to ensure the vested amount is as intended.
  * sure to account the supply/balance adjustment in the vesting schedule to ensure the vested amount is as intended.
+ *
+ * NOTE: Chains with support for native ERC20s may allow the vesting wallet to withdraw the underlying asset as both an
+ * ERC20 and as native currency. For example, if chain C supports token A and the wallet gets deposited 100 A, then
+ * at 50% of the vesting period, the beneficiary can withdraw 50 A as ERC20 and 25 A as native currency (totaling 75 A).
+ * Consider disabling one of the withdrawal methods.
  */
  */
 contract VestingWallet is Context, Ownable {
 contract VestingWallet is Context, Ownable {
     event EtherReleased(uint256 amount);
     event EtherReleased(uint256 amount);

+ 22 - 10
contracts/governance/extensions/GovernorCountingOverridable.sol

@@ -8,8 +8,8 @@ import {VotesExtended} from "../utils/VotesExtended.sol";
 import {GovernorVotes} from "./GovernorVotes.sol";
 import {GovernorVotes} from "./GovernorVotes.sol";
 
 
 /**
 /**
- * @dev Extension of {Governor} which enables delegatees to override the vote of their delegates. This module requires a
- * token token that inherits `VotesExtended`.
+ * @dev Extension of {Governor} which enables delegators to override the vote of their delegates. This module requires a
+ * token that inherits {VotesExtended}.
  */
  */
 abstract contract GovernorCountingOverridable is GovernorVotes {
 abstract contract GovernorCountingOverridable is GovernorVotes {
     bytes32 public constant OVERRIDE_BALLOT_TYPEHASH =
     bytes32 public constant OVERRIDE_BALLOT_TYPEHASH =
@@ -35,7 +35,10 @@ abstract contract GovernorCountingOverridable is GovernorVotes {
         mapping(address voter => VoteReceipt) voteReceipt;
         mapping(address voter => VoteReceipt) voteReceipt;
     }
     }
 
 
-    event VoteReduced(address indexed voter, uint256 proposalId, uint8 support, uint256 weight);
+    /// @dev The votes casted by `delegate` were reduced by `weight` after an override vote was casted by the original token holder
+    event VoteReduced(address indexed delegate, uint256 proposalId, uint8 support, uint256 weight);
+
+    /// @dev A delegated vote on `proposalId` was overridden by `weight`
     event OverrideVoteCast(address indexed voter, uint256 proposalId, uint8 support, uint256 weight, string reason);
     event OverrideVoteCast(address indexed voter, uint256 proposalId, uint8 support, uint256 weight, string reason);
 
 
     error GovernorAlreadyOverridenVote(address account);
     error GovernorAlreadyOverridenVote(address account);
@@ -52,6 +55,11 @@ abstract contract GovernorCountingOverridable is GovernorVotes {
 
 
     /**
     /**
      * @dev See {IGovernor-hasVoted}.
      * @dev See {IGovernor-hasVoted}.
+     *
+     * NOTE: Calling {castVote} (or similar) casts a vote using the voting power that is delegated to the voter.
+     * Conversely, calling {castOverrideVote} (or similar) uses the voting power of the account itself, from its asset
+     * balances. Casting an "override vote" does not count as voting and won't be reflected by this getter. Consider
+     * using {hasVotedOverride} to check if an account has casted an "override vote" for a given proposal id.
      */
      */
     function hasVoted(uint256 proposalId, address account) public view virtual override returns (bool) {
     function hasVoted(uint256 proposalId, address account) public view virtual override returns (bool) {
         return _proposalVotes[proposalId].voteReceipt[account].casted != 0;
         return _proposalVotes[proposalId].voteReceipt[account].casted != 0;
@@ -120,7 +128,11 @@ abstract contract GovernorCountingOverridable is GovernorVotes {
         return totalWeight;
         return totalWeight;
     }
     }
 
 
-    /// @dev Variant of {Governor-_countVote} that deals with vote overrides.
+    /**
+     * @dev Variant of {Governor-_countVote} that deals with vote overrides.
+     *
+     * NOTE: See {hasVoted} for more details about the difference between {castVote} and {castOverrideVote}.
+     */
     function _countOverride(uint256 proposalId, address account, uint8 support) internal virtual returns (uint256) {
     function _countOverride(uint256 proposalId, address account, uint8 support) internal virtual returns (uint256) {
         ProposalVote storage proposalVote = _proposalVotes[proposalId];
         ProposalVote storage proposalVote = _proposalVotes[proposalId];
 
 
@@ -132,9 +144,9 @@ abstract contract GovernorCountingOverridable is GovernorVotes {
             revert GovernorAlreadyOverridenVote(account);
             revert GovernorAlreadyOverridenVote(account);
         }
         }
 
 
-        uint256 proposalSnapshot = proposalSnapshot(proposalId);
-        uint256 overridenWeight = VotesExtended(address(token())).getPastBalanceOf(account, proposalSnapshot);
-        address delegate = VotesExtended(address(token())).getPastDelegate(account, proposalSnapshot);
+        uint256 snapshot = proposalSnapshot(proposalId);
+        uint256 overridenWeight = VotesExtended(address(token())).getPastBalanceOf(account, snapshot);
+        address delegate = VotesExtended(address(token())).getPastDelegate(account, snapshot);
         uint8 delegateCasted = proposalVote.voteReceipt[delegate].casted;
         uint8 delegateCasted = proposalVote.voteReceipt[delegate].casted;
 
 
         proposalVote.voteReceipt[account].hasOverriden = true;
         proposalVote.voteReceipt[account].hasOverriden = true;
@@ -150,7 +162,7 @@ abstract contract GovernorCountingOverridable is GovernorVotes {
         return overridenWeight;
         return overridenWeight;
     }
     }
 
 
-    /// @dev variant of {Governor-_castVote} that deals with vote overrides.
+    /// @dev Variant of {Governor-_castVote} that deals with vote overrides. Returns the overridden weight.
     function _castOverride(
     function _castOverride(
         uint256 proposalId,
         uint256 proposalId,
         address account,
         address account,
@@ -168,7 +180,7 @@ abstract contract GovernorCountingOverridable is GovernorVotes {
         return overridenWeight;
         return overridenWeight;
     }
     }
 
 
-    /// @dev Public function for casting an override vote
+    /// @dev Public function for casting an override vote. Returns the overridden weight.
     function castOverrideVote(
     function castOverrideVote(
         uint256 proposalId,
         uint256 proposalId,
         uint8 support,
         uint8 support,
@@ -178,7 +190,7 @@ abstract contract GovernorCountingOverridable is GovernorVotes {
         return _castOverride(proposalId, voter, support, reason);
         return _castOverride(proposalId, voter, support, reason);
     }
     }
 
 
-    /// @dev Public function for casting an override vote using a voter's signature
+    /// @dev Public function for casting an override vote using a voter's signature. Returns the overridden weight.
     function castOverrideVoteBySig(
     function castOverrideVoteBySig(
         uint256 proposalId,
         uint256 proposalId,
         uint8 support,
         uint8 support,

+ 11 - 10
contracts/governance/utils/Votes.sol

@@ -71,6 +71,15 @@ abstract contract Votes is Context, EIP712, Nonces, IERC5805 {
         return "mode=blocknumber&from=default";
         return "mode=blocknumber&from=default";
     }
     }
 
 
+    /**
+     * @dev Validate that a timepoint is in the past, and return it as a uint48.
+     */
+    function _validateTimepoint(uint256 timepoint) internal view returns (uint48) {
+        uint48 currentTimepoint = clock();
+        if (timepoint >= currentTimepoint) revert ERC5805FutureLookup(timepoint, currentTimepoint);
+        return SafeCast.toUint48(timepoint);
+    }
+
     /**
     /**
      * @dev Returns the current amount of votes that `account` has.
      * @dev Returns the current amount of votes that `account` has.
      */
      */
@@ -87,11 +96,7 @@ abstract contract Votes is Context, EIP712, Nonces, IERC5805 {
      * - `timepoint` must be in the past. If operating using block numbers, the block must be already mined.
      * - `timepoint` must be in the past. If operating using block numbers, the block must be already mined.
      */
      */
     function getPastVotes(address account, uint256 timepoint) public view virtual returns (uint256) {
     function getPastVotes(address account, uint256 timepoint) public view virtual returns (uint256) {
-        uint48 currentTimepoint = clock();
-        if (timepoint >= currentTimepoint) {
-            revert ERC5805FutureLookup(timepoint, currentTimepoint);
-        }
-        return _delegateCheckpoints[account].upperLookupRecent(SafeCast.toUint48(timepoint));
+        return _delegateCheckpoints[account].upperLookupRecent(_validateTimepoint(timepoint));
     }
     }
 
 
     /**
     /**
@@ -107,11 +112,7 @@ abstract contract Votes is Context, EIP712, Nonces, IERC5805 {
      * - `timepoint` must be in the past. If operating using block numbers, the block must be already mined.
      * - `timepoint` must be in the past. If operating using block numbers, the block must be already mined.
      */
      */
     function getPastTotalSupply(uint256 timepoint) public view virtual returns (uint256) {
     function getPastTotalSupply(uint256 timepoint) public view virtual returns (uint256) {
-        uint48 currentTimepoint = clock();
-        if (timepoint >= currentTimepoint) {
-            revert ERC5805FutureLookup(timepoint, currentTimepoint);
-        }
-        return _totalCheckpoints.upperLookupRecent(SafeCast.toUint48(timepoint));
+        return _totalCheckpoints.upperLookupRecent(_validateTimepoint(timepoint));
     }
     }
 
 
     /**
     /**

+ 30 - 17
contracts/governance/utils/VotesExtended.sol

@@ -6,15 +6,36 @@ import {Votes} from "./Votes.sol";
 import {SafeCast} from "../../utils/math/SafeCast.sol";
 import {SafeCast} from "../../utils/math/SafeCast.sol";
 
 
 /**
 /**
- * @dev Extension of {Votes} that adds exposes checkpoints for delegations and balances.
+ * @dev Extension of {Votes} that adds checkpoints for delegations and balances.
+ *
+ * WARNING: While this contract extends {Votes}, valid uses of {Votes} may not be compatible with
+ * {VotesExtended} without additional considerations. This implementation of {_transferVotingUnits} must
+ * run AFTER the voting weight movement is registered, such that it is reflected on {_getVotingUnits}.
+ *
+ * Said differently, {VotesExtended} MUST be integrated in a way that calls {_transferVotingUnits} AFTER the
+ * asset transfer is registered and balances are updated:
+ *
+ * ```solidity
+ * contract VotingToken is Token, VotesExtended {
+ *   function transfer(address from, address to, uint256 tokenId) public override {
+ *     super.transfer(from, to, tokenId); // <- Perform the transfer first ...
+ *     _transferVotingUnits(from, to, 1); // <- ... then call _transferVotingUnits.
+ *   }
+ *
+ *   function _getVotingUnits(address account) internal view override returns (uint256) {
+ *      return balanceOf(account);
+ *   }
+ * }
+ * ```
+ *
+ * {ERC20Votes} and {ERC721Votes} follow this pattern and are thus safe to use with {VotesExtended}.
  */
  */
 abstract contract VotesExtended is Votes {
 abstract contract VotesExtended is Votes {
-    using SafeCast for uint256;
     using Checkpoints for Checkpoints.Trace160;
     using Checkpoints for Checkpoints.Trace160;
     using Checkpoints for Checkpoints.Trace208;
     using Checkpoints for Checkpoints.Trace208;
 
 
-    mapping(address delegatee => Checkpoints.Trace160) private _delegateCheckpoints;
-    mapping(address account => Checkpoints.Trace208) private _balanceOfCheckpoints;
+    mapping(address delegator => Checkpoints.Trace160) private _userDelegationCheckpoints;
+    mapping(address account => Checkpoints.Trace208) private _userVotingUnitsCheckpoints;
 
 
     /**
     /**
      * @dev Returns the delegate of an `account` at a specific moment in the past. If the `clock()` is
      * @dev Returns the delegate of an `account` at a specific moment in the past. If the `clock()` is
@@ -25,11 +46,7 @@ abstract contract VotesExtended is Votes {
      * - `timepoint` must be in the past. If operating using block numbers, the block must be already mined.
      * - `timepoint` must be in the past. If operating using block numbers, the block must be already mined.
      */
      */
     function getPastDelegate(address account, uint256 timepoint) public view virtual returns (address) {
     function getPastDelegate(address account, uint256 timepoint) public view virtual returns (address) {
-        uint48 currentTimepoint = clock();
-        if (timepoint >= currentTimepoint) {
-            revert ERC5805FutureLookup(timepoint, currentTimepoint);
-        }
-        return address(_delegateCheckpoints[account].upperLookupRecent(timepoint.toUint48()));
+        return address(_userDelegationCheckpoints[account].upperLookupRecent(_validateTimepoint(timepoint)));
     }
     }
 
 
     /**
     /**
@@ -41,18 +58,14 @@ abstract contract VotesExtended is Votes {
      * - `timepoint` must be in the past. If operating using block numbers, the block must be already mined.
      * - `timepoint` must be in the past. If operating using block numbers, the block must be already mined.
      */
      */
     function getPastBalanceOf(address account, uint256 timepoint) public view virtual returns (uint256) {
     function getPastBalanceOf(address account, uint256 timepoint) public view virtual returns (uint256) {
-        uint48 currentTimepoint = clock();
-        if (timepoint >= currentTimepoint) {
-            revert ERC5805FutureLookup(timepoint, currentTimepoint);
-        }
-        return _balanceOfCheckpoints[account].upperLookupRecent(timepoint.toUint48());
+        return _userVotingUnitsCheckpoints[account].upperLookupRecent(_validateTimepoint(timepoint));
     }
     }
 
 
     /// @inheritdoc Votes
     /// @inheritdoc Votes
     function _delegate(address account, address delegatee) internal virtual override {
     function _delegate(address account, address delegatee) internal virtual override {
         super._delegate(account, delegatee);
         super._delegate(account, delegatee);
 
 
-        _delegateCheckpoints[account].push(clock(), uint160(delegatee));
+        _userDelegationCheckpoints[account].push(clock(), uint160(delegatee));
     }
     }
 
 
     /// @inheritdoc Votes
     /// @inheritdoc Votes
@@ -60,10 +73,10 @@ abstract contract VotesExtended is Votes {
         super._transferVotingUnits(from, to, amount);
         super._transferVotingUnits(from, to, amount);
         if (from != to) {
         if (from != to) {
             if (from != address(0)) {
             if (from != address(0)) {
-                _balanceOfCheckpoints[from].push(clock(), _getVotingUnits(from).toUint208());
+                _userVotingUnitsCheckpoints[from].push(clock(), SafeCast.toUint208(_getVotingUnits(from)));
             }
             }
             if (to != address(0)) {
             if (to != address(0)) {
-                _balanceOfCheckpoints[to].push(clock(), _getVotingUnits(to).toUint208());
+                _userVotingUnitsCheckpoints[to].push(clock(), SafeCast.toUint208(_getVotingUnits(to)));
             }
             }
         }
         }
     }
     }

+ 44 - 7
contracts/interfaces/draft-IERC4337.sol

@@ -11,7 +11,7 @@ pragma solidity ^0.8.20;
  * - `callData` (`bytes`): The data to pass to the sender during the main execution call
  * - `callData` (`bytes`): The data to pass to the sender during the main execution call
  * - `callGasLimit` (`uint256`): The amount of gas to allocate the main execution call
  * - `callGasLimit` (`uint256`): The amount of gas to allocate the main execution call
  * - `verificationGasLimit` (`uint256`): The amount of gas to allocate for the verification step
  * - `verificationGasLimit` (`uint256`): The amount of gas to allocate for the verification step
- * - `preVerificationGas` (`uint256`): Extra gas to pay the bunder
+ * - `preVerificationGas` (`uint256`): Extra gas to pay the bundler
  * - `maxFeePerGas` (`uint256`): Maximum fee per gas (similar to EIP-1559 max_fee_per_gas)
  * - `maxFeePerGas` (`uint256`): Maximum fee per gas (similar to EIP-1559 max_fee_per_gas)
  * - `maxPriorityFeePerGas` (`uint256`): Maximum priority fee per gas (similar to EIP-1559 max_priority_fee_per_gas)
  * - `maxPriorityFeePerGas` (`uint256`): Maximum priority fee per gas (similar to EIP-1559 max_priority_fee_per_gas)
  * - `paymaster` (`address`): Address of paymaster contract, (or empty, if account pays for itself)
  * - `paymaster` (`address`): Address of paymaster contract, (or empty, if account pays for itself)
@@ -27,7 +27,7 @@ pragma solidity ^0.8.20;
  * - `callData` (`bytes`)
  * - `callData` (`bytes`)
  * - `accountGasLimits` (`bytes32`): concatenation of verificationGas (16 bytes) and callGas (16 bytes)
  * - `accountGasLimits` (`bytes32`): concatenation of verificationGas (16 bytes) and callGas (16 bytes)
  * - `preVerificationGas` (`uint256`)
  * - `preVerificationGas` (`uint256`)
- * - `gasFees` (`bytes32`): concatenation of maxPriorityFee (16 bytes) and maxFeePerGas (16 bytes)
+ * - `gasFees` (`bytes32`): concatenation of maxPriorityFeePerGas (16 bytes) and maxFeePerGas (16 bytes)
  * - `paymasterAndData` (`bytes`): concatenation of paymaster fields (or empty)
  * - `paymasterAndData` (`bytes`): concatenation of paymaster fields (or empty)
  * - `signature` (`bytes`)
  * - `signature` (`bytes`)
  */
  */
@@ -38,17 +38,25 @@ struct PackedUserOperation {
     bytes callData;
     bytes callData;
     bytes32 accountGasLimits; // `abi.encodePacked(verificationGasLimit, callGasLimit)` 16 bytes each
     bytes32 accountGasLimits; // `abi.encodePacked(verificationGasLimit, callGasLimit)` 16 bytes each
     uint256 preVerificationGas;
     uint256 preVerificationGas;
-    bytes32 gasFees; // `abi.encodePacked(maxPriorityFee, maxFeePerGas)` 16 bytes each
-    bytes paymasterAndData; // `abi.encodePacked(paymaster, paymasterVerificationGasLimit, paymasterPostOpGasLimit, paymasterData)`
+    bytes32 gasFees; // `abi.encodePacked(maxPriorityFeePerGas, maxFeePerGas)` 16 bytes each
+    bytes paymasterAndData; // `abi.encodePacked(paymaster, paymasterVerificationGasLimit, paymasterPostOpGasLimit, paymasterData)` (20 bytes, 16 bytes, 16 bytes, dynamic)
     bytes signature;
     bytes signature;
 }
 }
 
 
 /**
 /**
  * @dev Aggregates and validates multiple signatures for a batch of user operations.
  * @dev Aggregates and validates multiple signatures for a batch of user operations.
+ *
+ * A contract could implement this interface with custom validation schemes that allow signature aggregation,
+ * enabling significant optimizations and gas savings for execution and transaction data cost.
+ *
+ * Bundlers and clients whitelist supported aggregators.
+ *
+ * See https://eips.ethereum.org/EIPS/eip-7766[ERC-7766]
  */
  */
 interface IAggregator {
 interface IAggregator {
     /**
     /**
      * @dev Validates the signature for a user operation.
      * @dev Validates the signature for a user operation.
+     * Returns an alternative signature that should be used during bundling.
      */
      */
     function validateUserOpSignature(
     function validateUserOpSignature(
         PackedUserOperation calldata userOp
         PackedUserOperation calldata userOp
@@ -73,6 +81,12 @@ interface IAggregator {
 
 
 /**
 /**
  * @dev Handle nonce management for accounts.
  * @dev Handle nonce management for accounts.
+ *
+ * Nonces are used in accounts as a replay protection mechanism and to ensure the order of user operations.
+ * To avoid limiting the number of operations an account can perform, the interface allows using parallel
+ * nonces by using a `key` parameter.
+ *
+ * See https://eips.ethereum.org/EIPS/eip-4337#semi-abstracted-nonce-support[ERC-4337 semi-abstracted nonce support].
  */
  */
 interface IEntryPointNonces {
 interface IEntryPointNonces {
     /**
     /**
@@ -84,7 +98,11 @@ interface IEntryPointNonces {
 }
 }
 
 
 /**
 /**
- * @dev Handle stake management for accounts.
+ * @dev Handle stake management for entities (i.e. accounts, paymasters, factories).
+ *
+ * The EntryPoint must implement the following API to let entities like paymasters have a stake,
+ * and thus have more flexibility in their storage access
+ * (see https://eips.ethereum.org/EIPS/eip-4337#reputation-scoring-and-throttlingbanning-for-global-entities[reputation, throttling and banning.])
  */
  */
 interface IEntryPointStake {
 interface IEntryPointStake {
     /**
     /**
@@ -120,6 +138,8 @@ interface IEntryPointStake {
 
 
 /**
 /**
  * @dev Entry point for user operations.
  * @dev Entry point for user operations.
+ *
+ * User operations are validated and executed by this contract.
  */
  */
 interface IEntryPoint is IEntryPointNonces, IEntryPointStake {
 interface IEntryPoint is IEntryPointNonces, IEntryPointStake {
     /**
     /**
@@ -143,11 +163,13 @@ interface IEntryPoint is IEntryPointNonces, IEntryPointStake {
 
 
     /**
     /**
      * @dev Executes a batch of user operations.
      * @dev Executes a batch of user operations.
+     * @param beneficiary Address to which gas is refunded up completing the execution.
      */
      */
     function handleOps(PackedUserOperation[] calldata ops, address payable beneficiary) external;
     function handleOps(PackedUserOperation[] calldata ops, address payable beneficiary) external;
 
 
     /**
     /**
      * @dev Executes a batch of aggregated user operations per aggregator.
      * @dev Executes a batch of aggregated user operations per aggregator.
+     * @param beneficiary Address to which gas is refunded up completing the execution.
      */
      */
     function handleAggregatedOps(
     function handleAggregatedOps(
         UserOpsPerAggregator[] calldata opsPerAggregator,
         UserOpsPerAggregator[] calldata opsPerAggregator,
@@ -156,11 +178,23 @@ interface IEntryPoint is IEntryPointNonces, IEntryPointStake {
 }
 }
 
 
 /**
 /**
- * @dev Base interface for an account.
+ * @dev Base interface for an ERC-4337 account.
  */
  */
 interface IAccount {
 interface IAccount {
     /**
     /**
      * @dev Validates a user operation.
      * @dev Validates a user operation.
+     *
+     * * MUST validate the caller is a trusted EntryPoint
+     * * MUST validate that the signature is a valid signature of the userOpHash, and SHOULD
+     *   return SIG_VALIDATION_FAILED (and not revert) on signature mismatch. Any other error MUST revert.
+     * * MUST pay the entryPoint (caller) at least the “missingAccountFunds” (which might
+     *   be zero, in case the current account’s deposit is high enough)
+     *
+     * Returns an encoded packed validation data that is composed of the following elements:
+     *
+     * - `authorizer` (`address`): 0 for success, 1 for failure, otherwise the address of an authorizer contract
+     * - `validUntil` (`uint48`): The UserOp is valid only up to this time. Zero for “infinite”.
+     * - `validAfter` (`uint48`): The UserOp is valid only after this time.
      */
      */
     function validateUserOp(
     function validateUserOp(
         PackedUserOperation calldata userOp,
         PackedUserOperation calldata userOp,
@@ -193,7 +227,8 @@ interface IPaymaster {
     }
     }
 
 
     /**
     /**
-     * @dev Validates whether the paymaster is willing to pay for the user operation.
+     * @dev Validates whether the paymaster is willing to pay for the user operation. See
+     * {IAccount-validateUserOp} for additional information on the return value.
      *
      *
      * NOTE: Bundlers will reject this method if it modifies the state, unless it's whitelisted.
      * NOTE: Bundlers will reject this method if it modifies the state, unless it's whitelisted.
      */
      */
@@ -205,6 +240,8 @@ interface IPaymaster {
 
 
     /**
     /**
      * @dev Verifies the sender is the entrypoint.
      * @dev Verifies the sender is the entrypoint.
+     * @param actualGasCost the actual amount paid (by account or paymaster) for this UserOperation
+     * @param actualUserOpFeePerGas total gas used by this UserOperation (including preVerification, creation, validation and execution)
      */
      */
     function postOp(
     function postOp(
         PostOpMode mode,
         PostOpMode mode,

+ 29 - 0
contracts/interfaces/draft-IERC7579.sol

@@ -10,6 +10,7 @@ uint256 constant MODULE_TYPE_EXECUTOR = 2;
 uint256 constant MODULE_TYPE_FALLBACK = 3;
 uint256 constant MODULE_TYPE_FALLBACK = 3;
 uint256 constant MODULE_TYPE_HOOK = 4;
 uint256 constant MODULE_TYPE_HOOK = 4;
 
 
+/// @dev Minimal configuration interface for ERC-7579 modules
 interface IERC7579Module {
 interface IERC7579Module {
     /**
     /**
      * @dev This function is called by the smart account during installation of the module
      * @dev This function is called by the smart account during installation of the module
@@ -36,6 +37,11 @@ interface IERC7579Module {
     function isModuleType(uint256 moduleTypeId) external view returns (bool);
     function isModuleType(uint256 moduleTypeId) external view returns (bool);
 }
 }
 
 
+/**
+ * @dev ERC-7579 Validation module (type 1).
+ *
+ * A module that implements logic to validate user operations and signatures.
+ */
 interface IERC7579Validator is IERC7579Module {
 interface IERC7579Validator is IERC7579Module {
     /**
     /**
      * @dev Validates a UserOperation
      * @dev Validates a UserOperation
@@ -44,6 +50,7 @@ interface IERC7579Validator is IERC7579Module {
      *
      *
      * MUST validate that the signature is a valid signature of the userOpHash
      * MUST validate that the signature is a valid signature of the userOpHash
      * SHOULD return ERC-4337's SIG_VALIDATION_FAILED (and not revert) on signature mismatch
      * SHOULD return ERC-4337's SIG_VALIDATION_FAILED (and not revert) on signature mismatch
+     * See {IAccount-validateUserOp} for additional information on the return value
      */
      */
     function validateUserOp(PackedUserOperation calldata userOp, bytes32 userOpHash) external returns (uint256);
     function validateUserOp(PackedUserOperation calldata userOp, bytes32 userOpHash) external returns (uint256);
 
 
@@ -63,6 +70,12 @@ interface IERC7579Validator is IERC7579Module {
     ) external view returns (bytes4);
     ) external view returns (bytes4);
 }
 }
 
 
+/**
+ * @dev ERC-7579 Hooks module (type 4).
+ *
+ * A module that implements logic to execute before and after the account executes a user operation,
+ * either individually or batched.
+ */
 interface IERC7579Hook is IERC7579Module {
 interface IERC7579Hook is IERC7579Module {
     /**
     /**
      * @dev Called by the smart account before execution
      * @dev Called by the smart account before execution
@@ -93,6 +106,11 @@ struct Execution {
     bytes callData;
     bytes callData;
 }
 }
 
 
+/**
+ * @dev ERC-7579 Execution.
+ *
+ * Accounts should implement this interface so that the Entrypoint and ERC-7579 modules can execute operations.
+ */
 interface IERC7579Execution {
 interface IERC7579Execution {
     /**
     /**
      * @dev Executes a transaction on behalf of the account.
      * @dev Executes a transaction on behalf of the account.
@@ -109,6 +127,7 @@ interface IERC7579Execution {
      *         This function is intended to be called by Executor Modules
      *         This function is intended to be called by Executor Modules
      * @param mode The encoded execution mode of the transaction. See ModeLib.sol for details
      * @param mode The encoded execution mode of the transaction. See ModeLib.sol for details
      * @param executionCalldata The encoded execution call data
      * @param executionCalldata The encoded execution call data
+     * @return returnData An array with the returned data of each executed subcall
      *
      *
      * MUST ensure adequate authorization control: i.e. onlyExecutorModule
      * MUST ensure adequate authorization control: i.e. onlyExecutorModule
      * If a mode is requested that is not supported by the Account, it MUST revert
      * If a mode is requested that is not supported by the Account, it MUST revert
@@ -119,6 +138,11 @@ interface IERC7579Execution {
     ) external returns (bytes[] memory returnData);
     ) external returns (bytes[] memory returnData);
 }
 }
 
 
+/**
+ * @dev ERC-7579 Account Config.
+ *
+ * Accounts should implement this interface to expose information that identifies the account, supported modules and capabilities.
+ */
 interface IERC7579AccountConfig {
 interface IERC7579AccountConfig {
     /**
     /**
      * @dev Returns the account id of the smart account
      * @dev Returns the account id of the smart account
@@ -148,6 +172,11 @@ interface IERC7579AccountConfig {
     function supportsModule(uint256 moduleTypeId) external view returns (bool);
     function supportsModule(uint256 moduleTypeId) external view returns (bool);
 }
 }
 
 
+/**
+ * @dev ERC-7579 Module Config.
+ *
+ * Accounts should implement this interface to allow installing and uninstalling modules.
+ */
 interface IERC7579ModuleConfig {
 interface IERC7579ModuleConfig {
     event ModuleInstalled(uint256 moduleTypeId, address module);
     event ModuleInstalled(uint256 moduleTypeId, address module);
     event ModuleUninstalled(uint256 moduleTypeId, address module);
     event ModuleUninstalled(uint256 moduleTypeId, address module);

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


+ 7 - 7
contracts/proxy/Clones.sol

@@ -57,7 +57,7 @@ library Clones {
      * @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`.
      * @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`.
      *
      *
      * This function uses the create2 opcode and a `salt` to deterministically deploy
      * This function uses the create2 opcode and a `salt` to deterministically deploy
-     * the clone. Using the same `implementation` and `salt` multiple time will revert, since
+     * the clone. Using the same `implementation` and `salt` multiple times will revert, since
      * the clones cannot be deployed twice at the same address.
      * the clones cannot be deployed twice at the same address.
      */
      */
     function cloneDeterministic(address implementation, bytes32 salt) internal returns (address instance) {
     function cloneDeterministic(address implementation, bytes32 salt) internal returns (address instance) {
@@ -163,8 +163,8 @@ library Clones {
      * access the arguments within the implementation, use {fetchCloneArgs}.
      * access the arguments within the implementation, use {fetchCloneArgs}.
      *
      *
      * This function uses the create2 opcode and a `salt` to deterministically deploy the clone. Using the same
      * This function uses the create2 opcode and a `salt` to deterministically deploy the clone. Using the same
-     * `implementation` and `salt` multiple time will revert, since the clones cannot be deployed twice at the same
-     * address.
+     * `implementation`, `args` and `salt` multiple times will revert, since the clones cannot be deployed twice
+     * at the same address.
      */
      */
     function cloneDeterministicWithImmutableArgs(
     function cloneDeterministicWithImmutableArgs(
         address implementation,
         address implementation,
@@ -227,9 +227,9 @@ library Clones {
      *   function should only be used to check addresses that are known to be clones.
      *   function should only be used to check addresses that are known to be clones.
      */
      */
     function fetchCloneArgs(address instance) internal view returns (bytes memory) {
     function fetchCloneArgs(address instance) internal view returns (bytes memory) {
-        bytes memory result = new bytes(instance.code.length - 0x2d); // revert if length is too short
+        bytes memory result = new bytes(instance.code.length - 45); // revert if length is too short
         assembly ("memory-safe") {
         assembly ("memory-safe") {
-            extcodecopy(instance, add(result, 0x20), 0x2d, mload(result))
+            extcodecopy(instance, add(result, 32), 45, mload(result))
         }
         }
         return result;
         return result;
     }
     }
@@ -248,11 +248,11 @@ library Clones {
         address implementation,
         address implementation,
         bytes memory args
         bytes memory args
     ) private pure returns (bytes memory) {
     ) private pure returns (bytes memory) {
-        if (args.length > 0x5fd3) revert CloneArgumentsTooLong();
+        if (args.length > 24531) revert CloneArgumentsTooLong();
         return
         return
             abi.encodePacked(
             abi.encodePacked(
                 hex"61",
                 hex"61",
-                uint16(args.length + 0x2d),
+                uint16(args.length + 45),
                 hex"3d81600a3d39f3363d3d373d3d3d363d73",
                 hex"3d81600a3d39f3363d3d373d3d3d363d73",
                 implementation,
                 implementation,
                 hex"5af43d82803e903d91602b57fd5bf3",
                 hex"5af43d82803e903d91602b57fd5bf3",

+ 5 - 2
contracts/token/ERC20/extensions/ERC1363.sol

@@ -48,7 +48,8 @@ abstract contract ERC1363 is ERC20, ERC165, IERC1363 {
 
 
     /**
     /**
      * @dev Moves a `value` amount of tokens from the caller's account to `to`
      * @dev Moves a `value` amount of tokens from the caller's account to `to`
-     * and then calls {IERC1363Receiver-onTransferReceived} on `to`.
+     * and then calls {IERC1363Receiver-onTransferReceived} on `to`. Returns a flag that indicates
+     * if the call succeeded.
      *
      *
      * Requirements:
      * Requirements:
      *
      *
@@ -75,7 +76,8 @@ abstract contract ERC1363 is ERC20, ERC165, IERC1363 {
 
 
     /**
     /**
      * @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism
      * @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism
-     * and then calls {IERC1363Receiver-onTransferReceived} on `to`.
+     * and then calls {IERC1363Receiver-onTransferReceived} on `to`. Returns a flag that indicates
+     * if the call succeeded.
      *
      *
      * Requirements:
      * Requirements:
      *
      *
@@ -108,6 +110,7 @@ abstract contract ERC1363 is ERC20, ERC165, IERC1363 {
     /**
     /**
      * @dev Sets a `value` amount of tokens as the allowance of `spender` over the
      * @dev Sets a `value` amount of tokens as the allowance of `spender` over the
      * caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.
      * caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.
+     * Returns a flag that indicates if the call succeeded.
      *
      *
      * Requirements:
      * Requirements:
      *
      *

+ 0 - 1
contracts/token/ERC20/utils/SafeERC20.sol

@@ -5,7 +5,6 @@ pragma solidity ^0.8.20;
 
 
 import {IERC20} from "../IERC20.sol";
 import {IERC20} from "../IERC20.sol";
 import {IERC1363} from "../../../interfaces/IERC1363.sol";
 import {IERC1363} from "../../../interfaces/IERC1363.sol";
-import {Address} from "../../../utils/Address.sol";
 
 
 /**
 /**
  * @title SafeERC20
  * @title SafeERC20

+ 5 - 7
contracts/utils/Bytes.sol

@@ -27,15 +27,13 @@ library Bytes {
      * NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf[Javascript's `Array.indexOf`]
      * NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf[Javascript's `Array.indexOf`]
      */
      */
     function indexOf(bytes memory buffer, bytes1 s, uint256 pos) internal pure returns (uint256) {
     function indexOf(bytes memory buffer, bytes1 s, uint256 pos) internal pure returns (uint256) {
-        unchecked {
-            uint256 length = buffer.length;
-            for (uint256 i = pos; i < length; ++i) {
-                if (bytes1(_unsafeReadBytesOffset(buffer, i)) == s) {
-                    return i;
-                }
+        uint256 length = buffer.length;
+        for (uint256 i = pos; i < length; ++i) {
+            if (bytes1(_unsafeReadBytesOffset(buffer, i)) == s) {
+                return i;
             }
             }
-            return type(uint256).max;
         }
         }
+        return type(uint256).max;
     }
     }
 
 
     /**
     /**

+ 7 - 3
contracts/utils/CAIP10.sol

@@ -2,10 +2,9 @@
 
 
 pragma solidity ^0.8.24;
 pragma solidity ^0.8.24;
 
 
-import {SafeCast} from "./math/SafeCast.sol";
 import {Bytes} from "./Bytes.sol";
 import {Bytes} from "./Bytes.sol";
-import {CAIP2} from "./CAIP2.sol";
 import {Strings} from "./Strings.sol";
 import {Strings} from "./Strings.sol";
+import {CAIP2} from "./CAIP2.sol";
 
 
 /**
 /**
  * @dev Helper library to format and parse CAIP-10 identifiers
  * @dev Helper library to format and parse CAIP-10 identifiers
@@ -14,9 +13,14 @@ import {Strings} from "./Strings.sol";
  * account_id:        chain_id + ":" + account_address
  * account_id:        chain_id + ":" + account_address
  * chain_id:          [-a-z0-9]{3,8}:[-_a-zA-Z0-9]{1,32} (See {CAIP2})
  * chain_id:          [-a-z0-9]{3,8}:[-_a-zA-Z0-9]{1,32} (See {CAIP2})
  * account_address:   [-.%a-zA-Z0-9]{1,128}
  * account_address:   [-.%a-zA-Z0-9]{1,128}
+ *
+ * WARNING: According to [CAIP-10's canonicalization section](https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-10.md#canonicalization),
+ * the implementation remains at the developer's discretion. Please note that case variations may introduce ambiguity.
+ * For example, when building hashes to identify accounts or data associated to them, multiple representations of the
+ * same account would derive to different hashes. For EVM chains, we recommend using checksummed addresses for the
+ * "account_address" part. They can be generated onchain using {Strings-toChecksumHexString}.
  */
  */
 library CAIP10 {
 library CAIP10 {
-    using SafeCast for uint256;
     using Strings for address;
     using Strings for address;
     using Bytes for bytes;
     using Bytes for bytes;
 
 

+ 5 - 2
contracts/utils/CAIP2.sol

@@ -2,7 +2,6 @@
 
 
 pragma solidity ^0.8.24;
 pragma solidity ^0.8.24;
 
 
-import {SafeCast} from "./math/SafeCast.sol";
 import {Bytes} from "./Bytes.sol";
 import {Bytes} from "./Bytes.sol";
 import {Strings} from "./Strings.sol";
 import {Strings} from "./Strings.sol";
 
 
@@ -13,9 +12,13 @@ import {Strings} from "./Strings.sol";
  * chain_id:    namespace + ":" + reference
  * chain_id:    namespace + ":" + reference
  * namespace:   [-a-z0-9]{3,8}
  * namespace:   [-a-z0-9]{3,8}
  * reference:   [-_a-zA-Z0-9]{1,32}
  * reference:   [-_a-zA-Z0-9]{1,32}
+ *
+ * WARNING: In some cases, multiple CAIP-2 identifiers may all be valid representation of a single chain.
+ * For EVM chains, it is recommended to use `eip155:xxx` as the canonical representation (where `xxx` is
+ * the EIP-155 chain id). Consider the possible ambiguity when processing CAIP-2 identifiers or when using them
+ * in the context of hashes.
  */
  */
 library CAIP2 {
 library CAIP2 {
-    using SafeCast for uint256;
     using Strings for uint256;
     using Strings for uint256;
     using Bytes for bytes;
     using Bytes for bytes;
 
 

+ 28 - 15
contracts/utils/NoncesKeyed.sol

@@ -4,22 +4,26 @@ pragma solidity ^0.8.20;
 import {Nonces} from "./Nonces.sol";
 import {Nonces} from "./Nonces.sol";
 
 
 /**
 /**
- * @dev Alternative to {Nonces}, that support key-ed nonces.
+ * @dev Alternative to {Nonces}, that supports key-ed nonces.
  *
  *
  * Follows the https://eips.ethereum.org/EIPS/eip-4337#semi-abstracted-nonce-support[ERC-4337's semi-abstracted nonce system].
  * Follows the https://eips.ethereum.org/EIPS/eip-4337#semi-abstracted-nonce-support[ERC-4337's semi-abstracted nonce system].
+ *
+ * NOTE: This contract inherits from {Nonces} and reuses its storage for the first nonce key (i.e. `0`). This
+ * makes upgrading from {Nonces} to {NoncesKeyed} safe when using their upgradeable versions (e.g. `NoncesKeyedUpgradeable`).
+ * Doing so will NOT reset the current state of nonces, avoiding replay attacks where a nonce is reused after the upgrade.
  */
  */
 abstract contract NoncesKeyed is Nonces {
 abstract contract NoncesKeyed is Nonces {
     mapping(address owner => mapping(uint192 key => uint64)) private _nonces;
     mapping(address owner => mapping(uint192 key => uint64)) private _nonces;
 
 
     /// @dev Returns the next unused nonce for an address and key. Result contains the key prefix.
     /// @dev Returns the next unused nonce for an address and key. Result contains the key prefix.
     function nonces(address owner, uint192 key) public view virtual returns (uint256) {
     function nonces(address owner, uint192 key) public view virtual returns (uint256) {
-        return key == 0 ? nonces(owner) : ((uint256(key) << 64) | _nonces[owner][key]);
+        return key == 0 ? nonces(owner) : _pack(key, _nonces[owner][key]);
     }
     }
 
 
     /**
     /**
      * @dev Consumes the next unused nonce for an address and key.
      * @dev Consumes the next unused nonce for an address and key.
      *
      *
-     * Returns the current value without the key prefix. Consumed nonce is increased, so calling this functions twice
+     * Returns the current value without the key prefix. Consumed nonce is increased, so calling this function twice
      * with the same arguments will return different (sequential) results.
      * with the same arguments will return different (sequential) results.
      */
      */
     function _useNonce(address owner, uint192 key) internal virtual returns (uint256) {
     function _useNonce(address owner, uint192 key) internal virtual returns (uint256) {
@@ -27,7 +31,7 @@ abstract contract NoncesKeyed is Nonces {
         // decremented or reset. This guarantees that the nonce never overflows.
         // decremented or reset. This guarantees that the nonce never overflows.
         unchecked {
         unchecked {
             // It is important to do x++ and not ++x here.
             // It is important to do x++ and not ++x here.
-            return key == 0 ? _useNonce(owner) : _nonces[owner][key]++;
+            return key == 0 ? _useNonce(owner) : _pack(key, _nonces[owner][key]++);
         }
         }
     }
     }
 
 
@@ -35,11 +39,17 @@ abstract contract NoncesKeyed is Nonces {
      * @dev Same as {_useNonce} but checking that `nonce` is the next valid for `owner`.
      * @dev Same as {_useNonce} but checking that `nonce` is the next valid for `owner`.
      *
      *
      * This version takes the key and the nonce in a single uint256 parameter:
      * This version takes the key and the nonce in a single uint256 parameter:
-     * - use the first 8 bytes for the key
-     * - use the last 24 bytes for the nonce
+     * - use the first 24 bytes for the key
+     * - use the last 8 bytes for the nonce
      */
      */
     function _useCheckedNonce(address owner, uint256 keyNonce) internal virtual override {
     function _useCheckedNonce(address owner, uint256 keyNonce) internal virtual override {
-        _useCheckedNonce(owner, uint192(keyNonce >> 64), uint64(keyNonce));
+        (uint192 key, ) = _unpack(keyNonce);
+        if (key == 0) {
+            super._useCheckedNonce(owner, keyNonce);
+        } else {
+            uint256 current = _useNonce(owner, key);
+            if (keyNonce != current) revert InvalidAccountNonce(owner, current);
+        }
     }
     }
 
 
     /**
     /**
@@ -48,13 +58,16 @@ abstract contract NoncesKeyed is Nonces {
      * This version takes the key and the nonce as two different parameters.
      * This version takes the key and the nonce as two different parameters.
      */
      */
     function _useCheckedNonce(address owner, uint192 key, uint64 nonce) internal virtual {
     function _useCheckedNonce(address owner, uint192 key, uint64 nonce) internal virtual {
-        if (key == 0) {
-            super._useCheckedNonce(owner, nonce);
-        } else {
-            uint256 current = _useNonce(owner, key);
-            if (nonce != current) {
-                revert InvalidAccountNonce(owner, current);
-            }
-        }
+        _useCheckedNonce(owner, _pack(key, nonce));
+    }
+
+    /// @dev Pack key and nonce into a keyNonce
+    function _pack(uint192 key, uint64 nonce) private pure returns (uint256) {
+        return (uint256(key) << 64) | nonce;
+    }
+
+    /// @dev Unpack a keyNonce into its key and nonce components
+    function _unpack(uint256 keyNonce) private pure returns (uint192 key, uint64 nonce) {
+        return (uint192(keyNonce >> 64), uint64(keyNonce));
     }
     }
 }
 }

+ 49 - 8
contracts/utils/Strings.sol

@@ -158,7 +158,7 @@ library Strings {
      * NOTE: This function will revert if the result does not fit in a `uint256`.
      * NOTE: This function will revert if the result does not fit in a `uint256`.
      */
      */
     function tryParseUint(string memory input) internal pure returns (bool success, uint256 value) {
     function tryParseUint(string memory input) internal pure returns (bool success, uint256 value) {
-        return tryParseUint(input, 0, bytes(input).length);
+        return _tryParseUintUncheckedBounds(input, 0, bytes(input).length);
     }
     }
 
 
     /**
     /**
@@ -172,6 +172,19 @@ library Strings {
         uint256 begin,
         uint256 begin,
         uint256 end
         uint256 end
     ) internal pure returns (bool success, uint256 value) {
     ) internal pure returns (bool success, uint256 value) {
+        if (end > bytes(input).length || begin > end) return (false, 0);
+        return _tryParseUintUncheckedBounds(input, begin, end);
+    }
+
+    /**
+     * @dev Implementation of {tryParseUint} that does not check bounds. Caller should make sure that
+     * `begin <= end <= input.length`. Other inputs would result in undefined behavior.
+     */
+    function _tryParseUintUncheckedBounds(
+        string memory input,
+        uint256 begin,
+        uint256 end
+    ) private pure returns (bool success, uint256 value) {
         bytes memory buffer = bytes(input);
         bytes memory buffer = bytes(input);
 
 
         uint256 result = 0;
         uint256 result = 0;
@@ -216,7 +229,7 @@ library Strings {
      * NOTE: This function will revert if the absolute value of the result does not fit in a `uint256`.
      * NOTE: This function will revert if the absolute value of the result does not fit in a `uint256`.
      */
      */
     function tryParseInt(string memory input) internal pure returns (bool success, int256 value) {
     function tryParseInt(string memory input) internal pure returns (bool success, int256 value) {
-        return tryParseInt(input, 0, bytes(input).length);
+        return _tryParseIntUncheckedBounds(input, 0, bytes(input).length);
     }
     }
 
 
     uint256 private constant ABS_MIN_INT256 = 2 ** 255;
     uint256 private constant ABS_MIN_INT256 = 2 ** 255;
@@ -232,10 +245,23 @@ library Strings {
         uint256 begin,
         uint256 begin,
         uint256 end
         uint256 end
     ) internal pure returns (bool success, int256 value) {
     ) internal pure returns (bool success, int256 value) {
+        if (end > bytes(input).length || begin > end) return (false, 0);
+        return _tryParseIntUncheckedBounds(input, begin, end);
+    }
+
+    /**
+     * @dev Implementation of {tryParseInt} that does not check bounds. Caller should make sure that
+     * `begin <= end <= input.length`. Other inputs would result in undefined behavior.
+     */
+    function _tryParseIntUncheckedBounds(
+        string memory input,
+        uint256 begin,
+        uint256 end
+    ) private pure returns (bool success, int256 value) {
         bytes memory buffer = bytes(input);
         bytes memory buffer = bytes(input);
 
 
         // Check presence of a negative sign.
         // Check presence of a negative sign.
-        bytes1 sign = bytes1(_unsafeReadBytesOffset(buffer, begin));
+        bytes1 sign = begin == end ? bytes1(0) : bytes1(_unsafeReadBytesOffset(buffer, begin)); // don't do out-of-bound (possibly unsafe) read if sub-string is empty
         bool positiveSign = sign == bytes1("+");
         bool positiveSign = sign == bytes1("+");
         bool negativeSign = sign == bytes1("-");
         bool negativeSign = sign == bytes1("-");
         uint256 offset = (positiveSign || negativeSign).toUint();
         uint256 offset = (positiveSign || negativeSign).toUint();
@@ -280,7 +306,7 @@ library Strings {
      * NOTE: This function will revert if the result does not fit in a `uint256`.
      * NOTE: This function will revert if the result does not fit in a `uint256`.
      */
      */
     function tryParseHexUint(string memory input) internal pure returns (bool success, uint256 value) {
     function tryParseHexUint(string memory input) internal pure returns (bool success, uint256 value) {
-        return tryParseHexUint(input, 0, bytes(input).length);
+        return _tryParseHexUintUncheckedBounds(input, 0, bytes(input).length);
     }
     }
 
 
     /**
     /**
@@ -294,10 +320,23 @@ library Strings {
         uint256 begin,
         uint256 begin,
         uint256 end
         uint256 end
     ) internal pure returns (bool success, uint256 value) {
     ) internal pure returns (bool success, uint256 value) {
+        if (end > bytes(input).length || begin > end) return (false, 0);
+        return _tryParseHexUintUncheckedBounds(input, begin, end);
+    }
+
+    /**
+     * @dev Implementation of {tryParseHexUint} that does not check bounds. Caller should make sure that
+     * `begin <= end <= input.length`. Other inputs would result in undefined behavior.
+     */
+    function _tryParseHexUintUncheckedBounds(
+        string memory input,
+        uint256 begin,
+        uint256 end
+    ) private pure returns (bool success, uint256 value) {
         bytes memory buffer = bytes(input);
         bytes memory buffer = bytes(input);
 
 
         // skip 0x prefix if present
         // skip 0x prefix if present
-        bool hasPrefix = bytes2(_unsafeReadBytesOffset(buffer, begin)) == bytes2("0x");
+        bool hasPrefix = (end > begin + 1) && bytes2(_unsafeReadBytesOffset(buffer, begin)) == bytes2("0x"); // don't do out-of-bound (possibly unsafe) read if sub-string is empty
         uint256 offset = hasPrefix.toUint() * 2;
         uint256 offset = hasPrefix.toUint() * 2;
 
 
         uint256 result = 0;
         uint256 result = 0;
@@ -354,13 +393,15 @@ library Strings {
         uint256 begin,
         uint256 begin,
         uint256 end
         uint256 end
     ) internal pure returns (bool success, address value) {
     ) internal pure returns (bool success, address value) {
-        // check that input is the correct length
-        bool hasPrefix = bytes2(_unsafeReadBytesOffset(bytes(input), begin)) == bytes2("0x");
+        if (end > bytes(input).length || begin > end) return (false, address(0));
+
+        bool hasPrefix = (end > begin + 1) && bytes2(_unsafeReadBytesOffset(bytes(input), begin)) == bytes2("0x"); // don't do out-of-bound (possibly unsafe) read if sub-string is empty
         uint256 expectedLength = 40 + hasPrefix.toUint() * 2;
         uint256 expectedLength = 40 + hasPrefix.toUint() * 2;
 
 
+        // check that input is the correct length
         if (end - begin == expectedLength) {
         if (end - begin == expectedLength) {
             // length guarantees that this does not overflow, and value is at most type(uint160).max
             // length guarantees that this does not overflow, and value is at most type(uint160).max
-            (bool s, uint256 v) = tryParseHexUint(input, begin, end);
+            (bool s, uint256 v) = _tryParseHexUintUncheckedBounds(input, begin, end);
             return (s, address(uint160(v)));
             return (s, address(uint160(v)));
         } else {
         } else {
             return (false, address(0));
             return (false, address(0));

+ 84 - 17
test/account/utils/draft-ERC4337Utils.test.js

@@ -2,13 +2,16 @@ const { ethers } = require('hardhat');
 const { expect } = require('chai');
 const { expect } = require('chai');
 const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
 const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
 
 
-const { packValidationData, packPaymasterData, UserOperation } = require('../../helpers/erc4337');
+const { packValidationData, UserOperation } = require('../../helpers/erc4337');
 const { MAX_UINT48 } = require('../../helpers/constants');
 const { MAX_UINT48 } = require('../../helpers/constants');
+const ADDRESS_ONE = '0x0000000000000000000000000000000000000001';
 
 
 const fixture = async () => {
 const fixture = async () => {
-  const [authorizer, sender, entrypoint, paymaster] = await ethers.getSigners();
+  const [authorizer, sender, entrypoint, factory, paymaster] = await ethers.getSigners();
   const utils = await ethers.deployContract('$ERC4337Utils');
   const utils = await ethers.deployContract('$ERC4337Utils');
-  return { utils, authorizer, sender, entrypoint, paymaster };
+  const SIG_VALIDATION_SUCCESS = await utils.$SIG_VALIDATION_SUCCESS();
+  const SIG_VALIDATION_FAILED = await utils.$SIG_VALIDATION_FAILED();
+  return { utils, authorizer, sender, entrypoint, factory, paymaster, SIG_VALIDATION_SUCCESS, SIG_VALIDATION_FAILED };
 };
 };
 
 
 describe('ERC4337Utils', function () {
 describe('ERC4337Utils', function () {
@@ -41,6 +44,20 @@ describe('ERC4337Utils', function () {
         MAX_UINT48,
         MAX_UINT48,
       ]);
       ]);
     });
     });
+
+    it('parse canonical values', async function () {
+      expect(this.utils.$parseValidationData(this.SIG_VALIDATION_SUCCESS)).to.eventually.deep.equal([
+        ethers.ZeroAddress,
+        0n,
+        MAX_UINT48,
+      ]);
+
+      expect(this.utils.$parseValidationData(this.SIG_VALIDATION_FAILED)).to.eventually.deep.equal([
+        ADDRESS_ONE,
+        0n,
+        MAX_UINT48,
+      ]);
+    });
   });
   });
 
 
   describe('packValidationData', function () {
   describe('packValidationData', function () {
@@ -65,6 +82,21 @@ describe('ERC4337Utils', function () {
         validationData,
         validationData,
       );
       );
     });
     });
+
+    it('packing reproduced canonical values', async function () {
+      expect(this.utils.$packValidationData(ethers.Typed.address(ethers.ZeroAddress), 0n, 0n)).to.eventually.equal(
+        this.SIG_VALIDATION_SUCCESS,
+      );
+      expect(this.utils.$packValidationData(ethers.Typed.bool(true), 0n, 0n)).to.eventually.equal(
+        this.SIG_VALIDATION_SUCCESS,
+      );
+      expect(this.utils.$packValidationData(ethers.Typed.address(ADDRESS_ONE), 0n, 0n)).to.eventually.equal(
+        this.SIG_VALIDATION_FAILED,
+      );
+      expect(this.utils.$packValidationData(ethers.Typed.bool(false), 0n, 0n)).to.eventually.equal(
+        this.SIG_VALIDATION_FAILED,
+      );
+    });
   });
   });
 
 
   describe('combineValidationData', function () {
   describe('combineValidationData', function () {
@@ -133,13 +165,6 @@ describe('ERC4337Utils', function () {
   });
   });
 
 
   describe('hash', function () {
   describe('hash', function () {
-    it('returns the user operation hash', async function () {
-      const userOp = new UserOperation({ sender: this.sender, nonce: 1 });
-      const chainId = await ethers.provider.getNetwork().then(({ chainId }) => chainId);
-
-      expect(this.utils.$hash(userOp.packed)).to.eventually.equal(userOp.hash(this.utils.target, chainId));
-    });
-
     it('returns the operation hash with specified entrypoint and chainId', async function () {
     it('returns the operation hash with specified entrypoint and chainId', async function () {
       const userOp = new UserOperation({ sender: this.sender, nonce: 1 });
       const userOp = new UserOperation({ sender: this.sender, nonce: 1 });
       const chainId = 0xdeadbeef;
       const chainId = 0xdeadbeef;
@@ -151,6 +176,33 @@ describe('ERC4337Utils', function () {
   });
   });
 
 
   describe('userOp values', function () {
   describe('userOp values', function () {
+    describe('intiCode', function () {
+      beforeEach(async function () {
+        this.userOp = new UserOperation({
+          sender: this.sender,
+          nonce: 1,
+          verificationGas: 0x12345678n,
+          factory: this.factory,
+          factoryData: '0x123456',
+        });
+
+        this.emptyUserOp = new UserOperation({
+          sender: this.sender,
+          nonce: 1,
+        });
+      });
+
+      it('returns factory', async function () {
+        expect(this.utils.$factory(this.userOp.packed)).to.eventually.equal(this.factory);
+        expect(this.utils.$factory(this.emptyUserOp.packed)).to.eventually.equal(ethers.ZeroAddress);
+      });
+
+      it('returns factoryData', async function () {
+        expect(this.utils.$factoryData(this.userOp.packed)).to.eventually.equal('0x123456');
+        expect(this.utils.$factoryData(this.emptyUserOp.packed)).to.eventually.equal('0x');
+      });
+    });
+
     it('returns verificationGasLimit', async function () {
     it('returns verificationGasLimit', async function () {
       const userOp = new UserOperation({ sender: this.sender, nonce: 1, verificationGas: 0x12345678n });
       const userOp = new UserOperation({ sender: this.sender, nonce: 1, verificationGas: 0x12345678n });
       expect(this.utils.$verificationGasLimit(userOp.packed)).to.eventually.equal(userOp.verificationGas);
       expect(this.utils.$verificationGasLimit(userOp.packed)).to.eventually.equal(userOp.verificationGas);
@@ -183,28 +235,43 @@ describe('ERC4337Utils', function () {
 
 
     describe('paymasterAndData', function () {
     describe('paymasterAndData', function () {
       beforeEach(async function () {
       beforeEach(async function () {
-        this.verificationGasLimit = 0x12345678n;
-        this.postOpGasLimit = 0x87654321n;
-        this.paymasterAndData = packPaymasterData(this.paymaster, this.verificationGasLimit, this.postOpGasLimit);
         this.userOp = new UserOperation({
         this.userOp = new UserOperation({
           sender: this.sender,
           sender: this.sender,
           nonce: 1,
           nonce: 1,
-          paymasterAndData: this.paymasterAndData,
+          paymaster: this.paymaster,
+          paymasterVerificationGasLimit: 0x12345678n,
+          paymasterPostOpGasLimit: 0x87654321n,
+          paymasterData: '0xbeefcafe',
+        });
+
+        this.emptyUserOp = new UserOperation({
+          sender: this.sender,
+          nonce: 1,
         });
         });
       });
       });
 
 
       it('returns paymaster', async function () {
       it('returns paymaster', async function () {
-        expect(this.utils.$paymaster(this.userOp.packed)).to.eventually.equal(this.paymaster);
+        expect(this.utils.$paymaster(this.userOp.packed)).to.eventually.equal(this.userOp.paymaster);
+        expect(this.utils.$paymaster(this.emptyUserOp.packed)).to.eventually.equal(ethers.ZeroAddress);
       });
       });
 
 
       it('returns verificationGasLimit', async function () {
       it('returns verificationGasLimit', async function () {
         expect(this.utils.$paymasterVerificationGasLimit(this.userOp.packed)).to.eventually.equal(
         expect(this.utils.$paymasterVerificationGasLimit(this.userOp.packed)).to.eventually.equal(
-          this.verificationGasLimit,
+          this.userOp.paymasterVerificationGasLimit,
         );
         );
+        expect(this.utils.$paymasterVerificationGasLimit(this.emptyUserOp.packed)).to.eventually.equal(0n);
       });
       });
 
 
       it('returns postOpGasLimit', async function () {
       it('returns postOpGasLimit', async function () {
-        expect(this.utils.$paymasterPostOpGasLimit(this.userOp.packed)).to.eventually.equal(this.postOpGasLimit);
+        expect(this.utils.$paymasterPostOpGasLimit(this.userOp.packed)).to.eventually.equal(
+          this.userOp.paymasterPostOpGasLimit,
+        );
+        expect(this.utils.$paymasterPostOpGasLimit(this.emptyUserOp.packed)).to.eventually.equal(0n);
+      });
+
+      it('returns data', async function () {
+        expect(this.utils.$paymasterData(this.userOp.packed)).to.eventually.equal(this.userOp.paymasterData);
+        expect(this.utils.$paymasterData(this.emptyUserOp.packed)).to.eventually.equal('0x');
       });
       });
     });
     });
   });
   });

+ 2 - 3
test/account/utils/draft-ERC7579Utils.test.js

@@ -1,6 +1,6 @@
 const { ethers } = require('hardhat');
 const { ethers } = require('hardhat');
 const { expect } = require('chai');
 const { expect } = require('chai');
-const { loadFixture, setBalance } = require('@nomicfoundation/hardhat-network-helpers');
+const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
 const {
 const {
   EXEC_TYPE_DEFAULT,
   EXEC_TYPE_DEFAULT,
   EXEC_TYPE_TRY,
   EXEC_TYPE_TRY,
@@ -17,11 +17,10 @@ const coder = ethers.AbiCoder.defaultAbiCoder();
 
 
 const fixture = async () => {
 const fixture = async () => {
   const [sender] = await ethers.getSigners();
   const [sender] = await ethers.getSigners();
-  const utils = await ethers.deployContract('$ERC7579Utils');
+  const utils = await ethers.deployContract('$ERC7579Utils', { value: ethers.parseEther('1') });
   const utilsGlobal = await ethers.deployContract('$ERC7579UtilsGlobalMock');
   const utilsGlobal = await ethers.deployContract('$ERC7579UtilsGlobalMock');
   const target = await ethers.deployContract('CallReceiverMock');
   const target = await ethers.deployContract('CallReceiverMock');
   const anotherTarget = await ethers.deployContract('CallReceiverMock');
   const anotherTarget = await ethers.deployContract('CallReceiverMock');
-  await setBalance(utils.target, ethers.parseEther('1'));
   return { utils, utilsGlobal, target, anotherTarget, sender };
   return { utils, utilsGlobal, target, anotherTarget, sender };
 };
 };
 
 

+ 0 - 0
test/governance/utils/VotesAdditionalCheckpoints.test.js → test/governance/utils/VotesExtended.test.js


+ 24 - 8
test/helpers/erc4337.js

@@ -26,10 +26,14 @@ function packValidationData(validAfter, validUntil, authorizer) {
   );
   );
 }
 }
 
 
-function packPaymasterData(paymaster, verificationGasLimit, postOpGasLimit) {
+function packInitCode(factory, factoryData) {
+  return ethers.solidityPacked(['address', 'bytes'], [getAddress(factory), factoryData]);
+}
+
+function packPaymasterAndData(paymaster, paymasterVerificationGasLimit, paymasterPostOpGasLimit, paymasterData) {
   return ethers.solidityPacked(
   return ethers.solidityPacked(
-    ['address', 'uint128', 'uint128'],
-    [getAddress(paymaster), verificationGasLimit, postOpGasLimit],
+    ['address', 'uint128', 'uint128', 'bytes'],
+    [getAddress(paymaster), paymasterVerificationGasLimit, paymasterPostOpGasLimit, paymasterData],
   );
   );
 }
 }
 
 
@@ -38,14 +42,18 @@ class UserOperation {
   constructor(params) {
   constructor(params) {
     this.sender = getAddress(params.sender);
     this.sender = getAddress(params.sender);
     this.nonce = params.nonce;
     this.nonce = params.nonce;
-    this.initCode = params.initCode ?? '0x';
+    this.factory = params.factory ?? undefined;
+    this.factoryData = params.factoryData ?? '0x';
     this.callData = params.callData ?? '0x';
     this.callData = params.callData ?? '0x';
     this.verificationGas = params.verificationGas ?? 10_000_000n;
     this.verificationGas = params.verificationGas ?? 10_000_000n;
     this.callGas = params.callGas ?? 100_000n;
     this.callGas = params.callGas ?? 100_000n;
     this.preVerificationGas = params.preVerificationGas ?? 100_000n;
     this.preVerificationGas = params.preVerificationGas ?? 100_000n;
     this.maxPriorityFee = params.maxPriorityFee ?? 100_000n;
     this.maxPriorityFee = params.maxPriorityFee ?? 100_000n;
     this.maxFeePerGas = params.maxFeePerGas ?? 100_000n;
     this.maxFeePerGas = params.maxFeePerGas ?? 100_000n;
-    this.paymasterAndData = params.paymasterAndData ?? '0x';
+    this.paymaster = params.paymaster ?? undefined;
+    this.paymasterVerificationGasLimit = params.paymasterVerificationGasLimit ?? 0n;
+    this.paymasterPostOpGasLimit = params.paymasterPostOpGasLimit ?? 0n;
+    this.paymasterData = params.paymasterData ?? '0x';
     this.signature = params.signature ?? '0x';
     this.signature = params.signature ?? '0x';
   }
   }
 
 
@@ -53,12 +61,19 @@ class UserOperation {
     return {
     return {
       sender: this.sender,
       sender: this.sender,
       nonce: this.nonce,
       nonce: this.nonce,
-      initCode: this.initCode,
+      initCode: this.factory ? packInitCode(this.factory, this.factoryData) : '0x',
       callData: this.callData,
       callData: this.callData,
       accountGasLimits: pack(this.verificationGas, this.callGas),
       accountGasLimits: pack(this.verificationGas, this.callGas),
       preVerificationGas: this.preVerificationGas,
       preVerificationGas: this.preVerificationGas,
       gasFees: pack(this.maxPriorityFee, this.maxFeePerGas),
       gasFees: pack(this.maxPriorityFee, this.maxFeePerGas),
-      paymasterAndData: this.paymasterAndData,
+      paymasterAndData: this.paymaster
+        ? packPaymasterAndData(
+            this.paymaster,
+            this.paymasterVerificationGasLimit,
+            this.paymasterPostOpGasLimit,
+            this.paymasterData,
+          )
+        : '0x',
       signature: this.signature,
       signature: this.signature,
     };
     };
   }
   }
@@ -90,6 +105,7 @@ module.exports = {
   SIG_VALIDATION_SUCCESS,
   SIG_VALIDATION_SUCCESS,
   SIG_VALIDATION_FAILURE,
   SIG_VALIDATION_FAILURE,
   packValidationData,
   packValidationData,
-  packPaymasterData,
+  packInitCode,
+  packPaymasterAndData,
   UserOperation,
   UserOperation,
 };
 };

+ 43 - 6
test/utils/Nonces.behavior.js

@@ -86,7 +86,7 @@ function shouldBehaveLikeNoncesKeyed() {
 
 
         await expect(this.mock.$_useNonce(sender, ethers.Typed.uint192(0n)))
         await expect(this.mock.$_useNonce(sender, ethers.Typed.uint192(0n)))
           .to.emit(this.mock, 'return$_useNonce_address_uint192')
           .to.emit(this.mock, 'return$_useNonce_address_uint192')
-          .withArgs(1n);
+          .withArgs(keyOffset(0n) + 1n);
 
 
         expect(this.mock.nonces(sender, ethers.Typed.uint192(0n))).to.eventually.equal(keyOffset(0n) + 2n);
         expect(this.mock.nonces(sender, ethers.Typed.uint192(0n))).to.eventually.equal(keyOffset(0n) + 2n);
         expect(this.mock.nonces(sender, ethers.Typed.uint192(17n))).to.eventually.equal(keyOffset(17n) + 0n);
         expect(this.mock.nonces(sender, ethers.Typed.uint192(17n))).to.eventually.equal(keyOffset(17n) + 0n);
@@ -98,18 +98,18 @@ function shouldBehaveLikeNoncesKeyed() {
 
 
         await expect(this.mock.$_useNonce(sender, ethers.Typed.uint192(17n)))
         await expect(this.mock.$_useNonce(sender, ethers.Typed.uint192(17n)))
           .to.emit(this.mock, 'return$_useNonce_address_uint192')
           .to.emit(this.mock, 'return$_useNonce_address_uint192')
-          .withArgs(0n);
+          .withArgs(keyOffset(17n) + 0n);
 
 
         await expect(this.mock.$_useNonce(sender, ethers.Typed.uint192(17n)))
         await expect(this.mock.$_useNonce(sender, ethers.Typed.uint192(17n)))
           .to.emit(this.mock, 'return$_useNonce_address_uint192')
           .to.emit(this.mock, 'return$_useNonce_address_uint192')
-          .withArgs(1n);
+          .withArgs(keyOffset(17n) + 1n);
 
 
         expect(this.mock.nonces(sender, ethers.Typed.uint192(0n))).to.eventually.equal(keyOffset(0n) + 0n);
         expect(this.mock.nonces(sender, ethers.Typed.uint192(0n))).to.eventually.equal(keyOffset(0n) + 0n);
         expect(this.mock.nonces(sender, ethers.Typed.uint192(17n))).to.eventually.equal(keyOffset(17n) + 2n);
         expect(this.mock.nonces(sender, ethers.Typed.uint192(17n))).to.eventually.equal(keyOffset(17n) + 2n);
       });
       });
     });
     });
 
 
-    describe('_useCheckedNonce', function () {
+    describe('_useCheckedNonce(address, uint256)', function () {
       it('default variant uses key 0', async function () {
       it('default variant uses key 0', async function () {
         const currentNonce = await this.mock.nonces(sender, ethers.Typed.uint192(0n));
         const currentNonce = await this.mock.nonces(sender, ethers.Typed.uint192(0n));
 
 
@@ -135,12 +135,49 @@ function shouldBehaveLikeNoncesKeyed() {
         // reuse same nonce
         // reuse same nonce
         await expect(this.mock.$_useCheckedNonce(sender, currentNonce))
         await expect(this.mock.$_useCheckedNonce(sender, currentNonce))
           .to.be.revertedWithCustomError(this.mock, 'InvalidAccountNonce')
           .to.be.revertedWithCustomError(this.mock, 'InvalidAccountNonce')
-          .withArgs(sender, 1);
+          .withArgs(sender, currentNonce + 1n);
 
 
         // use "future" nonce too early
         // use "future" nonce too early
         await expect(this.mock.$_useCheckedNonce(sender, currentNonce + 10n))
         await expect(this.mock.$_useCheckedNonce(sender, currentNonce + 10n))
           .to.be.revertedWithCustomError(this.mock, 'InvalidAccountNonce')
           .to.be.revertedWithCustomError(this.mock, 'InvalidAccountNonce')
-          .withArgs(sender, 1);
+          .withArgs(sender, currentNonce + 1n);
+      });
+    });
+
+    describe('_useCheckedNonce(address, uint192, uint64)', function () {
+      const MASK = 0xffffffffffffffffn;
+
+      it('default variant uses key 0', async function () {
+        const currentNonce = await this.mock.nonces(sender, ethers.Typed.uint192(0n));
+
+        await this.mock.$_useCheckedNonce(sender, ethers.Typed.uint192(0n), currentNonce);
+
+        expect(this.mock.nonces(sender, ethers.Typed.uint192(0n))).to.eventually.equal(currentNonce + 1n);
+      });
+
+      it('use nonce at another key', async function () {
+        const currentNonce = await this.mock.nonces(sender, ethers.Typed.uint192(17n));
+
+        await this.mock.$_useCheckedNonce(sender, ethers.Typed.uint192(17n), currentNonce & MASK);
+
+        expect(this.mock.nonces(sender, ethers.Typed.uint192(17n))).to.eventually.equal(currentNonce + 1n);
+      });
+
+      it('reverts when nonce is not the expected', async function () {
+        const currentNonce = await this.mock.nonces(sender, ethers.Typed.uint192(42n));
+
+        // use and increment
+        await this.mock.$_useCheckedNonce(sender, ethers.Typed.uint192(42n), currentNonce & MASK);
+
+        // reuse same nonce
+        await expect(this.mock.$_useCheckedNonce(sender, ethers.Typed.uint192(42n), currentNonce & MASK))
+          .to.be.revertedWithCustomError(this.mock, 'InvalidAccountNonce')
+          .withArgs(sender, currentNonce + 1n);
+
+        // use "future" nonce too early
+        await expect(this.mock.$_useCheckedNonce(sender, ethers.Typed.uint192(42n), (currentNonce & MASK) + 10n))
+          .to.be.revertedWithCustomError(this.mock, 'InvalidAccountNonce')
+          .withArgs(sender, currentNonce + 1n);
       });
       });
     });
     });
   });
   });

+ 23 - 0
test/utils/Strings.t.sol

@@ -24,4 +24,27 @@ contract StringsTest is Test {
     function testParseChecksumHex(address value) external {
     function testParseChecksumHex(address value) external {
         assertEq(value, value.toChecksumHexString().parseAddress());
         assertEq(value, value.toChecksumHexString().parseAddress());
     }
     }
+
+    function testTryParseHexUintExtendedEnd(string memory random) external pure {
+        uint256 length = bytes(random).length;
+        assembly ("memory-safe") {
+            mstore(add(add(random, 0x20), length), 0x3030303030303030303030303030303030303030303030303030303030303030)
+        }
+
+        (bool success, ) = random.tryParseHexUint(1, length + 1);
+        assertFalse(success);
+    }
+
+    function testTryParseAddressExtendedEnd(address random, uint256 begin) external pure {
+        begin = bound(begin, 3, 43);
+        string memory input = random.toHexString();
+        uint256 length = bytes(input).length;
+
+        assembly ("memory-safe") {
+            mstore(add(add(input, 0x20), length), 0x3030303030303030303030303030303030303030303030303030303030303030)
+        }
+
+        (bool success, ) = input.tryParseAddress(begin, begin + 40);
+        assertFalse(success);
+    }
 }
 }

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

@@ -240,6 +240,11 @@ describe('Strings', function () {
       expect(await this.mock.$tryParseUint('1 000')).deep.equal([false, 0n]);
       expect(await this.mock.$tryParseUint('1 000')).deep.equal([false, 0n]);
     });
     });
 
 
+    it('parseUint invalid range', async function () {
+      expect(this.mock.$parseUint('12', 3, 2)).to.be.revertedWithCustomError(this.mock, 'StringsInvalidChar');
+      expect(await this.mock.$tryParseUint('12', 3, 2)).to.deep.equal([false, 0n]);
+    });
+
     it('parseInt overflow', async function () {
     it('parseInt overflow', async function () {
       await expect(this.mock.$parseInt((ethers.MaxUint256 + 1n).toString(10))).to.be.revertedWithPanic(
       await expect(this.mock.$parseInt((ethers.MaxUint256 + 1n).toString(10))).to.be.revertedWithPanic(
         PANIC_CODES.ARITHMETIC_OVERFLOW,
         PANIC_CODES.ARITHMETIC_OVERFLOW,
@@ -276,6 +281,11 @@ describe('Strings', function () {
       expect(await this.mock.$tryParseInt('1 000')).to.deep.equal([false, 0n]);
       expect(await this.mock.$tryParseInt('1 000')).to.deep.equal([false, 0n]);
     });
     });
 
 
+    it('parseInt invalid range', async function () {
+      expect(this.mock.$parseInt('-12', 3, 2)).to.be.revertedWithCustomError(this.mock, 'StringsInvalidChar');
+      expect(await this.mock.$tryParseInt('-12', 3, 2)).to.deep.equal([false, 0n]);
+    });
+
     it('parseHexUint overflow', async function () {
     it('parseHexUint overflow', async function () {
       await expect(this.mock.$parseHexUint((ethers.MaxUint256 + 1n).toString(16))).to.be.revertedWithPanic(
       await expect(this.mock.$parseHexUint((ethers.MaxUint256 + 1n).toString(16))).to.be.revertedWithPanic(
         PANIC_CODES.ARITHMETIC_OVERFLOW,
         PANIC_CODES.ARITHMETIC_OVERFLOW,
@@ -303,6 +313,11 @@ describe('Strings', function () {
       expect(await this.mock.$tryParseHexUint('1 000')).to.deep.equal([false, 0n]);
       expect(await this.mock.$tryParseHexUint('1 000')).to.deep.equal([false, 0n]);
     });
     });
 
 
+    it('parseHexUint invalid begin and end', async function () {
+      expect(this.mock.$parseHexUint('0x', 3, 2)).to.be.revertedWithCustomError(this.mock, 'StringsInvalidChar');
+      expect(await this.mock.$tryParseHexUint('0x', 3, 2)).to.deep.equal([false, 0n]);
+    });
+
     it('parseAddress invalid format', async function () {
     it('parseAddress invalid format', async function () {
       for (const addr of [
       for (const addr of [
         '0x736a507fB2881d6bB62dcA54673CF5295dC07833', // valid
         '0x736a507fB2881d6bB62dcA54673CF5295dC07833', // valid