Skip to content

Commit

Permalink
Mock borrower and better natspec (#13)
Browse files Browse the repository at this point in the history
* Renamed FlashBorrower to MockBorrower

* Better comments on tests

* Better natspec on BaseWrapper

* Better natspec on wrappers

* PR fixes

---------

Co-authored-by: alcueca <[email protected]>
  • Loading branch information
alcueca and alcueca authored Aug 8, 2023
1 parent a9ea128 commit 91fb05a
Show file tree
Hide file tree
Showing 13 changed files with 69 additions and 114 deletions.
71 changes: 0 additions & 71 deletions FlashLender.t.sol

This file was deleted.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@alcueca/erc3156pp-wrappers",
"description": "Wrappers for existing Flash Lenders to be accessible as ERC3156++ Flash Lenders",
"name": "@alcueca/erc7399-wrappers",
"description": "Wrappers for existing Flash Lenders to be accessible as ERC7399 Flash Lenders",
"version": "1.0.0",
"author": {
"name": "alcueca",
Expand Down
22 changes: 20 additions & 2 deletions src/BaseWrapper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,19 @@ import "erc7399/IERC7399.sol";

import { TransferHelper, ERC20 } from "./utils/TransferHelper.sol";

/// @dev All ERC7399 flash loan wrappers have the same general structure.
/// - The ERC7399 `flash` function is the entry point for the flash loan.
/// - The wrapper calls the underlying lender flash lender on their non-ERC7399 flash lending call to borrow the funds.
/// - The lender sends the funds to the wrapper.
/// - The wrapper receives the callback from the lender.
/// - The wrapper sends the funds to the loan receiver.
/// - The wrapper calls the callback supplied by the original borrower.
/// - The callback from the original borrower executes.
/// - Depending on the lender, the wrapper may have to approve it to pull the repayment.
/// - If there is any data to return, it is kept in a storage variable.
/// - The wrapper exits the callback.
/// - The lender verifies or pulls the repayment.
/// - The wrapper returns to the original borrower the stored result of its callback.
abstract contract BaseWrapper is IERC7399 {
using TransferHelper for address;

Expand All @@ -19,6 +32,9 @@ abstract contract BaseWrapper is IERC7399 {
bytes internal _callbackResult;

/// @inheritdoc IERC7399
/// @dev The entry point for the ERC7399 flash loan. Packs data to convert the legacy flash loan into an ERC7399
/// flash loan. Then it calls the legacy flash loan. Once the flash loan is done, checks if there is any return
/// data and returns it.
function flash(
address loanReceiver,
address asset,
Expand Down Expand Up @@ -46,10 +62,12 @@ abstract contract BaseWrapper is IERC7399 {
return result;
}

/// @dev Call the flashloan function in the child contract
/// @dev Call the legacy flashloan function in the child contract. This is where we borrow from Aave, Uniswap, etc.
function _flashLoan(address asset, uint256 amount, bytes memory params) internal virtual;

/// @dev Handle the common parts of bridging the callback
/// @dev Handle the common parts of bridging the callback from legacy to ERC7399. Transfer the funds to the loan
/// receiver. Call the callback supplied by the original borrower. Approve the repayment if necessary. If there is
/// any result, it is kept in a storage variable to be retrieved on `flash` after the legacy flash loan is finished.
function bridgeToCallback(address asset, uint256 amount, uint256 fee, bytes memory params) internal {
Data memory data = abi.decode(params, (Data));
_transferAssets(asset, amount, data.loanReceiver);
Expand Down
3 changes: 3 additions & 0 deletions src/aave/AaveWrapper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ import { FixedPointMathLib } from "lib/solmate/src/utils/FixedPointMathLib.sol";

import { BaseWrapper, IERC7399, ERC20 } from "../BaseWrapper.sol";


/// @dev Aave Flash Lender that uses the Aave Pool as source of liquidity.
/// Aave doesn't allow flow splitting or pushing repayments, so this wrapper is completely vanilla.
contract AaveWrapper is BaseWrapper, IFlashLoanSimpleReceiver {
using FixedPointMathLib for uint256;
using ReserveConfiguration for DataTypes.ReserveConfigurationMap;
Expand Down
3 changes: 3 additions & 0 deletions src/balancer/BalancerWrapper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import { FixedPointMathLib } from "lib/solmate/src/utils/FixedPointMathLib.sol";

import { BaseWrapper, IERC7399, ERC20 } from "../BaseWrapper.sol";


/// @dev Balancer Flash Lender that uses Balancer Pools as source of liquidity.
/// Balancer allows pushing repayments, so we override `_repayTo`.
contract BalancerWrapper is BaseWrapper, IFlashLoanRecipient {
using Arrays for uint256;
using Arrays for address;
Expand Down
5 changes: 3 additions & 2 deletions src/erc3156/ERC3156Wrapper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import { BaseWrapper, IERC7399 } from "../BaseWrapper.sol";

/**
* @author Alberto Cuesta Cañada
* @dev ERC3156++ Flash Lender that uses ERC3156 Flash Lenders as source of liquidity.
* @dev ERC7399 Flash Lender that uses ERC3156 Flash Lenders as source of liquidity.
* ERC3156 doesn't allow flow splitting or pushing repayments, so this wrapper is completely vanilla.
*/
contract ERC3156Wrapper is BaseWrapper, IERC3156FlashBorrower {
bytes32 public constant CALLBACK_SUCCESS = keccak256("ERC3156FlashBorrower.onFlashLoan");
Expand Down Expand Up @@ -44,7 +45,7 @@ contract ERC3156Wrapper is BaseWrapper, IERC3156FlashBorrower {
IERC3156FlashLender lender = lenders[asset];
require(address(lender) != address(0), "Unsupported currency");

// We get funds from an ERC3156 lender to serve the ERC3156++ flash loan in our ERC3156 callback
// We get funds from an ERC3156 lender to serve the ERC7399 flash loan in our ERC3156 callback
lender.flashLoan(this, address(asset), amount, data);
}

Expand Down
2 changes: 2 additions & 0 deletions src/uniswapV3/UniswapV3Wrapper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { PoolAddress } from "./interfaces/PoolAddress.sol";

import { BaseWrapper, IERC7399, ERC20 } from "../BaseWrapper.sol";

/// @dev Uniswap V3 Flash Lender that uses Uniswap V3 Pools as source of liquidity.
/// Uniswap V3 allows pushing repayments, so we override `_repayTo`.
contract UniswapV3Wrapper is BaseWrapper, IUniswapV3FlashCallback {
using PoolAddress for address;
using { canLoan, balance } for IUniswapV3Pool;
Expand Down
10 changes: 5 additions & 5 deletions test/AaveWrapper.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@ import { StdCheats } from "forge-std/StdCheats.sol";

import { ERC20 } from "solmate/tokens/ERC20.sol";

import { FlashBorrower } from "./FlashBorrower.sol";
import { MockBorrower } from "./MockBorrower.sol";
import { AaveWrapper } from "../src/aave/AaveWrapper.sol";
import { IPoolAddressesProvider } from "../src/aave/interfaces/IPoolAddressesProvider.sol";

/// @dev If this is your first time with Forge, read this tutorial in the Foundry Book:
/// https://book.getfoundry.sh/forge/writing-tests
contract AaveWrapperTest is PRBTest, StdCheats {
AaveWrapper internal wrapper;
FlashBorrower internal borrower;
MockBorrower internal borrower;
address internal dai;
IPoolAddressesProvider internal provider;

Expand All @@ -32,7 +32,7 @@ contract AaveWrapperTest is PRBTest, StdCheats {
dai = 0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1;

wrapper = new AaveWrapper(provider);
borrower = new FlashBorrower(wrapper);
borrower = new MockBorrower(wrapper);
deal(address(dai), address(this), 1e18); // For fees
}

Expand All @@ -54,11 +54,11 @@ contract AaveWrapperTest is PRBTest, StdCheats {
ERC20(dai).transfer(address(borrower), fee);
bytes memory result = borrower.flashBorrow(dai, loan);

// Test the return values
// Test the return values passed through the wrapper
(bytes32 callbackReturn) = abi.decode(result, (bytes32));
assertEq(uint256(callbackReturn), uint256(borrower.ERC3156PP_CALLBACK_SUCCESS()), "Callback failed");

// Test the borrower state
// Test the borrower state during the callback
assertEq(borrower.flashInitiator(), address(borrower));
assertEq(address(borrower.flashAsset()), address(dai));
assertEq(borrower.flashAmount(), loan);
Expand Down
10 changes: 5 additions & 5 deletions test/BalancerWrapper.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { ERC20 } from "solmate/tokens/ERC20.sol";
import { Arrays } from "src/utils/Arrays.sol";

import { IFlashLoaner } from "../src/balancer/interfaces/IFlashLoaner.sol";
import { FlashBorrower } from "./FlashBorrower.sol";
import { MockBorrower } from "./MockBorrower.sol";
import { BalancerWrapper } from "../src/balancer/BalancerWrapper.sol";

/// @dev If this is your first time with Forge, read this tutorial in the Foundry Book:
Expand All @@ -20,7 +20,7 @@ contract BalancerWrapperTest is PRBTest, StdCheats {
using Arrays for address;

BalancerWrapper internal wrapper;
FlashBorrower internal borrower;
MockBorrower internal borrower;
address internal dai;
IFlashLoaner internal balancer;

Expand All @@ -37,7 +37,7 @@ contract BalancerWrapperTest is PRBTest, StdCheats {
dai = 0x6B175474E89094C44Da98b954EedeAC495271d0F;

wrapper = new BalancerWrapper(balancer);
borrower = new FlashBorrower(wrapper);
borrower = new MockBorrower(wrapper);
deal(address(dai), address(this), 1e18); // For fees
}

Expand All @@ -59,11 +59,11 @@ contract BalancerWrapperTest is PRBTest, StdCheats {
ERC20(dai).transfer(address(borrower), fee);
bytes memory result = borrower.flashBorrow(dai, loan);

// Test the return values
// Test the return values passed through the wrapper
(bytes32 callbackReturn) = abi.decode(result, (bytes32));
assertEq(uint256(callbackReturn), uint256(borrower.ERC3156PP_CALLBACK_SUCCESS()), "Callback failed");

// Test the borrower state
// Test the borrower state during the callback
assertEq(borrower.flashInitiator(), address(borrower));
assertEq(address(borrower.flashAsset()), address(dai));
assertEq(borrower.flashAmount(), loan);
Expand Down
12 changes: 5 additions & 7 deletions test/BaseWrapper.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,14 @@ import { StdCheats } from "forge-std/StdCheats.sol";

import { ERC20 } from "solmate/tokens/ERC20.sol";

import { FlashBorrower } from "./FlashBorrower.sol";
import { MockBorrower } from "./MockBorrower.sol";

import { BaseWrapper } from "src/BaseWrapper.sol";

/// @dev If this is your first time with Forge, read this tutorial in the Foundry Book:
/// https://book.getfoundry.sh/forge/writing-tests
contract BaseWrapperTest is PRBTest, StdCheats {
FooWrapper internal wrapper;
FooLender internal lender;
FlashBorrower internal borrower;
MockBorrower internal borrower;
address internal dai;

/// @dev A function invoked before each test case is run.
Expand All @@ -33,7 +31,7 @@ contract BaseWrapperTest is PRBTest, StdCheats {
lender = new FooLender();
deal(address(dai), address(lender), 10_000_000e18);
wrapper = new FooWrapper(lender);
borrower = new FlashBorrower(wrapper);
borrower = new MockBorrower(wrapper);
deal(address(dai), address(this), 1e18); // For fees
}

Expand All @@ -44,7 +42,7 @@ contract BaseWrapperTest is PRBTest, StdCheats {
ERC20(dai).transfer(address(borrower), fee);
bytes memory result = borrower.flashBorrow(dai, loan);

// Test the return values
// Test the return values passed through the wrapper
(bytes32 callbackReturn) = abi.decode(result, (bytes32));
assertEq(uint256(callbackReturn), uint256(borrower.ERC3156PP_CALLBACK_SUCCESS()), "Callback failed");

Expand All @@ -61,7 +59,7 @@ contract BaseWrapperTest is PRBTest, StdCheats {
vm.record();
bytes memory result = borrower.flashBorrowVoid(dai, loan);

// Test the return values
// Test the return values passed through the wrapper
assertEq(result, "", "Void result");

// Test the wrapper state, no storage writes on void results
Expand Down
10 changes: 5 additions & 5 deletions test/ERC3156Wrapper.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ import { IERC3156FlashLender } from "lib/erc3156/contracts/interfaces/IERC3156Fl

import { ERC20 } from "solmate/tokens/ERC20.sol";

import { FlashBorrower } from "./FlashBorrower.sol";
import { MockBorrower } from "./MockBorrower.sol";
import { ERC3156Wrapper } from "../src/erc3156/ERC3156Wrapper.sol";

/// @dev If this is your first time with Forge, read this tutorial in the Foundry Book:
/// https://book.getfoundry.sh/forge/writing-tests
contract ERC3156WrapperTest is PRBTest, StdCheats {
ERC3156Wrapper internal wrapper;
FlashBorrower internal borrower;
MockBorrower internal borrower;
address internal dai;
IERC3156FlashLender internal makerFlash;

Expand All @@ -36,7 +36,7 @@ contract ERC3156WrapperTest is PRBTest, StdCheats {
IERC3156FlashLender[] memory lenders = new IERC3156FlashLender[](1);
lenders[0] = makerFlash;
wrapper = new ERC3156Wrapper(assets, lenders);
borrower = new FlashBorrower(wrapper);
borrower = new MockBorrower(wrapper);
deal(address(dai), address(this), 1e18); // For fees
}

Expand All @@ -58,11 +58,11 @@ contract ERC3156WrapperTest is PRBTest, StdCheats {
ERC20(dai).transfer(address(borrower), fee);
bytes memory result = borrower.flashBorrow(dai, loan);

// Test the return values
// Test the return values passed through the wrapper
(bytes32 callbackReturn) = abi.decode(result, (bytes32));
assertEq(uint256(callbackReturn), uint256(borrower.ERC3156PP_CALLBACK_SUCCESS()), "Callback failed");

// Test the borrower state
// Test the borrower state during the callback
assertEq(borrower.flashInitiator(), address(borrower));
assertEq(address(borrower.flashAsset()), address(dai));
assertEq(borrower.flashAmount(), loan);
Expand Down
Loading

0 comments on commit 91fb05a

Please sign in to comment.