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/FlashLiquidatorV2.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.29;
import {MorphoLendingRouter} from "./routers/MorphoLendingRouter.sol";
import {IYieldStrategy} from "./interfaces/IYieldStrategy.sol";
import {MORPHO, MarketParams, Market, Id} from "./interfaces/Morpho/IMorpho.sol";
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
MorphoLendingRouter constant MORPHO_LENDING_ROUTER = MorphoLendingRouter(0x9a0c630C310030C4602d1A76583a3b16972ecAa0);
contract FlashLiquidatorV2 {
address private owner;
uint256 private constant ORACLE_PRICE_SCALE = 1e36;
uint256 private constant WAD = 1e18;
uint256 private constant VIRTUAL_ASSETS = 1;
uint256 private constant VIRTUAL_SHARES = 1e6;
uint256 private constant LIQUIDATION_CURSOR = 0.3e18;
uint256 private constant MAX_LIQUIDATION_INCENTIVE_FACTOR = 1.15e18;
function mulDivDown(uint256 x, uint256 y, uint256 d) internal pure returns (uint256) {
return (x * y) / d;
}
function wMulDown(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivDown(x, y, WAD);
}
function wDivDown(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivDown(x, WAD, y);
}
function toAssetsDown(uint256 shares, uint256 totalAssets, uint256 totalShares) internal pure returns (uint256) {
return mulDivDown(shares, totalAssets + VIRTUAL_ASSETS, totalShares + VIRTUAL_SHARES);
}
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
constructor() {
owner = msg.sender;
ERC20 usdc = ERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48);
ERC20 weth = ERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);
usdc.approve(address(MORPHO_LENDING_ROUTER), type(uint256).max);
weth.approve(address(MORPHO_LENDING_ROUTER), type(uint256).max);
usdc.approve(address(MORPHO), type(uint256).max);
weth.approve(address(MORPHO), type(uint256).max);
}
modifier onlyOwner() {
require(msg.sender == owner);
_;
}
function approve(address asset) external onlyOwner {
ERC20(asset).approve(address(MORPHO_LENDING_ROUTER), type(uint256).max);
ERC20(asset).approve(address(MORPHO), type(uint256).max);
}
/// @notice Calculate the liquidation incentive factor for a given market
/// @dev Mirrors Morpho's calculation: min(maxFactor, 1 / (1 - cursor × (1 - lltv)))
function calculateLiquidationIncentiveFactor(uint256 lltv) internal pure returns (uint256) {
// liquidationIncentiveFactor = min(MAX, WAD / (WAD - CURSOR × (WAD - lltv)))
uint256 denominator = WAD - wMulDown(LIQUIDATION_CURSOR, WAD - lltv);
uint256 incentiveFactor = wDivDown(WAD, denominator);
return min(MAX_LIQUIDATION_INCENTIVE_FACTOR, incentiveFactor);
}
/// @notice Calculate shares to liquidate to repay all borrow shares
/// @dev Reverses Morpho's calculation, rounding down to avoid reverts
/// @param borrowShares The total borrow shares to repay
/// @param collateralPrice The price of collateral from the vault oracle
/// @param market The Morpho market state
/// @param lltv The loan-to-value ratio for the market
/// @return sharesToLiquidate The amount of collateral shares to seize
function calculateSharesToLiquidate(
uint256 borrowShares,
uint256 collateralPrice,
Market memory market,
uint256 lltv
) internal pure returns (uint256 sharesToLiquidate) {
// Step 1: Convert borrow shares to assets (round down for safety)
uint256 repaidAssets = toAssetsDown(
borrowShares,
market.totalBorrowAssets,
market.totalBorrowShares
);
// Step 2: Calculate liquidation incentive factor
uint256 liquidationIncentiveFactor = calculateLiquidationIncentiveFactor(lltv);
// Step 3: Apply liquidation incentive (multiply by factor)
uint256 seizedAssetsQuoted = wMulDown(repaidAssets, liquidationIncentiveFactor);
// Step 4: Convert from loan token value to collateral shares (round down for safety)
sharesToLiquidate = mulDivDown(seizedAssetsQuoted, ORACLE_PRICE_SCALE, collateralPrice);
}
function flashLiquidate(
address vaultAddress,
address[] memory liquidateAccounts,
uint256[] memory sharesToLiquidate,
bool[] memory isMaxLiquidate,
uint256 assetsToBorrow,
bytes memory redeemData
) external {
IYieldStrategy vault = IYieldStrategy(vaultAddress);
address asset = vault.asset();
bytes memory flashLoanData = abi.encode(
vaultAddress, liquidateAccounts, sharesToLiquidate, isMaxLiquidate, redeemData
);
MORPHO.flashLoan(
asset,
assetsToBorrow,
flashLoanData
);
ERC20 assetToken = ERC20(asset);
uint256 finalBalance = assetToken.balanceOf(address(this));
if (finalBalance > 0) {
assetToken.transfer(owner, finalBalance);
}
}
function onMorphoFlashLoan(
uint256 /* assetsToBorrow */,
bytes memory flashLoanData
) external {
(
address vaultAddress,
address[] memory liquidateAccounts,
uint256[] memory sharesToLiquidate,
bool[] memory isMaxLiquidate,
bytes memory redeemData
) = abi.decode(flashLoanData, (address, address[], uint256[], bool[], bytes));
IYieldStrategy vaultContract = IYieldStrategy(vaultAddress);
// Get market parameters
MarketParams memory marketParams = MORPHO_LENDING_ROUTER.marketParams(vaultAddress);
// Accrue interest to get current state
MORPHO.accrueInterest(marketParams);
// Get market state
Market memory market = MORPHO.market(Id.wrap(keccak256(abi.encode(marketParams))));
for (uint256 i = 0; i < liquidateAccounts.length; i++) {
uint256 sharesToSeize = sharesToLiquidate[i];
// If max liquidate is requested, calculate the shares to liquidate
if (isMaxLiquidate[i]) {
// Get the account's borrow shares
uint256 borrowShares = MORPHO_LENDING_ROUTER.balanceOfBorrowShares(
liquidateAccounts[i],
vaultAddress
);
// Get the collateral price from the vault oracle
uint256 collateralPrice = vaultContract.price(liquidateAccounts[i]);
// Calculate shares to liquidate to repay all borrow shares
sharesToSeize = calculateSharesToLiquidate(
borrowShares,
collateralPrice,
market,
marketParams.lltv
);
}
MORPHO_LENDING_ROUTER.liquidate(liquidateAccounts[i], vaultAddress, sharesToSeize, 0);
}
uint256 sharesToRedeem = vaultContract.balanceOf(address(this));
vaultContract.redeemNative(sharesToRedeem, redeemData);
}
}"
},
"src/routers/MorphoLendingRouter.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.8.29;
import { InsufficientAssetsForRepayment } from "../interfaces/Errors.sol";
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { TokenUtils } from "../utils/TokenUtils.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { IYieldStrategy } from "../interfaces/IYieldStrategy.sol";
import {
IMorphoLiquidateCallback,
IMorphoFlashLoanCallback,
IMorphoRepayCallback
} from "../interfaces/Morpho/IMorphoCallbacks.sol";
import { ADDRESS_REGISTRY } from "../utils/Constants.sol";
import { AbstractLendingRouter } from "./AbstractLendingRouter.sol";
import {
MORPHO, MarketParams, Id, Position, Market, Withdrawal, PUBLIC_ALLOCATOR
} from "../interfaces/Morpho/IMorpho.sol";
struct MorphoParams {
address irm;
uint256 lltv;
}
struct MorphoAllocation {
address vault;
uint256 feeAmount;
Withdrawal[] withdrawals;
}
contract MorphoLendingRouter is
AbstractLendingRouter,
IMorphoLiquidateCallback,
IMorphoFlashLoanCallback,
IMorphoRepayCallback
{
using SafeERC20 for ERC20;
using TokenUtils for ERC20;
mapping(address vault => MorphoParams params) private s_morphoParams;
uint256 private transient t_vaultSharesReceived;
uint256 private transient t_borrowShares;
uint256 private transient t_profitsWithdrawn;
// Used for the health factor calculation to replicate Morpho's behavior
uint256 private constant VIRTUAL_ASSETS = 1;
uint256 private constant VIRTUAL_SHARES = 1e6;
function name() external pure override returns (string memory) {
return "Morpho";
}
function initializeMarket(address vault, address irm, uint256 lltv) external {
require(ADDRESS_REGISTRY.upgradeAdmin() == msg.sender);
// Cannot override parameters once they are set
require(s_morphoParams[vault].irm == address(0));
require(s_morphoParams[vault].lltv == 0);
s_morphoParams[vault] = MorphoParams({ irm: irm, lltv: lltv });
// If the market already exists this call will revert. This is okay because there should
// be no reason that the market would already exist unless something has gone wrong. In that
// case we would want to assess why the market was created and perhaps change the market
// parameters in order to fix the issue.
MORPHO.createMarket(marketParams(vault));
}
function marketParams(address vault) public view returns (MarketParams memory) {
return marketParams(vault, IYieldStrategy(vault).asset());
}
function marketParams(address vault, address asset) internal view returns (MarketParams memory) {
MorphoParams memory params = s_morphoParams[vault];
return MarketParams({
loanToken: asset,
collateralToken: vault,
oracle: vault,
irm: params.irm,
lltv: params.lltv
});
}
function morphoId(MarketParams memory m) internal pure returns (Id) {
return Id.wrap(keccak256(abi.encode(m)));
}
/// @dev Allows integration with the public allocator so that accounts can
/// ensure there is sufficient liquidity in the lending market before entering
function _allocate(address vault, MorphoAllocation[] calldata allocationData) internal {
MarketParams memory m = marketParams(vault);
uint256 totalFeeAmount;
for (uint256 i = 0; i < allocationData.length; i++) {
PUBLIC_ALLOCATOR.reallocateTo{ value: allocationData[i].feeAmount }(
allocationData[i].vault, allocationData[i].withdrawals, m
);
totalFeeAmount += allocationData[i].feeAmount;
}
require(msg.value == totalFeeAmount, "Insufficient fee amount");
}
function allocateAndEnterPosition(
address onBehalf,
address vault,
uint256 depositAssetAmount,
uint256 borrowAmount,
bytes calldata depositData,
MorphoAllocation[] calldata allocationData
)
external
payable
isAuthorized(onBehalf, vault)
nonReentrant
{
_allocate(vault, allocationData);
enterPosition(onBehalf, vault, depositAssetAmount, borrowAmount, depositData);
}
function allocateAndMigratePosition(
address onBehalf,
address vault,
address migrateFrom,
MorphoAllocation[] calldata allocationData
)
external
payable
isAuthorized(onBehalf, vault)
nonReentrant
{
_allocate(vault, allocationData);
migratePosition(onBehalf, vault, migrateFrom);
}
function _flashBorrowAndEnter(
address onBehalf,
address vault,
address asset,
uint256 depositAssetAmount,
uint256 borrowAmount,
bytes memory depositData,
address migrateFrom
)
internal
override
returns (uint256 vaultSharesReceived, uint256 borrowShares)
{
// At this point we will flash borrow funds from the lending market and then
// receive control in a different function on a callback.
bytes memory flashLoanData = abi.encode(onBehalf, vault, asset, depositAssetAmount, depositData, migrateFrom);
MORPHO.flashLoan(asset, borrowAmount, flashLoanData);
// These are only used to get these values back from the flash loan callback
// so that we can emit the event with the correct values
vaultSharesReceived = t_vaultSharesReceived;
borrowShares = t_borrowShares;
}
function onMorphoFlashLoan(uint256 assets, bytes calldata data) external override {
require(msg.sender == address(MORPHO));
(
address onBehalf,
address vault,
address asset,
uint256 depositAssetAmount,
bytes memory depositData,
address migrateFrom
) = abi.decode(data, (address, address, address, uint256, bytes, address));
t_vaultSharesReceived =
_enterOrMigrate(onBehalf, vault, asset, assets + depositAssetAmount, depositData, migrateFrom);
MarketParams memory m = marketParams(vault, asset);
// Borrow the assets in order to repay the flash loan
( /* */ , t_borrowShares) = MORPHO.borrow(m, assets, 0, onBehalf, address(this));
// Allow for flash loan to be repaid
ERC20(asset).checkApprove(address(MORPHO), assets);
}
function _supplyCollateral(
address onBehalf,
address vault,
address asset,
uint256 sharesReceived
)
internal
override
{
MarketParams memory m = marketParams(vault, asset);
// Allows the transfer from the lending market to the Morpho contract
IYieldStrategy(vault).allowTransfer(address(MORPHO), sharesReceived, onBehalf);
// We should receive shares in return
ERC20(vault).approve(address(MORPHO), sharesReceived);
MORPHO.supplyCollateral(m, sharesReceived, onBehalf, "");
}
function _withdrawCollateral(
address vault,
address asset,
uint256 sharesToRedeem,
address sharesOwner,
address receiver
)
internal
override
{
MarketParams memory m = marketParams(vault, asset);
MORPHO.withdrawCollateral(m, sharesToRedeem, sharesOwner, receiver);
}
function _exitWithRepay(
address onBehalf,
address vault,
address asset,
address receiver,
uint256 sharesToRedeem,
uint256 assetToRepay,
bytes memory redeemData
)
internal
override
returns (uint256 borrowSharesRepaid, uint256 profitsWithdrawn)
{
uint256 sharesToRepay;
if (assetToRepay == type(uint256).max) {
// If assetToRepay is uint256.max then get the morpho borrow shares amount to
// get a full exit.
sharesToRepay = balanceOfBorrowShares(onBehalf, vault);
assetToRepay = 0;
}
if (assetToRepay == 0 && sharesToRepay == 0) {
// Allows migration in the edge case where the user has no debt but
// still wants to migrate their position.
profitsWithdrawn = _redeemShares(
onBehalf, vault, asset, _isMigrate(receiver) ? receiver : address(0), sharesToRedeem, redeemData
);
} else {
bytes memory repayData =
abi.encode(onBehalf, vault, asset, receiver, sharesToRedeem, redeemData, _isMigrate(receiver));
// Will trigger a callback to onMorphoRepay
borrowSharesRepaid = _repay(vault, asset, assetToRepay, sharesToRepay, onBehalf, repayData);
profitsWithdrawn = t_profitsWithdrawn;
}
}
function _repay(
address vault,
address asset,
uint256 assetToRepay,
uint256 sharesToRepay,
address onBehalf,
bytes memory repayData
)
internal
returns (uint256 borrowSharesRepaid)
{
MarketParams memory m = marketParams(vault, asset);
( /* */ , borrowSharesRepaid) = MORPHO.repay(m, assetToRepay, sharesToRepay, onBehalf, repayData);
}
function onMorphoRepay(uint256 assetToRepay, bytes calldata data) external override {
require(msg.sender == address(MORPHO));
(
address sharesOwner,
address vault,
address asset,
address receiver,
uint256 sharesToRedeem,
bytes memory redeemData,
bool isMigrate
) = abi.decode(data, (address, address, address, address, uint256, bytes, bool));
uint256 assetsWithdrawn =
_redeemShares(sharesOwner, vault, asset, isMigrate ? receiver : address(0), sharesToRedeem, redeemData);
if (isMigrate) {
// When migrating we do not withdraw any assets and we must repay the entire debt
// from the previous lending router.
if (0 < assetToRepay) ERC20(asset).safeTransferFrom(receiver, address(this), assetToRepay);
assetsWithdrawn = assetToRepay;
}
// Transfer any profits to the receiver
if (assetsWithdrawn < assetToRepay) {
// We have to revert in this case because we've already redeemed the yield tokens
revert InsufficientAssetsForRepayment(assetToRepay, assetsWithdrawn);
}
uint256 profitsWithdrawn;
unchecked {
profitsWithdrawn = assetsWithdrawn - assetToRepay;
}
if (0 < profitsWithdrawn) ERC20(asset).safeTransfer(receiver, profitsWithdrawn);
// Allow morpho to repay the debt
ERC20(asset).checkApprove(address(MORPHO), assetToRepay);
// Set the transient variable to be used for later event emission
t_profitsWithdrawn = profitsWithdrawn;
}
function _liquidate(
address liquidator,
address vault,
address liquidateAccount,
uint256 sharesToLiquidate,
uint256 borrowSharesToRepay
)
internal
override
returns (uint256 sharesToLiquidator, uint256 borrowSharesRepaid)
{
MarketParams memory m = marketParams(vault);
uint256 borrowSharesBefore = balanceOfBorrowShares(liquidateAccount, vault);
// If the account's borrow shares are less than when the liquidator is trying to repay,
// set it to the account's borrow shares to prevent an underflow inside Morpho.
if (borrowSharesBefore < borrowSharesToRepay) borrowSharesToRepay = borrowSharesBefore;
// This does not return borrow shares repaid so we have to calculate it manually
(sharesToLiquidator, /* */ ) = MORPHO.liquidate(
m, liquidateAccount, sharesToLiquidate, borrowSharesToRepay, abi.encode(m.loanToken, liquidator)
);
borrowSharesRepaid = borrowSharesBefore - balanceOfBorrowShares(liquidateAccount, vault);
}
function onMorphoLiquidate(uint256 repaidAssets, bytes calldata data) external override {
require(msg.sender == address(MORPHO));
(address asset, address liquidator) = abi.decode(data, (address, address));
ERC20(asset).safeTransferFrom(liquidator, address(this), repaidAssets);
ERC20(asset).checkApprove(address(MORPHO), repaidAssets);
}
function balanceOfCollateral(
address account,
address vault
)
public
view
override
returns (uint256 collateralBalance)
{
MarketParams memory m = marketParams(vault);
collateralBalance = MORPHO.position(morphoId(m), account).collateral;
}
function balanceOfBorrowShares(
address account,
address vault
)
public
view
override
returns (uint256 borrowShares)
{
MarketParams memory m = marketParams(vault);
borrowShares = MORPHO.position(morphoId(m), account).borrowShares;
}
function convertBorrowSharesToAssets(
address vault,
uint256 borrowShares
)
external
override
returns (uint256 assets)
{
MarketParams memory m = marketParams(vault);
MORPHO.accrueInterest(m);
Market memory market = MORPHO.market(morphoId(m));
return (borrowShares * uint256(market.totalBorrowAssets)) / uint256(market.totalBorrowShares);
}
function _mulDivUp(uint256 x, uint256 y, uint256 d) internal pure returns (uint256) {
return (x * y + (d - 1)) / d;
}
function _toAssetsUp(
uint256 shares,
uint256 totalShares,
uint256 totalAssets
)
internal
pure
returns (uint256 assets)
{
return _mulDivUp(shares, totalAssets + VIRTUAL_ASSETS, totalShares + VIRTUAL_SHARES);
}
function healthFactor(
address borrower,
address vault
)
public
override
returns (uint256 borrowed, uint256 collateralValue, uint256 maxBorrow)
{
MarketParams memory m = marketParams(vault);
Id id = morphoId(m);
// Ensure interest is accrued before calculating health factor
MORPHO.accrueInterest(m);
Position memory position = MORPHO.position(id, borrower);
Market memory market = MORPHO.market(id);
if (position.borrowShares > 0) {
borrowed = _toAssetsUp(position.borrowShares, market.totalBorrowShares, market.totalBorrowAssets);
} else {
borrowed = 0;
}
collateralValue = (uint256(position.collateral) * IYieldStrategy(vault).price(borrower)) / 1e36;
maxBorrow = collateralValue * m.lltv / 1e18;
}
}
"
},
"src/interfaces/IYieldStrategy.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.8.29;
import { IERC20 } from "@openzeppelin/contracts/interfaces/IERC20.sol";
import { IERC20Metadata } from "@openzeppelin/contracts/interfaces/IERC20Metadata.sol";
import { IOracle } from "./Morpho/IOracle.sol";
/**
* @notice A strategy vault that is specifically designed for leveraged yield
* strategies. Minting and burning shares are restricted to the `enterPosition`
* and `exitPosition` functions respectively. This means that shares will be
* exclusively held on lending markets as collateral unless the LendingMarket is
* set to NONE. In this case, the user will just be holding the yield token without
* any leverage.
*
* The `transfer` function is non-standard in that transfers off of a lending market
* are restricted to ensure that liquidation conditions are met.
*
* This contract also serves as its own oracle.
*/
interface IYieldStrategy is IERC20, IERC20Metadata, IOracle {
event VaultCreated(address indexed vault);
// These can be emitted by the reward manager
event VaultRewardTransfer(address indexed token, address indexed account, uint256 amount);
event VaultRewardUpdate(address indexed rewardToken, uint128 emissionRatePerYear, uint32 endTime);
// This is emitted by the trading module
event TradeExecuted(address indexed sellToken, address indexed buyToken, uint256 sellAmount, uint256 buyAmount);
event FeesCollected(uint256 feesCollected);
/**
* @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 address of the accounting asset used for the
* to mark the price of the yield token excluding any market profit and loss.
* This is only used for off chain accounting.
*/
function accountingAsset() external view returns (address accountingAssetAddress);
/**
* @dev Returns the name of the strategy.
*/
function strategy() external view returns (string memory strategyName);
/**
* @dev Returns the address of the yield token held by the vault. Does not equal the share token,
* which represents each user's share of the yield tokens held by the vault.
*
* - MUST be an ERC-20 token contract.
* - MUST NOT revert.
*/
function yieldToken() external view returns (address yieldTokenAddress);
/**
* @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 effective supply which excludes any escrowed shares.
*/
function effectiveSupply() external view returns (uint256);
/**
* @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.
*/
function convertToAssets(uint256 shares) external view returns (uint256 assets);
/**
* @dev Returns the amount of yield tokens that the Vault would exchange for the amount of shares provided, in an
* ideal
* scenario where all the conditions are met.
*/
function convertSharesToYieldToken(uint256 shares) external view returns (uint256 yieldTokens);
/**
* @dev Returns the amount of yield tokens that the account would receive for the amount of shares provided.
*/
function convertYieldTokenToShares(uint256 shares) external view returns (uint256 yieldTokens);
/**
* @dev Returns the oracle price of a yield token in terms of the asset token.
*/
function convertYieldTokenToAsset() external view returns (uint256 price);
/**
* @dev Returns the fee rate of the vault where 100% = 1e18.
*/
function feeRate() external view returns (uint256 feeRate);
/**
* @dev Returns the balance of yield tokens accrued by the vault.
*/
function feesAccrued() external view returns (uint256 feesAccruedInYieldToken);
/**
* @dev Collects the fees accrued by the vault. Only callable by the owner.
*/
function collectFees() external returns (uint256 feesCollected);
/**
* @dev Returns the price of a yield token in terms of the asset token for the
* given borrower taking into account withdrawals.
*/
function price(address borrower) external returns (uint256 price);
/**
* @notice Mints shares for a given number of assets.
*
* @param assets The amount of assets to mint shares for.
* @param receiver The address to mint the shares to.
* @param depositData calldata used to deposit the assets.
*/
function mintShares(
uint256 assets,
address receiver,
bytes memory depositData
)
external
returns (uint256 sharesMinted);
/**
* @notice Burns shares for a given number of shares.
*
* @param sharesOwner The address of the account to burn the shares for.
* @param sharesToBurn The amount of shares to burn.
* @param redeemData calldata used to redeem the yield token.
*/
function burnShares(
address sharesOwner,
uint256 sharesToBurn,
uint256 sharesHeld,
bytes memory redeemData
)
external
returns (uint256 assetsWithdrawn);
/**
* @notice Allows the lending market to transfer shares on exit position
* or liquidation.
*
* @param to The address to allow the transfer to.
* @param amount The amount of shares to allow the transfer of.
* @param currentAccount The address of the current account.
*/
function allowTransfer(address to, uint256 amount, address currentAccount) external;
/**
* @notice Pre-liquidation function.
*
* @param liquidator The address of the liquidator.
* @param liquidateAccount The address of the account to liquidate.
* @param sharesToLiquidate The amount of shares to liquidate.
* @param accountSharesHeld The amount of shares the account holds.
*/
function preLiquidation(
address liquidator,
address liquidateAccount,
uint256 sharesToLiquidate,
uint256 accountSharesHeld
)
external;
/**
* @notice Post-liquidation function.
*
* @param liquidator The address of the liquidator.
* @param liquidateAccount The address of the account to liquidate.
* @param sharesToLiquidator The amount of shares to liquidate.
*/
function postLiquidation(address liquidator, address liquidateAccount, uint256 sharesToLiquidator) external;
/**
* @notice Redeems shares for assets for a native token.
*
* @param sharesToRedeem The amount of shares to redeem.
* @param redeemData calldata used to redeem the yield token.
*/
function redeemNative(uint256 sharesToRedeem, bytes memory redeemData) external returns (uint256 assetsWithdrawn);
/**
* @notice Initiates a withdraw for a given number of shares.
*
* @param account The address of the account to initiate the withdraw for.
* @param sharesHeld The number of shares the account holds.
* @param data calldata used to initiate the withdraw.
*/
function initiateWithdraw(
address account,
uint256 sharesHeld,
bytes calldata data,
address forceWithdrawFrom
)
external
returns (uint256 requestId);
/**
* @notice Initiates a withdraw for the native balance of the account.
*
* @param data calldata used to initiate the withdraw.
*/
function initiateWithdrawNative(bytes calldata data) external returns (uint256 requestId);
/**
* @notice Clears the current account.
*/
function clearCurrentAccount() external;
}
"
},
"src/interfaces/Morpho/IMorpho.sol": {
"content": "// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.8.28;
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 chains sharing the
/// same chain id and on forks 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 assumptions on the market's dependencies which, if broken, could break Morpho's liveness
/// properties (funds could get stuck):
/// - The token should not revert on `transfer` and `transferFrom` if balances and approvals are right.
/// - The amount of assets supplied and borrowed should not go above ~1e35 (otherwise the computation of
/// `toSharesUp` and `toSharesDown` can overflow).
/// - The IRM should not revert on `borrowRate`.
/// - The IRM should not return a very high borrow rate (otherwise the computation of `interest` in
/// `_accrueInterest` can overflow).
/// - The oracle should not revert `price`.
/// - The oracle should not return a very high price (otherwise the computation of `maxBorrow` in `_isHealthy` or of
/// `assetsRepaid` in `liquidate` can 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
/// `onMorphoRepay` 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);
}
IMorpho constant MORPHO = IMorpho(0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb);
struct Withdrawal {
/// @notice The market from which to withdraw.
MarketParams marketParams;
/// @notice The amount to withdraw.
uint128 amount;
}
/// @dev This interface is used for factorizing IPublicAllocatorStaticTyping and IPublicAllocator.
/// @dev Consider using the IPublicAllocator interface instead of this one.
interface IPublicAllocator {
/// @notice Reallocates from a list of markets to one market.
/// @param vault The MetaMorpho vault to reallocate.
/// @param withdrawals The markets to withdraw from,and the amounts to withdraw.
/// @param supplyMarketParams The market receiving total withdrawn to.
/// @dev Will call MetaMorpho's `reallocate`.
/// @dev Checks that the flow caps are respected.
/// @dev Will revert when `withdrawals` contains a duplicate or is not sorted.
/// @dev Will revert if `withdrawals` contains the supply market.
/// @dev Will revert if a withdrawal amount is larger than available liquidity.
function reallocateTo(
address vault,
Withdrawal[] calldata withdrawals,
MarketParams calldata supplyMarketParams
)
external
payable;
}
IPublicAllocator constant PUBLIC_ALLOCATOR = IPublicAllocator(0xfd32fA2ca22c76dD6E550706Ad913FC6CE91c75D);
"
},
"node_modules/@openzeppelin/contracts/token/ERC20/ERC20.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC20/ERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "./IERC20.sol";
import {IERC20Metadata} from "./extensions/IERC20Metadata.sol";
import {Context} from "../../utils/Context.sol";
import {IERC20Errors} from "../../interfaces/draft-IERC6093.sol";
/**
* @dev Implementation of the {IERC20} interface.
*
* This implementation is agnostic to the way tokens are created. This means
* that a supply mechanism has to be added in a derived contract using {_mint}.
*
* TIP: For a detailed writeup see our guide
* https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How
* to implement supply mechanisms].
*
* The default value of {decimals} is 18. To change this, you should override
* this function so it returns a different value.
*
* We have followed general OpenZeppelin Contracts guidelines: functions revert
* instead returning `false` on failure. This behavior is nonetheless
* conventional and does not conflict with the expectations of ERC-20
* applications.
*/
abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors {
mapping(address account => uint256) private _balances;
mapping(address account => mapping(address spender => uint256)) private _allowances;
uint256 private _totalSupply;
string private _name;
string private _symbol;
/**
* @dev Sets the values for {name} and {symbol}.
*
* Both values are immutable: they can only be set once during construction.
*/
constructor(string memory name_, string memory symbol_) {
_name = name_;
_symbol = symbol_;
}
/**
* @dev Returns the name of the token.
*/
function name() public view virtual returns (string memory) {
return _name;
}
/**
* @dev Returns the symbol of the token, usually a shorter version of the
* name.
*/
function symbol() public view virtual returns (string memory) {
return _symbol;
}
/**
* @dev Returns the number of decimals used to get its user representation.
* For example, if `decimals` equals `2`, a balance of `505` tokens should
* be displayed to a user as `5.05` (`505 / 10 ** 2`).
*
* Tokens usually opt for a value of 18, imitating the relationship between
* Ether and Wei. This is the default value returned by this function, unless
* it's overridden.
*
* NOTE: This information is only used for _display_ purposes: it in
* no way affects any of the arithmetic of the contract, including
* {IERC20-balanceOf} and {IERC20-transfer}.
*/
function decimals() public view virtual returns (uint8) {
return 18;
}
/// @inheritdoc IERC20
function totalSupply() public view virtual returns (uint256) {
return _totalSupply;
}
/// @inheritdoc IERC20
function balanceOf(address account) public view virtual returns (uint256) {
return _balances[account];
}
/**
* @dev See {IERC20-transfer}.
*
* Requirements:
*
* - `to` cannot be the zero address.
* - the caller must have a balance of at least `value`.
*/
function transfer(address to, uint256 value) public virtual returns (bool) {
address owner = _msgSender();
_transfer(owner, to, value);
return true;
}
/// @inheritdoc IERC20
function allowance(address owner, address spender) public view virtual returns (uint256) {
return _allowances[owner][spender];
}
/**
* @dev See {IERC20-approve}.
*
* NOTE: If `value` is the maximum `uint256`, the allowance is not updated on
* `transferFrom`. This is semantically equivalent to an infinite approval.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/
function approve(address spender, uint256 value) public virtual returns (bool) {
address owner = _msgSender();
_approve(owner, spender, value);
return true;
}
/**
* @dev See {IERC20-transferFrom}.
*
* Skips emitting an {Approval} event indicating an allowance update. This is not
* required by the ERC. See {xref-ERC20-_approve-address-address-uint256-bool-}[_approve].
*
* NOTE: Does not update the allowance if the current allowance
* is the maximum `uint256`.
*
* Requirements:
*
* - `from` and `to` cannot be the zero address.
* - `from` must have a balance of at least `value`.
* - the caller must have allowance for ``from``'s tokens of at least
* `value`.
*/
function transferFrom(address from, address to, uint256 value) public virtual returns (bool) {
address spender = _msgSender();
_spendAllowance(from, spender, value);
_transfer(from, to, value);
return true;
}
/**
* @dev Moves a `value` amount of tokens from `from` to `to`.
*
* This internal function is equivalent to {transfer}, and can be used to
* e.g. implement automatic token fees, slashing mechanisms, etc.
*
* Emits a {Transfer} event.
*
* NOTE: This function is not virtual, {_update} should be overridden instead.
*/
function _transfer(address from, address to, uint256 value) internal {
if (from == address(0)) {
revert ERC20InvalidSender(address(0));
}
if (to == address(0)) {
revert ERC20InvalidReceiver(address(0));
}
_update(from, to, value);
}
/**
* @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from`
* (or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding
* this function.
*
* Emits a {Transfer} event.
*/
function _update(address from, address to, uint256 value) internal virtual {
if (from == address(0)) {
// Overflow check required: The rest of the code assumes that totalSupply never overflows
_totalSupply += value;
} else {
uint256 fromBalance = _balances[from];
if (fromBalance < value) {
revert ERC20InsufficientBalance(from, fromBalance, value);
}
unchecked {
// Overflow not possible: value <= fromBalance <= totalSupply.
_balances[from] = fromBalance - value;
}
}
if (to == address(0)) {
unchecked {
// Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply.
_totalSupply -= value;
}
} else {
unchecked {
// Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256.
_balances[to] += value;
}
}
emit Transfer(from, to, value);
}
/**
* @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0).
* Relies on the `_update` mechanism
*
* Emits a {Transfer} event with `from` set to the zero address.
*
* NOTE: This function is not virtual, {_update} should be overridden instead.
*/
function _mint(address account, uint256 value) internal {
if (account == address(0)) {
revert ERC20InvalidReceiver(address(0));
}
_update(address(0), account, value);
}
/**
* @dev Destroys a `value` amount of tokens from `account`, lowering the total supply.
* Relies on the `_update` mechanism.
*
* Emits a {Transfer} event with `to` set to the zero address.
*
* NOTE: This function is not virtual, {_update} should be overridden instead
*/
function _burn(address account, uint256 value) internal {
if (account == address(0)) {
revert ERC20InvalidSender(address(0));
}
_update(account, address(0), value);
}
/**
* @dev Sets `value` as the allowance of `spender` over the `owner`'s tokens.
*
* This internal function is equivalent to `approve`, and can be used to
* e.g. set automatic allowances for certain subsystems, etc.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `owner` cannot be the zero address.
* - `spender` cannot be the zero address.
*
* Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument.
*/
function _approve(address owner, address spender, uint256 value) internal {
_approve(owner, spender, value, true);
}
/**
* @dev Variant of {_approve} with an optional flag to enable or disable the {Approval} event.
*
* By default (when calling {_approve}) the flag is set to true. On the other hand, approval changes made by
* `_spendAllowance` during the `transferFrom` operation set the flag to false. This saves gas by not emitting any
* `Approval` event during `transferFrom` operations.
*
* Anyone who wishes to continue emitting `Approval` events on the`transferFrom` operation can force the flag to
* true using the following override:
*
* ```solidity
* function _approve(address owner, address spender, uint256 value, bool) internal virtual override {
* super._approve(owner, spender, value, true);
* }
* ```
*
* Requirements are the same as {_approve}.
*/
function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual {
if (owner == address(0)) {
revert ERC20InvalidApprover(address(0));
}
if (spender == address(0)) {
revert ERC20InvalidSpender(address(0));
}
_allowances[owner][spender] = value;
if (emitEvent) {
emit Approval(owner, spender, value);
}
}
/**
* @dev Updates `owner`'s allowance for `spender` based on spent `value`.
*
* Does not update the allowance value in case of infinite allowance.
* Revert if not enough allowance is available.
*
* Does not emit an {Approval} event.
*/
function _spendAllowance(address owner, address spender, uint256 value) internal virtual {
uint256 currentAllowance = allowance(owner, spender);
if (currentAllowance < type(uint256).max) {
if (currentAllowance < value) {
revert ERC20InsufficientAllowance(spender, currentAllowance, value);
}
unchecked {
_approve(owner, spender, currentAllowance - value, false);
}
}
}
}
"
},
"src/interfaces/Errors.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.8.28;
error NotAuthorized(address operator, address user);
error Unauthorized(address caller);
error UnauthorizedLendingMarketTransfer(address from, address to, uint256 value);
error InsufficientYieldTokenBala
Submitted on: 2025-10-31 16:27:48
Comments
Log in to comment.
No comments yet.