Stratax Contracts

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

USDC Blacklist Freezes All Positions and Leads to Inevitable Liquidation

Author Revealed upon completion

If the Stratax contract address is blacklisted by USDC (Circle’s centralized blacklist), all positions using USDC as the borrow token become permanently frozen — unable to repay debt, unwind, or recover tokens — leading to inevitable liquidation by Aave and total loss of collateral

Description

  • The Stratax protocol uses USDC as a primary borrow token and explicitly declares `address public USDC` as a state variable. USDC (managed by Circle) has a built-in blacklist mechanism that can block any address from sending or receiving USDC tokens. When an address is blacklisted, all `transfer()`, `transferFrom()`, and `approve()` calls involving that address revert.

  • Since all positions in Stratax share a single contract address (`address(this)`) on Aave, a blacklist of the Stratax contract would block all USDC operations for every position. The contract cannot repay its Aave debt, cannot execute swaps via 1inch, and cannot recover USDC via `recoverTokens()`. The debt continues to accumulate interest, the health factor decreases over time, and Aave eventually liquidates the entire account.

// In _executeUnwindOperation — repaying debt requires USDC approve + transfer:
IERC20(_asset).approve(address(aavePool), _amount); // @> Reverts if contract is blacklisted
aavePool.repay(_asset, _amount, 2, address(this)); // @> Cannot repay USDC debt
// In recoverTokens — emergency recovery also blocked:
function recoverTokens(address _token, uint256 _amount) external onlyOwner {
IERC20(_token).transfer(owner, _amount); // @> Reverts if contract is blacklisted for USDC
}

Risk

Likelihood: Low

  • USDC blacklisting is a real mechanism actively used by Circle, but blacklisting a specific contract address requires regulatory action or legal disputes targeting the protocol directly.

  • The protocol explicitly integrates USDC as a core token (`address public USDC`), making this a foreseeable interaction with a known centralized stablecoin

Impact: Medium

  • Funds are directly at risk. All positions using USDC as the borrow token are permanently frozen with no recovery mechanism.

  • Accumulated interest on the frozen USDC debt continuously degrades the health factor until Aave liquidates the entire account.

    - Due to the shared health factor architecture, even positions that do not use USDC are liquidated because they share the same Aave account.

Proof of Concept

Command to run : forge test --mt testBlacklistedContractFrozen --fork-url https://ethereum-rpc.publicnode.com -vvv

function testBlacklistedContractFrozen() public {
// STEP 1: Open a position with USDC debt
vm.startPrank(ownerTrader);
IERC20(WETH).transfer(address(stratax), 10 ether);
vm.stopPrank();
vm.startPrank(address(stratax));
IERC20(WETH).approve(AAVE_POOL, 10 ether);
IPoolMinimal(AAVE_POOL).supply(WETH, 10 ether, address(stratax), 0);
IPoolMinimal(AAVE_POOL).borrow(USDC, 5000e6, 2, 0, address(stratax));
vm.stopPrank();
uint256 debtBefore = IERC20(AAVE_VARIABLE_DEBT_USDC).balanceOf(address(stratax));
assertTrue(debtBefore > 0, "Position should have debt");
// STEP 2: Simulate USDC blacklist
vm.mockCallRevert(USDC, abi.encodeWithSignature("transfer(address,uint256)"), "Blacklisted");
vm.mockCallRevert(USDC, abi.encodeWithSignature("approve(address,uint256)"), "Blacklisted");
// STEP 3: recoverTokens reverts — funds are frozen
vm.startPrank(ownerTrader);
vm.expectRevert("Blacklisted");
stratax.recoverTokens(USDC, 5000e6);
vm.stopPrank();
// Debt is still active and accumulating interest
uint256 debtAfter = IERC20(AAVE_VARIABLE_DEBT_USDC).balanceOf(address(stratax));
assertTrue(debtAfter >= debtBefore, "Debt is still active and growing");
}

Recommended Mitigation

Deploy individual proxy contracts per position to isolate blacklist risk. A blacklist on one position’s proxy does not affect other positions.


Additionally, consider supporting alternative stablecoins (DAI, USDT) as borrow tokens to reduce dependency on a single blacklistable token.

- // All positions share one contract address — single point of failure
- aavePool.borrow(USDC, amount, 2, 0, address(this));
+ // Each position is a separate proxy — blacklist only affects one position
+ BeaconProxy positionProxy = new BeaconProxy(address(beacon), initData);
+ Stratax(address(positionProxy)).createLeveragedPosition(...);

Support

FAQs

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

Give us feedback!