function _beforeRebalanceOrLiquidate(
address tokenIn,
uint256 maxAmount
) internal view returns (RebalanceMemoryVar memory op) {
op.stablePrice = getStableTokenPriceWithScale();
op.totalYieldToken = totalYieldToken;
op.totalStableToken = totalStableToken;
uint256 amountYieldToken = op.totalYieldToken;
uint256 amountStableToken;
if (tokenIn == yieldToken) {
// User pays fxUSD - direct usage
// ... fxUSD handling logic
} else {
// User pays USDC - convert to USD equivalent
uint256 maxAmountInUSD = (maxAmount * op.stablePrice) / PRECISION;
if (maxAmountInUSD < amountYieldToken) {
amountYieldToken = maxAmountInUSD;
} else {
amountStableToken = ((maxAmountInUSD - amountYieldToken) * PRECISION) / op.stablePrice;
}
}
// Ensure we don't exceed available stable tokens
if (amountStableToken > op.totalStableToken) {
amountStableToken = op.totalStableToken;
}
op.yieldTokenToUse = amountYieldToken;
op.stableTokenToUse = amountStableToken;
}/**
* @notice Rebalance all eligible ticks in the pool through FxUSDBasePool
* @param pool The address of the long pool
* @param tokenIn The token to use (fxUSD or stable token)
* @param maxAmount Maximum token amount to use
* @param minBaseOut Minimum collateral expected
* @return tokenUsed Amount of input token consumed
* @return baseOut Amount of collateral tokens received
*/
function rebalance(
address pool,
address tokenIn,
uint256 maxAmount,
uint256 minBaseOut
) external returns (uint256 tokenUsed, uint256 baseOut);// Approve tokens for rebalancing
IERC20(fxUSD).approve(fxUSDBasePool, maxFxUSD);
IERC20(stableToken).approve(fxUSDBasePool, maxStable);
// Rebalance entire pool
(uint256 tokenUsed, uint256 collateralReceived) =
IFxUSDBasePool(fxUSDBasePool).rebalance(
poolAddress, // target pool
fxUSD, // use fxUSD
maxFxUSD, // max amount
minCollateral // minimum expected
);/**
* @notice Rebalance positions in a specific tick through FxUSDBasePool
* @param pool The address of the long pool
* @param tick The specific tick to rebalance (risk level)
* @param tokenIn The token to use (fxUSD or stable token)
* @param maxAmount Maximum token amount to use
* @param minBaseOut Minimum collateral expected
* @return tokenUsed Amount of input token consumed
* @return baseOut Amount of collateral tokens received
*/
function rebalance(
address pool,
int16 tick,
address tokenIn,
uint256 maxAmount,
uint256 minBaseOut
) external returns (uint256 tokenUsed, uint256 baseOut);// Get highest risk tick
int16 topTick = ILongPool(poolAddress).getTopTick(); // you may want to replace this with your own logic to fetch ticks
// Prepare tokens (fxUSD or USDC)
IERC20(tokenIn).approve(fxUSDBasePool, maxAmount);
// Rebalance specific tick
(uint256 tokenUsed, uint256 collateralReceived) =
IFxUSDBasePool(fxUSDBasePool).rebalance(
poolAddress, // target pool
topTick, // highest risk tick
tokenIn, // fxUSD or USDC address
1000e18, // max 1000 tokens
0 // minimum collateral (set based on slippage tolerance)
);/**
* @notice Liquidate high-risk positions through FxUSDBasePool
* @param pool The address of the long pool
* @param tokenIn The token to use (fxUSD or stable token)
* @param maxAmount Maximum token amount to use
* @param minBaseOut Minimum collateral expected
* @return tokenUsed Amount of input token consumed
* @return baseOut Amount of collateral tokens received
*/
function liquidate(
address pool,
address tokenIn,
uint256 maxAmount,
uint256 minBaseOut
) external returns (uint256 tokenUsed, uint256 baseOut);// Check liquidation opportunity
bool canLiquidate = ILongPool(poolAddress).canLiquidate();
require(canLiquidate, "No liquidation opportunity");
// Prepare liquidation tokens
IERC20(tokenIn).approve(fxUSDBasePool, maxAmount);
// Execute liquidation
(uint256 tokenUsed, uint256 collateralReceived) =
IFxUSDBasePool(fxUSDBasePool).liquidate(
poolAddress, // target pool
tokenIn, // fxUSD or USDC
maxAmount, // max token amount
minCollateral // minimum expected collateral
);// From TickMath.sol:
int24 internal constant MIN_TICK = -32767;
int24 internal constant MAX_TICK = 32767;
// Tick calculation uses 1.0015 as the base
// ratioX96 = (1.0015^tick) * 2^96 /// @notice Return the details of the given position.
/// @param tokenId The id of position to query.
/// @return rawColls The amount of collateral tokens supplied in this position.
/// @return rawDebts The amount of debt tokens borrowed in this position.
function getPosition(uint256 tokenId) external view returns (uint256 rawColls, uint256 rawDebts); /// @dev Mapping from tick to tree node id.
mapping(int256 => uint48) public tickData; /// @dev Mapping from tree node id to tree node data.
mapping(uint256 => TickTreeNode) public tickTreeData; /// @dev Internal function to get the root of the given tree node.
/// @param node The id of the given tree node.
/// @return root The root node id.
/// @return collRatio The actual collateral ratio of the given node, multiplied by 2^60.
/// @return debtRatio The actual debt ratio of the given node, multiplied by 2^60.
function _getRootNode(uint256 node) internal view returns (uint256 root, uint256 collRatio, uint256 debtRatio) {
collRatio = E60;
debtRatio = E60;
while (true) {
bytes32 metadata = tickTreeData[node].metadata;
uint256 parent = metadata.decodeUint(PARENT_OFFSET, 48);
collRatio = (collRatio * metadata.decodeUint(COLL_RATIO_OFFSET, 64)) >> 60;
debtRatio = (debtRatio * metadata.decodeUint(DEBT_RATIO_OFFSET, 64)) >> 60;
if (parent == 0) break;
node = parent;
}
root = node;
}interface IShortPoolManager {
/**
* @notice Rebalance positions in a specific tick
* @param pool The address of short pool
* @param receiver The address to receive fxUSD
* @param tick The tick to rebalance
* @param maxRawDebts Maximum raw debt tokens to use
*/
function rebalance(
address pool,
address receiver,
int16 tick,
uint256 maxRawDebts
) external returns (uint256 colls, uint256 debts);
/**
* @notice Rebalance entire pool
* @param pool The address of short pool
* @param receiver The address to receive fxUSD
* @param maxRawDebts Maximum raw debt tokens to use
*/
function rebalance(
address pool,
address receiver,
uint256 maxRawDebts
) external returns (uint256 colls, uint256 debts);
/**
* @notice Liquidate high-risk short positions
* @param pool The address of short pool
* @param receiver The address to receive fxUSD
* @param maxRawDebts Maximum raw debt tokens to use
*/
function liquidate(
address pool,
address receiver,
uint256 maxRawDebts
) external returns (uint256 colls, uint256 debts);
}// Get debt token for the short pool
address debtToken = IShortPool(shortPoolAddress).debtToken();
// Approve debt tokens for rebalancing
IERC20(debtToken).approve(shortPoolManager, maxDebtAmount);
// Execute rebalance
(uint256 fxUSDReceived, uint256 debtUsed) =
IShortPoolManager(shortPoolManager).rebalance(
shortPoolAddress, // target short pool
msg.sender, // receive fxUSD
maxDebtAmount // max debt tokens to use
);// Users call ShortPoolManager.redeemByCreditNote()
function redeemByCreditNote(address pool, uint256 debts, uint256 minColls) external {
// Transfer CreditNote from user
IERC20(creditNote).safeTransferFrom(_msgSender(), address(this), debts);
// Redeem equivalent collateral
colls = IShortPool(pool).redeemByCreditNote(rawDebts);
// Apply protocol fees
uint256 protocolFees = (colls * getRedeemFeeRatio()) / FEE_PRECISION;
colls -= protocolFees;
// Transfer net collateral to user
_transferOut(fxUSD, colls, _msgSender());
}redeemByCreditNote to obtain the assets and get more USDCx ≥ (debt - target_ratio × price × coll) / (1 - target_ratio × (1 + incentive))function getRawDebtToRebalance(tick: ITickToBalance): bigint {
const rawDebts =
(tick.rawDebts * PRECISION * PRECISION - tick.debtRatio * tick.price * tick.rawColls) /
(PRECISION * PRECISION - (PRECISION * tick.debtRatio * (FEE_PRECISION + tick.bonusRatio)) / FEE_PRECISION);
return rawDebts;
}rawDebts / price * (1 + bonus) <= position.rawColls + balance rawDebts <= (position.rawColls + balance) / (1 + bonus) * pricefunction getRawDebtToLiquidate(position: IPositionToLiquidate, balance: bigint): bigint {
// rawDebts / price * (1 + bonus) <= position.rawColls + balance
// rawDebts <= (position.rawColls + balance) / (1 + bonus) * price
let rawDebts =
((((position.rawColls + balance) * position.price) / PRECISION) * FEE_PRECISION) /
(FEE_PRECISION + position.bonusRatio);
(position.rawColls * position.price) / PRECISION;
if (rawDebts > position.rawDebts) rawDebts = position.rawDebts;
return rawDebts;
} // do rebalance or liquidate
uint256 amountBase;
address pool;
if (callType == 0) {
(pool, amountBase, assets) = _doRebalance(tokenToUse, assets, userData);
} else if (callType == 1) {
(pool, amountBase, assets) = _doLiquidate(tokenToUse, assets, userData);
}
// swap base to USDC
uint256 usdcAmount = IERC20(USDC).balanceOf(address(this));
{
(, address swapTarget, bytes memory swapData) = abi.decode(userData[1:], (address, address, bytes));
IERC20(IPool(pool).collateralToken()).forceApprove(swapTarget, amountBase);
if (amountBase > 0) {
(bool success, ) = swapTarget.call(swapData);
_popupRevertReason(success);
}
}
usdcAmount = IERC20(USDC).balanceOf(address(this)) - usdcAmount;
// check if we have credit note
address shortPool = IPool(pool).counterparty();
if (shortPool != address(0)) {
address creditNote = IShortPool(shortPool).creditNote();
uint256 balance = IERC20(creditNote).balanceOf(address(this));
if (balance > 0) {
IERC20(creditNote).forceApprove(shortPoolManager, balance);
balance = IShortPoolManager(shortPoolManager).redeemByCreditNote(shortPool, balance, 0);
// swap fxUSD to USDC
usdcAmount += ICurveStableSwapNG(USDC_fxUSD_POOL).exchange(1, 0, balance, 0);
}
}