function test_UnwindResuppliesProfitsInsteadOfTransferringToUser() public {
deal(USDC, ownerTrader, 0);
deal(WETH, ownerTrader, 0);
MockOneInchRouter mock = new MockOneInchRouter();
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 = 500 * 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 hugeUSDC = flAmount * 2;
deal(USDC, address(mock), hugeUSDC);
bytes memory openSwapData = abi.encode(WETH, USDC, hugeUSDC);
vm.startPrank(ownerTrader);
IERC20(USDC).approve(address(s), collateralAmount);
s.createLeveragedPosition(
USDC,
flAmount,
collateralAmount,
WETH,
borrowAmount,
openSwapData,
0
);
vm.stopPrank();
(uint256 collateralToWithdraw, uint256 debtAmount) = s.calculateUnwindParams(USDC, WETH);
uint256 hugeWETH = debtAmount + 10 ether;
deal(WETH, address(mock), hugeWETH);
bytes memory unwindSwapData = abi.encode(USDC, WETH, hugeWETH);
uint256 ownerUSDCBefore = IERC20(USDC).balanceOf(ownerTrader);
uint256 ownerWETHBefore = IERC20(WETH).balanceOf(ownerTrader);
vm.startPrank(ownerTrader);
s.unwindPosition(
USDC,
collateralToWithdraw,
WETH,
debtAmount,
unwindSwapData,
0
);
vm.stopPrank();
uint256 ownerUSCDAfter = IERC20(USDC).balanceOf(ownerTrader);
uint256 ownerWETHAfter = IERC20(WETH).balanceOf(ownerTrader);
assertEq(ownerUSCDAfter, ownerUSDCBefore, "USDC should not be sent to user on unwind");
assertEq(ownerWETHAfter, ownerWETHBefore, "WETH should not be sent to user on unwind");
uint256 contractUSCDAfter = IERC20(USDC).balanceOf(address(s));
uint256 contractWETHAfter = IERC20(WETH).balanceOf(address(s));
assertEq(contractUSCDAfter, 0, "USDC left in contract");
assertEq(contractWETHAfter, 0, "WETH left in contract");
(uint256 totalCollAfter, , , , , uint256 hfAfter) =
IPool(AAVE_POOL).getUserAccountData(address(s));
assertTrue(totalCollAfter > 0, "Collateral should remain due to resupply");
assertTrue(hfAfter > 1e18, "Health factor should be healthy");
}
contract MockOneInchRouter {
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), "mock: pull failed");
}
}
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), "mock: pay out failed");
bytes memory out = abi.encode(outputAmount, spent);
assembly {
return(add(out, 0x20), mload(out))
}
}
}
function _executeUnwindOperation(address _asset, uint256 _amount, uint256 _premium, bytes calldata _params)
internal
returns (bool)
{
(, address user, UnwindParams memory unwindParams) =
abi.decode(_params, (OperationType, address, UnwindParams));
// ... repay, withdraw, swap ...
uint256 totalDebt = _amount + _premium;
require(returnAmount >= totalDebt, "Insufficient funds to repay flash loan");
- // Supply any leftover tokens back to Aave
- // Note: There might be other positions open, so unwinding one position will increase the health factor
- if (returnAmount - totalDebt > 0) {
- IERC20(_asset).approve(address(aavePool), returnAmount - totalDebt);
- aavePool.supply(_asset, returnAmount - totalDebt, address(this), 0);
- }
+ // Transfer any leftover (profit/equity) to the user who initiated unwind
+ if (returnAmount > totalDebt) {
+ uint256 surplus = returnAmount - totalDebt;
+ IERC20(_asset).transfer(user, surplus);
+ }
IERC20(_asset).approve(address(aavePool), totalDebt);
emit PositionUnwound(user, unwindParams.collateralToken, _asset, _amount, withdrawnAmount);
return true;
}