Skip to content

Commit aba8aa5

Browse files
committed
fixate veda adapter deposit / withdrawal token in constructor
1 parent 231e1de commit aba8aa5

4 files changed

Lines changed: 87 additions & 79 deletions

File tree

.env.example

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@ METASWAP_ADDRESS=
99
SWAPS_API_SIGNER_ADDRESS=
1010
ARGS_EQUALITY_CHECK_ENFORCER_ADDRESS=
1111
VEDA_ADAPTER_OWNER_ADDRESS=
12-
BORING_VAULT_ADDRESS=
12+
VEDA_BORING_VAULT_ADDRESS=
1313
VEDA_TELLER_ADDRESS=
14+
VEDA_DEPOSIT_TOKEN_ADDRESS=
1415

1516
# Required for verifying contracts
1617
ETHERSCAN_API_KEY=

script/DeployVedaAdapter.s.sol

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,22 @@ contract DeployVedaAdapter is Script {
2020
address delegationManager;
2121
address boringVault;
2222
address vedaTeller;
23+
address depositToken;
2324

2425
function setUp() public {
2526
salt = bytes32(abi.encodePacked(vm.envString("SALT")));
2627
vedaAdapterOwner = vm.envAddress("VEDA_ADAPTER_OWNER_ADDRESS");
2728
delegationManager = vm.envAddress("DELEGATION_MANAGER_ADDRESS");
28-
boringVault = vm.envAddress("BORING_VAULT_ADDRESS");
29+
boringVault = vm.envAddress("VEDA_BORING_VAULT_ADDRESS");
2930
vedaTeller = vm.envAddress("VEDA_TELLER_ADDRESS");
31+
depositToken = vm.envAddress("VEDA_DEPOSIT_TOKEN_ADDRESS");
3032
deployer = msg.sender;
3133
console2.log("~~~");
3234
console2.log("Owner: %s", vedaAdapterOwner);
3335
console2.log("DelegationManager: %s", delegationManager);
3436
console2.log("BoringVault: %s", boringVault);
3537
console2.log("VedaTeller: %s", vedaTeller);
38+
console2.log("DepositToken: %s", depositToken);
3639
console2.log("Deployer: %s", deployer);
3740
console2.log("Salt:");
3841
console2.logBytes32(salt);
@@ -42,7 +45,8 @@ contract DeployVedaAdapter is Script {
4245
console2.log("~~~");
4346
vm.startBroadcast();
4447

45-
address vedaAdapter = address(new VedaAdapter{ salt: salt }(vedaAdapterOwner, delegationManager, boringVault, vedaTeller));
48+
address vedaAdapter =
49+
address(new VedaAdapter{ salt: salt }(vedaAdapterOwner, delegationManager, boringVault, vedaTeller, depositToken));
4650
console2.log("VedaAdapter: %s", vedaAdapter);
4751

4852
vm.stopBroadcast();

src/helpers/VedaAdapter.sol

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

Comments
 (0)