Skip to content

Commit e1f7c63

Browse files
committed
Audit April 2025 - 4.8 - Added Reentrancy Guard to SwapByDelegation
1 parent 0f8e128 commit e1f7c63

2 files changed

Lines changed: 53 additions & 2 deletions

File tree

src/helpers/DelegationMetaSwapAdapter.sol

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { MessageHashUtils } from "@openzeppelin/contracts/utils/cryptography/Mes
66
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
77
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
88
import { Ownable2Step, Ownable } from "@openzeppelin/contracts/access/Ownable2Step.sol";
9+
import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
910
import { ModeLib } from "@erc7579/lib/ModeLib.sol";
1011
import { ExecutionLib } from "@erc7579/lib/ExecutionLib.sol";
1112
import { ExecutionHelper } from "@erc7579/core/ExecutionHelper.sol";
@@ -27,7 +28,7 @@ import { CALLTYPE_SINGLE, EXECTYPE_DEFAULT } from "../utils/Constants.sol";
2728
* signature that incorporates an expiration timestamp. The signature is verified during swap execution to ensure
2829
* that it is still valid.
2930
*/
30-
contract DelegationMetaSwapAdapter is ExecutionHelper, Ownable2Step {
31+
contract DelegationMetaSwapAdapter is ExecutionHelper, Ownable2Step, ReentrancyGuard {
3132
using ModeLib for ModeCode;
3233
using ExecutionLib for bytes;
3334
using SafeERC20 for IERC20;
@@ -218,6 +219,7 @@ contract DelegationMetaSwapAdapter is ExecutionHelper, Ownable2Step {
218219
bool _useTokenWhitelist
219220
)
220221
external
222+
nonReentrant
221223
{
222224
_validateSignature(_signatureData);
223225

@@ -274,7 +276,7 @@ contract DelegationMetaSwapAdapter is ExecutionHelper, Ownable2Step {
274276
* @param _tokenTo The output token of the swap.
275277
* @param _recipient The address that will receive the swapped tokens.
276278
* @param _amountFrom The amount of tokens to be swapped.
277-
* @param _balanceFromBefore The contracts balance of _tokenFrom before the incoming token transfer is credited.
279+
* @param _balanceFromBefore The contract's balance of _tokenFrom before the incoming token transfer is credited.
278280
* @param _swapData Arbitrary data required by the aggregator (e.g. encoded swap params).
279281
*/
280282
function swapTokens(

test/helpers/DelegationMetaSwapAdapter.t.sol

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
66
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
77
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
88
import { ExecutionLib } from "@erc7579/lib/ExecutionLib.sol";
9+
import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
910

1011
import { BasicERC20 } from "../utils/BasicERC20.t.sol";
1112
import { 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

Comments
 (0)