DeFiFoundrySolidity
16,653 OP
View results
Submission Details
Severity: high
Invalid

Concurrent reentrancy-like risk in `claimAndSwap`

Summary

The claimAndSwap function can be invoked by multiple Keepers concurrently. Because there is no protection against simultaneous calls, two or more Keepers can attempt to claim and then swap tokens at nearly the same time. This situation can lead to a race condition and inconsistent state updates, similar to a reentrancy vulnerability. While not a classical reentrancy (no direct external call re-entering the same function), the lack of mutual exclusion or synchronization can still cause unintended behaviors, such as incorrect balances or missed/slippage-failing transactions.

Vulnerability Details

Location:

https://github.com/Cyfrin/2024-12-alchemix/blob/82798f4891e41959eef866bd1d4cb44fc1e26439/src/StrategyArb.sol#L71

https://github.com/Cyfrin/2024-12-alchemix/blob/82798f4891e41959eef866bd1d4cb44fc1e26439/src/StrategyMainnet.sol#L92

https://github.com/Cyfrin/2024-12-alchemix/blob/82798f4891e41959eef866bd1d4cb44fc1e26439/src/StrategyOp.sol#L79

One of the claimAndSwap function:

function claimAndSwap(uint256 _amountClaim, uint256 _minOut, IRamsesRouter.route[] calldata _path)
external
{
transmuter.claim(_amountClaim, address(this));
uint256 balBefore = asset.balanceOf(address(this));
_swapUnderlyingToAsset(_amountClaim, _minOut, _path);
uint256 balAfter = asset.balanceOf(address(this));
require((balAfter - balBefore) >= _minOut, "Slippage too high");
transmuter.deposit(asset.balanceOf(address(this)), address(this));
}

The claimAndSwap function allows multiple Keepers (by calling transmuter.claim(...) followed by _swapUnderlyingToAsset(...) and finally transmuter.deposit(...)) to run concurrently. If two transactions are mined in the same block or rapid succession, could lead to:

  • Concurrent Claims: Each call tries to claim _amountClaim from the Transmuter contract, potentially exceeding the Transmuter’s available WETH balance.

  • Price & Slippage Inconsistency: One transaction can alter on-chain liquidity (via the router) just before another transaction swaps, causing the second transaction’s _minOut check to revert or pass incorrectly due to rapidly changing pool states.

  • Logic Mismatch: If one Keeper claims and deposits back to the Transmuter while another call is still in progress, the balBefore and balAfter calculations can become misleading, failing to correctly enforce the intended slippage or premium requirement.

Impact

  • Inconsistent States & Accounting: Multiple overlapping calls can yield these variables that deviate from reality, particularly if the Transmuter’s available WETH gets split among concurrent claims or partially satisfies them.

  • Failed Arbitrage or Reverts: When one Keeper’s swap significantly shifts the price, another Keeper’s _minOut check may revert unnecessarily or succeed under unexpected conditions.

  • Loss of Funds: Incorrectly handling user funds under race conditions while can't always ensure swapping at a premium(other vulnerable in the scope)

Tools Used

Manual Review

Recommendations

1.Implement Reentrancy Guards:

Use OpenZeppelin’s ReentrancyGuard to prevent multiple simultaneous executions of claimAndSwap

import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract StrategyArb is BaseStrategy, ReentrancyGuard {
// ...
function claimAndSwap(
uint256 _amountClaim,
uint256 _minOut,
IRamsesRouter.route[] calldata _path
) external onlyKeepers nonReentrant {
// Function logic
}
// ...
}

2.Use Mutex (Mutual Exclusion) Locks:
Introduce a state variable to act as a mutex, ensuring only one Keeper can execute claimAndSwap at a time.

bool private locked;
modifier noReentrant() {
require(!locked, "No re-entrancy");
locked = true;
_;
locked = false;
}
function claimAndSwap(
uint256 _amountClaim,
uint256 _minOut,
IRamsesRouter.route[] calldata _path
) external onlyKeepers noReentrant {
// Function logic
}
Updates

Appeal created

inallhonesty Lead Judge 10 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement
508110516 Submitter
10 months ago
inallhonesty Lead Judge
10 months ago
inallhonesty Lead Judge 10 months ago
Submission Judgement Published
Invalidated
Reason: Lack of quality

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.