Skip to content

Commit c022400

Browse files
committed
optimize inputs and add docs regarding senity check inputs
1 parent 7b3fb1d commit c022400

File tree

2 files changed

+93
-114
lines changed

2 files changed

+93
-114
lines changed

src/helpers/VedaAdapter.sol

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

Comments
 (0)