From f2838bfdc170497bc2e7a9f0b35b73547187d658 Mon Sep 17 00:00:00 2001 From: JoscelynFarr Date: Fri, 26 Apr 2024 14:31:49 +0800 Subject: [PATCH] add moonwell oracle and liquidation contract --- src/FlashLoanLiquidateGM.sol | 106 ------------------------------- src/FlashLoanLiquidateMtoken.sol | 67 +++++++++++++++++++ src/oracle/MoonwellOracle.sol | 29 +++++++++ 3 files changed, 96 insertions(+), 106 deletions(-) delete mode 100644 src/FlashLoanLiquidateGM.sol create mode 100644 src/FlashLoanLiquidateMtoken.sol create mode 100644 src/oracle/MoonwellOracle.sol diff --git a/src/FlashLoanLiquidateGM.sol b/src/FlashLoanLiquidateGM.sol deleted file mode 100644 index 48e1b0e..0000000 --- a/src/FlashLoanLiquidateGM.sol +++ /dev/null @@ -1,106 +0,0 @@ -/* - Copyright 2022 JOJO Exchange - SPDX-License-Identifier: BUSL-1.1 -*/ - -pragma solidity ^0.8.19; - -import "@openzeppelin/contracts/access/Ownable.sol"; -import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import "./interfaces/IJUSDBank.sol"; -import "./libraries/SignedDecimalMath.sol"; - -contract FlashLoanLiquidateGM is Ownable { - using SafeERC20 for IERC20; - using SignedDecimalMath for uint256; - - address public immutable USDC; - address public immutable JUSD; - address public jusdBank; - address public insurance; - - mapping(address => bool) public whiteListAsset; - - struct LiquidateData { - uint256 actualCollateral; - uint256 insuranceFee; - uint256 actualLiquidatedT0; - uint256 actualLiquidated; - uint256 liquidatedRemainUSDC; - } - - constructor(address _jusdBank, address _USDC, address _JUSD, address _insurance) { - jusdBank = _jusdBank; - USDC = _USDC; - JUSD = _JUSD; - insurance = _insurance; - } - - modifier onlyBank() { - require(jusdBank == msg.sender, "Ownable: caller only can be JUSDBank"); - _; - } - - function setWhiteListAsset(address token, bool isValid) public onlyOwner { - whiteListAsset[token] = isValid; - } - - function JOJOFlashLoan(address asset, uint256, address to, bytes calldata param) external onlyBank { - (LiquidateData memory liquidateData, bytes memory liquidatorParam) = abi.decode(param, (LiquidateData, bytes)); - address liquidator; - assembly { - liquidator := mload(add(liquidatorParam, 20)) - } - require(whiteListAsset[asset], "asset is not in the whitelist"); - IERC20(asset).safeTransfer(liquidator, IERC20(asset).balanceOf(address(this))); - IERC20(JUSD).safeTransferFrom(liquidator, address(this), liquidateData.actualLiquidated); - IERC20(JUSD).approve(jusdBank, liquidateData.actualLiquidated); - IJUSDBank(jusdBank).repay(liquidateData.actualLiquidated, to); - - IERC20(USDC).safeTransferFrom( - liquidator, address(this), liquidateData.insuranceFee + liquidateData.liquidatedRemainUSDC - ); - IERC20(USDC).safeTransfer(insurance, liquidateData.insuranceFee); - if (liquidateData.liquidatedRemainUSDC != 0) { - IERC20(USDC).safeTransfer(address(jusdBank), liquidateData.liquidatedRemainUSDC); - } - } - - function getMultiCall( - address withdrawalVault, - address gmToken, - address receiver, - uint256 executionFee, - uint256 gmTokenAmount, - uint256[] memory minimumRecive - ) - public - pure - returns (bytes memory) - { - bytes[] memory param = new bytes[](3); - param[0] = abi.encodeWithSignature("sendWnt(address,uint256)", withdrawalVault, executionFee); - param[1] = abi.encodeWithSignature( - "sendTokens(address,address,uint256)", - gmToken, - withdrawalVault, - gmTokenAmount - ); - param[2] = abi.encodeWithSignature( - "createWithdrawal(address,address,address,address,address[],address[],uint256,uint256,bool,uint256,uint256)", - receiver, - 0x0000000000000000000000000000000000000000, - 0xff00000000000000000000000000000000000001, - gmToken, - new address[](0), - new address[](0), - minimumRecive[0], - minimumRecive[1], - false, - executionFee, - 0 - ); - return abi.encodeWithSignature("multicall(bytes[])", param); - } -} diff --git a/src/FlashLoanLiquidateMtoken.sol b/src/FlashLoanLiquidateMtoken.sol new file mode 100644 index 0000000..089245a --- /dev/null +++ b/src/FlashLoanLiquidateMtoken.sol @@ -0,0 +1,67 @@ +/* + Copyright 2022 JOJO Exchange + SPDX-License-Identifier: BUSL-1.1 +*/ + +pragma solidity ^0.8.19; + +import "@openzeppelin/contracts/access/Ownable.sol"; +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "./interfaces/IJUSDBank.sol"; +import "./interfaces/IJUSDExchange.sol"; +import "./libraries/SignedDecimalMath.sol"; + +interface MTokenInterface { + function redeem(uint redeemTokens) external returns (uint); +} + +contract FlashLoanLiquidateMtoken is Ownable { + using SafeERC20 for IERC20; + using SignedDecimalMath for uint256; + + address public immutable USDC; + address public immutable JUSD; + address public jusdBank; + address public jusdExchange; + address public insurance; + + struct LiquidateData { + uint256 actualCollateral; + uint256 insuranceFee; + uint256 actualLiquidatedT0; + uint256 actualLiquidated; + uint256 liquidatedRemainUSDC; + } + + constructor(address _jusdBank, address _jusdExchange, address _USDC, address _JUSD, address _insurance) { + jusdBank = _jusdBank; + jusdExchange = _jusdExchange; + USDC = _USDC; + JUSD = _JUSD; + insurance = _insurance; + } + + function JOJOFlashLoan(address asset, uint256 amount, address to, bytes calldata param) external { + (LiquidateData memory liquidateData, bytes memory originParam) = abi.decode(param, (LiquidateData, bytes)); + (address liquidator, uint256 minReceive) = + abi.decode(originParam, (address, uint256)); + + MTokenInterface(asset).redeem(amount); + uint256 USDCAmount = IERC20(USDC).balanceOf(address(this)); + require(USDCAmount >= minReceive, "receive amount is too small"); + IERC20(USDC).approve(jusdExchange, liquidateData.actualLiquidated); + IJUSDExchange(jusdExchange).buyJUSD(liquidateData.actualLiquidated, address(this)); + IERC20(JUSD).approve(jusdBank, liquidateData.actualLiquidated); + IJUSDBank(jusdBank).repay(liquidateData.actualLiquidated, to); + IERC20(USDC).safeTransfer(insurance, liquidateData.insuranceFee); + if (liquidateData.liquidatedRemainUSDC != 0) { + IERC20(USDC).safeTransfer(address(jusdBank), liquidateData.liquidatedRemainUSDC); + } + IERC20(USDC).safeTransfer( + liquidator, + USDCAmount - liquidateData.insuranceFee - liquidateData.actualLiquidated + - liquidateData.liquidatedRemainUSDC + ); + } +} diff --git a/src/oracle/MoonwellOracle.sol b/src/oracle/MoonwellOracle.sol new file mode 100644 index 0000000..87ef950 --- /dev/null +++ b/src/oracle/MoonwellOracle.sol @@ -0,0 +1,29 @@ +/* + Copyright 2022 JOJO Exchange + SPDX-License-Identifier: BUSL-1.1 +*/ + +pragma solidity ^0.8.19; + +import "@openzeppelin/contracts/access/Ownable.sol"; +import "@openzeppelin/contracts/utils/math/SafeCast.sol"; + +interface MTokenInterface { + function exchangeRateStored() external view returns (uint); +} + +contract MoonwellOracle { + uint256 public price; + address public immutable source; + string public description; + + constructor(string memory _description, address _source) { + description = _description; + source = _source; + } + + function getAssetPrice() external view returns (uint256) { + uint256 rate = MTokenInterface(source).exchangeRateStored(); + return rate; + } +}