Created
September 3, 2025 07:02
-
-
Save cNoveron/a76be781eef541607b7688fa1cf4f45d to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| pragma solidity ^0.5.16; | |
| pragma experimental ABIEncoderV2; | |
| import "./XSDInterface.sol"; | |
| import "./UltimateLoan.sol"; | |
| import "../PriceOracle.sol"; | |
| import "../Comptroller.sol"; | |
| import "../XErc20.sol"; | |
| import "../ExponentialNoError.sol"; | |
| import "../SwapTools/ISwapRouter.sol"; | |
| import "../SwapTools/Path.sol"; | |
| import "../EIP20Interface.sol"; | |
| import "../EIP20NonStandardInterface.sol"; | |
| contract XSDStabilizer is ExponentialNoError { | |
| /// @notice These represent the fee percentage of Lexe taken when supplying and burning scaled as a mantissa | |
| uint256 public lowFee = 0.001e18; | |
| uint256 public highFee = 0.05e18; | |
| /// @notice These represent the treshholds for different fee levels scaled as a mantissa | |
| uint256 public feeTreshhold = 1e18; | |
| uint256 public mintHighFeeTreshhold = 0.998e18; | |
| uint256 public burnHighFeeTreshhold = 1.002e18; | |
| /// @notice Array containing all xTokens that can be supplied | |
| XToken[] public supplyTokens; | |
| /// @notice xWBTC token (used if all suppliable stablecoins need to be liquidated) | |
| XErc20 public xWBTC; | |
| /// @notice bool for Reentry | |
| bool private _notEntered = true; | |
| /// @notice Mapping containing the status of each token | |
| mapping(address => bool) blacklist; | |
| uint256 public maxSlippage = 0.99e18; | |
| using Path for bytes; | |
| struct LiquidationParams { | |
| bool isActive; | |
| /// @notice Amount of liquidated token that will be swapped mapped to each output token | |
| mapping(address => uint256) inputAmounts; | |
| /// @notice Amount of output tokens that still need to be liquidated | |
| uint256 remainingOutputTokens; | |
| } | |
| bool public isLiquidating; | |
| mapping(address => LiquidationParams) public liquidationParams; | |
| struct PoolShare { | |
| XErc20 token; | |
| uint256 share; | |
| } | |
| PoolShare[] public poolShares; | |
| ISwapRouter public swapRouter; | |
| XToken public xLEXE; | |
| PriceOracle public priceOracle; | |
| /// @notice Interface towards the core XSD Contract | |
| XSDInterface public xsdInterface; | |
| Comptroller comptroller; | |
| address public admin; | |
| address public ultimateLoanAddress; | |
| /// @notice Keeps track of protocol and stabilizer liquidity | |
| address public liquidityOracle; | |
| uint256 public packedLiquidity; | |
| /// @notice Emitted when token are minted | |
| event Mint( | |
| address minter, | |
| uint256 amount, | |
| address token, | |
| uint256 suppliedAmount, | |
| uint256 lexeFee | |
| ); | |
| event MintShares( | |
| address minter, | |
| uint256 amount, | |
| UltimateLoan.supplyToken[] tokens, | |
| uint256 lexeFee | |
| ); | |
| /// @notice Emitted when tokens are burned | |
| event Burn( | |
| address burner, | |
| uint256 amount, | |
| address token, | |
| uint256 distributedAmount, | |
| uint256 lexeFee | |
| ); | |
| event BurnShares( | |
| address burner, | |
| uint256 amount, | |
| UltimateLoan.supplyToken[] tokens, | |
| uint256[] distributedAmount, | |
| uint256 lexeFee | |
| ); | |
| /// @notice Emitted when token is banned | |
| event Banned(address token); | |
| /// @notice Emitted when token is unbanned | |
| event Unbanned(address token); | |
| /// @notice Emitted when all suppliable stablecoins have been liquidated | |
| event Deactivated(); | |
| /// @notice Emitted when UL address is changed by admin | |
| event UltimateLoanAddressChanged( | |
| address oldUltimateLoanAddress, | |
| address newUltimateLoanAddress | |
| ); | |
| /// @notice Emitted when price oracle is changed by admin | |
| event PriceOracleChanged(address oldPriceOracle, address newPriceOracle); | |
| /// @notice Emitted when liquidity oracle is changed by admin | |
| event LiquidityOracleChanged( | |
| address oldLiquidityOracle, | |
| address newLiquidityOracle | |
| ); | |
| /// @notice Emitted when comptroller is changed by admin | |
| event ComptrollerChanged(address oldComptroller, address newComptroller); | |
| struct ConstructorParams { | |
| address[] _supplyTokens; | |
| address xLexeAddress; | |
| address priceOracleAddress; | |
| address xsdAddress; | |
| address xwbtcAddress; | |
| address comptrollerAddress; | |
| PoolShare[] _poolShares; | |
| address _swapRouter; | |
| address _liquidityOracle; | |
| } | |
| constructor(ConstructorParams memory params) public { | |
| for (uint256 i = 0; i < params._supplyTokens.length; i++) { | |
| supplyTokens.push(XToken(params._supplyTokens[i])); | |
| } | |
| xLEXE = XToken(params.xLexeAddress); | |
| priceOracle = PriceOracle(params.priceOracleAddress); | |
| xsdInterface = XSDInterface(params.xsdAddress); | |
| xWBTC = XErc20(params.xwbtcAddress); | |
| comptroller = Comptroller(params.comptrollerAddress); | |
| swapRouter = ISwapRouter(params._swapRouter); | |
| liquidityOracle = params._liquidityOracle; | |
| uint256 totalAmount = 0; | |
| for (uint256 i = 0; i < params._poolShares.length; i++) { | |
| totalAmount += params._poolShares[i].share; | |
| poolShares.push(params._poolShares[i]); | |
| } | |
| require(totalAmount == 1e18); | |
| admin = msg.sender; | |
| } | |
| function validateToken(XToken token) public view returns (bool) { | |
| bool validToken = false; | |
| uint256 supplyLength = supplyTokens.length; | |
| for (uint256 i = 0; i < supplyLength; i++) { | |
| if (supplyTokens[i] == token) { | |
| validToken = true; | |
| break; | |
| } | |
| } | |
| return validToken; | |
| } | |
| /** | |
| * @notice Mints XSD | |
| * @param amount Amount of XSD that will be minted | |
| * @param token Token that will be used for supplying | |
| */ | |
| function mint( | |
| uint256 amount, | |
| XToken token | |
| ) external nonReentrant returns (uint256, uint256) { | |
| require(amount >= 1e18, "Amount must be at least one!"); | |
| bool validToken = validateToken(token); | |
| require(validToken, "Invalid Token!"); | |
| uint256 XSDPrice = getXSDPrice(); | |
| require(!blacklist[address(token)], "Token is blacklisted or banned!"); | |
| require( | |
| getHypotheticalLendexeShare(amount) < 0.6e18, | |
| "Market share of XSD is too high!" | |
| ); | |
| uint256 lexeFee = getLexeFeeMint(amount, XSDPrice); | |
| require( | |
| lexeFee != uint256(-1), | |
| "Minting not allowed due to low price!" | |
| ); | |
| if (lexeFee > 0) { | |
| bool transferStatusLexe = xLEXE.transferFrom( | |
| msg.sender, | |
| address(xsdInterface), | |
| lexeFee | |
| ); | |
| require( | |
| transferStatusLexe, | |
| "No allowance granted for Lexe or insufficient balance!" | |
| ); | |
| } | |
| uint256 supplyAmount = div_( | |
| Exp({mantissa: amount}), | |
| Exp({mantissa: token.exchangeRateCurrent()}) | |
| ).mantissa; | |
| bool transferStatusToken = token.transferFrom( | |
| msg.sender, | |
| address(xsdInterface), | |
| supplyAmount | |
| ); | |
| require( | |
| transferStatusToken, | |
| "No allowance granted or insufficient balance!" | |
| ); | |
| xsdInterface.mint(msg.sender, amount); | |
| emit Mint(msg.sender, amount, address(token), supplyAmount, lexeFee); | |
| return (amount, lexeFee); | |
| } | |
| /** | |
| * @notice Mints XSD with multiple supplyTokens | |
| * @param amount Amount of XSD that will be minted | |
| * @param supplyTokens_ Tokens that will be used for supplying with the share they make up of the total amount | |
| */ | |
| function mintShares( | |
| uint256 amount, | |
| UltimateLoan.supplyToken[] calldata supplyTokens_ | |
| ) external nonReentrant returns (uint256, uint256) { | |
| require(amount >= 1e18, "Amount must be at least one!"); | |
| Exp memory totalShare = Exp({mantissa: 0}); | |
| for (uint256 i = 0; i < supplyTokens_.length; i++) { | |
| totalShare = add_(totalShare, supplyTokens_[i].share); | |
| require(validateToken(supplyTokens_[i].token), "Invalid Token!"); | |
| require( | |
| !blacklist[address(supplyTokens_[i].token)], | |
| "Token is blacklisted or banned!" | |
| ); | |
| uint256 supplyAmount = div_( | |
| mul_(Exp({mantissa: amount}), supplyTokens_[i].share), | |
| Exp({mantissa: supplyTokens_[i].token.exchangeRateCurrent()}) | |
| ).mantissa; | |
| bool transferStatusToken = supplyTokens_[i].token.transferFrom( | |
| msg.sender, | |
| address(xsdInterface), | |
| supplyAmount | |
| ); | |
| require( | |
| transferStatusToken, | |
| "No allowance granted or insufficient balance!" | |
| ); | |
| } | |
| require(totalShare.mantissa == 1e18, "Sum of shares has to equal 1e18"); | |
| if (msg.sender == ultimateLoanAddress) { | |
| amount = mul_(amount, 2); //UL can mint double since it holds an equivalent amount of Lexe | |
| } | |
| uint256 XSDPrice = getXSDPrice(); | |
| require( | |
| getHypotheticalLendexeShare(amount) < 0.6e18, | |
| "Market share of XSD is too high!" | |
| ); | |
| uint256 lexeFee = getLexeFeeMint(amount, XSDPrice); | |
| require( | |
| lexeFee != uint256(-1), | |
| "Minting not allowed due to low price!" | |
| ); | |
| if (lexeFee > 0) { | |
| bool transferStatusLexe = xLEXE.transferFrom( | |
| msg.sender, | |
| address(xsdInterface), | |
| lexeFee | |
| ); | |
| require( | |
| transferStatusLexe, | |
| "No allowance granted for Lexe or insufficient balance!" | |
| ); | |
| } | |
| xsdInterface.mint(msg.sender, amount); | |
| emit MintShares(msg.sender, amount, supplyTokens_, lexeFee); | |
| return (amount, lexeFee); | |
| } | |
| /** | |
| * @notice Get the amount of xLEXE that needs to be paid when minting | |
| * @param amount Amount of XSD that will be minted | |
| * @param xsdPrice Price of XSD | |
| * @return uint Pmount of xLEXE that needs to be paid | |
| */ | |
| function getLexeFeeMint( | |
| uint256 amount, | |
| uint256 xsdPrice | |
| ) public returns (uint256) { | |
| uint256 feePercentage; | |
| if (xsdPrice >= feeTreshhold) { | |
| return 0; | |
| } else if (xsdPrice < mintHighFeeTreshhold) { | |
| feePercentage = add_( | |
| lowFee, | |
| div_( | |
| mul_((sub_(feeTreshhold, xsdPrice)), sub_(highFee, lowFee)), | |
| sub_(feeTreshhold, mintHighFeeTreshhold) | |
| ) | |
| ); | |
| } else { | |
| // Minting disabled if price too low | |
| return uint256(-1); | |
| } | |
| uint256 LexePrice = priceOracle.getUnderlyingPrice(xLEXE); | |
| uint256 LexeExchangeRate = xLEXE.exchangeRateCurrent(); | |
| // feePercentage * amount * xsdPrice / (LexePrice * LexeExchangeRate) = lexeFee | |
| return | |
| div_( | |
| mul_(Exp({mantissa: feePercentage}), Exp({mantissa: amount})), | |
| mul_( | |
| Exp({mantissa: LexePrice}), | |
| Exp({mantissa: LexeExchangeRate}) | |
| ) | |
| ).mantissa; | |
| } | |
| /** | |
| * @notice Burns XSD | |
| * @param amount Amount of XSD that will be burned | |
| * @param token Token that will be exchanged for the burned xsd | |
| */ | |
| function burn( | |
| uint256 amount, | |
| XToken token | |
| ) external nonReentrant returns (uint256) { | |
| require(amount >= 1e18, "Amount must be at least one!"); | |
| bool validToken = validateToken(token); | |
| require(validToken, "Invalid Token!"); | |
| uint256 XSDPrice = getXSDPrice(); | |
| require(!blacklist[address(token)], "Token is blacklisted or banned!"); | |
| uint256 lexeFee = getLexeFeeBurn(amount, XSDPrice); | |
| require( | |
| lexeFee != uint256(-1), | |
| "Burning not allowed due to high price!" | |
| ); | |
| if (lexeFee > 0) { | |
| bool transferStatusLexe = xLEXE.transferFrom( | |
| msg.sender, | |
| address(xsdInterface), | |
| lexeFee | |
| ); | |
| require( | |
| transferStatusLexe, | |
| "No allowance granted for Lexe or insufficient balance!" | |
| ); | |
| } | |
| uint256 burnAmount = div_( | |
| Exp({mantissa: amount}), | |
| Exp({mantissa: token.exchangeRateCurrent()}) | |
| ).mantissa; | |
| require( | |
| token.balanceOf(address(xsdInterface)) >= burnAmount, | |
| "Insufficient Supply of requested Token!" | |
| ); | |
| xsdInterface.burn(msg.sender, amount); //reverts if XSD balance of msg.sender is less than amount | |
| bool transferStatusToken = token.transferFrom( | |
| address(xsdInterface), | |
| msg.sender, | |
| burnAmount | |
| ); | |
| require( | |
| transferStatusToken, | |
| "No allowance granted for token or insufficient balance" | |
| ); | |
| emit Burn(msg.sender, amount, address(token), burnAmount, lexeFee); | |
| } | |
| /** | |
| * @notice Burns XSD | |
| * @param amount Amount of XSD that will be burned | |
| * @param supplyTokens_ Tokens that will be exchanged for the burned xsd | |
| */ | |
| function burnShares( | |
| uint256 amount, | |
| UltimateLoan.supplyToken[] calldata supplyTokens_ | |
| ) external nonReentrant returns (uint256) { | |
| require(amount >= 1e18, "Amount must be at least one!"); | |
| Exp memory totalShare = Exp({mantissa: 0}); | |
| uint256[] memory distributedAmounts = new uint256[]( | |
| supplyTokens_.length | |
| ); | |
| for (uint256 i = 0; i < supplyTokens_.length; i++) { | |
| totalShare = add_(totalShare, supplyTokens_[i].share); | |
| bool validToken = validateToken(supplyTokens_[i].token); | |
| require(validToken, "Invalid Token!"); | |
| require( | |
| !blacklist[address(supplyTokens_[i].token)], | |
| "Token is blacklisted or banned!" | |
| ); | |
| distributedAmounts[i] = mul_( | |
| div_( | |
| Exp({mantissa: amount}), | |
| Exp({ | |
| mantissa: supplyTokens_[i].token.exchangeRateCurrent() | |
| }) | |
| ), | |
| supplyTokens_[i].share | |
| ).mantissa; | |
| require( | |
| supplyTokens_[i].token.balanceOf(address(xsdInterface)) >= | |
| distributedAmounts[i], | |
| "Insufficient Supply of requested Token!" | |
| ); | |
| bool transferStatus = supplyTokens_[i].token.transferFrom( | |
| address(xsdInterface), | |
| msg.sender, | |
| distributedAmounts[i] | |
| ); | |
| require( | |
| transferStatus, | |
| "No allowance granted for token or insufficient balance" | |
| ); | |
| } | |
| require(totalShare.mantissa == 1e18, "Sum of shares has to equal 1e18"); | |
| uint256 XSDPrice = getXSDPrice(); | |
| uint256 lexeFee = getLexeFeeBurn(amount, XSDPrice); | |
| require( | |
| lexeFee != uint256(-1), | |
| "Burning not allowed due to high price!" | |
| ); | |
| if (msg.sender != ultimateLoanAddress && lexeFee > 0) { | |
| //UL only burns when somebody repays their loan, no fee will be charged | |
| bool transferStatusLexe = xLEXE.transferFrom( | |
| msg.sender, | |
| address(xsdInterface), | |
| lexeFee | |
| ); | |
| require( | |
| transferStatusLexe, | |
| "No allowance granted for Lexe or insufficient balance!" | |
| ); | |
| } | |
| xsdInterface.burn(msg.sender, amount); //reverts if XSD balance of msg.sender is less than amount | |
| emit BurnShares( | |
| msg.sender, | |
| amount, | |
| supplyTokens_, | |
| distributedAmounts, | |
| msg.sender != ultimateLoanAddress ? lexeFee : 0 | |
| ); | |
| } | |
| /** | |
| * @notice Burns XSD directly without handing out stablecoins. Can only be called by UL | |
| * @param amount Amount of XSD that will be burned | |
| */ | |
| function burnDirectly(uint256 amount) external { | |
| address ultimateLoanAddress_ = ultimateLoanAddress; | |
| require( | |
| msg.sender == ultimateLoanAddress_, | |
| "Only UL can burn directly" | |
| ); | |
| xsdInterface.burn(ultimateLoanAddress_, amount); | |
| } | |
| /** | |
| * @notice Get the amount of xLEXE that needs to be paid when burning | |
| * @param amount Amount of XSD that will be burned | |
| * @param xsdPrice Price of XSD | |
| * @return uint Amount of xLEXE that needs to be paid | |
| */ | |
| function getLexeFeeBurn( | |
| uint256 amount, | |
| uint256 xsdPrice | |
| ) public returns (uint256) { | |
| uint256 feePercentage; | |
| if (xsdPrice <= feeTreshhold) { | |
| return 0; | |
| } else if (xsdPrice < burnHighFeeTreshhold) { | |
| feePercentage = add_( | |
| lowFee, | |
| div_( | |
| mul_((sub_(xsdPrice, feeTreshhold)), sub_(highFee, lowFee)), | |
| sub_(burnHighFeeTreshhold, feeTreshhold) | |
| ) | |
| ); | |
| } else { | |
| // Burning disabled | |
| return uint256(-1); | |
| } | |
| uint256 LexePrice = priceOracle.getUnderlyingPrice(xLEXE); | |
| uint256 LexeExchangeRate = xLEXE.exchangeRateCurrent(); | |
| // feePercentage * amount * xsdPrice / (LexePrice * LexeExchangeRate) = lexeFee | |
| return | |
| div_( | |
| mul_(Exp({mantissa: feePercentage}), Exp({mantissa: amount})), | |
| mul_( | |
| Exp({mantissa: LexePrice}), | |
| Exp({mantissa: LexeExchangeRate}) | |
| ) | |
| ).mantissa; | |
| } | |
| /** | |
| * @notice Get the current price of XSD | |
| * @return uint XSD price | |
| */ | |
| function getXSDPrice() public returns (uint256) { | |
| if (xsdInterface.totalSupply() == 0) { | |
| return 1e18; | |
| } | |
| return priceOracle.assetPrices(address(xsdInterface)); | |
| } | |
| function getSupplyValue() public returns (uint256) { | |
| XToken[] memory _supplyTokens = supplyTokens; | |
| uint256[] memory prices = new uint256[](supplyTokens.length); | |
| // fetch prices of suppliable tokens | |
| for (uint256 i = 0; i < _supplyTokens.length; i++) { | |
| uint256 price = priceOracle.getUnderlyingPrice(_supplyTokens[i]); | |
| require(price != 0, "Oracle is disabled!"); | |
| prices[i] = price; | |
| } | |
| Exp memory totalValue = Exp({mantissa: 0}); | |
| //calculates the total value of each suppliable token adds it | |
| for (uint256 i = 0; i < _supplyTokens.length; i++) { | |
| totalValue = add_( | |
| totalValue, | |
| mul_( | |
| Exp({mantissa: prices[i]}), | |
| Exp({ | |
| mantissa: _supplyTokens[i].balanceOfUnderlying( | |
| address(xsdInterface) | |
| ) | |
| }) | |
| ) | |
| ); | |
| } | |
| //add value of supplied xLEXE | |
| uint256 LEXEPrice = priceOracle.getUnderlyingPrice(xLEXE); | |
| return | |
| add_( | |
| totalValue, | |
| mul_( | |
| Exp({mantissa: LEXEPrice}), | |
| Exp({ | |
| mantissa: xLEXE.balanceOfUnderlying( | |
| address(xsdInterface) | |
| ) | |
| }) | |
| ) | |
| ).mantissa; | |
| } | |
| /** | |
| * @notice Get the hypothetical XSD supply share percentage of the Lendexe market capital scaled as mantissa | |
| * @param amount amount of xsd that would be minted | |
| * @return uint XSD supply share percentage | |
| */ | |
| function getHypotheticalLendexeShare( | |
| uint256 amount | |
| ) public view returns (uint256) { | |
| uint256 _packedLiquidity = packedLiquidity; | |
| return | |
| div_( | |
| add_( | |
| Exp(uint256(uint192(_packedLiquidity) >> 96)), | |
| Exp(amount) | |
| ), | |
| Exp(_packedLiquidity >> 192) | |
| ).mantissa; | |
| } | |
| /** | |
| * @notice Updates the blacklist | |
| */ | |
| function updateBlacklist() public nonReentrant { | |
| require(!isLiquidating, "Already liquidating!"); | |
| XToken[] memory _supplyTokens = supplyTokens; | |
| for (uint i = 0; i < _supplyTokens.length; i++) { | |
| XToken token = _supplyTokens[i]; | |
| bool status = blacklist[address(token)]; | |
| uint256 tokenPrice = priceOracle.getUnderlyingPrice(token); | |
| if ( | |
| !blacklist[address(token)] && tokenPrice < mintHighFeeTreshhold | |
| ) { | |
| require( | |
| uint256(uint96(packedLiquidity)) + 10 minutes > | |
| block.timestamp, | |
| "Liquidity isn't fresh!" | |
| ); | |
| blacklist[address(token)] = true; | |
| liquidate(XErc20(address(token))); | |
| emit Banned(address(token)); | |
| return; // max one liquidation per tx | |
| } | |
| } | |
| } | |
| function liquidate(XErc20 token) internal { | |
| uint balance = token.balanceOf(address(xsdInterface)); | |
| uint xTokenCash = div_( | |
| Exp(token.getCash()), | |
| Exp(token.exchangeRateCurrent()) | |
| ).mantissa; | |
| uint maxRedeemableAmount = balance < xTokenCash ? balance : xTokenCash; | |
| bool transferStatus = token.transferFrom( | |
| address(xsdInterface), | |
| address(this), | |
| maxRedeemableAmount | |
| ); | |
| require(transferStatus, "Stabilizer: Not enough balance on XSD Core"); //this should never be hit | |
| require(token.redeem(maxRedeemableAmount) == 0, "Redeem failed"); // 0 is the enum for no error | |
| EIP20NonStandardInterface underlyingToken = EIP20NonStandardInterface( | |
| token.underlying() | |
| ); | |
| uint underlyingBalance = underlyingToken.balanceOf(address(this)); | |
| underlyingToken.approve((address(swapRouter)), underlyingBalance); | |
| PoolShare[] memory _poolShares = poolShares; | |
| if (_poolShares.length == 1) { | |
| // if this is the last token, liquidate to WBTC instead | |
| liquidationParams[address(token)].isActive = true; | |
| liquidationParams[address(token)].inputAmounts[ | |
| xWBTC.underlying() | |
| ] = underlyingBalance; | |
| liquidationParams[address(token)].remainingOutputTokens = 1; | |
| emit Deactivated(); // dead man | |
| return; | |
| } | |
| uint256 liquidateShare = 0; | |
| uint256 liquidateIndex = 0; | |
| for (; liquidateIndex < _poolShares.length; liquidateIndex++) { | |
| if (_poolShares[liquidateIndex].token == token) { | |
| liquidateShare = _poolShares[liquidateIndex].share; | |
| break; | |
| } | |
| } | |
| Exp memory remainder = sub_( | |
| Exp({mantissa: 1e18}), | |
| Exp({mantissa: liquidateShare}) | |
| ); | |
| delete poolShares; | |
| liquidationParams[address(token)].isActive = true; | |
| liquidationParams[address(token)].remainingOutputTokens = | |
| _poolShares.length - | |
| 1; | |
| mapping(address => uint256) storage inputAmounts = liquidationParams[ | |
| address(token) | |
| ].inputAmounts; | |
| for (uint256 i = 0; i < _poolShares.length; i++) { | |
| if (i == liquidateIndex) { | |
| continue; | |
| } | |
| uint newShare = div_(Exp(_poolShares[i].share), remainder).mantissa; | |
| poolShares.push(PoolShare(_poolShares[i].token, newShare)); | |
| inputAmounts[address(_poolShares[i].token)] = mul_( | |
| Exp(newShare), | |
| Exp(underlyingBalance) | |
| ).mantissa; | |
| } | |
| isLiquidating = true; | |
| } | |
| function submitSwapRoute( | |
| bytes memory path, | |
| uint amount | |
| ) public nonReentrant { | |
| (address inputToken, address outputToken) = path.getFirstAndLastPool(); | |
| require(liquidationParams[inputToken].isActive, "Token not active!"); | |
| uint maxAmount = liquidationParams[inputToken].inputAmounts[ | |
| outputToken | |
| ]; | |
| require(amount <= maxAmount && maxAmount > 0, "Amount too high!"); | |
| uint8 decimalsIn = EIP20Interface(inputToken).decimals(); | |
| uint8 decimalsOut = EIP20Interface(outputToken).decimals(); | |
| uint amountOutMinimum = scaleToDecimal( | |
| mul_( | |
| div_( | |
| mul_( | |
| Exp({mantissa: amount}), | |
| Exp({mantissa: priceOracle.assetPrices(inputToken)}) | |
| ), | |
| Exp({mantissa: priceOracle.assetPrices(outputToken)}) | |
| ), | |
| Exp({mantissa: maxSlippage}) | |
| ).mantissa, | |
| decimalsIn, | |
| decimalsOut | |
| ); | |
| uint amountOut = swapRouter.exactInput( | |
| ISwapRouter.ExactInputParams( | |
| path, | |
| address(this), | |
| block.timestamp, | |
| amount, | |
| amountOutMinimum | |
| ) | |
| ); | |
| uint reward = div_(sub_(Exp(amountOut), Exp(amountOutMinimum)), 4) | |
| .mantissa; | |
| if (reward > 0) { | |
| EIP20NonStandardInterface(outputToken).transfer(msg.sender, reward); | |
| } | |
| liquidationParams[inputToken].inputAmounts[outputToken] = sub_( | |
| Exp(liquidationParams[inputToken].inputAmounts[outputToken]), | |
| Exp(amount) | |
| ).mantissa; | |
| if (liquidationParams[inputToken].inputAmounts[outputToken] == 0) { | |
| liquidationParams[inputToken].remainingOutputTokens--; | |
| if (liquidationParams[inputToken].remainingOutputTokens == 0) { | |
| liquidationParams[inputToken].isActive = false; | |
| isLiquidating = false; | |
| PoolShare[] memory _poolShares = poolShares; | |
| for (uint i = 0; i < _poolShares.length; i++) { | |
| if (outputToken == address(_poolShares[i].token)) { | |
| mintXTokens(_poolShares[i].token); | |
| return; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| function scaleToDecimal( | |
| uint256 amount, | |
| uint8 decimalsIn, | |
| uint8 decimalsOut | |
| ) internal pure returns (uint256) { | |
| if (decimalsIn > decimalsOut) { | |
| return | |
| div_( | |
| Exp(amount), | |
| 10 ** | |
| sub_( | |
| Exp(uint256(decimalsIn)), | |
| Exp(uint256(decimalsOut)) | |
| ).mantissa | |
| ).mantissa; | |
| } else { | |
| return | |
| mul_( | |
| Exp(amount), | |
| 10 ** | |
| sub_( | |
| Exp(uint256(decimalsOut)), | |
| Exp(uint256(decimalsIn)) | |
| ).mantissa | |
| ).mantissa; | |
| } | |
| } | |
| function mintXTokens(XErc20 supplyToken) internal { | |
| EIP20NonStandardInterface underlyingSupplyToken = EIP20NonStandardInterface( | |
| supplyToken.underlying() | |
| ); | |
| uint underlyingSupplyTokenBalance = underlyingSupplyToken.balanceOf( | |
| address(this) | |
| ); | |
| underlyingSupplyToken.approve( | |
| (address(supplyToken)), | |
| underlyingSupplyTokenBalance | |
| ); | |
| supplyToken.mint(underlyingSupplyTokenBalance); | |
| supplyToken.transfer( | |
| address(xsdInterface), | |
| supplyToken.balanceOf(address(this)) | |
| ); | |
| } | |
| /** | |
| * @notice Unbans a previously banned token | |
| * @dev Needs to be called by admin | |
| * @param token Token that should be enabled again | |
| */ | |
| function _unbanToken(XToken token) external { | |
| require(msg.sender == admin, "Only admin can enable banned Tokens!"); | |
| blacklist[address(token)] = false; | |
| emit Unbanned(address(token)); | |
| } | |
| // admin setter functions | |
| function _setUltimateLoanAddress(address ultimateLoanAddress_) external { | |
| require(msg.sender == admin, "Only admin can enable banned Tokens!"); | |
| address oldUltimateLoanAddress = ultimateLoanAddress; | |
| ultimateLoanAddress = ultimateLoanAddress_; | |
| emit UltimateLoanAddressChanged( | |
| oldUltimateLoanAddress, | |
| ultimateLoanAddress_ | |
| ); | |
| } | |
| function _setPriceOracle(address priceOracle_) external { | |
| require(msg.sender == admin, "Only admin can set the price oracle!"); | |
| address oldPriceOracle = address(priceOracle); | |
| priceOracle = PriceOracle(priceOracle_); | |
| emit PriceOracleChanged(oldPriceOracle, priceOracle_); | |
| } | |
| function _setLiquidityOracle(address liquidityOracle_) external { | |
| require( | |
| msg.sender == admin, | |
| "Only admin can set the liquidity oracle!" | |
| ); | |
| address oldLiquidityOracle = liquidityOracle; | |
| liquidityOracle = liquidityOracle_; | |
| emit LiquidityOracleChanged(oldLiquidityOracle, liquidityOracle_); | |
| } | |
| function _setComptroller(address comptroller_) external { | |
| require(msg.sender == admin, "Only admin can enable banned Tokens!"); | |
| address oldComptroller = address(comptroller); | |
| comptroller = Comptroller(comptroller_); | |
| emit ComptrollerChanged(oldComptroller, comptroller_); | |
| } | |
| /*** Reentrancy Guard ***/ | |
| /** | |
| * @dev Prevents a contract from calling itself, directly or indirectly. | |
| */ | |
| modifier nonReentrant() { | |
| require(_notEntered, "re-entered"); | |
| _notEntered = false; | |
| _; | |
| _notEntered = true; // get a gas-refund post-Istanbul | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment