Skip to content
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

Alternative flash loan entry point without function pointers #16

Merged
merged 3 commits into from
Nov 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 16 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# ERC7399 Flash Lender Wrappers

This repository contains contracts that work as [ERC7399](https://github.com/ethereum/EIPs/blob/d072207e24e3cc12b6315909e6a65275a38e1984/EIPS/eip-7399.md) entry points for popular flash lenders.
This repository contains contracts that work as
[ERC7399](https://github.com/ethereum/EIPs/blob/d072207e24e3cc12b6315909e6a65275a38e1984/EIPS/eip-7399.md) entry points
for popular flash lenders.

## How Do These Wrappers Work

Expand All @@ -20,23 +22,28 @@ sequenceDiagram
Wrapper --> Wrapper: approves token repayment to lender †
Wrapper -->> Lender: lender calls transferFrom(wrapper, amount + fee) †
```
† For the BalancerWrapper and Uniswap v3 the borrower transfers the repayment to the lender and the wrapper skips the repayment approval.

† For the BalancerWrapper and Uniswap v3 the borrower transfers the repayment to the lender and the wrapper skips the
repayment approval.

## Addresses

Contracts are deployed at the same address for all supported networks.

| Contract | Lender | Address | Networks |
| ---- | ---- | ---- | ---- |
|[AaveWrapper](src/aave/AaveWrapper.sol)|Aave v3|0x02C7632b84B3447845531541d0285D67E656e50c|Arbitrum One, Optimism|
|[BalancerWrapper](src/balancer/BalancerWrapper.sol)|Balancer v2|0x3d4DF8596e5750A4F721c8764d585dcc8623d009|Arbitrum One, Optimism|
|[UniswapV3Wrapper](src/uniswapV3/UniswapV3Wrapper.sol)|Uniswap v3|0x23de8e0bB91A105bEFf9d40d8d75C1A9fE40f523|Arbitrum One, Optimism|
| Contract | Lender | Address | Networks |
| ------------------------------------------------------ | ----------- | ------------------------------------------ | ---------------------- |
| [AaveWrapper](src/aave/AaveWrapper.sol) | Aave v3 | 0x02C7632b84B3447845531541d0285D67E656e50c | Arbitrum One, Optimism |
| [BalancerWrapper](src/balancer/BalancerWrapper.sol) | Balancer v2 | 0x3d4DF8596e5750A4F721c8764d585dcc8623d009 | Arbitrum One, Optimism |
| [UniswapV3Wrapper](src/uniswapV3/UniswapV3Wrapper.sol) | Uniswap v3 | 0x23de8e0bB91A105bEFf9d40d8d75C1A9fE40f523 | Arbitrum One, Optimism |

When a contract requires constructor parameters which vary per network, these are supplied by the [Registry](https://github.com/alcueca/registry) deployed at 0x05caE14d1A348B29d2b169697b4BE51009a9C4dF in each supported network.
When a contract requires constructor parameters which vary per network, these are supplied by the
[Registry](https://github.com/alcueca/registry) deployed at 0x05caE14d1A348B29d2b169697b4BE51009a9C4dF in each supported
network.

## Flash Loans

For detail on executing flash loans, please refer to the [ERC7399](https://github.com/ethereum/EIPs/blob/d072207e24e3cc12b6315909e6a65275a38e1984/EIPS/eip-7399.md) EIP.
For detail on executing flash loans, please refer to the
[ERC7399](https://github.com/ethereum/EIPs/blob/d072207e24e3cc12b6315909e6a65275a38e1984/EIPS/eip-7399.md) EIP.

## Using This Repository

Expand Down
31 changes: 30 additions & 1 deletion src/BaseWrapper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ pragma solidity ^0.8.19;
import "erc7399/IERC7399.sol";

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

/// @dev All ERC7399 flash loan wrappers have the same general structure.
/// - The ERC7399 `flash` function is the entry point for the flash loan.
Expand Down Expand Up @@ -52,6 +53,34 @@ abstract contract BaseWrapper is IERC7399 {
initiatorData: initiatorData
});

return _flash(asset, amount, data);
}

/// @dev Alternative entry point for the ERC7399 flash loan, without function pointers. 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,
uint256 amount,
bytes calldata initiatorData,
address callbackTarget,
bytes4 callbackSelector
)
external
returns (bytes memory result)
{
Data memory data = Data({
loanReceiver: loanReceiver,
initiator: msg.sender,
callback: FunctionCodec.decodeFunction(callbackTarget, callbackSelector),
initiatorData: initiatorData
});

return _flash(asset, amount, data);
}

function _flash(address asset, uint256 amount, Data memory data) internal virtual returns (bytes memory result) {
_flashLoan(asset, amount, abi.encode(data));

result = _callbackResult;
Expand All @@ -63,7 +92,7 @@ abstract contract BaseWrapper is IERC7399 {
}

/// @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;
function _flashLoan(address asset, uint256 amount, bytes memory data) internal virtual;

/// @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
Expand Down
48 changes: 48 additions & 0 deletions src/utils/FunctionCodec.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

library FunctionCodec {
function encodeParams(address contractAddr, bytes4 selector) internal pure returns (bytes24) {
return bytes24(bytes20(contractAddr)) | bytes24(selector) >> 160;
}

function decodeParams(bytes24 encoded) internal pure returns (address contractAddr, bytes4 selector) {
contractAddr = address(bytes20(encoded));
selector = bytes4(encoded << 160);
}

function encodeFunction(
function(address, address, address, uint256, uint256, bytes memory) external returns (bytes memory) f
)
internal
pure
returns (bytes24)
{
return encodeParams(f.address, f.selector);
}

function decodeFunction(
address contractAddr,
bytes4 selector
)
internal
pure
returns (function(address, address, address, uint256, uint256, bytes memory) external returns (bytes memory) f)
{
uint32 s = uint32(selector);
// solhint-disable-next-line no-inline-assembly
assembly {
f.address := contractAddr
f.selector := s
}
}

function decodeFunction(bytes24 encoded)
internal
pure
returns (function(address, address, address, uint256, uint256, bytes memory) external returns (bytes memory) f)
{
(address contractAddr, bytes4 selector) = decodeParams(encoded);
return decodeFunction(contractAddr, selector);
}
}
15 changes: 15 additions & 0 deletions test/BaseWrapper.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,21 @@ contract BaseWrapperTest is PRBTest, StdCheats {
assertEq(vm.load(address(wrapper), bytes32(uint256(0))), "");
}

function test_flashLoanNoPointers() external {
console2.log("test_flashLoan");
uint256 loan = 1e18;
uint256 fee = wrapper.flashFee(dai, loan);
ERC20(dai).transfer(address(borrower), fee);
bytes memory result = borrower.flashBorrow(dai, loan);

// 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 wrapper state (return bytes should be cleaned up)
assertEq(vm.load(address(wrapper), bytes32(uint256(0))), "");
}

function test_flashLoan_void() external {
console2.log("test_flashLoan_void");
uint256 loan = 1e18;
Expand Down
7 changes: 7 additions & 0 deletions test/MockBorrower.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
pragma solidity ^0.8.19;

import "erc7399/IERC7399.sol";
import "src/BaseWrapper.sol";

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

Expand Down Expand Up @@ -134,6 +135,12 @@ contract MockBorrower {
return lender.flash(address(loanReceiver), asset, amount, "", this.onFlashLoan);
}

function flashBorrowNoPointers(address asset, uint256 amount) public returns (bytes memory) {
return BaseWrapper(address(lender)).flash(
address(loanReceiver), asset, amount, "", address(this), this.onFlashLoan.selector
);
}

function flashBorrowAndSteal(address asset, uint256 amount) public returns (bytes memory) {
return lender.flash(address(loanReceiver), asset, amount, "", this.onSteal);
}
Expand Down
Loading