Stratax Contracts

First Flight #57
Beginner FriendlyDeFi
100 EXP
Submission Details
Impact: high
Likelihood: medium

`recoverTokens` Can Drain Active Position Collateral (aTokens)

Author Revealed upon completion

Root + Impact

Description

  • The recoverTokens function is designed as an emergency recovery mechanism that allows the owner to retrieve tokens accidentally sent to the contract. It accepts any ERC20 token address and amount, transferring the specified amount to the owner.

  • The function does not distinguish between "stuck" tokens and tokens that are critical to active Aave positions. When the Stratax contract supplies collateral to Aave, it receives aTokens (e.g., aWETH, aUSDC) which are standard ERC20 tokens held by the contract. The owner can call recoverTokens with an aToken address to withdraw collateral from under active leveraged positions, destroying position health.

@> function recoverTokens(address _token, uint256 _amount) external onlyOwner {
@> IERC20(_token).transfer(owner, _amount);
@> }

Risk

Likelihood:

  • The function is callable by the owner at any time with no restrictions on which tokens can be recovered. In a proxy-based deployment where ownership may be transferred or managed by a multisig, a single compromised signer or a governance attack could trigger this.

  • Even without malicious intent, a well-meaning owner could accidentally recover aTokens, not realizing they represent active Aave collateral.

Impact:

  • Recovering aTokens removes the Aave collateral backing leveraged positions. The position health factor drops below 1.0, triggering Aave liquidation and resulting in loss of the remaining collateral (liquidation penalty on Aave V3 is typically 5-10%).

  • The function does not check the return value of transfer, so silent failures would not revert but leave the contract in an inconsistent state.

Proof of Concept

This Foundry test demonstrates that the owner can call recoverTokens with the aWETH token address (Aave's interest-bearing WETH wrapper) and successfully transfer the contract's entire aToken balance to themselves. No allowlist, denylist, or health-factor check prevents the recovery of tokens that back active leveraged positions.

function test_BRIDGE006_RecoverTokensDrainsATokens() public {
// aWETH on Ethereum mainnet
address aWETH = 0x4d5F47FA6A74757f35C14fD3a6Ef8E3C9BC514E8;
uint256 aTokenBalance = 2e18;
vm.mockCall(
aWETH,
abi.encodeWithSignature("transfer(address,uint256)", ownerTrader, aTokenBalance),
abi.encode(true)
);
// Owner recovers aTokens -- no check prevents this
vm.prank(ownerTrader);
stratax.recoverTokens(aWETH, aTokenBalance);
// Active position's collateral is now drained
}

Recommended Mitigation

Introduce a protectedTokens mapping that the owner populates with aToken addresses when positions are opened. The recoverTokens function should reject any token in this set. Additionally, check the return value of transfer to avoid silent failures.

+ mapping(address => bool) public protectedTokens;
+
+ function setProtectedToken(address _token, bool _protected) external onlyOwner {
+ protectedTokens[_token] = _protected;
+ }
+
function recoverTokens(address _token, uint256 _amount) external onlyOwner {
+ require(!protectedTokens[_token], "Cannot recover protected token");
- IERC20(_token).transfer(owner, _amount);
+ bool success = IERC20(_token).transfer(owner, _amount);
+ require(success, "Transfer failed");
}

Support

FAQs

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

Give us feedback!