@@ -23,6 +23,8 @@ import { IVedaTeller } from "./interfaces/IVedaTeller.sol";
2323 * - Teller: The contract that orchestrates deposits/withdrawals. The adapter calls `teller.deposit()`
2424 * for deposits and `teller.withdraw()` for withdrawals (user-facing, no special
2525 * role needed).
26+ * - depositToken: The single ERC20 token used for both deposits and withdrawals. Fixed at construction;
27+ * deposits transfer this token into the vault, and withdrawals redeem vault shares back to this token.
2628 *
2729 * Delegation Flow:
2830 * 1. The user creates an initial delegation to an "operator" address (a DeleGator-upgraded account).
@@ -37,22 +39,22 @@ import { IVedaTeller } from "./interfaces/IVedaTeller.sol";
3739 * 3. For deposits: the adapter redeems the delegation chain, transfers tokens from the user to itself,
3840 * approves the BoringVault, and calls `teller.deposit()` to mint shares to the user.
3941 * For withdrawals: the adapter redeems the delegation chain, transfers vault shares from the user
40- * to itself, and calls `teller.withdraw()` to burn shares and send underlying assets to the user.
42+ * to itself, and calls `teller.withdraw()` to burn shares and send `depositToken` assets to the user.
4143 *
4244 * Requirements:
4345 * - VedaAdapter must approve the BoringVault to spend deposit tokens
4446 *
4547 * Leaf Caveat Format:
4648 * - The first caveat of the leaf delegation (`_delegations[0].caveats[0]`) must follow the
4749 * 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+ * The adapter parses only the amount from these terms; the token address encoded in bytes 0–19 is
51+ * consumed by the enforcer itself and is not read by this adapter .
5052 *
5153 * @notice Security consideration: Anyone can call `depositByDelegation` and `withdrawByDelegation` — there is no
5254 * caller restriction. Security is enforced entirely through the delegation chain. The redelegation from the
5355 * operator to this adapter MUST include an `ERC20TransferAmountEnforcer` caveat capped to exactly the intended
5456 * deposit or withdrawal amount, and it MUST be the first caveat (`caveats[0]`) of that redelegation — the
55- * adapter reads token and amount directly from `_delegations[0].caveats[0].terms`. Once that amount is
57+ * adapter reads the amount directly from `_delegations[0].caveats[0].terms`. Once that amount is
5658 * transferred the enforcer's running total is exhausted and any replay attempt will revert, making the
5759 * delegation effectively single-use. A delegation without this enforcer as the first caveat (or with an amount
5860 * larger than intended) could be exploited by any caller to transfer more tokens than authorised.
@@ -75,7 +77,6 @@ contract VedaAdapter is Ownable2Step {
7577 */
7678 struct WithdrawParams {
7779 Delegation[] delegations;
78- address token;
7980 uint256 minimumAssets;
8081 }
8182
@@ -97,7 +98,7 @@ contract VedaAdapter is Ownable2Step {
9798 * @notice Emitted when a withdrawal operation is executed via delegation
9899 * @param delegator Address of the share owner (delegator)
99100 * @param delegate Address of the executor (delegate)
100- * @param token Address of the underlying token withdrawn
101+ * @param token Address of the underlying token withdrawn (always `depositToken`)
101102 * @param shareAmount Amount of vault shares burned
102103 * @param assetsOut Amount of underlying tokens sent to the delegator
103104 */
@@ -161,33 +162,50 @@ contract VedaAdapter is Ownable2Step {
161162 */
162163 IVedaTeller public immutable teller;
163164
165+ /**
166+ * @notice The ERC20 token used for all deposits and withdrawals
167+ * @dev Fixed at construction. Deposits transfer this token into the vault; withdrawals redeem vault
168+ * shares back to this token.
169+ */
170+ IERC20 public immutable depositToken;
171+
164172 ////////////////////////////// Constructor //////////////////////////////
165173
166174 /**
167- * @notice Initializes the adapter with delegation manager, BoringVault, and Teller addresses
175+ * @notice Initializes the adapter with delegation manager, BoringVault, Teller, and deposit token addresses
168176 * @param _owner Address of the contract owner
169177 * @param _delegationManager Address of the delegation manager contract
170178 * @param _boringVault Address of the BoringVault (token approval target)
171179 * @param _teller Address of the Teller contract (deposit entry point)
180+ * @param _depositToken Address of the ERC20 token used for all deposits and withdrawals
172181 */
173- constructor (address _owner , address _delegationManager , address _boringVault , address _teller ) Ownable (_owner) {
174- if (_delegationManager == address (0 ) || _boringVault == address (0 ) || _teller == address (0 )) {
182+ constructor (
183+ address _owner ,
184+ address _delegationManager ,
185+ address _boringVault ,
186+ address _teller ,
187+ address _depositToken
188+ )
189+ Ownable (_owner)
190+ {
191+ if (_delegationManager == address (0 ) || _boringVault == address (0 ) || _teller == address (0 ) || _depositToken == address (0 )) {
175192 revert InvalidZeroAddress ();
176193 }
177194
178195 delegationManager = IDelegationManager (_delegationManager);
179196 boringVault = _boringVault;
180197 teller = IVedaTeller (_teller);
198+ depositToken = IERC20 (_depositToken);
181199 }
182200
183201 ////////////////////////////// External Methods //////////////////////////////
184202
185203 /**
186204 * @notice Deposits tokens into a Veda BoringVault using delegation-based token transfer
187- * @dev Redeems the delegation to transfer tokens to this adapter, then calls deposit
205+ * @dev Redeems the delegation to transfer `depositToken` from the user to this adapter, then calls deposit
188206 * on the Teller which mints vault shares directly to the original token owner.
189207 * Requires at least 2 delegations forming a chain from user to operator to this adapter.
190- * The deposit token and amount are parsed from the first caveat of the leaf delegation
208+ * The deposit amount is parsed from the first caveat of the leaf delegation
191209 * (`_delegations[0].caveats[0].terms`), which must follow the ERC20TransferAmountEnforcer
192210 * format: abi.encodePacked(address token, uint256 amount).
193211 * @param _delegations Array of Delegation objects, sorted leaf to root
@@ -205,7 +223,7 @@ contract VedaAdapter is Ownable2Step {
205223
206224 /**
207225 * @notice Deposits tokens using multiple delegation streams, executed sequentially
208- * @dev Each element is executed one after the other. Token and amount for each stream are parsed
226+ * @dev Each element is executed one after the other. The amount for each stream is parsed
209227 * from the first caveat of each stream's leaf delegation.
210228 * @param _depositStreams Array of deposit parameters
211229 * @notice Security consideration: Callable by anyone. Each redelegation in the batch MUST include an
@@ -228,29 +246,28 @@ contract VedaAdapter is Ownable2Step {
228246 }
229247
230248 /**
231- * @notice Withdraws underlying tokens from a Veda BoringVault using delegation-based share transfer
249+ * @notice Withdraws `depositToken` from a Veda BoringVault using delegation-based share transfer
232250 * @dev Redeems the delegation to transfer vault shares to this adapter, then calls withdraw
233- * on the Teller which burns shares and sends underlying assets directly to the original share owner.
251+ * on the Teller which burns shares and sends `depositToken` assets directly to the original share owner.
234252 * Requires at least 2 delegations forming a chain from user to operator to this adapter.
235253 * The share amount is parsed from the first caveat of the leaf delegation
236254 * (`_delegations[0].caveats[0].terms`), which must follow the ERC20TransferAmountEnforcer
237255 * format: abi.encodePacked(address boringVault, uint256 shareAmount).
238256 * @param _delegations Array of Delegation objects, sorted leaf to root
239- * @param _token Address of the underlying token to receive from the vault
240257 * @param _minimumAssets Minimum underlying assets the caller expects to receive, used as a
241258 * sanity-check bound. The Veda vault conversion is always at fair value; rate drift from
242259 * yield streaming is negligible. A tolerance of 0.1-0.5% is recommended. If this check
243260 * causes a revert, no funds are lost — retry with a fresh quote.
244261 * @notice Security consideration: Callable by anyone. The redelegation passed in MUST include an
245- * `ERC20TransferAmountEnforcer` as its first caveat (`caveats[0]`), capped to exactly `_shareAmount`,
246- * to prevent over-spending or replay.
262+ * `ERC20TransferAmountEnforcer` as its first caveat (`caveats[0]`), capped to exactly the intended
263+ * share amount, to prevent over-spending or replay.
247264 */
248- function withdrawByDelegation (Delegation[] calldata _delegations , address _token , uint256 _minimumAssets ) external {
249- _executeWithdrawByDelegation (_delegations, _token, _minimumAssets);
265+ function withdrawByDelegation (Delegation[] calldata _delegations , uint256 _minimumAssets ) external {
266+ _executeWithdrawByDelegation (_delegations, _minimumAssets);
250267 }
251268
252269 /**
253- * @notice Withdraws underlying tokens using multiple delegation streams, executed sequentially
270+ * @notice Withdraws `depositToken` using multiple delegation streams, executed sequentially
254271 * @dev Each element is executed one after the other. The share amount for each stream is parsed
255272 * from the first caveat of each stream's leaf delegation.
256273 * @param _withdrawStreams Array of withdraw parameters
@@ -264,7 +281,7 @@ contract VedaAdapter is Ownable2Step {
264281
265282 for (uint256 i = 0 ; i < streamsLength_;) {
266283 WithdrawParams calldata params_ = _withdrawStreams[i];
267- _executeWithdrawByDelegation (params_.delegations, params_.token, params_. minimumAssets);
284+ _executeWithdrawByDelegation (params_.delegations, params_.minimumAssets);
268285 unchecked {
269286 ++ i;
270287 }
@@ -307,30 +324,30 @@ contract VedaAdapter is Ownable2Step {
307324 }
308325
309326 /**
310- * @notice Parses ERC20TransferAmountEnforcer terms from calldata bytes
327+ * @notice Parses the transfer amount from ERC20TransferAmountEnforcer terms
311328 * @dev Terms format: abi.encodePacked(address token, uint256 amount) = 52 bytes.
329+ * The token address (bytes 0–19) is validated by the enforcer itself and is not read here.
330+ * Only the amount (bytes 20–51) is returned.
312331 * @param _terms The raw terms bytes from a caveat
313- * @return token_ The token address encoded in the first 20 bytes
314332 * @return amount_ The uint256 amount encoded in bytes 20-51
315333 */
316- function _parseERC20TransferTerms (bytes calldata _terms ) private pure returns (address token_ , uint256 amount_ ) {
334+ function _parseERC20TransferTerms (bytes calldata _terms ) private pure returns (uint256 amount_ ) {
317335 if (_terms.length < 52 ) revert InvalidTermsLength ();
318- token_ = address (bytes20 (_terms[0 :20 ]));
319336 amount_ = uint256 (bytes32 (_terms[20 :52 ]));
320337 }
321338
322339 /**
323340 * @notice Internal implementation of deposit by delegation
324- * @dev Parses the deposit token and amount from the first caveat of the leaf delegation
325- * via `_parseERC20TransferTerms`.
341+ * @dev Parses the deposit amount from the first caveat of the leaf delegation
342+ * via `_parseERC20TransferTerms`. Uses `depositToken` as the transfer token.
326343 * @param _delegations Delegation chain, sorted leaf to root
327344 * @param _minimumMint Minimum vault shares expected (sanity-check bound)
328345 */
329346 function _executeDepositByDelegation (Delegation[] calldata _delegations , uint256 _minimumMint ) internal {
330347 uint256 length_ = _delegations.length ;
331348 if (length_ < 2 ) revert InvalidDelegationsLength ();
332349
333- ( address token_ , uint256 amount_ ) = _parseERC20TransferTerms (_delegations[0 ].caveats[0 ].terms);
350+ uint256 amount_ = _parseERC20TransferTerms (_delegations[0 ].caveats[0 ].terms);
334351 address rootDelegator_ = _delegations[length_ - 1 ].delegator;
335352
336353 // Redeem delegation: transfer tokens from user to this adapter
@@ -341,33 +358,30 @@ contract VedaAdapter is Ownable2Step {
341358 encodedModes_[0 ] = ModeLib.encodeSimpleSingle ();
342359
343360 bytes [] memory executionCallDatas_ = new bytes [](1 );
344- executionCallDatas_[0 ] = ExecutionLib.encodeSingle (token_, 0 , abi.encodeCall (IERC20 .transfer, (address (this ), amount_)));
361+ executionCallDatas_[0 ] =
362+ ExecutionLib.encodeSingle (address (depositToken), 0 , abi.encodeCall (IERC20 .transfer, (address (this ), amount_)));
345363
346364 delegationManager.redeemDelegations (permissionContexts_, encodedModes_, executionCallDatas_);
347365
348366 // Approve BoringVault to pull tokens, then deposit via Teller
349- _ensureAllowance (IERC20 (token_) , boringVault, amount_);
350- uint256 shares_ = teller.deposit (token_ , amount_, _minimumMint, rootDelegator_, address (0 ));
367+ _ensureAllowance (depositToken , boringVault, amount_);
368+ uint256 shares_ = teller.deposit (address (depositToken) , amount_, _minimumMint, rootDelegator_, address (0 ));
351369
352- emit DepositExecuted (rootDelegator_, msg .sender , token_ , amount_, shares_);
370+ emit DepositExecuted (rootDelegator_, msg .sender , address (depositToken) , amount_, shares_);
353371 }
354372
355373 /**
356374 * @notice Internal implementation of withdraw by delegation
357375 * @dev Parses the share amount from the first caveat of the leaf delegation
358- * via `_parseERC20TransferTerms`. The caveat encodes the vault share token and amount;
359- * `_token` is the desired underlying output asset (e.g. USDC), which differs from the
360- * share token in the caveat.
376+ * via `_parseERC20TransferTerms`. Redeems vault shares and sends `depositToken` to the root delegator.
361377 * @param _delegations Delegation chain, sorted leaf to root
362- * @param _token Underlying output token to receive from the vault (differs from the share token in the caveat)
363378 * @param _minimumAssets Minimum underlying assets expected (sanity-check bound)
364379 */
365- function _executeWithdrawByDelegation (Delegation[] calldata _delegations , address _token , uint256 _minimumAssets ) internal {
380+ function _executeWithdrawByDelegation (Delegation[] calldata _delegations , uint256 _minimumAssets ) internal {
366381 uint256 length_ = _delegations.length ;
367382 if (length_ < 2 ) revert InvalidDelegationsLength ();
368- if (_token == address (0 )) revert InvalidZeroAddress ();
369383
370- (, uint256 shareAmount_ ) = _parseERC20TransferTerms (_delegations[0 ].caveats[0 ].terms);
384+ uint256 shareAmount_ = _parseERC20TransferTerms (_delegations[0 ].caveats[0 ].terms);
371385 address rootDelegator_ = _delegations[length_ - 1 ].delegator;
372386
373387 // Redeem delegation: transfer vault shares from user to this adapter
@@ -383,9 +397,9 @@ contract VedaAdapter is Ownable2Step {
383397
384398 delegationManager.redeemDelegations (permissionContexts_, encodedModes_, executionCallDatas_);
385399
386- // Withdraw from Teller: burns shares from this adapter, sends underlying to root delegator
387- uint256 assetsOut_ = teller.withdraw (_token , shareAmount_, _minimumAssets, rootDelegator_);
400+ // Withdraw from Teller: burns shares from this adapter, sends depositToken to root delegator
401+ uint256 assetsOut_ = teller.withdraw (address (depositToken) , shareAmount_, _minimumAssets, rootDelegator_);
388402
389- emit WithdrawExecuted (rootDelegator_, msg .sender , _token , shareAmount_, assetsOut_);
403+ emit WithdrawExecuted (rootDelegator_, msg .sender , address (depositToken) , shareAmount_, assetsOut_);
390404 }
391405}
0 commit comments