-
Notifications
You must be signed in to change notification settings - Fork 44
✨ adapter: use morpho flash loans #817
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
patitonar
wants to merge
1
commit into
main
Choose a base branch
from
morpho-flashloan-adapter
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "@exactly/protocol": patch | ||
| --- | ||
|
|
||
| ✨ adapter: use morpho flash loans |
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,108 +1,61 @@ | ||
| // SPDX-License-Identifier: GPL-3.0 | ||
| pragma solidity ^0.8.20; | ||
|
|
||
| import { AccessControl } from "@openzeppelin/contracts-v4/access/AccessControl.sol"; | ||
| import { IERC20 } from "@openzeppelin/contracts-v4/interfaces/IERC20.sol"; | ||
| import { IERC4626 } from "@openzeppelin/contracts-v4/interfaces/IERC4626.sol"; | ||
| import { SafeTransferLib } from "solady/src/utils/SafeTransferLib.sol"; | ||
|
|
||
| import { IFlashLoaner, IFlashLoanRecipient } from "./DebtRoller.sol"; | ||
|
|
||
| /// @title FlashLoanAdapter | ||
| /// @notice Adapter to use Balancer V3 vault with Balancer V2 interface. | ||
| /// Uses wrapped tokens when there's not enough liquidity in the vault. | ||
| contract FlashLoanAdapter is AccessControl { | ||
| IBalancerVaultV3 public immutable vault; | ||
| /// @notice Adapter to use Morpho flash loans with the `IFlashLoaner` interface. | ||
| /// Morpho flash loans are free, so reported fees are always zero. | ||
| contract FlashLoanAdapter is IFlashLoaner { | ||
| using SafeTransferLib for address; | ||
|
|
||
| /// @notice Mapping to check if a token is a wrapped token. | ||
| mapping(IERC20 wToken => bool isWToken) public isWToken; | ||
| /// @notice Mapping to get the wrapped token for an asset. | ||
| mapping(IERC20 asset => IERC4626 wToken) public wTokens; | ||
| /// @notice Morpho contract that provides the flash loans. | ||
| IMorpho public immutable morpho; | ||
|
|
||
| constructor(IBalancerVaultV3 vault_, address owner) { | ||
| _grantRole(DEFAULT_ADMIN_ROLE, owner); | ||
| vault = vault_; | ||
| constructor(IMorpho morpho_) { | ||
| morpho = morpho_; | ||
| } | ||
|
|
||
| /// @notice Performs a flash loan using the Balancer V3 vault. | ||
| /// @notice Performs a flash loan using Morpho. | ||
| /// @param recipient The address to receive the flash loan. | ||
| /// @param tokens The tokens to borrow. | ||
| /// @param amounts The amounts to borrow. | ||
| /// @param data Additional data to pass to the recipient. | ||
| // solhint-disable-next-line gas-calldata-parameters | ||
| function flashLoan(address recipient, IERC20[] memory tokens, uint256[] memory amounts, bytes memory data) external { | ||
| if (tokens.length != 1 || amounts.length != 1) revert InvalidLength(); | ||
| uint256[] memory fees = new uint256[](1); | ||
| if (tokens[0].balanceOf(address(vault)) < amounts[0]) { | ||
| IERC4626 wToken = wTokens[tokens[0]]; | ||
| if (wToken == IERC4626(address(0)) || wToken.convertToAssets(wToken.balanceOf(address(vault))) < amounts[0]) { | ||
| revert InsufficientLiquidity(); | ||
| } | ||
|
|
||
| tokens[0] = IERC20(address(wToken)); | ||
| uint256 shares = wToken.previewWithdraw(amounts[0]); | ||
| fees[0] = wToken.previewMint(shares) - amounts[0]; | ||
| amounts[0] = shares; | ||
| } | ||
| vault.unlock( | ||
| abi.encodeWithSelector(this.receiveFlashLoan.selector, abi.encode(recipient, tokens, amounts, fees, data)) | ||
| ); | ||
| morpho.flashLoan(address(tokens[0]), amounts[0], abi.encode(recipient, tokens, amounts, data)); | ||
| } | ||
|
|
||
| /// @notice Receives a flash loan from the Balancer V3 vault. | ||
| /// @param payload The payload containing the recipient, tokens, amounts, fees, and data. | ||
| function receiveFlashLoan(bytes calldata payload) external { | ||
| if (msg.sender != address(vault)) revert UnauthorizedVault(); | ||
| ( | ||
| address recipient, | ||
| IERC20[] memory tokens, | ||
| uint256[] memory amounts, | ||
| uint256[] memory fees, | ||
| bytes memory data | ||
| ) = abi.decode(payload, (address, IERC20[], uint256[], uint256[], bytes)); | ||
| /// @notice Receives a flash loan from Morpho, forwards it to the recipient and approves the repayment. | ||
| /// @param payload The payload containing the recipient, tokens, amounts, and data. | ||
| function onMorphoFlashLoan(uint256, bytes calldata payload) external { | ||
| if (msg.sender != address(morpho)) revert UnauthorizedMorpho(); | ||
| (address recipient, IERC20[] memory tokens, uint256[] memory amounts, bytes memory data) = abi.decode( | ||
| payload, | ||
| (address, IERC20[], uint256[], bytes) | ||
| ); | ||
| address token = address(tokens[0]); | ||
|
|
||
| if (isWToken[tokens[0]]) { | ||
| IERC4626 wToken = IERC4626(address(tokens[0])); | ||
| vault.sendTo(tokens[0], address(this), amounts[0]); | ||
| tokens[0] = IERC20(wToken.asset()); | ||
| amounts[0] = wToken.redeem(amounts[0], recipient, address(this)); | ||
| IFlashLoanRecipient(recipient).receiveFlashLoan(tokens, amounts, fees, data); | ||
| token.safeTransfer(recipient, amounts[0]); | ||
| IFlashLoanRecipient(recipient).receiveFlashLoan(tokens, amounts, new uint256[](1), data); | ||
|
|
||
| tokens[0].approve(address(wToken), amounts[0] + fees[0]); | ||
| wToken.deposit(amounts[0] + fees[0], address(vault)); | ||
| vault.settle(wToken, amounts[0]); | ||
| } else { | ||
| vault.sendTo(tokens[0], recipient, amounts[0]); | ||
| IFlashLoanRecipient(recipient).receiveFlashLoan(tokens, amounts, fees, data); | ||
| tokens[0].transfer(address(vault), amounts[0]); | ||
| vault.settle(tokens[0], amounts[0]); | ||
| } | ||
| token.safeApprove(address(morpho), amounts[0]); | ||
| } | ||
|
|
||
| /// @notice Sets a wrapped token for an asset. | ||
| /// @param asset The asset to set the wrapped token for. | ||
| /// @param token The wrapped token to set. | ||
| function setWToken(IERC20 asset, IERC4626 token) external onlyRole(DEFAULT_ADMIN_ROLE) { | ||
| wTokens[asset] = token; | ||
| isWToken[token] = true; | ||
| emit WTokenSet(asset, token, msg.sender); | ||
| /// @notice Returns the amount of `token` available to be flash loaned. | ||
| /// @param token The token to check. | ||
| function available(IERC20 token) external view returns (uint256) { | ||
| return token.balanceOf(address(morpho)); | ||
| } | ||
| } | ||
|
|
||
| interface IBalancerVaultV3 { | ||
| function sendTo(IERC20 token, address to, uint256 amount) external; | ||
| function settle(IERC20 token, uint256 amountHint) external returns (uint256 credit); | ||
| function unlock(bytes calldata data) external returns (bytes memory); | ||
| interface IMorpho { | ||
| function flashLoan(address token, uint256 assets, bytes calldata data) external; | ||
| } | ||
|
|
||
| interface IFlashLoanRecipient { | ||
| function receiveFlashLoan( | ||
| IERC20[] memory tokens, | ||
| uint256[] memory amounts, | ||
| uint256[] memory fees, | ||
| bytes calldata data | ||
| ) external; | ||
| } | ||
|
|
||
| error InsufficientLiquidity(); | ||
| error InvalidLength(); | ||
| error UnauthorizedVault(); | ||
|
|
||
| event WTokenSet(IERC20 indexed asset, IERC4626 indexed wToken, address indexed account); | ||
| error UnauthorizedMorpho(); | ||
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
This file was deleted.
Oops, something went wrong.
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| { | ||
| "address": "0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb" | ||
| } |
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| { | ||
| "address": "0xce95AfbB8EA029495c66020883F87aaE8864AF92" | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.