|
| 1 | +// SPDX-License-Identifier: UNLICENSED |
| 2 | +pragma solidity ^0.8.13; |
| 3 | + |
| 4 | +import {IMorpho, MarketParams} from "../interfaces/IMorpho.sol"; |
| 5 | +import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; |
| 6 | +import {SignetStd} from "../SignetStd.sol"; |
| 7 | +import {RollupOrders} from "zenith/src/orders/RollupOrders.sol"; |
| 8 | +import {Passage} from "zenith/src/passage/Passage.sol"; |
| 9 | + |
| 10 | +// How this works: |
| 11 | +// - The Signet Orders system calls `transferFrom` and check for the presence |
| 12 | +// of a `Fill` event. |
| 13 | +// - The MorphoShortcut contract implements `transferFrom` to run |
| 14 | +// supply/repay logic on a specific Morpho Market. |
| 15 | +// - The rollup contract uses Orders to send tokens to the shortcut |
| 16 | +// and then call the shortcut to perform the action. |
| 17 | +// - This all occurs in the same block. |
| 18 | + |
| 19 | +// The rollup order is structured as follows: |
| 20 | +// - Input: X token on rollup chain. |
| 21 | +// - Output: transfer X token on host chain to shortcut. |
| 22 | +// - Output: invoke host chain shortcut contract. |
| 23 | +// |
| 24 | +// The Signet STF logic ensures that the Input is not delivered unless the |
| 25 | +// outputs are succesful. |
| 26 | + |
| 27 | +// Note: |
| 28 | +// We've provided a rollup contract for the example, but this could also be |
| 29 | +// initiated via a signed order from an EOA. No contract is needed. Both |
| 30 | +// contracts and EOAs can use the Morpho shortcut to interact with Morpho from |
| 31 | +// Signet. |
| 32 | + |
| 33 | +// Note: |
| 34 | +// The example order has no spread. Real orders can have a spread, and can also |
| 35 | +// have their inputs in ANY asset. I.e. the user can use rollup ETH to pay for |
| 36 | +// USDC collateral or repay a WBTC loan on the host Morpho market. This is |
| 37 | +// pretty neat. |
| 38 | + |
| 39 | +// The Rollup contract creates orders to interact with Morpho on host net via |
| 40 | +// the shortcut. |
| 41 | +contract UseMorpho is SignetStd { |
| 42 | + /// @dev Address of the shortcut on the host chain. |
| 43 | + address immutable repayShortcut; |
| 44 | + address immutable supplyShortcut; |
| 45 | + address immutable borrowShortcut; |
| 46 | + |
| 47 | + address immutable hostLoanToken; |
| 48 | + address immutable hostCollateralToken; |
| 49 | + |
| 50 | + IERC20 immutable ruLoanToken; |
| 51 | + IERC20 immutable ruCollateralToken; |
| 52 | + |
| 53 | + constructor(address _repayShortcut, address _supplyShortcut, address _hostLoan, address _hostCollateral) |
| 54 | + SignetStd() |
| 55 | + { |
| 56 | + repayShortcut = _repayShortcut; |
| 57 | + supplyShortcut = _supplyShortcut; |
| 58 | + |
| 59 | + // Autodetect rollup tokens based on token addresses on host network. |
| 60 | + hostLoanToken = _hostLoan; |
| 61 | + hostCollateralToken = _hostCollateral; |
| 62 | + if (_hostLoan == HOST_WETH) { |
| 63 | + ruLoanToken = WETH; |
| 64 | + } else if (_hostLoan == HOST_WBTC) { |
| 65 | + ruLoanToken = WBTC; |
| 66 | + } else if (_hostLoan == HOST_USDC || _hostLoan == HOST_USDT) { |
| 67 | + ruLoanToken = WUSD; |
| 68 | + } else { |
| 69 | + revert("Unsupported loan token"); |
| 70 | + } |
| 71 | + if (_hostCollateral == HOST_WETH) { |
| 72 | + ruCollateralToken = WETH; |
| 73 | + } else if (_hostCollateral == HOST_WBTC) { |
| 74 | + ruCollateralToken = WBTC; |
| 75 | + } else if (_hostCollateral == HOST_USDC || _hostCollateral == HOST_USDT) { |
| 76 | + ruCollateralToken = WUSD; |
| 77 | + } else { |
| 78 | + revert("Unsupported collateral token"); |
| 79 | + } |
| 80 | + |
| 81 | + // Pre-emptively approve the Orders contract to spend our tokens. |
| 82 | + ruLoanToken.approve(address(ORDERS), type(uint256).max); |
| 83 | + ruCollateralToken.approve(address(ORDERS), type(uint256).max); |
| 84 | + } |
| 85 | + |
| 86 | + // Supply some amount of the collateral token on behalf of the user. |
| 87 | + function supplyCollateral(address onBehalf, uint256 amount) public { |
| 88 | + if (amount > 0) { |
| 89 | + ruCollateralToken.transferFrom(msg.sender, address(this), amount); |
| 90 | + } |
| 91 | + |
| 92 | + // the amount is whatever our current balance is |
| 93 | + amount = ruCollateralToken.balanceOf(address(this)); |
| 94 | + |
| 95 | + RollupOrders.Input[] memory inputs = new RollupOrders.Input[](1); |
| 96 | + inputs[0] = makeInput(address(ruCollateralToken), amount); |
| 97 | + |
| 98 | + // The first output pays the collateral token to the shortcut. |
| 99 | + // The second output calls the shortcut to supply the collateral. |
| 100 | + RollupOrders.Output[] memory outputs = new RollupOrders.Output[](2); |
| 101 | + outputs[0] = makeHostOutput(address(hostCollateralToken), amount, supplyShortcut); |
| 102 | + outputs[1] = makeHostOutput(supplyShortcut, amount, onBehalf); |
| 103 | + |
| 104 | + ORDERS.initiate( |
| 105 | + block.timestamp, // no deadline |
| 106 | + inputs, |
| 107 | + outputs |
| 108 | + ); |
| 109 | + } |
| 110 | + |
| 111 | + // Repay some amount of the loan token on behalf of the user. |
| 112 | + function repay(address onBehalf, uint256 amount) public { |
| 113 | + if (amount > 0) { |
| 114 | + ruLoanToken.transferFrom(msg.sender, address(this), amount); |
| 115 | + } |
| 116 | + |
| 117 | + // Send all tokens. |
| 118 | + amount = ruLoanToken.balanceOf(address(this)); |
| 119 | + |
| 120 | + RollupOrders.Input[] memory inputs = new RollupOrders.Input[](1); |
| 121 | + inputs[0] = makeInput(address(ruLoanToken), amount); |
| 122 | + |
| 123 | + // The first output pays the loan token to the shortcut. |
| 124 | + // The second output calls the shortcut to repay the loan. |
| 125 | + RollupOrders.Output[] memory outputs = new RollupOrders.Output[](2); |
| 126 | + outputs[0] = makeHostOutput(address(hostLoanToken), amount, repayShortcut); |
| 127 | + outputs[1] = makeHostOutput(repayShortcut, amount, onBehalf); |
| 128 | + |
| 129 | + ORDERS.initiate( |
| 130 | + block.timestamp, // no deadline |
| 131 | + inputs, |
| 132 | + outputs |
| 133 | + ); |
| 134 | + } |
| 135 | + |
| 136 | + // Borrow some amount of the loan token and send it to the rollup. |
| 137 | + function borrow(address onBehalf, uint256 amount) public { |
| 138 | + RollupOrders.Input[] memory inputs = new RollupOrders.Input[](0); |
| 139 | + |
| 140 | + // The first output calls the shortcut to borrow the loan. |
| 141 | + // The second output sends the loan token to the user on the rollup. |
| 142 | + RollupOrders.Output[] memory outputs = new RollupOrders.Output[](2); |
| 143 | + outputs[0] = makeHostOutput(borrowShortcut, amount, onBehalf); |
| 144 | + outputs[1] = makeRollupOutput(address(ruLoanToken), amount, msg.sender); |
| 145 | + |
| 146 | + ORDERS.initiate( |
| 147 | + block.timestamp, // no deadline |
| 148 | + inputs, |
| 149 | + outputs |
| 150 | + ); |
| 151 | + } |
| 152 | + |
| 153 | + // Supply collateral and then borrow against it. |
| 154 | + function supplyCollateralBorrow(address onBehalf, uint256 supplyAmnt, uint256 borrowAmnt) external { |
| 155 | + // Note: this could be more gas efficient by combining into a single |
| 156 | + // order. |
| 157 | + supplyCollateral(onBehalf, supplyAmnt); |
| 158 | + borrow(onBehalf, borrowAmnt); |
| 159 | + } |
| 160 | +} |
| 161 | + |
| 162 | +abstract contract HostMorphoUser { |
| 163 | + IMorpho immutable morpho; |
| 164 | + |
| 165 | + // This is an unrolled MarketParams struct. |
| 166 | + IERC20 immutable loanToken; |
| 167 | + IERC20 immutable collateralToken; |
| 168 | + |
| 169 | + address immutable oracle; |
| 170 | + address immutable irm; |
| 171 | + uint256 immutable lltv; |
| 172 | + |
| 173 | + error InsufficentTokensReceived(uint256 received, uint256 required); |
| 174 | + |
| 175 | + constructor(IMorpho _morpho, MarketParams memory _params) { |
| 176 | + morpho = _morpho; |
| 177 | + |
| 178 | + loanToken = IERC20(_params.loanToken); |
| 179 | + collateralToken = IERC20(_params.collateralToken); |
| 180 | + oracle = _params.oracle; |
| 181 | + irm = _params.irm; |
| 182 | + lltv = _params.lltv; |
| 183 | + |
| 184 | + loanToken.approve(address(_morpho), type(uint256).max); |
| 185 | + collateralToken.approve(address(_morpho), type(uint256).max); |
| 186 | + } |
| 187 | + |
| 188 | + function checkReceived(uint256 received, uint256 required) internal pure { |
| 189 | + if (received < required) { |
| 190 | + revert InsufficentTokensReceived(received, required); |
| 191 | + } |
| 192 | + } |
| 193 | + |
| 194 | + function loadParams() internal view returns (MarketParams memory params) { |
| 195 | + params.loanToken = address(loanToken); |
| 196 | + params.collateralToken = address(collateralToken); |
| 197 | + params.oracle = oracle; |
| 198 | + params.irm = irm; |
| 199 | + params.lltv = lltv; |
| 200 | + } |
| 201 | +} |
| 202 | + |
| 203 | +// This contract should be deployed on the host chain. It is used as a shortcut |
| 204 | +// to supply collateral to Morpho and can be invoked by the rollup via an Order. |
| 205 | +contract HostMorphoRepay is HostMorphoUser { |
| 206 | + constructor(IMorpho _morpho, MarketParams memory _params) HostMorphoUser(_morpho, _params) {} |
| 207 | + |
| 208 | + /// Uses the ERC20 transferFrom interface to invoke contract logic. This |
| 209 | + /// allows us to invoke logic from the Orders contract |
| 210 | + function transferFrom(address, address recipient, uint256 amount) external returns (bool) { |
| 211 | + uint256 loanTokenBalance = loanToken.balanceOf(address(this)); |
| 212 | + checkReceived(loanTokenBalance, amount); |
| 213 | + morpho.repay(loadParams(), loanTokenBalance, 0, recipient, ""); |
| 214 | + return true; |
| 215 | + } |
| 216 | +} |
| 217 | + |
| 218 | +contract HostMorphoSupply is HostMorphoUser { |
| 219 | + constructor(IMorpho _morpho, MarketParams memory _params) HostMorphoUser(_morpho, _params) {} |
| 220 | + |
| 221 | + /// Uses the ERC20 transferFrom interface to invoke contract logic. This |
| 222 | + /// allows us to invoke logic from the Orders contract |
| 223 | + function transferFrom(address, address recipient, uint256 amount) external returns (bool) { |
| 224 | + uint256 collateralTokenBalance = collateralToken.balanceOf(address(this)); |
| 225 | + |
| 226 | + checkReceived(collateralTokenBalance, amount); |
| 227 | + |
| 228 | + morpho.supplyCollateral(loadParams(), collateralTokenBalance, recipient, ""); |
| 229 | + |
| 230 | + // Future extension: |
| 231 | + // borrow some amount of loanToken |
| 232 | + // and send it to the rollup |
| 233 | + |
| 234 | + return true; |
| 235 | + } |
| 236 | +} |
| 237 | + |
| 238 | +contract HosyMorphoBorrow is HostMorphoUser { |
| 239 | + constructor(IMorpho _morpho, MarketParams memory _params) HostMorphoUser(_morpho, _params) {} |
| 240 | + |
| 241 | + // This function |
| 242 | + function calculateBorrow() internal pure returns (uint256 tokens) { |
| 243 | + return 0; |
| 244 | + } |
| 245 | + |
| 246 | + function transferFrom(address filler, address onBehalf, uint256 amount) external returns (bool) { |
| 247 | + // borrow some amount of loanToken |
| 248 | + morpho.borrow(loadParams(), amount, 0, onBehalf, filler); |
| 249 | + return true; |
| 250 | + } |
| 251 | +} |
0 commit comments