Description:
Multi-signature wallet contract requiring multiple confirmations for transaction execution.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"src/adapters/MorphoVaultController.sol": {
"content": "// SPDX-License-Identifier: BSL-1.1
pragma solidity ^0.8.24;
import {VaultController} from "../libraries/VaultController.sol";
import {MorphoVaultLib} from "../libraries/MorphoVaultLib.sol";
import {IBundler3} from "bundler3/src/interfaces/IBundler3.sol";
import {IMorpho, MarketParams} from "morpho-blue/src/interfaces/IMorpho.sol";
import {GeneralAdapter1} from "bundler3/src/adapters/GeneralAdapter1.sol";
import {IERC4626} from "@openzeppelin/contracts/interfaces/IERC4626.sol";
import {MarketParamsLib} from "morpho-blue/src/libraries/MarketParamsLib.sol";
import {SharesMathLib} from "morpho-blue/src/libraries/SharesMathLib.sol";
import {RebalanceData, RebalanceAssetsPerShare} from "../types/RebalanceTypes.sol";
import {VaultConfig, MarketConfig} from "../types/StrategyTypes.sol";
import {SafeERC20, IERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {StrategyManager} from "../libraries/StrategyManager.sol";
import {MorphoLeverageLib} from "../libraries/MorphoLeverageLib.sol";
import {MorphoSeedingLib} from "../libraries/MorphoSeedingLib.sol";
import {PriceLib} from "../libraries/PriceLib.sol";
import {IWNative} from "bundler3/src/interfaces/IWNative.sol";
/**
* @title MorphoVaultController
* @author Variable Logic Labs, Corp (hello@blend.money)
* @notice Adapter for Morpho actions
* @dev Handles morpho actions using flashloans to increase and
* decrease user allocations.
*/
contract MorphoVaultController is VaultController {
using MarketParamsLib for MarketParams;
using SharesMathLib for uint256;
using SafeERC20 for IERC20;
/*//////////////////////////////////////////////////////////////
STATE VARIABLES
//////////////////////////////////////////////////////////////*/
IBundler3 internal immutable BUNDLER3;
GeneralAdapter1 internal immutable GENERAL_ADAPTER;
IMorpho internal immutable MORPHO;
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
/**
* @notice Constructor for the MorphoVaultController
* @param _strategyManager The strategy manager contract
* @param _generalAdapter The general adapter contract
*/
constructor(StrategyManager _strategyManager, GeneralAdapter1 _generalAdapter) VaultController(_strategyManager) {
require(address(_generalAdapter) != address(0), ZeroAddress());
BUNDLER3 = IBundler3(address(_generalAdapter.BUNDLER3()));
GENERAL_ADAPTER = _generalAdapter;
MORPHO = IMorpho(address(_generalAdapter.MORPHO()));
}
/*//////////////////////////////////////////////////////////////
MODIFIERS
//////////////////////////////////////////////////////////////*/
modifier _setAuthorization() {
MORPHO.setAuthorization(address(GENERAL_ADAPTER), true);
_;
MORPHO.setAuthorization(address(GENERAL_ADAPTER), false);
}
/*//////////////////////////////////////////////////////////////
EXTERNAL FUNCTIONS
//////////////////////////////////////////////////////////////*/
/**
* @notice Executes a rebalance on a vault using the specified configuration and rebalance data
* @param vault The vault to rebalance
* @param rebalanceData An array of rebalance instructions (amount, increase/decrease, etc.)
*/
function executeRebalance(address vault, RebalanceData[] calldata rebalanceData)
external
override
_validateRebalanceData(vault, rebalanceData)
_setAuthorization
_onlyDelegateCall
{
VaultConfig memory vaultConfig = STRATEGY_MANAGER.getVaultConfig(vault);
uint256[] memory rebalanceAmounts = new uint256[](rebalanceData.length);
bool[] memory rebalanceAreIncreases = new bool[](rebalanceData.length);
// Unpack the rebalance assets per share
(RebalanceAssetsPerShare memory rebalanceAssetsPerShare,) =
MorphoVaultLib._unpackPriceData(rebalanceData[0].extraData);
// Wrap native gas token if it's the vault token (e.g., ETH -> WETH)
address vaultToken = IERC4626(vault).asset();
uint256 nativeBalance = address(this).balance;
IWNative wrappedNative = GENERAL_ADAPTER.WRAPPED_NATIVE();
if (vaultToken == address(wrappedNative) && nativeBalance > 0) {
wrappedNative.deposit{value: nativeBalance}();
}
// Deposit any existing vault tokens from the safe into the vault
// This ensures all vault tokens are available for rebalancing operations
MorphoVaultLib._depositAssets(
IERC4626(vault), type(uint256).max, rebalanceAssetsPerShare.vaultDepositAssetsPerShare
);
// Process all decrease operations first (repay debt, withdraw collateral)
// This adds funds to the general adapter, so we need to deposit less vault tokens
for (uint256 i = 0; i < rebalanceData.length; ++i) {
if (rebalanceData[i].amount > 0 && !rebalanceData[i].isIncrease) {
MarketConfig memory marketConfig = vaultConfig.markets[i];
rebalanceAmounts[i] = PriceLib.isLevered(marketConfig.leverage)
? MorphoLeverageLib._decreaseLeverageAllocation(
marketConfig, rebalanceData[i], GENERAL_ADAPTER, MORPHO, BUNDLER3
)
: MorphoSeedingLib._decreaseSeedingAllocation(vault, marketConfig, rebalanceData[i]);
}
}
// Process all increase operations (supply collateral, borrow assets)
// This requires funds to be transferred out of the vault for the operations
for (uint256 i = 0; i < rebalanceData.length; ++i) {
if (rebalanceData[i].isIncrease) {
if (rebalanceData[i].amount > 0) {
MarketConfig memory marketConfig = vaultConfig.markets[i];
rebalanceAmounts[i] = PriceLib.isLevered(marketConfig.leverage)
? MorphoLeverageLib._increaseLeverageAllocation(
vault, marketConfig, rebalanceData[i], GENERAL_ADAPTER, BUNDLER3, MORPHO
)
: MorphoSeedingLib._increaseSeedingAllocation(vault, marketConfig, rebalanceData[i]);
}
rebalanceAreIncreases[i] = true;
}
}
MorphoLeverageLib._cleanGeneralAdapter(
IERC4626(vault),
IERC20(vaultToken),
rebalanceAssetsPerShare.vaultDepositAssetsPerShare,
GENERAL_ADAPTER,
BUNDLER3
);
// Emit rebalancing event with all operation details for transparency and tracking
emit Rebalanced(address(this), vault, rebalanceAmounts, rebalanceAreIncreases);
}
}
"
},
"src/libraries/VaultController.sol": {
"content": "// SPDX-License-Identifier: BSL-1.1
pragma solidity ^0.8.24;
import {IVaultController} from "../interfaces/controllers/IVaultController.sol";
import {VaultConfig} from "../types/StrategyTypes.sol";
import {RebalanceData} from "../types/RebalanceTypes.sol";
import {StrategyManager} from "./StrategyManager.sol";
import {DelegateController} from "./DelegateController.sol";
/**
* @title VaultController
* @author Variable Logic Labs, Corp (hello@blend.money)
* @notice Abstract contract providing a secure and controlled execution environment for vault actions.
* @dev This contract serves as a base for specific vault controller implementations.
* It validates vaults and their associated action controllers using a `RolesReceiver` contract,
* ensuring that only authorized operations can be performed.
* It is designed to be inherited from, with child contracts implementing the core logic.
*/
abstract contract VaultController is IVaultController, DelegateController {
/*//////////////////////////////////////////////////////////////
STATE VARIABLES
//////////////////////////////////////////////////////////////*/
StrategyManager internal immutable STRATEGY_MANAGER;
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
/**
* @notice Emitted when a rebalance is executed
* @param safeAddress The address of the safe initiating the rebalance
* @param vault The vault where the rebalance occurs
* @param rebalanceAmounts The amounts for each rebalance operation
* @param rebalanceAreIncreases Booleans indicating if the rebalance is an increase or decrease
*/
event Rebalanced(
address indexed safeAddress, address indexed vault, uint256[] rebalanceAmounts, bool[] rebalanceAreIncreases
);
/*//////////////////////////////////////////////////////////////
ERRORS
//////////////////////////////////////////////////////////////*/
/// @notice Thrown when the rebalance data does not match the vault markets.
error InvalidRebalanceData();
/// @notice Thrown when zero address is provided where it is not allowed.
error ZeroAddress();
/// @notice Thrown when the vault is not real
error InvalidVault();
/*//////////////////////////////////////////////////////////////
MODIFIERS
//////////////////////////////////////////////////////////////*/
/**
* @notice Validates rebalance data against vault configuration
* @dev Ensures vault exists and rebalance data matches market configuration
* @param rebalanceData Array of rebalance data that should match vault markets
* @custom:throws InvalidRebalanceData If rebalance data length doesn't match markets length
* @custom:throws InvalidMarketId If rebalance data market ID doesn't match vault market ID
*/
modifier _validateRebalanceData(address vault, RebalanceData[] calldata rebalanceData) {
// Resolve the config directly from the strategy manager
VaultConfig memory vaultConfig = STRATEGY_MANAGER.getVaultConfig(vault);
// Ensure rebalance data matches number of markets in vault config
require(rebalanceData.length == vaultConfig.markets.length, InvalidRebalanceData());
// Ensure that the vault is real
require(address(vaultConfig.control) != address(0), InvalidVault());
// Ensure that there is at least one market
require(vaultConfig.markets.length > 0, InvalidVault());
// Ensure rebalance data matches market ID for each market
for (uint256 i = 0; i < rebalanceData.length; i++) {
require(rebalanceData[i].marketId == vaultConfig.markets[i].marketId, InvalidRebalanceData());
}
// Continue execution
_;
}
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
/**
* @notice Constructor for the VaultController
* @param _strategyManager The strategy manager contract
*/
constructor(StrategyManager _strategyManager) {
require(address(_strategyManager) != address(0), ZeroAddress());
STRATEGY_MANAGER = _strategyManager;
}
}
"
},
"src/libraries/MorphoVaultLib.sol": {
"content": "// SPDX-License-Identifier: BSL-1.1
pragma solidity ^0.8.24;
import {RebalanceAssetsPerShare, StoredRebalanceAssetsPerShare} from "../types/RebalanceTypes.sol";
import {IERC4626} from "@openzeppelin/contracts/interfaces/IERC4626.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {MathRayLib} from "bundler3/src/libraries/MathRayLib.sol";
/**
* @title MorphoVault
* @author Variable Logic Labs, Corp (hello@blend.money)
* @notice A library for interacting with ERC-4626 vaults, specifically for depositing and withdrawing assets.
* @dev This library provides internal functions to handle deposits and withdrawals from ERC-4626 compliant vaults,
* including slippage protection based on share price.
*/
library MorphoVaultLib {
using MathRayLib for uint256;
/*//////////////////////////////////////////////////////////////
ERRORS
//////////////////////////////////////////////////////////////*/
/// @notice Thrown when the leverage is less than or equal to 1x.
error InvalidLeverage();
/// @notice Thrown when the slippage is exceeded.
error SlippageExceeded();
/*//////////////////////////////////////////////////////////////
FUNCTIONS
//////////////////////////////////////////////////////////////*/
/// @notice Given a leverage and an amount, returns borrow amount needed to satisfy the LTV.
/// @dev LTV is defined as 1 - (1 / leverage)
/// @param leverage The leverage to use - 1e18 = 1x leverage
/// @param amount The amount to borrow.
/// @return The amount to borrow.
function _borrowAmount(uint256 leverage, uint256 amount) internal pure returns (uint256) {
require(leverage > 1e18, InvalidLeverage());
// Calculate LTV: 1 - (1 / leverage) = (leverage - 1e18) / leverage
// Borrow amount = total_position * LTV
return (amount * (leverage - 1e18)) / leverage;
}
/// @notice Given a leverage and a total position, returns the user amount needed to complete the flashloan.
/// @dev This is the opposite of _borrowAmount. User amount = totalPosition / leverage
/// @param leverage The leverage to use - 1e18 = 1x leverage
/// @param totalPosition The total position size after leverage.
/// @return The amount the user needs to provide.
function _userAmount(uint256 leverage, uint256 totalPosition) internal pure returns (uint256) {
require(leverage > 1e18, InvalidLeverage());
return (totalPosition * 1e18) / leverage;
}
/**
* @notice Unpacks the stored rebalance assets per share from the extra data
* @param extraData The extra data containing the stored rebalance assets per share
* @return rebalanceAssetsPerShare The rebalance assets per share
* @return remainingExtraData The remaining extra data
*/
function _unpackPriceData(bytes memory extraData)
internal
pure
returns (RebalanceAssetsPerShare memory rebalanceAssetsPerShare, bytes memory remainingExtraData)
{
(StoredRebalanceAssetsPerShare memory storedRebalanceAssetsPerShare, bytes memory _remainingExtraData) =
abi.decode(extraData, (StoredRebalanceAssetsPerShare, bytes));
rebalanceAssetsPerShare = RebalanceAssetsPerShare({
vaultDepositAssetsPerShare: storedRebalanceAssetsPerShare.vaultDepositAssetsPerShare,
vaultWithdrawAssetsPerShare: storedRebalanceAssetsPerShare.vaultWithdrawAssetsPerShare,
morphoBorrowAssetsPerShare: storedRebalanceAssetsPerShare.morphoBorrowAssetsPerShare,
morphoRepayAssetsPerShare: storedRebalanceAssetsPerShare.morphoRepayAssetsPerShare,
wrappedCollateralAssetsPerShare: storedRebalanceAssetsPerShare.wrappedCollateralAssetsPerShare
});
remainingExtraData = _remainingExtraData;
}
/**
* @notice Unpacks the true withdraw collateral token from the extra data
* @param extraData The extra data to unpack
* @return trueWithdrawCollateralToken The true withdraw collateral token
* @return remainingData The remaining data
*/
function _unpackSeedingDecreaseData(bytes memory extraData)
internal
pure
returns (IERC20 trueWithdrawCollateralToken, bytes memory remainingData)
{
(trueWithdrawCollateralToken, remainingData) = abi.decode(extraData, (IERC20, bytes));
}
/**
* @notice Redeems shares from a vault
* @param vault The vault to redeem shares from
* @param shares The number of shares to redeem
* @param assetPerShare The asset per share of the vault
* @dev If shares is 0, the function does nothing
* @custom:link https://github.com/morpho-org/bundler3/blob/e6fdcbcd2a59fe5fe9cbd9e0cf7d45f5d2aa3f49/src/adapters/GeneralAdapter1.sol#L126-L139
*/
function _redeemShares(IERC4626 vault, uint256 shares, uint256 assetPerShare) internal {
shares = shares == type(uint256).max ? vault.balanceOf(address(this)) : shares;
if (shares == 0) return;
uint256 assets = vault.redeem(shares, address(this), address(this));
require(assets.rDivDown(shares) >= assetPerShare, SlippageExceeded());
}
/**
* @notice Deposits assets into a vault
* @param vault The vault to deposit assets into
* @param assets The number of assets to deposit
* @param assetPerShare The asset per share of the vault
* @dev If assets is 0, the function does nothing
* @custom:link https://github.com/morpho-org/bundler3/blob/e6fdcbcd2a59fe5fe9cbd9e0cf7d45f5d2aa3f49/src/adapters/GeneralAdapter1.sol#L76-L94
*/
function _depositAssets(IERC4626 vault, uint256 assets, uint256 assetPerShare) internal {
IERC20 underlying = IERC20(vault.asset());
assets = assets == type(uint256).max ? underlying.balanceOf(address(this)) : assets;
if (assets == 0) return;
SafeERC20.forceApprove(underlying, address(vault), type(uint256).max);
uint256 shares = vault.deposit(assets, address(this));
SafeERC20.forceApprove(underlying, address(vault), 0);
require(assets.rDivUp(shares) <= assetPerShare, SlippageExceeded());
}
/**
* @notice Withdraws assets from a vault
* @param vault The vault to withdraw assets from
* @param assets The number of assets to withdraw
* @param assetPerShare The asset per share of the vault
* @dev If assets is 0, the function does nothing and if assets is type(uint256).max, the function
* redeems the entire balance of the vault (through _redeemShares)
* @custom:link https://github.com/morpho-org/bundler3/blob/e6fdcbcd2a59fe5fe9cbd9e0cf7d45f5d2aa3f49/src/adapters/GeneralAdapter1.sol#L105-L115
*/
function _withdrawAssets(IERC4626 vault, uint256 assets, uint256 assetPerShare) internal {
if (assets == type(uint256).max) {
_redeemShares(vault, type(uint256).max, assetPerShare);
} else if (assets > 0) {
uint256 shares = vault.withdraw(assets, address(this), address(this));
require(assets.rDivDown(shares) >= assetPerShare, SlippageExceeded());
}
}
}
"
},
"lib/bundler3/src/interfaces/IBundler3.sol": {
"content": "// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.8.0;
/// @notice Struct containing all the data needed to make a call.
/// @notice The call target is `to`, the calldata is `data` with value `value`.
/// @notice If `skipRevert` is true, other planned calls will continue executing even if this call reverts. `skipRevert`
/// will ignore all reverts. Use with caution.
/// @notice If the call will trigger a reenter, the callbackHash should be set to the hash of the reenter bundle data.
struct Call {
address to;
bytes data;
uint256 value;
bool skipRevert;
bytes32 callbackHash;
}
/// @custom:security-contact security@morpho.org
interface IBundler3 {
function multicall(Call[] calldata) external payable;
function reenter(Call[] calldata) external;
function reenterHash() external view returns (bytes32);
function initiator() external view returns (address);
}
"
},
"lib/morpho-blue/src/interfaces/IMorpho.sol": {
"content": "// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;
type Id is bytes32;
struct MarketParams {
address loanToken;
address collateralToken;
address oracle;
address irm;
uint256 lltv;
}
/// @dev Warning: For `feeRecipient`, `supplyShares` does not contain the accrued shares since the last interest
/// accrual.
struct Position {
uint256 supplyShares;
uint128 borrowShares;
uint128 collateral;
}
/// @dev Warning: `totalSupplyAssets` does not contain the accrued interest since the last interest accrual.
/// @dev Warning: `totalBorrowAssets` does not contain the accrued interest since the last interest accrual.
/// @dev Warning: `totalSupplyShares` does not contain the additional shares accrued by `feeRecipient` since the last
/// interest accrual.
struct Market {
uint128 totalSupplyAssets;
uint128 totalSupplyShares;
uint128 totalBorrowAssets;
uint128 totalBorrowShares;
uint128 lastUpdate;
uint128 fee;
}
struct Authorization {
address authorizer;
address authorized;
bool isAuthorized;
uint256 nonce;
uint256 deadline;
}
struct Signature {
uint8 v;
bytes32 r;
bytes32 s;
}
/// @dev This interface is used for factorizing IMorphoStaticTyping and IMorpho.
/// @dev Consider using the IMorpho interface instead of this one.
interface IMorphoBase {
/// @notice The EIP-712 domain separator.
/// @dev Warning: Every EIP-712 signed message based on this domain separator can be reused on another chain sharing
/// the same chain id because the domain separator would be the same.
function DOMAIN_SEPARATOR() external view returns (bytes32);
/// @notice The owner of the contract.
/// @dev It has the power to change the owner.
/// @dev It has the power to set fees on markets and set the fee recipient.
/// @dev It has the power to enable but not disable IRMs and LLTVs.
function owner() external view returns (address);
/// @notice The fee recipient of all markets.
/// @dev The recipient receives the fees of a given market through a supply position on that market.
function feeRecipient() external view returns (address);
/// @notice Whether the `irm` is enabled.
function isIrmEnabled(address irm) external view returns (bool);
/// @notice Whether the `lltv` is enabled.
function isLltvEnabled(uint256 lltv) external view returns (bool);
/// @notice Whether `authorized` is authorized to modify `authorizer`'s position on all markets.
/// @dev Anyone is authorized to modify their own positions, regardless of this variable.
function isAuthorized(address authorizer, address authorized) external view returns (bool);
/// @notice The `authorizer`'s current nonce. Used to prevent replay attacks with EIP-712 signatures.
function nonce(address authorizer) external view returns (uint256);
/// @notice Sets `newOwner` as `owner` of the contract.
/// @dev Warning: No two-step transfer ownership.
/// @dev Warning: The owner can be set to the zero address.
function setOwner(address newOwner) external;
/// @notice Enables `irm` as a possible IRM for market creation.
/// @dev Warning: It is not possible to disable an IRM.
function enableIrm(address irm) external;
/// @notice Enables `lltv` as a possible LLTV for market creation.
/// @dev Warning: It is not possible to disable a LLTV.
function enableLltv(uint256 lltv) external;
/// @notice Sets the `newFee` for the given market `marketParams`.
/// @param newFee The new fee, scaled by WAD.
/// @dev Warning: The recipient can be the zero address.
function setFee(MarketParams memory marketParams, uint256 newFee) external;
/// @notice Sets `newFeeRecipient` as `feeRecipient` of the fee.
/// @dev Warning: If the fee recipient is set to the zero address, fees will accrue there and will be lost.
/// @dev Modifying the fee recipient will allow the new recipient to claim any pending fees not yet accrued. To
/// ensure that the current recipient receives all due fees, accrue interest manually prior to making any changes.
function setFeeRecipient(address newFeeRecipient) external;
/// @notice Creates the market `marketParams`.
/// @dev Here is the list of assumptions on the market's dependencies (tokens, IRM and oracle) that guarantees
/// Morpho behaves as expected:
/// - The token should be ERC-20 compliant, except that it can omit return values on `transfer` and `transferFrom`.
/// - The token balance of Morpho should only decrease on `transfer` and `transferFrom`. In particular, tokens with
/// burn functions are not supported.
/// - The token should not re-enter Morpho on `transfer` nor `transferFrom`.
/// - The token balance of the sender (resp. receiver) should decrease (resp. increase) by exactly the given amount
/// on `transfer` and `transferFrom`. In particular, tokens with fees on transfer are not supported.
/// - The IRM should not re-enter Morpho.
/// - The oracle should return a price with the correct scaling.
/// @dev Here is a list of properties on the market's dependencies that could break Morpho's liveness properties
/// (funds could get stuck):
/// - The token can revert on `transfer` and `transferFrom` for a reason other than an approval or balance issue.
/// - A very high amount of assets (~1e35) supplied or borrowed can make the computation of `toSharesUp` and
/// `toSharesDown` overflow.
/// - The IRM can revert on `borrowRate`.
/// - A very high borrow rate returned by the IRM can make the computation of `interest` in `_accrueInterest`
/// overflow.
/// - The oracle can revert on `price`. Note that this can be used to prevent `borrow`, `withdrawCollateral` and
/// `liquidate` from being used under certain market conditions.
/// - A very high price returned by the oracle can make the computation of `maxBorrow` in `_isHealthy` overflow, or
/// the computation of `assetsRepaid` in `liquidate` overflow.
/// @dev The borrow share price of a market with less than 1e4 assets borrowed can be decreased by manipulations, to
/// the point where `totalBorrowShares` is very large and borrowing overflows.
function createMarket(MarketParams memory marketParams) external;
/// @notice Supplies `assets` or `shares` on behalf of `onBehalf`, optionally calling back the caller's
/// `onMorphoSupply` function with the given `data`.
/// @dev Either `assets` or `shares` should be zero. Most use cases should rely on `assets` as an input so the
/// caller is guaranteed to have `assets` tokens pulled from their balance, but the possibility to mint a specific
/// amount of shares is given for full compatibility and precision.
/// @dev Supplying a large amount can revert for overflow.
/// @dev Supplying an amount of shares may lead to supply more or fewer assets than expected due to slippage.
/// Consider using the `assets` parameter to avoid this.
/// @param marketParams The market to supply assets to.
/// @param assets The amount of assets to supply.
/// @param shares The amount of shares to mint.
/// @param onBehalf The address that will own the increased supply position.
/// @param data Arbitrary data to pass to the `onMorphoSupply` callback. Pass empty data if not needed.
/// @return assetsSupplied The amount of assets supplied.
/// @return sharesSupplied The amount of shares minted.
function supply(
MarketParams memory marketParams,
uint256 assets,
uint256 shares,
address onBehalf,
bytes memory data
) external returns (uint256 assetsSupplied, uint256 sharesSupplied);
/// @notice Withdraws `assets` or `shares` on behalf of `onBehalf` and sends the assets to `receiver`.
/// @dev Either `assets` or `shares` should be zero. To withdraw max, pass the `shares`'s balance of `onBehalf`.
/// @dev `msg.sender` must be authorized to manage `onBehalf`'s positions.
/// @dev Withdrawing an amount corresponding to more shares than supplied will revert for underflow.
/// @dev It is advised to use the `shares` input when withdrawing the full position to avoid reverts due to
/// conversion roundings between shares and assets.
/// @param marketParams The market to withdraw assets from.
/// @param assets The amount of assets to withdraw.
/// @param shares The amount of shares to burn.
/// @param onBehalf The address of the owner of the supply position.
/// @param receiver The address that will receive the withdrawn assets.
/// @return assetsWithdrawn The amount of assets withdrawn.
/// @return sharesWithdrawn The amount of shares burned.
function withdraw(
MarketParams memory marketParams,
uint256 assets,
uint256 shares,
address onBehalf,
address receiver
) external returns (uint256 assetsWithdrawn, uint256 sharesWithdrawn);
/// @notice Borrows `assets` or `shares` on behalf of `onBehalf` and sends the assets to `receiver`.
/// @dev Either `assets` or `shares` should be zero. Most use cases should rely on `assets` as an input so the
/// caller is guaranteed to borrow `assets` of tokens, but the possibility to mint a specific amount of shares is
/// given for full compatibility and precision.
/// @dev `msg.sender` must be authorized to manage `onBehalf`'s positions.
/// @dev Borrowing a large amount can revert for overflow.
/// @dev Borrowing an amount of shares may lead to borrow fewer assets than expected due to slippage.
/// Consider using the `assets` parameter to avoid this.
/// @param marketParams The market to borrow assets from.
/// @param assets The amount of assets to borrow.
/// @param shares The amount of shares to mint.
/// @param onBehalf The address that will own the increased borrow position.
/// @param receiver The address that will receive the borrowed assets.
/// @return assetsBorrowed The amount of assets borrowed.
/// @return sharesBorrowed The amount of shares minted.
function borrow(
MarketParams memory marketParams,
uint256 assets,
uint256 shares,
address onBehalf,
address receiver
) external returns (uint256 assetsBorrowed, uint256 sharesBorrowed);
/// @notice Repays `assets` or `shares` on behalf of `onBehalf`, optionally calling back the caller's
/// `onMorphoReplay` function with the given `data`.
/// @dev Either `assets` or `shares` should be zero. To repay max, pass the `shares`'s balance of `onBehalf`.
/// @dev Repaying an amount corresponding to more shares than borrowed will revert for underflow.
/// @dev It is advised to use the `shares` input when repaying the full position to avoid reverts due to conversion
/// roundings between shares and assets.
/// @dev An attacker can front-run a repay with a small repay making the transaction revert for underflow.
/// @param marketParams The market to repay assets to.
/// @param assets The amount of assets to repay.
/// @param shares The amount of shares to burn.
/// @param onBehalf The address of the owner of the debt position.
/// @param data Arbitrary data to pass to the `onMorphoRepay` callback. Pass empty data if not needed.
/// @return assetsRepaid The amount of assets repaid.
/// @return sharesRepaid The amount of shares burned.
function repay(
MarketParams memory marketParams,
uint256 assets,
uint256 shares,
address onBehalf,
bytes memory data
) external returns (uint256 assetsRepaid, uint256 sharesRepaid);
/// @notice Supplies `assets` of collateral on behalf of `onBehalf`, optionally calling back the caller's
/// `onMorphoSupplyCollateral` function with the given `data`.
/// @dev Interest are not accrued since it's not required and it saves gas.
/// @dev Supplying a large amount can revert for overflow.
/// @param marketParams The market to supply collateral to.
/// @param assets The amount of collateral to supply.
/// @param onBehalf The address that will own the increased collateral position.
/// @param data Arbitrary data to pass to the `onMorphoSupplyCollateral` callback. Pass empty data if not needed.
function supplyCollateral(MarketParams memory marketParams, uint256 assets, address onBehalf, bytes memory data)
external;
/// @notice Withdraws `assets` of collateral on behalf of `onBehalf` and sends the assets to `receiver`.
/// @dev `msg.sender` must be authorized to manage `onBehalf`'s positions.
/// @dev Withdrawing an amount corresponding to more collateral than supplied will revert for underflow.
/// @param marketParams The market to withdraw collateral from.
/// @param assets The amount of collateral to withdraw.
/// @param onBehalf The address of the owner of the collateral position.
/// @param receiver The address that will receive the collateral assets.
function withdrawCollateral(MarketParams memory marketParams, uint256 assets, address onBehalf, address receiver)
external;
/// @notice Liquidates the given `repaidShares` of debt asset or seize the given `seizedAssets` of collateral on the
/// given market `marketParams` of the given `borrower`'s position, optionally calling back the caller's
/// `onMorphoLiquidate` function with the given `data`.
/// @dev Either `seizedAssets` or `repaidShares` should be zero.
/// @dev Seizing more than the collateral balance will underflow and revert without any error message.
/// @dev Repaying more than the borrow balance will underflow and revert without any error message.
/// @dev An attacker can front-run a liquidation with a small repay making the transaction revert for underflow.
/// @param marketParams The market of the position.
/// @param borrower The owner of the position.
/// @param seizedAssets The amount of collateral to seize.
/// @param repaidShares The amount of shares to repay.
/// @param data Arbitrary data to pass to the `onMorphoLiquidate` callback. Pass empty data if not needed.
/// @return The amount of assets seized.
/// @return The amount of assets repaid.
function liquidate(
MarketParams memory marketParams,
address borrower,
uint256 seizedAssets,
uint256 repaidShares,
bytes memory data
) external returns (uint256, uint256);
/// @notice Executes a flash loan.
/// @dev Flash loans have access to the whole balance of the contract (the liquidity and deposited collateral of all
/// markets combined, plus donations).
/// @dev Warning: Not ERC-3156 compliant but compatibility is easily reached:
/// - `flashFee` is zero.
/// - `maxFlashLoan` is the token's balance of this contract.
/// - The receiver of `assets` is the caller.
/// @param token The token to flash loan.
/// @param assets The amount of assets to flash loan.
/// @param data Arbitrary data to pass to the `onMorphoFlashLoan` callback.
function flashLoan(address token, uint256 assets, bytes calldata data) external;
/// @notice Sets the authorization for `authorized` to manage `msg.sender`'s positions.
/// @param authorized The authorized address.
/// @param newIsAuthorized The new authorization status.
function setAuthorization(address authorized, bool newIsAuthorized) external;
/// @notice Sets the authorization for `authorization.authorized` to manage `authorization.authorizer`'s positions.
/// @dev Warning: Reverts if the signature has already been submitted.
/// @dev The signature is malleable, but it has no impact on the security here.
/// @dev The nonce is passed as argument to be able to revert with a different error message.
/// @param authorization The `Authorization` struct.
/// @param signature The signature.
function setAuthorizationWithSig(Authorization calldata authorization, Signature calldata signature) external;
/// @notice Accrues interest for the given market `marketParams`.
function accrueInterest(MarketParams memory marketParams) external;
/// @notice Returns the data stored on the different `slots`.
function extSloads(bytes32[] memory slots) external view returns (bytes32[] memory);
}
/// @dev This interface is inherited by Morpho so that function signatures are checked by the compiler.
/// @dev Consider using the IMorpho interface instead of this one.
interface IMorphoStaticTyping is IMorphoBase {
/// @notice The state of the position of `user` on the market corresponding to `id`.
/// @dev Warning: For `feeRecipient`, `supplyShares` does not contain the accrued shares since the last interest
/// accrual.
function position(Id id, address user)
external
view
returns (uint256 supplyShares, uint128 borrowShares, uint128 collateral);
/// @notice The state of the market corresponding to `id`.
/// @dev Warning: `totalSupplyAssets` does not contain the accrued interest since the last interest accrual.
/// @dev Warning: `totalBorrowAssets` does not contain the accrued interest since the last interest accrual.
/// @dev Warning: `totalSupplyShares` does not contain the accrued shares by `feeRecipient` since the last interest
/// accrual.
function market(Id id)
external
view
returns (
uint128 totalSupplyAssets,
uint128 totalSupplyShares,
uint128 totalBorrowAssets,
uint128 totalBorrowShares,
uint128 lastUpdate,
uint128 fee
);
/// @notice The market params corresponding to `id`.
/// @dev This mapping is not used in Morpho. It is there to enable reducing the cost associated to calldata on layer
/// 2s by creating a wrapper contract with functions that take `id` as input instead of `marketParams`.
function idToMarketParams(Id id)
external
view
returns (address loanToken, address collateralToken, address oracle, address irm, uint256 lltv);
}
/// @title IMorpho
/// @author Morpho Labs
/// @custom:contact security@morpho.org
/// @dev Use this interface for Morpho to have access to all the functions with the appropriate function signatures.
interface IMorpho is IMorphoBase {
/// @notice The state of the position of `user` on the market corresponding to `id`.
/// @dev Warning: For `feeRecipient`, `p.supplyShares` does not contain the accrued shares since the last interest
/// accrual.
function position(Id id, address user) external view returns (Position memory p);
/// @notice The state of the market corresponding to `id`.
/// @dev Warning: `m.totalSupplyAssets` does not contain the accrued interest since the last interest accrual.
/// @dev Warning: `m.totalBorrowAssets` does not contain the accrued interest since the last interest accrual.
/// @dev Warning: `m.totalSupplyShares` does not contain the accrued shares by `feeRecipient` since the last
/// interest accrual.
function market(Id id) external view returns (Market memory m);
/// @notice The market params corresponding to `id`.
/// @dev This mapping is not used in Morpho. It is there to enable reducing the cost associated to calldata on layer
/// 2s by creating a wrapper contract with functions that take `id` as input instead of `marketParams`.
function idToMarketParams(Id id) external view returns (MarketParams memory);
}
"
},
"lib/bundler3/src/adapters/GeneralAdapter1.sol": {
"content": "// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.8.28;
import {IWNative} from "../interfaces/IWNative.sol";
import {IERC4626} from "../../lib/openzeppelin-contracts/contracts/interfaces/IERC4626.sol";
import {MarketParams, IMorpho} from "../../lib/morpho-blue/src/interfaces/IMorpho.sol";
import {CoreAdapter, ErrorsLib, IERC20, SafeERC20, Address} from "./CoreAdapter.sol";
import {MathRayLib} from "../libraries/MathRayLib.sol";
import {SafeCast160} from "../../lib/permit2/src/libraries/SafeCast160.sol";
import {Permit2Lib} from "../../lib/permit2/src/libraries/Permit2Lib.sol";
import {MorphoBalancesLib} from "../../lib/morpho-blue/src/libraries/periphery/MorphoBalancesLib.sol";
import {MarketParamsLib} from "../../lib/morpho-blue/src/libraries/MarketParamsLib.sol";
import {MorphoLib} from "../../lib/morpho-blue/src/libraries/periphery/MorphoLib.sol";
/// @custom:security-contact security@morpho.org
/// @notice Chain agnostic adapter contract n°1.
contract GeneralAdapter1 is CoreAdapter {
using SafeCast160 for uint256;
using MarketParamsLib for MarketParams;
using MathRayLib for uint256;
/* IMMUTABLES */
/// @notice The address of the Morpho contract.
IMorpho public immutable MORPHO;
/// @dev The address of the wrapped native token.
IWNative public immutable WRAPPED_NATIVE;
/* CONSTRUCTOR */
/// @param bundler3 The address of the Bundler3 contract.
/// @param morpho The address of the Morpho protocol.
/// @param wNative The address of the canonical native token wrapper.
constructor(address bundler3, address morpho, address wNative) CoreAdapter(bundler3) {
require(morpho != address(0), ErrorsLib.ZeroAddress());
require(wNative != address(0), ErrorsLib.ZeroAddress());
MORPHO = IMorpho(morpho);
WRAPPED_NATIVE = IWNative(wNative);
}
/* ERC4626 ACTIONS */
/// @notice Mints shares of an ERC4626 vault.
/// @dev Underlying tokens must have been previously sent to the adapter.
/// @dev Assumes the given vault implements EIP-4626.
/// @param vault The address of the vault.
/// @param shares The amount of vault shares to mint.
/// @param maxSharePriceE27 The maximum amount of assets to pay to get 1 share, scaled by 1e27.
/// @param receiver The address to which shares will be minted.
function erc4626Mint(address vault, uint256 shares, uint256 maxSharePriceE27, address receiver)
external
onlyBundler3
{
require(receiver != address(0), ErrorsLib.ZeroAddress());
require(shares != 0, ErrorsLib.ZeroShares());
IERC20 underlyingToken = IERC20(IERC4626(vault).asset());
SafeERC20.forceApprove(underlyingToken, vault, type(uint256).max);
uint256 assets = IERC4626(vault).mint(shares, receiver);
SafeERC20.forceApprove(underlyingToken, vault, 0);
require(assets.rDivUp(shares) <= maxSharePriceE27, ErrorsLib.SlippageExceeded());
}
/// @notice Deposits underlying token in an ERC4626 vault.
/// @dev Underlying tokens must have been previously sent to the adapter.
/// @dev Assumes the given vault implements EIP-4626.
/// @param vault The address of the vault.
/// @param assets The amount of underlying token to deposit. Pass `type(uint).max` to deposit the adapter's balance.
/// @param maxSharePriceE27 The maximum amount of assets to pay to get 1 share, scaled by 1e27.
/// @param receiver The address to which shares will be minted.
function erc4626Deposit(address vault, uint256 assets, uint256 maxSharePriceE27, address receiver)
external
onlyBundler3
{
require(receiver != address(0), ErrorsLib.ZeroAddress());
IERC20 underlyingToken = IERC20(IERC4626(vault).asset());
if (assets == type(uint256).max) assets = underlyingToken.balanceOf(address(this));
require(assets != 0, ErrorsLib.ZeroAmount());
SafeERC20.forceApprove(underlyingToken, vault, type(uint256).max);
uint256 shares = IERC4626(vault).deposit(assets, receiver);
SafeERC20.forceApprove(underlyingToken, vault, 0);
require(assets.rDivUp(shares) <= maxSharePriceE27, ErrorsLib.SlippageExceeded());
}
/// @notice Withdraws underlying token from an ERC4626 vault.
/// @dev Assumes the given `vault` implements EIP-4626.
/// @dev If `owner` is the initiator, they must have previously approved the adapter to spend their vault shares.
/// Otherwise, vault shares must have been previously sent to the adapter.
/// @param vault The address of the vault.
/// @param assets The amount of underlying token to withdraw.
/// @param minSharePriceE27 The minimum number of assets to receive per share, scaled by 1e27.
/// @param receiver The address that will receive the withdrawn assets.
/// @param owner The address on behalf of which the assets are withdrawn. Can only be the adapter or the initiator.
function erc4626Withdraw(address vault, uint256 assets, uint256 minSharePriceE27, address receiver, address owner)
external
onlyBundler3
{
require(receiver != address(0), ErrorsLib.ZeroAddress());
require(owner == address(this) || owner == initiator(), ErrorsLib.UnexpectedOwner());
require(assets != 0, ErrorsLib.ZeroAmount());
uint256 shares = IERC4626(vault).withdraw(assets, receiver, owner);
require(assets.rDivDown(shares) >= minSharePriceE27, ErrorsLib.SlippageExceeded());
}
/// @notice Redeems shares of an ERC4626 vault.
/// @dev Assumes the given `vault` implements EIP-4626.
/// @dev If `owner` is the initiator, they must have previously approved the adapter to spend their vault shares.
/// Otherwise, vault shares must have been previously sent to the adapter.
/// @param vault The address of the vault.
/// @param shares The amount of vault shares to redeem. Pass `type(uint).max` to redeem the owner's shares.
/// @param minSharePriceE27 The minimum number of assets to receive per share, scaled by 1e27.
/// @param receiver The address that will receive the withdrawn assets.
/// @param owner The address on behalf of which the shares are redeemed. Can only be the adapter or the initiator.
function erc4626Redeem(address vault, uint256 shares, uint256 minSharePriceE27, address receiver, address owner)
external
onlyBundler3
{
require(receiver != address(0), ErrorsLib.ZeroAddress());
require(owner == address(this) || owner == initiator(), ErrorsLib.UnexpectedOwner());
if (shares == type(uint256).max) shares = IERC4626(vault).balanceOf(owner);
require(shares != 0, ErrorsLib.ZeroShares());
uint256 assets = IERC4626(vault).redeem(shares, receiver, owner);
require(assets.rDivDown(shares) >= minSharePriceE27, ErrorsLib.SlippageExceeded());
}
/* MORPHO CALLBACKS */
/// @notice Receives supply callback from the Morpho contract.
/// @param data Bytes containing an abi-encoded Call[].
function onMorphoSupply(uint256, bytes calldata data) external {
morphoCallback(data);
}
/// @notice Receives supply collateral callback from the Morpho contract.
/// @param data Bytes containing an abi-encoded Call[].
function onMorphoSupplyCollateral(uint256, bytes calldata data) external {
morphoCallback(data);
}
/// @notice Receives repay callback from the Morpho contract.
/// @param data Bytes containing an abi-encoded Call[].
function onMorphoRepay(uint256, bytes calldata data) external {
morphoCallback(data);
}
/// @notice Receives flashloan callback from the Morpho contract.
/// @param data Bytes containing an abi-encoded Call[].
function onMorphoFlashLoan(uint256, bytes calldata data) external {
morphoCallback(data);
}
/* MORPHO ACTIONS */
/// @notice Supplies loan asset on Morpho.
/// @dev Either `assets` or `shares` should be zero. Most usecases should rely on `assets` as an input so the
/// adapter is guaranteed to have `assets` tokens pulled from its balance, but the possibility to mint a specific
/// amount of shares is given for full compatibility and precision.
/// @dev Loan tokens must have been previously sent to the adapter.
/// @param marketParams The Morpho market to supply assets to.
/// @param assets The amount of assets to supply. Pass `type(uint).max` to supply the adapter's loan asset balance.
/// @param shares The amount of shares to mint.
/// @param maxSharePriceE27 The maximum amount of assets supplied per minted share, scaled by 1e27.
/// @param onBehalf The address that will own the increased supply position.
/// @param data Arbitrary data to pass to the `onMorphoSupply` callback. Pass empty data if not needed.
function morphoSupply(
MarketParams calldata marketParams,
uint256 assets,
uint256 shares,
uint256 maxSharePriceE27,
address onBehalf,
bytes calldata data
) external onlyBundler3 {
// Do not check `onBehalf` against the zero address as it's done in Morpho.
require(onBehalf != address(this), ErrorsLib.AdapterAddress());
if (assets == type(uint256).max) {
assets = IERC20(marketParams.loanToken).balanceOf(address(this));
require(assets != 0, ErrorsLib.ZeroAmount());
}
// Morpho's allowance is not reset as it is trusted.
SafeERC20.forceApprove(IERC20(marketParams.loanToken), address(MORPHO), type(uint256).max);
(uint256 suppliedAssets, uint256 suppliedShares) = MORPHO.supply(marketParams, assets, shares, onBehalf, data);
require(suppliedAssets.rDivUp(suppliedShares) <= maxSharePriceE27, ErrorsLib.SlippageExceeded());
}
/// @notice Supplies collateral on Morpho.
/// @dev Collateral tokens must have been previously sent to the adapter.
/// @param marketParams The Morpho market to supply collateral to.
/// @param assets The amount of collateral to supply. Pass `type(uint).max` to supply the adapter's collateral
/// balance.
/// @param onBehalf The address that will own the increased collateral position.
/// @param data Arbitrary data to pass to the `onMorphoSupplyCollateral` callback. Pass empty data if not needed.
function morphoSupplyCollateral(
MarketParams calldata marketParams,
uint256 assets,
address onBehalf,
bytes calldata data
) external onlyBundler3 {
// Do not check `onBehalf` against the zero address as it's done at Morpho's level.
require(onBehalf != address(this), ErrorsLib.AdapterAddress());
if (assets == type(uint256).max) assets = IERC20(marketParams.collateralToken).balanceOf(address(this));
require(assets != 0, ErrorsLib.ZeroAmount());
// Morpho's allowance is not reset as it is trusted.
SafeERC20.forceApprove(IERC20(marketParams.collateralToken), address(MORPHO), type(uint256).max);
MORPHO.supplyCollateral(marketParams, assets, onBehalf, data);
}
/// @notice Borrows assets on Morpho.
/// @dev Either `assets` or `shares` should be zero. Most usecases should rely on `assets` as an input so the
/// initiator is guaranteed to borrow `assets` tokens, but the possibility to mint a specific amount of shares is
/// given for full compatibility and precision.
/// @dev Initiator must have previously authorized the adapter to act on their behalf on Morpho.
/// @param marketParams The Morpho market to borrow assets from.
/// @param assets The amount of assets to borrow.
/// @param shares The amount of shares to mint.
/// @param minSharePriceE27 The minimum amount of assets borrowed per borrow share minted, scaled by 1e27.
/// @param receiver The address that will receive the borrowed assets.
function morphoBorrow(
MarketParams calldata marketParams,
uint256 assets,
uint256 shares,
uint256 minSharePriceE27,
address receiver
) external onlyBundler3 {
(uint256 borrowedAssets, uint256 borrowedShares) =
MORPHO.borrow(marketParams, assets, shares, initiator(), receiver);
require(borrowedAssets.rDivDown(borrowedShares) >= minSharePriceE27, ErrorsLib.SlippageExceeded());
}
/// @notice Repays assets on Morpho.
/// @dev Either `assets` or `shares` should be zero. Most usecases should rely on `assets` as an input so the
/// adapter is guaranteed to have `assets` tokens pulled from its balance, but the possibility to burn a specific
/// amount of shares is given for full compatibility and precision.
/// @dev Loan tokens must have been previously sent to the adapter.
/// @param marketParams The Morpho market to repay assets to.
/// @param assets The amount of assets to repay. Pass `type(uint).max` to repay the adapter's loan asset balance.
/// @param shares The amount of shares to burn. Pass `type(uint).max` to repay the initiator's entire debt.
/// @param maxSharePriceE27 The maximum amount of assets repaid per borrow share burned, scaled by 1e27.
/// @param onBehalf The address of the owner of the debt position.
/// @param data Arbitrary data to pass to the `onMorphoRepay` callback. Pass empty data if not needed.
function morphoRepay(
MarketParams calldata marketParams,
uint256 assets,
uint256 shares,
uint256 maxSharePriceE27,
address onBehalf,
bytes calldata data
) external onlyBundler3 {
// Do not check `onBehalf` against the zero address as it's done at Morpho's level.
require(onBehalf != address(this), ErrorsLib.AdapterAddress());
if (assets == type(uint256).max) {
assets = IERC20(marketParams.loanToken).balanceOf(address(this));
require(assets != 0, ErrorsLib.ZeroAmount());
}
if (shares == type(uint256).max) {
shares = MorphoLib.borrowShares(MORPHO, marketParams.id(), onBehalf);
require(shares != 0, ErrorsLib.ZeroAmount());
}
// Morpho's allowance is not reset as it is trusted.
SafeERC20.forceApprove(IERC20(marketParams.loanToken), address(MORPHO), type(uint256).max);
(uint256 repaidAssets, uint256 repaidShares) = MORPHO.repay(marketParams, assets, shares, onBehalf, data);
require(repaidAssets.rDivUp(repaidShares) <= maxSharePriceE27, ErrorsLib.SlippageExceeded());
}
/// @notice Withdraws assets on Morpho.
/// @dev Either `assets` or `shares` should be zero. Most usecases should rely on `assets` as an input so the
/// initiator is guaranteed to withdraw `assets` tokens, but the possibility to burn a specific amount of shares is
/// given for full compatibility and precision.
/// @dev Initiator must have previously authorized the maodule to act on their behalf on Morpho.
/// @param marketParams The Morpho market to withdraw assets from.
/// @param assets The amount of assets to withdraw.
/// @param shares The amount of shares to burn. Pass `type(uint).max` to burn all the initiator's supply shares.
/// @param minSharePriceE27 The minimum amount of assets withdraw per burn share, scaled by 1e27.
/// @param receiver The address that will receive the withdrawn assets.
function morphoWithdraw(
MarketParams calldata marketParams,
uint256 assets,
uint256 shares,
uint256 minSharePriceE27,
address receiver
) external onlyBundler3 {
if (shares == type(uint256).max) {
shares = MorphoLib.supplyShares(MORPHO, marketParams.id(), initiator());
require(shares != 0, ErrorsLib.ZeroAmount());
}
(uint256 withdrawnAssets, uint256 withdrawnShares) =
MORPHO.withdraw(marketParams, assets, shares, initiator(), receiver);
require(withdrawnAssets.rDivDown(withdrawnShares) >= minSharePriceE27, ErrorsLib.SlippageExceeded());
}
/// @notice Withdraws collateral from Morpho.
/// @dev Initiator must have previously authorized the adapter to act on their behalf on Morpho.
/// @param marketParams The Morpho market to withdraw collateral from.
/// @param assets The amount of collateral to withdraw. Pass `type(uint).max` to withdraw the initiator's collateral
/// balance.
/// @param receiver The address that will receive the collateral assets.
function morphoWithdrawCollateral(MarketParams calldata marketParams, uint256 assets, address receiver)
external
onlyBundler3
{
if (assets == type(uint256).max) assets = MorphoLib.collateral(MORPHO, marketParams.id(), initiator());
require(assets != 0, ErrorsLib.ZeroAmount());
MORPHO.withdrawCollateral(marketParams, assets, initiator(), receiver);
}
/// @notice Triggers a flash loan on Morpho.
/// @param token The address of the token to flash loan.
/// @param assets The amount of assets to flash loan.
/// @param data Arbitrary data to pass to the `onMorphoFlashLoan` callback.
function morphoFlashLoan(address token, uint256 assets, bytes calldata data) external onlyBundler3 {
require(assets != 0, ErrorsLib.ZeroAmount());
// Morpho's allowance is not reset as it is trusted.
SafeERC20.forceApprove(IERC20(token), address(MORPHO), type(uint256).max);
MORPHO.flashLoan(token, assets, data);
}
/* PERMIT2 ACTIONS */
/// @notice Transfers with Permit2.
/// @param token The address of the ERC20 token to transfer.
/// @param receiver The address that will receive the tokens.
/// @param amount The amount of token to transfer. Pass `type(uint).max` to transfer the initiator's balance.
function permit2TransferFrom(address token, address receiver, uint256 amount) external onlyBundler3 {
require(receiver != address(0), ErrorsLib.ZeroAddress());
address initiator = initiator();
if (amount == type(uint256).max) amount = IERC20(token).balanceOf(initiator);
require(amount != 0, ErrorsLib.ZeroAmount());
Permit2Lib.PERMIT2.transferFrom(initiator, receiver, amount.toUint160(), token);
}
/* TRANSFER ACTIONS */
/// @notice Transfers ERC20 tokens from the initiator.
/// @notice Initiator must have given sufficient allowance to the Adapter to spend their tokens.
/// @param token The address of the ERC20 token to transfer.
/// @param receiver The address that will receive the tokens.
/// @param amount The amount of token to transfer. Pass `type(uint).max` to transfer the initiator's balance.
function erc20TransferFrom(address token, address receiver, uint256 amount) external onlyBundler3 {
require(receiver != address(0), ErrorsLib.ZeroAddress());
address initiator = initiator();
if (amount == type(uint256).max) amount = IERC20(token).balanceOf(initiator);
require(amount != 0, ErrorsLib.ZeroAmount());
SafeERC20.safeTransferFrom(IERC20(token), initiator, receiver, amount);
}
/* WRAPPED NATIVE TOKEN ACTIONS */
/// @notice Wraps native tokens to wNative.
/// @dev Native tokens must have been previously sent to the adapter.
/// @param amount The amount of native token to wrap. Pass `type(uint).max` to wrap the adapter's balance.
/// @param receiver The account receiving the wrapped native tokens.
function wrapNative(uint256 amount, address receiver) external onlyBundler3 {
if (amount == type(uint256).max) amount = address(this).balance;
require(amount != 0, ErrorsLib.ZeroAmount());
WRAPPED_NATIVE.deposit{value: amount}();
if (receiver != address(this)) SafeERC20.safeTransfer(IERC20(address(WRAPPED_NATIVE)), receiver, amount);
}
/// @notice Unwraps wNative tokens to the native token.
/// @dev Wrapped native tokens must have been previously sent to the adapter.
/// @param amount The amount of wrapped native token to unwrap. Pass `type(uint).max` to unwrap the adapter's
/// balance.
/// @param receiver The account receiving the native tokens.
function unwrapNative(uint256 amount, address receiver) external onlyBundler3 {
if (amount == type(uint256).max) amount = WRAPPED_NATIVE.balanceOf(address(this));
require(amount != 0, ErrorsLib.ZeroAmount());
WRAPPED_NATIVE.withdraw(amount);
if (receiver != address(this)) Address.sendValue(payable(receiver), amount);
}
/* INTERNAL FUNCTIONS */
/// @dev Triggers `_multicall` logic during a callback.
function morphoCallback(bytes calldata data) internal {
require(msg.sender == address(MORPHO), ErrorsLib.UnauthorizedSender());
// No need to approve Morpho to pull tokens because it should already be approved max.
reenterBundler3(data);
}
}
"
},
"lib/openzeppelin-contracts/contracts/interfaces/IERC4626.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (interfaces/IERC4626.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../token/ERC20/IERC20.sol";
import {IERC20Metadata} from "../token/ERC20/extensions/IERC20Metadata.sol";
/**
* @dev Interface of the ERC-4626 "Tokenized Vault Standard", as defined in
* https://eips.ethereum.org/EIPS/eip-4626[ERC-4626].
*/
interface IERC4626 is IERC20, IERC20Metadata {
event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares);
event Withdraw(
address indexed sender,
address indexed receiver,
address indexed owner,
uint256 assets,
uint256 shares
);
/**
* @dev Returns the address of the underlying token used for the Vault for accounting, depositing, and withdrawing.
*
* - MUST be an ERC-20 token contract.
* - MUST NOT revert.
*/
function asset() external view returns (address assetTokenAddress);
/**
* @dev Returns the total amount of the underlying asset that is “managed” by Vault.
*
* - SHOULD include any compounding that occurs from yield.
* - MUST be inclusive of any fees that are charged against assets in the Vault.
* - MUST NOT revert.
*/
function totalAssets() external view returns (uint256 totalManagedAssets);
/**
* @dev Returns the amount of shares that the Vault would exchange for the amount of assets provided, in an ideal
* scenario where all the conditions are met.
*
* - MUST NOT be inclusive of any fees that are charged against assets in the Vault.
* - MUST NOT show any variations depending on the caller.
* - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange.
* - MUST NOT revert.
*
* NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the
* “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and
* from.
*/
function convertToShares(uint256 assets) external view returns (uint256 shares);
/**
* @dev Returns the amount of assets that the Vault would exchange for the amount of shares provided, in an ideal
* scenario where all the conditions are met.
*
* - MUST NOT be inclusive of any fees that are charged against assets in the Vault.
* - MUST NOT show any variations depending on the caller.
* - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange.
* - MUST NOT revert.
*
* NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the
* “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and
* from.
*/
function convertToAssets(uint256 shares) external view returns (uint256 assets);
/**
* @dev Returns the maximum amount of the underlying asset that can be deposited into the Vault for the receiver,
* through a deposit call.
*
* - MUST return a limited value if receiver is subject to some deposit limit.
* - MUST return 2 ** 256 - 1 if there is no limit on the
Submitted on: 2025-10-12 16:49:54
Comments
Log in to comment.
No comments yet.