function calculateUnwindParams(address _collateralToken, address _borrowToken)
public view returns (uint256 collateralToWithdraw, uint256 debtAmount)
{
(,, address debtToken) = aaveDataProvider.getReserveTokensAddresses(_borrowToken);
debtAmount = IERC20(debtToken).balanceOf(address(this));
uint256 debtTokenPrice = IStrataxOracle(strataxOracle).getPrice(_borrowToken);
uint256 collateralTokenPrice = IStrataxOracle(strataxOracle).getPrice(_collateralToken);
collateralToWithdraw =
(debtTokenPrice * debtAmount * 10**IERC20(_collateralToken).decimals())
/ (collateralTokenPrice * 10**IERC20(_borrowToken).decimals());
collateralToWithdraw = (collateralToWithdraw * 1050) / 1000;
}
(,, uint256 liqThreshold,,,,,,,) =
aaveDataProvider.getReserveConfigurationData(unwindParams.collateralToken);
withdrawnAmount = aavePool.withdraw(unwindParams.collateralToken, collateralToWithdraw, address(this));
contract MockOneInchRouter_UnwindDrift {
function _pullAllApproved(address token, address from) internal returns (uint256 spent) {
uint256 allowance = IERC20(token).allowance(from, address(this));
uint256 bal = IERC20(token).balanceOf(from);
spent = allowance < bal ? allowance : bal;
if (spent > 0) require(IERC20(token).transferFrom(from, address(this), spent), "pull fail");
}
fallback() external payable {
(address inputToken, address outputToken, uint256 outputAmount) =
abi.decode(msg.data, (address, address, uint256));
uint256 spent = _pullAllApproved(inputToken, msg.sender);
require(IERC20(outputToken).transfer(msg.sender, outputAmount), "pay fail");
bytes memory out = abi.encode(outputAmount, spent);
assembly { return(add(out, 0x20), mload(out)) }
}
}
function test_Unwind_ViewVsExecution_Discrepancy() public {
MockOneInchRouter_UnwindDrift mock = new MockOneInchRouter_UnwindDrift();
Stratax impl = new Stratax();
UpgradeableBeacon b = new UpgradeableBeacon(address(impl), address(this));
bytes memory initData = abi.encodeWithSelector(
Stratax.initialize.selector,
AAVE_POOL,
AAVE_PROTOCOL_DATA_PROVIDER,
address(mock),
USDC,
address(strataxOracle)
);
BeaconProxy p = new BeaconProxy(address(b), initData);
Stratax s = Stratax(address(p));
s.transferOwnership(ownerTrader);
uint256 collateralAmount = 1_000 * 1e6;
(uint256 flAmount, uint256 borrowAmount) = s.calculateOpenParams(
Stratax.TradeDetails({
collateralToken: USDC,
borrowToken: WETH,
desiredLeverage: 30_000,
collateralAmount: collateralAmount,
collateralTokenPrice: 0,
borrowTokenPrice: 0,
collateralTokenDec: 6,
borrowTokenDec: 18
})
);
deal(USDC, ownerTrader, collateralAmount);
uint256 premiumOpen = (flAmount * s.flashLoanFeeBps()) / s.FLASHLOAN_FEE_PREC();
uint256 minReturnOpen = flAmount + premiumOpen;
deal(USDC, address(mock), minReturnOpen);
bytes memory openSwapData = abi.encode(WETH, USDC, minReturnOpen);
vm.startPrank(ownerTrader);
IERC20(USDC).approve(address(s), collateralAmount);
s.createLeveragedPosition(
USDC, flAmount, collateralAmount, WETH, borrowAmount, openSwapData, minReturnOpen
);
vm.stopPrank();
(uint256 viewCollateralToWithdraw, uint256 debtAmount) = s.calculateUnwindParams(USDC, WETH);
(,, uint256 liqThreshold,,,,,,,) =
IProtocolDataProvider(AAVE_PROTOCOL_DATA_PROVIDER).getReserveConfigurationData(USDC);
uint256 debtPrice = strataxOracle.getPrice(WETH);
uint256 collPrice = strataxOracle.getPrice(USDC);
uint8 collDec = IERC20(USDC).decimals();
uint8 debtDec = IERC20(WETH).decimals();
uint256 execCollateralToWithdraw = (
debtAmount * debtPrice * (10 ** collDec) * s.LTV_PRECISION()
) / (collPrice * (10 ** debtDec) * liqThreshold);
uint256 premiumUnwind = (debtAmount * s.flashLoanFeeBps()) / s.FLASHLOAN_FEE_PREC();
uint256 payback = debtAmount + premiumUnwind + 1e15;
deal(WETH, address(mock), payback);
bytes memory unwindSwapData = abi.encode(USDC, WETH, payback);
(address aUSDC,,) = IProtocolDataProvider(AAVE_PROTOCOL_DATA_PROVIDER).getReserveTokensAddresses(USDC);
uint256 aBalBefore = IERC20(aUSDC).balanceOf(address(s));
vm.startPrank(ownerTrader);
s.unwindPosition(USDC, viewCollateralToWithdraw, WETH, debtAmount, unwindSwapData, 0);
vm.stopPrank();
uint256 aBalAfter = IERC20(aUSDC).balanceOf(address(s));
uint256 withdrawnActual = aBalBefore - aBalAfter;
uint256 tol = 10_000;
assertApproxEqAbs(withdrawnActual, execCollateralToWithdraw, tol, "execution matches LT-based math");
assertTrue(withdrawnActual + tol < viewCollateralToWithdraw, "view overestimates vs execution");
}
function _executeUnwindOperation(address _asset, uint256 _amount, uint256 _premium, bytes calldata _params)
internal returns (bool)
{
(, address user, UnwindParams memory u) = abi.decode(_params, (OperationType, address, UnwindParams));
+ // Use the caller-provided amount (already sized by the view/helper/UI)
+ // Optional: cap by actual collateral to avoid over-withdraw attempts.
+ uint256 toWithdraw = u.collateralToWithdraw;
- // Old: recomputation using liquidationThreshold (drifts from view)
- (,, uint256 liqThreshold,,,,,,,) =
- aaveDataProvider.getReserveConfigurationData(u.collateralToken);
- uint256 collateralToWithdraw = (...) / (... * liqThreshold);
- uint256 withdrawnAmount = aavePool.withdraw(u.collateralToken, collateralToWithdraw, address(this));
+ uint256 withdrawnAmount = aavePool.withdraw(u.collateralToken, toWithdraw, address(this));
// proceed with swap & repayment...
}