Stratax Contracts

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

Interface/implementation drift (IStratax vs Stratax)

Author Revealed upon completion

Description

  • A public interface should faithfully describe the concrete contract’s external ABI so integrators can rely on function names, parameter lists, and return types. Calls made through the interface must successfully hit existing selectors on the implementation.

  • IStratax no longer matches Stratax:

    • The interface exposes calculateParams(TradeDetails) while the implementation only provides calculateOpenParams(TradeDetails) (different name/selector).

    • The interface declares unwindPosition(address,address,uint256,bytes,uint256) (5 params), but the implementation requires 6 params including collateralToWithdraw.

    • The interface declares getters like BASIS_POINTS() that do not exist; the implementation defines constants such as FLASHLOAN_FEE_PREC instead.

    As a result, interface-based calls compile but revert at runtime because the function selectors are missing on Stratax.

// IStratax
// @> declares a function that doesn't exist on Stratax
function calculateParams(TradeDetails memory details)
external view returns (uint256 flashLoanAmount, uint256 borrowAmount);
// @> missing collateralToWithdraw param vs implementation
function unwindPosition(
address _collateralToken,
address _debtToken,
uint256 _debtAmount,
bytes calldata _oneInchSwapData,
uint256 _minReturnAmount
) external;
// @> declares a getter that Stratax doesn't implement
function BASIS_POINTS() external view returns (uint256);
// Stratax
// @> actual name/signature differs (calculateOpenParams)
function calculateOpenParams(TradeDetails memory details)
public view returns (uint256 flashLoanAmount, uint256 borrowAmount);
// @> requires 6th arg: collateralToWithdraw
function unwindPosition(
address _collateralToken,
uint256 _collateralToWithdraw, // <— extra param required
address _debtToken,
uint256 _debtAmount,
bytes calldata _oneInchSwapData,
uint256 _minReturnAmount
) external onlyOwner;
// @> no BASIS_POINTS() getter; has constants like FLASHLOAN_FEE_PREC instead
uint256 public constant FLASHLOAN_FEE_PREC = 10000;

Risk

Likelihood: High

  • Integrators commonly code against interfaces; whenever they call calculateParams or the 5‑arg unwindPosition through IStratax, the call will happen during integration or testing and revert due to missing selectors.

  • The Stratax test bundle includes both interface and implementation files, increasing the chance someone imports the interface in new tests/scripts and hits this drift.

Impact: High

  • Immediate integration breakage: On-chain calls through IStratax revert at runtime, halting workflows (e.g., position management UIs, automation bots).

  • Ecosystem confusion / maintenance burden: Conflicting ABIs complicate audits, SDKs, and partner integrations; downstream users may ship incorrect calldata or assume unsupported behaviors.

Proof of Concept

  • Copy the code below to test/fork/Stratax.t.sol.

  • Run command forge test --mc StrataxForkTest -vv.

function test_InterfaceDrift_missingConstantSelector_fails() public view {
// IStratax declares: function BASIS_POINTS() external view returns (uint256);
// Stratax has no such getter; call should fail.
bytes4 sel = bytes4(keccak256("BASIS_POINTS()"));
(bool ok, ) = address(stratax).staticcall(abi.encodeWithSelector(sel));
assertTrue(!ok, "BASIS_POINTS() unexpectedly succeeded");
}
function test_InterfaceDrift_unwindSignatureMismatch_fails() public {
// IStratax declares 5-arg unwindPosition(address,address,uint256,bytes,uint256)
// Stratax requires 6 args (includes collateralToWithdraw), so this selector is missing.
bytes4 sel = bytes4(
keccak256("unwindPosition(address,address,uint256,bytes,uint256)")
);
// Use harmless dummy values; we only assert that the selector is absent.
address collateral = USDC;
address debtToken = WETH;
uint256 debtAmount = 0;
bytes memory swapData = hex"";
uint256 minReturn = 0;
(bool ok, ) = address(stratax).call(
abi.encodeWithSelector(sel, collateral, debtToken, debtAmount, swapData, minReturn)
);
assertTrue(!ok, "5-arg unwindPosition unexpectedly succeeded");
}
function test_InterfaceDrift_calculatePararams_fails() public view {
// IStratax: function calculateParams(TradeDetails calldata)
// external view returns (uint256, uint256);
// Stratax has no such function; call should fail.
bytes4 sel = bytes4(keccak256("calculateParams((address,address,uint256,uint256,uint256,uint256,uint8,uint8))"));
(bool ok, ) = address(stratax).staticcall(abi.encodeWithSelector(sel, Stratax.TradeDetails({
collateralToken: USDC,
borrowToken: WETH,
desiredLeverage: 30_000,
collateralAmount: 1_000 * 1e6,
collateralTokenPrice: 0,
borrowTokenPrice: 0,
collateralTokenDec: 6,
borrowTokenDec: 18
})));
assertTrue(!ok, "calculateParams unexpectedly succeeded");
}
function test_InterfaceDrift_reference_calculateOpenParams_works() public view {
// Show that the *actual* function exists and works on the fork,
// contrasting with the stale IStratax.calculateParams.
(uint256 flashLoanAmount, uint256 borrowAmount) = stratax.calculateOpenParams(
Stratax.TradeDetails({
collateralToken: USDC,
borrowToken: WETH,
desiredLeverage: 30_000, // 3x
collateralAmount: 1_000 * 1e6, // 1,000 USDC
collateralTokenPrice: 0, // let oracle supply it
borrowTokenPrice: 0, // let oracle supply it
collateralTokenDec: 6,
borrowTokenDec: 18
})
);
assertTrue(flashLoanAmount > 0, "flashLoanAmount should be > 0");
assertTrue(borrowAmount > 0, "borrowAmount should be > 0");
}

Output:

[⠒] Compiling...
No files changed, compilation skipped
Ran 8 tests for test/fork/Stratax.t.sol:StrataxForkTest
[PASS] test_Example_SwapWithRealData() (gas: 1212169)
Logs:
Available swap data files: 3
Randomly selected block: 24329390
Current fork block number is: 24329390
[SKIP] test_FFI_Get1inchSwapData() (gas: 0)
Logs:
Available swap data files: 3
Randomly selected block: 24329390
Current fork block number is: 24329390
[PASS] test_InterfaceDrift_calculatePararams_fails() (gas: 18868)
Logs:
Available swap data files: 3
Randomly selected block: 24329390
Current fork block number is: 24329390
[PASS] test_InterfaceDrift_missingConstantSelector_fails() (gas: 17566)
Logs:
Available swap data files: 3
Randomly selected block: 24329390
Current fork block number is: 24329390
[PASS] test_InterfaceDrift_reference_calculateOpenParams_works() (gas: 100902)
Logs:
Available swap data files: 3
Randomly selected block: 24329390
Current fork block number is: 24329390
[PASS] test_InterfaceDrift_unwindSignatureMismatch_fails() (gas: 18611)
Logs:
Available swap data files: 3
Randomly selected block: 24329390
Current fork block number is: 24329390
[PASS] test_OpenAndUnwindPosition() (gas: 2269015)
Logs:
Available swap data files: 3
Randomly selected block: 24329390
Current fork block number is: 24329390
Unwind: calculating params
Unwind: get 1inch data
Unwind: calling stratax to unwind position
[PASS] test_USDCTokenExists() (gas: 16408)
Logs:
Available swap data files: 3
Randomly selected block: 24329390
Current fork block number is: 24329390
Suite result: ok. 7 passed; 0 failed; 1 skipped; finished in 831.29ms (89.61ms CPU time)
Ran 1 test suite in 838.50ms (831.29ms CPU time): 7 tests passed, 0 failed, 1 skipped (8 total tests)

Recommended Mitigation

  • Update the interface to match the implementation:

- interface IStratax {
- struct TradeDetails {
- uint256 ltv;
- uint256 desiredLeverage;
- uint256 collateralAmount;
- uint256 collateralTokenPrice;
- uint256 borrowTokenPrice;
- uint256 collateralTokenDec;
- uint256 borrowTokenDec;
- }
-
- function calculateParams(TradeDetails memory details)
- external view returns (uint256 flashLoanAmount, uint256 borrowAmount);
-
- function unwindPosition(
- address _collateralToken,
- address _debtToken,
- uint256 _debtAmount,
- bytes calldata _oneInchSwapData,
- uint256 _minReturnAmount
- ) external;
-
- function BASIS_POINTS() external view returns (uint256);
- }
+ interface IStratax {
+ struct TradeDetails {
+ address collateralToken;
+ address borrowToken;
+ uint256 desiredLeverage;
+ uint256 collateralAmount;
+ uint256 collateralTokenPrice;
+ uint256 borrowTokenPrice;
+ uint256 collateralTokenDec;
+ uint256 borrowTokenDec;
+ }
+
+ function calculateOpenParams(TradeDetails memory details)
+ external view returns (uint256 flashLoanAmount, uint256 borrowAmount);
+
+ function unwindPosition(
+ address _collateralToken,
+ uint256 _collateralToWithdraw,
+ address _debtToken,
+ uint256 _debtAmount,
+ bytes calldata _oneInchSwapData,
+ uint256 _minReturnAmount
+ ) external;
+ // remove nonexistent getters like BASIS_POINTS()
+ }

Support

FAQs

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

Give us feedback!