@@ -17,6 +17,10 @@ import { ModeCode } from "../utils/Types.sol";
1717 * - 32 bytes: periodDuration (in seconds).
1818 * - 32 bytes: startDate for the first period.
1919 *
20+ * The _args parameter must contain a single uint256 value representing the index of the token
21+ * configuration to use from the _terms array. This index must be less than the total number
22+ * of configurations in _terms.
23+ *
2024 * For optimal gas usage, it is recommended that the configurations in _terms are sorted
2125 * from the most frequently used token to the least frequently used token. Tokens placed
2226 * earlier in the sequence will be processed first, reducing gas consumption.
@@ -83,7 +87,7 @@ contract MultiTokenPeriodEnforcer is CaveatEnforcer {
8387 * @param _delegationHash The delegation hash.
8488 * @param _delegationManager The delegation manager's address.
8589 * @param _terms A concatenation of one or more 116-byte configurations.
86- * @param _token The token for which the available amount is requested (address(0) for native) .
90+ * @param _args A single uint256 value representing the index of the token configuration to use .
8791 * @return availableAmount_ The remaining transferable amount in the current period.
8892 * @return isNewPeriod_ True if a new period has begun.
8993 * @return currentPeriod_ The current period index.
@@ -92,20 +96,21 @@ contract MultiTokenPeriodEnforcer is CaveatEnforcer {
9296 bytes32 _delegationHash ,
9397 address _delegationManager ,
9498 bytes calldata _terms ,
95- address _token
99+ bytes calldata _args
96100 )
97101 external
98102 view
99103 returns (uint256 availableAmount_ , bool isNewPeriod_ , uint256 currentPeriod_ )
100104 {
101- PeriodicAllowance memory storedAllowance_ = periodicAllowances[_delegationManager][_delegationHash][_token];
105+ (address token_ , uint256 periodAmount_ , uint256 periodDuration_ , uint256 startDate_ ) =
106+ getTermsInfo (_terms, abi.decode (_args, (uint256 )));
107+
108+ PeriodicAllowance memory storedAllowance_ = periodicAllowances[_delegationManager][_delegationHash][token_];
102109 if (storedAllowance_.startDate != 0 ) {
103110 return _getAvailableAmount (storedAllowance_);
104111 }
105112
106113 // Not yet initialized; simulate using provided terms.
107- (uint256 periodAmount_ , uint256 periodDuration_ , uint256 startDate_ ) = getTermsInfo (_terms, _token);
108-
109114 PeriodicAllowance memory allowance_ = PeriodicAllowance ({
110115 periodAmount: periodAmount_,
111116 periodDuration: periodDuration_,
@@ -123,14 +128,15 @@ contract MultiTokenPeriodEnforcer is CaveatEnforcer {
123128 * For native transfers, expects _executionCallData to decode to (target, value, callData)
124129 * with an empty callData.
125130 * @param _terms A concatenation of one or more 116-byte configurations.
131+ * @param _args A single uint256 value representing the index of the token configuration to use.
126132 * @param _mode The execution mode (must be single callType, default execType).
127133 * @param _executionCallData The encoded execution data.
128134 * @param _delegationHash The delegation hash.
129135 * @param _redeemer The address intended to receive the tokens/ETH.
130136 */
131137 function beforeHook (
132138 bytes calldata _terms ,
133- bytes calldata ,
139+ bytes calldata _args ,
134140 ModeCode _mode ,
135141 bytes calldata _executionCallData ,
136142 bytes32 _delegationHash ,
@@ -142,48 +148,38 @@ contract MultiTokenPeriodEnforcer is CaveatEnforcer {
142148 onlySingleCallTypeMode (_mode)
143149 onlyDefaultExecutionMode (_mode)
144150 {
145- _validateAndConsumeTransfer (_terms, _executionCallData, _delegationHash, _redeemer);
151+ _validateAndConsumeTransfer (_terms, _args, _executionCallData, _delegationHash, _redeemer);
146152 }
147153
148154 /**
149- * @notice Searches the provided _terms for a configuration matching _token .
155+ * @notice Retrieves the configuration for a specific token index from _terms .
150156 * @dev Expects _terms length to be a multiple of 116.
151157 * @param _terms A concatenation of 116-byte configurations.
152- * @param _token The token address to search for (address(0) for native transfers).
158+ * @param _tokenIndex The index of the token configuration to retrieve.
159+ * @return token_ The token address at the specified index.
153160 * @return periodAmount_ The maximum transferable amount for this token.
154161 * @return periodDuration_ The period duration (in seconds) for this token.
155162 * @return startDate_ The start date for the first period.
156163 */
157164 function getTermsInfo (
158165 bytes calldata _terms ,
159- address _token
166+ uint256 _tokenIndex
160167 )
161168 public
162169 pure
163- returns (uint256 periodAmount_ , uint256 periodDuration_ , uint256 startDate_ )
170+ returns (address token_ , uint256 periodAmount_ , uint256 periodDuration_ , uint256 startDate_ )
164171 {
165172 uint256 termsLength_ = _terms.length ;
166173 require (termsLength_ != 0 && termsLength_ % 116 == 0 , "MultiTokenPeriodEnforcer:invalid-terms-length " );
167174
168- // Iterate over the byte offset directly in increments of 116 bytes.
169- for (uint256 offset_ = 0 ; offset_ < termsLength_;) {
170- // Extract token address from the first 20 bytes.
171- address token_ = address (bytes20 (_terms[offset_:offset_ + 20 ]));
172- if (token_ == _token) {
173- // Get periodAmount from the next 32 bytes.
174- periodAmount_ = uint256 (bytes32 (_terms[offset_ + 20 :offset_ + 52 ]));
175- // Get periodDuration from the subsequent 32 bytes.
176- periodDuration_ = uint256 (bytes32 (_terms[offset_ + 52 :offset_ + 84 ]));
177- // Get startDate from the final 32 bytes.
178- startDate_ = uint256 (bytes32 (_terms[offset_ + 84 :offset_ + 116 ]));
179- return (periodAmount_, periodDuration_, startDate_);
180- }
175+ uint256 numConfigs_ = termsLength_ / 116 ;
176+ require (_tokenIndex < numConfigs_, "MultiTokenPeriodEnforcer:invalid-token-index " );
181177
182- unchecked {
183- offset_ += 116 ;
184- }
185- }
186- revert ( " MultiTokenPeriodEnforcer:token-config-not-found " );
178+ uint256 offset_ = _tokenIndex * 116 ;
179+ token_ = address ( bytes20 (_terms[ offset_:offset_ + 20 ])) ;
180+ periodAmount_ = uint256 ( bytes32 (_terms[offset_ + 20 :offset_ + 52 ]));
181+ periodDuration_ = uint256 ( bytes32 (_terms[offset_ + 52 :offset_ + 84 ]));
182+ startDate_ = uint256 ( bytes32 (_terms[offset_ + 84 :offset_ + 116 ]) );
187183 }
188184
189185 /**
@@ -237,12 +233,14 @@ contract MultiTokenPeriodEnforcer is CaveatEnforcer {
237233 * - For ERC20 transfers (_token != address(0)): requires callData length to be 68 with a
238234 * valid IERC20.transfer selector, and zero value.
239235 * @param _terms The concatenated configurations.
236+ * @param _args A single uint256 value representing the index of the token configuration to use.
240237 * @param _executionCallData The encoded execution data.
241238 * @param _delegationHash The delegation hash.
242239 * @param _redeemer The address intended to receive the tokens/ETH.
243240 */
244241 function _validateAndConsumeTransfer (
245242 bytes calldata _terms ,
243+ bytes calldata _args ,
246244 bytes calldata _executionCallData ,
247245 bytes32 _delegationHash ,
248246 address _redeemer
@@ -251,28 +249,32 @@ contract MultiTokenPeriodEnforcer is CaveatEnforcer {
251249 {
252250 uint256 transferAmount_;
253251 address token_;
252+ {
253+ // Decode _executionCallData using decodeSingle.
254+ (address target_ , uint256 value_ , bytes calldata callData_ ) = _executionCallData.decodeSingle ();
254255
255- // Decode _executionCallData using decodeSingle.
256- (address target_ , uint256 value_ , bytes calldata callData_ ) = _executionCallData.decodeSingle ();
257-
258- if (callData_.length == 68 ) {
259- // ERC20 transfer.
260- require (value_ == 0 , "MultiTokenPeriodEnforcer:invalid-value-in-erc20-transfer " );
261- require (bytes4 (callData_[0 :4 ]) == IERC20 .transfer.selector , "MultiTokenPeriodEnforcer:invalid-method " );
262- token_ = target_;
263- transferAmount_ = uint256 (bytes32 (callData_[36 :68 ]));
264- } else if (callData_.length == 0 ) {
265- // Native transfer.
266- require (value_ > 0 , "MultiTokenPeriodEnforcer:invalid-zero-value-in-native-transfer " );
267- token_ = address (0 );
268- transferAmount_ = value_;
269- } else {
270- // If callData length is neither 68 nor 0, revert.
271- revert ("MultiTokenPeriodEnforcer:invalid-call-data-length " );
256+ if (callData_.length == 68 ) {
257+ // ERC20 transfer.
258+ require (value_ == 0 , "MultiTokenPeriodEnforcer:invalid-value-in-erc20-transfer " );
259+ require (bytes4 (callData_[0 :4 ]) == IERC20 .transfer.selector , "MultiTokenPeriodEnforcer:invalid-method " );
260+ token_ = target_;
261+ transferAmount_ = uint256 (bytes32 (callData_[36 :68 ]));
262+ } else if (callData_.length == 0 ) {
263+ // Native transfer.
264+ require (value_ > 0 , "MultiTokenPeriodEnforcer:invalid-zero-value-in-native-transfer " );
265+ token_ = address (0 );
266+ transferAmount_ = value_;
267+ } else {
268+ // If callData length is neither 68 nor 0, revert.
269+ revert ("MultiTokenPeriodEnforcer:invalid-call-data-length " );
270+ }
272271 }
272+ // Get the token configuration from the specified index
273+ (address configuredToken_ , uint256 periodAmount_ , uint256 periodDuration_ , uint256 startDate_ ) =
274+ getTermsInfo (_terms, abi.decode (_args, (uint256 )));
273275
274- // Retrieve the configuration for the token from _terms.
275- ( uint256 periodAmount_ , uint256 periodDuration_ , uint256 startDate_ ) = getTermsInfo (_terms, token_ );
276+ // Verify that the token in the execution matches the configured token
277+ require (token_ == configuredToken_, " MultiTokenPeriodEnforcer:token-mismatch " );
276278
277279 // Use the multi-token mapping.
278280 PeriodicAllowance storage allowance_ = periodicAllowances[msg .sender ][_delegationHash][token_];
0 commit comments