@@ -42,6 +42,12 @@ import { IVedaTeller } from "./interfaces/IVedaTeller.sol";
4242 * Requirements:
4343 * - VedaAdapter must approve the BoringVault to spend deposit tokens
4444 *
45+ * Leaf Caveat Format:
46+ * - The first caveat of the leaf delegation (`_delegations[0].caveats[0]`) must follow the
47+ * ERC20TransferAmountEnforcer terms format: abi.encodePacked(address token, uint256 amount) (52 bytes).
48+ * The adapter parses token and amount directly from these terms instead of accepting them as
49+ * function inputs, ensuring the caller cannot supply values that differ from what the delegator authorised.
50+ *
4551 * @notice Security consideration: Anyone can call `depositByDelegation` and `withdrawByDelegation` — there is no
4652 * caller restriction. Security is enforced entirely through the delegation chain. The redelegation from the
4753 * operator to this adapter MUST include an `ERC20TransferAmountEnforcer` caveat capped to exactly the intended
@@ -60,8 +66,6 @@ contract VedaAdapter is Ownable2Step {
6066 */
6167 struct DepositParams {
6268 Delegation[] delegations;
63- address token;
64- uint256 amount;
6569 uint256 minimumMint;
6670 }
6771
@@ -71,7 +75,6 @@ contract VedaAdapter is Ownable2Step {
7175 struct WithdrawParams {
7276 Delegation[] delegations;
7377 address token;
74- uint256 shareAmount;
7578 uint256 minimumAssets;
7679 }
7780
@@ -180,20 +183,26 @@ contract VedaAdapter is Ownable2Step {
180183 * @dev Redeems the delegation to transfer tokens to this adapter, then calls deposit
181184 * on the Teller which mints vault shares directly to the original token owner.
182185 * Requires at least 2 delegations forming a chain from user to operator to this adapter.
186+ * The deposit token and amount are parsed from the first caveat of the leaf delegation
187+ * (`_delegations[0].caveats[0].terms`), which must follow the ERC20TransferAmountEnforcer
188+ * format: abi.encodePacked(address token, uint256 amount).
183189 * @param _delegations Array of Delegation objects, sorted leaf to root
184- * @param _token Address of the token to deposit
185- * @param _amount Amount of tokens to deposit
186- * @param _minimumMint Minimum vault shares the user expects to receive (slippage protection)
190+ * @param _minimumMint Minimum vault shares the caller expects to receive, used as a sanity-check
191+ * bound. The Veda vault conversion is always at fair value; rate drift from yield streaming
192+ * is negligible. A tolerance of 0.1-0.5% is recommended. If this check causes a revert,
193+ * no funds are lost — retry with a fresh quote.
187194 * @notice Security consideration: Callable by anyone. The redelegation passed in MUST include an
188- * `ERC20TransferAmountEnforcer` capped to exactly `_amount` to prevent over-spending or replay.
195+ * `ERC20TransferAmountEnforcer` capped to exactly the intended deposit amount to prevent
196+ * over-spending or replay.
189197 */
190- function depositByDelegation (Delegation[] memory _delegations , address _token , uint256 _amount , uint256 _minimumMint ) external {
191- _executeDepositByDelegation (_delegations, _token, _amount, _minimumMint, msg .sender );
198+ function depositByDelegation (Delegation[] memory _delegations , uint256 _minimumMint ) external {
199+ _executeDepositByDelegation (_delegations, _minimumMint, msg .sender );
192200 }
193201
194202 /**
195203 * @notice Deposits tokens using multiple delegation streams, executed sequentially
196- * @dev Each element is executed one after the other.
204+ * @dev Each element is executed one after the other. Token and amount for each stream are parsed
205+ * from the first caveat of each stream's leaf delegation.
197206 * @param _depositStreams Array of deposit parameters
198207 * @notice Security consideration: Callable by anyone. Each redelegation in the batch MUST include an
199208 * `ERC20TransferAmountEnforcer` capped to exactly the intended deposit amount to prevent over-spending or replay.
@@ -205,7 +214,7 @@ contract VedaAdapter is Ownable2Step {
205214 address caller_ = msg .sender ;
206215 for (uint256 i = 0 ; i < streamsLength_;) {
207216 DepositParams memory params_ = _depositStreams[i];
208- _executeDepositByDelegation (params_.delegations, params_.token, params_.amount, params_. minimumMint, caller_);
217+ _executeDepositByDelegation (params_.delegations, params_.minimumMint, caller_);
209218 unchecked {
210219 ++ i;
211220 }
@@ -219,27 +228,32 @@ contract VedaAdapter is Ownable2Step {
219228 * @dev Redeems the delegation to transfer vault shares to this adapter, then calls withdraw
220229 * on the Teller which burns shares and sends underlying assets directly to the original share owner.
221230 * Requires at least 2 delegations forming a chain from user to operator to this adapter.
231+ * The share amount is parsed from the first caveat of the leaf delegation
232+ * (`_delegations[0].caveats[0].terms`), which must follow the ERC20TransferAmountEnforcer
233+ * format: abi.encodePacked(address boringVault, uint256 shareAmount).
222234 * @param _delegations Array of Delegation objects, sorted leaf to root
223- * @param _token Address of the underlying token to receive
224- * @param _shareAmount Amount of vault shares to redeem
225- * @param _minimumAssets Minimum underlying assets the user expects to receive (slippage protection)
235+ * @param _token Address of the underlying token to receive from the vault
236+ * @param _minimumAssets Minimum underlying assets the caller expects to receive, used as a
237+ * sanity-check bound. The Veda vault conversion is always at fair value; rate drift from
238+ * yield streaming is negligible. A tolerance of 0.1-0.5% is recommended. If this check
239+ * causes a revert, no funds are lost — retry with a fresh quote.
226240 * @notice Security consideration: Callable by anyone. The redelegation passed in MUST include an
227241 * `ERC20TransferAmountEnforcer` capped to exactly `_shareAmount` to prevent over-spending or replay.
228242 */
229243 function withdrawByDelegation (
230244 Delegation[] memory _delegations ,
231245 address _token ,
232- uint256 _shareAmount ,
233246 uint256 _minimumAssets
234247 )
235248 external
236249 {
237- _executeWithdrawByDelegation (_delegations, _token, _shareAmount, _minimumAssets, msg .sender );
250+ _executeWithdrawByDelegation (_delegations, _token, _minimumAssets, msg .sender );
238251 }
239252
240253 /**
241254 * @notice Withdraws underlying tokens using multiple delegation streams, executed sequentially
242- * @dev Each element is executed one after the other.
255+ * @dev Each element is executed one after the other. The share amount for each stream is parsed
256+ * from the first caveat of each stream's leaf delegation.
243257 * @param _withdrawStreams Array of withdraw parameters
244258 * @notice Security consideration: Callable by anyone. Each redelegation in the batch MUST include an
245259 * `ERC20TransferAmountEnforcer` capped to exactly the intended share amount to prevent over-spending or replay.
@@ -251,7 +265,7 @@ contract VedaAdapter is Ownable2Step {
251265 address caller_ = msg .sender ;
252266 for (uint256 i = 0 ; i < streamsLength_;) {
253267 WithdrawParams memory params_ = _withdrawStreams[i];
254- _executeWithdrawByDelegation (params_.delegations, params_.token, params_.shareAmount, params_. minimumAssets, caller_);
268+ _executeWithdrawByDelegation (params_.delegations, params_.token, params_.minimumAssets, caller_);
255269 unchecked {
256270 ++ i;
257271 }
@@ -295,24 +309,37 @@ contract VedaAdapter is Ownable2Step {
295309
296310 /**
297311 * @notice Internal implementation of deposit by delegation
312+ * @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).
298315 * @param _delegations Delegation chain, sorted leaf to root
299- * @param _token Token to deposit
300- * @param _amount Amount to deposit
301- * @param _minimumMint Minimum vault shares expected
316+ * @param _minimumMint Minimum vault shares expected (sanity-check bound)
302317 * @param _caller Address of the caller, used only for event emission
303318 */
304319 function _executeDepositByDelegation (
305320 Delegation[] memory _delegations ,
306- address _token ,
307- uint256 _amount ,
308321 uint256 _minimumMint ,
309322 address _caller
310323 )
311324 internal
312325 {
313326 uint256 length_ = _delegations.length ;
314327 if (length_ < 2 ) revert InvalidDelegationsLength ();
315- if (_token == address (0 )) revert InvalidZeroAddress ();
328+
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+ }
316343
317344 address rootDelegator_ = _delegations[length_ - 1 ].delegator;
318345
@@ -324,30 +351,32 @@ contract VedaAdapter is Ownable2Step {
324351 encodedModes_[0 ] = ModeLib.encodeSimpleSingle ();
325352
326353 bytes [] memory executionCallDatas_ = new bytes [](1 );
327- bytes memory encodedTransfer_ = abi.encodeCall (IERC20 .transfer, (address (this ), _amount ));
328- executionCallDatas_[0 ] = ExecutionLib.encodeSingle (_token , 0 , encodedTransfer_);
354+ bytes memory encodedTransfer_ = abi.encodeCall (IERC20 .transfer, (address (this ), amount_ ));
355+ executionCallDatas_[0 ] = ExecutionLib.encodeSingle (token_ , 0 , encodedTransfer_);
329356
330357 delegationManager.redeemDelegations (permissionContexts_, encodedModes_, executionCallDatas_);
331358
332359 // Approve BoringVault to pull tokens, then deposit via Teller
333- _ensureAllowance (IERC20 (_token ), boringVault, _amount );
334- uint256 shares_ = teller.deposit (_token, _amount , _minimumMint, rootDelegator_, address (0 ));
360+ _ensureAllowance (IERC20 (token_ ), boringVault, amount_ );
361+ uint256 shares_ = teller.deposit (token_, amount_ , _minimumMint, rootDelegator_, address (0 ));
335362
336- emit DepositExecuted (rootDelegator_, _caller, _token, _amount , shares_);
363+ emit DepositExecuted (rootDelegator_, _caller, token_, amount_ , shares_);
337364 }
338365
339366 /**
340367 * @notice Internal implementation of withdraw by delegation
368+ * @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.
341372 * @param _delegations Delegation chain, sorted leaf to root
342- * @param _token Underlying token to receive
343- * @param _shareAmount Amount of vault shares to redeem
344- * @param _minimumAssets Minimum underlying assets expected
373+ * @param _token Underlying token to receive from the vault (not in the caveat; differs from the share token)
374+ * @param _minimumAssets Minimum underlying assets expected (sanity-check bound)
345375 * @param _caller Address of the caller, used only for event emission
346376 */
347377 function _executeWithdrawByDelegation (
348378 Delegation[] memory _delegations ,
349379 address _token ,
350- uint256 _shareAmount ,
351380 uint256 _minimumAssets ,
352381 address _caller
353382 )
@@ -357,6 +386,16 @@ contract VedaAdapter is Ownable2Step {
357386 if (length_ < 2 ) revert InvalidDelegationsLength ();
358387 if (_token == address (0 )) revert InvalidZeroAddress ();
359388
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+
360399 address rootDelegator_ = _delegations[length_ - 1 ].delegator;
361400
362401 // Redeem delegation: transfer vault shares from user to this adapter
@@ -367,14 +406,14 @@ contract VedaAdapter is Ownable2Step {
367406 encodedModes_[0 ] = ModeLib.encodeSimpleSingle ();
368407
369408 bytes [] memory executionCallDatas_ = new bytes [](1 );
370- bytes memory encodedTransfer_ = abi.encodeCall (IERC20 .transfer, (address (this ), _shareAmount ));
409+ bytes memory encodedTransfer_ = abi.encodeCall (IERC20 .transfer, (address (this ), shareAmount_ ));
371410 executionCallDatas_[0 ] = ExecutionLib.encodeSingle (boringVault, 0 , encodedTransfer_);
372411
373412 delegationManager.redeemDelegations (permissionContexts_, encodedModes_, executionCallDatas_);
374413
375414 // Withdraw from Teller: burns shares from this adapter, sends underlying to root delegator
376- uint256 assetsOut_ = teller.withdraw (_token, _shareAmount , _minimumAssets, rootDelegator_);
415+ uint256 assetsOut_ = teller.withdraw (_token, shareAmount_ , _minimumAssets, rootDelegator_);
377416
378- emit WithdrawExecuted (rootDelegator_, _caller, _token, _shareAmount , assetsOut_);
417+ emit WithdrawExecuted (rootDelegator_, _caller, _token, shareAmount_ , assetsOut_);
379418 }
380419}
0 commit comments