Stratax Contracts

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

Incorrect numerator in getMaxLeverage produces wrong results when precision constants diverge

Author Revealed upon completion

Description

  • Stratax::getMaxLeverage computes the theoretical maximum leverage using the geometric series formula 1 / (1 - LTV). Scaled to integer arithmetic, the correct formula is (LEVERAGE_PRECISION * LTV_PRECISION) / (LTV_PRECISION - ltv). The result is used to validate that desiredLeverage does not exceed the physical maximum for a given collateral asset.

  • The implementation uses LEVERAGE_PRECISION twice in the numerator instead of LEVERAGE_PRECISION * LTV_PRECISION. Because both constants are currently equal to 1e4, the bug produces the correct result by coincidence. If either constant is changed in a future refactor, getMaxLeverage will silently overstate the maximum leverage by a factor of LEVERAGE_PRECISION / LTV_PRECISION, allowing the leverage validation check to be bypassed.

uint256 public constant LTV_PRECISION = 1e4;
uint256 public constant LEVERAGE_PRECISION = 1e4;
function getMaxLeverage(uint256 _ltv) public pure returns (uint256 maxLeverage) {
require(_ltv > 0 && _ltv < LTV_PRECISION, "Invalid LTV");
// @> maxLeverage = (LEVERAGE_PRECISION * LEVERAGE_PRECISION) / (LTV_PRECISION - _ltv);
}

Risk

Likelihood:

  • LEVERAGE_PRECISION is increased for finer leverage granularity (e.g. to 1e6) while LTV_PRECISION stays at 1e4 — a common precision refactor in DeFi protocols.

  • Either constant is changed independently during an upgrade, since their equality is not enforced or documented as an invariant.

Impact:

  • The require(details.desiredLeverage <= maxLeverage) check in _validateAndCalculatePosition passes for physically impossible leverage values, allowing positions that Aave cannot support to be submitted.

  • The flash loan amount calculated from the oversized leverage causes the Aave pool call to fail or results in an immediately liquidatable position, with the user losing funds to liquidation penalties.

Proof of Concept

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import {Test} from "forge-std/Test.sol";
contract LeveragePrecisionMismatchTest is Test {
uint256 constant LTV_PRECISION_CURRENT = 1e4;
uint256 constant LEV_PRECISION_CURRENT = 1e4;
uint256 constant LEV_PRECISION_REFACTOR = 1e6;
uint256 constant LTV_80 = 8000;
/// @notice Bug is invisible when both precisions are equal (current state).
function test_bugInvisible_whenPrecisionsEqual() public pure {
uint256 buggy = (LEV_PRECISION_CURRENT * LEV_PRECISION_CURRENT) / (LTV_PRECISION_CURRENT - LTV_80);
uint256 correct = (LEV_PRECISION_CURRENT * LTV_PRECISION_CURRENT) / (LTV_PRECISION_CURRENT - LTV_80);
assertEq(buggy, correct); // both = 50_000 = 5x
}
/// @notice After refactor (LEVERAGE_PRECISION = 1e6), formula overstates max leverage 100x.
function test_bugExplodes_whenPrecisionsDiverge() public pure {
uint256 buggy = (LEV_PRECISION_REFACTOR * LEV_PRECISION_REFACTOR) / (LTV_PRECISION_CURRENT - LTV_80);
uint256 correct = (LEV_PRECISION_REFACTOR * LTV_PRECISION_CURRENT) / (LTV_PRECISION_CURRENT - LTV_80);
// buggy = 500_000_000 (500x), correct = 5_000_000 (5x)
assertEq(buggy / LEV_PRECISION_REFACTOR, 500, "Buggy: 500x instead of 5x");
assertEq(correct / LEV_PRECISION_REFACTOR, 5, "Fixed: correct 5x");
// 50x leverage request passes buggy check, should be rejected
uint256 requested = 50 * LEV_PRECISION_REFACTOR;
assertTrue(requested <= buggy, "50x passes buggy validation");
assertFalse(requested <= correct, "50x correctly rejected by fixed formula");
}
}

Recommended Mitigation

function getMaxLeverage(uint256 _ltv) public pure returns (uint256 maxLeverage) {
require(_ltv > 0 && _ltv < LTV_PRECISION, "Invalid LTV");
- maxLeverage = (LEVERAGE_PRECISION * LEVERAGE_PRECISION) / (LTV_PRECISION - _ltv);
+ maxLeverage = (LEVERAGE_PRECISION * LTV_PRECISION) / (LTV_PRECISION - _ltv);
}

Support

FAQs

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

Give us feedback!