@@ -8,9 +8,9 @@ import { ModeCode } from "../utils/Types.sol";
88
99/**
1010 * @title AllowedCalldataAnyOfEnforcer
11- * @dev Like `AllowedCalldataEnforcer`, but the delegator supplies several allowed byte sequences (`bytes[]`) .
12- * @dev At `dataStart `, the execution calldata must exactly match **at least one** of those sequences (each candidate is compared
13- * over its full length , starting at `dataStart `).
11+ * @dev Like `AllowedCalldataEnforcer`, but the delegator supplies several allowed byte sequences of **equal length** .
12+ * @dev At `startIndex `, the execution calldata must exactly match **at least one** of those sequences (each candidate is compared
13+ * over `valueLength` bytes , starting at `startIndex `).
1414 * @dev This enforcer operates only in single execution call type and with default execution mode.
1515 * @dev Prefer static or fixed-layout regions of calldata; validating dynamic types remains possible but is more error-prone,
1616 * same as for `AllowedCalldataEnforcer`.
@@ -21,11 +21,11 @@ contract AllowedCalldataAnyOfEnforcer is CaveatEnforcer {
2121 ////////////////////////////// Public Methods //////////////////////////////
2222
2323 /**
24- * @notice Allows the delegator to restrict calldata so that one of several allowed slices matches at a fixed offset.
25- * @dev For each candidate `v` in the decoded array , checks `callData[dataStart : dataStart + len(v) ] == v `.
26- * @param _terms Packed header plus ABI-encoded `bytes[]` :
27- * - the first 32 bytes: `uint256` start index in the execution call data (same layout as `AllowedCalldataEnforcer`)
28- * - the remainder: `abi.encode(bytes[])` where each element is a non-empty candidate byte string
24+ * @notice Allows the delegator to restrict calldata so that one of several equal-length slices matches at a fixed offset.
25+ * @dev For each candidate, checks `callData[startIndex : startIndex + valueLength ] == candidate `.
26+ * @param _terms Binary layout :
27+ * - **First 32 bytes:** `uint128 startIndex` (high 128 bits) | `uint128 valueLength` (low 128 bits) of one big-endian word.
28+ * - **Remainder:** `candidateCount` candidates concatenated, each exactly `valueLength` bytes (so `len(remainder) == candidateCount * valueLength`).
2929 * @param _mode The execution mode. (Must be Single callType, Default execType)
3030 * @param _executionCallData The execution the delegate is trying to execute.
3131 */
@@ -48,39 +48,53 @@ contract AllowedCalldataAnyOfEnforcer is CaveatEnforcer {
4848 }
4949
5050 /**
51- * @notice Decodes the terms used in this CaveatEnforcer.
51+ * @notice Decodes and validates the terms used in this CaveatEnforcer.
52+ * @dev After reading `valueLength` from the header word, requires `valueLength >= 1`, a non-empty remainder, and that the
53+ * remainder length is a multiple of `valueLength`.
5254 * @param _terms Encoded data used during the execution hooks.
53- * @return dataStart_ The start index in the execution's call data.
54- * @return values_ ABI-decoded array of candidate byte strings; each element must be at least one byte long.
55+ * @return startIndex_ Start index in the execution's call data.
56+ * @return valueLength_ Length of every candidate slice and of the compared execution calldata window.
57+ * @return candidateCount_ Number of candidates in the concatenated tail (`(len(_terms) - 32) / valueLength_`).
5558 */
56- function getTermsInfo (bytes calldata _terms ) public pure returns (uint256 dataStart_ , bytes [] memory values_ ) {
57- require (_terms.length >= 32 , "AllowedCalldataAnyOfEnforcer:invalid-terms-size " );
58- dataStart_ = uint256 (bytes32 (_terms[0 :32 ]));
59- values_ = abi.decode (_terms[32 :], (bytes []));
60- require (values_.length > 0 , "AllowedCalldataAnyOfEnforcer:no-allowed-values " );
61- for (uint256 i = 0 ; i < values_.length ; ++ i) {
62- require (values_[i].length >= 1 , "AllowedCalldataAnyOfEnforcer:invalid-value-length " );
63- }
59+ function getTermsInfo (bytes calldata _terms )
60+ public
61+ pure
62+ returns (uint128 startIndex_ , uint128 valueLength_ , uint256 candidateCount_ )
63+ {
64+ require (_terms.length > 32 , "AllowedCalldataAnyOfEnforcer:invalid-terms-size " );
65+ uint256 metadataWord_ = uint256 (bytes32 (_terms[0 :32 ]));
66+ startIndex_ = uint128 (metadataWord_ >> 128 );
67+ valueLength_ = uint128 (metadataWord_);
68+
69+ require (valueLength_ >= 1 , "AllowedCalldataAnyOfEnforcer:invalid-value-length " );
70+
71+ uint256 concatenatedValuesLength_ = _terms.length - 32 ;
72+ require (concatenatedValuesLength_ != 0 , "AllowedCalldataAnyOfEnforcer:no-allowed-values " );
73+ require (concatenatedValuesLength_ % uint256 (valueLength_) == 0 , "AllowedCalldataAnyOfEnforcer:invalid-values-padding " );
74+
75+ candidateCount_ = concatenatedValuesLength_ / uint256 (valueLength_);
6476 }
6577
6678 /**
67- * @notice Validates that the execution calldata matches one of the allowed slices at `dataStart `.
79+ * @notice Validates that the execution calldata matches one of the allowed slices at `startIndex `.
6880 * @param _terms Encoded terms (see `beforeHook`).
6981 * @param _executionCallData The encoded single execution payload.
7082 */
7183 function _validateCalldata (bytes calldata _terms , bytes calldata _executionCallData ) private pure {
72- (uint256 dataStart_ , bytes [] memory values_ ) = getTermsInfo (_terms);
84+ (uint128 startIndex_ , uint128 valueLength_ , uint256 candidateCount_ ) = getTermsInfo (_terms);
85+
86+ uint256 dataStart_ = uint256 (startIndex_);
87+ uint256 lengthToMatch_ = uint256 (valueLength_);
7388 (,, bytes calldata callData_ ) = _executionCallData.decodeSingle ();
7489
90+ require (dataStart_ + lengthToMatch_ <= callData_.length , "AllowedCalldataAnyOfEnforcer:invalid-calldata-length " );
91+
92+ bytes calldata callDataToMatch_ = callData_[dataStart_:dataStart_ + lengthToMatch_];
93+
7594 bool matched_;
76- uint256 n_ = values_.length ;
77- for (uint256 i = 0 ; i < n_; ++ i) {
78- bytes memory candidate_ = values_[i];
79- uint256 len_ = candidate_.length ;
80- if (dataStart_ + len_ > callData_.length ) {
81- continue ;
82- }
83- if (keccak256 (callData_[dataStart_:dataStart_ + len_]) == keccak256 (candidate_)) {
95+ for (uint256 i = 0 ; i < candidateCount_; ++ i) {
96+ uint256 offset_ = 32 + i * lengthToMatch_;
97+ if (callDataToMatch_ == _terms[offset_:offset_ + lengthToMatch_]) {
8498 matched_ = true ;
8599 break ;
86100 }
0 commit comments