@@ -6,6 +6,7 @@ import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
66import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol " ;
77import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol " ;
88import { ExecutionLib } from "@erc7579/lib/ExecutionLib.sol " ;
9+ import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol " ;
910
1011import { BasicERC20 } from "../utils/BasicERC20.t.sol " ;
1112import { BaseTest } from "../utils/BaseTest.t.sol " ;
@@ -477,6 +478,37 @@ contract DelegationMetaSwapAdapterMockTest is DelegationMetaSwapAdapterBaseTest
477478 delegationMetaSwapAdapter.swapByDelegation (sigData_, delegations_, false );
478479 }
479480
481+ /**
482+ * @notice Tests that the swapByDelegation function is protected against reentrancy attacks
483+ */
484+ function test_revertOnReentrancy () public {
485+ // Set up mock contracts
486+ _setUpMockContracts ();
487+
488+ // Create malicious token that will attempt reentrancy
489+ ReentrantToken maliciousToken_ = new ReentrantToken (address (delegationMetaSwapAdapter));
490+ tokenA = BasicERC20 (address (maliciousToken_));
491+
492+ _updateAllowedTokens ();
493+
494+ // Build the delegation chain
495+ Delegation[] memory delegations_ = new Delegation [](2 );
496+ Delegation memory vaultDelegation_ = _getVaultDelegation ();
497+ Delegation memory subVaultDelegation_ = _getSubVaultDelegation (EncoderLib._getDelegationHash (vaultDelegation_));
498+ delegations_[1 ] = vaultDelegation_;
499+ delegations_[0 ] = subVaultDelegation_;
500+
501+ // Prepare swap data with malicious token
502+ bytes memory swapData_ = _encodeSwapData (IERC20 (tokenA), IERC20 (tokenB), amountFrom, amountTo, hex "" , 0 , address (0 ), true );
503+ bytes memory apiData_ = _encodeApiData (aggregatorId, IERC20 (tokenA), amountFrom, swapData_);
504+ DelegationMetaSwapAdapter.SignatureData memory sigData_ = _buildSigData (apiData_);
505+
506+ // Attempt reentrancy attack
507+ vm.prank (address (subVault.deleGator));
508+ vm.expectRevert (ReentrancyGuard.ReentrancyGuardReentrantCall.selector );
509+ delegationMetaSwapAdapter.swapByDelegation (sigData_, delegations_, true );
510+ }
511+
480512 /// @notice Verifies that only the current owner can initiate ownership transfer.
481513 function test_revert_transferOwnership_ifNotOwner () public {
482514 _setUpMockContracts ();
@@ -1444,3 +1476,20 @@ contract MetaSwapMock {
14441476 }
14451477 }
14461478}
1479+
1480+ // Helper contract that attempts reentrancy
1481+ contract ReentrantToken is ERC20 {
1482+ address public immutable delegationMetaSwapAdapter;
1483+
1484+ constructor (address _delegationMetaSwapAdapter ) ERC20 ("Malicious " , "MAL " ) {
1485+ delegationMetaSwapAdapter = _delegationMetaSwapAdapter;
1486+ }
1487+
1488+ function transfer (address , uint256 ) public override returns (bool ) {
1489+ // Attempt to reenter swapByDelegation
1490+ DelegationMetaSwapAdapter (payable (delegationMetaSwapAdapter)).swapByDelegation (
1491+ DelegationMetaSwapAdapter.SignatureData ({ signature: hex "" , expiration: 0 , apiData: hex "" }), new Delegation [](0 ), true
1492+ );
1493+ return true ;
1494+ }
1495+ }
0 commit comments