// SPDX-License-Identifier: GPL-3.0-or-later pragma solidity 0.7.6; import { PerpMath } from "./PerpMath.sol"; import { PerpSafeCast } from "./PerpSafeCast.sol"; import { FullMath } from "@uniswap/v3-core/contracts/libraries/FullMath.sol"; import { SafeMathUpgradeable } from "@openzeppelin/contracts-upgradeable/math/SafeMathUpgradeable.sol"; library SwapMath { using PerpMath for int256; using PerpSafeCast for uint256; using SafeMathUpgradeable for uint256; // // CONSTANT // uint256 internal constant _ONE_HUNDRED_PERCENT = 1e6; // 100% // // INTERNAL PURE // function calcAmountScaledByFeeRatio( uint256 amount, uint24 feeRatio, bool isScaledUp ) internal pure returns (uint256) { // when scaling up, round up to avoid imprecision; it's okay as long as we round down later return isScaledUp ? FullMath.mulDivRoundingUp(amount, _ONE_HUNDRED_PERCENT, uint256(_ONE_HUNDRED_PERCENT).sub(feeRatio)) : FullMath.mulDiv(amount, uint256(_ONE_HUNDRED_PERCENT).sub(feeRatio), _ONE_HUNDRED_PERCENT); } /// @return scaledAmountForUniswapV3PoolSwap the unsigned scaled amount for UniswapV3Pool.swap() /// @return signedScaledAmountForReplaySwap the signed scaled amount for _replaySwap() /// @dev for UniswapV3Pool.swap(), scaling the amount is necessary to achieve the custom fee effect /// @dev for _replaySwap(), however, as we can input ExchangeFeeRatioRatio directly in SwapMath.computeSwapStep(), /// there is no need to stick to the scaled amount /// @dev refer to CH._openPosition() docstring for explainer diagram function calcScaledAmountForSwaps( bool isBaseToQuote, bool isExactInput, uint256 amount, uint24 exchangeFeeRatio, uint24 uniswapFeeRatio ) internal pure returns (uint256 scaledAmountForUniswapV3PoolSwap, int256 signedScaledAmountForReplaySwap) { if (isBaseToQuote) { scaledAmountForUniswapV3PoolSwap = isExactInput ? calcAmountScaledByFeeRatio(amount, uniswapFeeRatio, true) : calcAmountScaledByFeeRatio(amount, exchangeFeeRatio, true); } else { scaledAmountForUniswapV3PoolSwap = isExactInput ? calcAmountWithFeeRatioReplaced(amount, uniswapFeeRatio, exchangeFeeRatio, true) : amount; } // x : uniswapFeeRatio, y : exchangeFeeRatioRatio // since we can input ExchangeFeeRatioRatio directly in SwapMath.computeSwapStep() in _replaySwap(), // when !isBaseToQuote, we can use the original amount directly // ex: when x(uniswapFeeRatio) = 1%, y(exchangeFeeRatioRatio) = 3%, input == 1 quote // our target is to get fee == 0.03 quote // if scaling the input as 1 * 0.97 / 0.99, the fee calculated in `_replaySwap()` won't be 0.03 signedScaledAmountForReplaySwap = isBaseToQuote ? scaledAmountForUniswapV3PoolSwap.toInt256() : amount.toInt256(); signedScaledAmountForReplaySwap = isExactInput ? signedScaledAmountForReplaySwap : signedScaledAmountForReplaySwap.neg256(); } /// @param isReplacingUniswapFeeRatio is to replace uniswapFeeRatio or clearingHouseFeeRatio /// let x : uniswapFeeRatio, y : clearingHouseFeeRatio /// true: replacing uniswapFeeRatio with clearingHouseFeeRatio: amount * (1 - y) / (1 - x) /// false: replacing clearingHouseFeeRatio with uniswapFeeRatio: amount * (1 - x) / (1 - y) /// multiplying a fee is applying it as the new standard and dividing a fee is removing its effect /// @dev calculate the amount when feeRatio is switched between uniswapFeeRatio and clearingHouseFeeRatio function calcAmountWithFeeRatioReplaced( uint256 amount, uint24 uniswapFeeRatio, uint24 clearingHouseFeeRatio, bool isReplacingUniswapFeeRatio ) internal pure returns (uint256) { (uint24 newFeeRatio, uint24 replacedFeeRatio) = isReplacingUniswapFeeRatio ? (clearingHouseFeeRatio, uniswapFeeRatio) : (uniswapFeeRatio, clearingHouseFeeRatio); return FullMath.mulDivRoundingUp( amount, uint256(_ONE_HUNDRED_PERCENT).sub(newFeeRatio), uint256(_ONE_HUNDRED_PERCENT).sub(replacedFeeRatio) ); } }