Skip to content

Commit fb16918

Browse files
committed
Audit April 2025 - 4.2 - Passing token config through index in the args
1 parent 0f8e128 commit fb16918

2 files changed

Lines changed: 162 additions & 145 deletions

File tree

src/enforcers/MultiTokenPeriodEnforcer.sol

Lines changed: 50 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)