Stratax Contracts

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

calculateOpenParams does not validate manually provided price decimals, producing incorrect borrow amounts when prices have wrong precision

Author Revealed upon completion

Description

  • Stratax::calculateOpenParams accepts a TradeDetails struct that includes collateralTokenPrice and borrowTokenPrice. When these fields are non-zero, the function uses them directly without fetching from the oracle. The function and its NatSpec document that prices must be in 8-decimal Chainlink format, but no on-chain validation enforces this.

  • If a caller provides prices in a different decimal format (e.g. 18 decimals instead of 8), the borrowAmount calculation is scaled by the ratio of the actual and expected decimals. When only one price has the wrong decimal count, the inflation or deflation is not cancelled out, resulting in a borrowAmount that is off by a factor of up to 10^10.

function calculateOpenParams(TradeDetails memory details)
public view
returns (uint256 flashLoanAmount, uint256 borrowAmount)
{
// If collateral token price is zero, fetch it from the oracle
if (details.collateralTokenPrice == 0) {
details.collateralTokenPrice = IStrataxOracle(strataxOracle).getPrice(details.collateralToken);
}
// If borrow token price is zero, fetch it from the oracle
if (details.borrowTokenPrice == 0) {
details.borrowTokenPrice = IStrataxOracle(strataxOracle).getPrice(details.borrowToken);
}
// ...
// @> uint256 totalCollateralValueUSD =
// @> (totalCollateral * details.collateralTokenPrice) / (10 ** details.collateralTokenDec);
// @> uint256 borrowAmount =
// @> (borrowValueUSD * 10 ** details.borrowTokenDec) / details.borrowTokenPrice;
}

Risk

Likelihood:

  • A frontend or integration passes prices obtained from a non-Chainlink source (e.g. an 18-decimal DEX price feed) without normalizing to 8 decimals before calling calculateOpenParams.

  • A developer manually specifies only one of the two prices (e.g. for testing or a custom collateral path) and uses a different decimal format than Chainlink's 8-decimal standard.

Impact:

  • When only the collateral price has wrong decimals (e.g. 18 instead of 8), borrowAmount is inflated by 10^10. Passing this to createLeveragedPosition causes Aave to be asked to borrow an astronomically large amount, which reverts, or if it somehow succeeds, immediately liquidates the position.

  • When only the borrow price has wrong decimals, borrowAmount is deflated by 10^10. The resulting position is severely underfunded — the collateral supplied far exceeds the debt, leaving most of the user's capital locked idle without generating the intended leverage.

Proof of Concept

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import {Test} from "forge-std/Test.sol";
contract ManualPriceDecimalsPoCTest is Test {
uint256 constant COLLATERAL_AMOUNT = 1000e6;
uint256 constant LTV = 8000;
uint256 constant BORROW_SAFETY = 9500;
uint256 constant LTV_PREC = 1e4;
function _borrowAmount(uint256 collPrice, uint256 borrowPrice) internal pure returns (uint256) {
uint256 totalCollateral = COLLATERAL_AMOUNT * 3; // 3x leverage
uint256 totalCollValueUSD = (totalCollateral * collPrice) / 1e6;
uint256 borrowValueUSD = (totalCollValueUSD * LTV * BORROW_SAFETY) / (LTV_PREC * 1e4);
return (borrowValueUSD * 1e18) / borrowPrice;
}
/// @notice Correct 8-decimal prices produce sane borrow amount.
function test_baseline() public pure {
uint256 result = _borrowAmount(1e8, 3000e8);
assertEq(result, 0.76e18, "baseline: ~0.76 WETH");
}
/// @notice Only collateral price has 18 decimals — borrow inflated 10^10x.
function test_collateralPriceWrongDecimals() public pure {
uint256 inflated = _borrowAmount(1e18, 3000e8);
uint256 baseline = _borrowAmount(1e8, 3000e8);
assertEq(inflated / baseline, 1e10, "10^10 inflation from wrong collateral price decimals");
}
/// @notice Only borrow price has 18 decimals — borrow deflated 10^10x.
function test_borrowPriceWrongDecimals() public pure {
uint256 deflated = _borrowAmount(1e8, 3000e18);
uint256 baseline = _borrowAmount(1e8, 3000e8);
assertEq(baseline / deflated, 1e10, "10^10 deflation from wrong borrow price decimals");
}
}

Recommended Mitigation

if (details.collateralTokenPrice == 0) {
require(strataxOracle != address(0), "Oracle not set");
details.collateralTokenPrice = IStrataxOracle(strataxOracle).getPrice(details.collateralToken);
+ } else {
+ require(
+ IStrataxOracle(strataxOracle).getPriceDecimals(details.collateralToken) == 8,
+ "Collateral price must have 8 decimals"
+ );
}
if (details.borrowTokenPrice == 0) {
require(strataxOracle != address(0), "Oracle not set");
details.borrowTokenPrice = IStrataxOracle(strataxOracle).getPrice(details.borrowToken);
+ } else {
+ require(
+ IStrataxOracle(strataxOracle).getPriceDecimals(details.borrowToken) == 8,
+ "Borrow price must have 8 decimals"
+ );
}

Support

FAQs

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

Give us feedback!