From c779e24fbc5f64d401bca31848596da434993d07 Mon Sep 17 00:00:00 2001 From: Rubilmax Date: Mon, 24 Jul 2023 15:51:24 +0200 Subject: [PATCH 01/12] feat(flash-loan): add flash loan --- src/Blue.sol | 21 +++++++++++++++++++++ src/interfaces/IFlashBorrower.sol | 18 ++++++++++++++++++ src/libraries/Errors.sol | 2 ++ 3 files changed, 41 insertions(+) create mode 100644 src/interfaces/IFlashBorrower.sol diff --git a/src/Blue.sol b/src/Blue.sol index 9c864cfde..6977b7dbd 100644 --- a/src/Blue.sol +++ b/src/Blue.sol @@ -3,6 +3,7 @@ pragma solidity 0.8.20; import {IIrm} from "src/interfaces/IIrm.sol"; import {IERC20} from "src/interfaces/IERC20.sol"; +import {IFlashBorrower} from "src/interfaces/IFlashBorrower.sol"; import {Errors} from "./libraries/Errors.sol"; import {SharesMath} from "src/libraries/SharesMath.sol"; @@ -13,6 +14,9 @@ import {SafeTransferLib} from "src/libraries/SafeTransferLib.sol"; uint256 constant WAD = 1e18; uint256 constant ALPHA = 0.5e18; +/// @dev The expected success hash returned by the FlashBorrower. +bytes32 constant FLASH_BORROWER_SUCCESS_HASH = keccak256("FlashBorrower.onFlashLoan"); + contract Blue { using SharesMath for uint256; using FixedPointMathLib for uint256; @@ -244,6 +248,23 @@ contract Blue { market.borrowableAsset.safeTransferFrom(msg.sender, address(this), repaid); } + // Flash Loans. + + function flashLoan(IFlashBorrower receiver, IERC20 token, uint256 amount, bytes calldata data) + public + virtual + returns (bytes memory) + { + token.safeTransfer(address(receiver), amount); + + (bytes32 successHash, bytes memory returnData) = receiver.onFlashLoan(msg.sender, token, amount, data); + require(successHash == FLASH_BORROWER_SUCCESS_HASH, Errors.INVALID_SUCCESS_HASH); + + token.safeTransferFrom(address(receiver), address(this), amount); + + return returnData; + } + // Position management. function setApproval(address manager, bool isAllowed) external { diff --git a/src/interfaces/IFlashBorrower.sol b/src/interfaces/IFlashBorrower.sol new file mode 100644 index 000000000..543ff4ec7 --- /dev/null +++ b/src/interfaces/IFlashBorrower.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.0; + +import {IERC20} from "src/interfaces/IERC20.sol"; + +/// @dev Interface of the FlashBorrower, inspired by https://eips.ethereum.org/EIPS/eip-3156. +/// The FlashLender's `flashLoan` function now returns the FlashBorrower's return data. +interface IFlashBorrower { + /// @dev Receive a flash loan. + /// @param initiator The initiator of the loan. + /// @param token The loan currency. + /// @param amount The amount of tokens lent. + /// @param data Arbitrary data structure, intended to contain user-defined parameters. + /// @return The keccak256 hash of "FlashBorrower.onFlashLoan" and any additional arbitrary data. + function onFlashLoan(address initiator, IERC20 token, uint256 amount, bytes calldata data) + external + returns (bytes32, bytes memory); +} diff --git a/src/libraries/Errors.sol b/src/libraries/Errors.sol index c7c2235d8..ca5d27fe2 100644 --- a/src/libraries/Errors.sol +++ b/src/libraries/Errors.sol @@ -23,4 +23,6 @@ library Errors { string internal constant INSUFFICIENT_LIQUIDITY = "insufficient liquidity"; string internal constant HEALTHY_POSITION = "position is healthy"; + + string internal constant INVALID_SUCCESS_HASH = "invalid success hash"; } From 33c390b68675a6d895c46d5f4d3a1d0b85c9faae Mon Sep 17 00:00:00 2001 From: Rubilmax Date: Mon, 24 Jul 2023 16:07:31 +0200 Subject: [PATCH 02/12] test(flash-loan): add flash borrower mock --- src/Blue.sol | 5 +--- src/interfaces/IFlashBorrower.sol | 5 +++- src/interfaces/IFlashLender.sol | 18 +++++++++++++++ src/libraries/SafeTransferLib.sol | 29 +++++++++++++++++++++++ src/mocks/FlashBorrowerMock.sol | 38 +++++++++++++++++++++++++++++++ test/hardhat/Blue.spec.ts | 16 +++++++++++++ 6 files changed, 106 insertions(+), 5 deletions(-) create mode 100644 src/interfaces/IFlashLender.sol create mode 100644 src/mocks/FlashBorrowerMock.sol diff --git a/src/Blue.sol b/src/Blue.sol index 6977b7dbd..d6ab32758 100644 --- a/src/Blue.sol +++ b/src/Blue.sol @@ -3,7 +3,7 @@ pragma solidity 0.8.20; import {IIrm} from "src/interfaces/IIrm.sol"; import {IERC20} from "src/interfaces/IERC20.sol"; -import {IFlashBorrower} from "src/interfaces/IFlashBorrower.sol"; +import {IFlashBorrower, FLASH_BORROWER_SUCCESS_HASH} from "src/interfaces/IFlashBorrower.sol"; import {Errors} from "./libraries/Errors.sol"; import {SharesMath} from "src/libraries/SharesMath.sol"; @@ -14,9 +14,6 @@ import {SafeTransferLib} from "src/libraries/SafeTransferLib.sol"; uint256 constant WAD = 1e18; uint256 constant ALPHA = 0.5e18; -/// @dev The expected success hash returned by the FlashBorrower. -bytes32 constant FLASH_BORROWER_SUCCESS_HASH = keccak256("FlashBorrower.onFlashLoan"); - contract Blue { using SharesMath for uint256; using FixedPointMathLib for uint256; diff --git a/src/interfaces/IFlashBorrower.sol b/src/interfaces/IFlashBorrower.sol index 543ff4ec7..a5b41d482 100644 --- a/src/interfaces/IFlashBorrower.sol +++ b/src/interfaces/IFlashBorrower.sol @@ -1,8 +1,11 @@ -// SPDX-License-Identifier: AGPL-3.0-only +// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.0; import {IERC20} from "src/interfaces/IERC20.sol"; +/// @dev The expected success hash returned by the FlashBorrower. +bytes32 constant FLASH_BORROWER_SUCCESS_HASH = keccak256("FlashBorrower.onFlashLoan"); + /// @dev Interface of the FlashBorrower, inspired by https://eips.ethereum.org/EIPS/eip-3156. /// The FlashLender's `flashLoan` function now returns the FlashBorrower's return data. interface IFlashBorrower { diff --git a/src/interfaces/IFlashLender.sol b/src/interfaces/IFlashLender.sol new file mode 100644 index 000000000..59e21c675 --- /dev/null +++ b/src/interfaces/IFlashLender.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.0; + +import {IERC20} from "src/interfaces/IERC20.sol"; +import {IFlashBorrower} from "./IFlashBorrower.sol"; + +/// @dev Interface of the Flash Lender, inspired by https://eips.ethereum.org/EIPS/eip-3156. +/// The FlashLender's `flashLoan` function now returns the FlashBorrower's return data. +interface IFlashLender { + /// @dev Initiate a flash loan. + /// @param receiver The receiver of the tokens in the loan, and the receiver of the callback. + /// @param token The loan currency. + /// @param amount The amount of tokens lent. + /// @param data Arbitrary data structure, intended to contain user-defined parameters. + function flashLoan(IFlashBorrower receiver, IERC20 token, uint256 amount, bytes calldata data) + external + returns (bytes memory); +} diff --git a/src/libraries/SafeTransferLib.sol b/src/libraries/SafeTransferLib.sol index 016cd598a..eba51ba20 100644 --- a/src/libraries/SafeTransferLib.sol +++ b/src/libraries/SafeTransferLib.sol @@ -66,4 +66,33 @@ library SafeTransferLib { require(success, "TRANSFER_FAILED"); } + + function safeApprove(IERC20 token, address to, uint256 amount) internal { + bool success; + + /// @solidity memory-safe-assembly + assembly { + // Get a pointer to some free memory. + let freeMemoryPointer := mload(0x40) + + // Write the abi-encoded calldata into memory, beginning with the function selector. + mstore(freeMemoryPointer, 0x095ea7b300000000000000000000000000000000000000000000000000000000) + mstore(add(freeMemoryPointer, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument. + mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type. + + success := + and( + // Set success to whether the call reverted, if not we check it either + // returned exactly 1 (can't just be non-zero data), or had no return data. + or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())), + // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2. + // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space. + // Counterintuitively, this call must be positioned second to the or() call in the + // surrounding and() call or else returndatasize() will be zero during the computation. + call(gas(), token, 0, freeMemoryPointer, 68, 0, 32) + ) + } + + require(success, "APPROVE_FAILED"); + } } diff --git a/src/mocks/FlashBorrowerMock.sol b/src/mocks/FlashBorrowerMock.sol new file mode 100644 index 000000000..9e64eed63 --- /dev/null +++ b/src/mocks/FlashBorrowerMock.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.0; + +import {IERC20} from "src/interfaces/IERC20.sol"; +import {IFlashLender} from "src/interfaces/IFlashLender.sol"; +import {IFlashBorrower, FLASH_BORROWER_SUCCESS_HASH} from "src/interfaces/IFlashBorrower.sol"; + +import {SafeTransferLib} from "src/libraries/SafeTransferLib.sol"; + +contract FlashBorrowerMock is IFlashBorrower { + using SafeTransferLib for IERC20; + + IFlashLender private immutable _LENDER; + + constructor(IFlashLender lender) { + _LENDER = lender; + } + + /* EXTERNAL */ + + function flashLoan(IERC20 token, uint256 amount, bytes calldata data) external virtual returns (bytes memory) { + return _LENDER.flashLoan(this, token, amount, data); + } + + /// @inheritdoc IFlashBorrower + function onFlashLoan(address initiator, IERC20 token, uint256 amount, bytes calldata) + external + virtual + returns (bytes32, bytes memory) + { + require(msg.sender == address(_LENDER), "invalid lender"); + require(initiator == address(this), "invalid initiator"); + + token.safeApprove(address(_LENDER), amount); + + return (FLASH_BORROWER_SUCCESS_HASH, bytes("")); + } +} diff --git a/test/hardhat/Blue.spec.ts b/test/hardhat/Blue.spec.ts index 08193f88e..bc92c7974 100644 --- a/test/hardhat/Blue.spec.ts +++ b/test/hardhat/Blue.spec.ts @@ -5,6 +5,7 @@ import { expect } from "chai"; import { BigNumber, constants, utils } from "ethers"; import hre from "hardhat"; import { Blue, OracleMock, ERC20Mock, IrmMock } from "types"; +import { FlashBorrowerMock } from "types/src/mocks/FlashBorrowerMock"; const closePositions = false; const initBalance = constants.MaxUint256.div(2); @@ -51,6 +52,8 @@ describe("Blue", () => { let nbLiquidations: number; + let flashBorrower: FlashBorrowerMock; + const updateMarket = (newMarket: Partial) => { market = { ...market, ...newMarket }; id = identifier(market); @@ -110,6 +113,10 @@ describe("Blue", () => { await borrowable.setBalance(liquidator.address, initBalance); await borrowable.connect(liquidator).approve(blue.address, constants.MaxUint256); + + const FlashBorrowerFactory = await hre.ethers.getContractFactory("FlashBorrowerMock", admin); + + flashBorrower = await FlashBorrowerFactory.deploy(blue.address); }); it("should simulate gas cost [main]", async () => { @@ -185,4 +192,13 @@ describe("Blue", () => { await borrowableOracle.setPrice(BigNumber.WAD); } }); + + it("should simuate gas cost [flashloan]", async () => { + const user = signers[0]; + const amount = BigNumber.WAD; + + await blue.connect(user).supply(market, amount, user.address); + + await flashBorrower.flashLoan(borrowable.address, amount.div(2), []); + }); }); From 4f498386be16dea5ca90dd044f8a63788e77c144 Mon Sep 17 00:00:00 2001 From: Romain Milon Date: Tue, 25 Jul 2023 09:55:51 +0200 Subject: [PATCH 03/12] refactor(flash-loan): remove virtual modifier Co-authored-by: MathisGD <74971347+MathisGD@users.noreply.github.com> Signed-off-by: Romain Milon --- src/Blue.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Blue.sol b/src/Blue.sol index d6ab32758..899d746f7 100644 --- a/src/Blue.sol +++ b/src/Blue.sol @@ -249,7 +249,6 @@ contract Blue { function flashLoan(IFlashBorrower receiver, IERC20 token, uint256 amount, bytes calldata data) public - virtual returns (bytes memory) { token.safeTransfer(address(receiver), amount); From f36fdf095abd7f74d96f6d6d4b9a1ac6daff5138 Mon Sep 17 00:00:00 2001 From: Romain Milon Date: Tue, 25 Jul 2023 09:56:18 +0200 Subject: [PATCH 04/12] docs(flash-loan): modify natspecs Co-authored-by: MathisGD <74971347+MathisGD@users.noreply.github.com> Signed-off-by: Romain Milon --- src/interfaces/IFlashBorrower.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/interfaces/IFlashBorrower.sol b/src/interfaces/IFlashBorrower.sol index a5b41d482..71e12b9e4 100644 --- a/src/interfaces/IFlashBorrower.sol +++ b/src/interfaces/IFlashBorrower.sol @@ -13,7 +13,7 @@ interface IFlashBorrower { /// @param initiator The initiator of the loan. /// @param token The loan currency. /// @param amount The amount of tokens lent. - /// @param data Arbitrary data structure, intended to contain user-defined parameters. + /// @param data Arbitrary data, intended to contain user-defined parameters. /// @return The keccak256 hash of "FlashBorrower.onFlashLoan" and any additional arbitrary data. function onFlashLoan(address initiator, IERC20 token, uint256 amount, bytes calldata data) external From cf7e86b5a7b470423dc2b42417798aecb4cfb891 Mon Sep 17 00:00:00 2001 From: Romain Milon Date: Tue, 25 Jul 2023 10:05:36 +0200 Subject: [PATCH 05/12] docs(flash-borrower): update natspecs Co-authored-by: Merlin Egalite <44097430+MerlinEgalite@users.noreply.github.com> Signed-off-by: Romain Milon --- src/interfaces/IFlashBorrower.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/interfaces/IFlashBorrower.sol b/src/interfaces/IFlashBorrower.sol index 71e12b9e4..a026c138c 100644 --- a/src/interfaces/IFlashBorrower.sol +++ b/src/interfaces/IFlashBorrower.sol @@ -9,7 +9,7 @@ bytes32 constant FLASH_BORROWER_SUCCESS_HASH = keccak256("FlashBorrower.onFlashL /// @dev Interface of the FlashBorrower, inspired by https://eips.ethereum.org/EIPS/eip-3156. /// The FlashLender's `flashLoan` function now returns the FlashBorrower's return data. interface IFlashBorrower { - /// @dev Receive a flash loan. + /// @dev Receives a flash loan. /// @param initiator The initiator of the loan. /// @param token The loan currency. /// @param amount The amount of tokens lent. From c6d46f3b64d5c9cdd6699197883611fb9c4c1fa0 Mon Sep 17 00:00:00 2001 From: Romain Milon Date: Tue, 25 Jul 2023 10:06:00 +0200 Subject: [PATCH 06/12] docs(flash-borrower): update natspecs Co-authored-by: Merlin Egalite <44097430+MerlinEgalite@users.noreply.github.com> Signed-off-by: Romain Milon --- src/interfaces/IFlashBorrower.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/interfaces/IFlashBorrower.sol b/src/interfaces/IFlashBorrower.sol index a026c138c..259ff7ba0 100644 --- a/src/interfaces/IFlashBorrower.sol +++ b/src/interfaces/IFlashBorrower.sol @@ -11,7 +11,7 @@ bytes32 constant FLASH_BORROWER_SUCCESS_HASH = keccak256("FlashBorrower.onFlashL interface IFlashBorrower { /// @dev Receives a flash loan. /// @param initiator The initiator of the loan. - /// @param token The loan currency. + /// @param token The token lent. /// @param amount The amount of tokens lent. /// @param data Arbitrary data, intended to contain user-defined parameters. /// @return The keccak256 hash of "FlashBorrower.onFlashLoan" and any additional arbitrary data. From eee3cfdf9883ab7ccbfa264ac51cfe93661b8c28 Mon Sep 17 00:00:00 2001 From: Romain Milon Date: Tue, 25 Jul 2023 10:06:15 +0200 Subject: [PATCH 07/12] docs(flash-lender): update natspecs Co-authored-by: Merlin Egalite <44097430+MerlinEgalite@users.noreply.github.com> Signed-off-by: Romain Milon --- src/interfaces/IFlashLender.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/interfaces/IFlashLender.sol b/src/interfaces/IFlashLender.sol index 59e21c675..6c91e2eb7 100644 --- a/src/interfaces/IFlashLender.sol +++ b/src/interfaces/IFlashLender.sol @@ -7,7 +7,7 @@ import {IFlashBorrower} from "./IFlashBorrower.sol"; /// @dev Interface of the Flash Lender, inspired by https://eips.ethereum.org/EIPS/eip-3156. /// The FlashLender's `flashLoan` function now returns the FlashBorrower's return data. interface IFlashLender { - /// @dev Initiate a flash loan. + /// @dev Initiates a flash loan. /// @param receiver The receiver of the tokens in the loan, and the receiver of the callback. /// @param token The loan currency. /// @param amount The amount of tokens lent. From 47b5ffdfda03d1b0d14a5a2410745eb86fae5a92 Mon Sep 17 00:00:00 2001 From: Romain Milon Date: Tue, 25 Jul 2023 11:04:29 +0200 Subject: [PATCH 08/12] docs(flash-lender): update natspecs Co-authored-by: Merlin Egalite <44097430+MerlinEgalite@users.noreply.github.com> Signed-off-by: Romain Milon --- src/interfaces/IFlashLender.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/interfaces/IFlashLender.sol b/src/interfaces/IFlashLender.sol index 6c91e2eb7..4110e247a 100644 --- a/src/interfaces/IFlashLender.sol +++ b/src/interfaces/IFlashLender.sol @@ -9,7 +9,7 @@ import {IFlashBorrower} from "./IFlashBorrower.sol"; interface IFlashLender { /// @dev Initiates a flash loan. /// @param receiver The receiver of the tokens in the loan, and the receiver of the callback. - /// @param token The loan currency. + /// @param token The token lent. /// @param amount The amount of tokens lent. /// @param data Arbitrary data structure, intended to contain user-defined parameters. function flashLoan(IFlashBorrower receiver, IERC20 token, uint256 amount, bytes calldata data) From 0b6bfad5574d1fcc2476860d5c579166c73a5de2 Mon Sep 17 00:00:00 2001 From: Rubilmax Date: Tue, 25 Jul 2023 11:36:30 +0200 Subject: [PATCH 09/12] refactor(flash-loan): stick to eip3156 --- src/Blue.sol | 30 +++++++++++++++------- src/interfaces/IERC20.sol | 4 ++- src/interfaces/IERC3156FlashBorrower.sol | 20 +++++++++++++++ src/interfaces/IERC3156FlashLender.sol | 32 ++++++++++++++++++++++++ src/interfaces/IFlashBorrower.sol | 21 ---------------- src/interfaces/IFlashLender.sol | 18 ------------- src/mocks/FlashBorrowerMock.sol | 26 ++++++++----------- test/forge/Blue.t.sol | 12 +++++++++ test/hardhat/Blue.spec.ts | 5 ++-- 9 files changed, 100 insertions(+), 68 deletions(-) create mode 100644 src/interfaces/IERC3156FlashBorrower.sol create mode 100644 src/interfaces/IERC3156FlashLender.sol delete mode 100644 src/interfaces/IFlashBorrower.sol delete mode 100644 src/interfaces/IFlashLender.sol diff --git a/src/Blue.sol b/src/Blue.sol index 9f009cd0d..3a87b131b 100644 --- a/src/Blue.sol +++ b/src/Blue.sol @@ -3,7 +3,8 @@ pragma solidity 0.8.20; import {IIrm} from "src/interfaces/IIrm.sol"; import {IERC20} from "src/interfaces/IERC20.sol"; -import {IFlashBorrower, FLASH_BORROWER_SUCCESS_HASH} from "src/interfaces/IFlashBorrower.sol"; +import {IERC3156FlashLender} from "src/interfaces/IERC3156FlashLender.sol"; +import {IERC3156FlashBorrower, FLASH_BORROWER_SUCCESS_HASH} from "src/interfaces/IERC3156FlashBorrower.sol"; import {Errors} from "./libraries/Errors.sol"; import {SharesMath} from "src/libraries/SharesMath.sol"; @@ -15,7 +16,7 @@ uint256 constant WAD = 1e18; uint256 constant MAX_FEE = 0.2e18; uint256 constant ALPHA = 0.5e18; -contract Blue { +contract Blue is IERC3156FlashLender { using SharesMath for uint256; using FixedPointMathLib for uint256; using SafeTransferLib for IERC20; @@ -248,18 +249,29 @@ contract Blue { // Flash Loans. - function flashLoan(IFlashBorrower receiver, IERC20 token, uint256 amount, bytes calldata data) - public - returns (bytes memory) + /// @inheritdoc IERC3156FlashLender + function maxFlashLoan(address token) external view returns (uint256) { + return IERC20(token).balanceOf(address(this)); + } + + /// @inheritdoc IERC3156FlashLender + function flashFee(address, uint256) external pure returns (uint256) { + return 0; + } + + /// @inheritdoc IERC3156FlashLender + function flashLoan(IERC3156FlashBorrower receiver, address token, uint256 amount, bytes calldata data) + external + returns (bool) { - token.safeTransfer(address(receiver), amount); + IERC20(token).safeTransfer(address(receiver), amount); - (bytes32 successHash, bytes memory returnData) = receiver.onFlashLoan(msg.sender, token, amount, data); + bytes32 successHash = receiver.onFlashLoan(msg.sender, token, amount, 0, data); require(successHash == FLASH_BORROWER_SUCCESS_HASH, Errors.INVALID_SUCCESS_HASH); - token.safeTransferFrom(address(receiver), address(this), amount); + IERC20(token).safeTransferFrom(address(receiver), address(this), amount); - return returnData; + return true; } // Position management. diff --git a/src/interfaces/IERC20.sol b/src/interfaces/IERC20.sol index 31f0cc95f..b7610676f 100644 --- a/src/interfaces/IERC20.sol +++ b/src/interfaces/IERC20.sol @@ -3,4 +3,6 @@ pragma solidity >=0.5.0; /// @dev Empty because we only call functions in assembly. It prevents calling /// transfer (transferFrom) instead of safeTransfer (safeTransferFrom). -interface IERC20 {} +interface IERC20 { + function balanceOf(address owner) external view returns (uint256); +} diff --git a/src/interfaces/IERC3156FlashBorrower.sol b/src/interfaces/IERC3156FlashBorrower.sol new file mode 100644 index 000000000..98cdea7b9 --- /dev/null +++ b/src/interfaces/IERC3156FlashBorrower.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.5.0; + +/// @dev The expected success hash returned by the FlashBorrower. +bytes32 constant FLASH_BORROWER_SUCCESS_HASH = keccak256("ERC3156FlashBorrower.onFlashLoan"); + +interface IERC3156FlashBorrower { + /** + * @dev Receives a flash loan. + * @param initiator The initiator of the loan. + * @param token The token lent. + * @param amount The amount of tokens lent. + * @param fee The additional amount of tokens to repay. + * @param data Arbitrary data structure, intended to contain user-defined parameters. + * @return The keccak256 hash of "ERC3156FlashBorrower.onFlashLoan" + */ + function onFlashLoan(address initiator, address token, uint256 amount, uint256 fee, bytes calldata data) + external + returns (bytes32); +} diff --git a/src/interfaces/IERC3156FlashLender.sol b/src/interfaces/IERC3156FlashLender.sol new file mode 100644 index 000000000..fa9d7a852 --- /dev/null +++ b/src/interfaces/IERC3156FlashLender.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.5.0; + +import {IERC3156FlashBorrower} from "src/interfaces/IERC3156FlashBorrower.sol"; + +interface IERC3156FlashLender { + /** + * @dev The amount of currency available to be lent. + * @param token The token lent. + * @return The amount of `token` that can be borrowed. + */ + function maxFlashLoan(address token) external view returns (uint256); + + /** + * @dev The fee to be charged for a given loan. + * @param token The token lent. + * @param amount The amount of tokens lent. + * @return The amount of `token` to be charged for the loan, on top of the returned principal. + */ + function flashFee(address token, uint256 amount) external view returns (uint256); + + /** + * @dev Initiate a flash loan. + * @param receiver The receiver of the tokens in the loan, and the receiver of the callback. + * @param token The token lent. + * @param amount The amount of tokens lent. + * @param data Arbitrary data structure, intended to contain user-defined parameters. + */ + function flashLoan(IERC3156FlashBorrower receiver, address token, uint256 amount, bytes calldata data) + external + returns (bool); +} diff --git a/src/interfaces/IFlashBorrower.sol b/src/interfaces/IFlashBorrower.sol deleted file mode 100644 index 259ff7ba0..000000000 --- a/src/interfaces/IFlashBorrower.sol +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.0; - -import {IERC20} from "src/interfaces/IERC20.sol"; - -/// @dev The expected success hash returned by the FlashBorrower. -bytes32 constant FLASH_BORROWER_SUCCESS_HASH = keccak256("FlashBorrower.onFlashLoan"); - -/// @dev Interface of the FlashBorrower, inspired by https://eips.ethereum.org/EIPS/eip-3156. -/// The FlashLender's `flashLoan` function now returns the FlashBorrower's return data. -interface IFlashBorrower { - /// @dev Receives a flash loan. - /// @param initiator The initiator of the loan. - /// @param token The token lent. - /// @param amount The amount of tokens lent. - /// @param data Arbitrary data, intended to contain user-defined parameters. - /// @return The keccak256 hash of "FlashBorrower.onFlashLoan" and any additional arbitrary data. - function onFlashLoan(address initiator, IERC20 token, uint256 amount, bytes calldata data) - external - returns (bytes32, bytes memory); -} diff --git a/src/interfaces/IFlashLender.sol b/src/interfaces/IFlashLender.sol deleted file mode 100644 index 4110e247a..000000000 --- a/src/interfaces/IFlashLender.sol +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.0; - -import {IERC20} from "src/interfaces/IERC20.sol"; -import {IFlashBorrower} from "./IFlashBorrower.sol"; - -/// @dev Interface of the Flash Lender, inspired by https://eips.ethereum.org/EIPS/eip-3156. -/// The FlashLender's `flashLoan` function now returns the FlashBorrower's return data. -interface IFlashLender { - /// @dev Initiates a flash loan. - /// @param receiver The receiver of the tokens in the loan, and the receiver of the callback. - /// @param token The token lent. - /// @param amount The amount of tokens lent. - /// @param data Arbitrary data structure, intended to contain user-defined parameters. - function flashLoan(IFlashBorrower receiver, IERC20 token, uint256 amount, bytes calldata data) - external - returns (bytes memory); -} diff --git a/src/mocks/FlashBorrowerMock.sol b/src/mocks/FlashBorrowerMock.sol index 9e64eed63..0d60e3f21 100644 --- a/src/mocks/FlashBorrowerMock.sol +++ b/src/mocks/FlashBorrowerMock.sol @@ -2,37 +2,31 @@ pragma solidity ^0.8.0; import {IERC20} from "src/interfaces/IERC20.sol"; -import {IFlashLender} from "src/interfaces/IFlashLender.sol"; -import {IFlashBorrower, FLASH_BORROWER_SUCCESS_HASH} from "src/interfaces/IFlashBorrower.sol"; +import {IERC3156FlashLender} from "src/interfaces/IERC3156FlashLender.sol"; +import {IERC3156FlashBorrower, FLASH_BORROWER_SUCCESS_HASH} from "src/interfaces/IERC3156FlashBorrower.sol"; import {SafeTransferLib} from "src/libraries/SafeTransferLib.sol"; -contract FlashBorrowerMock is IFlashBorrower { +contract FlashBorrowerMock is IERC3156FlashBorrower { using SafeTransferLib for IERC20; - IFlashLender private immutable _LENDER; + IERC3156FlashLender private immutable _LENDER; - constructor(IFlashLender lender) { + constructor(IERC3156FlashLender lender) { _LENDER = lender; } /* EXTERNAL */ - function flashLoan(IERC20 token, uint256 amount, bytes calldata data) external virtual returns (bytes memory) { - return _LENDER.flashLoan(this, token, amount, data); - } - - /// @inheritdoc IFlashBorrower - function onFlashLoan(address initiator, IERC20 token, uint256 amount, bytes calldata) + /// @inheritdoc IERC3156FlashBorrower + function onFlashLoan(address, address token, uint256 amount, uint256 fee, bytes calldata) external - virtual - returns (bytes32, bytes memory) + returns (bytes32) { require(msg.sender == address(_LENDER), "invalid lender"); - require(initiator == address(this), "invalid initiator"); - token.safeApprove(address(_LENDER), amount); + IERC20(token).safeApprove(address(_LENDER), amount + fee); - return (FLASH_BORROWER_SUCCESS_HASH, bytes("")); + return FLASH_BORROWER_SUCCESS_HASH; } } diff --git a/test/forge/Blue.t.sol b/test/forge/Blue.t.sol index 6df98903c..387c734ce 100644 --- a/test/forge/Blue.t.sol +++ b/test/forge/Blue.t.sol @@ -8,6 +8,7 @@ import "src/Blue.sol"; import {ERC20Mock as ERC20} from "src/mocks/ERC20Mock.sol"; import {OracleMock as Oracle} from "src/mocks/OracleMock.sol"; import {IrmMock as Irm} from "src/mocks/IrmMock.sol"; +import {FlashBorrowerMock} from "src/mocks/FlashBorrowerMock.sol"; contract BlueTest is Test { using FixedPointMathLib for uint256; @@ -25,6 +26,7 @@ contract BlueTest is Test { Irm private irm; Market public market; Id public id; + FlashBorrowerMock internal flashBorrower; function setUp() public { // Create Blue. @@ -35,6 +37,7 @@ contract BlueTest is Test { collateralAsset = new ERC20("collateral", "C", 18); borrowableOracle = new Oracle(); collateralOracle = new Oracle(); + flashBorrower = new FlashBorrowerMock(blue); irm = new Irm(blue); market = Market( @@ -685,6 +688,15 @@ contract BlueTest is Test { vm.stopPrank(); } + + function testFlashLoan(uint256 amount) public { + amount = bound(amount, 1, 2 ** 64); + + borrowableAsset.setBalance(address(this), amount); + blue.supply(market, amount, address(this)); + + blue.flashLoan(flashBorrower, address(borrowableAsset), amount, bytes("")); + } } function neq(Market memory a, Market memory b) pure returns (bool) { diff --git a/test/hardhat/Blue.spec.ts b/test/hardhat/Blue.spec.ts index bc92c7974..062ca4651 100644 --- a/test/hardhat/Blue.spec.ts +++ b/test/hardhat/Blue.spec.ts @@ -46,14 +46,13 @@ describe("Blue", () => { let borrowableOracle: OracleMock; let collateralOracle: OracleMock; let irm: IrmMock; + let flashBorrower: FlashBorrowerMock; let market: Market; let id: Buffer; let nbLiquidations: number; - let flashBorrower: FlashBorrowerMock; - const updateMarket = (newMarket: Partial) => { market = { ...market, ...newMarket }; id = identifier(market); @@ -199,6 +198,6 @@ describe("Blue", () => { await blue.connect(user).supply(market, amount, user.address); - await flashBorrower.flashLoan(borrowable.address, amount.div(2), []); + await blue.flashLoan(flashBorrower.address, borrowable.address, amount.div(2), []); }); }); From e54f7b0e3bd6588fd5adfad41d9fe0b8e9bfef6b Mon Sep 17 00:00:00 2001 From: Rubilmax Date: Tue, 25 Jul 2023 11:50:59 +0200 Subject: [PATCH 10/12] refactor(libraries): use solmate in mocks --- src/libraries/SafeTransferLib.sol | 29 ----------------------------- src/mocks/FlashBorrowerMock.sol | 7 +++---- 2 files changed, 3 insertions(+), 33 deletions(-) diff --git a/src/libraries/SafeTransferLib.sol b/src/libraries/SafeTransferLib.sol index eba51ba20..016cd598a 100644 --- a/src/libraries/SafeTransferLib.sol +++ b/src/libraries/SafeTransferLib.sol @@ -66,33 +66,4 @@ library SafeTransferLib { require(success, "TRANSFER_FAILED"); } - - function safeApprove(IERC20 token, address to, uint256 amount) internal { - bool success; - - /// @solidity memory-safe-assembly - assembly { - // Get a pointer to some free memory. - let freeMemoryPointer := mload(0x40) - - // Write the abi-encoded calldata into memory, beginning with the function selector. - mstore(freeMemoryPointer, 0x095ea7b300000000000000000000000000000000000000000000000000000000) - mstore(add(freeMemoryPointer, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument. - mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type. - - success := - and( - // Set success to whether the call reverted, if not we check it either - // returned exactly 1 (can't just be non-zero data), or had no return data. - or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())), - // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2. - // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space. - // Counterintuitively, this call must be positioned second to the or() call in the - // surrounding and() call or else returndatasize() will be zero during the computation. - call(gas(), token, 0, freeMemoryPointer, 68, 0, 32) - ) - } - - require(success, "APPROVE_FAILED"); - } } diff --git a/src/mocks/FlashBorrowerMock.sol b/src/mocks/FlashBorrowerMock.sol index 0d60e3f21..227b0d9c4 100644 --- a/src/mocks/FlashBorrowerMock.sol +++ b/src/mocks/FlashBorrowerMock.sol @@ -1,14 +1,13 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.0; -import {IERC20} from "src/interfaces/IERC20.sol"; import {IERC3156FlashLender} from "src/interfaces/IERC3156FlashLender.sol"; import {IERC3156FlashBorrower, FLASH_BORROWER_SUCCESS_HASH} from "src/interfaces/IERC3156FlashBorrower.sol"; -import {SafeTransferLib} from "src/libraries/SafeTransferLib.sol"; +import {ERC20, SafeTransferLib} from "solmate/utils/SafeTransferLib.sol"; contract FlashBorrowerMock is IERC3156FlashBorrower { - using SafeTransferLib for IERC20; + using SafeTransferLib for ERC20; IERC3156FlashLender private immutable _LENDER; @@ -25,7 +24,7 @@ contract FlashBorrowerMock is IERC3156FlashBorrower { { require(msg.sender == address(_LENDER), "invalid lender"); - IERC20(token).safeApprove(address(_LENDER), amount + fee); + ERC20(token).safeApprove(address(_LENDER), amount + fee); return FLASH_BORROWER_SUCCESS_HASH; } From 762b88dc9fd136b020d7e90a092548a30e0da4c8 Mon Sep 17 00:00:00 2001 From: Rubilmax Date: Tue, 25 Jul 2023 12:16:57 +0200 Subject: [PATCH 11/12] refactor(flash-loan): remove erc3156 --- src/Blue.sol | 28 +++++---------------- src/interfaces/IERC3156FlashBorrower.sol | 20 --------------- src/interfaces/IERC3156FlashLender.sol | 32 ------------------------ src/interfaces/IFlashBorrower.sol | 13 ++++++++++ src/interfaces/IFlashLender.sol | 15 +++++++++++ src/mocks/FlashBorrowerMock.sol | 21 ++++++---------- test/forge/Blue.t.sol | 2 ++ 7 files changed, 44 insertions(+), 87 deletions(-) delete mode 100644 src/interfaces/IERC3156FlashBorrower.sol delete mode 100644 src/interfaces/IERC3156FlashLender.sol create mode 100644 src/interfaces/IFlashBorrower.sol create mode 100644 src/interfaces/IFlashLender.sol diff --git a/src/Blue.sol b/src/Blue.sol index 3a87b131b..acb639d4f 100644 --- a/src/Blue.sol +++ b/src/Blue.sol @@ -3,8 +3,8 @@ pragma solidity 0.8.20; import {IIrm} from "src/interfaces/IIrm.sol"; import {IERC20} from "src/interfaces/IERC20.sol"; -import {IERC3156FlashLender} from "src/interfaces/IERC3156FlashLender.sol"; -import {IERC3156FlashBorrower, FLASH_BORROWER_SUCCESS_HASH} from "src/interfaces/IERC3156FlashBorrower.sol"; +import {IFlashLender} from "src/interfaces/IFlashLender.sol"; +import {IFlashBorrower} from "src/interfaces/IFlashBorrower.sol"; import {Errors} from "./libraries/Errors.sol"; import {SharesMath} from "src/libraries/SharesMath.sol"; @@ -16,7 +16,7 @@ uint256 constant WAD = 1e18; uint256 constant MAX_FEE = 0.2e18; uint256 constant ALPHA = 0.5e18; -contract Blue is IERC3156FlashLender { +contract Blue is IFlashLender { using SharesMath for uint256; using FixedPointMathLib for uint256; using SafeTransferLib for IERC20; @@ -249,29 +249,13 @@ contract Blue is IERC3156FlashLender { // Flash Loans. - /// @inheritdoc IERC3156FlashLender - function maxFlashLoan(address token) external view returns (uint256) { - return IERC20(token).balanceOf(address(this)); - } - - /// @inheritdoc IERC3156FlashLender - function flashFee(address, uint256) external pure returns (uint256) { - return 0; - } - - /// @inheritdoc IERC3156FlashLender - function flashLoan(IERC3156FlashBorrower receiver, address token, uint256 amount, bytes calldata data) - external - returns (bool) - { + /// @inheritdoc IFlashLender + function flashLoan(IFlashBorrower receiver, address token, uint256 amount, bytes calldata data) external { IERC20(token).safeTransfer(address(receiver), amount); - bytes32 successHash = receiver.onFlashLoan(msg.sender, token, amount, 0, data); - require(successHash == FLASH_BORROWER_SUCCESS_HASH, Errors.INVALID_SUCCESS_HASH); + receiver.onFlashLoan(msg.sender, token, amount, data); IERC20(token).safeTransferFrom(address(receiver), address(this), amount); - - return true; } // Position management. diff --git a/src/interfaces/IERC3156FlashBorrower.sol b/src/interfaces/IERC3156FlashBorrower.sol deleted file mode 100644 index 98cdea7b9..000000000 --- a/src/interfaces/IERC3156FlashBorrower.sol +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity >=0.5.0; - -/// @dev The expected success hash returned by the FlashBorrower. -bytes32 constant FLASH_BORROWER_SUCCESS_HASH = keccak256("ERC3156FlashBorrower.onFlashLoan"); - -interface IERC3156FlashBorrower { - /** - * @dev Receives a flash loan. - * @param initiator The initiator of the loan. - * @param token The token lent. - * @param amount The amount of tokens lent. - * @param fee The additional amount of tokens to repay. - * @param data Arbitrary data structure, intended to contain user-defined parameters. - * @return The keccak256 hash of "ERC3156FlashBorrower.onFlashLoan" - */ - function onFlashLoan(address initiator, address token, uint256 amount, uint256 fee, bytes calldata data) - external - returns (bytes32); -} diff --git a/src/interfaces/IERC3156FlashLender.sol b/src/interfaces/IERC3156FlashLender.sol deleted file mode 100644 index fa9d7a852..000000000 --- a/src/interfaces/IERC3156FlashLender.sol +++ /dev/null @@ -1,32 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity >=0.5.0; - -import {IERC3156FlashBorrower} from "src/interfaces/IERC3156FlashBorrower.sol"; - -interface IERC3156FlashLender { - /** - * @dev The amount of currency available to be lent. - * @param token The token lent. - * @return The amount of `token` that can be borrowed. - */ - function maxFlashLoan(address token) external view returns (uint256); - - /** - * @dev The fee to be charged for a given loan. - * @param token The token lent. - * @param amount The amount of tokens lent. - * @return The amount of `token` to be charged for the loan, on top of the returned principal. - */ - function flashFee(address token, uint256 amount) external view returns (uint256); - - /** - * @dev Initiate a flash loan. - * @param receiver The receiver of the tokens in the loan, and the receiver of the callback. - * @param token The token lent. - * @param amount The amount of tokens lent. - * @param data Arbitrary data structure, intended to contain user-defined parameters. - */ - function flashLoan(IERC3156FlashBorrower receiver, address token, uint256 amount, bytes calldata data) - external - returns (bool); -} diff --git a/src/interfaces/IFlashBorrower.sol b/src/interfaces/IFlashBorrower.sol new file mode 100644 index 000000000..65a3cf88b --- /dev/null +++ b/src/interfaces/IFlashBorrower.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.5.0; + +interface IFlashBorrower { + /** + * @dev Receives a flash loan. + * @param initiator The initiator of the loan. + * @param token The token lent. + * @param amount The amount of tokens lent. + * @param data Arbitrary data structure, intended to contain user-defined parameters. + */ + function onFlashLoan(address initiator, address token, uint256 amount, bytes calldata data) external; +} diff --git a/src/interfaces/IFlashLender.sol b/src/interfaces/IFlashLender.sol new file mode 100644 index 000000000..1cba63ca5 --- /dev/null +++ b/src/interfaces/IFlashLender.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.5.0; + +import {IFlashBorrower} from "src/interfaces/IFlashBorrower.sol"; + +interface IFlashLender { + /** + * @dev Initiate a flash loan. + * @param receiver The receiver of the tokens in the loan, and the receiver of the callback. + * @param token The token lent. + * @param amount The amount of tokens lent. + * @param data Arbitrary data structure, intended to contain user-defined parameters. + */ + function flashLoan(IFlashBorrower receiver, address token, uint256 amount, bytes calldata data) external; +} diff --git a/src/mocks/FlashBorrowerMock.sol b/src/mocks/FlashBorrowerMock.sol index 227b0d9c4..08c5aa869 100644 --- a/src/mocks/FlashBorrowerMock.sol +++ b/src/mocks/FlashBorrowerMock.sol @@ -1,31 +1,26 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.0; -import {IERC3156FlashLender} from "src/interfaces/IERC3156FlashLender.sol"; -import {IERC3156FlashBorrower, FLASH_BORROWER_SUCCESS_HASH} from "src/interfaces/IERC3156FlashBorrower.sol"; +import {IFlashLender} from "src/interfaces/IFlashLender.sol"; +import {IFlashBorrower} from "src/interfaces/IFlashBorrower.sol"; import {ERC20, SafeTransferLib} from "solmate/utils/SafeTransferLib.sol"; -contract FlashBorrowerMock is IERC3156FlashBorrower { +contract FlashBorrowerMock is IFlashBorrower { using SafeTransferLib for ERC20; - IERC3156FlashLender private immutable _LENDER; + IFlashLender private immutable _LENDER; - constructor(IERC3156FlashLender lender) { + constructor(IFlashLender lender) { _LENDER = lender; } /* EXTERNAL */ - /// @inheritdoc IERC3156FlashBorrower - function onFlashLoan(address, address token, uint256 amount, uint256 fee, bytes calldata) - external - returns (bytes32) - { + /// @inheritdoc IFlashBorrower + function onFlashLoan(address, address token, uint256 amount, bytes calldata) external { require(msg.sender == address(_LENDER), "invalid lender"); - ERC20(token).safeApprove(address(_LENDER), amount + fee); - - return FLASH_BORROWER_SUCCESS_HASH; + ERC20(token).safeApprove(address(_LENDER), amount); } } diff --git a/test/forge/Blue.t.sol b/test/forge/Blue.t.sol index 387c734ce..caa9d8635 100644 --- a/test/forge/Blue.t.sol +++ b/test/forge/Blue.t.sol @@ -696,6 +696,8 @@ contract BlueTest is Test { blue.supply(market, amount, address(this)); blue.flashLoan(flashBorrower, address(borrowableAsset), amount, bytes("")); + + assertEq(borrowableAsset.balanceOf(address(blue)), amount, "balanceOf"); } } From 5e8be49b994160ad36d6518bf5c0ece95b6a72a8 Mon Sep 17 00:00:00 2001 From: MerlinEgalite Date: Thu, 27 Jul 2023 09:09:13 +0200 Subject: [PATCH 12/12] chore: remove function in interface + small modif NATSPECs --- src/interfaces/IERC20.sol | 4 +--- src/interfaces/IFlashBorrower.sol | 12 +++++------- src/interfaces/IFlashLender.sol | 12 +++++------- 3 files changed, 11 insertions(+), 17 deletions(-) diff --git a/src/interfaces/IERC20.sol b/src/interfaces/IERC20.sol index b7610676f..31f0cc95f 100644 --- a/src/interfaces/IERC20.sol +++ b/src/interfaces/IERC20.sol @@ -3,6 +3,4 @@ pragma solidity >=0.5.0; /// @dev Empty because we only call functions in assembly. It prevents calling /// transfer (transferFrom) instead of safeTransfer (safeTransferFrom). -interface IERC20 { - function balanceOf(address owner) external view returns (uint256); -} +interface IERC20 {} diff --git a/src/interfaces/IFlashBorrower.sol b/src/interfaces/IFlashBorrower.sol index 65a3cf88b..eb0d490d6 100644 --- a/src/interfaces/IFlashBorrower.sol +++ b/src/interfaces/IFlashBorrower.sol @@ -2,12 +2,10 @@ pragma solidity >=0.5.0; interface IFlashBorrower { - /** - * @dev Receives a flash loan. - * @param initiator The initiator of the loan. - * @param token The token lent. - * @param amount The amount of tokens lent. - * @param data Arbitrary data structure, intended to contain user-defined parameters. - */ + /// @dev Receives a flash loan. + /// @param initiator The initiator of the loan. + /// @param token The token lent. + /// @param amount The amount of tokens lent. + /// @param data Arbitrary data structure, intended to contain user-defined parameters. function onFlashLoan(address initiator, address token, uint256 amount, bytes calldata data) external; } diff --git a/src/interfaces/IFlashLender.sol b/src/interfaces/IFlashLender.sol index 1cba63ca5..603e18902 100644 --- a/src/interfaces/IFlashLender.sol +++ b/src/interfaces/IFlashLender.sol @@ -4,12 +4,10 @@ pragma solidity >=0.5.0; import {IFlashBorrower} from "src/interfaces/IFlashBorrower.sol"; interface IFlashLender { - /** - * @dev Initiate a flash loan. - * @param receiver The receiver of the tokens in the loan, and the receiver of the callback. - * @param token The token lent. - * @param amount The amount of tokens lent. - * @param data Arbitrary data structure, intended to contain user-defined parameters. - */ + /// @dev Initiate a flash loan. + /// @param receiver The receiver of the tokens in the loan, and the receiver of the callback. + /// @param token The token lent. + /// @param amount The amount of tokens lent. + /// @param data Arbitrary data structure, intended to contain user-defined parameters. function flashLoan(IFlashBorrower receiver, address token, uint256 amount, bytes calldata data) external; }