Skip to content

Commit

Permalink
Merge pull request #169 from morpho-labs/refactor/bundlers
Browse files Browse the repository at this point in the history
Refactor bundlers and add docs
  • Loading branch information
Rubilmax authored Sep 15, 2023
2 parents ab1d412 + 77c967a commit b582435
Show file tree
Hide file tree
Showing 27 changed files with 310 additions and 116 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/foundry.yml
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ jobs:
invariant-runs: 64
invariant-depth: 1024
- type: "fast"
fuzz-runs: 256
fuzz-runs: 4096
max-test-rejects: 65536
invariant-runs: 16
invariant-depth: 256
Expand Down
8 changes: 4 additions & 4 deletions contracts/BaseBundler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ abstract contract BaseBundler is BaseSelfMulticall, BaseCallbackReceiver {
return _multicall(data);
}

/// @dev Transfers the minimum between the given `amount` and the bundler's balance of `asset` from the bundler to
/// `recipient`.
/// @notice Transfers the minimum between the given `amount` and the bundler's balance of `asset` from the bundler
/// to `recipient`.
function transfer(address asset, address recipient, uint256 amount) external payable {
require(recipient != address(0), ErrorsLib.ZERO_ADDRESS);
require(recipient != address(this), ErrorsLib.BUNDLER_ADDRESS);
Expand All @@ -46,8 +46,8 @@ abstract contract BaseBundler is BaseSelfMulticall, BaseCallbackReceiver {
ERC20(asset).safeTransfer(recipient, amount);
}

/// @dev Transfers the minimum between the given `amount` and the bundler's balance of native asset from the bundler
/// to `recipient`.
/// @notice Transfers the minimum between the given `amount` and the bundler's balance of native asset from the
/// bundler to `recipient`.
function transferNative(address recipient, uint256 amount) external payable {
require(recipient != address(0), ErrorsLib.ZERO_ADDRESS);
require(recipient != address(this), ErrorsLib.BUNDLER_ADDRESS);
Expand Down
47 changes: 27 additions & 20 deletions contracts/ERC4626Bundler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,51 +18,58 @@ abstract contract ERC4626Bundler is BaseBundler {

/* ACTIONS */

function mint(address vault, uint256 shares, address receiver) external payable {
require(receiver != address(0), ErrorsLib.ZERO_ADDRESS);
/// @notice Mints the given amount of `shares` on the given ERC4626 `vault`, on behalf of `owner`.
function erc4626Mint(address vault, uint256 shares, address owner) external payable {
require(owner != address(0), ErrorsLib.ZERO_ADDRESS);

address asset = IERC4626(vault).asset();
uint256 amount = Math.min(IERC4626(vault).maxDeposit(receiver), ERC20(asset).balanceOf(address(this)));
uint256 assets = Math.min(IERC4626(vault).maxDeposit(owner), ERC20(asset).balanceOf(address(this)));

shares = Math.min(shares, IERC4626(vault).previewDeposit(amount));
amount = IERC4626(vault).previewMint(shares);
shares = Math.min(shares, IERC4626(vault).previewDeposit(assets));
assets = IERC4626(vault).previewMint(shares);

require(amount != 0, ErrorsLib.ZERO_AMOUNT);
require(assets != 0, ErrorsLib.ZERO_AMOUNT);

// Approve 0 first to comply with tokens that implement the anti frontrunning approval fix.
ERC20(asset).safeApprove(vault, 0);
ERC20(asset).safeApprove(vault, amount);
IERC4626(vault).mint(shares, receiver);
ERC20(asset).safeApprove(vault, assets);
IERC4626(vault).mint(shares, owner);
}

function deposit(address vault, uint256 amount, address receiver) external payable {
require(receiver != address(0), ErrorsLib.ZERO_ADDRESS);
/// @notice Deposits the given amount of `assets` on the given ERC4626 `vault`, on behalf of `owner`.
function erc4626Deposit(address vault, uint256 assets, address owner) external payable {
require(owner != address(0), ErrorsLib.ZERO_ADDRESS);

address asset = IERC4626(vault).asset();

amount = Math.min(amount, IERC4626(vault).maxDeposit(receiver));
amount = Math.min(amount, ERC20(asset).balanceOf(address(this)));
assets = Math.min(assets, IERC4626(vault).maxDeposit(owner));
assets = Math.min(assets, ERC20(asset).balanceOf(address(this)));

require(amount != 0, ErrorsLib.ZERO_AMOUNT);
require(assets != 0, ErrorsLib.ZERO_AMOUNT);

// Approve 0 first to comply with tokens that implement the anti frontrunning approval fix.
ERC20(asset).safeApprove(vault, 0);
ERC20(asset).safeApprove(vault, amount);
IERC4626(vault).deposit(amount, receiver);
ERC20(asset).safeApprove(vault, assets);
IERC4626(vault).deposit(assets, owner);
}

function withdraw(address vault, uint256 amount, address receiver) external payable {
/// @notice Withdraws the given amount of `assets` from the given ERC4626 `vault`, transferring assets to
/// `receiver`.
/// @notice Warning: should only be called via the bundler's `multicall` function.
function erc4626Withdraw(address vault, uint256 assets, address receiver) external payable {
require(receiver != address(0), ErrorsLib.ZERO_ADDRESS);

address initiator = _initiator;
amount = Math.min(amount, IERC4626(vault).maxWithdraw(initiator));
assets = Math.min(assets, IERC4626(vault).maxWithdraw(initiator));

require(amount != 0, ErrorsLib.ZERO_AMOUNT);
require(assets != 0, ErrorsLib.ZERO_AMOUNT);

IERC4626(vault).withdraw(amount, receiver, initiator);
IERC4626(vault).withdraw(assets, receiver, initiator);
}

function redeem(address vault, uint256 shares, address receiver) external payable {
/// @notice Redeems the given amount of `shares` from the given ERC4626 `vault`, transferring assets to `receiver`.
/// @notice Warning: should only be called via the bundler's `multicall` function.
function erc4626Redeem(address vault, uint256 shares, address receiver) external payable {
require(receiver != address(0), ErrorsLib.ZERO_ADDRESS);

address initiator = _initiator;
Expand Down
35 changes: 19 additions & 16 deletions contracts/MorphoBundler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -55,17 +55,17 @@ abstract contract MorphoBundler is BaseBundler, IMorphoBundler {

/* ACTIONS */

/// @dev Approves this contract to manage the `authorization.authorizer`'s position via EIP712 `signature`.
/// @notice Approves this contract to manage the `authorization.authorizer`'s position via EIP712 `signature`.
function morphoSetAuthorizationWithSig(Authorization calldata authorization, Signature calldata signature)
external
payable
{
MORPHO.setAuthorizationWithSig(authorization, signature);
}

/// @dev Supplies `amount` of `asset` of `onBehalf` using permit2 in a single tx.
/// The supplied amount cannot be used as collateral but is eligible to earn interest.
/// Note: pass `amount = type(uint256).max` to supply the bundler's borrowable asset balance.
/// @notice Supplies `amount` of `asset` of `onBehalf` using permit2 in a single tx.
/// @notice The supplied amount cannot be used as collateral but is eligible to earn interest.
/// @dev Pass `amount = type(uint256).max` to supply the bundler's borrowable asset balance.
function morphoSupply(
MarketParams calldata marketparams,
uint256 amount,
Expand All @@ -85,8 +85,8 @@ abstract contract MorphoBundler is BaseBundler, IMorphoBundler {
MORPHO.supply(marketparams, amount, shares, onBehalf, data);
}

/// @dev Supplies `amount` of `asset` collateral to the pool on behalf of `onBehalf`.
/// Note: pass `amount = type(uint256).max` to supply the bundler's collateral asset balance.
/// @notice Supplies `amount` of `asset` collateral to the pool on behalf of `onBehalf`.
/// @dev Pass `amount = type(uint256).max` to supply the bundler's collateral asset balance.
function morphoSupplyCollateral(
MarketParams calldata marketparams,
uint256 amount,
Expand All @@ -105,17 +105,18 @@ abstract contract MorphoBundler is BaseBundler, IMorphoBundler {
MORPHO.supplyCollateral(marketparams, amount, onBehalf, data);
}

/// @dev Borrows `amount` of `asset` on behalf of the sender. Sender must have previously approved the bundler as
/// their manager on Blue.
/// @notice Borrows `amount` of `asset` on behalf of the sender.
/// @notice Warning: should only be called via the bundler's `multicall` function.
/// @dev Initiator must have previously authorized the bundler to act on their behalf on Blue.
function morphoBorrow(MarketParams calldata marketparams, uint256 amount, uint256 shares, address receiver)
external
payable
{
MORPHO.borrow(marketparams, amount, shares, _initiator, receiver);
}

/// @dev Repays `amount` of `asset` on behalf of `onBehalf`.
/// Note: pass `amount = type(uint256).max` to repay the bundler's borrowable asset balance.
/// @notice Repays `amount` of `asset` on behalf of `onBehalf`.
/// @dev Pass `amount = type(uint256).max` to repay the bundler's borrowable asset balance.
function morphoRepay(
MarketParams calldata marketparams,
uint256 amount,
Expand All @@ -135,25 +136,27 @@ abstract contract MorphoBundler is BaseBundler, IMorphoBundler {
MORPHO.repay(marketparams, amount, shares, onBehalf, data);
}

/// @dev Withdraws `amount` of the borrowable asset on behalf of `onBehalf`. Sender must have previously authorized
/// the bundler to act on their behalf on Blue.
/// @notice Withdraws `amount` of the borrowable asset on behalf of `onBehalf`.
/// @notice Warning: should only be called via the bundler's `multicall` function.
/// @dev Initiator must have previously authorized the bundler to act on their behalf on Blue.
function morphoWithdraw(MarketParams calldata marketparams, uint256 amount, uint256 shares, address receiver)
external
payable
{
MORPHO.withdraw(marketparams, amount, shares, _initiator, receiver);
}

/// @dev Withdraws `amount` of the collateral asset on behalf of sender. Sender must have previously authorized the
/// bundler to act on their behalf on Blue.
/// @notice Withdraws `amount` of the collateral asset on behalf of sender.
/// @notice Warning: should only be called via the bundler's `multicall` function.
/// @dev Initiator must have previously authorized the bundler to act on their behalf on Blue.
function morphoWithdrawCollateral(MarketParams calldata marketparams, uint256 amount, address receiver)
external
payable
{
MORPHO.withdrawCollateral(marketparams, amount, _initiator, receiver);
}

/// @dev Triggers a liquidation on Blue.
/// @notice Triggers a liquidation on Blue.
function morphoLiquidate(
MarketParams calldata marketparams,
address borrower,
Expand All @@ -166,7 +169,7 @@ abstract contract MorphoBundler is BaseBundler, IMorphoBundler {
MORPHO.liquidate(marketparams, borrower, seizedAssets, repaidShares, data);
}

/// @dev Triggers a flash loan on Blue.
/// @notice Triggers a flash loan on Blue.
function morphoFlashLoan(address asset, uint256 amount, bytes calldata data) external payable {
_approveMaxBlue(asset);

Expand Down
8 changes: 5 additions & 3 deletions contracts/Permit2Bundler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,18 @@ abstract contract Permit2Bundler is BaseBundler {

/* ACTIONS */

/// @dev Approves the given `amount` of `asset` from sender to be spent by this contract via Permit2 with the given
/// `deadline` & EIP712 `signature`.
/// @notice Approves the given `amount` of `asset` from sender to be spent by this contract via Permit2 with the
/// given `deadline` & EIP-712 `signature`.
/// @notice Warning: should only be called via the bundler's `multicall` function.
function approve2(address asset, uint256 amount, uint256 deadline, Signature calldata signature) external payable {
require(amount != 0, ErrorsLib.ZERO_AMOUNT);

ERC20(asset).simplePermit2(_initiator, address(this), amount, deadline, signature.v, signature.r, signature.s);
}

/// @dev Transfers the given `amount` of `asset` from sender to this contract via ERC20 transfer with Permit2
/// @notice Transfers the given `amount` of `asset` from sender to this contract via ERC20 transfer with Permit2
/// fallback.
/// @notice Warning: should only be called via the bundler's `multicall` function.
function transferFrom2(address asset, uint256 amount) external payable {
require(amount != 0, ErrorsLib.ZERO_AMOUNT);

Expand Down
6 changes: 4 additions & 2 deletions contracts/WNativeBundler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ abstract contract WNativeBundler is BaseBundler {

/* ACTIONS */

/// @dev Wraps the given `amount` of the native token to wNative and transfers it to `receiver`.
/// @notice Wraps the given `amount` of the native token to wNative and transfers it to `receiver`.
/// @notice Warning: should only be called via the bundler's `multicall` function.
function wrapNative(uint256 amount, address receiver) external payable {
require(receiver != address(0), ErrorsLib.ZERO_ADDRESS);

Expand All @@ -51,7 +52,8 @@ abstract contract WNativeBundler is BaseBundler {
if (receiver != address(this)) ERC20(WRAPPED_NATIVE).safeTransfer(receiver, amount);
}

/// @dev Unwraps the given `amount` of wNative to the native token and transfers it to `receiver`.
/// @notice Unwraps the given `amount` of wNative to the native token and transfers it to `receiver`.
/// @notice Warning: should only be called via the bundler's `multicall` function.
function unwrapNative(uint256 amount, address receiver) external payable {
require(receiver != address(this), ErrorsLib.BUNDLER_ADDRESS);
require(receiver != address(0), ErrorsLib.ZERO_ADDRESS);
Expand Down
7 changes: 2 additions & 5 deletions contracts/ethereum-mainnet/EthereumBundler.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.8.21;

import "./libraries/ConstantsLib.sol";

import {EVMBundler} from "../EVMBundler.sol";
import {StEthBundler} from "./StEthBundler.sol";
import {WNativeBundler} from "../WNativeBundler.sol";
Expand All @@ -10,11 +12,6 @@ import {WNativeBundler} from "../WNativeBundler.sol";
/// @custom:contact [email protected]
/// @notice Bundler contract specific to the Ethereum mainnet.
contract EthereumBundler is EVMBundler, WNativeBundler, StEthBundler {
/* CONSTANTS */

/// @dev The address of the WETH contract on Ethereum mainnet.
address public constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;

/* CONSTRUCTOR */

constructor(address morpho) EVMBundler(morpho) WNativeBundler(WETH) {}
Expand Down
4 changes: 2 additions & 2 deletions contracts/ethereum-mainnet/StEthBundler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ abstract contract StEthBundler is BaseBundler {

/* ACTIONS */

/// @dev Wraps the given `amount` of stETH to wstETH and transfers it to `receiver`.
/// @notice Wraps the given `amount` of stETH to wstETH and transfers it to `receiver`.
function wrapStEth(uint256 amount, address receiver) external payable {
require(receiver != address(0), ErrorsLib.ZERO_ADDRESS);

Expand All @@ -46,7 +46,7 @@ abstract contract StEthBundler is BaseBundler {
if (receiver != address(this)) ERC20(WST_ETH).safeTransfer(receiver, amount);
}

/// @dev Unwraps the given `amount` of wstETH to stETH and transfers it to `receiver`.
/// @notice Unwraps the given `amount` of wstETH to stETH and transfers it to `receiver`.
function unwrapStEth(uint256 amount, address receiver) external payable {
require(receiver != address(this), ErrorsLib.BUNDLER_ADDRESS);
require(receiver != address(0), ErrorsLib.ZERO_ADDRESS);
Expand Down
2 changes: 1 addition & 1 deletion contracts/ethereum-mainnet/interfaces/IStEth.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.5.0;
pragma solidity >=0.6.2;

interface IStEth {
function transferShares(address _recipient, uint256 _sharesAmount) external returns (uint256);
Expand Down
2 changes: 1 addition & 1 deletion contracts/ethereum-mainnet/interfaces/IWStEth.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;
pragma solidity >=0.6.2;

interface IWStEth {
function DOMAIN_SEPARATOR() external view returns (bytes32);
Expand Down
5 changes: 5 additions & 0 deletions contracts/ethereum-mainnet/libraries/ConstantsLib.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.0;

/// @dev The address of the WETH contract on Ethereum mainnet.
address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
2 changes: 1 addition & 1 deletion contracts/interfaces/IMorphoBundler.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;
pragma solidity >=0.6.2;

import {
IMorphoRepayCallback,
Expand Down
2 changes: 1 addition & 1 deletion contracts/interfaces/IMulticall.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;
pragma solidity >=0.6.2;

interface IMulticall {
function multicall(uint256 deadline, bytes[] calldata data) external payable returns (bytes[] memory results);
Expand Down
2 changes: 1 addition & 1 deletion contracts/interfaces/IWNative.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;
pragma solidity >=0.6.2;

interface IWNative {
function deposit() external payable;
Expand Down
16 changes: 10 additions & 6 deletions contracts/migration/AaveV2MigrationBundler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,17 @@ contract AaveV2MigrationBundler is MigrationBundler, Permit2Bundler {

/* ACTIONS */

function aaveV2Withdraw(address asset, uint256 amount, address to) external payable {
AAVE_V2_POOl.withdraw(asset, amount, to);
}

function aaveV2Repay(address asset, uint256 amount, uint256 rateMode) external payable {
/// @notice Repays `amount` of `asset` on AaveV2, on behalf of the initiator.
/// @notice Warning: should only be called via the bundler's `multicall` function.
function aaveV2Repay(address asset, uint256 amount, uint256 interestRateMode) external payable {
_approveMaxTo(asset, address(AAVE_V2_POOl));

AAVE_V2_POOl.repay(asset, amount, rateMode, _initiator);
AAVE_V2_POOl.repay(asset, amount, interestRateMode, _initiator);
}

/// @notice Withdraws `amount` of `asset` on AaveV3, on behalf of the initiator, transferring funds to `receiver`.
/// @dev Initiator must have previously transferred their aTokens to the bundler.
function aaveV2Withdraw(address asset, uint256 amount, address receiver) external payable {
AAVE_V2_POOl.withdraw(asset, amount, receiver);
}
}
15 changes: 11 additions & 4 deletions contracts/migration/AaveV3MigrationBundler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,23 @@ contract AaveV3MigrationBundler is MigrationBundler, Permit2Bundler {

/* ACTIONS */

function aaveV3Withdraw(address asset, uint256 amount, address to) external payable {
AAVE_V3_POOL.withdraw(asset, amount, to);
}

/// @notice Repays `amount` of `asset` on AaveV3, on behalf of the initiator.
/// @notice Warning: should only be called via the bundler's `multicall` function.
function aaveV3Repay(address asset, uint256 amount, uint256 interestRateMode) external payable {
_approveMaxTo(asset, address(AAVE_V3_POOL));

AAVE_V3_POOL.repay(asset, amount, interestRateMode, _initiator);
}

/// @notice Withdraws `amount` of `asset` on AaveV3, on behalf of the initiator, transferring funds to `receiver`.
/// @dev Initiator must have previously transferred their aTokens to the bundler.
function aaveV3Withdraw(address asset, uint256 amount, address receiver) external payable {
AAVE_V3_POOL.withdraw(asset, amount, receiver);
}

/// @notice Approves the bundler to manage the initiator's `aToken` balance, given a signed EIP-2612 approval
/// message.
/// @notice Warning: should only be called via the bundler's `multicall` function.
function aaveV3PermitAToken(address aToken, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s)
external
payable
Expand Down
Loading

0 comments on commit b582435

Please sign in to comment.