@@ -140,6 +140,9 @@ contract VedaAdapter is Ownable2Step {
140140 /// @dev Thrown when the batch array is empty
141141 error InvalidBatchLength ();
142142
143+ /// @dev Thrown when the leaf caveat terms are shorter than 52 bytes (ERC20TransferAmountEnforcer format)
144+ error InvalidTermsLength ();
145+
143146 ////////////////////////////// State //////////////////////////////
144147
145148 /**
@@ -307,11 +310,26 @@ contract VedaAdapter is Ownable2Step {
307310 }
308311 }
309312
313+ /**
314+ * @notice Parses ERC20TransferAmountEnforcer terms from memory bytes
315+ * @dev Terms format: abi.encodePacked(address token, uint256 amount) = 52 bytes.
316+ * Slice syntax is only available for calldata; assembly is used to read from memory bytes.
317+ * @param _terms The raw terms bytes from a caveat
318+ * @return token_ The token address encoded in the first 20 bytes
319+ * @return amount_ The uint256 amount encoded in bytes 20-51
320+ */
321+ function _parseERC20TransferTerms (bytes memory _terms ) private pure returns (address token_ , uint256 amount_ ) {
322+ if (_terms.length < 52 ) revert InvalidTermsLength ();
323+ assembly {
324+ token_ := shr (96 , mload (add (_terms, 32 )))
325+ amount_ := mload (add (_terms, 52 ))
326+ }
327+ }
328+
310329 /**
311330 * @notice Internal implementation of deposit by delegation
312331 * @dev Parses the deposit token and amount from the first caveat of the leaf delegation
313- * (`_delegations[0].caveats[0].terms`), which must be 52 bytes in
314- * ERC20TransferAmountEnforcer format: abi.encodePacked(address token, uint256 amount).
332+ * via `_parseERC20TransferTerms`.
315333 * @param _delegations Delegation chain, sorted leaf to root
316334 * @param _minimumMint Minimum vault shares expected (sanity-check bound)
317335 * @param _caller Address of the caller, used only for event emission
@@ -326,21 +344,7 @@ contract VedaAdapter is Ownable2Step {
326344 uint256 length_ = _delegations.length ;
327345 if (length_ < 2 ) revert InvalidDelegationsLength ();
328346
329- // Parse token and amount from the leaf delegation's first caveat terms.
330- // Terms format (ERC20TransferAmountEnforcer): abi.encodePacked(address token, uint256 amount) = 52 bytes.
331- // Slice syntax is only available for calldata; use assembly to read from memory bytes.
332- bytes memory terms_ = _delegations[0 ].caveats[0 ].terms;
333- address token_;
334- uint256 amount_;
335- assembly {
336- // Memory layout of `terms_`: [length (32 bytes)][data ...].
337- // `add(terms_, 32)` points to byte 0 of the data.
338- // The address occupies bytes 0-19 (high 20 bytes of the first 32-byte word).
339- token_ := shr (96 , mload (add (terms_, 32 )))
340- // The uint256 occupies bytes 20-51; load the 32-byte word starting at byte 20.
341- amount_ := mload (add (terms_, 52 ))
342- }
343-
347+ (address token_ , uint256 amount_ ) = _parseERC20TransferTerms (_delegations[0 ].caveats[0 ].terms);
344348 address rootDelegator_ = _delegations[length_ - 1 ].delegator;
345349
346350 // Redeem delegation: transfer tokens from user to this adapter
@@ -366,11 +370,11 @@ contract VedaAdapter is Ownable2Step {
366370 /**
367371 * @notice Internal implementation of withdraw by delegation
368372 * @dev Parses the share amount from the first caveat of the leaf delegation
369- * (`_delegations[0].caveats[0].terms`), which must be 52 bytes in
370- * ERC20TransferAmountEnforcer format: abi.encodePacked(address boringVault, uint256 shareAmount).
371- * The transfer target is always the immutable `boringVault` address .
373+ * via `_parseERC20TransferTerms`. The caveat encodes the vault share token and amount;
374+ * `_token` is the desired underlying output asset (e.g. USDC), which differs from the
375+ * share token in the caveat .
372376 * @param _delegations Delegation chain, sorted leaf to root
373- * @param _token Underlying token to receive from the vault (not in the caveat; differs from the share token)
377+ * @param _token Underlying output token to receive from the vault (differs from the share token in the caveat )
374378 * @param _minimumAssets Minimum underlying assets expected (sanity-check bound)
375379 * @param _caller Address of the caller, used only for event emission
376380 */
@@ -386,16 +390,7 @@ contract VedaAdapter is Ownable2Step {
386390 if (length_ < 2 ) revert InvalidDelegationsLength ();
387391 if (_token == address (0 )) revert InvalidZeroAddress ();
388392
389- // Parse share amount from the leaf delegation's first caveat terms.
390- // Terms format (ERC20TransferAmountEnforcer): abi.encodePacked(address boringVault, uint256 shareAmount) = 52 bytes.
391- // Slice syntax is only available for calldata; use assembly to read from memory bytes.
392- bytes memory terms_ = _delegations[0 ].caveats[0 ].terms;
393- uint256 shareAmount_;
394- assembly {
395- // The uint256 shareAmount occupies bytes 20-51; load the 32-byte word starting at byte 20.
396- shareAmount_ := mload (add (terms_, 52 ))
397- }
398-
393+ (, uint256 shareAmount_ ) = _parseERC20TransferTerms (_delegations[0 ].caveats[0 ].terms);
399394 address rootDelegator_ = _delegations[length_ - 1 ].delegator;
400395
401396 // Redeem delegation: transfer vault shares from user to this adapter
0 commit comments