Skip to content

Commit 7b3fb1d

Browse files
committed
Remove NotLeafDelegator check and add security consideration
1 parent 25a8c19 commit 7b3fb1d

File tree

2 files changed

+20
-48
lines changed

2 files changed

+20
-48
lines changed

src/helpers/VedaAdapter.sol

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,14 @@ import { IVedaTeller } from "./interfaces/IVedaTeller.sol";
4141
*
4242
* Requirements:
4343
* - VedaAdapter must approve the BoringVault to spend deposit tokens
44+
*
45+
* @notice Security consideration: Anyone can call `depositByDelegation` and `withdrawByDelegation` — there is no
46+
* caller restriction. Security is enforced entirely through the delegation chain. The redelegation from the
47+
* operator to this adapter MUST include an `ERC20TransferAmountEnforcer` caveat capped to exactly the intended
48+
* deposit or withdrawal amount. Once that amount is transferred the enforcer's running total is exhausted and
49+
* any replay attempt will revert, making the delegation effectively single-use. A delegation without this
50+
* enforcer (or with an amount larger than intended) could be exploited by any caller to transfer more tokens
51+
* than authorised.
4452
*/
4553
contract VedaAdapter is Ownable2Step {
4654
using SafeERC20 for IERC20;
@@ -129,9 +137,6 @@ contract VedaAdapter is Ownable2Step {
129137
/// @dev Thrown when the batch array is empty
130138
error InvalidBatchLength();
131139

132-
/// @dev Thrown when msg.sender is not the leaf delegator
133-
error NotLeafDelegator();
134-
135140
////////////////////////////// State //////////////////////////////
136141

137142
/**
@@ -179,16 +184,19 @@ contract VedaAdapter is Ownable2Step {
179184
* @param _token Address of the token to deposit
180185
* @param _amount Amount of tokens to deposit
181186
* @param _minimumMint Minimum vault shares the user expects to receive (slippage protection)
187+
* @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.
182189
*/
183190
function depositByDelegation(Delegation[] memory _delegations, address _token, uint256 _amount, uint256 _minimumMint) external {
184191
_executeDepositByDelegation(_delegations, _token, _amount, _minimumMint, msg.sender);
185192
}
186193

187194
/**
188195
* @notice Deposits tokens using multiple delegation streams, executed sequentially
189-
* @dev Each element is executed one after the other. The caller must be the delegator
190-
* (first delegate in the chain) for each stream.
196+
* @dev Each element is executed one after the other.
191197
* @param _depositStreams Array of deposit parameters
198+
* @notice Security consideration: Callable by anyone. Each redelegation in the batch MUST include an
199+
* `ERC20TransferAmountEnforcer` capped to exactly the intended deposit amount to prevent over-spending or replay.
192200
*/
193201
function depositByDelegationBatch(DepositParams[] memory _depositStreams) external {
194202
uint256 streamsLength_ = _depositStreams.length;
@@ -215,6 +223,8 @@ contract VedaAdapter is Ownable2Step {
215223
* @param _token Address of the underlying token to receive
216224
* @param _shareAmount Amount of vault shares to redeem
217225
* @param _minimumAssets Minimum underlying assets the user expects to receive (slippage protection)
226+
* @notice Security consideration: Callable by anyone. The redelegation passed in MUST include an
227+
* `ERC20TransferAmountEnforcer` capped to exactly `_shareAmount` to prevent over-spending or replay.
218228
*/
219229
function withdrawByDelegation(
220230
Delegation[] memory _delegations,
@@ -229,9 +239,10 @@ contract VedaAdapter is Ownable2Step {
229239

230240
/**
231241
* @notice Withdraws underlying tokens using multiple delegation streams, executed sequentially
232-
* @dev Each element is executed one after the other. The caller must be the delegator
233-
* (first delegate in the chain) for each stream.
242+
* @dev Each element is executed one after the other.
234243
* @param _withdrawStreams Array of withdraw parameters
244+
* @notice Security consideration: Callable by anyone. Each redelegation in the batch MUST include an
245+
* `ERC20TransferAmountEnforcer` capped to exactly the intended share amount to prevent over-spending or replay.
235246
*/
236247
function withdrawByDelegationBatch(WithdrawParams[] memory _withdrawStreams) external {
237248
uint256 streamsLength_ = _withdrawStreams.length;
@@ -288,7 +299,7 @@ contract VedaAdapter is Ownable2Step {
288299
* @param _token Token to deposit
289300
* @param _amount Amount to deposit
290301
* @param _minimumMint Minimum vault shares expected
291-
* @param _caller Authorized caller (must match leaf delegator)
302+
* @param _caller Address of the caller, used only for event emission
292303
*/
293304
function _executeDepositByDelegation(
294305
Delegation[] memory _delegations,
@@ -301,7 +312,6 @@ contract VedaAdapter is Ownable2Step {
301312
{
302313
uint256 length_ = _delegations.length;
303314
if (length_ < 2) revert InvalidDelegationsLength();
304-
if (_delegations[0].delegator != _caller) revert NotLeafDelegator();
305315
if (_token == address(0)) revert InvalidZeroAddress();
306316

307317
address rootDelegator_ = _delegations[length_ - 1].delegator;
@@ -332,7 +342,7 @@ contract VedaAdapter is Ownable2Step {
332342
* @param _token Underlying token to receive
333343
* @param _shareAmount Amount of vault shares to redeem
334344
* @param _minimumAssets Minimum underlying assets expected
335-
* @param _caller Authorized caller (must match leaf delegator)
345+
* @param _caller Address of the caller, used only for event emission
336346
*/
337347
function _executeWithdrawByDelegation(
338348
Delegation[] memory _delegations,
@@ -345,7 +355,6 @@ contract VedaAdapter is Ownable2Step {
345355
{
346356
uint256 length_ = _delegations.length;
347357
if (length_ < 2) revert InvalidDelegationsLength();
348-
if (_delegations[0].delegator != _caller) revert NotLeafDelegator();
349358
if (_token == address(0)) revert InvalidZeroAddress();
350359

351360
address rootDelegator_ = _delegations[length_ - 1].delegator;

test/helpers/VedaLending.t.sol

Lines changed: 0 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -294,23 +294,6 @@ contract VedaLendingTest is BaseTest {
294294
vedaAdapter.depositByDelegation(delegations_, address(USDC), DEPOSIT_AMOUNT, 0);
295295
}
296296

297-
/// @notice depositByDelegation must revert when msg.sender does not match delegations[0].delegator
298-
function test_depositByDelegation_revertsOnUnauthorizedCaller() public {
299-
Delegation memory delegation_ =
300-
_createTransferDelegation(address(users.bob.deleGator), address(vedaAdapter), address(USDC), type(uint256).max);
301-
Delegation memory redelegation_ =
302-
_createAdapterRedelegation(EncoderLib._getDelegationHash(delegation_), address(USDC), DEPOSIT_AMOUNT);
303-
304-
Delegation[] memory delegations_ = new Delegation[](2);
305-
delegations_[0] = redelegation_;
306-
delegations_[1] = delegation_;
307-
308-
// Alice tries to call but Bob is delegations[0].delegator
309-
vm.expectRevert(VedaAdapter.NotLeafDelegator.selector);
310-
vm.prank(address(users.alice.deleGator));
311-
vedaAdapter.depositByDelegation(delegations_, address(USDC), DEPOSIT_AMOUNT, 0);
312-
}
313-
314297
/// @notice depositByDelegation must revert when token address is zero
315298
function test_depositByDelegation_revertsOnZeroTokenAddress() public {
316299
Delegation memory delegation_ =
@@ -377,26 +360,6 @@ contract VedaLendingTest is BaseTest {
377360
vedaAdapter.withdrawByDelegation(delegations_, address(USDC), DEPOSIT_AMOUNT, 0);
378361
}
379362

380-
/// @notice withdrawByDelegation must revert when msg.sender does not match delegations[0].delegator
381-
function test_withdrawByDelegation_revertsOnUnauthorizedCaller() public {
382-
_setupLendingState();
383-
384-
Delegation memory delegation_ = _createTransferDelegation(
385-
address(users.bob.deleGator), address(vedaAdapter), address(BORING_VAULT), type(uint256).max
386-
);
387-
Delegation memory redelegation_ =
388-
_createAdapterRedelegation(EncoderLib._getDelegationHash(delegation_), address(BORING_VAULT), DEPOSIT_AMOUNT);
389-
390-
Delegation[] memory delegations_ = new Delegation[](2);
391-
delegations_[0] = redelegation_;
392-
delegations_[1] = delegation_;
393-
394-
// Alice tries to call but Bob is delegations[0].delegator
395-
vm.expectRevert(VedaAdapter.NotLeafDelegator.selector);
396-
vm.prank(address(users.alice.deleGator));
397-
vedaAdapter.withdrawByDelegation(delegations_, address(USDC), DEPOSIT_AMOUNT, 0);
398-
}
399-
400363
/// @notice withdrawByDelegation must revert when token address is zero
401364
function test_withdrawByDelegation_revertsOnZeroTokenAddress() public {
402365
_setupLendingState();

0 commit comments

Comments
 (0)